|
| 1 | +#!/usr/bin/env bash |
| 2 | + |
| 3 | +# DeepSeek-R1 B300 vLLM SPEED-Bench AL matrix collector. |
| 4 | +# |
| 5 | +# Produces the golden acceptance-length (AL) reference matrix consumed by the |
| 6 | +# synthetic-acceptance framework: for each MTP level (num_speculative_tokens), |
| 7 | +# measure the REAL AL on a single SPEED-Bench category (default: coding) and emit |
| 8 | +# a YAML matrix identical in shape to benchmarks/speedbench-reference-al.yaml. |
| 9 | +# This measures real MTP acceptance; the synthetic value is injected downstream |
| 10 | +# by the throughput recipe, not here. |
| 11 | +# |
| 12 | +# Adapted from speedbench/dsv4_fp4_b300_vllm.sh. DeepSeek-R1 is DeepSeek-V3 |
| 13 | +# architecture (MLA dense attention), NOT V4 (DSA / Lightning Indexer), so vs the |
| 14 | +# DSV4 collector: |
| 15 | +# - NO --tokenizer-mode deepseek_v4 / --reasoning-parser deepseek_v4 / |
| 16 | +# --tool-call-parser deepseek_v4 (all V4-specific; the official vLLM R1 |
| 17 | +# serve command is bare). reasoning-parser is irrelevant here anyway: AL is |
| 18 | +# read from /metrics, not from parsed output. |
| 19 | +# - NO --attention_config.use_fp4_indexer_cache (that knob is dsv32/MLA-indexer |
| 20 | +# only; R1 is plain MLA and never reads it). |
| 21 | +# - NO --block-size / --compilation-config (the official R1 recipe omits them; |
| 22 | +# defaults apply). --kv-cache-dtype fp8 IS kept, to match the dsv4/qwen/glm |
| 23 | +# collectors so all golden AL values share one kv-cache numeric regime. |
| 24 | +# - FP4 on Blackwell needs FlashInfer MoE: export VLLM_USE_FLASHINFER_MOE_FP4=1. |
| 25 | +# - THINKING: R1 is a pure reasoning model and always emits <think> (its chat |
| 26 | +# template has no enable_thinking toggle). There is no thinking-off mode, so |
| 27 | +# this collector measures thinking_on only and needs no --chat-template-kwargs |
| 28 | +# shim (the default client-side template render already enables thinking). |
| 29 | +# |
| 30 | +# Checkpoint (B300 / Blackwell): NVFP4 build nvidia/DeepSeek-R1-0528-NVFP4-v2, |
| 31 | +# basename dsr1-fp4 on the runner (resolved by launch_b300-nv.sh). |
| 32 | +# |
| 33 | +# Usage (inside the vLLM container, on a B300 node): |
| 34 | +# export MODEL=/data/models/dsr1-fp4 |
| 35 | +# bash benchmarks/single_node/speedbench/dsr1_fp4_b300_vllm.sh |
| 36 | +# |
| 37 | +# Tunables (env): |
| 38 | +# MTP_LIST space-separated MTP levels (default "1 2 3 4 5 6 7 8") |
| 39 | +# THINKING_MODES space-separated: on (default "on"; R1 has no off) |
| 40 | +# CATEGORY SPEED-Bench category (default coding) |
| 41 | +# SPEEDBENCH_OUTPUT_LEN per-request output len (default 4096) |
| 42 | +# OUT_YAML output matrix path (default $RESULTS_DIR/speedbench-reference-al.yaml) |
| 43 | + |
| 44 | +set -uo pipefail |
| 45 | +source "$(dirname "$0")/../../benchmark_lib.sh" |
| 46 | + |
| 47 | +MODEL="${MODEL:?MODEL env var required (e.g. /data/models/dsr1-fp4)}" |
| 48 | +SERVE_MODEL="${MODEL_PATH:-$MODEL}" |
| 49 | +TP="${TP:-8}" |
| 50 | +DP_ATTENTION="${DP_ATTENTION:-false}" |
| 51 | +PORT="${PORT:-8888}" |
| 52 | + |
| 53 | +MTP_LIST="${MTP_LIST:-1 2 3 4 5 6 7 8}" |
| 54 | +THINKING_MODES="${THINKING_MODES:-on}" |
| 55 | +CATEGORY="${CATEGORY:-coding}" |
| 56 | +MODEL_KEY="${MODEL_KEY:-$(basename "$SERVE_MODEL" | tr '[:upper:]' '[:lower:]')}" |
| 57 | +SPEEDBENCH_OUTPUT_LEN="${SPEEDBENCH_OUTPUT_LEN:-4096}" |
| 58 | +CONCURRENCY="${CONCURRENCY:-1}" |
| 59 | +# Provider-recommended sampling from the DeepSeek-R1 checkpoint generation_config |
| 60 | +# (temperature 0.6, top_p 0.95; no top_k). vLLM's own default top_p is 1.0, so it |
| 61 | +# MUST be passed explicitly or the measured AL is taken at the wrong settings. |
| 62 | +TEMPERATURE="${TEMPERATURE:-0.6}" |
| 63 | +TOP_P="${TOP_P:-0.95}" |
| 64 | + |
| 65 | +SPEEDBENCH_DIR="${SPEEDBENCH_DIR:-/workspace/speed_bench_data}" |
| 66 | +# Flat results dir to match the speedbench-al.yml artifact glob |
| 67 | +# (speedbench_results/server_*.log) and its pre-run `rm -rf speedbench_results`. |
| 68 | +RESULTS_DIR="${RESULTS_DIR:-/workspace/speedbench_results}" |
| 69 | +OUT_YAML="${OUT_YAML:-$RESULTS_DIR/speedbench-reference-al.yaml}" |
| 70 | + |
| 71 | +# Blackwell FP4 MoE path (DeepSeek-R1 FP4 on B-series): required per vLLM R1 docs. |
| 72 | +export VLLM_USE_FLASHINFER_MOE_FP4="${VLLM_USE_FLASHINFER_MOE_FP4:-1}" |
| 73 | +export VLLM_ENGINE_READY_TIMEOUT_S=3600 |
| 74 | + |
| 75 | +mkdir -p "$RESULTS_DIR" |
| 76 | +nvidia-smi |
| 77 | +if [[ "$SERVE_MODEL" != /* ]]; then hf download "$SERVE_MODEL"; fi |
| 78 | + |
| 79 | +# ---- Download SPEED-Bench dataset ---- |
| 80 | +echo "=== Downloading SPEED-Bench dataset ===" |
| 81 | +pip install -q datasets tiktoken |
| 82 | +curl -LsSf https://raw.githubusercontent.com/NVIDIA-NeMo/Skills/refs/heads/main/nemo_skills/dataset/speed-bench/prepare.py \ |
| 83 | + | python3 - --config qualitative --output_dir "$SPEEDBENCH_DIR" |
| 84 | + |
| 85 | +if [[ ! -f "$SPEEDBENCH_DIR/qualitative.jsonl" ]]; then |
| 86 | + echo "CRITICAL: SPEED-Bench download failed — $SPEEDBENCH_DIR/qualitative.jsonl not found" |
| 87 | + exit 1 |
| 88 | +fi |
| 89 | + |
| 90 | +PARALLEL_ARGS=(--tensor-parallel-size "$TP" --data-parallel-size 1) |
| 91 | +if [ "${DP_ATTENTION}" = "true" ]; then |
| 92 | + PARALLEL_ARGS=(--tensor-parallel-size 1 --data-parallel-size "$TP") |
| 93 | +fi |
| 94 | + |
| 95 | +fetch_metric() { |
| 96 | + local port="$1" name="$2" |
| 97 | + curl -s "http://localhost:${port}/metrics" \ |
| 98 | + | grep -oP "${name}\\{[^}]*\\} \\K[0-9.]+" || echo "0" |
| 99 | +} |
| 100 | + |
| 101 | +SERVER_PID="" |
| 102 | +_descendants() { |
| 103 | + local pid="$1" child |
| 104 | + for child in $(pgrep -P "$pid" 2>/dev/null || true); do |
| 105 | + echo "$child" |
| 106 | + _descendants "$child" |
| 107 | + done |
| 108 | +} |
| 109 | +cleanup_server() { |
| 110 | + if [[ -n "$SERVER_PID" ]]; then |
| 111 | + local descendants |
| 112 | + descendants=$(_descendants "$SERVER_PID") |
| 113 | + kill "$SERVER_PID" 2>/dev/null || true |
| 114 | + wait "$SERVER_PID" 2>/dev/null || true |
| 115 | + local pid |
| 116 | + for pid in $descendants; do |
| 117 | + kill -9 "$pid" 2>/dev/null || true |
| 118 | + done |
| 119 | + local waited=0 |
| 120 | + while [[ $waited -lt 120 ]]; do |
| 121 | + local used |
| 122 | + used=$(nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits 2>/dev/null | sort -rn | head -1) |
| 123 | + if [[ -z "$used" || "$used" -lt 2000 ]]; then break; fi |
| 124 | + sleep 3; waited=$((waited + 3)) |
| 125 | + done |
| 126 | + SERVER_PID="" |
| 127 | + fi |
| 128 | +} |
| 129 | +trap 'cleanup_server' EXIT |
| 130 | + |
| 131 | +start_gpu_monitor |
| 132 | + |
| 133 | +declare -A AL_RESULT |
| 134 | + |
| 135 | +run_cell() { |
| 136 | + local mode="$1" mtp="$2" |
| 137 | + |
| 138 | + echo "" |
| 139 | + echo "==========================================" |
| 140 | + echo " Cell: thinking=$mode MTP=$mtp category=$CATEGORY" |
| 141 | + echo "==========================================" |
| 142 | + |
| 143 | + local serve_args=( |
| 144 | + --host 0.0.0.0 --port "$PORT" |
| 145 | + "${PARALLEL_ARGS[@]}" |
| 146 | + --pipeline-parallel-size 1 |
| 147 | + --trust-remote-code |
| 148 | + --enable-expert-parallel |
| 149 | + --kv-cache-dtype fp8 |
| 150 | + --no-enable-prefix-caching |
| 151 | + --max-model-len 16384 |
| 152 | + --speculative-config "{\"method\": \"mtp\", \"num_speculative_tokens\": $mtp}" |
| 153 | + ) |
| 154 | + |
| 155 | + local server_log="$RESULTS_DIR/server_${mode}_mtp${mtp}.log" |
| 156 | + vllm serve "$SERVE_MODEL" "${serve_args[@]}" > "$server_log" 2>&1 & |
| 157 | + SERVER_PID=$! |
| 158 | + |
| 159 | + if ! wait_for_server_ready --port "$PORT" --server-log "$server_log" --server-pid "$SERVER_PID"; then |
| 160 | + echo " -> server failed to start (thinking=$mode mtp=$mtp), recording N/A" |
| 161 | + AL_RESULT["${mode}_${mtp}"]="N/A" |
| 162 | + cleanup_server |
| 163 | + return |
| 164 | + fi |
| 165 | + |
| 166 | + local acc_before drf_before acc_after drf_after |
| 167 | + acc_before=$(fetch_metric "$PORT" "vllm:spec_decode_num_accepted_tokens_total") |
| 168 | + drf_before=$(fetch_metric "$PORT" "vllm:spec_decode_num_drafts_total") |
| 169 | + |
| 170 | + vllm bench serve \ |
| 171 | + --model "$SERVE_MODEL" \ |
| 172 | + --port "$PORT" \ |
| 173 | + --dataset-name speed_bench \ |
| 174 | + --dataset-path "$SPEEDBENCH_DIR" \ |
| 175 | + --speed-bench-category "$CATEGORY" \ |
| 176 | + --speed-bench-output-len "$SPEEDBENCH_OUTPUT_LEN" \ |
| 177 | + --num-prompts -1 \ |
| 178 | + --max-concurrency "$CONCURRENCY" \ |
| 179 | + --save-result \ |
| 180 | + --save-detailed \ |
| 181 | + --result-dir "$RESULTS_DIR" \ |
| 182 | + --result-filename "speedbench_${mode}_mtp${mtp}" \ |
| 183 | + --trust-remote-code \ |
| 184 | + --temperature "$TEMPERATURE" \ |
| 185 | + --top-p "$TOP_P" |
| 186 | + |
| 187 | + acc_after=$(fetch_metric "$PORT" "vllm:spec_decode_num_accepted_tokens_total") |
| 188 | + drf_after=$(fetch_metric "$PORT" "vllm:spec_decode_num_drafts_total") |
| 189 | + |
| 190 | + local delta_acc delta_drf al |
| 191 | + delta_acc=$(awk "BEGIN {printf \"%d\", $acc_after - $acc_before}") |
| 192 | + delta_drf=$(awk "BEGIN {printf \"%d\", $drf_after - $drf_before}") |
| 193 | + if [[ "$delta_drf" -gt 0 ]]; then |
| 194 | + al=$(awk "BEGIN {printf \"%.2f\", 1 + ($delta_acc / $delta_drf)}") |
| 195 | + else |
| 196 | + al="N/A" |
| 197 | + fi |
| 198 | + echo " -> thinking=$mode MTP=$mtp AL=$al (accepted=$delta_acc drafts=$delta_drf)" |
| 199 | + AL_RESULT["${mode}_${mtp}"]="$al" |
| 200 | + |
| 201 | + cleanup_server |
| 202 | +} |
| 203 | + |
| 204 | +for mode in $THINKING_MODES; do |
| 205 | + for mtp in $MTP_LIST; do |
| 206 | + run_cell "$mode" "$mtp" |
| 207 | + done |
| 208 | +done |
| 209 | + |
| 210 | +stop_gpu_monitor |
| 211 | + |
| 212 | +# ---- Emit the YAML matrix ---- |
| 213 | +emit_mode_block() { |
| 214 | + local mode="$1" |
| 215 | + for mtp in $MTP_LIST; do |
| 216 | + echo " $mtp: ${AL_RESULT[${mode}_${mtp}]:-N/A}" |
| 217 | + done |
| 218 | +} |
| 219 | + |
| 220 | +{ |
| 221 | + echo "# Acceptance Length (AL) reference values measured with SPEED-Bench." |
| 222 | + echo "# dataset: $CATEGORY | temperature: $TEMPERATURE | top_p: $TOP_P | output_len: $SPEEDBENCH_OUTPUT_LEN" |
| 223 | + echo "# DeepSeek-R1 always reasons (no thinking-off mode), so only thinking_on is emitted." |
| 224 | + echo "# Measured on $MODEL_KEY (B300, vLLM MTP), per num_speculative_tokens." |
| 225 | + echo "# Auto-generated by benchmarks/single_node/speedbench/dsr1_fp4_b300_vllm.sh (speedbench-al.yml)." |
| 226 | + echo "#" |
| 227 | + echo "# key = num_speculative_tokens (MTP level); value = golden AL" |
| 228 | + echo "${MODEL_KEY}:" |
| 229 | + if [[ " $THINKING_MODES " == *" on "* ]]; then |
| 230 | + echo " thinking_on:" |
| 231 | + emit_mode_block on |
| 232 | + fi |
| 233 | + if [[ " $THINKING_MODES " == *" off "* ]]; then |
| 234 | + echo " thinking_off:" |
| 235 | + emit_mode_block off |
| 236 | + fi |
| 237 | +} > "$OUT_YAML" |
| 238 | + |
| 239 | +echo "" |
| 240 | +echo "==========================================" |
| 241 | +echo " SPEED-Bench AL matrix written to: $OUT_YAML" |
| 242 | +echo "==========================================" |
| 243 | +cat "$OUT_YAML" |
0 commit comments