Skip to content

[AMD] DeepSeek-V4.1-Flash on MI350X (gfx950) - #39186

Closed
kevin-mii wants to merge 40 commits into
sgl-project:dsv4.1from
kevin-mii:dsv41-amd
Closed

kevin-mii wants to merge 40 commits into
sgl-project:dsv4.1from
kevin-mii:dsv41-amd

Conversation

@kevin-mii

@kevin-mii kevin-mii commented Sep 12, 2026 •

Copy link
Copy Markdown
Collaborator

Enable DeepSeek-V4.1 on AMD MI350X (gfx950), based on dsv4.1 at 099615b20e.

Changes

  • HIP JIT kernels and Triton implementations for mHC, FP4 indexing, routing, quantization, and gfx950 dense GEMMs.
  • Fuse query RoPE into padded attention output on HIP decode and gfx950 large prefill, preserving AMD BF16 rounding.
  • HIP attention metadata, sorted top-k selection, V4.1 FP8/FP4 KV stores, low-ratio decode/verification, and multistream preparation.
  • AMD model and AITER MoE dispatch, fused sorting, and shared-expert reduction.
  • Select attention graph variants from the DP-group maximum sequence length so ranks replay matching graphs.
  • Fix the shared-expert fusion guard for ROCm DeepSeek-V3 and apply the review-requested ROCm source-pointer casts.

Scope and external dependency

Changes outside python/sglang and test are limited to the ROCm Dockerfile, the AITER synchronization patch, and the DSV4.1 EP4 tuning CSV. The pinned AITER stage-1 kernel can leave LDS-DMA loads in flight across a barrier. The patch drains those loads and fences their scheduling; dropping it would remove a correctness fix.

The PR no longer changes global Docker environment defaults, pre-commit configuration, or documentation. The model-specific CSV was restored after measuring the performance effect of removing it. The existing AITER pin and cherry-picks come from the target branch. No workflow changes are introduced relative to the target.

