Skip to content

Commit 8a428bf

Browse files
jiacao-amdclaude
andcommitted
[AMD][AgentX] dsv4-fp4-mi355x-vllm-agentic-mtp: enable the DEP8 arm, fix gpu-mem-util, add opt-in RoPE+KV fusion
Builds on #2381 (amd/agentx_dsv4_vllm_mtp_0728). Enable DEP8 (DP-attention + EP8) -------------------------------- The entry comment ("Sweeps DP-attention on/off and EP=8") and the script header (DEP = DP_ATTENTION=true, EP_SIZE>1) both describe this arm, and the script already implements it: EP_SIZE>1 appends --enable-expert-parallel and --enable-ep-weight-filter, DP_ATTENTION=true swaps to --tensor-parallel-size 1 --data-parallel-size TP plus the DEP8 prefill budget. But every search-space row was ep:1/dp-attn:false, so neither branch was ever taken. Adds a row that does: { tp: 8, ep: 8, dp-attn: true, kv-offloading: none, conc-list: [32, 48, 64], router: vllm-router 0.1.14 } The existing pure-TP row is kept as the comparison baseline -- it is the configuration the MI355X agentic profile was captured on. Motivation from that 200-step trace (TP8/DP1/EP-off): * MoE GEMMs are latency-bound at small per-rank batch and scale super-linearly with it -- mfma_moe1 x2.16 and mfma_moe2 x2.78 going from 16 to 28 decode tokens/step, across 1643 ms of kernel time (20.6%). DP-attention raises per-rank batch and should convert directly. * cross_device_reduce_2stage costs 577.8 ms (7.2%; 368.7 ms in decode) as the per-layer MoE allreduce that EP replaces with all2all. The second effect is the one that needs measuring rather than extrapolating: all2all on MI355X is many small messages and may not beat the fused allreduce. That is why this is a benchmark arm. conc starts at 32 because --max-num-seqs is applied PER DP rank under DP-attention; at conc 8 across 8 ranks most ranks sit empty and DEP is strictly worse than pure TP. The router is required, not optional -- it expands the single HTTP backend into one logical worker per DP rank and stamps X-data-parallel-rank on forwarded requests. Both commented-out DPA arms already carried it. Fix --gpu-memory-utilization ---------------------------- Was a flat 0.90; now 0.86 for kv-offloading:none, 0.90 only when offloading is on. The comment block above the assignment derives 0.86 in detail (0.95 requests 273.6 GiB and every DP worker hard-fails against the ~256 GiB free-at-init ceiling from the MI355X ~32 GiB/GPU carveout; 0.85 leaves ~23.9 GiB KV, below the 24.06 GiB one 1M-context request needs once the MTP draft layer's KV is counted) and then the assignment used 0.90 anyway, for mooncake headroom. Both mooncake and lmcache arms are commented out, so every active arm is GPU-resident and 0.90 (259.2 GiB) sits above the documented hard-fail ceiling. The DRAM value is preserved behind a KV_OFFLOADING check. Opt-in ROCm RoPE + KV-cache fusion (ROCM_ROPE_KVCACHE_FUSION=1) --------------------------------------------------------------- vLLM's O3 defaults enable fuse_rope_kvcache / fuse_rope_kvcache_cat_mla on ROCm, but both land disabled in this recipe. The gate is splitting_ops, not the custom-op list: with splitting_ops unset, set_splitting_ops_for_v1 populates it from _attention_ops (appending unified_kv_cache_update / unified_mla_kv_cache_update) and then explicitly disables fuse_rope_kvcache with "splitting_ops is None and Inductor graph partition is not enabled". The profiled server log shows exactly that populated list, confirming the fusion never ran. Passing an explicit empty splitting_ops is the escape hatch the warning itself names. FULL_DECODE_ONLY is unaffected: the empty-list downgrade branch only rewrites PIECEWISE and FULL_AND_PIECEWISE. Trace evidence for the payoff (194 decode steps): _inverse_rope_gptj_kernel 78.6 ms n=12800 6.14 us fusedDeepseekV4QNormRopeKVRopeQuantInsert 80.3 ms n=12800 6.27 us _finalize_norm_rope_quant_store 31.8 ms n=6200 5.14 us All three sit on the ~4 us MI355X dispatch floor, so most of that time is launch overhead rather than work. rope_kvcache_fusion_max_token_num defaults to 256 and decode steps verify 16-28 tokens, so decode stays in range. Left off by default: it changes the compiled graph structure and needs its own accuracy gate before becoming the baseline. Verification ------------ bash -n on the script; yaml.safe_load on both YAML files; and a harness simulation of the arg-construction logic confirming all four (dp-attn, ep, kv-offloading, fusion) combinations emit the intended flags. Not yet run on hardware. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 6450c4d commit 8a428bf

3 files changed

Lines changed: 78 additions & 5 deletions

File tree

‎benchmarks/single_node/agentic/dsv4_fp4_mi355x_vllm_mtp.sh‎

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,38 @@ else
412412
fi
413413
CONTEXT_LEN=1048576
414414

415+
# ROCm RoPE + KV-cache fusion (opt-in, ROCM_ROPE_KVCACHE_FUSION=1).
416+
#
417+
# vLLM's O3 defaults enable fuse_rope_kvcache / fuse_rope_kvcache_cat_mla on
418+
# ROCm (see enable_rope_kvcache_fusion in vllm/config/vllm.py), but both land
419+
# disabled here. The gate is splitting_ops, not the custom-op list: when
420+
# splitting_ops is left unset, set_splitting_ops_for_v1 populates it from
421+
# _attention_ops -- which appends unified_kv_cache_update /
422+
# unified_mla_kv_cache_update -- and then explicitly disables fuse_rope_kvcache
423+
# with "splitting_ops is None and Inductor graph partition is not enabled".
424+
# The profiled run shows exactly that list, so the fusion never ran.
425+
# ("+rotary_embedding" is appended automatically when the flag survives, so it
426+
# does not need to be listed by hand.)
427+
#
428+
# Passing an explicit empty splitting_ops takes the documented escape hatch.
429+
# FULL_DECODE_ONLY is unaffected by the empty list: the downgrade branch for
430+
# empty splitting_ops only rewrites PIECEWISE and FULL_AND_PIECEWISE.
431+
#
432+
# Trace evidence for the payoff (194 decode steps, MI355X agentic):
433+
# _inverse_rope_gptj_kernel 78.6 ms n=12800 6.14 us avg
434+
# fusedDeepseekV4QNormRopeKVRopeQuantInsert 80.3 ms n=12800 6.27 us avg
435+
# _finalize_norm_rope_quant_store 31.8 ms n=6200 5.14 us avg
436+
# All three sit on the ~4 us MI355X dispatch floor, so most of that time is
437+
# launch overhead rather than work. rope_kvcache_fusion_max_token_num defaults
438+
# to 256 and decode steps here verify 16-28 tokens, so decode stays in range.
439+
#
440+
# Left off by default: this changes the compiled graph structure and needs its
441+
# own accuracy gate before it becomes the baseline.
442+
COMPILATION_CONFIG='{"mode":3,"cudagraph_mode":"FULL_DECODE_ONLY"}'
443+
if [ "${ROCM_ROPE_KVCACHE_FUSION:-0}" = "1" ]; then
444+
COMPILATION_CONFIG='{"mode":3,"cudagraph_mode":"FULL_DECODE_ONLY","splitting_ops":[],"pass_config":{"fuse_rope_kvcache":true,"fuse_rope_kvcache_cat_mla":true}}'
445+
fi
446+
415447
# MTP: cudagraph capture sizes are in TOKENS. With num_speculative_tokens=N,
416448
# every uniform decode batch of S seqs verifies S*(1+N) tokens, so capture the
417449
# explicit multiples (1+N), 2*(1+N), ..., MAX_NUM_SEQS*(1+N) -- one graph per
@@ -442,8 +474,17 @@ fi
442474
# "available KV cache memory ... larger than ..." on the tighter (eval-only)
443475
# relaunch. 0.86 (247.7 GiB) adds ~2.6 GiB, nearly all to KV (~26.5 GiB), so
444476
# the KV check clears with margin while still keeping ~8 GiB free-mem headroom
445-
# below the ~256 GiB hard-fail ceiling. Additional 0.9 for mooncake headroom
446-
GPU_MEM_UTIL=0.90
477+
# below the ~256 GiB hard-fail ceiling.
478+
#
479+
# The DRAM-offload arms (mooncake/lmcache) wanted extra headroom and pushed this
480+
# to 0.90, but those arms are commented out in amd-master.yaml -- the active arms
481+
# are both kv-offloading:none. 0.90 (259.2 GiB) is above the ~256 GiB hard-fail
482+
# ceiling the comment above documents, so keep the derived 0.86 for GPU-resident
483+
# runs and only raise it when a DRAM-offload arm is re-enabled.
484+
GPU_MEM_UTIL=0.86
485+
if [ "$KV_OFFLOADING" != "none" ]; then
486+
GPU_MEM_UTIL=0.90
487+
fi
447488

