Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions benchmarks/benchmark_lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1660,7 +1660,7 @@ resolve_trace_source() {
# WEKA_LOADER_OVERRIDE.
local default_loader
case "${MODEL_PREFIX:-}" in
dsv4*|minimaxm3*)
dsv4*|glm5.2*|minimaxm3*)
default_loader="semianalysis_cc_traces_weka_062126"
;;
*)
Expand Down Expand Up @@ -1780,8 +1780,11 @@ build_replay_cmd() {
# only after those requests drain and resumes from the resulting live state.
REPLAY_CMD+=" --agentic-cache-warmup-duration 600"
# Give long-context warmup requests up to 30 minutes to drain before
# declaring warmup failed. Recipes whose saturation arms carry a larger
# in-flight working set may override via AGENTIC_WARMUP_GRACE_PERIOD
# (grace is a maximum wait, not a fixed sleep — drain exits when done).
# cancelling any remaining requests and starting profiling.
REPLAY_CMD+=" --warmup-grace-period 1800"
REPLAY_CMD+=" --warmup-grace-period ${AGENTIC_WARMUP_GRACE_PERIOD:-1800}"
# Use server-reported usage fields (prompt_tokens / completion_tokens) for
# ISL/OSL instead of client-side tokenizer.encode(). Auto-enables
# stream_options.include_usage on the OpenAI chat endpoint. Skips the
Expand Down
186 changes: 186 additions & 0 deletions benchmarks/single_node/agentic/glm5.2_fp4_b300_sglang.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
#!/usr/bin/env bash
set -euo pipefail
set -x

# Agentic trace replay benchmark for GLM-5.2 NVFP4 on B300 using SGLang.
Comment on lines +1 to +5

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.

🟡 This PR's title and description are English-only, violating the AGENTS.md rule that all PR titles/descriptions must be bilingual (title format ' / <中文标题>' plus a mirrored Chinese section in the body).

Extended reasoning...

AGENTS.md line 7 states as an explicit, mandatory rule: "PR and GitHub-issue titles & descriptions must be bilingual — include a Simplified Chinese version in addition to English. Title format: <English title> / <中文标题>. In the PR/issue body, follow the English content with its Chinese translation (e.g. a ## 中文说明 section mirroring the summary...)". This applies to every PR, with no carve-out for benchmark-recipe PRs.

This PR's title is 'Add GLM-5.2 NVFP4 B300 SGLang single-node agentic benchmarks' — no '/ <中文标题>' suffix — and the description's Summary, Changes, and Validation sections are entirely in English with no mirrored '## 中文说明' section or equivalent.

This is not a matter of subjective style: the repo's own commit log shows a sibling recipe PR following the rule correctly. Commit d85fa13 (PR #2182) has the bilingual title 'Add MiniMax M3 8k/1k Dynamo vLLM B300 EAGLE recipes / 新增 MiniMax M3 8k/1k Dynamo vLLM B300 EAGLE 配方', proving other contributors in this exact benchmark-recipe workflow are expected to (and do) satisfy the rule. This PR is the outlier.

Step-by-step proof:

  1. AGENTS.md:7 mandates bilingual title format ' / <中文标题>' and a mirrored Chinese section in the body for every PR.
  2. This PR's title metadata reads 'Add GLM-5.2 NVFP4 B300 SGLang single-node agentic benchmarks' — checking the string for '/' followed by CJK characters finds none.
  3. The description contains only '## Summary', '## Changes', '## Validation' — all English — with no '## 中文说明' or any CJK text anywhere in the body.
  4. Comparing against PR Add MiniMax M3 8k/1k Dynamo vLLM B300 EAGLE recipes / 新增 MiniMax M3 8k/1k Dynamo vLLM B300 EAGLE 配方 #2182 in the git log (same repo, same benchmark-recipe category, merged 3 commits prior) shows the correctly-formatted bilingual title, confirming the rule is actively enforced/followed elsewhere and this PR is non-compliant.

Impact: none on benchmark correctness or CI — this is a process/documentation compliance gap, not a code defect. Fix is simple: the author (or whoever finalizes the merge) should append '/ <一句中文标题>' to the PR title and add a '## 中文说明' section mirroring the Summary/Changes/Validation content before merge.

#
# Server flags follow the SGLang cookbook B300 NVFP4 single-node recipes
# (https://docs.sglang.io/cookbook/autoregressive/GLM/GLM-5.2), STP only:
# the cookbook's EAGLE MTP variants are intentionally not wired up yet.
# DP_ATTENTION=false -> low-latency arm (TP8, fp8 KV, cutedsl bf16 GEMM)
# DP_ATTENTION=true -> high-throughput arm (TP8 + DP8 attention-DP)
#
# Required env vars:
# MODEL, TP, CONC, KV_OFFLOADING, TOTAL_CPU_DRAM_GB, RESULT_DIR, DURATION,
# EP_SIZE, DP_ATTENTION

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 [[ "$KV_OFFLOADING" != "none" ]]; then
echo "Error: KV_OFFLOADING=$KV_OFFLOADING is not supported by this recipe" >&2
exit 1
fi

if [[ -n "${SLURM_JOB_ID:-}" ]]; then
echo "JOB $SLURM_JOB_ID running on ${SLURMD_NODENAME:-unknown}"
fi

# `hf download` creates the target dir if missing and is itself idempotent.
# When MODEL_PATH is unset (stand-alone runs), fall back to the HF_HUB_CACHE.
# Either way, MODEL_PATH is what the server is launched with.
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
nvidia-smi

resolve_trace_source
install_agentic_deps

SERVER_LOG="$RESULT_DIR/server.log"
mkdir -p "$RESULT_DIR"

# With attention-DP, front the DP ranks with sglang-router using consistent
# hashing on the AIPerf correlation id so multi-turn sessions stay on the DP
# rank that holds their radix-cache prefix.
USE_SGLANG_ROUTER=false
SGLANG_BACKEND_PORT="$PORT"
ROUTER_LOG="$RESULT_DIR/router.log"
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))
fi

