Skip to content
Merged
217 changes: 217 additions & 0 deletions benchmarks/single_node/agentic/qwen3.8next_fp8_h200_sglang_mtp.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
#!/usr/bin/env bash
set -euo pipefail
set -x

# Agentic trace replay benchmark for Qwen3.8-Flash-Next FP8 on H200 using
# SGLang with MTP speculative decoding. Day-zero recipe; SGLang is the
# plan-of-record engine for this model (MODELS.md), and it is spec-decode only,
# per the AgentX policy that new agentic arms ship with speculative decoding
# enabled rather than as an STP/MTP A/B.
#
# H200 is Hopper, so this arm is FP8 (Qwen/Qwen3.8-Flash-Next-FP8, 172.8 GiB at
# TP8) rather than the NVFP4 checkpoint the Blackwell arms use: NVFP4 needs
# SM100 tensor cores. Attention backend stays flashinfer (sm_90).
#
# Structure follows the proven H100 MTP AgentX replay path
# (HiCache host-DRAM offload, the multi_tokenizer cached_tokens_details patch,
# aiperf-driven trace replay). H200's 141 GB HBM3e is roomier than H100's 80 GB,
# so --mem-fraction-static is 0.8 rather than 0.75, matching
# fixed_seq_len/qwen3.5_fp8_h200_mtp.sh. Attention backend stays flashinfer
# (sm_90); the trtllm_mha path is Blackwell-only.
#
# Speculative decoding mirrors fixed_seq_len/qwen3.5_fp8_h100_mtp.sh:
# SGLANG_ENABLE_SPEC_V2=1 with --speculative-algorithm EAGLE, 3 steps, eagle-topk
# 1 and 4 draft tokens, i.e. 3 speculative tokens per verification step.
#
# Throughput runs pin acceptance to the committed golden AL through SGLang's
# simulated-acceptance path; the EVAL_ONLY accuracy run leaves it off and keeps
# real verification. See the SGLANG_SIMULATE_ACC_* block.
#
# Required env vars:
# MODEL, TP, CONC, KV_OFFLOADING, TOTAL_CPU_DRAM_GB, RESULT_DIR
#
# KV_OFFLOADING=dram requires KV_OFFLOAD_BACKEND=hicache.

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

check_env_vars MODEL TP CONC KV_OFFLOADING TOTAL_CPU_DRAM_GB RESULT_DIR DURATION EP_SIZE

SCHEDULER_RECV_INTERVAL=${SCHEDULER_RECV_INTERVAL:-10}

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 traces and install deps ----------------------------------------
# Keep the 256k-capped with-subagents corpus the H100 Qwen3.5 AgentX recipe
# uses (470 traces, max in+out <= 256k). The unfiltered corpus has requests up
# to ~1M proxy tokens that the server would reject; H200's extra HBM raises the
# context ceiling but not past 256k for this model at TP8.
export WEKA_LOADER_OVERRIDE=semianalysis_cc_traces_weka_with_subagents_256k

resolve_trace_source
install_agentic_deps

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

CACHE_ARGS=()
if require_agentic_kv_offload_backend hicache; then
# HiCache extends RadixAttention, so do not pass --disable-radix-cache.
# Hybrid GDN/Mamba allocates one KV and one Mamba host pool per rank.
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"
HICACHE_HOST_POOL_COUNT="${HICACHE_HOST_POOL_COUNT:-2}"
HICACHE_WRITE_POLICY="${HICACHE_WRITE_POLICY:-write_through_selective}"
MAX_HICACHE_SIZE_GB=$((TOTAL_CPU_DRAM_GB / TP / HICACHE_HOST_POOL_COUNT))
HICACHE_SIZE_GB="${HICACHE_SIZE_GB:-$MAX_HICACHE_SIZE_GB}"
if [ "$HICACHE_SIZE_GB" -gt "$MAX_HICACHE_SIZE_GB" ]; then
echo "Error: HICACHE_SIZE_GB=$HICACHE_SIZE_GB exceeds configured per-pool limit $MAX_HICACHE_SIZE_GB" >&2
exit 1
fi
if [ "$HICACHE_SIZE_GB" -lt 1 ]; then
echo "Error: computed HICACHE_SIZE_GB=$HICACHE_SIZE_GB from TOTAL_CPU_DRAM_GB=$TOTAL_CPU_DRAM_GB, TP=$TP, HICACHE_HOST_POOL_COUNT=$HICACHE_HOST_POOL_COUNT" >&2
exit 1
fi
echo "HiCache CPU pool: ${HICACHE_SIZE_GB} GB per rank per host pool across TP=${TP}, host_pool_count=${HICACHE_HOST_POOL_COUNT}"
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 "$HICACHE_WRITE_POLICY"
)
fi

echo "Starting SGLang server..."
export PYTHONNOUSERSITE=1
export SGLANG_ENABLE_SPEC_V2=1