AMD runtime switches remain in srt/environ.py: SGLANG_OPT_HIP_FUSE_SINKHORN_INTO_NORM, SGLANG_OPT_HIP_FUSED_MOE_REDUCE_ADD, and SGLANG_OPT_HIP_FUSED_DECODE_GLUE default on; SGLANG_OPT_HIP_ATTN_KV_SPLITS=4 (0 uses AITER's cost model).

Latest optimization port and rebase

Rebased all 16 existing commits onto 099615b20e; git range-diff reports each patch unchanged. This incorporates the shared deterministic-router FP32 output and graph token-count fixes.

On MI350X with ROCm 7.2.4:

  • RoPE, model dispatch, and decode/prefill graph helpers: 33 passed, 10 subtests. RoPE checks exact BF16 output, untouched input/padding, changed-position graph replay, and prefill output buffers larger than 2 GiB.
  • AMD regression directory: 105 passed, 4 skipped, 86 subtests, using the same environment below.
  • Deterministic router: FP32 output preserves 16 close scores both with and without batch-invariant mode.
  • RoPE+copy versus fused store, graph-replay microbenchmarks: decode 1.95–2.67x; prefill 4096 tokens 28.85 -> 23.59 us and 65536 tokens 516.31 -> 467.83 us. These are operator timings, not model throughput.
  • RoPE tests also pass independently on all eight MI350X GPUs: 5 tests and 8 subtests per GPU. This checks each GPU, not collective communication.
  • Changed-file pre-commit checks pass.
  • Eight-rank DSpark collective regression currently aborts inside RCCL graph capture. It also aborts on pre-rebase 217338f4b5; a temporary test using the production PyNccl capture context also aborts. No test or production workaround is committed. The earlier successful eight-rank result below has not been reproduced in this run.

The other optimization gaps remain separate follow-ups: Engram prefetch, routed-MoE quantization overlap, mHC statistics scheduling, CP dense indexing, and broader bounded replay support. Full-model validation still needs weights and a launch/benchmark configuration.

Earlier validation (before the latest rebase)

On eight MI350X VF GPUs with PyTorch 2.11.0+rocm7.2 and ROCm 7.2.4:

  • Targeted indexer, FP4 rounding, KV-store, mHC, quantization, MoE, metadata, and graph-policy suites passed.
  • Eight-rank DSpark sharded selection: 36 passed and 36 NVLink-only skips per rank using the upstream test configuration. Experimental PyNccl capture configurations aborted inside RCCL; those test edits were reverted.
  • Pipelined mHC/quantization tests required TRITON_HIP_USE_ASYNC_COPY=0; the default produced HIP error 401 before and after rebasing. No production workaround is added here.
  • Cleanup AMD suite: 105 passed, 4 skipped, 86 subtests with TRITON_HIP_USE_ASYNC_COPY=0 SGLANG_USE_ROCM700A=1 ROCM_QUICK_REDUCE_QUANTIZATION=INT8.
  • Cleanup model/graph regressions: 34 passed and 17 subtests with the restored ROCm defaults. An initial combined run aborted in AITER mHC; the separate rerun passed.
  • Pre-commit passes for all PR-changed files, including test registration with GITHUB_BASE_REF=dsv4.1. The all-files run hit an unrelated Rust Clippy file-descriptor limit.
  • Moved the 15 new MI35x regression files into test/registered/amd/dsv41/ to satisfy the target branch taxonomy without changing their CI runner assignments.
  • Original and cleaned AITER patches apply to the pinned source and produce identical Python ASTs.

Not executed: full-model inference, accuracy, throughput, and context-parallel inference after the rebase (no weights or benchmark configuration available); a fresh Docker build; CUDA and ROCm 10 validation. The context-parallel fallback has request-mapping coverage only. The local AITER installation does not have the stage-1 patch applied, so the kernel regressions do not validate that race fix.

CSV restoration measurements

Using current AITER 0138f88b2 with the synchronization fix on both paths, five gfx950 graph-replay microbenchmarks measured 1.06-1.29x operator speedup from the CSV. At one token, default 48.37 us -> tuned 37.44 us; at 4096 tokens, 1411.56 us -> 1248.79 us. All 12 rows survive normal AITER config merging and do not collide with existing keys.

The upstream tuning draft includes the benchmark and full methodology. This is synthetic operator timing, not full-model accuracy or throughput validation. Tuned/default maximum absolute differences were 0.0273-0.0410; the numerical tolerances still need independent validation. The synchronization tests pass both patched and unpatched current main, so they exercise the path without reproducing the reported intermittent race.

A second comparison on the installed SGLang-pinned AITER 4ad998328 also favored the CSV: M=1: 43.05 -> 37.17 us (1.16x), M=32: 291.22 -> 262.54 us (1.11x), M=128: 343.24 -> 316.97 us (1.08x), M=512: 419.93 -> 399.23 us (1.05x), M=4096: 1359.34 -> 1239.05 us (1.10x). This installation does not include the synchronization patch; the same installation was used for both sides. These are a8w4 measurements with AITER_BF16_FP8_MOE_BOUND=0, not the default small-batch bf16 route.

Historical results before rebase and cleanup

The original PR reported the following results on four MI350X GPUs. These have not been reproduced on the current revision, and depended on the earlier deployment setup, including global Docker defaults that are no longer changed by this PR.

DSpark, blog protocol (same method as the DeepSeek-V4.1-Flash kernel post: its prompt.json of 4096 random ids at seed 42, 1024 output tokens with ignore_eos, SGLANG_SIMULATE_ACC_LEN=5.5 / match-expected, SGLANG_RAGGED_VERIFY_MODE=static, block size 5, its benchmark.py with 6 runs per launch after a discarded warm-up, throughput from the first to the last streamed event):

BS=1, random 4k/1k DSpark off, EP4 DSpark on, EP4 DSpark on, TP4 MoE
output tokens/s (median) 147.11 548.15 (2 launches, 12 runs, 538.75-553.43) 555.74 (1 launch, 549.88-559.98)
measured accept length - 5.505 5.505

For reference the post reports 223.50 / 853.49 / 873.63 on 4x GB300 with the same protocol.

High-throughput recipe (--disable-radix-cache, real text): decode bs1 / bs8 / bs32 127 / 851 / 2382 -> 149 / 937 / 2694 tok/s; gsm8k 5-shot (1319) 0.898-0.916 across gated trees, in the 0.90 band. Prefill first-token logprob spread 0 across batch sizes 1-32 at 64/1024/2048 tokens; greedy decode identical across runs and page layouts (decode top-k is ordered by position, not KV slot).

Follow-ups

Upstream drafts: AITER synchronization fix #5561 and EP4 tuning #5562. Remove the downstream patch/CSV when the AITER pin includes the respective fixes. Deployment defaults remain a separate concern. Encoder SWA bounded replay and Engram KV prefetch remain CUDA-only. The previously reported DP prefill row asymmetry and eager idle-batch Engram assertion remain outside this cleanup.


CI States

Latest PR Test (Base): ❌ Run #35039152924
Latest PR Test (Extra): ❌ Run #35039152740
Latest PR Test (AMD ROCm 10): ❌ Run #35039152911

hnyls2002 and others added 28 commits September 15, 2026 16:38
…top-k epilogue

gfx950 ports of the DeepSeek V4 JIT kernels (index-K/Q fp4 packing, compressor stores, mHC boundary,
MXFP8 GEMV, fused K norm-rope) with the native fp8 type aliases, and an in-kernel sort of each row's
picks in the AOT top-k transform (sort_output) so the sparse kernels sum in a fixed order.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019tZvxNDYPWtWc7ZtfXuybA
…n ROCm

The fp4 indexer host (one-launch index-Q pack, sorted paged top-k, candidate blocks), the RoPE
fake-quant kernels, the mHC boundary with the Sinkhorn reduce hosted in the next RMSNorm launch, and the
decode glue launches (length fold, compression metadata, page table) that replace the torch
glue on the launch-bound decode step.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019tZvxNDYPWtWc7ZtfXuybA
The ROCm router gate (split-K GEMV up to 64 rows, one-launch gate + sort at M <= 2), the shared-expert add
folded into aiter's FlyDSL top-k reduction, the gfx950 native MXFP8 route and fp8-grid producers, and the
split-K bf16 x fp8 batched GEMM for the wo_a decode projection.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019tZvxNDYPWtWc7ZtfXuybA
… aiter sparse decode

The HIP radix attention backend (breakable prefill graphs, DSpark verify metadata, aiter_sparse decode with
a pinned split-KV count, decoder SWA bounded replay), the FlyDSL low-ratio indexer, FlashMLA glue, and the
aiter fp4 index-K cache layout behind one hook in the CUDA backend and the memory pool. The decode top-k
is ordered by position so attention bits do not follow the KV page layout.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019tZvxNDYPWtWc7ZtfXuybA
aiter MoE runner with fused sorting and the fused reduce-add scope, the one-launch MoE front, gfx950 fp8
dispatch into fp8_hip, the clamped SiLU, and the fused reduce-add gate that tolerates a fused or absent
shared expert.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019tZvxNDYPWtWc7ZtfXuybA
…dense route, Engram

if _is_hip dispatches from the model into deepseek_common/amd (fused mHC boundary with deferred
coefficients, gfx950 dense helpers, index-Q one-launch, fused decode glue, SWA tail slicing), the Engram
image select, the new SGLANG_OPT_HIP_* switches, and the ROCm defaults for the aiter batched GEMM and
the FlashMLA backend. The Engram hash kernel runs on HIP too, so upstream's in-kernel decode commit
(sgl-project#39138) serves both platforms and the history is written once per step.

deepseek_v4_low_ratio_sources and _every_row_routed follow the ForwardBatch rename
num_token_non_padded_cpu -> global_num_token_non_padded_cpu (the old name is no longer a field), which
the HIP low-ratio path reads under the breakable prefill graph.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019tZvxNDYPWtWc7ZtfXuybA
With --enable-dp-attention every DP rank must replay the same captured decode graph: the in-graph
collectives pair the ranks' registered buffers by capture order. The variant was chosen from the rank's own
lengths, so an idle rank replayed a different graph from the decoding rank and the busy rank read a
never-written peer buffer (garbage from position 1 on MI350X DP2). Carry the group max seq len through the
scheduler's existing per-step DP sync and select from it. Verified on HIP; Hopper captures no variants.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019tZvxNDYPWtWc7ZtfXuybA
…d FMoE rows

aiter 4ad99832 with two cherry-picks and a patch that drains the LDS-DMA group before the stage-1 K-step
barrier (a8w4 stage 1 was not bitwise repeatable without it), the DeepSeek-V4.1 EP4 a8w4 FMoE rows merged
through aiter's model_configs glob, the documented SGLANG_OPT_HIP_* switches, and a pre-commit exclude so
the whitespace fixers leave patch files intact.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019tZvxNDYPWtWc7ZtfXuybA
Bitwise and tolerance guards for every HIP kernel and path the MI350X recipes take, plus regression tests
for the bugs fixed here (fold-cache invalidation, decode selection order, sorted top-k padding, -1 keys,
saturation and tie rounding, the fused reduce gate, split-K row count, breakable-graph refresh contract).
Registered for the MI35x suite with measured est_time values.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019tZvxNDYPWtWc7ZtfXuybA
Keep the AITER synchronization fix, but leave global ROCm defaults and model-specific FMoE tuning to a separate deployment change.
Use one context line per hunk so the patch needs no whitespace-hook exclusions. Preserve both synchronization fixes and shorten their comments.
Keep the existing MI35x CI suites while conforming to the target branch registered-test layout. Test contents and runner selection are unchanged.
Removing the CSV selects AITER heuristic kernels. Five gfx950 graph-replay microbenchmarks measured 1.06-1.29x speedup with the tuning rows. Keep the model-specific config and its additive Docker copy while the upstream change is reviewed.
Enable the existing Triton store for HIP decode and gfx95 large prefill. Preserve AMD flat-RoPE BF16 rounding by rounding the sine product before the cosine FMA. Register exact-output, padding, graph-replay, and model-dispatch coverage on MI35x.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

amd deepseek documentation Improvements or additions to documentation jit-kernel memory-pool quant LLM Quantization sgl-kernel

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants