Skip to content

feat(docker): add an AMD ROCm image, for RDNA2 through RDNA4 and CDNA - #10820

Open
LeoBorcherding wants to merge 3 commits into
unslothai:mainfrom
LeoBorcherding:feature/docker-rocm-support-v2
Open

feat(docker): add an AMD ROCm image, for RDNA2 through RDNA4 and CDNA#10820
LeoBorcherding wants to merge 3 commits into
unslothai:mainfrom
LeoBorcherding:feature/docker-rocm-support-v2

Conversation

@LeoBorcherding

Copy link
Copy Markdown
Collaborator

adds an AMD ROCm image alongside the CUDA one from #5748. same docker/ layout, same build.sh / run.sh entry points, same smoke-test shape.

this is #6231 resynced. that PR targeted docker-blackwell-build, which was deleted when #5748 merged, so github auto-closed it and won't reopen it. the branch was also 2764 commits behind, and build.sh / run.sh were rewritten underneath it, so the --rocm hooks are reimplemented against the current files rather than merged.

what's here

  • docker/Dockerfile.rocm: ROCm torch wheels plus the bitsandbytes pre-release that carries the 4-bit decode fix. [huggingface] extra, SDPA fallback, since xformers has no ROCm wheel.
  • docker/Dockerfile.studio-rocm: Studio variant on the ROCm base.
  • docker/entrypoint-rocm.sh: preflight for /dev/kfd, rocm-smi, HIP torch, gfx arch.
  • docker/smoke_test_rocm.py: ROCm smoke test with a 5-step LoRA.
  • .github/workflows/docker-publish-rocm.yml: GPU-free amd64 build and publish.
  • build.sh --rocm and run.sh --rocm. run.sh reuses main's numeric --group-add gid lookup instead of passing the group by name, and skips the NVIDIA toolkit prompt on the ROCm path.

two things building it turned up

the old default installed a CUDA torch. the torch layer used --index-strategy unsafe-best-match with pypi as a fallback, which compares versions across both indexes. every ROCm wheel index below 7.2 trails pypi, so pypi won:

$ uv pip compile --index-strategy unsafe-best-match \
    --index-url https://download.pytorch.org/whl/rocm6.2 \
    --extra-index-url https://pypi.org/simple
torch==2.14.0
nvidia-cublas==13.1.1.3
nvidia-cudnn-cu13==9.24.0.43
nvidia-nccl-cu13==2.30.7

$ uv pip compile --index-url https://download.pytorch.org/whl/rocm6.2
torch==2.5.1+rocm6.2

rocm6.4 does the same thing (2.14.0 vs 2.9.1+rocm6.4). the build then failed its own assert torch.version.hip is not None. dropped the pypi fallback, and moved the default to ROCm 7.2.4: 6.2 tops out at torch 2.5.1, which current unsloth-zoo and transformers won't sit on, and RDNA4 has no kernels before 7.x anyway.

the image shipped two tritons in one directory. unsloth-zoo and cut-cross-entropy each declare a bare triton on linux, which is the NVIDIA-targeted pypi wheel, and it installs over torch's triton-rocm in the same site-packages/triton/:

$ python -c "import importlib.metadata as m; print(m.version('triton'), m.version('triton-rocm'))"
3.8.0 3.7.1
$ python -c "import triton; print(triton.__version__)"
3.7.1
$ grep triton-rocm torch-*.dist-info/METADATA
Requires-Dist: triton-rocm==3.7.1; platform_system == "Linux"

neither version describes what's actually on disk. uninstalling the pypi one and reinstalling the pin torch names fixes it:

$ python -c "import importlib.metadata as m; print(m.version('triton-rocm'))"
3.7.1
$ python -c "import triton; print(triton.__version__)"
3.7.1

index selection can't fix this, download.pytorch.org/whl/rocm7.2/triton/ is the pypi wheel mirrored rather than a ROCm build. the durable fix is probably in unsloth-zoo's metadata, where that dependency should be conditional.

hardware and OS coverage

no hosted github runner has an AMD GPU, and the self-hosted Strix Halo runner on ooba's fork is the wrong arch and OS for this, so a green check here says nothing about AMD behaviour. this table is the test report.

measured at 8ee07d6. docker/ is unchanged on main since that commit.