PARALLEL_ARGS=(--tp "$TP" --ep-size "$EP_SIZE")
CHUNKED_PREFILL_SIZE=8192
if [ "$DP_ATTENTION" = "true" ]; then
# chunked-prefill-size is a whole-engine budget split across DP ranks:
# the cookbook HT cell's 8192 becomes 1,024 tokens/rank/step under dp8,
# which starves prefill on the 1M-context agentic corpus (observed: a
# conc-256 warmup could not drain within AIPerf's 1800s grace period
# while KV usage sat at ~0.01). Use the cookbook's own dp8 lever from
# the B200 cells (32768 = ~4096/rank).
CHUNKED_PREFILL_SIZE=32768
# At conc 512 the saturation working set outlives the default 1800s
# warmup drain grace: the drain converges healthily (~0.45 req/s, zero
# errors) but needs ~2500s end to end. 3600 is a maximum wait, not a
# fixed sleep — lower-conc DPA points still finish as fast as they drain.
export AGENTIC_WARMUP_GRACE_PERIOD=3600
PARALLEL_ARGS+=(
--dp "$TP"
--enable-dp-attention
--tokenizer-worker-num "$TP"
--dist-init-addr "127.0.0.1:$((PORT + 2000))"
)
else
# Cookbook low-latency levers; the DP-attention cell omits them.
PARALLEL_ARGS+=(
--kv-cache-dtype fp8_e4m3
--bf16-gemm-backend cutedsl
--max-prefill-tokens 8192
)
fi

# AgentX concurrency counts live session trees, not individual requests.
# Allow subagent fan-out to exceed CONC without clipping request bursts.
MAX_RUNNING_REQUESTS=$((2 * CONC))
GRAPH_ARGS=()
if [ "$DP_ATTENTION" != "true" ]; then
# Cookbook low-latency captures graphs up to its request cap; the
# DP-attention cell leaves the CUDA-graph batch list at SGLang defaults.
CUDA_GRAPH_MAX_BS=$MAX_RUNNING_REQUESTS
[ "$CUDA_GRAPH_MAX_BS" -gt 64 ] && CUDA_GRAPH_MAX_BS=64
GRAPH_ARGS=(--cuda-graph-max-bs "$CUDA_GRAPH_MAX_BS")
fi