448489
# Long-context forward passes (~370K tokens with fp8 KV + DRAM offload) can exceed
449490
# vLLM's default 300s worker RPC timeout, killing the engine with
@@ -472,7 +513,7 @@ VLLM_CMD=(
472513
"${MODE_ARGS[@]}"
473514
--gpu-memory-utilization "$GPU_MEM_UTIL"
474515
--moe-backend aiter
475-
--compilation-config '{"mode":3,"cudagraph_mode":"FULL_DECODE_ONLY"}'
516+
--compilation-config "$COMPILATION_CONFIG"
476517
--speculative-config "$SPEC_CONFIG"
477518
--tokenizer-mode deepseek_v4
478519
--tool-call-parser deepseek_v4

‎configs/amd-master.yaml‎

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1550,9 +1550,31 @@ dsv4-fp4-mi355x-vllm-agentic-mtp:
15501550
search-space:
15511551
#- { tp: 8, ep: 1, dp-attn: true, kv-offloading: dram, kv-offload-backend: { name: mooncake, version: "v0.3.12" }, spec-decoding: mtp, conc-list: [48], router: { name: vllm-router, version: "0.1.14" } }
15521552
#- { tp: 8, ep: 1, dp-attn: true, kv-offloading: dram, kv-offload-backend: { name: lmcache, version: "v0.5.2" }, spec-decoding: mtp, conc-list: [48], router: { name: vllm-router, version: "0.1.14" } }
1553-
# DP+TP 160.9 GiB
1554-
# TP 134.9 GiB
1553+
# DP+TP 160.9 GiB
1554+
# TP 134.9 GiB
1555+
# Baseline: pure TP8, no EP. This is the arm the MI355X agentic profile was
1556+
# captured on (--tensor-parallel-size 8 --data-parallel-size 1, no all2all).
15551557
- { tp: 8, ep: 1, dp-attn: false, kv-offloading: none, spec-decoding: mtp, conc-list: [1, 4, 8, 16, 32, 40, 48] }
1558+
# DEP8: DP-attention + EP8. The entry comment above ("Sweeps DP-attention
1559+
# on/off and EP=8") and the script header both describe this arm, but no
1560+
# search-space row enabled it -- every row was ep:1/dp-attn:false, so
1561+
# neither --enable-expert-parallel nor the DP path was ever exercised.
1562+
#
1563+
# Profile-driven motivation (MI355X dsv4 agentic trace, 200 steps):
1564+
# * MoE GEMMs scale super-linearly with per-rank batch -- mfma_moe1 x2.16
1565+
# and mfma_moe2 x2.78 going from 16 to 28 decode tokens/step. They are
1566+
# latency-bound at small batch, so raising per-rank batch via DP-attn
1567+
# is a direct win on 1643 ms (20.6% of all kernel time).
1568+
# * cross_device_reduce_2stage costs 577.8 ms (7.2%) as the per-layer MoE
1569+
# allreduce that EP replaces with all2all dispatch/combine.
1570+
# Both effects need measurement, not extrapolation: all2all on MI355X is
1571+
# many small messages and may not beat the fused allreduce. Hence this arm.
1572+
#
1573+
# conc starts at 32: under DP-attn --max-num-seqs is applied PER DP rank,
1574+
# so conc<8 leaves most of the 8 ranks empty and DEP is strictly worse.
1575+
# router is required -- it expands the single HTTP backend into one logical
1576+
# worker per DP rank and stamps X-data-parallel-rank on forwarded requests.
1577+
- { tp: 8, ep: 8, dp-attn: true, kv-offloading: none, spec-decoding: mtp, conc-list: [32, 48, 64], router: { name: vllm-router, version: "0.1.14" } }
15561578

15571579
# DSv4-Pro FP4 on MI355X via SGLang. Uses a rocm720 mi35x image built off the
15581580
# amd/deepseek_v4 branch in sgl-project/sglang; the SHA is encoded in the

‎perf-changelog.yaml‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5364,3 +5364,13 @@
53645364
- "Apply the accuracy-gated Kimi-K2.5 MXFP4 settings: tuned AITER MXFP4 MoE, fused shared experts, FP8 KV cache, block size 16, 16384 batched tokens, 512 sequences, async scheduling, gpu-memory-utilization 0.85 (headroom for CUDA-graph capture on MI355X), and the AITER BF16 GEMM path"
53655365
- "Extend the TP4 and TP8 8k1k concurrency sweep from 64 to 128 (1k1k deprecated per #2263)"
53665366
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2213
5367+
5368+
- config-keys:
5369+
- dsv4-fp4-mi355x-vllm-agentic-mtp
5370+
description:
5371+
- "Add the DEP8 arm the entry always described but never ran. Both the amd-master.yaml entry comment ('Sweeps DP-attention on/off and EP=8') and the script header (which defines DEP as DP_ATTENTION=true, EP_SIZE>1) document this configuration, and the script already implements it -- EP_SIZE>1 appends --enable-expert-parallel and --enable-ep-weight-filter, DP_ATTENTION=true swaps to --tensor-parallel-size 1 --data-parallel-size TP plus the DEP8 prefill budget. But every search-space row was ep:1/dp-attn:false, so neither branch was ever taken. New row: { tp: 8, ep: 8, dp-attn: true, kv-offloading: none, conc-list: [32, 48, 64], router: vllm-router 0.1.14 }. The existing pure-TP row is kept as the comparison baseline; it is the configuration the MI355X agentic profile was captured on."
5372+
- "Profile-driven motivation from the 200-step MI355X agentic trace (TP8/DP1/EP-off). MoE GEMMs are latency-bound at small per-rank batch and scale super-linearly with it: mfma_moe1 x2.16 and mfma_moe2 x2.78 from 16 to 28 decode tokens/step, over 1643 ms of kernel time (20.6%). DP-attention raises per-rank batch and should convert directly. Separately, cross_device_reduce_2stage costs 577.8 ms (7.2%, 368.7 ms of it in decode) as the per-layer MoE allreduce that EP replaces with all2all dispatch/combine. The second effect is the one that needs measuring rather than extrapolating: all2all on MI355X is many small messages and may not beat the fused allreduce, which is precisely why this arm exists."
5373+
- "conc starts at 32 (not 1/4/8 as in the pure-TP row) because --max-num-seqs is applied PER DP rank under DP-attention: at conc 8 across 8 ranks most ranks sit empty and DEP is strictly worse than pure TP. The router entry is required, not optional -- it expands the single HTTP backend into one logical worker per DP rank and stamps X-data-parallel-rank on forwarded requests; without it requests do not spread across ranks. Both commented-out DPA arms already carried it."
5374+
- "Fix --gpu-memory-utilization: was a flat 0.90, now 0.86 for kv-offloading:none and 0.90 only when offloading is on. The comment block above the assignment derives 0.86 in detail (0.95 requests 273.6 GiB and every DP worker hard-fails against the ~256 GiB free-at-init ceiling from the MI355X ~32 GiB/GPU carveout; 0.85 leaves ~23.9 GiB KV, below the 24.06 GiB one 1M-context request needs once the MTP draft layer's KV is counted) and then the assignment used 0.90 anyway, for mooncake headroom -- but both mooncake and lmcache arms are commented out, so every active arm is GPU-resident and 0.90 (259.2 GiB) sits above the documented hard-fail ceiling. The DRAM value is preserved behind the KV_OFFLOADING check so re-enabling an offload arm keeps its headroom."
5375+
- "Add opt-in ROCm RoPE + KV-cache fusion via ROCM_ROPE_KVCACHE_FUSION=1 (default off). vLLM's O3 defaults enable fuse_rope_kvcache / fuse_rope_kvcache_cat_mla on ROCm, but both land disabled in this recipe. The gate is splitting_ops, not the custom-op list: with splitting_ops unset, set_splitting_ops_for_v1 populates it from _attention_ops (appending unified_kv_cache_update / unified_mla_kv_cache_update) and then explicitly disables fuse_rope_kvcache with 'splitting_ops is None and Inductor graph partition is not enabled'. The profiled server log shows exactly that populated list, confirming the fusion never ran. Passing an explicit empty splitting_ops is the escape hatch the warning itself names; FULL_DECODE_ONLY is unaffected because the empty-list downgrade branch only rewrites PIECEWISE and FULL_AND_PIECEWISE. Trace evidence for the payoff: _inverse_rope_gptj_kernel 78.6 ms (n=12800, 6.14 us avg), fusedDeepseekV4QNormRopeKVRopeQuantInsert 80.3 ms (n=12800, 6.27 us avg), _finalize_norm_rope_quant_store 31.8 ms (n=6200) -- all three on the ~4 us MI355X dispatch floor, so most of that time is launch overhead rather than work. rope_kvcache_fusion_max_token_num defaults to 256 and decode steps verify 16-28 tokens, so decode stays in range. Left off by default because it changes the compiled graph structure and needs its own accuracy gate."
5376+
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/TBD

0 commit comments

Comments
 (0)