Repository navigation
fix(linux): ship the CPU torchvision, gate the bundle, repair existing installs (#778) - #823
Merged
Merged
Conversation
packaging/torch-versions.json holds the torch line the Windows and Linux packages bake and the torchvision built for each torch line. Until now the Linux script, the Windows script and the desktop shell each carried their own copy, and the Linux script never named torchvision at all (#778). scripts/check_torch_bundle.py runs with the packaged interpreter and fails when torchvision, its nms op or onnx2torch does not load, when torchvision is not the version paired with the installed torch, when it is a CUDA build beside a torch that is not, when --expect-cpu is given and either is not a +cpu build, or when a compiled torchvision file links, or torchvision.libs ships, a CUDA library. Standard library only.
The CPU swap force-reinstalled torch and torchaudio with --no-deps and left torchvision where the project install put it. That is PyPI's default Linux wheel, the cu124 build, so the CPU package shipped torchvision 0.21.0+cu124 beside torch 2.6.0+cpu. Its _C.so and image.so need libcudart, libc10_cuda, libtorch_cuda and libnvjpeg, none of which ship, so import torchvision raised "operator torchvision::nms does not exist" and the karaoke split failed. The swap moves to scripts/linux/bake-cpu-torch.sh, names torchvision, reads the versions from packaging/torch-versions.json (TORCH_VERSION still overrides, and the matching torchvision is looked up for it) and runs the torch bundle gate. make-portable.sh runs the gate again on the stripped tree. Both variants bake CPU torch, so both expect +cpu.
… bundle PyPI's Windows torchvision 0.21.0 is already a CPU build (1,560,520 bytes, against 1,555,044 for +cpu and 6,140,979 for +cu124 on the PyTorch index), so the Windows packages were not broken. The CPU package now names torchvision in its swap anyway, from packaging/torch-versions.json, and both packages run the torch bundle gate so the property is checked rather than assumed.
Nothing in CI ran the swap, which is how #778 shipped. The new job installs the project the way make-portable.sh does, checks it reproduced the CUDA torchvision beside CPU torch and that the gate rejects it, then runs bake-cpu-torch.sh, which must pass the gate. CPU torch is seeded first so no CUDA torch is downloaded. Runs when the scripts, the versions file, pyproject.toml or uv.lock change.
The in-app updater replaces the executable and backend/, never python/, and uv.lock is unchanged, so an install that updates from 0.20.0 keeps the broken torchvision whatever the new package ships. repair_torchvision runs on every launch on Windows and Linux, from both the fast path and the full setup path (after the GPU step, before warmup). It reads torch/version.py and torchvision/version.py; only when torch is +cpu and torchvision is +cuXXX does it force-reinstall the matching torchvision+cpu from the CPU index and check it imports. A healthy install, an NVIDIA install on CUDA torch, an interrupted CUDA swap and an unknown torch line are left alone. A failed repair (offline) is logged and shown for a few seconds, never blocks the backend, and is tried again next launch. The decision is pure and runs on every host: version_from_version_py, installed_version, torchvision_repair_spec, plus cuda_torch_specs, which install_cuda_torch now uses. CPU_TORCH_VERSION, CPU_TORCHVISION_VERSION and torchvision_version_for_tag are gated any(not(macos), test) so the tests that pin them to packaging/torch-versions.json and uv.lock run on macOS too. Closes #778
thcp
marked this pull request as ready for review
October 21, 2026 09:37
thcp
added a commit
that referenced
this pull request
Oct 22, 2026
…g-release (#836) Closes #835 ## What changed - **PR to `main`, push to `main`, release:** full suite, as before. - **PR to any other base:** smoke + changed specs + mapped specs. A hub module (former `full` source entries: `main.js`, `state.js`, `mixer.js`, `catalog.js`, `index.html`, `app/api/jobs.py`, `stems.py`, `app/core/**`, ...) or an unmapped file under `static/` or `app/` now adds a fixed **core set** (17 specs) instead of everything. `uv.lock` and `pyproject.toml` add core too. - **Full suite on a non-main PR** only for a change to how the suite runs: `playwright.config.mjs`, `tests/e2e/helpers*`, `serve.sh`, `seed.py`, `package*.json`, the three e2e workflows, the selector, `spec-map.json`, or unmapped code under `tests/e2e/`. - **New `e2e-full.yml`:** full suite on every push to `big-release`, nightly (04:17 UTC) on `big-release`, and `workflow_dispatch` with a `ref` input. - **New reusable `e2e.yml`** (`workflow_call`, inputs `mode`, `base-ref`, `checkout-ref`) holds the steps once. Same pinned SHAs, `permissions: {}`, no secrets. - **Concurrency:** a new push to a PR cancels that PR's older e2e run. Pushes, releases, nightly and manual runs are never cancelled. `timeout-minutes: 30` per shard. ## What did not change - Application code, specs, `uv.lock`, package files. Three shards, `--pass-with-no-tests`. - Check names gain a prefix: `frontend-e2e / frontend-e2e (1)` to `(3)`. A reusable workflow always prefixes the caller job. No branch protection or ruleset requires these names today (checked). ## Real diffs, before and after | Diff | Files | Before | After | |---|---|---|---| | #806 (queue.js, catalog.js) | 5 | FULL 105 | 30 (core), includes queue-progress | | #820 | 7 | FULL 105 | 59 (core) | | #821 | 2 | 47 | 47 | | #823 | 8 | 5 | 5 | | #828 (uv.lock) | 4 | 5 | 22 (core) | | #819, #830, #831 | 7 to 19 | FULL 105 | FULL 105 (edit `spec-map.json`) | | #829 | 19 | FULL 105 | FULL 105 (edits `helpers.mjs`) | | #822 | 33 | FULL 105 | FULL 105 (edits `seed.py`) | | `main...big-release` | 331 | FULL | FULL (PR to main) | `npx playwright test --list <selected>` matches the selector exactly for #806 (30 files, 200 tests) and #820 (59 files, 465 tests). Full suite: 724 tests in 105 files. Core + smoke alone: 159 tests in 22 files. ## Trade-off A non-main PR can pass while a spec outside its core and mapped sets breaks. The push to `big-release` run catches it on the merge commit, minutes later. ## Limits - **The nightly schedule only starts once `e2e-full.yml` reaches `main`.** GitHub runs scheduled workflows from the default branch, and the job checks out `big-release` explicitly for that reason. - Neither concurrency nor the timeout helps a hosted runner GitHub never assigns (queue time does not count toward the timeout). Fix by hand: re-run the job, or `gh run cancel <id>` then `gh run rerun <id> --failed`. - This PR changes `ci.yml`, so its own CI runs the full suite. Expected.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #778
What changed
torchvision==0.21.0+cpubeside torch and torchaudio (newscripts/linux/bake-cpu-torch.sh, called bymake-portable.sh). Both Linux variants bake CPU torch, so both get the CPU torchvision.packaging/torch-versions.json(CPU torch line, torchvision per torch line). Read by the Linux and Windows scripts. A Rust test fails ifCPU_TORCH_VERSION,CPU_TORCHVISION_VERSIONor the CUDA pairs drift from it, and another fails ifuv.lockstops resolving that pair. An overriddenTORCH_VERSIONlooks up its torchvision in the same file.scripts/check_torch_bundle.pyfails the build if torchvision, its nms op or onnx2torch does not load, if the pair does not match, if torchvision is a CUDA build beside non-CUDA torch, if a CPU package is not+cpu, or if a compiled torchvision file links (readelf on Linux) or ships a CUDA library. It runs after the swap and again on the stripped tree, on Linux and Windows.+cpuand 6,140,979 for+cu124). The CPU package now names torchvision in its swap anyway, and both packages run the gate.torch-bundle.ymlon ubuntu-latest. It installs the project the waymake-portable.shdoes, checks it reproduced the cu124 torchvision beside CPU torch and that the gate rejects it, then runsbake-cpu-torch.sh, which must pass the gate. CPU torch is seeded first, so no CUDA wheels are downloaded.How existing installs get repaired
repair_torchvision, called bysetup.json every Windows/Linux launch (fast path and full setup, after the GPU step, before warmup).torch/version.pyandtorchvision/version.py. Onlytorch +cpuwithtorchvision +cuXXXtriggerspip install torchvision==<pair>+cpu --force-reinstall --no-depsfrom the CPU index, then an import check.What did not change
uv.lockandpyproject.toml: unchanged, so the runtime id is unchanged and the in-app update is still offered.Risks
cfg(not(target_os = "macos"))(the command body,verify_imports) only compiles in the Linux and Windows Rust CI jobs..pydimport names), an NVIDIA Linux first run, or a broken 0.20.0 install being repaired.