Skip to content

[Experiment] ROCm backend - #2300

Open
NripeshN wants to merge 459 commits into
ml-explore:mainfrom
NripeshN:rocm-support
Open

NripeshN wants to merge 459 commits into
ml-explore:mainfrom
NripeshN:rocm-support

Conversation

@NripeshN

@NripeshN NripeshN commented Jun 16, 2025 •

Copy link
Copy Markdown
Contributor

Experiment with ROCm backend.

install MLX with ROCm backend using:

mkdir build && cd build
cmake -DMLX_BUILD_ROCM=ON \
      -DCMAKE_PREFIX_PATH=/opt/rocm \
      -DCMAKE_HIP_ARCHITECTURES="gfx90a;gfx1100" \
      ..
make -j$(nproc)

closes #2556

Inspired by @zcbenz

@NripeshN NripeshN changed the title [Experiment] ROCm backend initial push [Experiment] ROCm backend Jun 16, 2025
@lin72h

lin72h commented Jun 17, 2025

Copy link
Copy Markdown

What an unexpected and amazing surprise! I'm absolutely thrilled.

@NripeshN

Copy link
Copy Markdown
Contributor Author

@awni
What do you think of this PR? Does this have the potential to be merged into main? I can turn this PR from experimental to WIP if so.

@angeloskath

Copy link
Copy Markdown
Member

I think this is good to stay as an experiment branch for some time while we work on core and CUDA. I don't think we have the bandwidth to merge this for a few months at least. Sorry if this is disappointing @NripeshN I don't mean to discourage you working on it.

@akshat2602

Copy link
Copy Markdown

I would love to see the ROCm backend get more traction. The new AI series of processors by AMD have a similar advantage to Apple Silicon with unified memory and getting MLX to run on those processors would be neat.

@countradooku

Copy link
Copy Markdown

Stole my idea :(

@goniz

goniz commented Jan 22, 2026

Copy link
Copy Markdown

How is this even possible for such an awesome PR to be left like this?

Copilot AI review requested due to automatic review settings January 24, 2026 17:08

Copilot AI 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.

Pull request overview

This PR adds experimental ROCm backend support to MLX, enabling execution on AMD GPUs. The implementation mirrors the CUDA backend structure, providing HIP-based implementations of core operations, memory management, and device handling.

Changes:

  • Added ROCm backend infrastructure with device management, memory allocation, and stream handling
  • Implemented HIP kernels for unary, binary, ternary operations, reductions, normalization (softmax, layer_norm, rms_norm), RoPE, and sorting
  • Updated build system (CMake) to support ROCm compilation with configurable GPU architectures

Reviewed changes

Copilot reviewed 59 out of 59 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
CMakeLists.txt Added MLX_BUILD_ROCM option and ROCm library detection
mlx/CMakeLists.txt Integrated ROCm backend build configuration
mlx/device.cpp Added ROCm device availability checks
mlx/backend/rocm/*.hip HIP kernel implementations for various operations
mlx/backend/rocm/device.* ROCm device and stream management
mlx/backend/rocm/allocator.* ROCm-specific memory allocator using HIP unified memory
mlx/backend/rocm/worker.* Async task execution worker for stream synchronization
mlx/backend/rocm/utils.* HIP utility functions and error handling
mlx/backend/rocm/jit_module.* JIT compilation support using HIPRTC
mlx/backend/rocm/device/*.hpp Device-side utility functions and type definitions
mlx/backend/rocm/CMakeLists.txt ROCm backend build configuration

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread mlx/backend/rocm/softmax.hip Outdated
Comment thread mlx/backend/rocm/device.cpp Outdated
Comment thread mlx/backend/rocm/layer_norm.hip Outdated
Comment thread mlx/backend/rocm/rope.hip Outdated
Comment thread mlx/backend/rocm/softmax.hip Outdated
Comment thread mlx/backend/rocm/allocator.cpp Outdated
Comment thread CMakeLists.txt Outdated
Comment thread mlx/backend/rocm/binary.hip Outdated
Comment thread mlx/backend/rocm/rms_norm.hip Outdated
Comment thread mlx/backend/rocm/layer_norm.hip Outdated
@goniz

goniz commented Jan 24, 2026

Copy link
Copy Markdown

👑👑👑

@NripeshN

Copy link
Copy Markdown
Contributor Author

Can anyone run

CMAKE_ARGS="-DMLX_BUILD_ROCM=ON" pip install -e .
CMAKE_ARGS="-DMLX_BUILD_ROCM=ON -DMLX_ROCM_ARCHITECTURES={based on your GPU}" pip install -e .

Replace {based on your GPU} with your GPU architecture

You can run

rocm-smi

to get your GPU information

@goniz

goniz commented Jan 24, 2026 •

Copy link
Copy Markdown

I'm getting this CMake error:

CMAKE_ARGS="-DMLX_BUILD_ROCM=ON -DMLX_ROCM_ARCHITECTURES=gfx1151" pip install -e .

      -- Configuring done (4.8s)
      CMake Error: The following variables are used in this project, but they are set to NOTFOUND.
      Please set them or make sure they are set and tested correctly in the CMake files:
      /home/goniz/Work/mlx/LAPACK_INCLUDE_DIRS
         used as include directory in directory /home/goniz/Work/mlx
      
      CMake Error in CMakeLists.txt:
        HIP_ARCHITECTURES is empty for target "mlx".
      
      
      CMake Error in CMakeLists.txt:
        HIP_ARCHITECTURES is empty for target "mlx".
      
      
      -- Generating done (0.0s)
      CMake Generate step failed.  Build files cannot be regene
rated correctly.

Running on Strix Halo (gfx1151)

@NripeshN

Copy link
Copy Markdown
Contributor Author

I'm getting this CMake error:

CMAKE_ARGS="-DMLX_BUILD_ROCM=ON -DMLX_ROCM_ARCHITECTURES=gfx1151" pip install -e .
     -- Configuring done (4.8s)
     CMake Error: The following variables are used in this project, but they are set to NOTFOUND.
     Please set them or make sure they are set and tested correctly in the CMake files:
     /home/goniz/Work/mlx/LAPACK_INCLUDE_DIRS
        used as include directory in directory /home/goniz/Work/mlx
     
     CMake Error in CMakeLists.txt:
       HIP_ARCHITECTURES is empty for target "mlx".
     
     
     CMake Error in CMakeLists.txt:
       HIP_ARCHITECTURES is empty for target "mlx".
     
     
     -- Generating done (0.0s)
     CMake Generate step failed.  Build files cannot be regene
rated correctly.

Running on Strix Halo (gfx1151)

Could you retry with the latest push please (p.s. keep your fingers crossed while it compiles, worked for me 138th time)😅

@goniz

goniz commented Jan 25, 2026

Copy link
Copy Markdown
  Created wheel for mlx: filename=mlx-0.30.4.dev20260125+cadf18c1-0.editable-cp314-cp314-linux_x86_64.whl size=4722 sha256=72c664adbfc4fb9ec317522a8d83b84f85d599d08bd691d7fec3abfdb6f3a5e9
  Stored in directory: /tmp/pip-ephem-wheel-cache-nt7w6bq0/wheels/8a/63/d1/d7d629a5ff73457822bb71aa527c083674bb19ca314735cd05
Successfully built mlx
Installing collected packages: mlx
Successfully installed mlx-0.30.4.dev20260125+cadf18c1

Now what can I test? 😍

@goniz

goniz commented Jan 25, 2026

Copy link
Copy Markdown

I'm getting this:

ImportError: /home/goniz/Work/mlx/python/mlx/lib/libmlx.so: undefined symbol: _ZN3mlx4core11Convolution8eval_gpuERKSt6vectorINS0_5arrayESaIS3_EERS3_

@NripeshN

Copy link
Copy Markdown
Contributor Author

I'm getting this:

ImportError: /home/goniz/Work/mlx/python/mlx/lib/libmlx.so: undefined symbol: _ZN3mlx4core11Convolution8eval_gpuERKSt6vectorINS0_5arrayESaIS3_EERS3_

I forgot to test the Python build my bad, can you try it now?

Unfortunately I might not be able to help after it compiles, I don't have an AMD GPU to run tests😔 I've tried replicating most things from cuda, so hopefully it works

@goniz

goniz commented Jan 26, 2026

Copy link
Copy Markdown

Now fails on load with this:

>>> import mlx.core
Traceback (most recent call last):
  File "<python-input-0>", line 1, in <module>
    import mlx.core
ImportError: /home/goniz/Work/mlx/python/mlx/lib/libmlx.so: undefined symbol: hiprtcCompileProgram

@goniz

goniz commented Jan 26, 2026

Copy link
Copy Markdown

Unfortunately I might not be able to help after it compiles, I don't have an AMD GPU to run tests😔 I've tried replicating most things from cuda, so hopefully it works

Omg I don't believe you did it without AMD card 😱😱

@NripeshN

NripeshN commented Jan 26, 2026 •

Copy link
Copy Markdown
Contributor Author

Now fails on load with this:

The latest push hopefully fixes the undefined symbol error Found the issue, working on the fix😩

Omg I don't believe you did it without AMD card 😱😱

Haha docker literally saves me and humbles me at the same time

@goniz

goniz commented Jan 26, 2026

Copy link
Copy Markdown
image

@goniz

goniz commented Jan 26, 2026

Copy link
Copy Markdown

I might got over excited:
image

@NripeshN

Copy link
Copy Markdown
Contributor Author

Wait it works?😅

Ah unfortunately unless a magic fairy sends me a PC with AMD GPU I cannot help after this😭 With the ram prices I doubt the magic fairy has the funds either🥲

@goniz

goniz commented Jan 26, 2026

Copy link
Copy Markdown

Latest commit broke something:
image

@NripeshN

Copy link
Copy Markdown
Contributor Author

Lemme try adding a fix for both the issues above actually. I had just made a stub implementation earlier.

@NripeshN

Copy link
Copy Markdown
Contributor Author

@goniz give the last push a try maybe. It might not work but you will definitely not have the same error atleast☺️

@goniz

goniz commented Jan 26, 2026

Copy link
Copy Markdown

mlx rocm-support ? ❯︎ python3 qwen3.py 
Fetching 9 files: 100%|██████| 9/9 [00:00<00:00, 201864.90it/s]
Download complete: : 0.00B [00:00, ?B/s]              ?, ?it/s]
==========
Traceback (most recent call last):
  File "/home/goniz/Work/mlx/qwen3.py", line 15, in <module>
    text = generate(model, tokenizer, prompt=prompt, verbose=True)
  File "/home/goniz/Work/mlx/venv/lib/python3.14/site-packages/mlx_lm/generate.py", line 762, in generate
    for response in stream_generate(model, tokenizer, prompt, **kwargs):
                    ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/goniz/Work/mlx/venv/lib/python3.14/site-packages/mlx_lm/generate.py", line 699, in stream_generate
    for n, (token, logprobs, from_draft) in enumerate(token_generator):
                                            ~~~~~~~~~^^^^^^^^^^^^^^^^^
  File "/home/goniz/Work/mlx/venv/lib/python3.14/site-packages/mlx_lm/generate.py", line 689, in <genexpr>
    (token, logprobs, False) for token, logprobs in token_generator
                                                    ^^^^^^^^^^^^^^^
  File "/home/goniz/Work/mlx/venv/lib/python3.14/site-packages/mlx_lm/generate.py", line 432, in generate_step
    mx.eval([c.state for c in prompt_cache])
    ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: Unsupported dtype for affine_dequantize

@NripeshN

Copy link
Copy Markdown
Contributor Author

Might fix it(????)

@goniz

goniz commented Jan 26, 2026

Copy link
Copy Markdown

mlx rocm-support ? ❯︎ python3 qwen3.py 
Fetching 9 files: 100%|███████| 9/9 [00:00<00:00, 28575.88it/s]
Download complete: : 0.00B [00:00, ?B/s]              ?, ?it/s]
==========
Traceback (most recent call last):
  File "/home/goniz/Work/mlx/qwen3.py", line 15, in <module>
    text = generate(model, tokenizer, prompt=prompt, verbose=True)
  File "/home/goniz/Work/mlx/venv/lib/python3.14/site-packages/mlx_lm/generate.py", line 762, in generate
    for response in stream_generate(model, tokenizer, prompt, **kwargs):
                    ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/goniz/Work/mlx/venv/lib/python3.14/site-packages/mlx_lm/generate.py", line 699, in stream_generate
    for n, (token, logprobs, from_draft) in enumerate(token_generator):
                                            ~~~~~~~~~^^^^^^^^^^^^^^^^^
  File "/home/goniz/Work/mlx/venv/lib/python3.14/site-packages/mlx_lm/generate.py", line 689, in <genexpr>
    (token, logprobs, False) for token, logprobs in token_generator
                                                    ^^^^^^^^^^^^^^^
  File "/home/goniz/Work/mlx/venv/lib/python3.14/site-packages/mlx_lm/generate.py", line 432, in generate_step
    mx.eval([c.state for c in prompt_cache])
    ~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
RuntimeError: QuantizedMatmul has no ROCm implementation.

Geramy and others added 24 commits July 15, 2026 13:16
Trim-on-free still burned 18s of hipFree (pre-Adam stalls). Pure recycle
on free; malloc_async still releases under memory_limit_/max_pool_size_.
Eager memory_limit_/max_pool_size_ release on every miss still burned
~18s hipFree/step. free() recycles only; malloc tries device alloc first
and reclaims BufferCache only on OOM. Kills pre-Adam free/alloc storms.
Fixed-shape train needs 100% size reuse: prefer equal_range exact hits,
hipMalloc without freelist thrash, reclaim only when active+cache nears
physical HBM (not soft memory_limit_). Avoids free storms and full-HBM hangs.
- BufferCache min_utilization=1.0 on ROCm: only exact-size reuse (100% fill)
- Reject ≤50% util oversize hand-outs; reclaim freelist at 85% HBM so KFD
  never sees 0 MB free (OOR hang at 204W)
- free path still pure recycle; release uses hipFreeAsync when pool path
- Invalidate JIT disk cache when requested kernel symbols are missing
  (source-only key left WPT=16 names absent after list growth → gfx1152
  "There is no kernel named …_strided<…, 16>")
- Cap max_compiled_work_per_thread on wave32/low-CU (gfx1152) to 8
- Fall back WPT 16→8→4→1 if a specialization is still absent
lemon-mlx-engine multi-arch fatbins match gfx9 and pinned host WARP_SIZE=64
while gfx1152 device kernels are wave32. Launching dim3(64, tile_n) into
wave32 QMV kernels corrupts lane/warp reductions → garbage decode text.

- Host launch dims use runtime HWInfo.warp_size / Device::warp_size()
- Multi-arch CMAKE: no longer force MLX_HOST_WARP_SIZE=64 when RDNA present
- Low-CU iGPU (≤8 CU / gfx1152): smaller QMV tiles, skip WMMA-QMM by default
- Log host_WARP_SIZE vs runtime warp for diagnosis
HIPBLASLT_EPILOGUE_SIGMOID_BIAS_EXT is missing on older hipBLASLt headers
(common on consumer gfx1152 / 860M stacks). That breaks lemon-mlx-engine
multi-arch builds at hipblaslt_gemm.cpp. #ifdef the optional EXT enums so
stock ROCm compiles; act-only epilogue path is unchanged when the symbols
exist.
RDNA (gfx10/11/12, incl. gfx1152) is wave32. CDNA-only stays 64.
- Host default WARP_SIZE when MLX_HOST_WARP_SIZE unset: 32 (was 64)
- CMake: if any RDNA arch is in CMAKE_HIP_ARCHITECTURES → host 32;
  only pure gfx9 lists get host 64. Multi-arch no longer matches gfx9 first.

Launches still must use runtime warp_size(); this stops the host fallback
from advertising 64 on consumer multi-arch fatbins.
RDNA4 (gfx1200/1201) is hardware wavefront 32 like RDNA2/3/3.5 — not 64.
Document that; keep host default 32 whenever any RDNA arch is present.

Optional overrides only (experiments / future hardware):
  cmake -DMLX_HOST_WARP_SIZE=32|64
  env   MLX_ROCM_FORCE_WARP_SIZE=32|64  (runtime launch; wrong value → garbage)
MI300X train died with HSA_STATUS_ERROR_MEMORY_APERTURE_VIOLATION in
moe_pack_kernel (~700 steps after resume). Two kernel bugs:

1. Expert id used as counts[e]/slot_map[e] with no bounds check. Garbage
   or signed -1 ids (0xFFFFFFFF as uint32) walk off the counts buffer.
2. float2 vectorized K-copy assumed 8-byte alignment; bf16 rows are often
   only 2-byte aligned → misaligned load/store faults on CDNA.

Also clamp unpack/slot_map source rows to [0, batch), and give M_pad +25%
slack so imbalanced routing after a cached measure is less likely to drop.
…her_mm

try_sorted_rhs_gather_mm and SegmentedMM::eval_gpu read routing metadata back
to the host to build dense GEMM segments, but did so with a plain hipMemcpy on
the NULL stream. MLX ROCm streams are created hipStreamNonBlocking (utils.cpp),
so they are exempt from legacy default-stream synchronization: the copy is NOT
ordered against the kernel that writes rhs_indices/segments, and the host can
read stale or uninitialized ids. SegmentedMM is worse -- the contiguous_copy_gpu
of `segments` is enqueued on that very stream immediately above the read.

Both now stage through a pinned buffer with hipMemcpyAsync on encoder.stream()
plus an explicit hipStreamSynchronize, matching the pattern that
try_moe_segment_gather_mm already used.

The ids are also used unchecked as pointer offsets:
  - expert_offset(e) -> e * b_batch_strides[0], no bound on e
  - segs[2i]/[2i+1]  -> k0/k1 offsets into a/b, no bound on K
Same class of bug f99ec23 fixed in moe_pack_kernel (a garbage or signed -1 id,
0xFFFFFFFF as uint32, walks off the buffer). Expert ids are now validated
against the group count -- falling back to the generic path rather than
computing on garbage -- and segment bounds are clamped to the K extent.

No measured throughput change on MI300X (14,371 vs 14,321 tok/s): pinned
staging keeps the dense-segment GEMM win intact.
save_safetensors wrote each tensor straight from arr.data<char>(). The ROCm
allocator returns real VRAM (hipMalloc); on MI300X that VRAM is mapped into the
host address space through a large PCIe BAR, so the pointer is valid but reading
it is uncached MMIO. Measured on gfx942 (xnack-):

  CPU direct BAR read     :    54 MB/s   <- what ostream->write() did
  MLX host read (np.array):    29 MB/s
  hipMemcpy DeviceToHost  :  10.4 GB/s
  hipMemcpy Default       :  50.4 GB/s

Real cost on a 1.5B train run: 3 GB model save = 102 s, 12.1 GB optimizer save
= 408 s -- 8.5 min per checkpoint and ~31% of wall-clock at save_every=1000,
with the GPU idle at 0% throughout. The disk does 1.7 GB/s, so this was never
I/O bound.

rocm::staged_write() chunks the tensor through a 64 MB pinned bounce buffer with
hipMemcpyDefault, making the write disk-bound instead of BAR-bound. It
hipDeviceSynchronize()s first (eval() only drains MLX's own non-blocking stream)
and no-ops for non-device pointers, so host/pinned arrays and CPU-only builds
are unaffected.

Fallback contract: staged_write returns false only BEFORE emitting any bytes;
once the first chunk is written a later failure throws rather than returning
false, since falling back mid-stream would append a second copy of the array and
silently corrupt the checkpoint.

Compile-verified only; runtime validation pending a full rebuild.
MoeSwigluSorted::eval_gpu allocated `out` with malloc_async and scattered into
it on the assumption stated at the call site:

    // Every token is packed exactly once → unpack covers all rows of out.

That assumption is false. moe_pack_kernel silently drops tokens in two places
(naive_gemm.hip):

    if (e >= n_experts) return;                  // OOB/garbage expert id
    if (slot < 0 || slot >= M_fixed) return;     // expert run exceeds capacity

A dropped token leaves slot_map[e,slot] = -1; moe_unpack_kernel skips src < 0;
that row of `out` is therefore NEVER WRITTEN and returns whatever the allocator
recycled.

This is not merely wrong-but-finite. Freed slot_map temporaries are int32
filled with -1 (moe_fill_neg1), and 0xFFFF reinterpreted as bf16 is exp=0xFF
with a non-zero mantissa — a NaN by IEEE definition. Recycling one of those
into an unwritten row is a NaN generator.

Observed on MI300X (gfx942) training a 1.5B MoE in bf16: intermittent FORWARD
loss=nan, self-recovering, and non-deterministic across runs resumed from an
identical checkpoint+build+env (10 NaN/219 steps vs 0 NaN/119 steps) — because
whether a dropped row lands on a -1-filled buffer or on benign finite garbage
depends on which block the allocator hands back. Weights stayed provably clean
(max|w|=0.75, zero non-finite over 1.5B params) and grad-norm was healthy right
up to each NaN (median 0.66, p95 1.39, no ramp), which is what ruled out
arithmetic instability and pointed at memory reuse.

The other two moe_unpack_tokens call sites already memset for exactly this
reason — matmul.cpp's gather_mm path ("Zero out then scatter packed results
(out may have garbage)") and the VJP's dx. The fused forward was the only path
that did not. Zeroing makes a dropped token contribute 0 instead of NaN.

Note this is a stopgap for the NaN, not for correctness: a dropped token is
still a lost token. See the following commit for the capacity fix.
moe_pack_m_pad cached max-run capacity in a static map keyed on (T,E) and
returned early on a hit, so it never re-measured. For a fixed training geometry
that is a SINGLE key: measured once on the first call of the process and frozen
for its entire lifetime.

Routing imbalance is not static. As the router specializes, an expert's max run
grows past that first measurement, tokens overflow M_fixed, and moe_pack_kernel
drops them. The drop rate therefore climbs monotonically over a run. Measured on
MI300X with a 1.5B MoE, unbiased sample (every 20th step):

    steps  9200- 9599 :  0% nan
    steps  9600- 9999 :  5% nan
    steps 10000-10399 : 40% nan
    steps 10400-10799 : 47% nan

and a fresh process resuming the SAME checkpoint at the same step range — which
re-measures M_pad against current routing — showed 0%. That is the tell: the
degradation lived in the frozen cache entry, not in the weights or the data.

The comment claimed under-size was "non-fatal (pack drops overflow tokens)".
That held for the paths that memset their output; it did not hold for the fused
forward, where a dropped row surfaced recycled memory as NaN (fixed in the
previous commit). Even with that fixed, a drop silently deletes a token from the
MoE computation, so capacity must track routing.

Re-measure every kRemeasureCalls (512) hits and keep the grow-only merge, so the
entry follows drift upward and can never shrink into a drop. Cost is ~1 sync per
512 calls, versus the ~150 syncs/step the cache was introduced to eliminate.
kernel_sdpa_flash_bwd_f32acc had no __syncthreads() between the dV loop and
the dS loop inside the K-tile loop. The dV loop reads S_sh (the softmax
probabilities P) across all q-rows for each k-column; the dS loop then
overwrites the same S_sh in place. Different threads own different S_sh
elements, so a thread in dS can clobber S_sh[r,c] before a thread in dV has
finished reading it -> corrupted dV, which propagates into dK/dQ.

The block runs 256 threads = 8 wavefronts on wave32 (RDNA3.5 gfx1152 / RDNA4
gfx1201) vs 4 on wave64 (CDNA gfx942), so the unsynchronized overwrite races
on RDNA but not CDNA. It also compounds across K-tiles, so it is invisible for
L<=BN (single tile) and grows with sequence length.

Reproduced by test_sdpa_ref on gfx1152 (Radeon 860M): L=8 OK at all D; L>=64
diverges (max|g-ref| ~1.3-1.8); D=64 L=1024 etc. The D=128/256 NaNs are a
SEPARATE issue (LDS budget: 4*BM*D floats exceeds the 64KB device limit; the
host guard  at scaled_dot_product_attention dispatch already
flags this) and are NOT addressed here.

Adding a barrier is strictly correctness-preserving (cannot make correct code
wrong), so this is safe. NOT YET VERIFIED on RDNA hardware — the only CDNA box
available never executes the WMMA path. Validate with:
  ./test_sdpa_ref   # on gfx1152/gfx1201 — L>=64 causal cases should pass
…it LDS)

The bwd kernel hardcoded BM=64 and the dispatch rejected D!=64, so head dims
above 64 (e.g. Qwen3.6 = 128) could not use the flash path. Forcing the kernel
at D=128/256 overflowed LDS (2*BM + BM*BN + 3*BM*D + 2*BN*D floats > 64KB) ->
garbage / NaN in test_sdpa_ref.

Make BM a template parameter and pick it per head dim so LDS fits a 64KB device:
  D=64  -> BM=64  (60.5 KB)
  D=128 -> BM=16  (41.1 KB)   <- Qwen3.6
  D=256 -> BM=8   (56.6 KB)
grid.y = ceil(qL/BM) and the dynamic-LDS byte count both derive from the chosen
BM, so shrinking BM only adds Q-tile blocks. The compute loops are D-agnostic
(for d < D), so this is a correctness-preserving retiling, not an algorithm
change. Support gate now allows D in {64,128,256}.

Pairs with the prior commit (missing dV/dS __syncthreads) which fixes the
wave32 race on the D=64 path.

Compile-verified for gfx942 (this f32acc kernel is plain-float, no WMMA
intrinsics). NOT yet numerically verified — validate on RDNA (gfx1152/gfx1201)
with ./test_sdpa_ref: all D in {64,128,256} x L in {8,64,256,1024} causal
should print OK.
…mmit)

The previous commit changed sdpa_flash_bwd_smem(int) -> (int,int), but its
callers in scaled_dot_product_attention.cpp (VJP::eval_gpu, VJP::use_fallback)
still call the 1-arg form -> undefined reference at link.

Keep the public signature int sdpa_flash_bwd_smem(int D). The BM-per-head-dim
mapping is now a single constexpr helper sdpa_flash_bwd_bm(D) used by BOTH the
size function and the kernel launch (BMv = sdpa_flash_bwd_bm(Dv)), so there is
one source of truth and no drift. Compile-checked for gfx942.
Online softmax used if (tid < BLOCK_M) so wave0 lanes 16..31 read S rows
written by wave1. rocWMMA store_matrix_sync to LDS is not made visible by
plain __syncthreads(); that matched L<=16 OK and L>=32 diverge/NaN on
gfx1152. Align softmax row ownership with the WMMA writer wave and flush
with rocwmma::synchronize_workgroup after WMMA→LDS stores. Move wave_scale
into dynamic smem and account for it in sdpa_flash_wmma_smem.
Device code used `#else WAVE=64` when __AMDGCN_WAVEFRONT_SIZE__ was unset.
On gfx115x that produced wave=tid/64 with a 128-thread RDNA launch, so only
rows 0..31 were written (L<=32 OK, L>=33 diverge/NaN). Use WARP_SIZE from
device/config.h (arch macros: RDNA→32, CDNA→64), matching host launch dims.

Verified on gfx1151: full D={64,128,256}×L={8..1024} causal OK (max|g-ref|<0.01).
Confirm exclusive 35B chat load passes; concurrent second process load
SIGSEGV exit 139 after device bind (copy_contiguous/HIP path). Document
ownership: robustness/OOM vs single-process correctness; no code fix PR yet.
CI uses pre-commit/action with --all-files. Format the tree so the
docs investigation PR is not blocked by unrelated style drift on
rocm-support sources. No intentional functional change.
docs(rocm): gfx1150 dual-process large-model load SIGSEGV (exclusive OK)
@Geramy

Geramy commented Jul 30, 2026

Copy link
Copy Markdown

I completed the build:

[..snip..]
[ 98%] Built target linear_regression
[ 98%] Built target metal_capture
[ 98%] Built target test_teardown
[ 98%] Built target logistic_regression
[ 98%] Built target tutorial
[ 98%] Built target distributed
[100%] Linking CXX executable tests
[100%] Built target tests
root@pop-os:/path/to/mlx-lm-rocm-support/build# python
bash: python: command not found
root@pop-os:/path/to/mlx-lm-rocm-support/build# curl -LsSf https://astral.sh/uv/install.sh | sh
source $HOME/.local/bin/env   # or: export PATH="$HOME/.local/bin:$PATH"
downloading uv 0.11.3 x86_64-unknown-linux-gnu
installing to /root/.local/bin
  uv
  uvx
everything's installed!

To add $HOME/.local/bin to your PATH, either restart your shell or run:

    source $HOME/.local/bin/env (sh, bash, zsh)
    source $HOME/.local/bin/env.fish (fish)
root@pop-os:/path/to/mlx-lm-rocm-support/build# uv --version
uv 0.11.3 (x86_64-unknown-linux-gnu)
root@pop-os:/path/to/mlx-lm-rocm-support/build# uv venv /opt/mlx-env --python 3.12
source /opt/mlx-env/bin/activate
Using CPython 3.12.13
Creating virtual environment at: /opt/mlx-env
Activate with: source /opt/mlx-env/bin/activate

But failed to import mlx_lm or run mlx_lm.chat :

# uv run --active ipython
warning: No `requires-python` value found in the workspace. Defaulting to `>=3.12`.
Python 3.12.13 (main, Mar 24 2026, 22:49:22) [Clang 22.1.1 ]
Type 'copyright', 'credits' or 'license' for more information
IPython 9.12.0 -- An enhanced Interactive Python. Type '?' for help.
Tip: Use `F2` or %edit with no arguments to open an empty editor with a temporary file.

In [1]: import mlx_lm
---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
Cell In[1], line 1
----> 1 import mlx_lm

File /path/to/mlx_lm/__init__.py:9
      5 from ._version import __version__
      7 os.environ["TRANSFORMERS_NO_ADVISORY_WARNINGS"] = "1"
----> 9 from .convert import convert
     10 from .generate import batch_generate, generate, stream_generate
     11 from .utils import load

File /path/to/mlx_lm/convert.py:7
      4 from pathlib import Path
      5 from typing import Callable, Optional, Union
----> 7 import mlx.core as mx
      8 import mlx.nn as nn
      9 from mlx.utils import tree_map_with_path

ImportError: /path/to/mlx-lm-rocm-support/python/mlx/core.cpython-312-x86_64-linux-gnu.so: undefined symbol: _ZN3mlx4core4fast10hip_kernelERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt6vectorIS7_SaIS7_EESE_S9_S9_bi

In [2]: exit
(mlx-env) root@pop-os:/path/to/mlx-lm-rocm-support# CMAKE_BUILD_PARALLEL_LEVEL=$(nproc)   uv pip install -e ".[dev]" --no-build-isolation
Using Python 3.12.13 environment at: /opt/mlx-env
Resolved 42 packages in 299ms
      Built mlx @ /path/to//mlx-lm-rocm-support
Prepared 1 package in 1m 43s
Uninstalled 1 package in 0.44ms
Installed 1 package in 2ms
 ~ mlx==0.31.2.dev20260404+a866ff4f (from file:///path/to/mlx-lm-rocm-support)
(mlx-env) root@pop-os:/path/to/mlx-lm-rocm-support# pushd
/path/to/mlx-lm /path/to/mlx-lm-rocm-support
(mlx-env) root@pop-os:/path/to/mlx-lm# git pull; CMAKE_BUILD_PARALLEL_LEVEL=$(nproc) uv pip install -U .  
Already up to date.
Using Python 3.12.13 environment at: /opt/mlx-env
Resolved 32 packages in 791ms
      Built mlx-lm @ file:///path/to/mlx-lm
Prepared 1 package in 392ms
Uninstalled 1 package in 1ms
Installed 1 package in 10ms
 ~ mlx-lm==0.31.2 (from file:///path/to/mlx-lm)
(mlx-env) root@pop-os:/path/to/mlx-lm# mlx_lm.
mlx_lm.awq            mlx_lm.chat           mlx_lm.dynamic_quant  mlx_lm.generate       mlx_lm.manage         mlx_lm.share          
mlx_lm.benchmark      mlx_lm.convert        mlx_lm.evaluate       mlx_lm.gptq           mlx_lm.perplexity     mlx_lm.upload         
mlx_lm.cache_prompt   mlx_lm.dwq            mlx_lm.fuse           mlx_lm.lora           mlx_lm.server         
(mlx-env) root@pop-os:/path/to/mlx-lm# mlx_lm.chat --help
Traceback (most recent call last):
  File "/opt/mlx-env/bin/mlx_lm.chat", line 4, in <module>
    from mlx_lm.chat import main
  File "/opt/mlx-env/lib/python3.12/site-packages/mlx_lm/__init__.py", line 9, in <module>
    from .convert import convert
  File "/opt/mlx-env/lib/python3.12/site-packages/mlx_lm/convert.py", line 7, in <module>
    import mlx.core as mx
ImportError: /path/to/mlx-lm-rocm-support/python/mlx/core.cpython-312-x86_64-linux-gnu.so: undefined symbol: _ZN3mlx4core4fast10hip_kernelERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERKSt6vectorIS7_SaIS7_EESE_S9_S9_bi
(mlx-env) root@pop-os:/path/to/mlx-lm# 

Did you ever get it working?

@Geramy

Geramy commented Jul 30, 2026 •

Copy link
Copy Markdown

@angeloskath @zcbenz How can I get this PR considered for Merge or what would be the easiest way for us to get it merged into mlx? I can provide AMD runners myself for consumer cards and hardware, I have plenty of equipment, RDNA3.5, 4 etc.... and a 10 GPU r9700 server I can offer for CI processes to do QA. I have been continually maintaining it and is implemented into an engine I am also maintaining, currently in alpha. I would really like all this hard work to be realized and pushed further. I believe if it got merged we could see a lot more contributions and help by the community. The AMD community is very strong and helpful.

If it would help, I can squash the history grouped by person so all people who made changes and improvements keep their recognition while reducing the overall commit history.

AMD also has released simulation software, where a CPU or another GPU can emulate another architecture of GPUs so we could have a full QA environment targeting all AMD GPUs so nothing is shipped without tests passing.

@zcbenz

zcbenz commented Aug 3, 2026

Copy link
Copy Markdown
Member

Currently we own all the code, i.e. we review and verify all code in this repo, so we can't just merge code that is maintained purely by community contributors, and we would require at least one full time team member to govern the new backend. Our team is hiring, but unfortunately we do not have a head count for maintaining a new backend, and we are already overloaded.

I think the only reasonable way to move forward is to have AMD commit resources and developers to maintain the rocm backend in the long term, which is hard at the moment but should be realistic once we get ollama to use mlx-cuda.

This branch has not been deployed

No deployments
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.

Add ROCm Support for AMD GPUs