Skip to content
Open
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
198 changes: 198 additions & 0 deletions benchmarks/single_node/agentic/qwen3.8next_fp4_b200_sglang_mtp.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
#!/usr/bin/env bash
set -euo pipefail
set -x

# AgentX trace replay for Qwen3.8-Flash-Next NVFP4 on B200 with SGLang
# native NEXTN MTP. Day-zero recipe; SGLang is the plan-of-record engine for
# this model (MODELS.md). Throughput uses the golden synthetic AL; evals retain
# real target-model verification.
#
# The checkpoint is RadixArk/Qwen3.8-Flash-Next-NVFP4 (126 GiB,
# quantization_config.quant_method = modelopt), so --quantization modelopt_fp4
# matches the same flag the Qwen3.5 NVFP4 sibling uses. The model ships native
# MTP modules (kept unquantized by the checkpoint's ignore list), so NEXTN
# needs no external drafter.
#
# TP1: the cookbook's verified single-node command for this model is --tp 1 on
# both Blackwell parts. 126 GiB of NVFP4 weights fit on one B200, so the
# model is not sharded and every rank-crossing collective disappears.

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

# Use the lightweight GSM8K eval instead of the AgentX SWE-bench default.
export EVAL_FRAMEWORK="lm-eval"

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
Comment thread
cursor[bot] marked this conversation as resolved.
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Tokenizer workers skipped at TP1

Medium Severity

Parallel tokenization is gated on TP >= 4, but this recipe only searches tp: 1, so --tokenizer-worker-num never applies. The nearby comment says 256k AgentX warmups miss the client timeout without it, and the same model's H100 arm always sets 6 tokenizer workers. Tokenization is CPU-side, so TP1 does not remove that cost.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7043888. Configure here.


# 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
# Keep server-side connections alive beyond AIPerf's 300-second client pool
# timeout so bursty AgentX trajectories cannot reuse a closing idle socket.
export SGLANG_TIMEOUT_KEEP_ALIVE=1800

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_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[@]}"
# Verified flags from the SGLang cookbook playground for this model on
# B200 / NVFP4 / single node. Quantization is read from the
# checkpoint, so no --quantization flag; the hybrid GDN linear-attention
# layers take their own backends rather than --attention-backend.
--linear-attn-prefill-backend flashinfer
--linear-attn-decode-backend flashinfer
# bfloat16 is mandatory on Blackwell: SGLang rejects the launch outright
# with "--linear-attn-decode-backend flashinfer on SM100+ requires
# --mamba-ssm-dtype bfloat16". Hopper wants the opposite -- flashinfer's
# gated_delta_rule_mtp verify kernel asserts a float32 state there -- so
# the H200 arm sets float32 and this one must not follow it.
--mamba-ssm-dtype bfloat16
--speculative-algorithm NEXTN
--speculative-num-steps 3
--speculative-eagle-topk 1
--speculative-num-draft-tokens 4
--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"
--mem-fraction-static 0.80
--stream-interval 50
--scheduler-recv-interval "$SCHEDULER_RECV_INTERVAL"
"${TOKENIZER_ARGS[@]}"
--tokenizer-path "$MODEL"
--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+=" --server-metrics http://localhost:$PORT/metrics"
run_agentic_replay_and_write_outputs "$RESULT_DIR"
fi
18 changes: 18 additions & 0 deletions configs/nvidia-master.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7392,6 +7392,24 @@ qwen3.5-fp4-b200-sglang-agentic-mtp:
- { 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] }


# Qwen3.8-Flash-Next NVFP4 AgentX on B200 via SGLang with native NEXTN MTP.
# Day-zero recipe: SGLang is the plan-of-record engine for this model. TP1 per
# the cookbook's verified single-node command; 126 GiB of NVFP4 weights fit on
# one B200, so the model is not sharded.
qwen3.8next-fp4-b200-sglang-agentic-mtp:
image: lmsysorg/sglang:qwen38flashnext
model: RadixArk/Qwen3.8-Flash-Next-NVFP4
model-prefix: qwen3.8next
runner: cluster:b200-nscale
Comment thread
cursor[bot] marked this conversation as resolved.
precision: fp4
framework: sglang
multinode: false
scenarios:
agentic-coding:
- dram-utilization: 0.8
search-space:
- { tp: 1, ep: 1, spec-decoding: mtp, kv-offloading: none, conc-list: [1, 4, 8, 12, 16] }
qwen3.5-fp4-gb200-dynamo-sglang-agentic-mtp:
image: lmsysorg/sglang:nightly-dev-20260818-c0b6474b
model: nvidia/Qwen3.5-397B-A17B-NVFP4-V2
Expand Down
14 changes: 14 additions & 0 deletions perf-changelog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6576,3 +6576,17 @@
- "Recipes sourced from srt-slurm (recipes/trtllm/qwen3.5-fp4/inferencex/gb300/{mtp,stp})."
- "Runner: launch_gb300-nv.sh bumped from NVIDIA/srt-slurm@v1.0.29 to v1.0.72 for the dynamo-trt+qwen3.5+fp4 path."
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2730

- config-keys:

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.

🔴 New changelog entry is appended directly after the prior entry's pr-link line (6492) with no blank-line separator, violating utils/prepare_perf_changelog_merge.py's validate_raw_change, which requires appended entries to follow the last historical byte with exactly \n- config-keys: (i.e. one blank line) when the base file doesn't already end in a blank line. Verified via byte inspection: base perf-changelog.yaml ends ...pull/2688\n and HEAD continues immediately with - config-keys: (no blank line).

Extended reasoning...

When merge automation runs canonicalize_appended_links()/resolve_conflict_bytes() (utils/prepare_perf_changelog_merge.py lines 90-97, 147-155) to canonicalize this entry's pr-link, validate_raw_change raises ChangelogValidationError('new changelog entries must be separated from history by one empty line and appended at the end') even after the separately-flagged pr-link placeholder is fixed, blocking merge until a blank line is inserted before line 6493.

Verification: normal (merge-prep automation raises an error the base branch does not). Byte inspection confirms base perf-changelog.yaml ends with a single newline (git show BASE:perf-changelog.yaml | od -> ...p u l l / 2 6 8 8 \n, one \n, no trailing blank line). HEAD appends the new block immediately: the diff shows pr-link: https://.../pull/2688 followed directly by +- config-keys: with no…

- qwen3.8next-fp4-b200-sglang-agentic-mtp
scenario-type:
- agentic-coding
description:
- "Add the day-zero Qwen3.8-Flash-Next NVFP4 AgentX recipe on B200 with SGLang native NEXTN MTP at TP4 and concurrency 1/4/8/12/16."
- "Serve RadixArk/Qwen3.8-Flash-Next-NVFP4 with modelopt_fp4 quantization, the trtllm_mha attention backend, and the flashinfer_trtllm MoE runner, following the Qwen3.5 NVFP4 B200 sibling."
- "Pin throughput runs to an interim acceptance length of 3.24 measured at three speculative tokens; eval-only runs keep real target verification."
- "Correct the serve flags to the SGLang cookbook's verified single-node command for this model: TP1 rather than TP4, the flashinfer linear-attention prefill and decode backends, bfloat16 Mamba SSM, and checkpoint-derived quantization."
- "Set the acceptance length to the committed golden thinking_on value of 2.32 at three speculative tokens, replacing the interim 3.24."
- "Keep the bfloat16 Mamba SSM state the cookbook specifies: SGLang requires it on SM100 or newer whenever the flashinfer linear-attention decode backend is selected."
- "Route the qwen3.8next fp4 prefix in the B200 nscale launcher to its staged checkpoint under the models tree; the launcher previously rejected the prefix outright."
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2751
19 changes: 19 additions & 0 deletions runners/launch_b200-nscale-compat.sh
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,25 @@ elif [[ $MODEL_PREFIX == "minimaxm3" && $PRECISION == "fp4" ]]; then
elif [[ $MODEL_PREFIX == "kimik3" && $PRECISION == "fp4" ]]; then
export MODEL_PATH="/scratch/models/Kimi-K3"
export SRT_SLURM_MODEL_PREFIX="kimik3"
elif [[ $MODEL_PREFIX == "qwen3.8next" && $PRECISION == "fp4" ]]; then
# Staged on the compute nodes like every other model here, so this is the
# ordinary branch again: no hf download and no writable target needed.
# Verified on im-b200-c004: 126 GB, index present, 206/206 shards, no
# .incomplete leftovers. The candidate search mirrors the dsv4 branch so a
# differently named staging directory needs no code change.
SELECTED_MODEL_PATH=""
if [[ -n "${MODEL_PATH:-}" && -d "${MODEL_PATH}" ]]; then
SELECTED_MODEL_PATH="$MODEL_PATH"
else
for candidate in /scratch/models/Qwen3.8-Flash-Next-NVFP4 /scratch/models/Qwen3.8-Flash-Next; do
if [[ -d "$candidate" ]]; then
SELECTED_MODEL_PATH="$candidate"
break
fi
done
fi
export MODEL_PATH="${SELECTED_MODEL_PATH:-/scratch/models/Qwen3.8-Flash-Next-NVFP4}"
export SRT_SLURM_MODEL_PREFIX="qwen3.8next-fp4"
else
echo "Unsupported model prefix/precision: $MODEL_PREFIX/$PRECISION"
echo "Available models under /scratch/models:"
Expand Down