Visitar URL original
Auto-chunk large wheels in add_wheels() / install_uploaded_wheels() by ElePT · Pull Request #2667 · Qiskit/qiskit-serverless · GitHub
Skip to content

Auto-chunk large wheels in add_wheels() / install_uploaded_wheels() - #2667

Draft
Elena Peña Tapia (ElePT) wants to merge 2 commits into
mainfrom
feature/auto-chunk-large-wheels
Draft

Elena Peña Tapia (ElePT) wants to merge 2 commits into
mainfrom
feature/auto-chunk-large-wheels

Conversation

@ElePT

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #2652 (bring your own wheels). A wheel larger than the gateway's per-request upload limit (MAX_REQUEST_BODY_SIZE_MB, default 50MB) is refused with a 413, so users have had to hand-split large wheels (e.g. CUDA torch-*+cu121*.whl) and rejoin them in the entrypoint. This makes that automatic and verified, almost entirely client-side.

What changes

  • add_wheels() splits any wheel over WHEEL_CHUNK_THRESHOLD_MB (default 40, under the 50 limit; override with WHEEL_CHUNK_THRESHOLD_MB_OVERRIDE) into ordered parts named <wheel>.whl.part-0001-of-000N, uploads each as its own request, and writes a combined .wheel-chunks-manifest.json (per-wheel sha256 + ordered part list) into the wheels/ dir. Smaller wheels upload whole exactly as before.
  • install_uploaded_wheels() reassembles each chunked wheel into a temp dir (the mount is read-only), verifies it against the manifest sha256, and only then pip installs it alongside any whole wheels. A missing part or checksum mismatch raises rather than installing a corrupt wheel. With no manifest present, behavior is byte-for-byte unchanged.
  • Transparent API: add_wheels() returns logical wheel names; list_wheels() hides parts + manifest and surfaces chunked wheels by their logical name; remove_wheels() deletes a chunked wheel's parts and prunes the manifest. Re-uploading a wheel clears its stale parts first so a smaller rebuild can't leave orphans.

Supporting change: sub_path on download

Reading the manifest back from wheels/ needs download-by-sub_path, which #2652 did not add (it added sub_path to upload/list/delete only). This threads sub_path through the whole download path — client file_download / GatewayFilesClient.download, the download view + use-case, and get_public_file_stream in both storage backends — making download symmetric with the others. This is the only gateway change.

Part naming & manifest

Parts: <wheel>.whl.part-0001-of-000N (only -/digits/. added; a part never ends in .whl, so the runtime's existing wheel scan skips it). Manifest: .wheel-chunks-manifest.json, {"schema":1,"chunked_wheels":[{wheel, sha256, size, part_size, part_count, parts:[…]}]}; the runtime rejects an unknown schema loudly.

Caveats (documented in the guide)

  • Chunk uploads rely on the deployment accepting application/octet-stream (parts) and application/json (manifest), which the default config does; a hardened allowlist could reject them.
  • Reassembly uses transient /tmp space during function start-up (bounded, cleaned up after install).
  • sha256 is over the whole reassembled wheel (per-part digests could be added later under a schema bump).

Testing

  • Client runtime (test_wheels.py): reassemble+verify, sha mismatch raises before pip, missing part raises, mixed whole+chunked, unknown schema rejected, workdir cleaned up, lone part ignored — plus the existing no-manifest tests as a regression guard.
  • Client API (test_serverless_client_functions.py): small→whole/no-manifest, at-threshold→whole, large→N parts + manifest with round-trip + sha256 check, re-upload cleans stale parts, partial-failure cleanup, list_wheels hides parts/manifest and shows logical names, remove_wheels deletes parts + prunes manifest.
  • Gateway storage (test_file_storage.py): get_public_file_stream(sub_path="wheels") round trip.
  • Ran locally (client venv): client suites green (108 passing across the touched files); black clean; pylint 10/10 on the client source. The gateway Django suite and gateway lint could not run in my environment — please confirm CI (tox -e py312, lint) before un-drafting.

Checklist

  • I have added tests to cover my changes.
  • I have updated the documentation accordingly.
  • I have added a release note.

🤖 Generated with Claude Code

A wheel larger than the gateway's per-request upload limit was refused with a
413, so users had to split and rejoin big wheels (e.g. CUDA torch) by hand. Make
that automatic and verified, client-side.

add_wheels() now splits any wheel over WHEEL_CHUNK_THRESHOLD_MB (default 40,
overridable via WHEEL_CHUNK_THRESHOLD_MB_OVERRIDE) into ordered parts named
<wheel>.whl.part-0001-of-000N, uploads each as its own request, and records a
combined .wheel-chunks-manifest.json (per-wheel sha256 + ordered part list) in
the wheels/ dir. install_uploaded_wheels() reassembles each chunked wheel into a
temp dir, verifies it against the manifest sha256, and only then pip-installs it
alongside any whole wheels; a missing part or checksum mismatch raises instead of
installing a corrupt wheel. Behavior with no manifest is unchanged.

The feature is transparent: add_wheels() returns logical wheel names, list_wheels()
hides parts and the manifest and surfaces chunked wheels by their logical name, and
remove_wheels() deletes a chunked wheel's parts and prunes the manifest. Re-uploading
a wheel clears its stale parts first so a smaller rebuild cannot leave orphans.

Also threads sub_path through the files download endpoint (client file_download,
GatewayFilesClient.download, the download view/use-case, and get_public_file_stream
in both storage backends), so the client can read the manifest back from wheels/ and
downloads are symmetric with upload/list/delete. The chunk threshold lives in
client constants mirroring MAX_ARTIFACT_FILE_SIZE_MB.

Adds client runtime + add_wheels/list_wheels/remove_wheels tests, a storage
get_public_file_stream(sub_path=) test, a release note, and documents the behavior
and its caveats (MIME allowlist, transient /tmp use) in the BYO-wheels guide.
CI's client lint (mypy) caught two issues the local run missed:

- _download_chunk_manifest called file_download with the wrong argument order
  (download_location and function were swapped into each other's positional slots).
  This was a real bug that the tests hid by mocking _download_chunk_manifest
  wholesale. Call it with keyword arguments and add two tests that exercise the
  real method (argument order + sub_path, and the no-manifest path).
- _reassemble_one used iter(lambda: ..., b"") to stream blocks, which mypy cannot
  type. Replace with a plain while loop.
@ElePT
Elena Peña Tapia (ElePT) marked this pull request as ready for review November 4, 2026 10:41
@ElePT
Elena Peña Tapia (ElePT) requested a review from a team as a code owner November 4, 2026 10:41
@Tansito
David (Tansito) marked this pull request as draft November 4, 2026 23:13

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant