Skip to content

docs(rocm): document MLX_ROCM_GATHER_QMV_EXPERT_BATCHED and the other runtime MLX_ROCM_* variables #2180

Closed

Description

@inureyes

Part of #1801

Problem / Background

MLX_ROCM_GATHER_QMV_EXPERT_BATCHED switches the expert-batched gather_qmm kernel, which has been on by default for bf16 and f16 affine since #2112 (#2066) and for bf16 mxfp4 since #2164 (#2106). A user who wants the per-row path back, or who is bisecting a MoE regression, can only find the variable in src/lib/mlx-cpp/patches-rocm/LOCAL_FIXES.md (items 9 and 31), two benchmark pages, a code comment and a test. It is absent from docs/environment-variables.md and from the ROCm section of docs/installation.md. The same holds for 42 other MLX_ROCM_* variables the overlay reads at runtime.

Current Behavior

  • grep -rhoE 'MLX_ROCM_[A-Z0-9_]+' src/lib/mlx-cpp/patches-rocm finds 51 names. docs/environment-variables.md names 7: MLX_ROCM_ARCHITECTURES, MLX_ROCM_FFT_CACHE_SIZE, MLX_ROCM_MAX_INFLIGHT_MB, MLX_ROCM_GPU_WATCHDOG_SECS, MLX_ROCM_WMMA_QMM, MLX_ROCM_WMMA_QMM_MAX_M, MLX_ROCM_QMM_DEQUANT_GEMM, all as prose inside "Build-time variables" (docs/environment-variables.md:179-233). MLX_ROCM_HOST_STAGE is a compile definition (mlx/backend/rocm/CMakeLists.txt:458), not an environment variable, and stays undocumented. The other 43 are runtime variables with no documentation.
  • docs/installation.md "Linux with AMD ROCm" (:366-574) covers only MLX_ROCM_ARCHITECTURES, MLX_ROCM_MAX_INFLIGHT_MB, MLX_ROCM_GPU_WATCHDOG_SECS and MLX_ROCM_FFT_CACHE_SIZE; its "Runtime environment variables" table (:575-591) has no ROCm rows.
  • Existing text is wrong for one variable: docs/environment-variables.md:225 says the dequantize + hipBLASLt route is enabled when "MLX_ROCM_QMM_DEQUANT_GEMM not 0", but use_rocblas_dequant_path() (quantized/qmm.hip:480-492) enables it only when the variable is unset or exactly 1; any other value, including true or an empty string, disables it.

Proposed Solution

  1. In docs/environment-variables.md, add a section ## ROCm backend variables directly after "Build-time variables". Move the existing runtime ROCm prose (FFT cache, in-flight bound, watchdog, WMMA QMM, WMMA QMM max M, dequant GEMM) into it unchanged except for the MLX_ROCM_QMM_DEQUANT_GEMM correction, and leave one sentence plus a link in "Build-time variables". Under it add a table with columns Variable | Values | Default | Purpose, the same as "Hardware and kernel diagnostic variables", with one row per variable below, grouped in this order: MoE gather qmv, dense qmv, dequantize + GEMM, GEMM libraries, unquantized MoE, allocator and device. State in the section intro that every path is under src/lib/mlx-cpp/patches-rocm/mlx/backend/rocm/, that most variables are read once per process and the ones marked "every call" can be changed between calls, and that "presence" variables take effect for any value, including 0.
  2. In docs/installation.md, add one paragraph to the ROCm "Running" subsection naming MLX_ROCM_GATHER_QMV_EXPERT_BATCHED=0 as the way to take the per-row MoE path for A/B or bisecting, linking to the new section, and add one row to the "Runtime environment variables" table that points to it.
  3. Take every value and default from the code at implementation time, not from this table, if the code has moved. fix(rocm): range-check GEMM env integers and fix a stale HIP graph comment #2152 (open) changes the GEMM integer parsers to warn on invalid input; if it merges first, document its behavior.
Variable Read at Values Default Effect
MLX_ROCM_GATHER_QMV_EXPERT_BATCHED quantized/qmm.hip:6242 0 off, 1 on, else default; every call on gather_qmv_expert_batched_kernel for a sorted, transposed gather_qmm with M == 1, B >= 64, E <= 64, B / E >= 4: bf16 or f16 affine 4/8-bit at group 64, and bf16 mxfp4. 0 sends every scheme to the unsorted-path kernels.
MLX_ROCM_GATHER_QMV_USE_WARP quantized/qmm.hip:6099 0/1; every call on for transposed 2/4/8-bit and affine 5/6-bit Gates the fast gather qmv kernels (wide, idot, warp-shared); 0 sends gather_qmm to the per-row gather_qmv_kernel.
MLX_ROCM_GATHER_QMV_THREADS_PER_COL quantized/qmm.hip:6071 16 or the wave size, else ignored select_qmv_threads_per_col (16; the wave size at K >= 16384 with batch 1 on wave32) Lanes per output column in the fast gather kernels; unset falls back to MLX_ROCM_QMV_THREADS_PER_COL.
MLX_ROCM_GATHER_QMV_USE_TILED quantized/qmm.hip:6318 presence off Opt-in tiled gather qmv for bf16 affine 4/8-bit group 64 with a unit-stride 1-D batch.
MLX_ROCM_GROUPED_PREFILL quantized/qmm.hip:6110 integer, nonzero enables off Work-in-progress grouped WMMA prefill for bf16 affine 4-bit group 64 gather_qmm; the code notes it regresses.
MLX_ROCM_GROUPED_PREFILL_MIN_B quantized/qmm.hip:6114 integer (atoi, unvalidated) 64 Minimum B for the grouped prefill.
MLX_ROCM_GATHER_QMV_USE_WMMA quantized/qmm.hip:6123 0/1 native WMMA and N % 16 == 0 Only inside the grouped prefill; 0 falls back to the standard dispatch.
MLX_ROCM_QMV_USE_WARP quantized/qmm.hip:3778 0/1 on when transposed 0 disables the warp-shared qmv for quantized_matmul, except where the batched qmv applies (forced on).
MLX_ROCM_QMV_THREADS_PER_COL quantized/qmm.hip:3791 16 or the wave size as the gather row Lanes per output column in the dense qmv.
MLX_ROCM_QMV_COLS_PER_BLOCK quantized/qmm.hip:506 4, 8, 16, 32, 64, else ignored; every call select_qmv_cols_per_block (by N, K, bits) Output columns per block for the dense qmv, the gather warp-shared and the expert-batched kernels.
MLX_ROCM_QMV_TILE_N quantized/qmm.hip:3846 integer (atoi, unvalidated) per-arch qmv_tile_n, 4 to 24 (device/config.h:169-186) Columns per block of the tiled qmv, halved until it divides N.
MLX_ROCM_QMV_NO_TILED quantized/qmm.hip:3851 presence tiled on Disables the L2-tiled qmv.
MLX_ROCM_QMV_6BIT_SLOW quantized/qmm.hip:3855, :6327 presence off Sends 6-bit dense and gather qmv back to the warp-shared kernel.
MLX_ROCM_FORCE_LOW_CU quantized/qmm.hip:591 value starting with 1 devices with 8 CUs or fewer Treats the device as a low-CU iGPU for qmv and WMMA tuning.
MLX_ROCM_QMM_DEQUANT_M_THRESHOLD quantized/qmm.hip:792 positive integer, else ignored heuristic quantized_matmul takes dequantize + GEMM exactly when M >= the value.
MLX_ROCM_QMM_DEQUANT_CACHE_SIZE quantized/qmm.hip:983 non-negative integer 8 Entries in the LRU cache of dequantized weights; 0 disables it.
MLX_ROCM_QMM_DEQUANT_CACHE_MAX_BYTES quantized/qmm.hip:999 non-negative integer, bytes 268435456 (256 MiB) Byte cap of that cache; 0 disables it.
MLX_ROCM_GEMM_F32_SOLUTION_INDEX matmul.cpp:194, gemms/rocblas_gemm.cpp:54, quantized/qmm.hip:1005 non-negative integer 0 (rocBLAS standard algorithm) rocBLAS solution index for single f32 GEMMs.
MLX_ROCM_GEMM_F32_BATCHED_SOLUTION_INDEX same files non-negative integer the single f32 index Same, for batched f32 GEMMs.
MLX_ROCM_GEMM_BF16_SOLUTION_INDEX matmul.cpp:205, gemms/rocblas_gemm.cpp:65, quantized/qmm.hip:1016 non-negative integer 0 rocBLAS solution index for single bf16 GEMMs.
MLX_ROCM_GEMM_BF16_BATCHED_SOLUTION_INDEX same files non-negative integer the single bf16 index Same, for batched bf16 GEMMs.
MLX_ROCM_GEMM_BF16 gemms/hipblaslt_gemm.cpp:259 first character 1, o or O off Forces bf16 instead of fp8 e4m3 for half-precision GEMMs on devices whose hipBLASLt has fp8 kernels (RDNA4). true does not enable it.
MLX_ROCM_NO_HIPBLASLT_EPILOGUE gemms/hipblaslt_gemm.cpp:378 presence epilogue on Disables the hipBLASLt bias epilogue that fuses a Linear bias into the GEMM.
MLX_ROCM_HIPBLASLT_TUNE gemms/hipblaslt_gemm.cpp:768 presence off Times every returned hipBLASLt algorithm per shape and keeps the fastest; costs warm-up time.
MLX_ROCM_GEMM_DEBUG gemms/hipblaslt_gemm.cpp:1252 presence off Prints each hipBLASLt GEMM's shape, transposes and epilogue to stderr.
MLX_ROCM_SORTED_GATHER matmul.cpp:1205 exactly 0 disables on Collapses consecutive same-expert rows of an M == 1 gather_mm into one tiled GEMM per run.
MLX_ROCM_MOE_SEG_MIN matmul.cpp:1215 integer >= 1, else ignored 1 Minimum average rows per expert run for segment GEMMs instead of the gemv gather.
MLX_ROCM_MOE_PACK matmul.cpp:1309 first character 0, f, F, n, N disables on Pack plus strided-batched hipBLASLt for bf16 1-D MoE gather_mm, padded rows, no device-to-host copy.
MLX_ROCM_MOE_DEVICE_SEG matmul.cpp:1317 first character 1, o, O, t, T off VALU device-segment path instead of the pack.
MLX_ROCM_MOE_PACK_EXACT matmul.cpp:1323 as above off Legacy exact max-run through a device-to-host copy (synchronizes).
MLX_ROCM_MOE_ASYNC matmul.cpp:1491 as above off Host path inside hipLaunchHostFunc; the code warns it can deadlock on some ROCm builds.
MLX_ROCM_MOE_NO_PIN_CACHE matmul.cpp:1551 as above off Stops reusing the pinned host copy of the sorted indices across the gate, up and down calls.
MLX_ROCM_MOE_PIN_STATS matmul.cpp:1582 presence off Prints [moe-pin] hits=... misses=... to stderr every 200 calls.
MLX_ROCM_MOE_PAD_SYNC gemms/naive_gemm.hip:2034 first character 0, n, N, f, F: heuristic only; a, A, 2: measure every call; else cache cache (one sync per (T, E)) Padded row count for the MoE pack GEMMs (matmul.cpp:1369, moe_swiglu.cpp:144, :576); heuristic-only may read out of bounds under extreme imbalance (gemms/naive_gemm.h:144-146).
MLX_ROCM_MOE_ZERO_SYNC moe_swiglu.cpp:133, :548 first character 0, f, F, n, N disables on Fused sorted-MoE SwiGLU forward and VJP use pack plus batched hipBLASLt with no device-to-host copy; off uses host run-length segments.
MLX_ROCM_MOE_VJP_DEVICE_SEG moe_swiglu.cpp:556 first character 1, o, O, t, T off VJP uses VALU token tiles and segmented weight gradients.
MLX_ROCM_FINEGRAINED allocator.cpp:95 integer, nonzero on on for an integrated GPU, off for discrete Fine-grained host-coherent device memory.
MLX_ROCM_USE_ASYNC_POOL allocator.cpp:112 presence off Stream-ordered hipMallocAsync pool.
MLX_ROCM_FORCE_ASYNC_POOL allocator.cpp:113, :351 presence off As MLX_ROCM_USE_ASYNC_POOL, and also creates the pool on gfx1201, which is skipped by default.
MLX_ROCM_NO_ASYNC_POOL allocator.cpp:110 presence unset Forces the pool off; wins over the two above.
MLX_ROCM_ALLOW_MANAGED_FALLBACK allocator.cpp:181 presence off (integrated GPUs always allow it) On a discrete GPU, retries a failed hipMalloc with hipMallocManaged instead of throwing.
MLX_ROCM_NO_MANAGED_FALLBACK allocator.cpp:183 presence unset Forbids that fallback on every device; a failed hipMalloc throws.
MLX_ROCM_FORCE_WARP_SIZE device.cpp:147 32 or 64, else ignored with a stderr line the device's wavefront size Overrides the host-side launch width; debug only, a wrong value produces garbage output.

Scope

In scope: docs/environment-variables.md (new section, the 43 rows, the moved prose, the MLX_ROCM_QMM_DEQUANT_GEMM correction) and docs/installation.md (one paragraph, one table row).

Out of scope: code changes to any parser (#2152 covers the GEMM integers); the overlay's undocumented non-MLX_ROCM_ variables (MLX_GRAPH_*, MLX_QMV_IDOT, MLX_QMV_IDOT_GATHER, MLX_QMV_NO_WIDE, MLX_SDPA_*, MLX_HIPBLASLT_*, MLX_NO_HIPBLASLT, MLX_HSACO_CACHE_DIR, MLX_GRIDX_MULT, MLX_NO_CONCAT_SPLIT, MLX_PURE_DEBUG, MLX_COUNT_COPIES), which need their own issue.

Implementation Notes

  • Reuse: the table layout of "Hardware and kernel diagnostic variables" (docs/environment-variables.md:516); the markdown conventions already in that file.
  • Constraints: describe what the code does today; do not mark a variable recommended for deployment. Mark MLX_ROCM_FORCE_WARP_SIZE, MLX_ROCM_MOE_ASYNC, heuristic-only MLX_ROCM_MOE_PAD_SYNC and MLX_ROCM_GROUPED_PREFILL as debug or experimental, as their code comments do.
  • Edge cases: for presence variables, say outright that =0 enables; for first-character parsers, list the accepted characters, not words.

Acceptance Criteria

  • docs/environment-variables.md has a ## ROCm backend variables section with all 43 rows, each with values, default and effect matching the code, and the six runtime variables already documented in it.
  • The comparison in Verification prints only MLX_ROCM_HOST_STAGE.
  • The MLX_ROCM_QMM_DEQUANT_GEMM text says only unset or exactly 1 enables the route.
  • docs/installation.md's ROCm section names MLX_ROCM_GATHER_QMV_EXPERT_BATCHED=0 and links to the new section, and its runtime-variable table points to it.
  • Internal links resolve (anchors checked by hand or with the docs build).

Verification

comm -23 <(grep -rhoE 'MLX_ROCM_[A-Z0-9_]+' src/lib/mlx-cpp/patches-rocm/mlx src/lib/mlx-cpp/patches-rocm/CMakeLists.txt | sort -u) <(grep -oE 'MLX_ROCM_[A-Z0-9_]+' docs/environment-variables.md | sort -u)
grep -n 'MLX_ROCM_GATHER_QMV_EXPERT_BATCHED' docs/installation.md docs/environment-variables.md

A pass: the first command prints MLX_ROCM_HOST_STAGE and nothing else; the second finds the variable in both files.

Activity

  1. added
    type:docsDocumentation improvements or additions
    area:docsUser and developer documentation
    platform:linuxLinux (CUDA / packaging) specific
    on Oct 6, 2026
  2. added a commit that references this issue on Oct 12, 2026
    b34d517
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:docsUser and developer documentationplatform:linuxLinux (CUDA / packaging) specificpriority:lowLow prioritystatus:doneCompletedtype:docsDocumentation improvements or additions

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions