diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2a47f262f..504d85d38 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -25,10 +25,10 @@ permissions: env: PRIMUS_TURBO_COMMIT: edc8d2ccb0be4888e80ee7c6e765fd3956026a32 # feat: add quantized tensor support for blockwise grouped gemm (#422) PRIMUS_TURBO_AITER_COMMIT: 0f3c58e6edb6754940bcf9fd5f09ccb6f389f52e # AITER v0.1.14.post1 (tag commit) — required by Primus-Turbo main aiter_utils.py - ROCSHMEM_COMMIT: 17ff985c026f9f97f85068647e863ab541dd5645 # Update version to 3.2.0 for 7.2.0 rocm release (#351) (#355) + #ROCSHMEM_COMMIT: 17ff985c026f9f97f85068647e863ab541dd5645 # Update version to 3.2.0 for 7.2.0 rocm release (#351) (#355) UCCL_COMMIT: 5afb4117893c58cc0c8557d9286336141a301053 # [EP]: fix fp8 error of internode_ll on amd gfx950 arch. (#710) - TRITON_COMMIT: 88b227e23f0445f3f695bad05bbf1a363b4f50e0 - BASE_IMAGE: docker.io/rocm/primus:v26.3 + TRITON_COMMIT: 09500db9f0fe66fd176d1f080e2017b37e7e995d # pinned triton-lang/triton commit built from source in Dockerfile + BASE_IMAGE: docker.io/rocm/primus:v26.4 MAXTEXT_BASE_IMAGE: docker.io/rocm/jax-training:maxtext-v26.2 jobs: @@ -123,16 +123,20 @@ jobs: echo "> Build Docker Image with tag: ${{ env.IMAGE_TAG }}" echo "> Build dependencies" start_time=$(date +%s) + # Optional build-args, off by default (kept as an array so a commented-out + # element here can't break the docker build \-continuation below). + EXTRA_BUILD_ARGS=() + # EXTRA_BUILD_ARGS+=(--build-arg ROCSHMEM_COMMIT=${ROCSHMEM_COMMIT}) docker build -f $CWS/.github/workflows/docker/Dockerfile \ --network=host \ -t tasimage/primus:${{env.IMAGE_TAG}} \ --build-arg BASE_IMAGE=${BASE_IMAGE} \ --build-arg PRIMUS_TURBO_COMMIT=${PRIMUS_TURBO_COMMIT} \ --build-arg PRIMUS_TURBO_AITER_COMMIT=${PRIMUS_TURBO_AITER_COMMIT} \ - --build-arg ROCSHMEM_COMMIT=${ROCSHMEM_COMMIT} \ --build-arg PRIMUS_TURBO_FRAMEWORK=PYTORCH \ --build-arg UCCL_COMMIT=${UCCL_COMMIT} \ --build-arg TRITON_COMMIT=${TRITON_COMMIT} \ + "${EXTRA_BUILD_ARGS[@]}" \ $CWS/.github/workflows/docker end_time=$(date +%s) elapsed=$((end_time - start_time)) @@ -143,7 +147,7 @@ jobs: docker push docker.io/tasimage/primus:${{env.IMAGE_TAG}} # docker login -u rocmshared -p ${{ secrets.ROCM_DOCKER_HUB_TOKEN }} - # Primus v26.3 already includes AINIC under /workspace. Re-enable this + # Primus v26.4 already includes AINIC under /workspace. Re-enable this # Dockerfile.ainic build only when we need to refresh the tasimage -ainic image. echo "> Build Docker Image with tag: ${{ env.IMAGE_TAG }}-ainic" start_time=$(date +%s) @@ -304,16 +308,18 @@ jobs: : > "$RUNNER_TEMP/runtime.tsv" # reset the CI runtime log for this job IMG="docker.io/tasimage/primus:${{ env.IMAGE_TAG }}" echo "Target image: ${IMG}" - # Reuse the container only if it is already running on the same image; - # otherwise recreate it from the freshly pushed image. - cur_img="$(docker inspect -f '{{.Config.Image}}' "${UT_CONTAINER}" 2>/dev/null || true)" + # Pull first, then compare image id (not tag name): IMAGE_TAG is + # constant ("latest") on main, so a tag-only check can't detect a + # same-tag repush and would keep reusing a stale container. + docker pull "${IMG}" + target_id="$(docker image inspect -f '{{.Id}}' "${IMG}" 2>/dev/null || true)" + cur_id="$(docker inspect -f '{{.Image}}' "${UT_CONTAINER}" 2>/dev/null || true)" running="$(docker inspect -f '{{.State.Running}}' "${UT_CONTAINER}" 2>/dev/null || true)" - if [ "${running}" = "true" ] && [ "${cur_img}" = "${IMG}" ]; then - echo "Reusing container ${UT_CONTAINER} (already on ${IMG})." + if [ "${running}" = "true" ] && [ -n "${target_id}" ] && [ "${cur_id}" = "${target_id}" ]; then + echo "Reusing container ${UT_CONTAINER} (already on ${IMG}, id=${target_id})." else - echo "Recreating container ${UT_CONTAINER} (running=${running}, image=${cur_img})." + echo "Recreating container ${UT_CONTAINER} (running=${running}, cur_id=${cur_id}, target_id=${target_id})." docker rm -f "${UT_CONTAINER}" >/dev/null 2>&1 || true - docker pull "${IMG}" docker run -d --name "${UT_CONTAINER}" \ --network host --ipc host --privileged \ --group-add video \ @@ -333,36 +339,8 @@ jobs: set -e pip install -r requirements.txt IN - # v26.3-only: drop this whole step on v26.4. Unit tests import torchtitan - # from the submodule source via conftest sys.path injection (v0.2.2 has - # PEP-420 namespace packages that need the source root on sys.path). This - # just removes any stale torchtitan in the image that would shadow it. - - name: Purge stale TorchTitan install (v26.3-only, drop on v26.4) - run: | - docker exec -i -e GITHUB_WORKSPACE="${CWS}" -w "${CWS}" "${UT_CONTAINER}" bash -s <<'IN' - set -e - pip uninstall -y torchtitan || true - python - <<'PY' - import glob, os, shutil, site - dirs = set(site.getsitepackages() + [site.getusersitepackages()]) - patterns = ( - "__editable__*torchtitan*", - "__editable__.torchtitan*.pth", - "torchtitan*.pth", - "torchtitan*.egg-link", - "torchtitan*.dist-info", - "torchtitan*.egg-info", - ) - for d in dirs: - for pat in patterns: - for f in glob.glob(os.path.join(d, pat)): - try: - shutil.rmtree(f) if os.path.isdir(f) else os.remove(f) - print("removed", f) - except OSError as e: - print("skip", f, e) - PY - IN + # No origami in this image (see docker/Dockerfile); primus_turbo falls + # back to heuristic kernel selection for MoE grouped-gemm. - name: Set UT_LOG_PATH run: | ts="$(date +%Y%m%d-%H%M%S)" diff --git a/.github/workflows/docker/Dockerfile b/.github/workflows/docker/Dockerfile index d25e6c77b..66ac6ad27 100644 --- a/.github/workflows/docker/Dockerfile +++ b/.github/workflows/docker/Dockerfile @@ -1,10 +1,10 @@ -ARG BASE_IMAGE=docker.io/rocm/primus:v26.3 +ARG BASE_IMAGE=docker.io/rocm/primus:v26.4 FROM ${BASE_IMAGE} ARG PRIMUS_TURBO_COMMIT ARG PRIMUS_TURBO_AITER_COMMIT ARG PRIMUS_TURBO_FRAMEWORK -ARG ROCSHMEM_COMMIT +# ARG ROCSHMEM_COMMIT # only needed if the rocSHMEM rebuild below is restored ARG UCCL_COMMIT ARG TRITON_COMMIT # Non-interactive APT @@ -21,34 +21,25 @@ RUN rm -rf /var/lib/apt/lists/* # --------------------------------------------------------------------------- # Enviroment variables # --------------------------------------------------------------------------- -ENV ROCSHMEM_HOME=/opt/rocshmem -ENV UCX_HOME=/opt/ucx -# ENV MPI_HOME=/opt/ompi -# Use the system OpenMPI prefix from the v26.3 base image. -ENV MPI_HOME=/usr/lib/x86_64-linux-gnu/openmpi -ENV ROCM_HOME=/opt/rocm ENV PRIMUS_TURBO_FRAMEWORK=${PRIMUS_TURBO_FRAMEWORK} -# ENV PATH="/opt/ompi/bin:/opt/ompi/sbin:${PATH}" -# ENV LD_LIBRARY_PATH="/opt/ompi/lib:${LD_LIBRARY_PATH}" ENV GPU_ARCHS="gfx942;gfx950" ENV PYTORCH_ROCM_ARCH="gfx942;gfx950" ENV HCC_AMDGPU_TARGET="gfx942,gfx950" -# --------------------------------------------------------------------------- -# Install rocSHMEM -# --------------------------------------------------------------------------- -RUN mkdir -p /opt && cd /opt && \ - git clone https://github.com/ROCm/rocSHMEM.git && \ - cd rocSHMEM && \ - git checkout ${ROCSHMEM_COMMIT} && \ - mkdir build && \ - cd build && \ - MPI_ROOT=${MPI_HOME} UCX_ROOT=${UCX_HOME} INSTALL_PREFIX=${ROCSHMEM_HOME} ../scripts/build_configs/gda \ - -DGDA_IONIC=ON \ - -DGDA_MLX5=ON \ - -DGDA_BNXT=ON \ - -DUSE_IPC=ON -RUN rm -rf /opt/rocSHMEM + +# rocSHMEM: used by Primus ODC via Primus-Turbo's odc_rocshmem_* extensions. +# This base image already ships a prebuilt install (commit 17ff985c, version +# 3.2.0) at $ROCSHMEM_HOME=/opt/rocshmem, so building it again is redundant. +# +# If a future base image drops it, restore by uncommenting below (and the +# `ARG ROCSHMEM_COMMIT` above). Note: cmake reads the ROCm version from +# -DROCM_PATH=, not $ROCM_PATH; omitting it falls back to the nonexistent +# /opt/rocm and fails. +# RUN mkdir -p /opt && cd /opt && git clone https://github.com/ROCm/rocSHMEM.git && \ +# cd rocSHMEM && git checkout ${ROCSHMEM_COMMIT} && mkdir build && cd build && \ +# MPI_ROOT=${MPI_HOME} UCX_ROOT=${UCX_HOME} INSTALL_PREFIX=${ROCSHMEM_HOME} \ +# ../scripts/build_configs/gda -DROCM_PATH=${ROCM_PATH} \ +# -DGDA_IONIC=ON -DGDA_MLX5=ON -DGDA_BNXT=ON -DUSE_IPC=ON # --------------------------------------------------------------------------- # Install Primus-Turbo @@ -79,12 +70,19 @@ RUN rm -rf /opt/Primus-Turbo RUN cd /opt && \ git clone https://github.com/triton-lang/triton.git && \ cd triton && \ - git checkout 09500db9 && \ + git checkout ${TRITON_COMMIT} && \ pip3 install -r python/requirements.txt && \ MAX_JOBS=96 pip3 install --no-build-isolation --force-reinstall --no-deps . RUN rm -rf /opt/triton +# torch's dist-info still pins the base image's stock triton build; without this, +# any later "pip install" (e.g. CI unit tests) sees torch's triton requirement as +# unsatisfiable and silently upgrades torch from PyPI, dragging in a CUDA stack. +RUN TORCH_META=$(python3 -c "import importlib.metadata as m; print(m.distribution('torch')._path / 'METADATA')") && \ + TRITON_VER=$(python3 -c "import importlib.metadata as m; print(m.version('triton'))") && \ + sed -i -E "s/^Requires-Dist: triton==.*/Requires-Dist: triton==${TRITON_VER}/" "${TORCH_META}" + # --------------------------------------------------------------------------- # Install UCCL-EP (skip for JAX framework) # --------------------------------------------------------------------------- @@ -100,23 +98,9 @@ RUN if [ "$PRIMUS_TURBO_FRAMEWORK" != "JAX" ]; then \ rm -rf /opt/uccl; \ fi -# --------------------------------------------------------------------------- -# Install fixed origami (rocm-libraries@223648a) over the base image's bundled -# 0.1.0. The bundled origami's rank_configs() raises -# `ValueError: vector::reserve` during MoE grouped-gemm kernel selection and -# crashes training (turbo's _safe_rank_configs only catches RuntimeError). -# Skipped for JAX. TODO: drop once a base image ships origami with the fix. -RUN if [ "$PRIMUS_TURBO_FRAMEWORK" != "JAX" ]; then \ - rm -rf /tmp/rocm-libraries && \ - git clone --filter=blob:none --no-checkout https://github.com/ROCm/rocm-libraries.git /tmp/rocm-libraries && \ - cd /tmp/rocm-libraries && \ - git sparse-checkout init --cone && \ - git sparse-checkout set shared/origami && \ - git checkout 223648a26928ebed7f3dd0ccdc044c09f1dccf9b && \ - (pip uninstall -y origami || true) && \ - pip install --no-cache-dir ./shared/origami/python && \ - rm -rf /tmp/rocm-libraries; \ - fi +# No origami in this image; primus_turbo falls back to heuristic kernel +# selection for MoE grouped-gemm. To add it back: +# pip install "git+https://github.com/ROCm/rocm-libraries.git@223648a26928ebed7f3dd0ccdc044c09f1dccf9b#subdirectory=shared/origami/python" # Set the default working directory WORKDIR /opt diff --git a/.github/workflows/docker/Dockerfile.ainic b/.github/workflows/docker/Dockerfile.ainic index ca103df2d..5308ae2b9 100644 --- a/.github/workflows/docker/Dockerfile.ainic +++ b/.github/workflows/docker/Dockerfile.ainic @@ -7,12 +7,16 @@ ARG AINIC_BUNDLE_PATH # Non-interactive APT ENV DEBIAN_FRONTEND=noninteractive +# `cmd | tee log` below reports only tee's exit code by default, silently +# swallowing real failures; pipefail makes the RUN fail with `cmd` instead. +SHELL ["/bin/bash", "-o", "pipefail", "-c"] # --------------------------------------------------------------------------- # Install build dependencies # --------------------------------------------------------------------------- +# initramfs-tools: required by the AINIC install.sh below. RUN apt-get update && \ - apt-get install jq dpkg-dev kmod xz-utils \ + apt-get install jq dpkg-dev kmod xz-utils initramfs-tools \ libfmt-dev libboost-all-dev \ libibverbs-dev ibverbs-utils infiniband-diags -y @@ -20,8 +24,18 @@ RUN apt-get update && \ # Enviroment variables # --------------------------------------------------------------------------- ENV WORKDIR=/workspace -ENV ROCM_PATH=/opt/rocm -ENV MPI_PATH=/usr/lib/x86_64-linux-gnu/openmpi +# ROCM_PATH/MPI_HOME already come from the base image; mirror MPI_HOME here +# instead of hardcoding the old v26.3 OpenMPI path. +ENV MPI_PATH=${MPI_HOME} + +# v26.4 moved ROCm to a pip-installed rocm-sdk-devel, so /opt/rocm no longer +# exists, and rocm-sdk-devel/bin/amdclang++ can't find its sibling clang++ +# (now under llvm/bin/). rccl/amd-anp still hardcode both, so symlink them in +# rather than patching those repos. +RUN ln -sfn "${ROCM_PATH}" /opt/rocm && \ + for f in "${ROCM_PATH}"/llvm/bin/clang*; do \ + ln -sf "$f" "${ROCM_PATH}/bin/$(basename "$f")"; \ + done # =============================== Build AINIC Driver =============================== # WARNING: Please ensure the following environment variables are correctly set: diff --git a/README.md b/README.md index 12b8810f9..504c7b09b 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ primus-cli deps sync --dir ~/.cache/Primus/third_party ```bash # For Megatron-LM and TorchTitan backends - docker pull rocm/primus:v26.3 + docker pull rocm/primus:v26.4 # For MaxText backend docker pull rocm/jax-training:maxtext-v26.4-jax0.9.1-te2.12.0 ``` @@ -102,7 +102,7 @@ primus-cli deps sync --dir ~/.cache/Primus/third_party git clone --recurse-submodules https://github.com/AMD-AGI/Primus.git cd Primus # checkout the branch for the specific release - git checkout release/v26.3 + git checkout release/v26.4 git submodule update --init --recursive ``` @@ -113,7 +113,7 @@ primus-cli deps sync --dir ~/.cache/Primus/third_party # NOTE: If your config downloads weights/tokenizer from Hugging Face Hub, # you typically need to pass HF_TOKEN into the container. # Run in the Primus repository root directory - ./primus-cli container --image rocm/primus:v26.3 \ + ./primus-cli container --image rocm/primus:v26.4 \ --env HF_TOKEN="hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ -- train pretrain --config examples/megatron/configs/MI300X/llama2_7B-BF16-pretrain.yaml ``` @@ -131,7 +131,7 @@ For more detailed usage instructions, see the [CLI User Guide](./docs/02-user-gu python -m venv primus-env source primus-env/bin/activate # Install Primus - pip install "primus==26.3.1" --no-deps --extra-index-url https://amd-agi.github.io/Primus/simple/ + pip install "primus==26.4.0" --no-deps --extra-index-url https://amd-agi.github.io/Primus/simple/ ``` @@ -142,7 +142,7 @@ For more detailed usage instructions, see the [CLI User Guide](./docs/02-user-gu 2. **Run training in container using pip-installed Primus** ```bash - primus-cli container --image rocm/primus:v26.3 \ + primus-cli container --image rocm/primus:v26.4 \ --env HF_TOKEN="hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ --volume /path/to/your/data:/data -- --log_file /data/run.log \ -- train pretrain --config /data/your/config.yaml diff --git a/benchmark/kernel/rccl/run_slurm.sh b/benchmark/kernel/rccl/run_slurm.sh index 2a6710147..26ba72aa1 100644 --- a/benchmark/kernel/rccl/run_slurm.sh +++ b/benchmark/kernel/rccl/run_slurm.sh @@ -10,7 +10,7 @@ # Usage: # DOCKER_IMAGE= sbatch run_slurm.sh # DOCKER_IMAGE= NNODES=2 PARTITION=my-gpu sbatch run_slurm.sh -# DOCKER_IMAGE=rocm/primus:v26.3 NNODES=2 sbatch -N2 -w smci355-ccs-aus-n04-[25,29] -p Compute-DCPT ./run_slurm.sh +# DOCKER_IMAGE=rocm/primus:v26.4 NNODES=2 sbatch -N2 -w smci355-ccs-aus-n04-[25,29] -p Compute-DCPT ./run_slurm.sh # # Environment variables (all optional except DOCKER_IMAGE): # DOCKER_IMAGE Docker image to use (required) diff --git a/benchmark/kernel/rccl/submit_pairs.sh b/benchmark/kernel/rccl/submit_pairs.sh index ba64c93a1..3f5a83a12 100755 --- a/benchmark/kernel/rccl/submit_pairs.sh +++ b/benchmark/kernel/rccl/submit_pairs.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -DOCKER_IMAGE="rocm/primus:v26.3" +DOCKER_IMAGE="rocm/primus:v26.4" #DOCKER_IMAGE="docker.gpuperf:5000/aai_2026_training/rocm/primus_megatron:v25.11_gpt_oss_sink" #DOCKER_IMAGE="docker.gpuperf:5000/gpuperf/primus:v26.1_sinkfa" NNODES=2 diff --git a/docs/01-getting-started/quickstart.md b/docs/01-getting-started/quickstart.md index b9075430c..bb9a4789a 100644 --- a/docs/01-getting-started/quickstart.md +++ b/docs/01-getting-started/quickstart.md @@ -65,7 +65,7 @@ Install Primus in a virtual environment: ```bash python -m venv primus-env source primus-env/bin/activate -pip install "primus==26.3.1" --no-deps --extra-index-url https://amd-agi.github.io/Primus/simple/ +pip install "primus==26.4.0" --no-deps --extra-index-url https://amd-agi.github.io/Primus/simple/ ``` > **Note:** This installs only the Primus CLI into your virtual environment (under `site-packages`), without other dependencies. Third-party submodules are downloaded on the first run of the container, and the complete training software stack is provided in the AMD-published Docker images. You can launch `primus-cli` from any directory. diff --git a/examples/README.md b/examples/README.md index 1a2aec904..eedff4c60 100644 --- a/examples/README.md +++ b/examples/README.md @@ -49,7 +49,7 @@ We recommend using the official [rocm/megatron-lm Docker image](https://hub.dock ```bash # Pull the latest Docker image -docker pull docker.io/rocm/primus:v26.3 +docker pull docker.io/rocm/primus:v26.4 ``` @@ -126,7 +126,7 @@ Multi-node training is launched via **SLURM**. Specify the number of nodes and the model config: ```bash -export DOCKER_IMAGE="docker.io/rocm/primus:v26.3" +export DOCKER_IMAGE="docker.io/rocm/primus:v26.4" export NNODES=8 # Example for megatron llama3.1_8B @@ -292,7 +292,7 @@ When using the `create` command to start a new training workload, the following | `--gpu` | Number of GPUs | 8 | | `--exp` | Path to experiment (training config) file (required) | — | | `--data_path` | Path to training data | — | -| `--image` | Docker image to use | `docker.io/rocm/primus:v26.3` | +| `--image` | Docker image to use | `docker.io/rocm/primus:v26.4` | | `--hf_token` | HuggingFace token | Read from env var `HF_TOKEN` | | `--workspace` | Workspace name | `primus-safe-pretrain` | | `--nodelist` | Comma-separated list of node hostnames to run on | — | diff --git a/examples/run_k8s_pretrain.sh b/examples/run_k8s_pretrain.sh index a57eda70d..ae07ab2bd 100644 --- a/examples/run_k8s_pretrain.sh +++ b/examples/run_k8s_pretrain.sh @@ -15,7 +15,7 @@ GPU="8" EXP_PATH="" DATA_PATH="" BACKEND="megatron" -IMAGE="docker.io/rocm/primus:v26.3" +IMAGE="docker.io/rocm/primus:v26.4" HF_TOKEN="${HF_TOKEN:-}" WORKSPACE="primus-safe-pretrain" NODELIST="" @@ -38,7 +38,7 @@ Options for create: --backend Training backend, e.g. megatron | torchtitan(default: megatron) --exp Path to EXP config (optional) --data_path Data path (optional) - --image Docker image to use (default: docker.io/rocm/primus:v26.3) + --image Docker image to use (default: docker.io/rocm/primus:v26.4) --hf_token HuggingFace token (default: from env HF_TOKEN) --workspace Workspace name (default: safe-cluster-dev) --nodelist Comma-separated list of node names to run on (optional) diff --git a/examples/run_local_pretrain.sh b/examples/run_local_pretrain.sh index f40292308..f0db4a280 100755 --- a/examples/run_local_pretrain.sh +++ b/examples/run_local_pretrain.sh @@ -16,7 +16,7 @@ Usage: bash run_local_pretrain.sh This script launches a Primus pretraining task inside a Docker/Podman container. Environment Variables: - DOCKER_IMAGE Docker image to use [Default: docker.io/rocm/primus:v26.3] + DOCKER_IMAGE Docker image to use [Default: docker.io/rocm/primus:v26.4] MASTER_ADDR Master node IP or hostname [Default: localhost] MASTER_PORT Master node port [Default: 1234] NNODES Total number of nodes [Default: 1] @@ -44,7 +44,7 @@ export EXP=${EXP:-"examples/megatron/exp_pretrain.yaml"} if [ "${BACKEND:-}" = "MaxText" ]; then DOCKER_IMAGE=${DOCKER_IMAGE:-"docker.io/rocm/jax-training:maxtext-v26.2"} else - DOCKER_IMAGE=${DOCKER_IMAGE:-"docker.io/rocm/primus:v26.3"} + DOCKER_IMAGE=${DOCKER_IMAGE:-"docker.io/rocm/primus:v26.4"} fi # Project root diff --git a/examples/run_local_pretrain_cli.sh b/examples/run_local_pretrain_cli.sh index 7e6305c9b..df930b077 100755 --- a/examples/run_local_pretrain_cli.sh +++ b/examples/run_local_pretrain_cli.sh @@ -15,7 +15,7 @@ EXP=${EXP:-"examples/megatron/exp_pretrain.yaml"} if [ "${BACKEND:-}" = "MaxText" ]; then DOCKER_IMAGE=${DOCKER_IMAGE:-"docker.io/rocm/jax-training:maxtext-v25.9"} else - DOCKER_IMAGE=${DOCKER_IMAGE:-"docker.io/rocm/primus:v26.3"} + DOCKER_IMAGE=${DOCKER_IMAGE:-"docker.io/rocm/primus:v26.4"} fi # ------------------ Cluster Env Defaults ------------------ diff --git a/examples/run_slurm_pretrain_cli.sh b/examples/run_slurm_pretrain_cli.sh index 6d10d0231..bdfa8850a 100755 --- a/examples/run_slurm_pretrain_cli.sh +++ b/examples/run_slurm_pretrain_cli.sh @@ -35,7 +35,7 @@ mkdir -p "$LOG_DIR" # NOTE: The --env entries below are passed into the container and will be visible # to the Primus training process (and system hooks) inside the container. bash "$PRIMUS_PATH/runner/primus-cli" slurm "${SLURM_ARGS[@]}" \ --- --image "${DOCKER_IMAGE:-rocm/primus:v26.3}" \ +-- --image "${DOCKER_IMAGE:-rocm/primus:v26.4}" \ -- \ --env "USING_AINIC=${USING_AINIC:-0}" \ --env "PATCH_TE_FLASH_ATTN=${PATCH_TE_FLASH_ATTN:-0}" \ diff --git a/primus/backends/megatron/patches/turbo/aiter_deepbind_patches.py b/primus/backends/megatron/patches/turbo/aiter_deepbind_patches.py index cf0852cda..fa3f7a21e 100644 --- a/primus/backends/megatron/patches/turbo/aiter_deepbind_patches.py +++ b/primus/backends/megatron/patches/turbo/aiter_deepbind_patches.py @@ -7,31 +7,23 @@ """ hd128 backward crash fix via scoped RTLD_DEEPBIND (ROCm/aiter#1332). -Affects gfx942 (MI300X/MI325X) and gfx950 (MI350X/MI355X). - -rocm/primus:v26.3's transformer_engine bundles a STALE vendored ``libmha_bwd.so`` -(old aiter f299f579) that, via ``libck_fused_attn.so``'s DT_NEEDED, interposes the -global symbol ``aiter::mha_bwd`` over the freshly pinned aiter (b5e03ed1) -> Turbo's -hd128 backward mis-selects the swa variant and launches with an invalid grid config -(gridDim.x=0 on gfx942 / "invalid configuration argument" on gfx950) -> 8-rank crash. -The ``mha_bwd_args`` ABI differs between the two aiter revisions, so a -*global* fix (RTLD_GLOBAL preload / .so overwrite) repairs Turbo but breaks the TE -path (TE's libck calls the stale libmha by the old ABI) -- isolation must be scoped. - -Fix: wrap ``importlib.import_module`` (aiter loads its mha extensions through it, -honouring ``sys.setdlopenflags()``) and OR in ``RTLD_DEEPBIND`` only for the pinned -aiter mha modules (``module_fmha_v3_bwd`` / ``mha_bwd_bf16_*``), so they bind their -own fresh ``aiter::mha_bwd`` while TE keeps the stale one. DEEPBIND is scoped to these -few extensions, leaving torch/c10 untouched. Installed by the ``before_train`` patch -below, gated on Turbo + affected arch (TE path never touched); ``before_train`` runs -well before the first backward that JIT-imports these modules. - -TEMPORARY: delete this file (and its turbo/__init__.py entry) once the base image's -TE is built against the updated aiter. +On gfx942/gfx950, rocm/primus:v26.3's transformer_engine bundles a stale vendored +``libmha_bwd.so`` that shadows the global symbol ``aiter::mha_bwd`` over the pinned +aiter build, crashing Turbo's hd128 backward. A global fix would break TE (it needs +the stale symbol), so isolation is scoped: wrap ``importlib.import_module`` and OR in +``RTLD_DEEPBIND`` only for the pinned aiter mha modules, installed via the +``before_train`` patch below. + +Self-detection: skipped once ``transformer_engine.__version__`` >= 2.14 (confirmed +fixed in rocm/primus:v26.4). Older images still get the patch. + +TEMPORARY: delete this file (and its turbo/__init__.py entry) once every supported +base image ships a TE built against the updated aiter. """ import importlib import os +import re import sys from primus.backends.megatron.patches.turbo.utils import is_primus_turbo_can_patch @@ -71,6 +63,34 @@ def _is_affected_arch() -> bool: return False +# First TE version that dropped the stale vendored libmha_bwd.so (see module docstring). +_TE_FIX_VERSION = (2, 14, 0) +_TE_FIX_VERSION_STR = ".".join(map(str, _TE_FIX_VERSION)) + + +def _te_version_tuple(): + """Best-effort (major, minor, patch) from ``transformer_engine.__version__``, or + None if TE isn't importable / the version string can't be parsed.""" + try: + import transformer_engine + + match = re.match(r"(\d+)\.(\d+)\.(\d+)", getattr(transformer_engine, "__version__", "") or "") + return tuple(int(part) for part in match.groups()) if match else None + except Exception: # pragma: no cover - defensive, never block training + return None + + +def _te_already_fixed() -> bool: + """True if the installed TE no longer vendors the stale libmha_bwd.so. + + Fails safe towards False (unfixed) on an unknown/unparseable version: applying + the hook on an already-fixed TE is a harmless no-op, but skipping it on an + unfixed TE reintroduces the hd128 backward crash. + """ + version = _te_version_tuple() + return version is not None and version >= _TE_FIX_VERSION + + def _install_deepbind_import_hook() -> bool: """ Idempotently wrap ``importlib.import_module`` to OR in RTLD_DEEPBIND for the pinned @@ -115,7 +135,7 @@ def _import_module_with_deepbind(name, package=None): def _can_install_aiter_deepbind(ctx: PatchContext) -> bool: - """Install only on affected arches (gfx942/gfx950) with the Turbo attention path active.""" + """Install only on affected arches (gfx942/gfx950) with an unfixed TE and Turbo attention active.""" args = get_args(ctx) if not bool(getattr(args, "use_turbo_attention", False)): return False @@ -127,6 +147,13 @@ def _can_install_aiter_deepbind(ctx: PatchContext) -> bool: "aiter DEEPBIND isolation not needed." ) return False + if _te_already_fixed(): + log_rank_0( + f"{_LOG_PREFIX} transformer_engine >= {_TE_FIX_VERSION_STR} detected " + "(no vendored stale libmha_bwd.so exported as aiter::mha_bwd); aiter DEEPBIND " + "isolation not needed on this image." + ) + return False return True @@ -137,8 +164,8 @@ def _can_install_aiter_deepbind(ctx: PatchContext) -> bool: description=( "On gfx942/gfx950, install the aiter mha RTLD_DEEPBIND import hook so the Turbo " "attention backward binds the pinned aiter::mha_bwd instead of TE's stale vendored " - "libmha. Gated on Turbo attention (TE path untouched). Temporary workaround " - "(ROCm/aiter#1332)." + "libmha. Gated on Turbo attention and an unfixed TE (auto-skips once " + f"transformer_engine >= {_TE_FIX_VERSION_STR}). Temporary workaround (ROCm/aiter#1332)." ), condition=_can_install_aiter_deepbind, ) diff --git a/runner/README.md b/runner/README.md index e93241c6a..2317d5663 100644 --- a/runner/README.md +++ b/runner/README.md @@ -162,7 +162,7 @@ This is the most common pattern when you need a fixed software stack. bash runner/primus-cli slurm \ -N 4 \ --nodelist "node[01-04]" \ --- --image "rocm/primus:v26.3" \ +-- --image "rocm/primus:v26.4" \ -- --env NCCL_DEBUG=INFO \ -- train pretrain --config examples/megatron/configs/MI300X/llama3.1_8B-BF16-pretrain.yaml ``` diff --git a/runner/primus-cli-container.sh b/runner/primus-cli-container.sh index 97928f15e..8e1883429 100755 --- a/runner/primus-cli-container.sh +++ b/runner/primus-cli-container.sh @@ -45,7 +45,7 @@ Docker/Podman Options: --cap-add Add Linux capabilities (e.g., SYS_PTRACE) Container Configuration: - --image Docker image [default: rocm/primus:v26.3] + --image Docker image [default: rocm/primus:v26.4] --name Container name --user Run as specific user (e.g., 1000:1000) --network Network mode (e.g., host, bridge) diff --git a/tests/unit_tests/backends/megatron/test_aiter_deepbind_patches.py b/tests/unit_tests/backends/megatron/test_aiter_deepbind_patches.py new file mode 100644 index 000000000..13f60b42e --- /dev/null +++ b/tests/unit_tests/backends/megatron/test_aiter_deepbind_patches.py @@ -0,0 +1,105 @@ +############################################################################### +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# +# See LICENSE for license information. +############################################################################### + +"""Unit tests for the aiter RTLD_DEEPBIND gating logic +(``primus/backends/megatron/patches/turbo/aiter_deepbind_patches.py``). + +These verify the patch self-detects whether the installed transformer_engine +already fixed the stale-vendored-libmha issue (ROCm/aiter#1332), without +needing a GPU. The actual RTLD_DEEPBIND import hook installation is exercised +separately in the diffusion GPU tests via ``tests.utils.install_aiter_deepbind_hook``. +""" + +import sys +import types + +import pytest + +import primus.backends.megatron.patches.turbo.aiter_deepbind_patches as patch_mod + + +def _fake_te_module(version: str) -> types.ModuleType: + module = types.ModuleType("transformer_engine") + module.__version__ = version + return module + + +@pytest.mark.parametrize( + "version, expected", + [ + ("2.14.0", (2, 14, 0)), + ("2.14.1", (2, 14, 1)), + ("1.11.0+abcdef0", (1, 11, 0)), + ("2.14.0.dev0+rocm", (2, 14, 0)), + ], +) +def test_te_version_tuple_parses_leading_semver(monkeypatch, version, expected): + monkeypatch.setitem(sys.modules, "transformer_engine", _fake_te_module(version)) + assert patch_mod._te_version_tuple() == expected + + +def test_te_version_tuple_none_when_unimportable(monkeypatch): + monkeypatch.setitem(sys.modules, "transformer_engine", None) # forces ImportError on import + assert patch_mod._te_version_tuple() is None + + +def test_te_version_tuple_none_when_unparseable(monkeypatch): + monkeypatch.setitem(sys.modules, "transformer_engine", _fake_te_module("not-a-version")) + assert patch_mod._te_version_tuple() is None + + +@pytest.mark.parametrize( + "version, expected_fixed", + [ + ("2.13.9", False), # pre-fix: still vendors the stale libmha_bwd.so + ("2.14.0", True), # fixed: renamed to te_libmha_*.so / qola::te:: + ("2.15.0", True), + ("1.11.0+abcdef0", False), # e.g. rocm/primus:v26.3's TE + ], +) +def test_te_already_fixed_thresholds_on_fix_version(monkeypatch, version, expected_fixed): + monkeypatch.setitem(sys.modules, "transformer_engine", _fake_te_module(version)) + assert patch_mod._te_already_fixed() is expected_fixed + + +def test_te_already_fixed_fails_safe_towards_unfixed_when_unknown(monkeypatch): + # An unparseable/missing version must NOT be treated as "fixed": skipping the + # hook on an actually-unfixed TE reintroduces the hd128 backward crash. + monkeypatch.setitem(sys.modules, "transformer_engine", None) + assert patch_mod._te_already_fixed() is False + + +def _condition_ctx(monkeypatch, *, use_turbo_attention=True, turbo_can_patch=True, affected_arch=True): + args = types.SimpleNamespace(use_turbo_attention=use_turbo_attention) + monkeypatch.setattr(patch_mod, "get_args", lambda ctx: args) + monkeypatch.setattr(patch_mod, "is_primus_turbo_can_patch", lambda ctx: turbo_can_patch) + monkeypatch.setattr(patch_mod, "_is_affected_arch", lambda: affected_arch) + monkeypatch.setattr(patch_mod, "log_rank_0", lambda *a, **k: None) + return object() # PatchContext stand-in; unused by the stubbed collaborators above + + +def test_can_install_skips_when_te_already_fixed(monkeypatch): + ctx = _condition_ctx(monkeypatch) + monkeypatch.setitem(sys.modules, "transformer_engine", _fake_te_module("2.14.0")) + assert patch_mod._can_install_aiter_deepbind(ctx) is False + + +def test_can_install_applies_when_te_unfixed_on_affected_arch(monkeypatch): + ctx = _condition_ctx(monkeypatch) + monkeypatch.setitem(sys.modules, "transformer_engine", _fake_te_module("1.11.0+abcdef0")) + assert patch_mod._can_install_aiter_deepbind(ctx) is True + + +def test_can_install_skips_when_arch_unaffected_regardless_of_te(monkeypatch): + ctx = _condition_ctx(monkeypatch, affected_arch=False) + monkeypatch.setitem(sys.modules, "transformer_engine", _fake_te_module("1.11.0+abcdef0")) + assert patch_mod._can_install_aiter_deepbind(ctx) is False + + +def test_can_install_skips_when_turbo_attention_disabled(monkeypatch): + ctx = _condition_ctx(monkeypatch, use_turbo_attention=False) + monkeypatch.setitem(sys.modules, "transformer_engine", _fake_te_module("1.11.0+abcdef0")) + assert patch_mod._can_install_aiter_deepbind(ctx) is False diff --git a/tools/docker/start_container.sh b/tools/docker/start_container.sh index d29289122..2fe081557 100755 --- a/tools/docker/start_container.sh +++ b/tools/docker/start_container.sh @@ -6,7 +6,7 @@ ############################################################################### PRIMUS_PATH=$(realpath "$(dirname "$0")/../..") -DOCKER_IMAGE=${DOCKER_IMAGE:-"docker.io/rocm/primus:v26.3"} +DOCKER_IMAGE=${DOCKER_IMAGE:-"docker.io/rocm/primus:v26.4"} DATA_PATH=${DATA_PATH:-"${PRIMUS_PATH}/data"} SANITIZED_USER=$(echo "${USER:-unknown}" | tr -cd '[:alnum:]_-') if [ -z "$SANITIZED_USER" ]; then