Skip to content

fix(docker): build native binaries for every arch and un-pin the base image - #1

Open
maple52046 wants to merge 1 commit into
mainfrom
fix/multi-arch-binaries-and-portable-base
Open

fix(docker): build native binaries for every arch and un-pin the base image#1
maple52046 wants to merge 1 commit into
mainfrom
fix/multi-arch-binaries-and-portable-base

Conversation

@maple52046

Copy link
Copy Markdown
Owner

Why

Built as-is, the image is tied to a single GPU architecture and to one specific torch base. Three of those are correctness problems that are independent of which base you use.

The three generic fixes

hip-stream was built with no offload arch at all. vendor/babelstream-hip/HIP.make never passes --offload-arch, so the target is left to hipcc's detection — and there is no GPU to detect during docker build. It does append EXTRA_FLAGS, so the archs now go in as one repeated flag each.

p2pBandwidthLatencyTest was deliberately limited to the first arch. On a host running any other arch from PYTORCH_ROCM_ARCH the binary just does not run. The previous comment attributed this to the Makefile interpolating GPU_TARGETS into a single --offload-arch, which is true, but the actual failure with a ;-separated value is that the shell splits the command in two:

hipcc ... --offload-arch=gfx942;gfx950 -I/opt/rocm/include ... -o p2pBandwidthLatencyTest p2p...cpp
ld.lld: error: undefined symbol: main

Everything after ; becomes a second command, so hipcc gets one arch and no source file — hence the confusing undefined symbol: main. The Makefile appends CPPFLAGS verbatim, so passing the first arch through GPU_TARGETS and the remaining ones as extra --offload-arch flags yields a genuine fat binary with no Makefile or source change.

ENV PYTHONPATH="/opt/aiter:${PYTHONPATH}" leaves a trailing colon when the base image does not set PYTHONPATH. An empty PYTHONPATH entry means "current directory", so any directory containing e.g. ./cmake shadows the installed module. Now only appended when there is something to append: /opt/aiter on a base without it, /opt/aiter:<inherited> on a base with it (both verified).

Net effect: all three native binaries cover every arch in PYTORCH_ROCM_ARCH, so one image runs on gfx942 and gfx950 and nothing is compiled at run time.

Base image becomes an ARG

BASE_IMAGE now defaults to the combination this was verified against: ROCm 7.2.4 / Python 3.13 / PyTorch 2.12.0 (rocm/ali-private:ubuntu22.04_rocm7.2.4_cp313_torch2.12.0_20260726). Labels and ROCM_VERSION were updated to match that default.

The two base-specific steps are made self-checking rather than deleted, so the file still works on the previous base:

  • apt remove -y amdgpu-install-internal now runs only when that package is actually installed. It is legitimate for bases that carry the internal repo, but apt remove on a missing package exits non-zero and fails the build on bases that do not.
  • cmake is reinstalled only when the base image's cmake is broken. Some torch bases install the pip cmake package under one interpreter while /usr/local/bin/cmake runs another, so from cmake import cmake resolves to the /opt/rocm/lib/cmake namespace directory and raises cannot import name 'cmake' from 'cmake'. TransferBench and rccl-tests both need cmake.

hipBLASLt client packages

The GEMM benchmarks need hipblaslt-bench, and the public radeon repo does not publish hipblaslt-benchmarks — it only ever existed in the offline bundle. The .debs are still bind-mounted from the build context, but now from a pkgs/ subdirectory so that COPY . /opt/benchtestkit does not drag them into the image. Defaults point at the 1.2.2 client packages built from the matching rocm-7.2.4 tag; both are ARGs.

vLLM

Unchanged — still intentionally absent, per this repo's existing rationale. paged_attention uses the aiter backend; the commented-out opt-in block is untouched. This is now also stated in the image description label.

Verification

Built and run on an MI355X (gfx950) host from the new default base.

benchtestkit check-env in the resulting image:

[ok] mpirun: /opt/mpich/bin/mpirun
[ok] python3: /usr/bin/python3
[ok] hip-stream: /usr/local/bin/hip-stream
[ok] p2pBandwidthLatencyTest: /usr/local/bin/p2pBandwidthLatencyTest
[ok] TransferBench: /usr/local/bin/TransferBench
[ok] hipblaslt-bench: /opt/rocm-7.2.4/bin/hipblaslt-bench

roc-obj-ls on each native binary reports gfx942 gfx950 — all three are fat binaries.

Real workloads on the gfx950 host:

  • hipblaslt-bench -m 1024 -n 1024 -k 1024 → 55924 Gflops (f16)
  • hip-stream → Triad 3031719 MB/s (~3.0 TB/s)
  • import aiter succeeds and JIT-compiles module_aiter_core in 9.0s; is_flydsl_available() is True

The framework stack is untouched by this image: torch 2.12.0+rocm7.2.4.gitcf5ea6e, triton 3.7.1, numpy 2.5.1, flash_attn 2.8.4 — identical to the base image. The only Python package added is pydantic 2.13.4 (benchtestkit's own dependency) plus its transitive deps; nothing pulls or downgrades torch or numpy.

Note for reviewers

BuildKit emits an UndefinedVar: '$PYTHONPATH' lint warning on bases that do not set the variable. That is exactly the case ${var:+...} exists to handle. Do not silence it by declaring ARG PYTHONPATH — an ARG shadows the inherited ENV and the append is lost (verified).

… image

The image was effectively tied to one GPU architecture and to one specific torch
base. Three of those are correctness problems independent of any particular base:

- hip-stream passed no --offload-arch at all, leaving the target to hipcc's
  detection -- and there is no GPU to detect during `docker build`.
- p2pBandwidthLatencyTest was deliberately built for the first arch only, so on a
  host running any other arch in PYTORCH_ROCM_ARCH the binary simply does not run.
  Its Makefile interpolates GPU_TARGETS into a single --offload-arch and an
  unquoted ';' value is additionally split by the shell into a second command,
  which surfaces as "ld.lld: error: undefined symbol: main". Passing the first arch
  through GPU_TARGETS and the rest as extra --offload-arch flags via the Makefile's
  CPPFLAGS passthrough produces a real fat binary with no Makefile or source edit.
- ENV PYTHONPATH="/opt/aiter:${PYTHONPATH}" leaves a trailing colon when the base
  does not set PYTHONPATH. An empty entry means "current directory", so any
  directory containing e.g. ./cmake shadows the installed module.

All three native binaries now cover every arch in PYTORCH_ROCM_ARCH, so one image
runs on gfx942 and gfx950 and nothing is compiled at run time.

The base image becomes an ARG, defaulting to the ROCm 7.2.4 / Python 3.13 /
PyTorch 2.12.0 combination this was verified against, and the two base-specific
steps are made self-checking so the file still works on other bases:

- the amdgpu-install-internal removal now runs only when that package is actually
  installed (`apt remove` on a missing package exits non-zero and fails the build);
- cmake is reinstalled only when the base image's cmake is broken.

The hipBLASLt client .debs move to a pkgs/ subdirectory of the build context so
`COPY . /opt/benchtestkit` no longer drags them into the image.

Co-authored-by: Cursor <cursoragent@cursor.com>
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