export PYTHONNOUSERSITE=1
export TORCH_CUDA_ARCH_LIST=10.0
# 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 (capped at 10s) can reuse a socket exactly as the
# server closes it -> ECONNRESET -> terminal warmup failure. Outlast the
# client pool so the race cannot occur.
export SGLANG_TIMEOUT_KEEP_ALIVE=900

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[@]}"
--quantization modelopt_fp4
# GLM-5.2 emits the GLM-4.7-style <tool_call>/<arg_key>/<arg_value> format;
# the glm47 parser is required for structured message.tool_calls (glm45
# leaves calls as raw text). Without it the SWE-bench mini-swe-agent eval
# dies with RepeatedFormatError ("No tool calls found in the response") on
# every instance and scores 0. Reasoning parser keeps hybrid-thinking
# output in reasoning_content instead of polluting content. Neither flag
# affects trace-replay throughput (pre-canned replay discards live
# responses).
--tool-call-parser glm47
--reasoning-parser glm45
--chunked-prefill-size "$CHUNKED_PREFILL_SIZE"
--mem-fraction-static 0.85
--max-running-requests "$MAX_RUNNING_REQUESTS"
"${GRAPH_ARGS[@]}"
--watchdog-timeout 1800
--enable-metrics
)

printf '%q ' "${SGLANG_CMD[@]}" | tee "$RESULT_DIR/sglang_command.txt"
printf '\n' | tee -a "$RESULT_DIR/sglang_command.txt"

echo "Starting SGLang server for B300..."
"${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..."
python3 -m sglang_router.launch_router \
--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
# GLM-5.2's chat template defaults to reasoning_effort=Max when the
# client passes no chat_template_kwargs (mini-swe-agent doesn't), and the
# heavy thinking burns the default 75-step budget: on the 23-instance
# slice, 12/23 trajectories exited LimitsExceeded unsubmitted while 10 of
# the 11 that submitted resolved. Double the step budget for this recipe;
# other recipes keep the shared 75 default.
export SWEBENCH_AGENT_STEP_LIMIT=150
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
22 changes: 22 additions & 0 deletions configs/nvidia-master.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7853,3 +7853,25 @@ qwen3.5-fp8-gb300-dynamo-sglang:
tp: 16
ep: 16
dp-attn: true

# GLM-5.2 B300 NVFP4 AgentX frontier from the SGLang cookbook B300 NVFP4
# single-node recipes (https://docs.sglang.io/cookbook/autoregressive/GLM/GLM-5.2),
# STP only (the cookbook's EAGLE MTP variants are deferred). The TP8 arm is the
# cookbook low-latency recipe (fp8 KV, cutedsl bf16 GEMM) and covers the
# interactivity end; the TP8/DP8 attention-DP arm is the cookbook
# high-throughput recipe behind sglang-router consistent hashing for session
# affinity. Conc lists are disjoint between arms so exp-names stay unique.
glm5.2-fp4-b300-sglang-agentic:
image: lmsysorg/sglang:v0.5.15.post1-cu130
model: nvidia/GLM-5.2-NVFP4
model-prefix: glm5.2
runner: cluster:b300-nv
precision: fp4
framework: sglang
multinode: false
scenarios:
agentic-coding:
- dram-utilization: 0.80
search-space:
- { tp: 8, kv-offloading: none, conc-list: [1, 2, 4, 8, 16, 32] }
- { tp: 8, dp-attn: true, kv-offloading: none, conc-list: [48, 64, 96, 128, 192, 256, 512], router: { name: sglang-router, version: "0.3.2" } }
Comment on lines +7864 to +7877

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 high-throughput arm ({ tp: 8, dp-attn: true, ... }) omits the ep key, so generate_sweep_configs.py defaults EP_SIZE to 1, and glm5.2_fp4_b300_sglang.sh unconditionally passes --ep-size $EP_SIZE to the server. This launches attention-DP (dp=8) with ep-size=1, i.e. no expert parallelism across the 8 DP ranks for this MoE model — every other dp-attn: true entry in this file (including the DSv4 recipe this PR says it copies) pairs it with an explicit ep equal to tp. Add ep: 8 to match the intended/validated recipe.

Extended reasoning...

The bug: The high-throughput search-space entry for glm5.2-fp4-b300-sglang-agentic is:

- { tp: 8, dp-attn: true, kv-offloading: none, conc-list: [48, 64, 96, 128, 192, 256, 512], router: { name: sglang-router, version: "0.3.2" } }

It sets tp: 8 and dp-attn: true but never sets ep. In utils/matrix_logic/generate_sweep_configs.py (single-node agentic branch, line 1055 — mirrored at lines 756/943 for the other scenario branches), the EP field is computed as:

Fields.EP.value: ep if ep is not None else 1,

