Add script to inject libnuma into published ROCm torch wheels - #8706
Open
atalman wants to merge 2 commits into
Open
Add script to inject libnuma into published ROCm torch wheels#8706atalman wants to merge 2 commits into
atalman wants to merge 2 commits into
Conversation
Hotfix path for pytorch/pytorch#195670. rocSHMEM's NUMAWrapper global ctor does `dlopen("libnuma.so")`, and `torch/lib` is on `libtorch_rocshmem.so`'s RPATH via `$ORIGIN`, so a bundled copy there satisfies it. The AlmaLinux manywheel builder had no numactl installed, so `repair_wheel.py`'s `rocm_os_deps()` skipped libnuma silently and the published ROCm 7.14 wheels ship without it. Every `import torch` prints E-001h rocSHMEM Could not open libnuma. Returning NUMAWrapper@...:48 and per pytorch/pytorch#189110 the failed dlopen can make rocSHMEM `exit()` at load, deadlocking `import torch` on hosts with no GPU or kfd. pytorch/pytorch#195672 fixes the builder for future builds. This repairs wheels that are already published, without a rebuild or a cherry-pick. Follows `repackage_torchaudio_cu130_to_cu132.py`: discover from the channel index, download, modify, regenerate RECORD, upload back to S3 and R2 with `x-amz-meta-checksum-sha256` so the PEP 503 index picks the new hash up. Both `libnuma.so.1` and a bare `libnuma.so` are written as real files. A correct build makes the second a symlink, but `wheel unpack`/`pack` does not round-trip symlinks and the two are equivalent as far as `dlopen` is concerned. Wheels that already contain libnuma are skipped, so re-running is safe. libnuma is not vendored in this repo; `--libnuma` takes a path and the docstring gives the `docker run almalinux:8` one-liner to extract one matching the builder's glibc, so the manylinux_2_28 tag stays honest. ## ZIP64 This uses `wheel unpack`/`wheel pack` as requested. `wheel pack` is known to emit an invalid ZIP64 header above 4GB (pypa/wheel#692), which is why pytorch's own `.ci/manywheel/repair_wheel.py` switched to auditwheel -- pytorch#189748 tracked ROCm wheels that installed under pip but failed under uv. ROCm wheels are ~1.4GB compressed and well over 4GB unpacked, so this is squarely in the affected range. Rather than leave that latent, `verify_zip64()` inspects every repacked archive before upload: it requires the ZIP64 end-of-central-directory locator and record to exist when they are needed, the locator's offset to match where the record actually is, and the ZIP64 central directory not to overrun the archive. If a wheel trips it, the run aborts before uploading and the message says to repack that one with auditwheel. So the known failure mode surfaces locally instead of reaching users. Test Plan: Built a synthetic `torch-2.14.0+rocm7.14-cp312-cp312-manylinux_2_28_x86_64.whl` containing `torch/lib/libtorch_rocshmem.so` and a valid RECORD, plus a real x86-64 libnuma, and exercised the module directly: - `wheel unpack` -> both libs added -> `wheel pack`; output filename preserved. - RECORD regenerated with correct hashes and sizes for both new files: torch/lib/libnuma.so,sha256=ScpOG8SgwQOYmqyXm3qpMPlhUXEFqSUBPYITRJDf0dQ,62064 torch/lib/libnuma.so.1,sha256=ScpOG8SgwQOYmqyXm3qpMPlhUXEFqSUBPYITRJDf0dQ,62064 - Pre-existing members preserved (`libtorch_rocshmem.so` still present). - `verify_wheel()` passes: both members present, decompress cleanly, listed in RECORD, ZIP64 records consistent. - Idempotency: `has_libnuma()` true afterwards, so a second run skips. - Input validation: a non-x86-64 ELF is rejected ("junk.so is not x86-64"), as is anything without the `libnuma.so.1` SONAME string. Not yet run against real 1.4GB wheels -- that is the exercise that will actually test the ZIP64 path, and `--dry-run` covers it without uploading.
|
@atalman is attempting to deploy a commit to the Meta Open Source Team on Vercel. A member of the Team first needs to authorize it. |
`wheel pack` emits an invalid ZIP64 header above 4GB (pypa/wheel#692). ROCm wheels are ~1.4GB compressed and well over 4GB unpacked, and pytorch#189748 tracked exactly that failure -- wheels that installed under pip but not under uv. pytorch's own `.ci/manywheel/repair_wheel.py` uses auditwheel for the same reason, as does `repackage_torchaudio_cu130_to_cu132.py` in this directory. Only `InWheelCtx` is used: unpack, edit the tree, repack, regenerate RECORD. The repair path is never invoked, so no patchelf, no RPATH rewriting, no library bundling, no platform retagging. Confirmed by inspection -- `InWheelCtx`'s source contains zero references to patchelf, repair or lib bundling, and one to RECORD. No new dependency surface: auditwheel needs only `packaging` and `pyelftools`, both pure Python. The patchelf *binary* is a requirement of `auditwheel repair`, which this does not call. Module-level imports are stdlib only; auditwheel and boto3 are imported at their call sites, as in the torchaudio script. This also drops the `subprocess` and `struct` imports the wheel-CLI version needed. Added `verify_unchanged()`, which enforces the claim rather than asserting it: the repacked wheel must differ from the original by exactly the two added members, with every other member's CRC and size identical. RECORD is exempt since it legitimately lists the new files, and zip directory entries are skipped because the repack writes explicit ones the original may lack. Dropped `verify_zip64()`; its whole purpose was guarding the `wheel pack` bug. Test Plan: Re-ran against the synthetic `torch-2.14.0+rocm7.14-cp312-cp312-manylinux_2_28_x86_64.whl`: - inject -> both libs added -> repack; filename preserved - RECORD regenerated with correct hashes and sizes: torch/lib/libnuma.so,sha256=ScpOG8SgwQOYmqyXm3qpMPlhUXEFqSUBPYITRJDf0dQ,62064 torch/lib/libnuma.so.1,sha256=ScpOG8SgwQOYmqyXm3qpMPlhUXEFqSUBPYITRJDf0dQ,62064 - `verify_unchanged`: "only 2 members added, nothing else changed" - `libtorch_rocshmem.so` preserved; second run skips - guard rejects an injected extra member: "repack added unexpected members: ['torch/lib/libsneaky.so']" - guard rejects a modified existing member: "torch/version.py changed during repack (crc/size ...)" Two bugs found while testing, both fixed here: - `InWheelCtx` chdirs into its unpack directory, so a relative `out_wheel` resolved against the wrong cwd and repacking died with FileNotFoundError. Both paths are now resolved before entering the context. - The first `verify_unchanged` was too strict and flagged the repack's own directory entries (`torch/`, `torch/lib/`, `*.dist-info/`) as unexpected additions. Still not exercised against real 1.4GB wheels; `--dry-run` covers that without uploading.
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.
Hotfix path for pytorch/pytorch#195670, so the already-published ROCm 7.14 wheels can be repaired without a rebuild or a cherry-pick.
Why
rocSHMEM's NUMAWrapper global ctor does
dlopen("libnuma.so"), andtorch/libis onlibtorch_rocshmem.so's RPATH via$ORIGIN, so a bundled copy there satisfies it. The AlmaLinux manywheel builder had no numactl installed, sorepair_wheel.py'srocm_os_deps()skipped libnuma silently and the published wheels ship without it. Everyimport torchprints:Per pytorch/pytorch#189110 the same failed dlopen can make rocSHMEM
exit()at load, tripping a rocprofiler-sdk atexit deadlock that hangsimport torchon hosts with no GPU or kfd — so this is not purely cosmetic.pytorch/pytorch#195672 fixes the builder for future builds.
What it does
Follows
repackage_torchaudio_cu130_to_cu132.py: discover from the channel index, download, modify, regenerate RECORD, upload back to S3 and R2 withx-amz-meta-checksum-sha256so the PEP 503 index picks up the new hash.Both
libnuma.so.1and a barelibnuma.soare written as real files. A correct build makes the second a symlink, but the repack doesn't round-trip symlinks and the two are equivalent todlopen. Wheels that already contain libnuma are skipped, so re-running is safe.libnuma isn't vendored here —
--libnumatakes a path, and the docstring gives adocker run almalinux:8one-liner to extract one matching the builder's glibc so themanylinux_2_28tag stays honest.Repacking: auditwheel, and no new dependency surface
wheel packemits an invalid ZIP64 header above 4GB (pypa/wheel#692), and ROCm wheels are ~1.4GB compressed / >4GB unpacked. pytorch#189748 tracked exactly that: wheels installing under pip but failing under uv. So this uses auditwheel, matching.ci/manywheel/repair_wheel.pyand the torchaudio script here.Only
InWheelCtxis used — unpack, edit, repack, regenerate RECORD. The repair path is never invoked, so no patchelf, no RPATH rewriting, no library bundling, no platform retagging. Verified by inspection:InWheelCtx's source has zero references to patchelf, repair or lib bundling, and one to RECORD.Dependencies: auditwheel needs only
packagingandpyelftools, both pure Python. The patchelf binary is a requirement ofauditwheel repair, which this doesn't call. Module-level imports are stdlib only; auditwheel and boto3 are imported at their call sites.And rather than just assert that,
verify_unchanged()enforces it: the repacked wheel must differ from the original by exactly the two added members, with every other member's CRC and size identical. RECORD is exempt (it legitimately lists the new files) and zip directory entries are skipped (the repack writes explicit ones the original may lack).Test plan
Built a synthetic
torch-2.14.0+rocm7.14-cp312-cp312-manylinux_2_28_x86_64.whlcontainingtorch/lib/libtorch_rocshmem.soand a valid RECORD, plus a real x86-64 libnuma:verify_unchanged: "only 2 members added, nothing else changed"libtorch_rocshmem.sopreserved; second run skips (idempotent)repack added unexpected members: ['torch/lib/libsneaky.so']torch/version.py changed during repack (crc/size ...)libnuma.so.1SONAME stringTwo bugs found while testing, both fixed:
InWheelCtxchdirs into its unpack directory, so a relativeout_wheelresolved against the wrong cwd and repacking died withFileNotFoundError. Both paths are now resolved before entering the context.verify_unchangedwas too strict and flagged the repack's own directory entries as unexpected additions.Not yet run against real 1.4GB wheels —
--dry-runcovers that without uploading. Each wheel unpacks in full, so budget ~6GB of scratch per wheel and pointTMPDIRat real storage rather than a tmpfs.