[DO NOT MERGE][AMD][DI][CI] Kimi-K2.6 disagg diagnostic recipes for non-MTP GSM8K drop - #30433
Closed
michaelzhang-ai wants to merge 10 commits into
Closed
[DO NOT MERGE][AMD][DI][CI] Kimi-K2.6 disagg diagnostic recipes for non-MTP GSM8K drop#30433michaelzhang-ai wants to merge 10 commits into
michaelzhang-ai wants to merge 10 commits into
Conversation
Co-authored-by: YC Yen-Ching Tseng <yctseng@amd.com>
Contributor
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
…8K drop The MI355X 2N 1P1D disagg nightly's Kimi-K2.6 non-MTP GSM8K only reaches ~0.88 (< 0.92) while the MTP variant passes ~0.95 and single-node non-MTP passes 0.944. page_size and disagg runtime args are already ruled out, and swapping the decode backend has no effect. Since MTP verify runs on the aiter (prefill) backend while non-MTP decode uses the decode backend, but both dispatch to AttnForwardMethod.MLA (absorbed) for their respective forward modes, the remaining axis is the decode FORWARD-MODE path over the MORI-transferred prefix, not the attention backend/kernel. Add three diagnostic recipes (registered in nightly-configs.yaml, perf sweep trimmed to conc=1 since only the GSM8K gate matters; run via workflow_dispatch `configs`): - 1p1d-aiterdecode: non-MTP, decode triton -> aiter (control: backend vs forward-mode). - 1p1d-nocudagraph: non-MTP + --disable-cuda-graph (HIP graph capture/replay over the transferred prefix?). - 1p1d-mtp-specdecode: MTP + --speculative-attention-mode decode (does routing verify through the decode-mode path also drop MTP to ~0.88?). Diagnostic only; not wired into the default nightly signal.
michaelzhang-ai
force-pushed
the
amd/kimik26-disagg-decode-repro
branch
from
July 8, 2026 00:08
1936fc8 to
4da279a
Compare
michaelzhang-ai
requested review from
Fridge003,
HaiShaw,
Kangyan-Zhou,
bingxche,
ispobock and
merrymercy
as code owners
July 8, 2026 00:08
…racy drop backend and HIP-graph are ruled out (aiterdecode=0.879, nocudagraph=0.883, both == baseline 0.878); MTP verify (extend-mode) over the same transferred KV is correct (~0.95). So the drop is intrinsic to the single-token absorbed-MLA decode path reading MORI-transferred KV. Add an env-gated (SGLANG_DEBUG_DISAGG_DECODE_DUMP), one-shot, try/except-wrapped dump in ModelRunner.forward that logs, for the first few decode batches on the decode worker: seq_lens, the req_to_token KV-slot mapping (count / negative slots), and layer-0 KV-pool content at those slots (abs mean, #zero-norm rows, first/last row norms). This distinguishes a wrong decode seq_len/slot mapping from missing/zero transferred KV at attended slots (e.g. the prefix boundary token). New recipe 1p1d-kvdump.yaml wires the env; registered in nightly-configs.
5 tasks
Isolate whether the non-MTP disagg GSM8K drop is page_size=256 in the decode path vs disagg-specific: same as the passing single-node eval plus --page-size 256, registered into the same suite so the MI35x Kimi nightly job runs default (~0.944) and page256 back-to-back on one image.
…imi disagg drop cause Port the split-KV hypothesis onto this branch (own base, not the parallel decodemeta branch). Force triton MLA decode to a single KV split via --triton-attention-num-kv-splits 1 + SGLANG_DEBUG_MLA_DECODE_NO_SPLIT_CAP (skip the MLA split floor) + static fill. The split-KV online-softmax reduction is decode-only (extend/verify stay ~0.95); if nsplit=1 recovers ~0.94 it is the culprit. New recipe 1p1d-nsplit1.yaml, registered in nightly-configs.
This was referenced Jul 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
On the
Nightly Test (AMD MI355X 2N 1P1D Disagg), Kimi-K2.6 non-MTP GSM8K only reaches ~0.88 (gate is 0.92), while:Already ruled out by prior investigation (see #30336 side-notes):
Why the backend is likely NOT the cause:
dispatch_attn_forward_methodroutes non-MTP decode → decode backend and MTP verify → prefill backend (becausespeculative_attention_modedefaults toprefill), but for their respective forward modes both dispatch toAttnForwardMethod.MLA(absorbed) — seemodels/deepseek_common/attention_backend_handler.py(handle_attention_aiter/handle_attention_triton). So the remaining axis is the decode FORWARD-MODE path reading the MORI-transferred prefix, not the attention backend/kernel.SGLANG_FP8_PAGED_MQA_LOGITS_TORCHis intentionally NOT tested here: it only affectslayers/attention/dsv4/(the DSA indexer), which Kimi's aiter/triton MLA path does not use.Modifications
Three diagnostic recipes under
scripts/ci/slurm/recipes/mi355x-fp8/kimik26/1k1k/, registered innightly-configs.yaml. Each isolates ONE variable vs the base1p1d.yaml/1p1d-mtp.yamland trims the perf sweep toconc=1(only the GSM8K gate matters). They are not part of the default nightly signal — trigger explicitly via theworkflow_dispatchconfigsinput:kimik26-fp8-1k1k-1p1d-aiterdecodetriton → aiterkimik26-fp8-1k1k-1p1d-nocudagraph--disable-cuda-graphkimik26-fp8-1k1k-1p1d-mtp-specdecode--speculative-attention-mode decodeInterpretation:
aiterdecode≈ 0.88 andmtp-specdecode≈ 0.88 → confirmed: the regression is the decode forward-mode path over MORI-transferred KV (metadata/graph), independent of backend and of MTP.nocudagraphrecovers to ~0.94 → the bug is in decode HIP graph capture/replay under disaggregation.How to run
Base — must be stacked on #30386, not plain
main. The MI355X disagg CI reinstalls the checked-out sglang via the runtime-checkout mechanism, which currently only exists on #30386 (amd-disagg-checkout). This branch is therefore based on0aac976(the #30386 head, and the exact commit the reference scheduled nightly ran on — it reproduces the clean 0.878 baseline). Running these recipe-only changes on plainmaindoes NOT work: without the runtime-checkout mechanism the baked image is used and Kimi-K2.6 fails to load withValueError: 'KimiK25ForConditionalGeneration' is not a registered model, and any latermainregressions would also leak in. In short: rebase onto #30386 or the run eats the upstream error.Checklist
generate_matrix.py --runner mi355xproduces the three new config names and--filteraccepts them.Validation runs
Baseline (bug is live, not a serving/MR failure): the scheduled nightly's base
kimik26-fp8-1k1k-1p1dleg serves correctly (PD probe OK, coherent output) but the GSM8K gate reports 0.878 < 0.92 and aborts before the sweep — run 28873810961, job:Diagnostic run (this PR's 3 variants): run 28907609511 on the corrected base (#30386 /
0aac976), dispatched withconfigs=kimik26-fp8-1k1k-1p1d-aiterdecode,kimik26-fp8-1k1k-1p1d-nocudagraph,kimik26-fp8-1k1k-1p1d-mtp-specdecode.Results filled in once the legs finish:
1p1d(non-MTP)1p1d-mtp1p1d-aiterdecode1p1d-nocudagraph1p1d-mtp-specdecode--speculative-attention-mode decode+ EAGLE3 external draft fails to start; the decode-mode axis needs a code-level probe instead(Diagnostic run: 28907609511.)
Conclusion so far
Two mechanistic suspects are eliminated: the attention backend (aiter decode ≡ triton decode ≈ 0.88) and HIP graph capture/replay (eager ≈ 0.88). Meanwhile MTP verify — which reads the same MORI-transferred KV but in extend-mode via the prefill backend — stays correct at ~0.95, and single-node non-MTP decode is 0.944.
So the drop is intrinsic to the single-token, absorbed-MLA decode forward-mode path reading disagg-transferred KV, independent of backend and graph. The transferred KV data itself is very likely fine (extend-mode reads it correctly). Prime remaining suspect: the decode forward metadata for the transferred prefix (seq_len / kv page indices / kv_indptr) on the decode worker, or the absorbed-MLA decode read of it.
Decode-side KV dump result (run 28909881181)
Added an env-gated one-shot probe (
SGLANG_DEBUG_DISAGG_DECODE_DUMP) inModelRunner.forwardthat logs, per decode batch on the decode worker:seq_lens, thereq_to_tokenKV-slot mapping, and layer-0 KV-pool content at those slots. Captured the warmup probe ("The capital of France is", bs=1, decode seq_len 6→9):Reading:
seq_len == kv_slots,neg_slots=0⇒ the decode seq_len / req_to_token slot mapping is correct.0.0(first3_norm=[0,0,0],zero_rowsconstant), while only the locally-generated decode tokens are populated (contiguous non-zero norms ~20–24, growing by one per step; the trailing0.0is the current token, written later in the forward).⇒ In the standard KV buffer (layer 0), the MORI-transferred prefill KV is absent (unwritten) at the decode-side attended slots. This points at KV transfer / decode-side KV placement, not the attention compute — consistent with backend & HIP-graph being ruled out.
Caveats to close next: only the short warmup probe was captured (5-dump budget spent before GSM8K); layer 0 only; and confirm this isn't a MORI staging-layout artifact (i.e. that the decode attention kernel really reads these zero slots). Follow-up: target a GSM8K request, dump several layers + the PREBUILT handoff, and compare the transferred length the prefill reported vs the decode
prefix_indices.Correction — KV transfer is fine on real (GSM8K) requests (run 28917883474)
Re-ran the probe skipping the short warmup (only
seq_len >= 64), across layers 0/30/60, page-bucketing the zero rows. On a real GSM8K request (prefix = 1298 tokens):zero=0on every layer,zero_pages=0/6,neg_slots=0. So the earlier "prefix KV is all zero" was an artifact of the tiny warmup probe (<1 page); for a real long prefix the MORI-transferred KV is fully present and correctly placed, and MTP-verify reading the same KV at ~0.95 confirms the values are correct too.Revised conclusion. Eliminated: attention backend, HIP graph, KV-transfer/placement, and decode seq_len/slot metadata. Given identical correct KV + metadata, the ~0.88 vs single-node 0.944 (absorbed decode) vs ~0.95 (extend/verify) gap must live in the decode-mode compute itself under disaggregation — leading suspect is the query position / RoPE the decode worker reconstructs for the transferred prefix (an off-by-one there corrupts attention scores while leaving KV norms untouched), or another absorbed-MLA-decode numeric detail that single-node decode gets right.
Position / RoPE check (run 28974822944) — also clean
Extended the probe to log the decode current-token position vs
seq_len-1. On GSM8K decodes:pos0 == seq_len-1,mismatch=0on every request/rank ⇒ decode positions/RoPE are correct.Elimination summary
Every ForwardBatch-level input to the decode attention is correct, yet absorbed-MLA decode over the transferred KV = ~0.88 while single-node absorbed-MLA decode = 0.944 and MHA/extend over the same KV = ~0.95.
_dispatch_mla_subtypeshows a pure decode step is always absorbed MLA — there is no server-arg to force decode→MHA, so the decode(absorbed)-vs-extend(MHA) axis can't be flipped by a recipe flag (the-mtp-specdecodeattempt to flip it crashed at init).Historical context (from #29855 + CI triage R241)
Single-node control result (run 28984448331) — decisive
Same passing single-node MI35x Kimi eval, default vs
--page-size 256, back-to-back on one image:Verdict: the drop is disagg-specific, and a regression (vs #29855 dev disagg 0.941). Single-node absorbed-MLA decode = 0.947 with correct KV; disagg absorbed-MLA decode = ~0.88 with also-correct KV/metadata/positions. Every non-disagg explanation is eliminated.
Convergence with the parallel
amd/kimik26-disagg-decodemetabranch (yctseng0211)A parallel investigation reached the same conclusion and is now probing the decode compute directly:
num_kv_splits). It is decode-only (extend/verify use no split-KV → explains why they stay ~0.95) and reduces across KV splits over the long transferred prefix — a plausible precision-loss site. Tested via--triton-attention-num-kv-splits 1+SGLANG_DEBUG_MLA_DECODE_NO_SPLIT_CAP(recipe1p1d-nsplit1).Update — nsplit=1 result (run 29042111257, ported to this branch): accuracy 0.875 → split-KV reduction is RULED OUT. Forcing a single decode KV split (
--triton-attention-num-kv-splits 1+ no-split-cap + static fill) leaves accuracy at baseline ~0.88. So the online-softmax cross-split reduction is not the cause.This is the most promising lead and supersedes the python/image bisect for now (the stuck bisect run was cancelled to free the single MI355X slot).
Regression bisect — step 1: python is NOT the cause (run 29058233535)
Reverted
python/sglang/srtto67361ff(#29855 kimi-merge, the 0.941 era) and ran disagg base Kimi via checkout-runtime (artifact confirmssha=629912085,0.5.15.dev520). Result: 0.879 — still ~0.88.Key confound found: the disagg workflow's
setupjob resolves the latest MI35x image dynamically and overrides the recipe's pinned tag. So:v0.5.13.post1-rocm720-mi35x-20260623(Jun-23 image)⇒ reverting python doesn't help; the differing variable is the image (aiter / ROCm / sgl-kernel). Also relevant: DI CI and Lzy17's 0.941 use the same physical
amd-sglangg09/g29 nodes (the DI runner onlysallocs them), so pass/fail is a code/image version difference, not a machine difference.Step 2 (in flight): full known-good repro — bisect branch (old python) + Jun-23 image pinned (
-f image=...20260623). If ~0.94, the regression is the image and known-good is reproduced; then bisect the image. If still ~0.88, the regression is neither current code nor image (environmental: NFS weights / aiter build).BISECT RESULT — the regression is the DOCKER IMAGE, not sglang code (run 29116830660)
Reproduced Lzy17's known-good cleanly: full checkout @
67361ff+ June-23 image (v0.5.13.post1-rocm720-mi35x-20260623), no checkout-runtime (that commit predates it, so it runs the image's coherent baked sglang) → GSM8K 0.941 ✅ (== Lzy17's 0.941).(Note: bisect step 2 = mixed python on old image crashed at init on an ABI mismatch — discarded, uninformative.)
Immediate mitigation available
Pin the Kimi-K2.6 disagg recipes to the Jun-23 image (or set the workflow
imageinput) to restore ~0.94 while the image regression is bisected. The scheduled nightly overrides the recipe's pinned image with the dynamically-resolved latest tag, which is what regressed.Image bisect result — first bad build is the
v0.5.14bump (Jun-26)...mi35x-20260623...mi35x-20260626Jun-24 and Jun-25 are still
v0.5.13.post1(same line as the good Jun-23), so the regression is thev0.5.13.post1 → v0.5.14image bump landing on Jun-26 (aiter / ROCm / sgl-kernel changes baked into the v0.5.14 image). Confirmed airtight: Jun-25 (v0.5.13.post1) = 0.939 (run 29282623403) — last good is Jun-25, first bad is Jun-26 (v0.5.14). Mitigation (pin known-good image + baked build) verified green at 0.939 (PR #31063).Next: bisect the image
Sweep
lmsysorg/sglang-rocm:v*-rocm720-mi35x-YYYYMMDDtags between Jun-23 (good) and the current latest (bad) via the workflowimageinput on the 67361ff known-good branch, to find the first bad image build.Open next steps (branching) — now that it's confirmed disagg-specific + a regression
Next step — this branch already runs via the runtime-checkout mechanism on #30386): a code-level probe on the decode worker for a fixed prompt — compare (a) per-layer KV-pool bytes and (b) the decode forward metadata vs a single-node baseline. A data match + metadata/read mismatch localizes the bug.
CI States
Latest PR Test (Base): ❌ Run #29042115255
Latest PR Test (Extra): ❌ Run #29042114884