Since the config never supplies ep, this resolves to EP_SIZE=1. That value flows straight into benchmarks/single_node/agentic/glm5.2_fp4_b300_sglang.sh, which builds PARALLEL_ARGS=(--tp "$TP" --ep-size "$EP_SIZE") unconditionally (no override or reset for the dp-attn branch — it only adds --dp "$TP" --enable-dp-attention ... on top). So the actual server launch for the documented '34,429 tok/s' high-throughput arm is --tp 8 --ep-size 1 --dp 8 --enable-dp-attention.

Why this is wrong: GLM-5.2 is an MoE model (glm_moe_dsa architecture). The whole point of pairing attention-DP with expert parallelism is to shard the MoE experts across the 8 DP ranks instead of replicating the full expert set on every GPU. With ep-size=1, SGLang runs with no expert parallelism — each of the 8 DP ranks holds/replicates the entire MoE expert set, which is a materially different (and much more memory- and compute-heavy) execution than the intended sharded layout.

Why nothing catches this today: There's no validation in generate_sweep_configs.py (or the launch script) requiring ep to be set — or to equal tp — whenever dp-attn: true is set for an MoE recipe. The default-to-1 behavior is silent, so a missing key produces a syntactically valid but semantically wrong config rather than an error.

Comparison to every other recipe in the file: Grepping configs/nvidia-master.yaml shows dozens of dp-attn: true entries (dsv4, minimaxm3, qwen3.5, etc.), and every single one pairs it with an explicit ep: equal to tp (e.g. { tp: 8, ep: 8, dp-attn: true }, { tp: 4, ep: 4, dp-attn: true }, { tp: 16, ep: 16, dp-attn: true } at lines 9892-9893 just above this diff). The PR description itself says the router setup is 'same pattern as the DSv4 B300 agentic recipe' (dsv4-fp4-b300-sglang-agentic-hicache), whose equivalent high-throughput arm is { tp: 8, ep: 8, dp-attn: true, ... }. This confirms the missing ep: 8 here is a copy-paste omission, not an intentional deviation.

Step-by-step proof:

  1. Config entry: { tp: 8, dp-attn: true, kv-offloading: none, conc-list: [...], router: {...} } — no ep key.
  2. generate_sweep_configs.py reads ep = bmk.get(Fields.EP.value)None (key absent).
  3. It writes Fields.EP.value: ep if ep is not None else 1EP_SIZE=1 in the generated matrix entry/env.
  4. glm5.2_fp4_b300_sglang.sh builds PARALLEL_ARGS=(--tp "$TP" --ep-size "$EP_SIZE") = --tp 8 --ep-size 1, then appends --dp "$TP" --enable-dp-attention ... since DP_ATTENTION=true.
  5. Resulting server launch: --tp 8 --ep-size 1 --dp 8 --enable-dp-attention — attention-DP is on, but expert parallelism is off.
  6. This is not the config that was on-node validated to get 34,429 tok/s @ conc 256 if that run used --ep-size 8 (matching every analogous recipe); the committed sweep will benchmark a different, degraded parallelism layout than what's documented.

Fix: add ep: 8 to the high-throughput arm, matching tp and the DSv4 reference recipe:

- { tp: 8, ep: 8, dp-attn: true, kv-offloading: none, conc-list: [48, 64, 96, 128, 192, 256, 512], router: { name: sglang-router, version: "0.3.2" } }

10 changes: 10 additions & 0 deletions perf-changelog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4934,6 +4934,16 @@
- "Add MiniMax M3 NVFP4 B300 Dynamo-vLLM disaggregated EAGLE3 recipes"
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2182

- config-keys:
- glm5.2-fp4-b300-sglang-agentic
description:
- "Add GLM-5.2 NVFP4 B300 SGLang single-node agentic benchmarks from the SGLang cookbook B300 NVFP4 recipes (STP only, no spec decoding)"
- "Image: lmsysorg/sglang:v0.5.15.post1-cu130; model nvidia/GLM-5.2-NVFP4"
- "Low-latency arm: TP8 (fp8 KV cache, cutedsl bf16 GEMM backend) at conc [1, 2, 4, 8, 16, 32]"
- "High-throughput arm: TP8/DP8 attention-DP behind sglang-router consistent hashing at conc [48, 64, 96, 128, 192, 256, 512]"
- "New 1M-context model prefix glm5.2 added to the unfiltered agentic corpus branch in benchmark_lib.sh"
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2268

- config-keys:
- kimik2.5-int4-b200-vllm
- kimik2.5-int4-b300-vllm
Expand Down