# 3 speculative tokens per step (num-steps 3, eagle-topk 1, 4 draft tokens),
# the same MTP shape as the fixed-seq-len Qwen3.5 recipes.
SPEC_ARGS=(
--speculative-algorithm NEXTN
--speculative-num-steps 3
--speculative-eagle-topk 1
--speculative-num-draft-tokens 4
)

# AgentX pins acceptance to the committed golden AL so submissions are compared
# on system performance at a fixed acceptance target rather than on draft-head
# quality (golden_al_distribution/README.md). 3.39 is the Qwen3.5 MTP curve at
# num_speculative_tokens=3, thinking_on (golden_al_distribution/qwen3.5_mtp.yaml)
# -- the same value the GB300 Qwen3.5 AgentX srt-slurm recipes pin.
# SGLANG_SIMULATE_ACC_TOKEN_MODE landed in SGLang v0.5.16, which is why this
# recipe pins that image rather than the non-MTP agentic sibling's v0.5.12.
#
Comment on lines +118 to +125

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.

🟡 Stale copy-pasted comment block still describes the Qwen3.5 golden AL rationale (3.39, golden_al_distribution/qwen3.5_mtp.yaml, GB300 srt-slurm pin, v0.5.16 vs v0.5.12 image reasoning) even though the code three lines below sets SGLANG_SIMULATE_ACC_LEN=3.24 for Qwen3.8next with its own correct rationale comment.

Extended reasoning...

A reviewer or future maintainer reading lines 118-125 sees a rationale for '3.39' tied to Qwen3.5's golden curve and an SGLang version-pin justification that does not apply to this recipe (image is lmsysorg/sglang:qwen38flashnext, not a version tag), creating confusion about why the actual exported value (3.24) and its interim/unverified status differ from the documented golden-AL claim, risking someone 'fixing' the value back to 3.39 or trusting the wrong image-pin justification.

Verification: nit — comment-only inconsistency, no runtime effect. Lines 118-124 are a stale copy-pasted comment block: "3.39 is the Qwen3.5 MTP curve at num_speculative_tokens=3, thinking_on (golden_al_distribution/qwen3.5_mtp.yaml) -- the same value the GB300 Qwen3.5 AgentX srt-slurm recipes pin. SGLANG_SIMULATE_ACC_TOKEN_MODE landed in SGLang v0.5.16, which is why this recipe pins that image rather than…

# EVAL_ONLY leaves simulated acceptance off: it commits drafted tokens
# regardless of the target logits, so generated text is wrong and the eval would
# score ~0.
if [ "${EVAL_ONLY:-false}" != "true" ]; then
# golden_al_distribution/qwen3.8next_mtp.yaml:
# qwen3.8-flash-next-fp8.thinking_on[3] = 2.32.
# --speculative-num-steps 3 with 4 draft tokens is 3 speculative tokens
# per verification step, i.e. the MTP=3 cell. AgentX replays run with
# thinking on, so the thinking_on row is the right one.
export SGLANG_SIMULATE_ACC_LEN=2.32
export SGLANG_SIMULATE_ACC_METHOD=match-expected
export SGLANG_SIMULATE_ACC_TOKEN_MODE=real-draft-token
fi

SGLANG_MULTI_TOKENIZER=/sgl-workspace/sglang/python/sglang/srt/managers/multi_tokenizer_mixin.py
if ! sed -n '/elif isinstance(output, BatchStrOutput):/,/input_token_logprobs_val=_extract_field_by_index/p' "$SGLANG_MULTI_TOKENIZER" \
| grep -q 'cached_tokens_details=_extract_field_by_index'; then
sed -i '/elif isinstance(output, BatchStrOutput):/,/input_token_logprobs_val=_extract_field_by_index/ {
/cached_tokens=_extract_field_by_index(output, "cached_tokens", i),/a\
cached_tokens_details=_extract_field_by_index(\
output, "cached_tokens_details", i\
),
}' "$SGLANG_MULTI_TOKENIZER"
fi

