Skip to content

[AMD] Fix DeepSeek-V4 fused-RMS FP8 scale metadata on gfx950 - #31727

Merged
HaiShaw merged 2 commits into
sgl-project:mainfrom
hdt98:users/hdt98/fix_dsv4_bpreshuffle_scale_layout
Aug 2, 2026
Merged

HaiShaw merged 2 commits into
sgl-project:mainfrom
hdt98:users/hdt98/fix_dsv4_bpreshuffle_scale_layout

Conversation

@hdt98

@hdt98 hdt98 commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Note

Exact two-node TP8/DP8/EP8 MORI validation remains pending; the closest available TP4/DP4/EP4 results are below.

Motivation

Fixes #31490. DeepSeek-V4-Flash FP8/FP4 on gfx950/ROCm 7.2 regressed from the ~0.925 GSM8K band to ~0.912 in the 1P1D DP-attention workload.

Root Cause

DeepSeek-V4's separate fused-RMS FP8 producer calls AITER with transpose_scale=True. AITER writes CK-ready transposed scale storage but returns contiguous-looking [M, G] metadata. For logical scales [[a,b,c],[d,e,f]], the storage is [a,d,b,e,c,f]; it needs stride (1, M), while contiguous metadata reads it as [[a,d,b],[e,c,f]].

The CK materializer added by #29275 trusted that metadata, associating valid scales with the wrong token/group cells when M > 1. Real-AITER A/B tests show bit-identical FP8 activations, an unchanged BF16 side output, and exact logical
scales after the zero-copy stride repair with the same storage pointer. M = 1 remains exactly unchanged. This tuple is injected only into DeepSeek-V4 attention-side dense GEMMs.

My thought is this points toward batch dependence and excludes wholesale activation corruption, but it was my mistake in not covering this case: a scale permutation can still cause a large GEMM error. The ~1–2 GSM8K-point effect is the measured end-to-end result, not a bound.

The #31490's TP == DP topology has attention TP size one. A matching TP4/DP4/EP4 trace that I was able to run saw zero attention gathers and CK in all 240 relevant GEMMs, excluding the initially suspected Triton/collective path from this issue.

Modifications

  • Restore truthful [M, G] strides at the DeepSeek-V4 AITER producer with a zero-copy view.
  • Test exact values, strides, storage aliasing, and decode-like/batched shapes with CPU and real AITER.

The shared DeepSeekV2/GLM producers, generic CK materializer, communicator, and Triton path are unchanged.

Accuracy Tests

DeepSeek-V4-Flash, two MI35x nodes, 1P1D TP4/DP4/EP4 per role, MORI, 8-shot GSM8K (1319 questions):

Configuration Stock Candidate
FP8, current CK 0.919 0.921 / 0.927
FP8, CK #8639 0.913 0.928
FP4, current CK 0.911 / 0.912 0.924 / 0.930 / 0.930

The stochastic runs show recovery and compatibility with current and future CK #8639.

Speed Tests and Profiling

Controlled candidate/stock; same model, topology, image, warm-ups, and 1024-token input/output workload:

Concurrency TPOT (lower is better) Output throughput
4 +0.187% -0.295%
16 -1.203% +1.174%
32 -1.047% +1.014%

No TPOT or output-throughput regression was observed.

Checklist

  • pre-commit run --all-files passed.
  • CPU and registered real-operator tests added.
  • Accuracy and speed results included.
  • No user-facing API/configuration or documentation change.
  • Forward-path change is code-style-compliant and zero-copy.

Review and Merge Process

Exact TP8/DP8/EP8 MORI remains the final integration validation gate before merge.


CI States

Latest PR Test (Base): 🚫 Run #30687653207
Latest PR Test (Extra): ❌ Run #30687653166

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces view_aiter_fused_rms_transposed_fp8_scale in fp8_utils.py to correctly restore logical [M, G] indexing for AITER fused-RMS transposed FP8 scales without copying physical bytes. This utility is integrated into the DeepSeek-V4 model's fused RMSNorm FP8 quantization path when _use_aiter_bpreshuffle_gfx95 is enabled. Comprehensive unit and integration tests have been added to validate the layout contract and correct behavior. No review comments were provided, so there is no feedback to address.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@michaelzhang-ai

Copy link
Copy Markdown
Collaborator

Picking up the pending two-node TP8/DP8/EP8 gate you flagged — I have a MI355X 1P1D disagg repro/harness for exactly these legs.

Dispatched this PR head (5cab80f1) on the MI355X 2N 1P1D disagg workflow (checkout-runtime applies this branch's Python over the current image), configs dsv4flash-fp8-1k1k-1p1d-dp8ep8 + dsv4flash-fp4-1k1k-1p1d-dp8ep8 (TP8 / DP8 / EP8, mori + unified_kv_triton):

Will post the GSM8K numbers when it lands. Nice find on the DeepSeek-V4 fused-RMS producer being the missed case — that explains why a consumer-only attempt on my side (#31617, x_scale.contiguous()) didn't move the needle: the mis-associated metadata originates upstream in _fused_rmsnorm_fp8_quant, not in aiter_w8a8_block_fp8_linear.

@michaelzhang-ai

michaelzhang-ai commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Two-node TP8/DP8/EP8 gate — PASS. run 29773160421 on this PR head (5cab80f1), MI355X 2N 1P1D disagg (mori + unified_kv_triton), GSM8K 1319Q / 8-shot:

config GSM8K (this PR) stock (regressed)
dsv4flash-fp8-1k1k-1p1d-dp8ep8 0.923 ~0.904–0.917
dsv4flash-fp4-1k1k-1p1d-dp8ep8 0.928 ~0.910–0.913

Both jobs completed/success, back in the ~0.925 good band and clear of the 0.91 gate — consistent with your TP4/DP4/EP4 numbers (FP8 0.921–0.928, FP4 0.928). So the fix holds at the TP8/DP8/EP8 topology too. LGTM.

@hdt98 @Lzy17 please veirfy.

@michaelzhang-ai

Copy link
Copy Markdown
Collaborator

Extra corroboration — same two configs, stock main vs this PR, on the same MI355X 2N 1P1D disagg harness:

config (TP8/DP8/EP8) stock main — nightly #114 (29716984185) this PR (29773160421) Δ
dsv4flash-fp8-1k1k-1p1d-dp8ep8 0.911 0.923 +0.012
dsv4flash-fp4-1k1k-1p1d-dp8ep8 0.914 0.928 +0.014

On stock main these two dp8ep8 flash legs are the only ones in the thin regressed band (0.911 / 0.914) — they passed nightly #114 by noise, right on the 0.91 gate. Every other DSV4 leg on that nightly sits at 0.917–0.946 with margin (and Kimi non-MTP is 0.948 after #31368). This PR lifts the two affected legs back into the ~0.925 good band with real headroom (+~0.013, matching #31490's measured effect). Net: this is the change that takes those legs from "flickering on the gate" to safely green.

@michaelzhang-ai
michaelzhang-ai self-requested a review July 21, 2026 00:23
@Lzy17

Lzy17 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Independent verification on MI355X — confirmed on both the dp8ep8 1P1D and the wide-EP16 2P1D topologies. Overlaid only the two changed files (fp8_utils.py + deepseek_v4.py) from this PR onto a stock image that still carries #29275, and ran stock vs. this-PR back to back. GSM8K 1319Q / 8-shot, gate 0.91.

dp8ep8 1P1D (image v0.5.16-rocm720-mi35x-20260728), reproducing your table:

leg stock (buggy) + this PR
dsv4flash-fp4-dp8ep8 0.913 0.926
dsv4flash-fp8-dp8ep8 0.915 0.924

Matches your TP8/DP8/EP8 numbers (fp8 0.923 / fp4 0.928) — the fix lands the affected legs back in the ~0.925 band with headroom.

Wide-EP16 2P1D (4-node, image v0.5.15.post1-rocm720-mi35x-20260722) — extra coverage not in the earlier runs:

leg stock + this PR
dsv4flash-fp8-base 0.902 0.924
dsv4flash-fp4-base 0.901 0.933
dsv4flash-fp8-mtp 0.915 0.924
dsv4flash-fp4-mtp 0.918 0.929
dsv4pro-fp8-base 0.923 0.951 (no regression)

The four Flash legs are the only ones that sat in the thin regressed band; on EP16 the two non-MTP Flash legs were actually below the 0.91 gate on a 3-run mean (0.902 / 0.901), and this PR clears all of them. Pro was already green and stays green (no regression). Net: confirmed effective across dp8ep8 and wide-EP16, FP8 and FP4, base and MTP. LGTM from the EP16 side too.

@hdt98

hdt98 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor Author

thanks @Lzy17 for adding the validation runs on MI355X for both dp8ep8 1P1D and EP16 2P1D topologies. Can a reviewer trigger /rerun-failed-ci or /run-ci-extra on either this or #32839 to clear gates?

@XinyuJiangCMU

XinyuJiangCMU commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Independent confirmation from a different workload.

I saw the same issue in DeepSeek-V4-Flash FP8 RL training on four MI355X nodes. I measured train_rollout_logprob_abs_diff, which is the per-token difference between the logprob recomputed by the trainer and the logprob returned by the rollout engine.

At step 0, I kept the weights, nodes, and recipe the same and only changed the code. The normal run-to-run noise is about 0.0015.

Arm abs_diff at step 0
upstream baseline 0.21432
plus an unrelated ROCm compressor change 0.16284
plus this PR 0.04381

A known-good tree gives 0.04452 on the same setup, so adding this PR brings the result back to the healthy range.

@michaelzhang-ai

michaelzhang-ai commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

The PR is ready to merge. @hdt98 @HaiShaw

@hdt98

hdt98 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

Thanks @michaelzhang-ai and @HaiShaw with new checkpoint weights for DeepSeek V4 Flash released today and published strong benchmark, please consider getting this enhancement fix in.

https://huggingface.co/deepseek-ai/DeepSeek-V4-Flash-0731

image

XinyuJiangCMU added a commit to XinyuJiangCMU/sglang that referenced this pull request Aug 1, 2026
AITER's fused-RMS with transpose_scale=True returns transposed physical bytes
behind row-major-looking metadata, so downstream CK materialization permutes the
scales. sgl-project#31727 restores the logical view with a zero-copy as_strided.

That PR has been open since 2026-07-19 and is not in main or sglang-miles, so
carrying it here is the only way this branch gets it. It had been living as a
container-side patch script, which meant it disappeared the moment the
containers were rebuilt on the new image: this run came back at
train_rollout_logprob_abs_diff 0.1696 against 0.0448 on a tree known to be
healthy, with SGLANG_OPT_USE_JIT_NORM=false confirmed present in all eight
schedulers. A patch that only exists inside a container is not carried by
anything -- not git, not the md5 check, not the env readback.

Taken verbatim from the PR, tests included, so it drops out cleanly if it lands
upstream.

Co-authored-by: Zhiyao Jiang <jessicajiang324@gmail.com>
@HaiShaw

HaiShaw commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

@hdt98 please post test results from new tests added.

@HaiShaw HaiShaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

AMD path changes only

@HaiShaw

HaiShaw commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

@amd-bot ci-status

@HaiShaw
HaiShaw merged commit 1685d29 into sgl-project:main Aug 2, 2026
128 of 165 checks passed
@amd-bot

amd-bot commented Aug 2, 2026

Copy link
Copy Markdown

@HaiShaw

CI Status for PR #31727

Merge verdict: Already merged (as 1685d29f). For the record: this PR's changed code was exercised and passed on the relevant suites, and none of the executed CI failures are attributable to this PR — they are all infra/environment issues (NVIDIA uv cache-lock timeout, an AMD JIT/HIP compile error in an unrelated scripted-runtime test, and an XPU backend timeout). NVIDIA base-b signal was incomplete (fast-fail cascade off an infra failure), but that path is irrelevant to this AMD/gfx950 FP8 change.

Note

The changed paths are covered by tests that ran and passed on this head SHA:

  • test/registered/quant/test_fused_rms_fp8_group_quant.pystage-a-test-1-gpu-small-amd ✅ (MI300)
  • test/registered/unit/layers/test_fp8_bpreshuffle_scale.pybase-a-test-cpu ✅ (patches _use_aiter_bpreshuffle_gfx95=True to force the gfx95 code path)
  • The gfx950 runner job stage-b-test-1-gpu-small-amd-mi35x (linux-mi35x-gpu-1) ✅

Caveat (author-acknowledged in the PR): the exact two-node TP8/DP8/EP8 MORI validation was not run in CI; only TP4/DP4/EP4-equivalent coverage exists. That topology remains unverified by PR CI.

Changed files: layers/quantization/fp8_utils.py (+11), models/deepseek_v4.py (+8), test/registered/quant/test_fused_rms_fp8_group_quant.py (+42), test/registered/unit/layers/test_fp8_bpreshuffle_scale.py (+55)

Executed CI failure attribution: AMD: 1 executed failure (0 related) · Others (NVIDIA/XPU): 2 executed root-cause failures (0 related). All remaining base-b-* / *-finish / wait-for-* reds are fast-fail cascades or aggregator jobs off these root causes.

AMD Executed Failures

Job Test File Test Function Error Related? Why
stage-b-test-1-gpu-small-amd (8) test/registered/scripted_runtime/test_scripted_runtime_core.py N/A (server-launch timeout) HIP JIT compile error amd_warp_sync_functions.h:306: static assertion 'sizeof(unsigned int) == 8' → Scheduler crash → TimeoutError: HTTP server did not connect within 300s 🟢 JIT kernel compile failure in the scripted-runtime path on the ROCm 7.0 image; touches no FP8/DeepSeek-V4 code. Image-pull manifest unknown also seen at job start — infra.

Other Executed Failures

Job Test File Test Function Error Related? Why
base-b-test-1-gpu-small (0) N/A (env setup) N/A uv … Failed to acquire lock on the distribution cache … Timeout (300s) during uv pip install -e python 🟢 Runner-level uv cache-lock contention; not code. This is the root cause of the entire base-b fast-fail cascade (large/2-gpu/b200 shards skipped or cancelled).
stage-b-test-1-gpu-xpu test/registered/xpu/test_intel_xpu_backend.py N/A (timeout) TIMEOUT after 1200s; preceded by torchcodec … libavutil.so.* cannot open shared object file 🟢 Intel XPU backend timeout + missing torchcodec system libs; unrelated backend and env issue.

Details / what to do before merge (moot — already merged)

  • Nothing to action on this PR's account: no 🔴/🟡 executed failures.
  • If re-triggering for confidence were desired, the three root causes (NVIDIA uv lock timeout, AMD scripted-runtime HIP JIT compile, XPU torchcodec/timeout) would each just need a rerun — none require a code change here.
  • Genuinely-unverified path is the TP8/DP8/EP8 two-node MORI validation called out by the author; that is a runtime-topology gap PR CI never exercised, not a CI failure.

Generated by amd-bot using Claude Code CLI

karverma-amd added a commit to karverma-amd/sglang that referenced this pull request Aug 6, 2026
…ducer tests, M==1 gate)

Addresses kkHuang-amd's review on sgl-project#33166:

- Consolidate the two identical stride-reinterprets into one producer-neutral
  helper. Main gained view_aiter_fused_rms_transposed_fp8_scale (sgl-project#31727) after
  this PR opened; it and this PR's bpreshuffle_fp8_scale_nocopy both do
  as_strided(scale, scale.shape, (1, scale.shape[0])). Merge into
  view_transposed_fp8_scale_nocopy(+_tuple); repoint deepseek_v4 (fused-RMS),
  deepseek_v2 (MoE down), forward_mla (MLA o_proj).
- Extract the emit gate (previously the triplicated
  `_use_aiter_bpreshuffle_gfx95 and shape[0] >= 2`) into
  emit_transposed_bpreshuffle_scale(m, on_bpreshuffle_gfx95=...), so the M>=2
  boundary is centralized and unit-testable.
- Add an MI35X producer-level test that invokes both real producers
  (fused_clamp_act_mul, fused_flatten_fp8_group_quant) with transpose_scale
  True+nocopy vs False+materialize, asserting bit-identical quantized output,
  equal scale values, the (1, M) column-major stride, and zero-copy sharing.
  This is the direct producer-level evidence the CPU stride test could not give.
- Pin the M==1 materialize fallback: gate returns False at M==1 (materialize
  path), and the materialized single-token scale keeps its values and (1,1)
  stride. Producer test also exercises M==1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
karverma-amd added a commit to karverma-amd/sglang that referenced this pull request Aug 6, 2026
…or consistency

Per review follow-up, consolidate onto main's existing
view_aiter_fused_rms_transposed_fp8_scale (sgl-project#31727) rather than a new neutral
name; add a matching _tuple variant and repoint the deepseek_v2 (MoE down) and
forward_mla (MLA o_proj) producers and the tests to it. Behavior unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
karverma-amd added a commit to karverma-amd/sglang that referenced this pull request Aug 6, 2026
…al-path test, M==1)

Addresses kkHuang-amd's review on sgl-project#33165:

- Consolidate the inline `torch.as_strided(x_scale, x_scale.shape,
  (1, x_scale.shape[0]))` in aiter_w8a8_block_fp8_linear onto the shared
  view_aiter_fused_rms_transposed_fp8_scale helper (main sgl-project#31727), for both the
  fresh-quant no-copy branch and the pre-existing triton row-major-view branch,
  so the metadata repair lives in one documented place.
- Add an MI35X real-path test (test_fp8_bpreshuffle_dense_linear_mi35x.py):
  quant-producer equivalence (aiter_per1x128_quant transpose_scale True vs
  False: identical quantized bytes, equal scale values, (1, M) stride, zero-copy)
  and end-to-end aiter_w8a8_block_fp8_linear new-path vs old-path (forced via
  patch) bit-exact GEMM output. Exercises the real quant + CK bpreshuffle GEMM.
- Pin the M==1 materialize fallback: CPU test for the single-row materialize
  values/(1,1) layout, and M==1 covered in the MI35X real-path test.
- Update the CPU test to use the shared helper instead of a local copy of the
  as_strided expression.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Atituiset pushed a commit to Atituiset/sglang that referenced this pull request Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] GSM8K accuracy regression on DeepSeek-V4-Flash-FP8 (dp8ep8, gfx950) traced to #29275 "Fix gfx95 bpreshuffle FP8 activation scale layout"

6 participants