Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
52d7a56
feat: add Qwen3.5 FP4 B200 AgentX MTP
cquil11 Jul 30, 2026
bf03912
chore: trigger B200 AgentX sweep
cquil11 Jul 30, 2026
9c682e2
perf: expand B200 AgentX preflight range
cquil11 Jul 30, 2026
2d6c12b
chore: use SGLang v0.5.16 for Qwen B200
cquil11 Jul 30, 2026
b118256
chore: collect SGLang cache metrics on B200
cquil11 Jul 30, 2026
e2c4ab5
fix: remove unsupported SGLang tool-choice flag on B200
cquil11 Jul 30, 2026
4a587bd
perf: prefer local Qwen FP4 weights on B200
cquil11 Jul 30, 2026
1b863c5
perf: prepare Qwen AgentX cache and DEP tuning
cquil11 Jul 30, 2026
1e89789
perf: add Qwen B200 DEP probes
cquil11 Jul 30, 2026
f7e3b37
perf: add matched B200 HiCache cliff probe
cquil11 Jul 30, 2026
191ebbe
perf: isolate B200 HiCache validation point
cquil11 Jul 30, 2026
b439bf7
perf: extend Qwen B200 TP4 cliff probe
cquil11 Jul 30, 2026
df5a388
perf: drop dominated Qwen B200 DEP arm
cquil11 Jul 30, 2026
ce05286
fix: enforce Qwen HiCache DRAM total
cquil11 Jul 30, 2026
718ff2b
perf: add B200 TP4 c72 HiCache A/B
cquil11 Jul 30, 2026
e7af32b
chore: merge current main into B200 submission
cquil11 Jul 30, 2026
efbe79d
refactor: remove dominated B200 DEP path
cquil11 Jul 30, 2026
0eddcce
perf: densify B200 TP2 cache cliff
cquil11 Jul 30, 2026
123f0f8
perf: resolve B200 Qwen cache cliff
cquil11 Jul 30, 2026
b2477e4
perf: prune dominated B200 TP2 point
cquil11 Jul 30, 2026
3ecfbc8
perf: stop B200 TP4 before cache thrashing
cquil11 Jul 30, 2026
e518bc0
perf: switch B200 TP2 to HiCache after c14
cquil11 Jul 30, 2026
a7b78e1
perf: prune dominated B200 TP4 c66
cquil11 Jul 30, 2026
28ac3e6
fix(agentx): use stable tokenizer startup
cquil11 Jul 30, 2026
801f922
perf(agentx): cap B200 no-offload frontier
cquil11 Jul 30, 2026
8d05af8
fix(agentx): scale Qwen tokenization by topology
cquil11 Jul 30, 2026
3ed39d3
perf(agentx): trim B200 cache-thrash tail
cquil11 Jul 30, 2026
3a1b5f7
fix(agentx): cap Qwen trace idle gaps
cquil11 Jul 30, 2026
e1ab4ff
chore(aiperf): pin merged trace idle cap branch
cquil11 Jul 30, 2026
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
173 changes: 173 additions & 0 deletions benchmarks/single_node/agentic/qwen3.5_fp4_b200_sglang_mtp.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
#!/usr/bin/env bash
set -euo pipefail
set -x

# AgentX trace replay for Qwen3.5-397B-A17B NVFP4 on B200 with SGLang
# native NEXTN MTP. Throughput uses the committed golden synthetic AL; evals
# retain real target-model verification.

source "$(dirname "$0")/../../benchmark_lib.sh"

check_env_vars \
MODEL TP CONC EP_SIZE KV_OFFLOADING \
TOTAL_CPU_DRAM_GB RESULT_DIR DURATION

SCHEDULER_RECV_INTERVAL=${SCHEDULER_RECV_INTERVAL:-10}

if [[ -n "${SLURM_JOB_ID:-}" ]]; then
echo "JOB $SLURM_JOB_ID running on ${SLURMD_NODENAME:-unknown}"
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
nvidia-smi

