Skip to content

perf(amd): port MI355X Qwen3.5 FP8 disagg sweep to srt-slurm - #2628

Open
cquil11 wants to merge 11 commits into
mainfrom
agent/port-qwen35-fp8-mi355x-srt-slurm
Open

perf(amd): port MI355X Qwen3.5 FP8 disagg sweep to srt-slurm#2628
cquil11 wants to merge 11 commits into
mainfrom
agent/port-qwen35-fp8-mi355x-srt-slurm

Conversation

@cquil11

@cquil11 cquil11 commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • replace the active MI355X Qwen3.5 FP8 legacy AMD disaggregated launcher with the existing native srt-slurm SGLang Router + MoRI recipe
  • preserve the production 1P1D TP8 topology and complete c8/c16/c32/c64/c128 8k1k search space
  • run every point inside one eight-hour Slurm allocation through the repository's unchanged benchmark_serving.py
  • remove the model-specific legacy wrapper that only translated matrix variables into amd_utils/submit.sh
  • consolidate the temporary -srt-disagg validation key back into the original production config key

Validation

  • srt-slurm dry-run passes against feat(runtime): add AMD accelerator support srt-slurm#1 at c609754b5622f96d5c12a93149e245308d4f1e9b
  • generated matrix contains exactly one topology job; the recipe owns all five concurrency points
  • 231 matrix-generation and validation tests pass
  • changed YAML parses and git diff --check passes

Stack

Based on #2542. This remains draft until the exact-head MI355X full recipe sweep completes.


Note

Medium Risk
Changes how production MI355X disagg benchmarks are submitted and how results are collected; behavior is preserved but depends on a pinned external srt-slurm revision and new Slurm staging logic.

Overview
Replaces the MI355X Qwen3.5 FP8 disaggregated path that went through amd_utils/submit.sh with srt-slurm (srtctl apply), SGLang Router, and MoRI KV transfer. The legacy wrapper qwen3.5_fp8_mi355x_sglang-disagg.sh is removed.

Adds an MI355X AMD Slurm cluster profile, a production recipe (disagg-1p1d-tp4p-tp8d-fixed-seq.yaml) that keeps 1P1D TP4 prefill + TP8 decode and runs the 8k/1k fixed-seq sweep at c8–c128 inside one eight-hour job via benchmark_serving.py. amd-master.yaml now expands to a single matrix row (conc-list: [1]) and passes CONFIG_FILE into prefill settings instead of per-concurrency Slurm submissions.

Adds launch_mi355x-amds-srt.sh (staging squashfs/HF cache, pinned srt-slurm commit, result collection) and dispatches from launch_mi355x-amds.sh when CONFIG_FILE is set. perf-changelog documents the migration.

Reviewed by Cursor Bugbot for commit d2d9db8. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions

Copy link
Copy Markdown
Contributor

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 As a PR reviewer and CODEOWNER, I have reviewed this and have.

For PR verification, add the full-sweep-fail-fast label (strongly recommended) to this PR — the benchmark sweep only runs on labeled PRs. Use full-sweep-enabled only if you need matrix jobs to keep running past a failure.

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 模板,包括保留英文语句 As a PR reviewer and CODEOWNER, I have reviewed this and have

如需进行 PR 验证,请为此 PR 添加 full-sweep-fail-fast 标签(强烈推荐)— 基准测试 sweep 仅在带有标签的 PR 上运行。仅当需要矩阵任务在失败后继续运行时才使用 full-sweep-enabled

PR 作者有责任确保合并后所有 GitHub Action 任务完全通过。 很多时候失败只是偶发抖动(flake),重新运行失败的任务即可解决。参见 GitHub 关于重新运行失败任务的文档

@cquil11
cquil11 force-pushed the agent/srt-slurm-amd-integration branch from 72ee996 to 2182835 Compare August 26, 2026 23:10
@cquil11
cquil11 force-pushed the agent/port-qwen35-fp8-mi355x-srt-slurm branch from dda2f3a to 32d3ee2 Compare August 26, 2026 23:54
@cquil11
cquil11 changed the base branch from agent/srt-slurm-amd-integration to main August 26, 2026 23:54
@cquil11
cquil11 marked this pull request as ready for review August 26, 2026 23:54
@cquil11
cquil11 requested a review from a team August 26, 2026 23:54
@claude

claude Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Claude finished @cquil11's task in 6m 38s —— View job


PR Review

  • Gather context and read the full diff vs origin/main
  • Review new recipe YAML, cluster config, and launcher script
  • Validate master config + perf-changelog rules (pairing, append position)
  • Validate image accessibility and enroot import pattern
  • Post findings and summary