shape changed by this PR what ran still needed
RDNA4 discrete, gfx1201, linux yes, it's the new default build only, on windows a real train on a KFD host
RDNA3 discrete, gfx1100-1102, linux yes, wheel index moved off 6.2 nothing build plus the 5-step LoRA
RDNA2 discrete, gfx1030/1031, linux yes, same nothing build plus the 5-step LoRA, this is the arch most likely to need HSA_OVERRIDE_GFX_VERSION
RDNA3.5 APU, gfx1150/1151 yes, unified memory path untouched but the wheel moved nothing build plus a train, and whether the carve-out reads sanely in a container
CDNA, gfx90a/942 yes, same nothing build plus a train
windows, Docker Desktop no build plus every non-GPU path nothing, see below

where it was run:

machine what it can honestly prove
Radeon AI PRO R9700 (gfx1201), Ryzen 7 8700F, Windows 11 26200, Docker Desktop 4.74, WSL2 backend the image builds and the wheel resolution is right. torch 2.12.1+rocm7.2, HIP 7.2.53211, bitsandbytes 0.50.3.dev0. the entrypoint's refusal path, run.sh --rocm end to end, and 58 existing docker tests. it can prove nothing about a GPU

windows can't test the GPU half of this, and the reason is structural. Docker Desktop's WSL2 backend has no /dev/kfd and no /dev/dri, only /dev/dxg. the host's own WSL ubuntu does see the card through DXG because its ROCm ships librocdxg.so; the rocm/dev-ubuntu-22.04 base images don't ship that shim, and mounting the host's copy in still segfaults rocminfo. so no flag combination gets this image onto a GPU there. the entrypoint currently tells such a user to modprobe amdgpu, which is impossible on that host, and that message should say so.

bottom line

solid: the image builds, the wheel resolution is correct on every ROCm index, the triton tree is consistent, and every failure path refuses clearly rather than half-working.

biggest gap: not one line of this has run on a GPU. Dockerfile.studio-rocm has never been built even once, and has two problems that don't need hardware to see. ENV SKIP_TORCH=true does nothing, install.sh derives that from --no-torch / UNSLOTH_NO_TORCH and sets it false unconditionally, so install.sh re-runs torch selection against an absent build-host GPU. and ARG BASE_TAG=test doesn't match the :latest that build.sh --rocm produces. happy to fix both, or drop the Studio image from this PR and do it separately.

closes #6230 when it lands. supersedes #6231.

Reapplies PR unslothai#6231 on top of main. Dockerfile.rocm, Dockerfile.studio-rocm,
entrypoint-rocm.sh, smoke_test_rocm.py, test_locally-rocm.sh and the publish
workflow are unchanged from that branch. build.sh and run.sh were rewritten by
unslothai#5748 after unslothai#6231 was opened, so the --rocm hooks are reimplemented against the
current files instead of merged: run.sh now reuses main's numeric --group-add
gid lookup, and skips the NVIDIA toolkit prompt on the ROCm path.
Two defects found by building the image:

The torch layer used --index-strategy unsafe-best-match with pypi as a
fallback, which compares versions across both indexes. Every ROCm index below
7.2 trails pypi's torch (rocm6.2 tops out at 2.5.1, rocm6.4 at 2.9.1), so the
best match is pypi's CUDA build: the PR's own default of rocm6.2 resolves to a
plain torch==2.14.0 pulling nvidia-cublas and nccl, and then fails the
torch.version.hip assertion. Dropped the fallback and moved the --rocm default
to 7.2.4, which is also the floor for RDNA4.