export WEKA_LOADER_OVERRIDE=semianalysis_cc_traces_weka_062126_256k
resolve_trace_source
install_agentic_deps

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

CACHE_ARGS=()
if require_agentic_kv_offload_backend hicache; then
REQUESTED_HICACHE_TOTAL_GB="${HICACHE_TOTAL_CPU_DRAM_GB:-$TOTAL_CPU_DRAM_GB}"
if [ "$REQUESTED_HICACHE_TOTAL_GB" -gt "$TOTAL_CPU_DRAM_GB" ]; then
echo "Error: requested HiCache pool ${REQUESTED_HICACHE_TOTAL_GB} GB exceeds configured capacity ${TOTAL_CPU_DRAM_GB} GB" >&2
exit 1
fi
TOTAL_CPU_DRAM_GB="$REQUESTED_HICACHE_TOTAL_GB"
# SGLang applies --hicache-size independently to Qwen's target KV and
# Mamba pools. Native NEXTN also creates a draft KV pool with the same
# slot count; its one attention layer adds 1/15 of the target KV bytes.
# Reserve 1 GB/rank for page alignment and enforce H * 31/15 per rank.
HICACHE_ALIGNMENT_RESERVE_GB=$TP
HICACHE_USABLE_TOTAL_GB=$((TOTAL_CPU_DRAM_GB - HICACHE_ALIGNMENT_RESERVE_GB))
if [ "$HICACHE_USABLE_TOTAL_GB" -lt 1 ]; then
echo "Error: insufficient DRAM after HiCache alignment reserve" >&2
exit 1
fi
MAX_HICACHE_SIZE_GB=$((HICACHE_USABLE_TOTAL_GB * 15 / TP / 31))
HICACHE_SIZE_GB="${HICACHE_SIZE_GB:-$MAX_HICACHE_SIZE_GB}"
if [ "$HICACHE_SIZE_GB" -lt 1 ] || [ "$HICACHE_SIZE_GB" -gt "$MAX_HICACHE_SIZE_GB" ]; then
echo "Error: HICACHE_SIZE_GB=$HICACHE_SIZE_GB outside 1..$MAX_HICACHE_SIZE_GB" >&2
exit 1
fi
PROJECTED_HICACHE_TOTAL_GB=$(((HICACHE_SIZE_GB * TP * 31 + 14) / 15 + HICACHE_ALIGNMENT_RESERVE_GB))
if [ "$PROJECTED_HICACHE_TOTAL_GB" -gt "$TOTAL_CPU_DRAM_GB" ]; then
echo "Error: projected HiCache use ${PROJECTED_HICACHE_TOTAL_GB} GB exceeds configured capacity ${TOTAL_CPU_DRAM_GB} GB" >&2
exit 1
fi
echo "HiCache CPU pools: ${HICACHE_SIZE_GB} GB target + Mamba + 1/15 draft per rank across TP=${TP}; projected node total ${PROJECTED_HICACHE_TOTAL_GB} GB <= ${TOTAL_CPU_DRAM_GB} GB"
CACHE_ARGS=(
--page-size 64
--enable-hierarchical-cache
--hicache-size "$HICACHE_SIZE_GB"
--hicache-io-backend kernel
--hicache-mem-layout page_first
--hicache-write-policy write_through_selective
)
fi

PARALLEL_ARGS=(
--tp "$TP"
--dp 1
--ep-size "$EP_SIZE"
)

# TP4 needs parallel tokenization to keep 256k AgentX warmups below the client
# request timeout. Keep TP2 on SGLang's single-worker default: multi-tokenizer
# startup races with the TP2 HiCache shared-memory initialization path.
TOKENIZER_ARGS=()
if [ "$TP" -ge 4 ]; then
TOKENIZER_ARGS=(--tokenizer-worker-num 6)
fi

