[AMD][AgentX] Add Kimi-K3 MXFP4 MI355X vLLM agentic MTP recipe - #2508
[AMD][AgentX] Add Kimi-K3 MXFP4 MI355X vLLM agentic MTP recipe#2508seungrokj wants to merge 42 commits into
Conversation
…patches Add kimik3_fp4_mi355x_mtp.sh and apply_k3_container_patches.sh (vLLM PR #50619 K3 fp8 MLA verify, aiter mla_gluon batch relax + PR #4474 int64 KV stride, Triton 3.7.0), wired into the recipe and amd-master.yaml. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…fastsafetensors Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… cudagraph, fused_rms_norm_gated Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… 0.9 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…k + conc 14 Rework apply_k3_container_patches.sh for the PIECEWISE / bf16-KV Gluon MLA DSpark stack (KDA state_indices coercion + aiter PR #4474 int64 KV stride, Triton 3.7.0) and add conc 14 to the agentic-mtp sweep list. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…idempotent container patches - compilation-config: mode 3 (piecewise) with explicit even-step cudagraph_capture_sizes up to 2*CONC*(1+SPEC_NUM_TOKENS) (6*CONC at spec=2), drop cudagraph_mode FULL_DECODE_ONLY; capture range decoupled from --max-num-seqs (now 1*CONC) - spec sampling: synthetic rejection with synthetic_acceptance_length 2.45 - a8w4 MoE toggle: VLLM_ROCM_USE_AITER_MOE_SITUV2_A8W4=1 - apply_k3_container_patches.sh: wrap the raw diff payload into a proper idempotent git-apply script (was a bare diff invoked via bash) - amd-master.yaml: kimik3 mtp conc-list -> [4, 8, 10, 12, 14, 16] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Thanks for the contribution! Please reach out to respective companies' CODEOWNER to fill in the latest PR_REVIEW_CHECKLIST.md before pinging core maintainer on Slack for review. In order for the signoff PR check bot to trigger, you must follow the PR_REVIEW_CHECKLIST.md template correctly, including the phrase For PR verification, add the PR authors are responsible for ensuring that after merging, all GitHub Action jobs fully pass. A lot of the time, failures are just flakes and simply re-running the failed jobs will fix it. See GitHub's docs on re-running failed jobs 感谢你的贡献!请联系相应公司的 CODEOWNER 填写最新的 PR_REVIEW_CHECKLIST.md,然后再在 Slack 上联系核心维护者进行审阅。为了触发 signoff PR 检查机器人,你必须正确遵循 PR_REVIEW_CHECKLIST.md 模板,包括保留英文语句 如需进行 PR 验证,请为此 PR 添加 PR 作者有责任确保合并后所有 GitHub Action 任务完全通过。 很多时候失败只是偶发抖动(flake),重新运行失败的任务即可解决。参见 GitHub 关于重新运行失败任务的文档 |
…I355X recipe Document kimik3-fp4-mi355x-vllm-agentic-mtp (Kimi-K3 MXFP4 TP8 + DSpark MTP, piecewise cudagraph capture, idempotent container patches) referencing PR #2508. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| fi | ||
|
|
||
| # ---- Optional axes ---------------------------------------------------------- | ||
| KV_CACHE_DTYPE_ARGS=(--kv-cache-dtype "auto") |
There was a problem hiding this comment.
🔴 Line 770 hardcodes KV_CACHE_DTYPE_ARGS=(--kv-cache-dtype "auto") as a literal instead of (--kv-cache-dtype "$KV_CACHE_DTYPE"), so the served vLLM instance always launches with bf16 KV regardless of the $KV_CACHE_DTYPE variable computed earlier in the script. This silently disables the recipe's documented primary optimization (fp8 KV) and, when an LMCache/fp8 offload arm is enabled via KV_CACHE_DTYPE=fp8, causes an N=1536-vs-768 block-size mismatch between the LMCache sizing logic and the actual bf16 server allocation.
Extended reasoning...
The bug: benchmarks/single_node/agentic/kimik3_fp4_mi355x_mtp.sh:770 sets:
KV_CACHE_DTYPE_ARGS=(--kv-cache-dtype "auto")as a hardcoded string literal. This array is the only place the vllm serve invocation gets its --kv-cache-dtype flag (line 818, "${KV_CACHE_DTYPE_ARGS[@]}" inside VLLM_CMD). Nowhere in the script is KV_CACHE_DTYPE_ARGS reassigned from the $KV_CACHE_DTYPE shell variable.
Where the computed value goes dead: $KV_CACHE_DTYPE is computed at line 206 (KV_CACHE_DTYPE="${KV_CACHE_DTYPE:-auto}"), force-set to fp8 for the vllm-simple-fp8/vllm-simple-fp8-lazy offload backends (line 689), and used to branch the LMCache unified-block-size derivation (LMCACHE_UNIFIED_BLOCK = 1536 for fp8 vs 768 otherwise, around line 272). All of that computation is legitimate and consistent — it's just never wired into the actual served flag.
Why the surrounding code doesn't catch this: The script has extensive comments asserting fp8 KV is "the DEFAULT for every arm on this model" and "the single largest lever available" (headers around lines 47 and 189-203), and the mla_gluon patch is explicitly said to "gate on KV_CACHE_DTYPE=fp8 and must see it already set." None of that intent is enforced at the point where the vLLM command is actually assembled — the literal at line 770 silently overrides it.
Concrete proof-by-example:
- A user sets
KV_OFFLOAD_BACKEND=vllm-simple-fp8(or any path that forcesKV_CACHE_DTYPE=fp8at line 689). - The LMCache/offload sizing logic reads
$KV_CACHE_DTYPE == fp8and derivesLMCACHE_UNIFIED_BLOCK=1536, then computesMAX_NUM_BATCHED_TOKENSandLMCACHE_CHUNK_SIZEunder the assumption N=1536 (an fp8-sized page). - At line 770,
KV_CACHE_DTYPE_ARGSis still the literal(--kv-cache-dtype "auto"). vllm servelaunches with--kv-cache-dtype auto, i.e., bf16 KV pages, whose actual unified block size is 768, not 1536.- The server's real N=768 now violates the
N <= max_num_batched_tokens < 2*Nconstraint the earlier code derived for N=1536 — this is exactly theValueError: Mamba-hybrid models with LMCache require block_size <= max_num_batched_tokens < 2*block_sizefailure the script's own comments warn about a few hundred lines earlier. - Even outside the LMCache arms, any attempt at an fp8-vs-bf16 A/B via
KV_CACHE_DTYPE=fp8env override silently does nothing — the server always runs bf16, doubling KV memory per token (~217 vs ~108 KiB/token) on a 1M-context recipe that is explicitly memory-tight.
Why it doesn't crash the currently-shipped sweep: The one search-space arm actually configured in configs/amd-master.yaml for this recipe uses kv-offloading: none, which skips the offload code path entirely, so KV_CACHE_DTYPE stays at its own default (auto) — coincidentally matching the hardcoded literal. The shipped matrix will not crash, but its results will silently be bf16-KV numbers being reported under a recipe whose whole premise (per its own comments) is fp8 KV as the primary lever, and enabling the fully-implemented LMCache/fp8 arms in the future would immediately hit the engine-init failure described above.
The fix is a one-line change: KV_CACHE_DTYPE_ARGS=(--kv-cache-dtype "$KV_CACHE_DTYPE").
| MAX_NUM_SEQS=$((1 * CONC)) | ||
| # Capture cudagraphs up to the DSpark MTP verify batch. The served slot cap is | ||
| # MAX_NUM_SEQS (1*CONC), but capture is sized off 2*CONC decode slots, each | ||
| # expanding to (1 + SPEC_NUM_TOKENS) rows during verify -> 2*CONC*(1+SPEC_NUM_TOKENS) | ||
| # (6*CONC at spec=2). Decoupled from MAX_NUM_SEQS so the capture range matches the | ||
| # temp_graph.sh config validated for capture even though max-num-seqs is lower. | ||
| # mode 3 (piecewise compile) with an explicit even-step capture list and no | ||
| # cudagraph_mode, matching temp_graph.sh. | ||
| MAX_CUDAGRAPH_CAPTURE_SIZE=$(( 2 * CONC * (1 + SPEC_NUM_TOKENS) )) | ||
| CUDAGRAPH_CAPTURE_SIZES="$(seq -s, 4 2 "$MAX_CUDAGRAPH_CAPTURE_SIZE")" | ||
| COMPILATION_CONFIG_ARGS=(--compilation-config "{\"mode\":3,\"max_cudagraph_capture_size\":$MAX_CUDAGRAPH_CAPTURE_SIZE,\"custom_ops\":[\"+fused_rms_norm_gated\"],\"cudagraph_capture_sizes\":[$CUDAGRAPH_CAPTURE_SIZES]}") | ||
| GPU_MEM_UTIL="0.9" |
There was a problem hiding this comment.
🟡 Inside the lmcache* KV-offload branch, MAX_NUM_SEQS and GPU_MEM_UTIL are deliberately lowered (via ${VAR:-default}) to leave GPU headroom for LMCache's staging buffers, per a detailed comment documenting a real OOM this fixes. But the unconditional plain assignments MAX_NUM_SEQS=$((1 * CONC)) and GPU_MEM_UTIL="0.9" later in the script clobber those values before vllm serve runs, so any LMCache offload arm would silently reintroduce the exact OOM the branch exists to prevent. Fix by making those assignments respect an already-set value, e.g. MAX_NUM_SEQS=${MAX_NUM_SEQS:-$((1 * CONC))} and GPU_MEM_UTIL="${GPU_MEM_UTIL:-0.9}".
Extended reasoning...
The LMCache offload branch (case "${KV_OFFLOAD_BACKEND:-}" in lmcache|lmcache-k27|lmcache-budget|lmcache-k3)) sets MAX_NUM_SEQS="${MAX_NUM_SEQS:-32}" and GPU_MEM_UTIL="${GPU_MEM_UTIL:-0.8}". The extensive comment directly above these lines documents a real production incident (run 30559676068): the LMCache connector's GPU-side staging buffers halve the effective OOM threshold compared to the no-offload case (num_computed_tokens=184,320 at only 5.2% KV usage vs. 360,960+ at 10-21% without the connector), and explains the two specific levers (max_num_seqs 128→32, gpu_memory_utilization 0.88→0.85) chosen to compensate.
The problem is that further down the script, after the offload case block has already run, two more assignments execute unconditionally for every code path:
MAX_NUM_SEQS=$((1 * CONC))
...
GPU_MEM_UTIL="0.9"
These are plain assignments, not ${VAR:-default} forms, so they always overwrite whatever the offload branch computed. Since they run after the case block in the script's control flow and before VLLM_CMD is built (which reads $MAX_NUM_SEQS and $GPU_MEM_UTIL for --max-num-seqs and --gpu-memory-utilization), the offload-branch's carefully-derived low-memory-headroom values never reach the actual vllm serve invocation.
Concretely: set KV_OFFLOAD_BACKEND=lmcache (or lmcache-k27/lmcache-budget/lmcache-k3) and trigger agentic_kv_offload_enabled. The case block runs and sets MAX_NUM_SEQS=32, GPU_MEM_UTIL=0.8 (assuming the env vars are otherwise unset). Then, unconditionally, MAX_NUM_SEQS=$((1 * CONC)) runs — at conc=16 this becomes MAX_NUM_SEQS=16, and even at higher conc this is decoupled from the intended 32 value entirely — and GPU_MEM_UTIL="0.9" overwrites 0.8. VLLM_CMD is then built with --gpu-memory-utilization 0.9 --max-num-seqs $MAX_NUM_SEQS, i.e. exactly the higher-memory-pressure configuration the comment block says caused HSA_STATUS_ERROR_OUT_OF_RESOURCES. The LMCache arm would OOM again, and the mitigation the author wrote (and documented at length) never actually executes.
Nothing in the existing code guards against this: the two blocks are independent plain-assignment statements with no ordering dependency check, and there's no assertion or comment near lines 779/790 acknowledging that they must not run when the offload branch already set these values.
The fix is straightforward and matches the pattern already used for MAX_NUM_SEQS/GPU_MEM_UTIL at the top of the offload branch: change both to ${VAR:-default} form, e.g. MAX_NUM_SEQS="${MAX_NUM_SEQS:-$((1 * CONC))}" and GPU_MEM_UTIL="${GPU_MEM_UTIL:-0.9}". That way the no-offload path (which never sets these vars beforehand) is unaffected, while the LMCache arms' pre-set values survive through to vllm serve.
One reviewer flagged this as a duplicate of a broader "bug_002" umbrella finding — that's a synthesis/tracking concern about how findings were split across reports, not a reason to doubt the underlying defect, which all four independent verifiers reproduced against the current source and confirmed as real. Severity is nit rather than normal because the only search-space arm actually shipped in configs/amd-master.yaml for this recipe uses kv-offloading: none, so the offload branch is never entered by the configured sweep and lines 779/790 currently hold exactly the intended no-offload values — this PR's CI will not be affected. But the LMCache offload path is fully implemented, documented as an intended future arm ("both should be walked back toward the no-offload values once this arm produces a number"), and reachable via env vars today, so anyone activating it (in this PR or a future config change) will hit this immediately.
|
see unofficial run visualizer at https://inferencex.semianalysis.com/inference?unofficialRun=31090250586 |
…gluon MLA padding, GMU 0.95 - Add cudagraph_mode FULL_AND_PIECEWISE, GMU 0.95, MAX_NUM_SEQS 2*CONC - EVAL_ONLY uses block rejection; throughput uses synthetic AL 2.51 - Export VLLM_ROCM_AITER_MLA_ASM_PADDING=gluon, AITER_DISABLE_FMHA_OPUS=1 - Add --disable-custom-all-reduce; wait_for_amd_gpu_clean before run - Slim apply_k3_container_patches.sh Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…r iteration Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…efill OOM The bf16-KV/GMU0.95 arm exhausted HBM during the KDA causal_conv1d prefill (HSA_STATUS_ERROR_OUT_OF_RESOURCES, torch.empty_like abort in kimi_gdn_linear_attn). fp8 KV halves the pool and GMU 0.9 restores transient-activation headroom; align max-num-seqs to the cudagraph capture size. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…matching max-num-seqs to capture range Setting --max-num-seqs to MAX_CUDAGRAPH_CAPTURE_SIZE (2*CONC*(1+SPEC)=48) let the spec-decode verify batch reach 48*3=144 rows, far beyond the captured cudagraph max of 48, causing an out-of-bounds illegal memory access in the aiter biased_grouped_topk MoE-router kernel during FULL_AND_PIECEWISE warmup. Restore --max-num-seqs to MAX_NUM_SEQS (2*CONC) so verify rows (16*3=48) exactly fill the capture range [4..48]. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…sem fix, switch to FULL_DECODE_ONLY, restore conc sweep Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… to conc=8 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…fy, prefix-caching cg-exclude, FULL_AND_PIECEWISE Route Kimi-K3 qo_len==1 decode to the asm MLA kernel while hard-routing the DSpark qo_len>1 verify to gluon (asm has no gqa<16 multi-token kernel). Keep the query fp8 on both paths. Add the #50619 cg-exclude hunks so the target FULL cudagraph capture no longer memory-faults in spec-verify warmup under --enable-prefix-caching, and switch to FULL_AND_PIECEWISE so the KDA/Mamba align spec-verify path runs piecewise instead of OOB-faulting. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
see unofficial run visualizer at https://inferencex.semianalysis.com/inference?unofficialRun=31273221156 |
|
see unofficial run visualizer at https://inferencex.semianalysis.com/inference?unofficialRun=31274337584 |
…es, restore conc sweep Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…adixArk/Kimi-K3-DSpark Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tch architectures to Qwen3DSparkModel Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…(step 1, 1..MAX) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…2048 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…gate, drop max-num-batched-tokens cap Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
see unofficial run visualizer at https://inferencex.semianalysis.com/inference?unofficialRun=31297269323 |
…0,12,14,16] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
see unofficial run visualizer at https://inferencex.semianalysis.com/inference?unofficialRun=31297394607 |
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
see unofficial run visualizer at https://inferencex.semianalysis.com/inference?unofficialRun=31309638899 |
…16], stride-2 cudagraph capture sizes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…match seq stride-2 from 1 produced odd-only sizes topping out at MAX-1, but max_cudagraph_capture_size (=2*CONC*(1+SPEC), always even) must equal max(cudagraph_capture_sizes). Start the sequence at 2 so it lands on MAX. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace per-conc seq with a fixed capture list (1 + even 2..96) and pin max_cudagraph_capture_size=96 so every concurrency shares one capture set and satisfies vLLM's max(cudagraph_capture_sizes)==max_cudagraph_capture_size. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rrow conc sweep Switch the MTP draft to the Inferact/Kimi-K3-DSpark hub model (drops the local staging + architecture-rewrite step), narrow the conc sweep to [1,4,8,10,14], and refresh the container patch script. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…to max 44 Narrow the pinned stride-2 cudagraph capture sizes to top out at 44 (matching the [1,4,8,10,14] conc sweep) and set max_cudagraph_capture_size accordingly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…t attention Switch the DSpark speculative-config attention_backend from TRITON_ATTN to TRITON_MLA in both eval and throughput paths. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…s, narrow to conc10 Replace the PR-fetching container patch with a byte-for-byte embedded/offline variant (aiter #4474+#4494, vllm #51171+#50578+#51011+#51040) so the patched source has no GitHub dependency or open-PR drift; validated gsm8k 0.90/0.85 on 2026-08-10. Narrow the conc sweep to [10] to isolate this config. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…..44, add lm_eval to patches Generate the cudagraph capture list as a contiguous 1..MAX via seq (driven off MAX_CUDAGRAPH_CAPTURE_SIZE=44) instead of the hardcoded stride-2 list, and add an optional lm_eval[api]==0.4.12 install to the container patch script (gated by WITH_LM_EVAL) to match the reference container's client eval tooling. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
kimik3-fp4-mi355x-vllm-agentic-mtp: single-node TP8 agentic-coding recipe for Kimi-K3 (MXFP4, 2.8T MoE, KDA/MLA hybrid, 1M native context) on MI355X (gfx950) with vLLM + DSpark MTP speculative decoding.apply_k3_container_patches.sh) delivered as an idempotentgit applywrapper (KDAstate_indicescoercion,triton_mlacudagraph support,rocm_aiter_mlasmall-head verify,mla_gluonint64 KV stride) plus Triton 3.7.0.compilation-configmode:3with an explicit even-stepcudagraph_capture_sizeslist up to2*CONC*(1+SPEC_NUM_TOKENS)(6*CONC at spec=2);--max-num-seqs=1*CONC.synthetic_acceptance_length 2.45; a8w4 MoE viaVLLM_ROCM_USE_AITER_MOE_SITUV2_A8W4=1.configs/amd-master.yaml: adds the recipe entry withconc-list: [4, 8, 10, 12, 14, 16].Test plan
apply_k3_container_patches.shapplies cleanly in-container and is a no-op on re-runstate_indices/ mla_gluon faults)🤖 Generated with Claude Code