unsloth-zoo and cut-cross-entropy both declare a bare triton on Linux, the
NVIDIA-targeted pypi wheel. It installs over torch's triton-rocm in the same
site-packages/triton/, leaving triton 3.8.0 by metadata and triton.__version__
3.7.1 in one tree. Uninstall it and reinstall the pin torch names.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-11T21:34:52.876607Z e7816f7 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e7816f7dea

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +109 to +111
ROCM_VERSION=${{ github.event.inputs.rocm_version || '6.2.4' }}
PYTHON_VERSION=3.12
TORCH_INDEX_URL=${{ github.event.inputs.torch_index_url || 'https://download.pytorch.org/whl/rocm6.2' }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Publish the ROCm 7.2 image needed by RDNA4

The push and scheduled paths in docker-publish-rocm.yml always pass ROCm 6.2 defaults, even though Dockerfile.rocm states that gfx1150/1151/1200/1201 require ROCm 7.2 and build.sh --rocm consequently defaults to 7.2.4. Thus the image published as unsloth/unsloth-rocm:latest lacks kernels for the advertised RDNA4 and Strix devices; align these workflow defaults with the supported 7.2 build.

Useful? React with 👍 / 👎.

Comment on lines +34 to +39
ENV UNSLOTH_STUDIO_HOME=/opt/unsloth-studio \
DEBIAN_FRONTEND=noninteractive \
# torch is already installed in the base image (ROCm wheels).
# Prevent install.sh from re-running get_torch_index_url() which would
# probe the absent build-host GPU and silently install a CPU wheel on top.
SKIP_TORCH=true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Pin ROCm torch when creating the Studio venv

On the documented GPU-free Studio build, this variable does not skip torch installation: install.sh unconditionally resets SKIP_TORCH=false and only honors --no-torch or UNSLOTH_NO_TORCH. It therefore probes an absent GPU and installs CPU torch into Studio's separate unsloth_studio venv; the base-image entrypoint still validates the ROCm torch in /opt/unsloth-venv, so startup can pass while Studio training uses the CPU build. The Studio install needs an explicit matching ROCm index/family rather than this ignored variable.

Useful? React with 👍 / 👎.

Comment on lines +28 to +29
ARG BASE_TAG=test
FROM unsloth-rocm:${BASE_TAG}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Default Studio to the tag produced by build.sh

Following the new entry point bash docker/build.sh --rocm produces unsloth-rocm:latest, but a subsequent Studio build without an override looks for unsloth-rocm:test. On a normal checkout with no separately tagged :test image, Docker attempts to resolve that nonexistent base and the Studio build fails before running any instructions; use the same default tag as build.sh or make the Studio build path pass the produced tag.

Useful? React with 👍 / 👎.

Comment on lines +112 to +113
UNSLOTH_REF=${{ github.event.inputs.unsloth_ref || (startsWith(github.ref, 'refs/tags/') && github.ref_name) || github.sha || 'main' }}
UNSLOTH_ZOO_REF=${{ github.event.inputs.unsloth_zoo_ref || (startsWith(github.ref, 'refs/tags/') && github.ref_name) || 'main' }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Fall back when the release tag is absent from unsloth-zoo

On every v* tag event this expression forces UNSLOTH_ZOO_REF to the Unsloth release tag, but Unsloth's release tags are not guaranteed to exist in unsloth-zoo. The existing CUDA publish workflow explicitly probes for the matching zoo tag and falls back to main; without that check, any release lacking a synchronized zoo tag makes the Git dependency installation fail and prevents the ROCm release image from being published.

Useful? React with 👍 / 👎.

Comment thread docker/build.sh
Comment on lines +34 to +35
ROCM_VERSION="${ROCM_VERSION:-7.2.4}"
TORCH_INDEX_URL="${TORCH_INDEX_URL:-https://download.pytorch.org/whl/rocm7.2}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Use architecture-specific torch wheels for Strix

For gfx1150/gfx1151, the advertised build.sh --rocm path installs the generic pytorch.org ROCm 7.2 wheel. The repository's own install.sh deliberately reroutes these architectures away from generic indexes below ROCm 7.13 to repo.amd.com/rocm/whl/<gfx>/, documenting a _grouped_mm segfault and subsequent Strix kernel bugs in the 7.2 build. Because this Dockerfile bypasses that installer routing, the default Strix image uses the known-broken wheel and can crash during supported training workloads.

Useful? React with 👍 / 👎.

Comment thread docker/entrypoint-rocm.sh
Comment on lines +125 to +129
# SUPPORTED maps gfx arch string -> (family, example cards).
SUPPORTED = {
"gfx906": ("Vega20 / CDNA0", "Radeon VII"),
"gfx908": ("CDNA1", "Instinct MI100"),
"gfx90a": ("CDNA2", "Instinct MI200 / MI210 / MI250"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Stop reporting gfx906 as supported by the default image

When the image is built through the new default build.sh --rocm path, it uses ROCm 7.2, yet this table reports gfx906 as supported. The gfx906 routing in this repository's install.sh states that ROCm 6.3 is the last wheel family whose BLAS kernels run on gfx906 and that newer generic wheels fail at the first BLAS call. An MI50 or Radeon VII user therefore passes the entrypoint with a reassuring “Supported” message and then fails in training; gfx906 needs a separate 6.3 build or a version-aware rejection here.

Useful? React with 👍 / 👎.

Comment on lines +112 to +113
UNSLOTH_REF=${{ github.event.inputs.unsloth_ref || (startsWith(github.ref, 'refs/tags/') && github.ref_name) || github.sha || 'main' }}
UNSLOTH_ZOO_REF=${{ github.event.inputs.unsloth_zoo_ref || (startsWith(github.ref, 'refs/tags/') && github.ref_name) || 'main' }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Resolve mutable refs before entering the Docker cache

Scheduled builds pass unsloth-zoo@main, and default workflow-dispatch builds pass both repositories as @main, directly into a cached Docker RUN command. When the repository SHA is unchanged, BuildKit can reuse the previous install layer even after either upstream branch advances, so a green nightly or manually triggered image can silently contain stale Unsloth Zoo or Unsloth code. The local build.sh and existing CUDA workflow resolve these names to commit SHAs specifically to invalidate this cache correctly; the ROCm workflow needs the same preparation.

Useful? React with 👍 / 👎.

Comment on lines +158 to +163
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) && github.event.inputs.unsloth_ref == '' }}
type=ref,event=tag
type=schedule,pattern=nightly
type=sha,prefix=sha-,format=short

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep override builds from replacing latest

On a default-branch workflow_dispatch, clearing unsloth_ref enables the stable latest tag even if unsloth_zoo_ref, rocm_version, or torch_index_url is overridden. A test dispatch for a custom zoo commit, legacy ROCm version, or experimental wheel index can therefore replace the public unsloth/unsloth-rocm:latest image. Gate latest on every build-affecting input remaining at its stable default, as the existing CUDA publish workflow does.

Useful? React with 👍 / 👎.

Comment thread docker/Dockerfile.rocm
Comment on lines +61 to +64
RUN apt-get update && apt-get install -y --no-install-recommends \
python3 python3-pip python3-venv \
curl wget git \
cmake build-essential libcurl4-openssl-dev \

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Install the requested Python version

The Dockerfile declares and the publish workflow passes PYTHON_VERSION=3.12, but this installation uses the unversioned Ubuntu 22.04 python3 packages and later creates the venv with that executable, so the resulting image actually uses Ubuntu's Python 3.10. Changing the build argument has no effect, and dependency markers or user code expecting the advertised 3.12 environment resolve differently; install python${PYTHON_VERSION} and create the venv from that interpreter.

Useful? React with 👍 / 👎.

Comment thread docker/entrypoint-rocm.sh
Comment on lines +105 to +109
print("ERROR: torch.cuda.is_available() is False despite rocm-smi working.")
print()
print("This image was built against ROCm 6.2. The host ROCm stack must be")
print("6.2 or newer. Check the host (NOT the container) with:")
print(" rocm-smi --version")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Report the actual ROCm baseline in preflight errors

For images produced by the new default build.sh --rocm path, torch and the base runtime are ROCm 7.2, but a failed availability probe tells users the image was built against 6.2 and that any 6.2-or-newer host is sufficient. This sends users with an incompatible 6.x host away from the required driver upgrade and makes the most important runtime failure diagnostic incorrect; derive the displayed version from torch.version.hip or the build argument.

Useful? React with 👍 / 👎.

@danielhanchen

Copy link
Copy Markdown
Member

Checked the --rocm hooks in docker/build.sh and docker/run.sh and the existing NVIDIA path stays gated behind the non-ROCm branch, which is the right shape. Since Dockerfile.studio-rocm has not been built and you flagged the SKIP_TORCH and BASE_TAG issues yourself, could you drop it to a follow-up so this lands as the base ROCm image only?

@danielhanchen

Copy link
Copy Markdown
Member

Part of this is not yours. (Python 3.13) and the sidebar failure inside Repo tests (CPU) were hitting every open PR: main has been red since #10706, and the DeepSeek R1 timing assertion was tripping on a real O(N^2) from #10507. Both are fixed in #10832.

Two failures are from this PR's own new files, and both gates are telling you the fix:

tests/studio/install/test_rocm_arch_table_parity.py::TestNoUnregisteredArchTable::test_no_unregistered_copies
  unregistered GPU-name/arch table(s): {'docker/entrypoint-rocm.sh': 7}

tests/studio/test_main_runs_survive_merge_bursts.py::test_every_workflow_that_runs_on_main_is_grouped_per_commit
  ['docker-publish-rocm.yml'] share one concurrency group across main commits
  1. docker/entrypoint-rocm.sh carries a seventh copy of the GPU name to arch table. Wire it into _name_tables() (or the spoof check) and add the path to _REGISTERED_TABLE_FILES, so that when the arch list changes the copies cannot drift apart silently.
  2. docker-publish-rocm.yml needs -${{ github.ref == 'refs/heads/main' && github.sha || '' }} appended to its concurrency group. Without it a merge burst cancels pending runs before they start, and cancel-in-progress does not help since it only spares runs already executing.

Rebase once #10832 is in.

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] AMD Docker support (mirrors the Blackwell from #5748)

2 participants