# AgentX concurrency counts live session trees rather than individual HTTP
# requests. Leave room for subagent fan-out and avoid spending HBM on graphs
# above the batch sizes that remain useful for this long-context workload.
MAX_RUNNING_REQUESTS=$((2 * CONC))
CUDA_GRAPH_MAX_BS="$CONC"
[ "$CUDA_GRAPH_MAX_BS" -gt 64 ] && CUDA_GRAPH_MAX_BS=64

export TORCH_CUDA_ARCH_LIST="10.0"
export PYTHONNOUSERSITE=1
export NCCL_NVLS_ENABLE=1
export SGL_ENABLE_JIT_DEEPGEMM=false
export SGLANG_ENABLE_FLASHINFER_GEMM=true

if [ "${EVAL_ONLY:-false}" != "true" ]; then
export SGLANG_SIMULATE_ACC_LEN=3.39
export SGLANG_SIMULATE_ACC_METHOD=match-expected
export SGLANG_SIMULATE_ACC_TOKEN_MODE=real-draft-token
fi

SGLANG_CMD=(
python3 -m sglang.launch_server
--model-path "$MODEL_PATH"
--served-model-name "$MODEL"
--host 0.0.0.0
--port "$PORT"
--trust-remote-code
"${PARALLEL_ARGS[@]}"
--enable-symm-mem
--quantization modelopt_fp4
--fp4-gemm-backend flashinfer_cutlass
--kv-cache-dtype fp8_e4m3
--mamba-ssm-dtype bfloat16
--attention-backend trtllm_mha
--moe-runner-backend flashinfer_trtllm
--cuda-graph-max-bs "$CUDA_GRAPH_MAX_BS"
--max-running-requests "$MAX_RUNNING_REQUESTS"
--max-prefill-tokens 16384
--chunked-prefill-size 16384
--mem-fraction-static 0.80
--stream-interval 50
--scheduler-recv-interval "$SCHEDULER_RECV_INTERVAL"
"${TOKENIZER_ARGS[@]}"
--tokenizer-path "$MODEL"
--reasoning-parser qwen3
--tool-call-parser qwen3_coder
--speculative-algorithm NEXTN
--speculative-num-steps 3
--speculative-eagle-topk 1
--speculative-num-draft-tokens 4
--enable-metrics
--enable-cache-report
"${CACHE_ARGS[@]}"
)

printf '%q ' "${SGLANG_CMD[@]}" | tee "$RESULT_DIR/sglang_command.txt"
printf '\n' | tee -a "$RESULT_DIR/sglang_command.txt"
"${SGLANG_CMD[@]}" > "$SERVER_LOG" 2>&1 &
SERVER_PID=$!

capture_cache_metrics() {
{
echo "=== SGLang cache metrics snapshot $(date --iso-8601=seconds) ==="
curl -fsS "http://localhost:$PORT/metrics" 2>/dev/null \
| grep -E '^(sglang:(cache_hit_rate|cached_tokens_total|prompt_tokens_total|hicache_host_used_tokens|hicache_host_total_tokens|token_usage|num_requests_running|num_requests_waiting))' \
|| true
echo "============================================================"
} >> "$SERVER_LOG"
}

wait_for_server_ready --port "$PORT" --server-log "$SERVER_LOG" --server-pid "$SERVER_PID"

capture_cache_metrics
trap capture_cache_metrics EXIT

if [ "${EVAL_ONLY:-false}" = "true" ]; then
run_eval --port "$PORT"
else
build_replay_cmd "$RESULT_DIR"
REPLAY_CMD+=" --trace-idle-gap-cap-seconds 300"
REPLAY_CMD+=" --server-metrics http://localhost:$PORT/metrics"
run_agentic_replay_and_write_outputs "$RESULT_DIR"
fi
31 changes: 31 additions & 0 deletions configs/nvidia-master.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8478,6 +8478,37 @@ minimaxm3-fp8-h200-vllm-agentic:
- { tp: 8, ep: 8, kv-offloading: none, conc-list: [2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 18, 20] }
- { tp: 8, ep: 8, kv-offloading: dram, kv-offload-backend: { name: mooncake, version: "0.3.11.post1" }, conc-list: [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 18, 20] }

