diff --git a/benchmarks/single_node/agentic/qwen3.8next_fp4_b200_sglang_mtp.sh b/benchmarks/single_node/agentic/qwen3.8next_fp4_b200_sglang_mtp.sh new file mode 100755 index 000000000..de36b5964 --- /dev/null +++ b/benchmarks/single_node/agentic/qwen3.8next_fp4_b200_sglang_mtp.sh @@ -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 +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 +# 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 diff --git a/configs/nvidia-master.yaml b/configs/nvidia-master.yaml index 438a3e9da..45dfde995 100644 --- a/configs/nvidia-master.yaml +++ b/configs/nvidia-master.yaml @@ -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 + 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 diff --git a/perf-changelog.yaml b/perf-changelog.yaml index ab92e7921..f82d613bf 100644 --- a/perf-changelog.yaml +++ b/perf-changelog.yaml @@ -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: + - 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 diff --git a/runners/launch_b200-nscale-compat.sh b/runners/launch_b200-nscale-compat.sh index 6621bc8a8..9dea33302 100644 --- a/runners/launch_b200-nscale-compat.sh +++ b/runners/launch_b200-nscale-compat.sh @@ -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:"