feat(agentx): DSv4-Pro FP4 MTP DPattn+EP8 on FlyDSL mega-MoE (conc 32) / 新增 DSv4-Pro FP4 MTP DPattn+EP8 FlyDSL mega-MoE 配置(并发 32) - #2503
Conversation
|
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 关于重新运行失败任务的文档 |
…ngelog 的 pr-link 为 #2503 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
|
||
| - config-keys: | ||
| - dsv4-fp4-mi355x-vllm-agentic-mtp | ||
| description: | ||
| - "Add an MTP variant of the DeepSeek-V4-Pro FP4 MI355X vLLM AgentX recipe (new benchmarks/single_node/agentic/dsv4_fp4_mi355x_vllm_mtp.sh plus an amd-master.yaml entry) on vllm/vllm-openai-rocm:nightly-b88916617d3d2249bff0dae5cecb6b727c980a20. Adds --speculative-config method=mtp num_speculative_tokens=3; throughput runs pin synthetic acceptance to the dsv4-pro golden AL (synthetic_acceptance_length=2.49) while EVAL_ONLY runs use real target verification, since synthetic acceptance bypasses verification and zeroes the eval score." | ||
| - "Carry the shared vllm-agentic fixes: EVAL_FRAMEWORK=lm-eval (never swebench); --max-num-seqs=CONC per DP rank under DP-attention and 2*CONC in pure TP; DEP8 --max-num-batched-tokens 16384 with --long-prefill-token-threshold 16384; --gpu-memory-utilization 0.90; --block-size 256; --max-model-len 1048576; cudagraph FULL_DECODE_ONLY; VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=1200 for long-context forward passes." | ||
| - "Add a DPattn+EP8 arm (tp 8, ep 8, dp-attn true, kv-offloading none, conc 32) behind the vllm-router. Pure TP8 replicates the MLA KV cache on every GPU, which caps available KV cache size and therefore the prefix-cache hit rate that dominates the AgentX trace-replay scenario; DP-attention shards KV per DP rank and EP8 shards the 384 routed experts, freeing HBM for KV." | ||
| - "Select --moe-backend flydsl on the DPattn+EP8 arm only (AITER FlyDSL mega-MoE, which fuses expert-parallel dispatch, both expert GEMMs, and combine). AITER resolves launch geometry from aiter/ops/flydsl/kernels/mega_moe_tuning_config/flydsl_gfx950_mi355x_IntraNode_ep8.json; DeepSeek-V4-Pro matches its hidden_dim=7168 / topk=6 / local_expert_num=48 fp4 dispatch rules, so the table hits instead of falling back to static geometry defaults. The pure-TP arms remain on --moe-backend aiter." | ||
| pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/PENDING |
There was a problem hiding this comment.
🔴 The new perf-changelog.yaml entry's pr-link is .../pull/PENDING, but this repo's merge tooling only recognizes a resolved pull/<number> link or the literal XXX placeholder — PENDING is neither. This PR's number (2503) is already known, so it should be pull/2503 (or XXX if left for merge-time auto-fill).
Extended reasoning...
The appended entry at the end of perf-changelog.yaml (line 5471) sets pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/PENDING. Every other entry in this 5400+ line append-only file links a real, resolved PR number. PENDING is not a value that appears anywhere else in the file — it's a stray placeholder that was never filled in with this PR's actual number (2503), which is already known from the PR metadata.
This is more than a cosmetic broken link. The repo's canonicalization tooling in utils/validate_perf_changelog.py defines exactly one accepted placeholder set:
CANONICAL_PR_LINK = re.compile(r"https://github\.com/SemiAnalysisAI/InferenceX/pull/\d+")
PR_LINK_PLACEHOLDERS = {"XXX", "https://github.com/SemiAnalysisAI/InferenceX/pull/XXX"}utils/prepare_perf_changelog_merge.py's canonicalize_appended_links() walks every entry appended by the PR and, for each one whose pr-link isn't already the expected pull/<pr_number> link, requires it to be a member of PR_LINK_PLACEHOLDERS before it will auto-rewrite it:
if link not in PR_LINK_PLACEHOLDERS:
raise ChangelogValidationError(
f"appended entry {index + 1} has unexpected pr-link {link!r}"
)PENDING matches neither the canonical pull/\d+ pattern nor either placeholder string, so this raises ChangelogValidationError.
That function is invoked from utils/merge_with_reuse.sh (the script used by the merge-prs skill) at its canonicalize step, which runs under set -e. So when this PR is merged through the standard automated path, the canonicalize step will throw and abort the script — the merge does not silently proceed with a bad link, it hard-fails. This is why the finding is more than "cosmetically inconsistent": it's a concrete failure an author/maintainer will hit at merge time, even though PR-time CI (which only runs validate_matrix_compatible_change via process_changelog.py, not pr-link canonicity) passes cleanly and gives no warning beforehand.
Step-by-step proof:
grep -c PENDING perf-changelog.yaml→ 1 match, at line 5471, and it is this PR's newly appended entry.- At merge time,
merge_with_reuse.shrunsprepare_perf_changelog_merge.py canonicalizefor PR feat(agentx): DSv4-Pro FP4 MTP DPattn+EP8 on FlyDSL mega-MoE (conc 32) / 新增 DSv4-Pro FP4 MTP DPattn+EP8 FlyDSL mega-MoE 配置(并发 32) #2503. canonicalize_appended_links()computesexpected_link = ".../pull/2503", finds the appended entry'slink = ".../pull/PENDING", seeslink != expected_link.- It checks
link not in PR_LINK_PLACEHOLDERS— true, sincePR_LINK_PLACEHOLDERS = {"XXX", ".../pull/XXX"}andPENDINGis not in that set. ChangelogValidationErroris raised; the script (running underset -e) exits non-zero, and the automated merge aborts.
The fix is a one-line change: replace PENDING with 2503 (matching every other entry's convention), or alternatively use the literal XXX placeholder, which the canonicalize step is built to auto-rewrite to the correct PR number at merge time.
| # These MI355X nodes have a stable ~32 GiB/GPU carveout: only ~256/288 GiB is | ||
| # free at init, independent of the KV-offload backend (observed identically on | ||
| # both the lmcache and GPU-resident kv-none arms). At | ||
| # --gpu-memory-utilization 0.95 vLLM requests 273.6 GiB and every DP worker | ||
| # hard-fails ("Free memory ... less than desired GPU memory utilization"). | ||
| # But 0.85 (244.8 GiB) leaves only ~23.9 GiB for KV, which is below the | ||
| # 24.06 GiB one request at max_model_len=1M needs once the MTP draft layer's | ||
| # extra per-token KV is counted -- engine init then dies with | ||
| # "available KV cache memory ... larger than ..." on the tighter (eval-only) | ||
| # relaunch. 0.86 (247.7 GiB) adds ~2.6 GiB, nearly all to KV (~26.5 GiB), so | ||
| # the KV check clears with margin while still keeping ~8 GiB free-mem headroom | ||
| # below the ~256 GiB hard-fail ceiling. Additional 0.9 for mooncake headroom | ||
| GPU_MEM_UTIL=0.90 |
There was a problem hiding this comment.
🔴 GPU_MEM_UTIL is set to 0.90 (259.2 GiB of the 288 GiB total), but the script's own comment immediately above derives that only ~256 GiB is free at init and that 0.86 (247.7 GiB) is the validated safe ceiling — 0.90 requests ~3.2 GiB more than is free, which is the exact "Free memory ... less than desired GPU memory utilization" hard-fail the comment attributes to 0.95. This will hard-fail vLLM engine startup on every DP worker for both configured arms (neither uses mooncake, so the trailing headroom justification doesn't apply). Fix by setting GPU_MEM_UTIL to 0.86 as the comment's own analysis concludes.
Extended reasoning...
The comment at lines 434-446 walks through the author's own empirical derivation of the safe --gpu-memory-utilization value for this recipe on MI355X, and the code directly contradicts the conclusion of that derivation.
The math, using the script's own model: vLLM's --gpu-memory-utilization is a fraction of total GPU memory (288 GiB on MI355X). The comment confirms this itself: 0.95 → 273.6 GiB (0.95 × 288 = 273.6). It then states that only ~256/288 GiB is actually free at engine init due to a stable ~32 GiB/GPU carveout, and that 0.95 (273.6 GiB) hard-fails every DP worker with Free memory ... less than desired GPU memory utilization because 273.6 > 256. It goes on to derive 0.86 (247.7 GiB) as the validated safe value, chosen specifically to leave ~8 GiB of headroom below that same 256 GiB ceiling.
Applying that identical formula to the value actually written in the code: GPU_MEM_UTIL=0.90 → 0.90 × 288 = 259.2 GiB, which is ~3.2 GiB above the 256 GiB free-memory ceiling the comment just established. This is not a smaller version of the 0.95 failure — it's the same failure mode, just with a smaller margin over the same ceiling.
Why nothing else in the code prevents this: GPU_MEM_UTIL is set unconditionally (line 446) and passed straight through as --gpu-memory-utilization "$GPU_MEM_UTIL" (line 495) with no override per KV-offload backend or per arm. Both arms configured for this recipe in amd-master.yaml (dsv4-fp4-mi355x-vllm-agentic-mtp) use kv-offloading: none, so there's no code path that would lower this value for either configured combination. The trailing comment fragment — "Additional 0.9 for mooncake headroom" — doesn't rescue this: (1) no mooncake arm exists in this PR's config, and (2) even if it did, raising --gpu-memory-utilization reduces free-memory headroom, it does not add it, so the stated rationale is backwards regardless of backend.
Impact: Under DP-attention (--data-parallel-size $TP --tensor-parallel-size 1), each of the 8 DP ranks runs its own vLLM engine on a single GPU, and each independently performs the free-memory check at init. With 0.90 requesting 259.2 GiB against ~256 GiB free, every rank hits the hard-fail on both the pure-TP8 arm and the DEP8+FlyDSL arm — the recipe produces no benchmark data on either arm as currently configured. The PR's validation section lists only pytest, config-generation, and bash -n checks; none of these exercise real GPU memory allocation, so this would not have been caught before merge.
Step-by-step proof:
- MI355X total HBM per GPU: 288 GiB (confirmed by the comment's own 0.95×288=273.6 check).
- Free memory at engine init, per the comment: ~256 GiB (288 − ~32 GiB carveout).
- vLLM's init-time check requires
free_memory >= total_memory * gpu_memory_utilization. - With
GPU_MEM_UTIL=0.90: required = 0.90 × 288 = 259.2 GiB. - Compare: 256 GiB free < 259.2 GiB required → check fails.
- This triggers the exact same
Free memory ... less than desired GPU memory utilizationerror the comment says occurs at 0.95, just with a ~3.2 GiB deficit instead of a ~17.6 GiB deficit. - Since this check runs independently on each of the 8 DP-rank engine processes, all 8 fail identically, and the pure-TP8 arm (single 8-GPU engine) fails identically for the same reason.
Fix: Set GPU_MEM_UTIL to 0.86, the value the comment's own derivation lands on as the validated safe ceiling with margin for both the free-memory check and the MTP draft layer's extra KV requirement.
| # MTP: cudagraph capture sizes are in TOKENS. With num_speculative_tokens=N, | ||
| # every uniform decode batch of S seqs verifies S*(1+N) tokens, so capture the | ||
| # explicit multiples (1+N), 2*(1+N), ..., MAX_NUM_SEQS*(1+N) -- one graph per | ||
| # decode batch of 1..MAX_NUM_SEQS seqs. vLLM rounds configured sizes up to | ||
| # multiples of (1+N) and dedups (adjust_cudagraph_sizes_for_spec_decode), so a | ||
| # plain 1..MAX_NUM_SEQS list would collapse to coverage of only | ||
| # MAX_NUM_SEQS/(1+N) seqs and drop the largest decode batches to eager. | ||
| NUM_SPEC_TOKENS=3 | ||
| TOKENS_PER_SEQ=$((1 + NUM_SPEC_TOKENS)) |
There was a problem hiding this comment.
🔴 TOKENS_PER_SEQ is computed at line 423 (with a detailed comment on why MTP cudagraph capture sizes must be explicit multiples of it) but is never actually applied — the --compilation-config passed to vllm serve at line 497 is a plain literal with no cudagraph_capture_sizes key. Both sibling scripts (dsv4_fp4_b200_vllm_mtp.sh, dsv4_fp4_b300_vllm_mtp.sh) build this into the config; this port dropped that step while keeping the dead variable and comment, so the new DEP8 arm (conc=32) will fall back to vLLM's default capture-size ladder and run its largest decode batches in eager mode.
Extended reasoning...
The bug: benchmarks/single_node/agentic/dsv4_fp4_mi355x_vllm_mtp.sh:415-423 carries an extensive comment explaining that, because MTP verifies S*(1+N) tokens for a decode batch of S sequences with N speculative tokens, the vLLM cudagraph capture sizes must be the explicit list (1+N), 2*(1+N), ..., MAX_NUM_SEQS*(1+N). Otherwise, vLLM's adjust_cudagraph_sizes_for_spec_decode rounds/dedups a plain 1..MAX_NUM_SEQS list, collapsing coverage to only MAX_NUM_SEQS/(1+N) seqs and pushing the largest decode batches to eager execution. The script computes NUM_SPEC_TOKENS=3 and TOKENS_PER_SEQ=$((1 + NUM_SPEC_TOKENS)) (line 423) to do exactly this — but TOKENS_PER_SEQ is never referenced again anywhere in the file (confirmed by grep: a single hit, the assignment itself).
Where it should have been used: The actual --compilation-config passed to vllm serve (line 497) is the hardcoded literal '{"mode":3,"cudagraph_mode":"FULL_DECODE_ONLY"}' — no cudagraph_capture_sizes key at all. I checked both named sibling scripts directly and confirmed they implement the missing piece:
dsv4_fp4_b300_vllm_mtp.shloopsnum_seqs=1..MAX_NUM_SEQS, buildsCUDA_GRAPH_CAPTURE_SIZESasnum_seqs*TOKENS_PER_SEQfor each, and injects it as"cudagraph_capture_sizes":[...]intoCOMPILATION_CONFIG, which is then passed tovllm serve.dsv4_fp4_b200_vllm_mtp.shinstead computesMAX_CUDAGRAPH_CAPTURE_SIZE=$((MAX_NUM_SEQS * TOKENS_PER_SEQ))and passes it via a dedicated--max-cudagraph-capture-sizeflag.
Both siblings carry the identical explanatory comment block, so the intended pattern for this port is unambiguous — the mi355x version copied the comment and the TOKENS_PER_SEQ computation but dropped the loop/flag that actually applies it, leaving a dead variable with an orphaned justification.
Why this isn't caught elsewhere: There's no --max-cudagraph-capture-size flag anywhere in the file (confirmed via grep), and bash -n / the PR's config-generation tests only check that the script is syntactically valid and that the YAML search-space entries resolve correctly — neither would catch a config string missing an optional key.
Impact — concrete walkthrough: This PR's headline new arm is DEP8 at conc=32. Under DP_ATTENTION=true, MAX_NUM_SEQS = CONC = 32 (line ~380). With NUM_SPEC_TOKENS=3, TOKENS_PER_SEQ=4. The intended capture list would be {4, 8, 12, ..., 128} (32 explicit sizes, one per decode batch of 1..32 sequences). Instead, vLLM falls back to its own default capture-size list (typically a geometric/log-spaced list of raw sizes, not multiples of 4), which adjust_cudagraph_sizes_for_spec_decode then rounds and dedups down to roughly MAX_NUM_SEQS/(1+N) ≈ 8 effective sequence-count buckets. Decode batches of roughly 9-32 sequences — exactly the high-concurrency regime this PR's DEP8 arm exists to exercise — get no matching captured graph and fall back to eager execution, which is markedly slower on ROCm. The benchmark will still run to completion and report numbers, but those numbers materially understate the throughput this configuration should achieve, undermining the PR's own stated goal of measuring DP-attention+EP8 throughput at conc=32.
Fix: Port the dsv4_fp4_b300_vllm_mtp.sh loop (or the b200 --max-cudagraph-capture-size approach) into this script, injecting cudagraph_capture_sizes into the --compilation-config string built at line 497 using the already-computed TOKENS_PER_SEQ and MAX_NUM_SEQS. This is a small, mechanical addition mirroring existing, validated code in the same directory.
| cleanup_lmcache_server() { | ||
| if [[ -n "$LMCACHE_PID" ]] && kill -0 "$LMCACHE_PID" 2>/dev/null; then | ||
| kill "$LMCACHE_PID" 2>/dev/null || true | ||
| wait "$LMCACHE_PID" 2>/dev/null || true | ||
| fi | ||
| } | ||
|
|
||
| trap cleanup_lmcache_server EXIT | ||
|
|
||
| cleanup_agentic_services() { | ||
| local exit_code=$? | ||
| trap - EXIT INT TERM | ||
| set +e | ||
| stop_background_process_tree "$ROUTER_PID" "vLLM router" | ||
| stop_background_process_tree "$SERVER_PID" "vLLM server" 60 | ||
| stop_background_process_tree "$MOONCAKE_MASTER_PID" "Mooncake master" | ||
| exit "$exit_code" | ||
| } | ||
| trap cleanup_agentic_services EXIT | ||
| trap 'exit 130' INT | ||
| trap 'exit 143' TERM |
There was a problem hiding this comment.
🟡 In the lmcache KV-offload branch, trap cleanup_lmcache_server EXIT (line 235) is immediately overwritten by trap cleanup_agentic_services EXIT (line 246), and bash EXIT traps replace rather than stack, so cleanup_lmcache_server never runs. cleanup_agentic_services only stops ROUTER_PID, SERVER_PID, and MOONCAKE_MASTER_PID — it never references LMCACHE_PID — so the backgrounded lmcache server process orphans on every exit, leaking its host-DRAM pool and its LMCACHE_PORT/LMCACHE_HTTP_PORT binds for any lmcache invocation. This is dead for this PR's configured arms (both use kv-offloading: none) and is a verbatim copy of the same defect in the pre-existing sibling script dsv4_fp4_mi355x_vllm.sh, but it should be fixed by adding LMCACHE_PID cleanup to cleanup_agentic_services (or dropping the redundant first trap) before this recipe is run with the lmcache backend.
Extended reasoning...
The bug: in the lmcache) case branch of the KV-offload switch, line 235 sets trap cleanup_lmcache_server EXIT, where cleanup_lmcache_server is the only function in the script that kills $LMCACHE_PID. A handful of lines later, line 246 sets trap cleanup_agentic_services EXIT. Bash traps do not stack per signal — registering a second handler for EXIT fully replaces the first — so cleanup_lmcache_server is silently dead code from that point on; it will never execute for any exit path (normal completion, error under set -euo pipefail, or a caught INT/TERM).\n\nWhy the surviving handler doesn't save it: cleanup_agentic_services (lines 237-245) does trap - EXIT INT TERM (so no further handler can run either) and then calls stop_background_process_tree on exactly three PIDs: ROUTER_PID, SERVER_PID, and MOONCAKE_MASTER_PID. It never mentions LMCACHE_PID. The LMCache MP server is launched later (~line 349, "\" ... & then LMCACHE_PID=$!) as a direct backgrounded child of the top-level script, not a child of the vLLM server process, so killing SERVER_PID's process tree does not reap it either.\n\nStep-by-step proof:\n1. Script is invoked with KV_OFFLOAD_BACKEND=lmcache.\n2. Line 235: trap cleanup_lmcache_server EXIT registers the LMCache-killing handler.\n3. Line 246: trap cleanup_agentic_services EXIT overwrites that registration — bash keeps only one EXIT handler.\n4. Later, lmcache server ... is started in the background; LMCACHE_PID=$! captures its PID.\n5. The script later exits (success, an error from any of the many set -euo pipefail-sensitive commands, or a signal). The shell runs the current EXIT trap, cleanup_agentic_services.\n6. cleanup_agentic_services stops the router, vLLM server, and Mooncake master trees, then exit "$exit_code". LMCACHE_PID is never referenced, never signaled.\n7. The lmcache server process (holding its --l1-size-gb host-DRAM pool, e.g. up to TOTAL_CPU_DRAM_GB) keeps running as an orphan, with LMCACHE_PORT/LMCACHE_HTTP_PORT still bound, until something external (e.g. SLURM cgroup teardown) reaps it.\n\nImpact: on any bare-metal or non-cgrouped invocation with the lmcache backend, every run leaks an orphaned LMCache server holding a large host-memory allocation and its ports, which can block the next run's bind to the same ports and accumulate leaked processes across repeated runs.\n\nScope for this PR: both arms configured for dsv4-fp4-mi355x-vllm-agentic-mtp in amd-master.yaml use kv-offloading: none, so KV_OFFLOAD_BACKEND is never set to lmcache in this PR's sweep and the branch is not exercised — this doesn't affect CI or the configured benchmark runs. It's also not new logic invented by this PR: the identical trap-overwrite structure (trap cleanup_lmcache_server EXIT immediately superseded by trap cleanup_agentic_services EXIT, with the same PID list omitting LMCACHE_PID) already exists verbatim in the pre-existing sibling script benchmarks/single_node/agentic/dsv4_fp4_mi355x_vllm.sh (lines 221/232), which this MTP variant was cloned from.\n\nFix: add stop_background_process_tree "$LMCACHE_PID" "LMCache server" inside cleanup_agentic_services (removing the need for the separate, now-redundant cleanup_lmcache_server/first trap), or otherwise consolidate to a single EXIT handler that accounts for every backgrounded PID the lmcache branch can set.
…ega-MoE Rebase the PR #2381 AgentX MTP recipe onto current main (which carries the AIPerf v1.0.1 bump, 65e0b1c) and add a DPattn+EP8 arm at concurrency 32. Pure TP8 replicates the MLA KV cache on every GPU, so available KV cache size does not grow with the node. In the AgentX trace-replay scenario, prefix-cache hit rate dominates and is bounded by that pool. DP-attention shards KV per DP rank and EP8 shards the 384 routed experts, freeing HBM for KV. The DEP arm selects --moe-backend flydsl (AITER FlyDSL mega-MoE: fused EP dispatch + both expert GEMMs + combine). AITER resolves launch geometry from mega_moe_tuning_config/flydsl_gfx950_mi355x_IntraNode_ep8.json; DeepSeek-V4-Pro matches its hidden_dim=7168 / topk=6 / local_expert_num=48 fp4 dispatch rules, so the table hits rather than falling back to static geometry defaults. The pure-TP arms are untouched and stay on --moe-backend aiter. 中文:将 PR #2381 的 AgentX MTP 配置变基到当前 main(已包含 AIPerf v1.0.1 升级 65e0b1c),并新增并发 32 的 DPattn+EP8 配置点。 纯 TP8 会在每张 GPU 上复制一份 MLA KV 缓存,可用 KV 缓存容量无法随节点规模增长。 在 AgentX 轨迹回放场景中,性能主要由前缀缓存(prefix cache)命中率决定,而命中率 受该缓存池大小限制。数据并行注意力(DP attention)按 DP rank 切分 KV,专家并行 EP8 切分 384 个路由专家,从而为 KV 释放出 HBM 空间。 DEP 配置点选用 --moe-backend flydsl(AITER FlyDSL mega-MoE:融合专家并行的 dispatch、两个专家 GEMM 与 combine)。AITER 从 mega_moe_tuning_config/flydsl_gfx950_mi355x_IntraNode_ep8.json 解析 kernel 启动 几何参数;DeepSeek-V4-Pro 与其 hidden_dim=7168 / topk=6 / local_expert_num=48 的 fp4 dispatch 规则匹配,因此可命中调优表,而不会回退到静态默认几何参数。 纯 TP 配置点保持不变,仍使用 --moe-backend aiter。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ngelog 的 pr-link 为 #2503 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…load CI run 31033203073 shows the DPattn+EP8 arm dying at model load on every worker: ValueError: moe_backend='flydsl' is not supported for MXFP4 MoE. Expected one of ['deep_gemm', 'flashinfer_trtllm', ..., 'aiter', ...] DeepSeek-V4-Pro resolves expert_dtype to 'fp4', so its experts are built by Mxfp4MoEMethod, and map_mxfp4_backend() in vllm/model_executor/layers/fused_moe/oracle/mxfp4.py hard-rejects any backend outside its MXFP4 allowlist. This is a hard failure in FusedMoE.__init__, not a fallback -- it happens before any KV cache is allocated. The rejection is on the quantization path, so it is independent of EP size and of tuning-table coverage: AITER's flydsl_gfx950_mi355x_IntraNode_ep8.json does match DSv4-Pro (hidden_dim=7168 / topk=6 / local_expert_num=48 at EP8), but is never consulted because FlyDSL is unreachable for an MXFP4 MoE in this build. Run all arms on --moe-backend aiter, which is on the allowlist. The DEP8 arm still answers its actual question: whether sharding KV per DP rank and the 384 routed experts across EP8 frees enough HBM to raise the prefix-cache hit rate that dominates AgentX. MOE_BACKEND stays overridable for retesting. 中文:CI 运行 31033203073 显示 DPattn+EP8 配置点在模型加载阶段所有 worker 均崩溃: ValueError: moe_backend='flydsl' is not supported for MXFP4 MoE. DeepSeek-V4-Pro 的 expert_dtype 解析为 'fp4',其专家层由 Mxfp4MoEMethod 构建, 而 vllm/model_executor/layers/fused_moe/oracle/mxfp4.py 中的 map_mxfp4_backend() 会硬性拒绝其 MXFP4 白名单之外的任何后端。这是 FusedMoE.__init__ 中的致命错误, 而非回退路径——发生在分配任何 KV 缓存之前。 该拒绝发生在量化路径上,因此与 EP 规模及调优表覆盖情况无关:AITER 的 flydsl_gfx950_mi355x_IntraNode_ep8.json 确实与 DSv4-Pro 匹配(EP8 下 hidden_dim=7168 / topk=6 / local_expert_num=48),但在该构建中 FlyDSL 对 MXFP4 MoE 不可达,因此该调优表根本不会被读取。 改为所有配置点均使用白名单内的 --moe-backend aiter。DEP8 配置点仍能回答其真正的 问题:按 DP rank 切分 KV、按 EP8 切分 384 个路由专家,能否释放足够 HBM 以提升 主导 AgentX 性能的前缀缓存命中率。MOE_BACKEND 仍可通过环境变量覆盖以便复测。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add trace capture to the DSv4-Pro MI355X AgentX MTP recipe so the DEP arm (dp-attn + EP8) can be traced under real trace-replay traffic. The shared PROFILE=1 path in benchmark_lib.sh hangs off run_benchmark_serving (--dataset-name random), which the agentic scenario never calls -- AgentX goes through build_replay_cmd + aiperf. profile.yml is likewise fixed-seq-len only (it never sets SCENARIO_SUBDIR). So capture in the recipe instead, driving vLLM's /start_profile and /stop_profile against the live replay. Gated behind AGENTX_TRACE=1 so default sweeps are untouched. The window is short by design: a 3600s replay across 8 DP ranks would produce an unusable full-duration trace. AGENTX_TRACE_DELAY_S (default 1800) must absorb dataset setup (4-14 min on the Weka corpus) plus per-lane warmup so the window lands in steady state; AGENTX_TRACE_WINDOW_S (default 20) bounds the slice. VLLM_TORCH_PROFILER_DIR must be set before the serve command or vLLM does not register the profiler endpoints at all, so it is exported alongside the other server env and pointed at RESULT_DIR/traces -- benchmark-tmpl.yml already uploads results/** as the agentic artifact. Capture is best-effort: it profiles the backend port directly (the router fans out over DP ranks), skips cleanly if the server is unhealthy, and never changes the replay's exit status. 中文:为 DSv4-Pro MI355X AgentX MTP 配置增加 trace 采集,使 DEP 配置点 (dp-attn + EP8)能在真实轨迹回放负载下被采集。 benchmark_lib.sh 中共用的 PROFILE=1 路径挂在 run_benchmark_serving (--dataset-name random)上,而 agentic 场景从不调用它——AgentX 走的是 build_replay_cmd + aiperf。profile.yml 同样只支持 fixed-seq-len(从不设置 SCENARIO_SUBDIR)。因此改为在配置脚本内采集,通过 vLLM 的 /start_profile 与 /stop_profile 端点对运行中的回放进行采样。 采集由 AGENTX_TRACE=1 控制,默认 sweep 不受影响。窗口刻意设短:3600s 回放跨 8 个 DP rank,全程采集会产出无法使用的巨大 trace。AGENTX_TRACE_DELAY_S(默认 1800)需 覆盖数据集准备(Weka 语料通常 4-14 分钟)与各 lane 预热,以确保窗口落在稳态; AGENTX_TRACE_WINDOW_S(默认 20)限定切片长度。 VLLM_TORCH_PROFILER_DIR 必须在 serve 命令之前设置,否则 vLLM 根本不会注册 profiler 端点,因此与其他 server 环境变量一同导出,并指向 RESULT_DIR/traces—— benchmark-tmpl.yml 已将 results/** 作为 agentic 产物上传。 采集为尽力而为:直接对 backend 端口采集(router 会在 DP rank 间分发),服务器不健康 时干净跳过,且不改变回放的退出状态。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both arms of dsv4-fp4-mi355x-vllm-agentic-mtp sit at conc 32, so --conc cannot target the DEP arm alone. Add a separate config file holding only the dp-attn + EP8 entry, so a trace run does not also spend a runner on the TP8 baseline. Not referenced by any sweep: full-sweep dispatches name configs/amd-master.yaml explicitly, so this file is only reachable via an explicit --config-files. 中文:dsv4-fp4-mi355x-vllm-agentic-mtp 的两个配置点并发均为 32,因此 --conc 无法 单独指定 DEP 配置点。新增一个仅包含 dp-attn + EP8 条目的独立 config 文件,使 trace 采集运行不会额外占用一台 runner 去跑 TP8 基线。 该文件不被任何 sweep 引用:full-sweep 派发均显式指定 configs/amd-master.yaml, 因此只能通过显式 --config-files 访问。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d env var The DEP8 trace run reached /start_profile and got HTTP 404: vLLM never registered the profiler endpoints. VLLM_TORCH_PROFILER_DIR no longer exists in this build -- it was dropped from vllm/envs.py and replaced by the structured ProfilerConfig (vllm/config/profiler.py), so setting it only logged "Unknown vLLM environment variable detected" and attach_router() in entrypoints/serve/profile/api_router.py left the routes off, since it gates on profiler_config.profiler being set. Build --profiler-config instead, and bound the capture with max_iterations so the engine self-stops after N profiled steps rather than leaning on the wall-clock window to keep an 8-DP-rank trace readable. Report the HTTP status from /start_profile too -- the 404 was previously swallowed as a bare "failed", which hid the root cause for a whole run. 修复 AgentX trace 采集:改用 --profiler-config,不再用已移除的环境变量 DEP8 trace 运行中 /start_profile 返回 404:vLLM 从未注册 profiler 端点。本 镜像已从 vllm/envs.py 移除 VLLM_TORCH_PROFILER_DIR,改为结构化的 ProfilerConfig,因此设置该环境变量只会打印 "Unknown vLLM environment variable detected",而 attach_router() 依据 profiler_config.profiler 是否设置来决定 是否挂载路由,于是端点缺失。 改为构造 --profiler-config,并用 max_iterations 限定采集范围,让引擎在 N 个 已采集步后自行停止,而不是依赖墙钟窗口来控制 8 个 DP rank 的 trace 体积。 同时输出 /start_profile 的 HTTP 状态码 —— 之前 404 被吞成笼统的 "failed", 导致整整一轮运行都没能定位根因。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds dsv4-fp4-mi355x-vllm-agentic-mtp-dpa-only: dp-attn true with ep 1, so the launch script emits --tensor-parallel-size 1 --data-parallel-size 8 without --enable-expert-parallel. This is not a degenerate "TP with nothing to shard" configuration. FusedMoEParallelConfig.make() takes the not-use_ep branch, and flatten_tp_across_dp_and_pcp() folds the 8 DP ranks into an effective MoE tp_size of 8, so the experts stay sharded 8 ways -- as tensor-parallel slices of every expert instead of whole experts per rank. Expert weights are not replicated and the KV pool stays per-rank, which makes this a clean EP-vs-MoE-TP comparison against the DEP8 arm at identical attention and KV layout. 新增关闭 EP 的 DPattn-only 配置,用于与 DEP 对比 新增 dsv4-fp4-mi355x-vllm-agentic-mtp-dpa-only:dp-attn 为 true 且 ep 为 1, 启动脚本因此生成 --tensor-parallel-size 1 --data-parallel-size 8,且不带 --enable-expert-parallel。 这并非「TP 无处可切」的退化配置。FusedMoEParallelConfig.make() 走 not-use_ep 分支,flatten_tp_across_dp_and_pcp() 将 8 个 DP rank 折叠为 MoE 的等效 tp_size=8,专家仍按 8 路切分 —— 只是切成每个专家的张量并行分片,而非每 rank 持有整个专家。专家权重不会被复制,KV 池仍按 rank 分片,因此本组与 DEP8 在完全相同的 attention 与 KV 布局下,构成一次干净的 EP 与 MoE-TP 对比。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
fd271fe to
6e9f17c
Compare
Summary
Rebases the AgentX DeepSeek-V4-Pro FP4 MTP recipe from #2381 onto current
main(which already carries the AIPerf v1.0.1 bump, 65e0b1c / #2484) and adds a DP-attention + EP8 arm at concurrency 32 using AITER's FlyDSL mega-MoE kernel.Why
Pure TP8 replicates the MLA KV cache on every GPU, so the available KV cache pool does not grow with the node. In the AgentX trace-replay scenario, prefix-cache hit rate dominates end-to-end performance and is bounded by that pool. DP-attention shards KV per DP rank and EP8 shards the 384 routed experts, freeing HBM for KV.
What changed
benchmarks/single_node/agentic/dsv4_fp4_mi355x_vllm_mtp.sh(new, from [AMD] [AGENTX] dsv4-fp4-mi355x-vllm-agentic: DEP tuning + LMCache refresh (MTP variant) #2381): adds a MoE-backend gate. Arms withDP_ATTENTION=true && EP_SIZE > 1select--moe-backend flydsl; every pure-TP arm keeps--moe-backend aiterand is unchanged, so [AMD] [AGENTX] dsv4-fp4-mi355x-vllm-agentic: DEP tuning + LMCache refresh (MTP variant) #2381's existing conc-1..48 sweep stays directly comparable.configs/amd-master.yaml: newdsv4-fp4-mi355x-vllm-agentic-mtpkey, same image as [AMD] [AGENTX] dsv4-fp4-mi355x-vllm-agentic: DEP tuning + LMCache refresh (MTP variant) #2381 (vllm/vllm-openai-rocm:nightly-b88916617d3d2249bff0dae5cecb6b727c980a20). Two arms: the TP8 sweep (conc-list: [1, 4, 8, 16, 32, 40, 48]) and the newtp: 8, ep: 8, dp-attn: truearm atconc: 32withvllm-router 0.1.14.perf-changelog.yaml: append-only entry.Does the FlyDSL EP8 kernel work on DSv4 + DPattn+EP8?
AITER resolves launch geometry via
resolve_tuning_config_path(), which globsmega_moe_tuning_config/flydsl_*_{kernel_type}_ep{world_size}.jsonand scores candidates on gfx arch then GPU model. On MI355X at EP8 that resolves toflydsl_gfx950_mi355x_IntraNode_ep8.json.Its first fp4 dispatch rule is
hidden_dim=7168, topk=6, local_expert_num=48. DeepSeek-V4-Pro'sconfig.jsongiveshidden_size=7168,num_experts_per_tok=6,n_routed_experts=384→ 384/8 = 48 local experts at EP8. Exact match, so the tuning table hits rather than falling back to the static 128-block / 4-warp defaults. A shape miss would not have been fatal either — the op logsusing static geometry defaultsand still runs. This PR's CI run confirms it empirically.Validation
pytest utils/matrix_logic/ -q→ 224 passedtest-configemits 8 entries with the DEP8 arm correctly carryingep: 8, dp-attn: True, conc: 32, router{vllm-router 0.1.14}bash -nclean on the launch script; MoE-backend gating verified for both arms中文说明
将 #2381 的 AgentX DeepSeek-V4-Pro FP4 MTP 配置变基到当前
main(已包含 AIPerf v1.0.1 升级,65e0b1ce / #2484),并新增使用 AITER FlyDSL mega-MoE kernel 的 数据并行注意力(DP attention) + EP8、并发 32 配置点。动机
纯 TP8 会在每张 GPU 上复制一份 MLA KV 缓存,因此可用 KV 缓存池不会随节点规模增长。在 AgentX 轨迹回放场景中,端到端性能主要由前缀缓存(prefix cache)命中率决定,而命中率受该缓存池大小限制。DP attention 按 DP rank 切分 KV,EP8 切分 384 个路由专家,从而为 KV 释放 HBM 空间。
改动内容
benchmarks/single_node/agentic/dsv4_fp4_mi355x_vllm_mtp.sh(新增,来自 [AMD] [AGENTX] dsv4-fp4-mi355x-vllm-agentic: DEP tuning + LMCache refresh (MTP variant) #2381):加入 MoE 后端选择逻辑。满足DP_ATTENTION=true && EP_SIZE > 1的配置点选用--moe-backend flydsl;所有纯 TP 配置点保持--moe-backend aiter不变,因此 [AMD] [AGENTX] dsv4-fp4-mi355x-vllm-agentic: DEP tuning + LMCache refresh (MTP variant) #2381 已有的 conc-1..48 扫描结果仍可直接对比。configs/amd-master.yaml:新增dsv4-fp4-mi355x-vllm-agentic-mtp配置键,镜像与 [AMD] [AGENTX] dsv4-fp4-mi355x-vllm-agentic: DEP tuning + LMCache refresh (MTP variant) #2381 相同。包含两个配置点:TP8 扫描(conc-list: [1, 4, 8, 16, 32, 40, 48]),以及新增的tp: 8, ep: 8, dp-attn: true、conc: 32、搭配vllm-router 0.1.14。perf-changelog.yaml:纯追加条目。该 kernel 在 DSv4 + DPattn+EP8 上能用吗?
AITER 通过
resolve_tuning_config_path()解析 kernel 启动几何参数:它在mega_moe_tuning_config/下按flydsl_*_{kernel_type}_ep{world_size}.json匹配文件,并先按 gfx 架构、再按 GPU 型号打分。MI355X 在 EP8 下解析到flydsl_gfx950_mi355x_IntraNode_ep8.json。该表的第一条 fp4 dispatch 规则为
hidden_dim=7168, topk=6, local_expert_num=48。DeepSeek-V4-Pro 的config.json中hidden_size=7168、num_experts_per_tok=6、n_routed_experts=384,在 EP8 下每 rank 本地专家数为 384/8 = 48。完全匹配,因此可命中调优表,而不会回退到静态的 128-block / 4-warp 默认参数。即使形状不匹配也不会致命——该算子会打印using static geometry defaults并继续运行。本 PR 的 CI 运行提供了实测确认。验证
pytest utils/matrix_logic/ -q→ 224 通过test-config生成 8 个条目,DEP8 配置点正确携带ep: 8, dp-attn: True, conc: 32, router{vllm-router 0.1.14}bash -n语法检查通过;两个配置点的 MoE 后端选择逻辑均已验证🤖 Generated with Claude Code