# Qwen3.5 has 15 full-attention layers with two 256-wide KV heads, so its
# target FP8 KV is 15,360 bytes/token before the native MTP layer. TP4
# replicates the two KV heads and measured 30.5 KB/token node-wide; TP2
# measured 15.3 KB/token. Against the 256k trace's 88.8k median and 148.8k
# p75 ISL, TP2 no-offload is already GPU-KV-bound by c18; measured c20
# HiCache improves both its throughput and latency, so offload takes over
# after c14 rather than repeating dominated no-offload points. TP2 c40 is
# capped to 25 running requests by the available HBM and matches c32 throughput
# with 15-second median TTFT, so the HiCache arm ends at c32.
# TP4 retains 83.9% prefix hits at c64. No-offload c66 and c68 are already
# throughput/latency dominated, while c70 collapses to 6% hits and 96% KV
# usage, so HiCache takes over at c64 and densely covers the boundary through
# c76. At c80 GPU KV reaches 100% with 19-second median TTFT, and c88 falls to
# 10% prefix hits, so the dominated upper tail is excluded.
qwen3.5-fp4-b200-sglang-agentic-mtp:
image: lmsysorg/sglang:v0.5.16-cu130
model: nvidia/Qwen3.5-397B-A17B-NVFP4
model-prefix: qwen3.5
runner: cluster:b200-dgxc
precision: fp4
framework: sglang
multinode: false
scenarios:
agentic-coding:
- dram-utilization: 0.80
search-space:
- { tp: 4, ep: 1, spec-decoding: mtp, kv-offloading: none, conc-list: [1, 4, 8, 12, 16, 20, 24, 28, 32, 40, 48, 56, 60, 62, 64] }
- { tp: 4, ep: 1, spec-decoding: mtp, kv-offloading: dram, kv-offload-backend: { name: hicache }, conc-list: [64, 66, 68, 70, 72, 76] }
- { tp: 2, ep: 1, spec-decoding: mtp, kv-offloading: none, conc-list: [1, 4, 8, 12, 14] }
- { tp: 2, ep: 1, spec-decoding: mtp, kv-offloading: dram, kv-offload-backend: { name: hicache }, conc-list: [16, 18, 20, 22, 24, 28, 32] }

dsv4-fp4-b200-sglang-agentic-hicache:
image: lmsysorg/sglang:nightly-dev-cu13-20260707-b4155233
model: deepseek-ai/DeepSeek-V4-Pro
Expand Down
9 changes: 9 additions & 0 deletions perf-changelog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5339,3 +5339,12 @@
- "Pass --use-chat-template to the benchmark, as required for EAGLE-style speculative decoding"
- "Validated on the node through the real launcher, every request successful: TEP4 concurrency 64 640/640 at 6895 tok/s (11% above the non-MTP arm's 6198 tok/s, mean TTFT 27.2s to 9.6s), and TEP4 concurrency 1 10/10 at 1233 tok/s (83% above the non-MTP arm's 672 tok/s, mean TPOT 12.6ms to 6.5ms)"
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2312

