Skip to content

[ROCm] Add ROCm/HIP wheel build, CI, and release parity with CUDA (#3171) - #3184

Open
andyluo7 wants to merge 8 commits into
kvcache-ai:mainfrom
andyluo7:rocm-ci-parity
Open

[ROCm] Add ROCm/HIP wheel build, CI, and release parity with CUDA (#3171)#3184
andyluo7 wants to merge 8 commits into
kvcache-ai:mainfrom
andyluo7:rocm-ci-parity

Conversation

@andyluo7

Copy link
Copy Markdown
Contributor

Summary

Closes #3171 (ROCm CI parity with CUDA CI).

Mooncake already supports AMD GPUs in the transfer engine (USE_HIP, hip_transport/, dmabuf MR registration from #2225), but there was no CI to build/test the ROCm wheel and no published mooncake-transfer-engine-rocm package. This adds that, mirroring the existing CUDA/MUSA workflows.

The transfer engine is torch-independent and has no device kernels, so the ROCm wheel is GPU-arch-agnostic (one wheel covers gfx942 and gfx950), and the ROCm runtime (libamdhip64/libhsa-runtime64) is already in build_wheel.sh's auditwheel exclude list and bound at load time. As a result the same wheel works in both the upstream vllm/vllm-openai-rocm and lmsysorg/sglang ROCm images.

Changes

  • scripts/build_wheel.sh — add HIP_BUILD variant → package mooncake-transfer-engine-rocm (ROCm runtime libs were already excluded by auditwheel).
  • .github/workflows/ci_rocm.yml — PR build check in the ROCm dev image (rocm/dev-ubuntu-22.04:7.2.3-complete), py3.10 + py3.12, mirroring ci_cu13.yml. No GPU needed to compile HIP.
  • .github/workflows/release-rocm.yaml — tag-triggered build + publish, mirroring the self-contained container build in release-musa.yaml; py3.10–3.13; publishes to PyPI (needs a PYPI_* token / pypi environment, same as other variants).
  • ci.yml — wire build-wheel-rocm into the CI graph and the CI Gate.

Validation (on real AMD hardware)

Built the wheel in rocm/dev-ubuntu-22.04:7.2.3-complete, installed it into the latest upstream images, and ran mooncake_master --version + mooncake-wheel/tests/test_transfer_on_hip.py (registers HIP buffers, loopback sync read+write with data verification):

GPU Image Python Result
MI300X gfx942 (Hotaisle) vllm/vllm-openai-rocm:v0.26.0 cp312 ✅ master + transfer test (2/2)
MI300X gfx942 (Hotaisle) lmsysorg/sglang:v0.5.16-rocm720-mi30x cp310 ✅ master + transfer test (2/2)
MI355X gfx950 (Crusoe) vllm/vllm-openai-rocm:v0.26.0 cp312 ✅ master + transfer test (2/2)
MI355X gfx950 (Crusoe) lmsysorg/sglang:v0.5.16-rocm720-mi35x cp310 ✅ master + transfer test (2/2)

ldd confirms engine.so binds libamdhip64.so.7 / libhsa-runtime64.so.1 to the host image's /opt/rocm at runtime (nothing vendored, no torch dependency).

Environment note (not a wheel/CI issue)

On the Crusoe MI355X node, when the container is given the ionic RoCE NICs, test_transfer_on_hip.py exercises RDMA GPU-VRAM registration. The dmabuf path is taken correctly (hsa_amd_portable_export_dmabuf succeeds), but ibv_reg_dmabuf_mr on the ionic NIC returns EFAULT — a node-level ionic kernel-driver limitation registering GPU dmabuf MRs (the same NIC family works on other clusters). This is independent of this PR (same wheel, same test); the transfer engine functions correctly via the local transport, and RDMA works wherever the NIC/driver supports GPU dmabuf MR registration.

🤖 Generated with Claude Code

Closes the ROCm CI parity gap (kvcache-ai#3171). Mooncake already supports AMD GPUs
in the transfer engine (USE_HIP, hip_transport, dmabuf MR registration from
kvcache-ai#2225), but there was no CI to build/test the wheel and no published
mooncake-transfer-engine-rocm package.

The transfer engine is torch-independent and has no device kernels, so the
ROCm wheel is GPU-arch-agnostic (one wheel covers gfx942 and gfx950) and the
ROCm runtime (libamdhip64/libhsa-runtime64) is auditwheel-excluded and bound
at load time -- so the same wheel works in both the upstream
vllm/vllm-openai-rocm and lmsysorg/sglang ROCm images.

- scripts/build_wheel.sh: add HIP_BUILD variant -> mooncake-transfer-engine-rocm
  (ROCm runtime libs were already in the auditwheel exclude list).
- .github/workflows/ci_rocm.yml: PR build check in the ROCm dev image
  (py3.10 + py3.12), mirroring ci_cu13.yml. No GPU needed to compile.
- .github/workflows/release-rocm.yaml: tag-triggered build+publish, mirroring
  release-musa.yaml (self-contained container build), py3.10-3.13.
- ci.yml: wire build-wheel-rocm into the CI graph and CI Gate.

Validated on real hardware (build in rocm/dev-ubuntu-22.04:7.2.3-complete,
installed into the target images):
- MI300X gfx942 (Hotaisle): SGLang v0.5.16-rocm720-mi30x (cp310) and
  vLLM v0.26.0 (cp312) -- mooncake_master --version + tests/test_transfer_on_hip.py
  (2/2 sync read+write loopback transfers) pass on-device.
- MI355X gfx950 (Crusoe): same images (mi35x variants), same tests.

Signed-off-by: andyluo7 <andy.luo@amd.com>
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

andyluo7 added 4 commits July 29, 2026 10:17
The rocm/dev-ubuntu image ships without git, so actions/checkout fell back
to a REST-API source tarball (no .git), and dependencies.sh's
`git submodule sync` failed with "not a git repository". Install git before
the checkout step and fetch submodules in checkout, matching how the
git-provisioned CUDA/MUSA containers behave.

Signed-off-by: andyluo7 <andy.luo@amd.com>
Setting PATH through GITHUB_ENV is not honored reliably across steps, so
cmake's USE_HIP configure could not find hipify-perl. Use GITHUB_PATH and
also export PATH=/opt/rocm/bin in the configure/build/wheel steps.

Signed-off-by: andyluo7 <andy.luo@amd.com>
The image's system python3.10 exists but ships without the venv module, so
the wheel smoke test's `python3.10 -m venv` failed ("ensurepip is not
available"). Install python{ver}-venv/-dev unconditionally instead of only
when the interpreter is missing (3.12 came from deadsnakes with venv, which
is why only 3.10 failed).

Signed-off-by: andyluo7 <andy.luo@amd.com>
The etcd-wrapper build fetches Go modules from proxy.golang.org, which can
fail with transient stream errors and stop the build. Retry cmake --build
up to 3 times; Go caches already-fetched modules so retries resume.

Signed-off-by: andyluo7 <andy.luo@amd.com>

@Aionw Aionw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found one release-blocking issue in the historical-tag backfill path.

- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.event_name == 'workflow_dispatch' && format('refs/tags/{0}', inputs.tag) || github.ref }}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Preserve the ROCm package rename when backfilling old tags

For workflow_dispatch, this checks out the historical tag before running scripts/build_wheel.sh. Tags predating this PR—including the documented example v0.3.12—do not recognize HIP_BUILD and have no ROCm package-name branch, so HIP_BUILD=1 is ignored and the script produces mooncake-transfer-engine instead of mooncake-transfer-engine-rocm. That mislabeled ROCm build then proceeds to the existing GitHub Release and PyPI upload steps. Please apply the ROCm metadata rename independently of the tagged script, or reject tags that do not contain HIP_BUILD support.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in e1d9bcd. Two guards, so a mislabeled ROCm build can never reach the GitHub Release / PyPI upload:

  1. Reject unsupported tags — after checkout, the job fails if the checked-out scripts/build_wheel.sh has no HIP_BUILD branch, with a clear message to use a tag at/after ROCm support landed.
  2. Assert the package name (defense in depth) — after build_wheel.sh, the job fails unless the produced wheel is mooncake_transfer_engine_rocm-*.whl, so an ignored HIP_BUILD (from any cause) can't proceed to publish.

Note: the same backfill-with-variant pattern exists in release-musa.yaml and release-npu.yaml (a pre-ROCm tag would similarly mislabel there) — happy to file a follow-up to add the same guard to those, keeping this PR scoped to ROCm.

A workflow_dispatch backfill checks out a historical tag and runs its
scripts/build_wheel.sh. Tags predating ROCm support have no HIP_BUILD branch,
so HIP_BUILD=1 is ignored and the script emits the default
"mooncake-transfer-engine" package, which the publish steps would then upload
over the CUDA package on PyPI.

- Reject any checked-out tree whose scripts/build_wheel.sh lacks HIP_BUILD.
- Defense in depth: assert the built wheel is mooncake-transfer-engine-rocm
  before the upload/publish steps run.

Signed-off-by: andyluo7 <andy.luo@amd.com>

@Aionw Aionw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One follow-up issue remains in the manual historical-tag backfill path.

# scripts/build_wheel.sh would ignore HIP_BUILD and emit the default
# "mooncake-transfer-engine" package, which the publish steps would
# then upload over the CUDA package on PyPI. Refuse such tags.
if ! grep -q 'HIP_BUILD' scripts/build_wheel.sh; then

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Keep the advertised v0.3.12 backfill usable

This guard safely prevents a mislabeled wheel from being published, but it also makes the documented workflow_dispatch example impossible: HIP_BUILD is introduced by this PR, so v0.3.12—and every tag that exists when this PR merges—will fail here. As a result, manual dispatch cannot backfill the initial ROCm package; it can only rerun future tags that already contain this change. If historical backfill is intended, please apply the ROCm package metadata rename in the workflow for legacy tags and keep the post-build filename assertion. Otherwise, update the input description to remove the v0.3.12 example and state explicitly that only tags containing HIP_BUILD are supported.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went with option (b) in 2dfa5a1 — the guard stays (it correctly prevents a mislabeled publish), and I've updated the workflow_dispatch input so it no longer advertises the pre-ROCm v0.3.12 example. It now reads "Release tag to (re)build the ROCm wheel for (must contain HIP_BUILD support)", with a comment noting older tags are rejected by design.

Backfilling the initial ROCm package onto a tag whose source predates ROCm support isn't meaningful (it'd build ROCm from a tree that never had it), so manual dispatch is intentionally limited to tags at/after this change. The first ROCm release will come from the normal v* tag push once this merges.

@Aionw

Aionw commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

This workflow publishes mooncake-transfer-engine-rocm using OIDC and does not pass an API token, so the existing PYPI_API_TOKEN/other package credentials do not authorize this new distribution. The ROCm project is not publicly present on PyPI yet, and a pending publisher is private, so its configuration cannot be verified from this PR.

Before merging/enabling the workflow, please confirm that PyPI has a pending Trusted Publisher configured with the exact identity kvcache-ai/Mooncake, workflow release-rocm.yaml, environment pypi, for project mooncake-transfer-engine-rocm. Otherwise the job will upload wheels to the GitHub Release first and then fail during PyPI publishing, leaving a partially published release. It would also be helpful to document this external prerequisite or add a protected/manual release gate.

Per review follow-ups (Aionw):
- workflow_dispatch input no longer advertises the pre-ROCm v0.3.12 example;
  it states only tags containing HIP_BUILD support can be built (older tags
  are rejected by the guard, so backfilling the initial ROCm package is not
  possible by design).
- Publish to PyPI before attaching wheels to the GitHub Release so a PyPI
  auth/config failure fails cleanly without leaving a dangling release.
- Document the one-time PyPI Trusted Publisher prerequisite for the new
  mooncake-transfer-engine-rocm project (OIDC, no API token).

Signed-off-by: andyluo7 <andy.luo@amd.com>
@andyluo7

Copy link
Copy Markdown
Contributor Author

Re: PyPI Trusted Publisher — good catch, addressed in 2dfa5a1:

  • Reordered publish steps so PyPI publishing runs before attaching wheels to the GitHub Release. Now a missing/misconfigured Trusted Publisher fails the job cleanly without leaving a dangling GitHub Release.
  • Documented the prerequisite inline in the workflow: PyPI must have a Trusted Publisher configured for the new project mooncake-transfer-engine-rocm with identity kvcache-ai/Mooncake, workflow release-rocm.yaml, environment pypi (OIDC, no API token).

That configuration is a one-time maintainer/PyPI-owner action I can't perform or verify from the PR side — @maintainers, please set up the pending Trusted Publisher for mooncake-transfer-engine-rocm before enabling/merging. The environment: pypi on the publish job can also serve as a manual release gate if you add required reviewers to that environment. (The build/CI jobs don't touch PyPI, so this only gates the actual release step.)

# the NEW project `mooncake-transfer-engine-rocm` with identity
# owner/repo `kvcache-ai/Mooncake`, workflow `release-rocm.yaml`, and
# environment `pypi`. Without it the publish step fails.
- name: Publish package to PyPI

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Keep the release recoverable after PyPI succeeds

Moving the PyPI upload first avoids leaving GitHub Release assets behind when authentication fails, but both uploads are still in the same job. If PyPI succeeds and softprops/action-gh-release then fails transiently, rerunning the failed job starts from the PyPI step, which rejects the already-uploaded filenames, so the workflow can no longer reach the GitHub Release upload.

Please split PyPI publishing and GitHub Release attachment into separate jobs, with the latter depending on the former. Then “re-run failed jobs” can retry only the GitHub Release job without republishing immutable PyPI files.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in e164c96 — split into two jobs: publish-pypi and publish-github-release (the latter needs: publish-pypi). Now a transient action-gh-release failure can be re-run on its own via "re-run failed jobs" without restarting the immutable PyPI upload.

exit 1
fi

- name: Upload Python wheel artifact

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Smoke-test the exact release wheels before publishing

The ROCm CI smoke test covers only Python 3.10 and 3.12 at the PR head, while this workflow builds and immediately publishes Python 3.10–3.13 wheels from the release tag. The release job currently checks only the filename, so packaging or ELF-layout failures in the exact release artifacts—especially cp311/cp313—can reach PyPI undetected.

Please install each matrix wheel into a fresh venv and run at least mooncake_master --version before uploading the artifact, matching the smoke test already used in ci_rocm.yml.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in e164c96 — added a "Smoke test the exact release wheel" step in the build matrix that installs each per-version artifact (cp310–cp313) into a fresh venv and runs mooncake_master --version before the wheel is uploaded, matching ci_rocm.yml. This runs on the release tag's actual artifacts, so cp311/cp313 packaging/ELF issues are caught before PyPI. (mooncake_master --version needs no GPU.)

Per review follow-ups (Aionw):
- Smoke-test the exact per-matrix release artifact (cp310-cp313): install each
  built wheel into a fresh venv and run `mooncake_master --version` before
  upload, so packaging/ELF failures (esp. cp311/cp313, not covered by the
  PR-head ci_rocm smoke test) can't reach PyPI. No GPU needed.
- Split publishing into `publish-pypi` and `publish-github-release` (the latter
  needs the former). PyPI uploads are immutable, so a combined job that failed
  on the GitHub Release step after a successful PyPI upload could never be
  re-run; separate jobs let the release upload be retried on its own.

Signed-off-by: andyluo7 <andy.luo@amd.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature Request]: ROCm CI parity with CUDA CI

3 participants