{ set +x; } 2>/dev/null
# AgentX concurrency counts live session trees rather than individual HTTP
# requests. Leave room for subagent fan-out, and do not spend HBM capturing
# graphs above the batch sizes that stay useful for this long-context workload.
# The Qwen3.5 H200 template left both flags commented out, so neither variable
# existed; NEXTN silently caps --max-running-requests at 48 when it is unset.
MAX_RUNNING_REQUESTS=$((2 * CONC))
CUDA_GRAPH_MAX_BS="$CONC"
if [ "$CUDA_GRAPH_MAX_BS" -gt 64 ]; then
CUDA_GRAPH_MAX_BS=64
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
# Verified flags from the SGLang cookbook playground for this model on
# H200 / FP8 / low latency / single node. NVFP4 is greyed out for Hopper,
# so FP8 is the whole verified surface here. TP4 with EP4, not TP8: the
# cookbook shards the 512-expert MoE across four ranks with expert
# parallelism rather than sharding attention eight ways.
--tp-size "$TP"
--ep-size "$EP_SIZE"
--dp-size 1
--mem-fraction-static 0.85
--chunked-prefill-size 8192
--linear-attn-prefill-backend flashinfer
--linear-attn-decode-backend flashinfer
# float32, not the cookbook's bfloat16. With NEXTN enabled the GDN linear
# attention backend routes verification through flashinfer's
# gated_delta_rule_mtp, which asserts initial_state.dtype == torch.float32
# and aborts CUDA graph capture on a bf16 SSM state:
# AssertionError: initial_state must be float32, got torch.bfloat16
# flashinfer/gdn_decode.py:761, via gdn_backend.py target_verify
# The cookbook command pairs bfloat16 with NEXTN, but this flashinfer build
# rejects that combination, and the state dtype is the half that can move.
--mamba-ssm-dtype float32
"${SPEC_ARGS[@]}"
--reasoning-parser auto
# NEXTN silently resets --max-running-requests to 48 when it is unset, so
# this must stay explicit and sized to the AgentX concurrency.
--max-running-requests "$MAX_RUNNING_REQUESTS"
--cuda-graph-max-bs "$CUDA_GRAPH_MAX_BS"
Comment thread
cursor[bot] marked this conversation as resolved.
--stream-interval 50
--scheduler-recv-interval "$SCHEDULER_RECV_INTERVAL"
--tokenizer-worker-num 6
--tokenizer-path "$MODEL"
--enable-metrics
"${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=$!
echo "Server PID: $SERVER_PID"

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

if [ "${EVAL_ONLY}" = "true" ]; then
run_eval --port "$PORT"
else
build_replay_cmd "$RESULT_DIR"
run_agentic_replay_and_write_outputs "$RESULT_DIR"
fi
19 changes: 19 additions & 0 deletions configs/nvidia-master.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7184,6 +7184,25 @@ qwen3.5-fp8-h200-sglang-agentic-mtp:
- { tp: 8, ep: 8, spec-decoding: mtp, kv-offloading: none, conc-list: [4, 8, 12, 16] }
- { tp: 8, ep: 8, spec-decoding: mtp, kv-offloading: dram, kv-offload-backend: { name: hicache }, conc-list: [4, 8, 12, 16] }


# Qwen3.8-Flash-Next FP8 AgentX on H200 via SGLang with native NEXTN MTP.
# Day-zero recipe. H200 is Hopper, so this arm is FP8: NVFP4 is greyed out for
# this part in the SGLang cookbook. TP4 with EP4 per the cookbook's verified
# low-latency single-node command, which shards the 512-expert MoE with expert
# parallelism rather than sharding attention eight ways.
qwen3.8next-fp8-h200-sglang-agentic-mtp:
image: lmsysorg/sglang:qwen38flashnext
model: Qwen/Qwen3.8-Flash-Next-FP8
model-prefix: qwen3.8next
runner: cluster:h200-dgxc
precision: fp8
framework: sglang
multinode: false
scenarios:
agentic-coding:
- dram-utilization: 0.8
search-space:
- { tp: 4, ep: 4, spec-decoding: mtp, kv-offloading: none, conc-list: [1, 4, 8, 12, 16] }
# H200 AgentX MTP frontier with DRAM HiCache. This is intentionally an MTP-only
# submission; the model's non-speculative AgentX arm is not included.
qwen3.5-fp8-h200-sglang-agentic-hicache-mtp:
Expand Down
12 changes: 12 additions & 0 deletions perf-changelog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6490,3 +6490,15 @@
- "Required lanes promote exporter startup timeouts, launch failures, and endpoint resolution failures to blocking validation failures, so a recipe cannot publish a result whose power collection never started."
- "Route only enabled recipes through the immutable producer fork and preserve non-power launcher revisions."
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2688

- config-keys:
- qwen3.8next-fp8-h200-sglang-agentic-mtp
scenario-type:
- agentic-coding
description:
- "Add the day-zero Qwen3.8-Flash-Next FP8 AgentX recipe on H200 with SGLang native NEXTN MTP at TP8 and concurrency 1/4/8/12/16."
- "Use the FP8 checkpoint rather than NVFP4 because Hopper has no SM100 tensor cores, and keep the flashinfer attention backend for sm_90."
- "Correct the serve flags to the SGLang cookbook's verified low-latency single-node command for this model: TP4 with EP4 rather than TP8, memory fraction 0.85, chunked prefill 8192, the flashinfer linear-attention prefill and decode backends, bfloat16 Mamba SSM, and the NEXTN speculative algorithm."
- "Pin throughput runs to the committed golden thinking_on acceptance length of 2.32 at three speculative tokens; eval-only runs keep real target verification."
- "Use a float32 Mamba SSM state: flashinfer's gated_delta_rule_mtp verify kernel asserts float32 and aborts CUDA graph capture on the bfloat16 state the cookbook command specifies."
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2753