- config-keys:
- qwen3.5-fp4-b200-sglang-agentic-mtp
description:
- "Add Qwen3.5-397B-A17B NVFP4 AgentX benchmark on B200 with SGLang native NEXTN MTP"
- "Use the 256k trace dataset and golden synthetic acceptance length 3.39"
- "Pin AIPerf trace idle-gap support and cap per-trace idle gaps at 300 seconds"
- "Image: lmsysorg/sglang:v0.5.16-cu130"
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2420
43 changes: 37 additions & 6 deletions runners/launch_b200-dgxc.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ set -x
# portability, but we resolve to /lustre/fsw/models/* here to avoid repeated
# downloading on every dgxc node. Runs for both single-node and multinode
# launches.
# NOTE: per-node /raid/models/* would be faster but is only populated on a
# subset of dgxc nodes today, so we use Lustre for reliability.
# Single-node Qwen3.5 FP4 runs probe the allocated node for a complete local
# /raid checkpoint below and use this shared path only as the fallback.
if [[ $MODEL_PREFIX == "dsr1" && $PRECISION == "fp4" ]]; then
export MODEL_PATH="/scratch/fsw/models/DeepSeek-R1-0528-NVFP4-v2"
export SRT_SLURM_MODEL_PREFIX="dsr1"
Expand Down Expand Up @@ -437,10 +437,6 @@ EOF
else

SQUASH_FILE="/home/sa-shared/containers/$(echo "$IMAGE" | sed 's/[\/:@#]/_/g').sqsh"
# Point the bench script at the local MODEL_PATH resolved above instead of
# pulling from the HF hub cache. Bench scripts skip `hf download` when
# MODEL is a local path.
export MODEL="$MODEL_PATH"
FRAMEWORK_SUFFIX=$([[ "$FRAMEWORK" == "trt" ]] && printf '_trt' || printf '')
SPEC_SUFFIX=$([[ "$SPEC_DECODING" == "mtp" ]] && printf '_mtp' || printf '')
# Prefer a framework-tagged script (e.g. dsv4_fp4_b200_vllm.sh) so models
Expand Down Expand Up @@ -474,6 +470,41 @@ else
salloc --partition=$SLURM_PARTITION --account=$SLURM_ACCOUNT --gres=gpu:$GPU_COUNT --exclusive --mem=0 --time="$SALLOC_TIME_LIMIT" --no-shell --job-name="$RUNNER_NAME"
JOB_ID=$(squeue --name="$RUNNER_NAME" -u "$USER" -h -o %A | head -n1)

# Qwen3.5 FP4 may be staged on local NVMe on a DGXC node.
# Resolve this only after Slurm assigns the node; checking on the login
# host would select the wrong filesystem. Validate every shard named by
# the safetensor index so a partial staging directory is never selected.
if [[ "$MODEL_PREFIX" == "qwen3.5" && "$PRECISION" == "fp4" ]]; then
LOCAL_MODEL_PATH="/raid/models/Qwen3.5-397B-A17B-NVFP4"
if srun --jobid="$JOB_ID" --nodes=1 --ntasks=1 \
python3 - "$LOCAL_MODEL_PATH" <<'PY'; then
import json
from pathlib import Path
import sys

model_path = Path(sys.argv[1])
index_path = model_path / "model.safetensors.index.json"
if not (model_path / "config.json").is_file() or not index_path.is_file():
raise SystemExit(1)

with index_path.open() as index_file:
shards = set(json.load(index_file).get("weight_map", {}).values())
if not shards or any(not (model_path / shard).is_file() for shard in shards):
raise SystemExit(1)
PY
MODEL_PATH="$LOCAL_MODEL_PATH"
export MODEL_PATH
echo "Using node-local NVMe checkpoint: $MODEL_PATH"
else
echo "Node-local Qwen3.5 FP4 checkpoint unavailable; using shared checkpoint: $MODEL_PATH"
fi
fi

# Point the bench script at the resolved local/shared MODEL_PATH instead of
# pulling from the HF hub cache. Bench scripts skip `hf download` when
# MODEL is a local path.
export MODEL="$MODEL_PATH"

# Use flock to serialize concurrent imports to the same squash file
# Override ENROOT_CACHE_PATH to avoid permission issues with system-wide cache on worker nodes
srun --jobid=$JOB_ID bash -c "
Expand Down
Loading