Summary

Found 1 blocking issue and 2 warnings (posted inline):

  • 🔴 The recipe's decode block inherits tensor-parallel-size: 4 from the &common_config anchor and never overrides it to 8, contradicting gpus_per_decode: 8, the tp8d name, the master config's decode.tp: 8, and the changelog — decode would either fail to launch or silently run TP4 while results are stamped as 8 decode GPUs. (inline) Fix this →
  • 🟡 The launcher pins srt-slurm commit ad63a66c, but the PR description says validation ran at c609754b (srt-slurm#1) and the changelog cites "PR [NVIDIA] Add DSR1 TensorRT Support and Enhanced Plotting #7" — the provenance references need reconciling so the pin matches what was actually validated. (inline)
  • 🟡 In runners/launch_mi355x-amds-srt.sh, when the srt-slurm job fails before the benchmark step, RESULT_DIR doesn't exist and cp -R aborts under set -e before the intended failure message and log collection; the sacct read has a similar accounting-lag flake. (inline)

Checks that passed: master config change is paired with a perf-changelog.yaml entry appended at the end of the file; the identity image lmsysorg/sglang:v0.5.16-rocm720-mi35x is public Docker Hub and the launcher has the enroot import ... docker:// reproducibility pattern (with a same-image local fast path); CONFIG_FILE is only set by this one row, so the exec hook in launch_mi355x-amds.sh can't hijack other MI355X jobs; conc-list: [1] is safe because utils/process_result.py takes concurrency from each result JSON's max_concurrency, and the ${RESULT_FILENAME}_*.json globs match the launcher's _srt-<jobid>_conc<N> naming; the benchmark_serving.py flags used (--random-num-workers, --num-warmups) exist.

decode:
<<: *common_config
mem-fraction-static: 0.85
prefill-round-robin-balance: true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Decode inherits prefill TP4

High Severity

The decode block merges common_config and never overrides tensor-parallel-size, so decode launches with TP4 while gpus_per_decode and the master config still describe TP8. The 1P1D TP4P+TP8D topology this recipe is named for does not actually start.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 32d3ee2. Configure here.

--dataset-name random \
--random-input-len 8192 \
--random-output-len 1024 \
--random-range-ratio 1.0 \

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Benchmark uses exact sequence lengths

Medium Severity

The custom client hardcodes --random-range-ratio 1.0, while the production 8k1k path this replaces always used 0.8 from the multi-node workflow. That changes sampled prompt and output lengths, so the new points are not the same 8k1k workload.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 32d3ee2. Configure here.

Comment on lines +99 to +102
decode:
<<: *common_config
mem-fraction-static: 0.85
prefill-round-robin-balance: true

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.

🔴 BLOCKING: Decode inherits tensor-parallel-size: 4 from the &common_config anchor and never overrides it, so the decode engine is TP4, not the TP8 this PR claims everywhere else (gpus_per_decode: 8 at line 39, the tp8d recipe name, decode.tp: 8 in configs/amd-master.yaml, and the perf-changelog's "one-decode TP8").

Why it matters: Existing srt-slurm sglang recipes (e.g. disagg-b300-1p1d-dep4-dep8.yaml) set tensor-parallel-size explicitly per role, so srt-slurm will not derive it from gpus_per_decode. Best case the run fails on the 4-vs-8 mismatch; worst case decode silently runs TP4 while the launcher stamps result filenames with gen_8/gpus_12 (it computes DECODE_GPUS from the master config's DECODE_TP=8), publishing incorrect perf/GPU numbers.

Fix:

Suggested change
decode:
<<: *common_config
mem-fraction-static: 0.85
prefill-round-robin-balance: true
decode:
<<: *common_config
tensor-parallel-size: 8
mem-fraction-static: 0.85
prefill-round-robin-balance: true

Also double-check the other inherited prefill-oriented values: decode inherits cuda-graph-bs: [1..8], so at the c64/c128 points decode batches above 8 run in eager mode — if that's not intentional, the published high-concurrency points will understate decode throughput.

Comment thread runners/launch_mi355x-amds-srt.sh Outdated
# in explicitly with CONFIG_FILE; all existing MI355X launch behavior remains
# unchanged for every other row.
SRT_SLURM_REPOSITORY="https://github.com/SemiAnalysisAI/srt-slurm.git"
SRT_SLURM_COMMIT="2f18c3ba4a59061b41bd1d26c8d04fb530692105"

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.

🟡 WARNING: The pinned commit doesn't match the validation provenance stated elsewhere. The PR description says the dry-run passed against SemiAnalysisAI/srt-slurm#1 at c609754b5622f96d5c12a93149e245308d4f1e9b, the perf-changelog entry says "Pin the launcher to the exact SemiAnalysisAI/srt-slurm PR #7 revision used for validation", and this script pins ad63a66c72404691bdda98c656d6e211156fb582. At most one of these can be the revision that was actually validated.

Why it matters: If ad63a66c was never exercised by the dry-run/full sweep, the first production run executes an unvalidated launcher revision; if it was, the changelog and PR description are documenting the wrong provenance for a pinned production dependency.

Fix: Reconcile the three references — update the PR description/changelog to the commit that actually ran the full MI355X sweep, or re-pin this to the validated commit.

Comment on lines +150 to +155
RESULT_DIR="${SHARED_RESULTS}/${JOB_ID}"
mkdir -p "$GITHUB_WORKSPACE/LOGS"
if [[ -f "$RESULT_DIR/runtime-logs.tar.gz" ]]; then
cp "$RESULT_DIR/runtime-logs.tar.gz" "$GITHUB_WORKSPACE/multinode_server_logs.tar.gz"
fi
cp -R "$RESULT_DIR/." "$GITHUB_WORKSPACE/LOGS/"

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.

🟡 WARNING: The failure path dies before reaching its own diagnostics. If the srt-slurm job fails before the benchmark step runs (e.g. a server never passes health check), ${SHARED_RESULTS}/${JOB_ID} is never created — the recipe's benchmark.command is what mkdirs it. Under set -euo pipefail, cp -R "$RESULT_DIR/." then aborts the script with a raw "No such file or directory", so the intended srt-slurm validation failed: ${JOB_STATE} message at the bottom never prints and no logs are collected for debugging. Relatedly, the read -r JOB_STATE ... from sacct at line 144 exits nonzero if sacct hasn't recorded the job yet (a common lag right after it leaves squeue), which also kills the script under set -e after an up-to-8-hour run.

Fix: Guard the copies on the directory existing (and consider a short retry around sacct):

Suggested change
RESULT_DIR="${SHARED_RESULTS}/${JOB_ID}"
mkdir -p "$GITHUB_WORKSPACE/LOGS"
if [[ -f "$RESULT_DIR/runtime-logs.tar.gz" ]]; then
cp "$RESULT_DIR/runtime-logs.tar.gz" "$GITHUB_WORKSPACE/multinode_server_logs.tar.gz"
fi
cp -R "$RESULT_DIR/." "$GITHUB_WORKSPACE/LOGS/"
RESULT_DIR="${SHARED_RESULTS}/${JOB_ID}"
mkdir -p "$GITHUB_WORKSPACE/LOGS"
if [[ -f "$RESULT_DIR/runtime-logs.tar.gz" ]]; then
cp "$RESULT_DIR/runtime-logs.tar.gz" "$GITHUB_WORKSPACE/multinode_server_logs.tar.gz"
fi
if [[ -d "$RESULT_DIR" ]]; then
cp -R "$RESULT_DIR/." "$GITHUB_WORKSPACE/LOGS/"
fi

With this guard, a failed job falls through to the existing JOB_STATE != COMPLETED check and exits with the meaningful message (the RESULTS array check already handles the no-results case).

Comment on lines +80 to +102
prefill: &common_config
served-model-name: "Qwen/Qwen3.5-397B-A17B-FP8"
tensor-parallel-size: 4
disaggregation-transfer-backend: mori
disaggregation-ib-device: "rdma0,rdma1,rdma2,rdma3,rdma4,rdma5,rdma6,rdma7"
kv-cache-dtype: "fp8_e4m3"
attention-backend: aiter
moe-dense-tp-size: 1
load-balance-method: round_robin
watchdog-timeout: 3600
decode-log-interval: 1000
log-level: warning
mem-fraction-static: 0.80
max-running-requests: 128
chunked-prefill-size: 262144
context-length: 16384
disable-radix-cache: true
cuda-graph-bs: [1, 2, 3, 4, 5, 6, 7, 8]
enable-metrics: true
decode:
<<: *common_config
mem-fraction-static: 0.85
prefill-round-robin-balance: true

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.

🔴 decode config merges *common_config (prefill's tensor-parallel-size: 4) via YAML anchor and never overrides it, so decode runs TP4 instead of TP8 despite the recipe name/gpus_per_decode: 8 and configs/amd-master.yaml decode.tp: 8 specifying TP8 decode.

Extended reasoning...

The benchmark topology silently regresses from the documented '1P TP4 + 1D TP8' to '1P TP4 + 1D TP4' on an 8-GPU decode node: decode either fails to start (TP size mismatch with the 8-GPU allocation/MoRI KV transfer expecting 8 decode ranks) or runs with half the GPUs actually used, producing throughput/latency numbers that do not reflect the intended TP8 decode topology and invalidating the perf-changelog claim of preserving 'the current one-prefill TP4 plus one-decode TP8 topology.'

Verification: normal. The new recipe's decode block (benchmarks/multi_node/srt-slurm-recipes/sglang/qwen3.5/mi355x/disagg-1p1d-tp4p-tp8d-fixed-seq.yaml:100-103) is decode:\n <<: *common_config\n mem-fraction-static: 0.85\n prefill-round-robin-balance: true. It merges &common_config which sets tensor-parallel-size: 4, and never overrides it, so decode inherits TP4. Meanwhile the same file sets…

Comment on lines +93 to +97
max-running-requests: 128
chunked-prefill-size: 262144
context-length: 16384
disable-radix-cache: true
cuda-graph-bs: [1, 2, 3, 4, 5, 6, 7, 8]

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.

🔴 The recipe's cuda-graph-bs is hard-coded to [1,2,3,4,5,6,7,8] for prefill (and inherited unchanged by decode via the *common_config anchor), even though max-running-requests is 128 and the benchmark sweeps concurrency up to 128. The legacy amd_utils config this replaces (benchmarks/multi_node/amd_utils/models.yaml, Qwen3.5-397B-A17B-FP8, no_dp branch used since dp-attn is false here) used cuda_graph_bs_range: "1-128", expanded via seq 1 128 in server_sglang.sh, i.e. a CUDA graph for every batch size 1-128.

Extended reasoning...

During the c16/c32/c64/c128 points of the sweep (the majority of the 5-point search space), SGLang's running batch size exceeds 8, so no captured CUDA graph exists and the engine silently falls back to eager-mode execution for prefill and decode alike. This produces throughput/latency numbers for most of the sweep that no longer reflect the previously-measured, CUDA-graph-covered production performance -- a silent, undocumented perf regression relative to the pre-migration amd_utils path, despite the PR claiming to 'preserve the production 1P1D TP4P+TP8D topology' and complete search space with parity. Fix: restore cuda-graph-bs to cover 1-128 (or the actual running-request ceiling) for both prefill and decode.

Verification: normal — benchmark-fidelity regression vs base introduced by this change. The new recipe caps CUDA-graph capture at batch size 8 while admitting batches up to 128 and sweeping concurrency to 128: - disagg-1p1d-tp4p-tp8d-fixed-seq.yaml:97 cuda-graph-bs: [1, 2, 3, 4, 5, 6, 7, 8] in the prefill &common_config anchor. - :93 max-running-requests: 128. - :100-102 decode does <<: *common_config

@cquil11
cquil11 force-pushed the agent/port-qwen35-fp8-mi355x-srt-slurm branch from 6513cc6 to 70228fa Compare August 27, 2026 01:56
chunked-prefill-size: 262144
context-length: 16384
disable-radix-cache: true
cuda-graph-bs: [1, 2, 3, 4, 5, 6, 7, 8]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CUDA graph batch list truncated

High Severity

cuda-graph-bs is [1, 2, 3, 4, 5, 6, 7, 8], but the production Qwen3.5 FP8 no-DP recipe captures graphs for batch sizes 1 through 128. The sweep still runs conc 16/32/64/128 with max-running-requests: 128, so those points miss CUDA graphs and will understate decode throughput versus the current MI355X baseline.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 70228fa. Configure here.

python3 /infmax-workspace/utils/bench_serving/benchmark_serving.py \
--backend openai-chat \
--base-url "http://${SRT_FRONTEND_HOST}:${SRT_FRONTEND_PORT}" \
--endpoint /v1/chat/completions \

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Benchmark backend switched to chat

Medium Severity

The custom client uses --backend openai-chat and /v1/chat/completions. The production AMD sweep called benchmark_serving.py with --backend openai (completions) and no chat template. Chat wrapping changes tokenized ISL/OSL and the router path, so results will not match the prior 8k1k disagg numbers.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 70228fa. Configure here.

@cquil11
cquil11 force-pushed the agent/port-qwen35-fp8-mi355x-srt-slurm branch 3 times, most recently from 2ffe415 to e0d0626 Compare August 27, 2026 06:23

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

There are 5 total unresolved issues (including 4 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e0d0626. Configure here.

@cquil11
cquil11 force-pushed the agent/port-qwen35-fp8-mi355x-srt-slurm branch from e0d0626 to 3f4f3fa Compare August 27, 2026 06:42
@github-actions

Copy link
Copy Markdown
Contributor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant