-
Notifications
You must be signed in to change notification settings - Fork 257
[AMD] Add MI355X DeepSeek-V4-Pro Sglang Single Node AgentX recipes #2600
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
billishyahao
wants to merge
3
commits into
main
Choose a base branch
from
amd/mi355x-agentx-dsv4-sgl-Aug12
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+305
−0
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
273 changes: 273 additions & 0 deletions
273
benchmarks/single_node/agentic/dsv4_fp4_mi355x_sglang_mtp.sh
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,273 @@ | ||
| #!/usr/bin/env bash | ||
| set -eo pipefail | ||
| set -x | ||
|
|
||
| # Agentic trace replay benchmark for DeepSeek-V4-Pro FP4 on MI355X using SGLang | ||
| # with EAGLE/MTP speculative decoding. | ||
|
|
||
| source "$(dirname "$0")/../../benchmark_lib.sh" | ||
|
|
||
| check_env_vars MODEL TP CONC KV_OFFLOADING TOTAL_CPU_DRAM_GB RESULT_DIR DURATION EP_SIZE DP_ATTENTION | ||
|
|
||
| if [[ -n "$SLURM_JOB_ID" ]]; then | ||
| echo "JOB $SLURM_JOB_ID running on $SLURMD_NODENAME" | ||
| fi | ||
|
|
||
| # ROCR/HIP visibility under slurm cgroups. | ||
| if [ -n "$ROCR_VISIBLE_DEVICES" ]; then | ||
| export HIP_VISIBLE_DEVICES="$ROCR_VISIBLE_DEVICES" | ||
| fi | ||
|
|
||
| if [[ -n "$MODEL_PATH" ]]; then | ||
| if [[ ! -d "$MODEL_PATH" || -z "$(ls -A "$MODEL_PATH" 2>/dev/null)" ]]; then | ||
| hf download "$MODEL" --local-dir "$MODEL_PATH" | ||
| fi | ||
| else | ||
| hf download "$MODEL" | ||
| export MODEL_PATH="$MODEL" | ||
| fi | ||
| rocm-smi || true | ||
| amd-smi || true | ||
|
|
||
| # A server killed on this node minutes earlier (previous job, crashed run) | ||
| # can still be draining its HBM: KFD reclaim takes minutes, and booting into a | ||
| # half-drained node fails RCCL init with HIP 'unhandled cuda error' / | ||
| # 'invalid argument'. DeepSeek-V4-Pro is an 805 GiB checkpoint, so the drain | ||
| # window here is at the long end. Wait for the GPUs to come back before | ||
| # launching. Per-GPU threshold: idle nodes hold a small driver/firmware VRAM | ||
| # baseline (observed up to ~4%/GPU), while a draining or occupied GPU sits at | ||
| # 50-90%. Require every GPU <= 10%. | ||
| GPU_CLEAN=false | ||
| for i in $(seq 1 90); do | ||
| VRAM_MAX=$(rocm-smi --showmemuse 2>/dev/null | grep -oE "GPU Memory Allocated \(VRAM%\): [0-9]+" | awk '{if ($NF > m) m = $NF} END {print m+0}') | ||
| if [ "${VRAM_MAX:-0}" -le 10 ]; then echo "GPUs clean (vram%max=$VRAM_MAX after $((i*10))s)"; GPU_CLEAN=true; break; fi | ||
| echo "waiting for prior-job GPU memory reclaim: vram%max=$VRAM_MAX"; sleep 10 | ||
| done | ||
| [ "$GPU_CLEAN" = "true" ] || { echo "Error: GPUs still draining prior job's memory after 15min" >&2; exit 1; } | ||
|
|
||
| # ---- Resolve traces and install deps ---------------------------------------- | ||
| resolve_trace_source | ||
| install_agentic_deps | ||
|
|
||
| SERVER_LOG="$RESULT_DIR/server.log" | ||
| ROUTER_LOG="$RESULT_DIR/router.log" | ||
| mkdir -p "$RESULT_DIR" | ||
|
|
||
| # ---- Client config ---------------------------------------------------------- | ||
| export PYTHONNOUSERSITE=1 | ||
| # Agentic warmup dispatches hundreds of large prompts at once; allow up to | ||
| # 15 minutes of TCP progress before AIPerf declares a connection dead. | ||
| export AIPERF_HTTP_TCP_USER_TIMEOUT=900000 | ||
| # AIPerf pins one pooled keep-alive connection per session (client-side | ||
| # keep-alive 300s) while uvicorn's default SGLANG_TIMEOUT_KEEP_ALIVE is 5s; | ||
| # inter-turn idle gaps can reuse a socket exactly as the server closes it. | ||
| # Outlast the client pool so the race cannot occur. | ||
| export SGLANG_TIMEOUT_KEEP_ALIVE=900 | ||
|
|
||
| # ---- DSv4 kernel routing / thinking mode ------------------------------------ | ||
| # Mirrors the deleted spec-none sibling plus the DSv4 block in | ||
| # benchmarks/multi_node/amd_utils/env.sh. AgentX measures the thinking-on | ||
| # regime, which is also the golden-AL curve committed for this model. | ||
| export SGLANG_DEFAULT_THINKING=1 | ||
| export SGLANG_DSV4_REASONING_EFFORT=high | ||
| export SGLANG_USE_ROCM700A=0 | ||
| export SGLANG_HACK_FLASHMLA_BACKEND=unified_kv_triton | ||
| export AITER_BF16_FP8_MOE_BOUND=0 | ||
|
|
||
| # Unified radix tree: per-component (full-attn / SWA) cache management for | ||
| # hybrid-attention models, plus proactive release of out-of-window SWA KV | ||
| # slots during chunked prefill. Without the latter, in-flight requests pin SWA | ||
| # KV for their whole context and the trailing window of cached sessions gets | ||
| # flushed under LRU, collapsing the effective prefix-cache hit rate on | ||
| # multi-turn agentic workloads. | ||
| export SGLANG_ENABLE_UNIFIED_RADIX_TREE=1 | ||
| export SGLANG_OPT_UNIFIED_CACHE_FREE_OUT_OF_WINDOW_SLOTS=1 | ||
|
|
||
| # ---- HiCache (host DRAM KV tier) -------------------------------------------- | ||
| # Per-arm L2 sizing: host pinned memory is roughly | ||
| # HICACHE_RATIO * (per-rank device KV pool) * TP, which must stay under the | ||
| # node's ~2.7 TB of DRAM. The deleted spec-none sibling used ratio 4 with a | ||
| # smaller device pool; at TP8 with mem-fraction-static 0.85 that would | ||
| # oversubscribe host DRAM, so this recipe starts from 1.5 (the value validated | ||
| # on this cluster by glm5.2_fp4_mi355x_sglang_mtp.sh) and leaves every knob | ||
| # overridable for tuning. | ||
| CACHE_ARGS=() | ||
| if agentic_kv_offload_enabled; then | ||
| case "$KV_OFFLOAD_BACKEND" in | ||
| hicache) | ||
| HICACHE_RATIO="${HICACHE_RATIO:-1.5}" | ||
| HICACHE_WRITE_POLICY="${HICACHE_WRITE_POLICY:-write_through}" | ||
| HICACHE_IO_BACKEND="${HICACHE_IO_BACKEND:-direct}" | ||
| HICACHE_MEM_LAYOUT="${HICACHE_MEM_LAYOUT:-page_first_direct}" | ||
| echo "HiCache DSv4 CPU tier: ratio=$HICACHE_RATIO, write_policy=$HICACHE_WRITE_POLICY, io_backend=$HICACHE_IO_BACKEND, mem_layout=$HICACHE_MEM_LAYOUT, dram_budget=${TOTAL_CPU_DRAM_GB} GB, tp=$TP" | ||
| CACHE_ARGS=( | ||
| --enable-hierarchical-cache | ||
| --hicache-ratio "$HICACHE_RATIO" | ||
| --hicache-write-policy "$HICACHE_WRITE_POLICY" | ||
| --hicache-io-backend "$HICACHE_IO_BACKEND" | ||
| --hicache-mem-layout "$HICACHE_MEM_LAYOUT" | ||
| ) | ||
| ;; | ||
| *) | ||
| echo "Error: unsupported KV_OFFLOAD_BACKEND '$KV_OFFLOAD_BACKEND' (expected: hicache)" >&2 | ||
| exit 1 | ||
| ;; | ||
| esac | ||
| fi | ||
|
|
||
| # ---- Parallelism ------------------------------------------------------------ | ||
| # NOTE: the DP-attention path below is currently DORMANT (no dp-attn arms in | ||
| # amd-master.yaml for this key). It is kept so a future arm can enable it | ||
| # without rebuilding the router plumbing: sglang-router fronts the DP ranks | ||
| # with consistent hashing on the AIPerf correlation id, keeping multi-turn | ||
| # sessions on the DP rank that holds their radix/hicache prefix. | ||
| USE_SGLANG_ROUTER=false | ||
| SGLANG_BACKEND_PORT="$PORT" | ||
| # Small prefill chunks interleave long-context agentic prefills across | ||
| # requests instead of letting one ~100K-token prefill monopolize the engine | ||
| # (the conc>=16 queue-saturation / decode-stall failure mode). 8192 = 32*256, | ||
| # a page-size multiple well under the dsv4 compressor kernel's uint16 token | ||
| # cap; same value the multi-node DeepSeek-V4-Pro-AgentX no_dp profile uses. | ||
| CHUNKED_PREFILL_SIZE=8192 | ||
| # MTP adds a draft KV pool and extra graph captures on top of the spec-none | ||
| # footprint, which ran at 0.90. | ||
| MEM_FRACTION_STATIC=0.85 | ||
| PARALLEL_ARGS=(--tensor-parallel-size "$TP") | ||
| if [ "$DP_ATTENTION" = "true" ]; then | ||
| USE_SGLANG_ROUTER=true | ||
| export AIPERF_HTTP_X_SMG_ROUTING_KEY_FROM_CORRELATION_ID=true | ||
| SGLANG_BACKEND_PORT=$((PORT + 1)) | ||
| SGLANG_ROUTER_METRICS_PORT=$((PORT + 10000)) | ||
| SGLANG_ROUTER_CMD=(python3 -m sglang_router.launch_router) | ||
|
|
||
| export SGLANG_SHARED_EXPERT_TP1=1 | ||
| export SGLANG_DP_SHARED_EXPERT_LOCAL=1 | ||
| export SGLANG_DP_USE_GATHERV=1 | ||
| export SGLANG_DP_USE_REDUCE_SCATTER=1 | ||
| export GPU_MAX_HW_QUEUES=5 | ||
|
|
||
| # Chunked prefill is a whole-engine budget, so widen it by the DP degree. | ||
| CHUNKED_PREFILL_SIZE=$((8192 * TP)) | ||
| PARALLEL_ARGS+=( | ||
| --dp "$TP" | ||
| --enable-dp-attention | ||
| --enable-prefill-delayer | ||
| ) | ||
| fi | ||
|
|
||
| if [ "$EP_SIZE" -gt 1 ]; then | ||
| PARALLEL_ARGS+=(--ep-size "$EP_SIZE") | ||
| fi | ||
|
|
||
| # AgentX concurrency counts live session trees, not individual requests. | ||
| # Subagent fan-out can push instantaneous request concurrency above CONC, so | ||
| # leave 2x headroom rather than clipping those bursts at the scheduler. | ||
| MAX_RUNNING_REQUESTS=$((2 * CONC)) | ||
| [ "$MAX_RUNNING_REQUESTS" -gt 256 ] && MAX_RUNNING_REQUESTS=256 | ||
| CUDA_GRAPH_MAX_BS=$MAX_RUNNING_REQUESTS | ||
| [ "$CUDA_GRAPH_MAX_BS" -gt 128 ] && CUDA_GRAPH_MAX_BS=128 | ||
|
|
||
| # Saturation arms carry a larger in-flight working set than the 30-minute | ||
| # default warmup drain allows. | ||
| if [ "$CONC" -ge 32 ]; then | ||
| export AGENTIC_WARMUP_GRACE_PERIOD=3600 | ||
| fi | ||
|
|
||
| # ---- Speculative decoding --------------------------------------------------- | ||
| # DeepSeek-V4 ships a built-in MTP head, loaded through the EAGLE spec path | ||
| # with eagle-topk 1 (a single MTP chain); NOT NEXTN, whose V3/R1 loader | ||
| # crashes on the V4 architecture. Depth 3 matches the vLLM agentic sibling | ||
| # (dsv4-fp4-mi355x-vllm-agentic-mtp) and the fixed-seq-len SGLang MTP recipe. | ||
| SPEC_ARGS=( | ||
| --speculative-algorithm EAGLE | ||
| --speculative-num-steps 3 | ||
| --speculative-eagle-topk 1 | ||
| --speculative-num-draft-tokens 4 | ||
| ) | ||
|
|
||
| # Throughput runs pin acceptance to the committed golden AL for this model, | ||
| # thinking mode, and draft length (golden_al_distribution/dsv4_mtp.yaml: | ||
| # thinking_on, 3 -> 2.49). Eval-only runs keep real target verification so | ||
| # accuracy stays meaningful. | ||
| if [ "${EVAL_ONLY:-false}" != "true" ]; then | ||
| export SGLANG_SIMULATE_ACC_LEN=2.49 | ||
| export SGLANG_SIMULATE_ACC_METHOD=match-expected | ||
| export SGLANG_SIMULATE_ACC_TOKEN_MODE=real-draft-token | ||
| fi | ||
|
|
||
| # ---- Launch ----------------------------------------------------------------- | ||
| # No --chat-template: the AgentX traces are tool-heavy, and | ||
| # chat_templates/deepseek_v4_thinking.jinja renders only system/user/assistant | ||
| # (tool definitions and role: tool messages are silently dropped, which would | ||
| # truncate prompts and distort ISL). The multi-node DeepSeek-V4-Pro-AgentX | ||
| # profile and the vLLM agentic sibling both serve DSv4 without an override. | ||
| SGLANG_CMD=( | ||
| python3 -m sglang.launch_server | ||
| --model-path "$MODEL_PATH" | ||
| --served-model-name "$MODEL" | ||
| --host 0.0.0.0 | ||
| --port "$SGLANG_BACKEND_PORT" | ||
| --trust-remote-code | ||
| "${PARALLEL_ARGS[@]}" | ||
| --attention-backend dsv4 | ||
| --page-size 256 | ||
| --swa-full-tokens-ratio 0.10 | ||
| --kv-cache-dtype fp8_e4m3 | ||
| --disable-shared-experts-fusion | ||
| --tool-call-parser deepseekv4 | ||
| --reasoning-parser deepseek-v4 | ||
| --chunked-prefill-size "$CHUNKED_PREFILL_SIZE" | ||
| --mem-fraction-static "$MEM_FRACTION_STATIC" | ||
| --max-running-requests "$MAX_RUNNING_REQUESTS" | ||
| --cuda-graph-max-bs "$CUDA_GRAPH_MAX_BS" | ||
| "${SPEC_ARGS[@]}" | ||
| "${CACHE_ARGS[@]}" | ||
| # MTP draft-token forward passes under long-context agentic load block the | ||
| # scheduler long enough to trip the 1800s watchdog mid-warmup. | ||
| --watchdog-timeout 3600 | ||
| --enable-metrics | ||
| ) | ||
|
|
||
| printf '%q ' "${SGLANG_CMD[@]}" | tee "$RESULT_DIR/sglang_command.txt" | ||
| printf '\n' | tee -a "$RESULT_DIR/sglang_command.txt" | ||
|
|
||
| { | ||
| echo "=== SGLANG_* env vars at launch ===" | ||
| env | grep -E '^SGLANG_' | sort | ||
| echo "===================================" | ||
| } | tee "$SERVER_LOG" | ||
|
|
||
| echo "Starting SGLang server for MI355X..." | ||
| "${SGLANG_CMD[@]}" >> "$SERVER_LOG" 2>&1 & | ||
| SERVER_PID=$! | ||
| echo "Server PID: $SERVER_PID" | ||
|
|
||
| wait_for_server_ready --port "$SGLANG_BACKEND_PORT" --server-log "$SERVER_LOG" --server-pid "$SERVER_PID" | ||
|
|
||
| if [ "$USE_SGLANG_ROUTER" = "true" ]; then | ||
| echo "Starting SGLang router on port $PORT for $TP DP ranks..." | ||
| "${SGLANG_ROUTER_CMD[@]}" \ | ||
| --worker-urls "http://localhost:$SGLANG_BACKEND_PORT" \ | ||
| --policy consistent_hashing \ | ||
| --request-id-headers x-correlation-id \ | ||
| --dp-aware \ | ||
| --host 0.0.0.0 \ | ||
| --port "$PORT" \ | ||
| --prometheus-host 127.0.0.1 \ | ||
| --prometheus-port "$SGLANG_ROUTER_METRICS_PORT" \ | ||
| --connect-timeout-secs 900 \ | ||
| --request-timeout-secs 14400 \ | ||
| --disable-health-check \ | ||
| --disable-retries > "$ROUTER_LOG" 2>&1 & | ||
| ROUTER_PID=$! | ||
| echo "Router PID: $ROUTER_PID" | ||
| wait_for_server_ready --port "$PORT" --server-log "$ROUTER_LOG" --server-pid "$ROUTER_PID" | ||
| fi | ||
|
|
||
| if [ "${EVAL_ONLY}" = "true" ]; then | ||
| run_eval --port "$PORT" | ||
| else | ||
| build_replay_cmd "$RESULT_DIR" | ||
| REPLAY_CMD+=" --server-metrics http://localhost:$SGLANG_BACKEND_PORT/metrics" | ||
| run_agentic_replay_and_write_outputs "$RESULT_DIR" | ||
| fi | ||
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
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
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 The 15-min GPU-drain-wait loop at lines 29-40 duplicates
wait_for_amd_gpu_clean()inbenchmark_lib.sh(lines 240-258) character-for-character, even though this script already sources that file. Replace the inline block with a call towait_for_amd_gpu_clean || exit 1so future threshold/timeout tuning only needs to happen in one place.Extended reasoning...
What the bug is. Lines 29-40 of the new
dsv4_fp4_mi355x_sglang_mtp.shre-implement a 15-minute GPU-drain-wait poll loop:This is copy-pasted verbatim (only the surrounding comment differs, mentioning 805 GiB for DeepSeek-V4-Pro vs. the ~1.4 TB comment in the GLM-5.2 sibling script) from
glm5.2_fp4_mi355x_sglang_mtp.sh, which has the identical block at the same lines.Why this is avoidable.
benchmark_lib.shalready defines a helper,wait_for_amd_gpu_clean()(lines 240-258), whose body is a character-for-character match of the inlined logic here: sameseq 1 90loop, samerocm-smi --showmemuse | grep -oE "GPU Memory Allocated (VRAM%): [0-9]+" | awkpipeline, same <=10% threshold, same 90x10s = 15-minute budget, and the same 'GPUs still draining prior job's memory after 15min' error message. This isn't a hypothetical refactor target — the helper is already the canonical, adopted pattern:kimik3_fp4_mi355x_mtp.shalready callswait_for_amd_gpu_cleaninstead of inlining the loop.The code path that triggers it. The new script sources
benchmark_lib.shat line 8 (source "$(dirname "$0")/../../benchmark_lib.sh"), sowait_for_amd_gpu_cleanis already in scope at the point where the inline block sits. Nothing prevents calling it directly.Why existing code doesn't prevent it. AGENTS.md states: 'Shared benchmark Bash behavior belongs in benchmark_lib.sh, with parameters passed through environment variables.' Nothing enforces this at review time beyond that guidance, so it's easy for a new recipe branched from a sibling script (in this case
glm5.2_fp4_mi355x_sglang_mtp.sh) to inherit an inlined pattern rather than the shared helper. Now three copies of this exact logic exist:benchmark_lib.sh's helper, and two duplicated inline copies inglm5.2_fp4_mi355x_sglang_mtp.shand this new script.Impact. No runtime failure — the inlined code is functionally correct and behaves identically to the helper. The cost is maintainability: any future tuning of the 10% threshold, the 90x10s timeout, or the rocm-smi parsing pipeline now has to be updated in up to three places instead of one, and they can silently drift (which has already started, per the differing comment wording).
Step-by-step proof of duplication:
benchmark_lib.sh:240-258— defineswait_for_amd_gpu_clean()with the loop, threshold, and error message described above.dsv4_fp4_mi355x_sglang_mtp.sh:29-40(this PR) — same loop, samerocm-smipipeline, same<= 10threshold, same error text, just inlined instead of calling the helper.glm5.2_fp4_mi355x_sglang_mtp.shat the equivalent lines — same inlined block again (source of the copy-paste).kimik3_fp4_mi355x_mtp.sh— this sibling script instead callswait_for_amd_gpu_clean, proving the helper is already the intended, working call site pattern.Fix. Since the script runs under
set -eo pipefail(line 2) and the helper returns 1 on failure, replace lines 29-40 with:(or simply
wait_for_amd_gpu_clean, sinceset -ewill already exit non-zero on failure).