|
| 1 | +#!/usr/bin/env bash |
| 2 | +set -eo pipefail |
| 3 | +set -x |
| 4 | + |
| 5 | +# Agentic trace replay benchmark for Kimi-K3 (MXFP4) on B300 using vLLM. |
| 6 | +# |
| 7 | +# Day-zero single-node recipe. Serve flags follow the Kimi-K3 production recipe |
| 8 | +# already exercised by the DSpark AL collector |
| 9 | +# (benchmarks/single_node/speedbench/kimik3_fp4_b300_vllm.sh) and the upstream |
| 10 | +# recipes.vllm.ai/moonshotai/Kimi-K3 guidance, adapted to the agentic scenario. |
| 11 | +# |
| 12 | +# Required env vars: |
| 13 | +# MODEL, TP, CONC, KV_OFFLOADING, TOTAL_CPU_DRAM_GB, RESULT_DIR, DURATION |
| 14 | +# |
| 15 | +# TP8 is the only single-node layout. The MXFP4 checkpoint is ~1.5 TB on disk; |
| 16 | +# at TP4 that is ~375 GB of weights per GPU against B300's 288 GB of HBM, so |
| 17 | +# only the full 8-GPU shard (~188 GB/GPU) fits. Do not add TP4/TP2 arms. |
| 18 | +# |
| 19 | +# Weights are pre-staged node-local: `Kimi-K3` is in the b300-nv launcher's |
| 20 | +# STAGED_MODELS, so MODEL_PATH resolves to the read-only /scratch/models/Kimi-K3 |
| 21 | +# mount and no download happens on the runner. The download block below only |
| 22 | +# covers stand-alone runs. |
| 23 | +# |
| 24 | +# KV_OFFLOADING: `none` (GPU-resident) or `dram` with |
| 25 | +# KV_OFFLOAD_BACKEND=vllm-simple (SimpleCPUOffloadConnector). |
| 26 | +# |
| 27 | +# Note on the DRAM arm: Kimi-K3 is a KDA/MLA hybrid — its linear-attention (KDA) |
| 28 | +# layers carry a recurrent state rather than paged KV blocks, and |
| 29 | +# SimpleCPUOffloadConnector offloads uniform paged blocks with no |
| 30 | +# hybrid-geometry handling. The arm is expected to offload only the MLA layers' |
| 31 | +# paged blocks; watch the bring-up sweep for KV-geometry errors at server init. |
| 32 | + |
| 33 | +source "$(dirname "$0")/../../benchmark_lib.sh" |
| 34 | + |
| 35 | +check_env_vars MODEL TP CONC KV_OFFLOADING TOTAL_CPU_DRAM_GB RESULT_DIR DURATION |
| 36 | + |
| 37 | +# The 2.8T MXFP4 checkpoint only fits across all 8 B300s (see header). |
| 38 | +if [ "$TP" -ne 8 ]; then |
| 39 | + echo "Error: Kimi-K3 on B300 requires TP=8 (a ~1.5 TB MXFP4 checkpoint does not fit at TP<8), got TP='$TP'" >&2 |
| 40 | + exit 1 |
| 41 | +fi |
| 42 | + |
| 43 | +if [[ -n "${EP_SIZE:-}" && "${EP_SIZE}" -gt 1 ]]; then |
| 44 | + echo "Error: this recipe ships the pure-TP8 profile; EP_SIZE='$EP_SIZE' is not wired yet" >&2 |
| 45 | + exit 1 |
| 46 | +fi |
| 47 | + |
| 48 | +if [[ -n "${SLURM_JOB_ID:-}" ]]; then |
| 49 | + echo "JOB $SLURM_JOB_ID running on ${SLURMD_NODENAME:-unknown}" |
| 50 | +fi |
| 51 | + |
| 52 | +# `hf download` creates the target dir if missing and is itself idempotent. |
| 53 | +# When MODEL_PATH is unset (stand-alone runs), fall back to the HF_HUB_CACHE. |
| 54 | +# Either way, MODEL_PATH is what the server is launched with. |
| 55 | +if [[ -n "${MODEL_PATH:-}" ]]; then |
| 56 | + if [[ ! -d "$MODEL_PATH" || -z "$(ls -A "$MODEL_PATH" 2>/dev/null)" ]]; then |
| 57 | + hf download "$MODEL" --local-dir "$MODEL_PATH" |
| 58 | + fi |
| 59 | +else |
| 60 | + hf download "$MODEL" |
| 61 | + export MODEL_PATH="$MODEL" |
| 62 | +fi |
| 63 | +nvidia-smi |
| 64 | + |
| 65 | +# ---- Resolve traces and install deps ---------------------------------------- |
| 66 | +resolve_trace_source |
| 67 | +install_agentic_deps |
| 68 | + |
| 69 | +# ---- Kimi-K3 production serving environment --------------------------------- |
| 70 | +export NCCL_DMABUF_ENABLE=0 |
| 71 | +export VLLM_ALLREDUCE_USE_FLASHINFER=1 |
| 72 | +export VLLM_USE_RUST_FRONTEND=1 |
| 73 | +# Loading ~1.5 TB of MXFP4 shards off the staged mount takes well past the |
| 74 | +# default readiness window even with fastsafetensors. |
| 75 | +export VLLM_ENGINE_READY_TIMEOUT_S=3600 |
| 76 | +export PYTHONNOUSERSITE=1 |
| 77 | +# AIPerf pins one pooled keep-alive connection per agentic session and reuses it |
| 78 | +# across turns, while the Rust frontend's default VLLM_HTTP_TIMEOUT_KEEP_ALIVE is |
| 79 | +# 5s. An inter-turn idle gap longer than that lets the client reuse a socket at |
| 80 | +# the moment the server closes it -> aiohttp ServerDisconnectedError -> AIPerf |
| 81 | +# treats it as a terminal warmup failure and aborts the whole job. This killed |
| 82 | +# the dram c4 arm ~15 min into run 30324907690 with a perfectly healthy server |
| 83 | +# (it kept serving after the client gave up). Outlast the client pool so the |
| 84 | +# race cannot occur. Same fix as glm5.2_fp4_b300_sglang.sh's |
| 85 | +# SGLANG_TIMEOUT_KEEP_ALIVE=900. |
| 86 | +export VLLM_HTTP_TIMEOUT_KEEP_ALIVE=900 |
| 87 | +# Agentic warmup dispatches large prompts at once; allow up to 15 minutes of TCP |
| 88 | +# progress before AIPerf declares a connection dead. |
| 89 | +export AIPERF_HTTP_TCP_USER_TIMEOUT=900000 |
| 90 | + |
| 91 | +# ---- Server config ---------------------------------------------------------- |
| 92 | +SERVER_LOG="$RESULT_DIR/server.log" |
| 93 | +mkdir -p "$RESULT_DIR" |
| 94 | + |
| 95 | +# ---- KV offloading ---------------------------------------------------------- |
| 96 | +# The generated TOTAL_CPU_DRAM_GB budget is the aggregate host-DRAM pool for the |
| 97 | +# node; SimpleCPUOffloadConnector is sized per rank. At dram-utilization 0.63 on |
| 98 | +# cluster:b300-nv this resolves to ~220 GiB per rank across the 8 TP ranks. |
| 99 | +OFFLOAD_ARGS=() |
| 100 | +case "${KV_OFFLOAD_BACKEND:-}" in |
| 101 | + "") |
| 102 | + require_agentic_kv_offload_none |
| 103 | + ;; |
| 104 | + vllm-simple) |
| 105 | + require_agentic_kv_offload_backend vllm-simple |
| 106 | + CPU_BYTES_PER_RANK=$(( TOTAL_CPU_DRAM_GB * 1000 * 1000 * 1000 / TP )) |
| 107 | + # Identical prefixes must hash to identical block keys run-to-run. |
| 108 | + export PYTHONHASHSEED=42 |
| 109 | + # lazy_offload must be a JSON boolean, not a quoted string: the |
| 110 | + # connector does bool(extra_config.get("lazy_offload", False)), and |
| 111 | + # bool("false") is True in Python — a quoted "false" would silently |
| 112 | + # turn lazy offload ON. Eager offload keeps block-hash behaviour |
| 113 | + # aligned with the other B300 vllm-simple arms. |
| 114 | + OFFLOAD_ARGS=( |
| 115 | + --kv-transfer-config |
| 116 | + "{\"kv_connector\":\"SimpleCPUOffloadConnector\",\"kv_role\":\"kv_both\",\"kv_connector_extra_config\":{\"cpu_bytes_to_use_per_rank\":${CPU_BYTES_PER_RANK},\"lazy_offload\":false}}" |
| 117 | + ) |
| 118 | + ;; |
| 119 | + *) |
| 120 | + echo "Error: unsupported KV_OFFLOAD_BACKEND='$KV_OFFLOAD_BACKEND' (expected empty or vllm-simple)" >&2 |
| 121 | + exit 1 |
| 122 | + ;; |
| 123 | +esac |
| 124 | + |
| 125 | +# Agentic fan-out: keep the scheduler headroom convention shared by the other |
| 126 | +# agentic recipes. Capture decode graphs only up to that batch size — a 93-layer |
| 127 | +# 2.8T model makes capturing vLLM's full 2048-wide ladder prohibitively slow. |
| 128 | +MAX_NUM_SEQS=$((2 * CONC)) |
| 129 | + |
| 130 | +echo "Starting vllm server..." |
| 131 | + |
| 132 | +{ set +x; } 2>/dev/null |
| 133 | +VLLM_CMD=( |
| 134 | + vllm serve "$MODEL_PATH" --served-model-name "$MODEL" |
| 135 | + --host 0.0.0.0 |
| 136 | + --port "$PORT" |
| 137 | + --tensor-parallel-size "$TP" |
| 138 | + --gpu-memory-utilization 0.90 |
| 139 | + --max-num-seqs "$MAX_NUM_SEQS" |
| 140 | + # Agentic replays run at the model's native context limit. |
| 141 | + --max-model-len 1048576 |
| 142 | + --trust-remote-code |
| 143 | + --load-format fastsafetensors |
| 144 | + --moe-backend auto |
| 145 | + --enable-prefix-caching |
| 146 | + --kv-cache-dtype fp8 |
| 147 | + --reasoning-parser kimi_k3 |
| 148 | + --tool-call-parser kimi_k3 |
| 149 | + --enable-auto-tool-choice |
| 150 | + # FP8 KV cache requires the prefill query quantization flag; MLA prefill |
| 151 | + # runs on FlashInfer per the production recipe. |
| 152 | + --attention-config '{"mla_prefill_backend":"FLASHINFER","use_prefill_query_quantization":true}' |
| 153 | + --max-cudagraph-capture-size "$MAX_NUM_SEQS" |
| 154 | + --disable-uvicorn-access-log |
| 155 | + "${OFFLOAD_ARGS[@]}" |
| 156 | +) |
| 157 | +printf '%q ' "${VLLM_CMD[@]}" | tee "$RESULT_DIR/vllm_command.txt" |
| 158 | +printf '\n' | tee -a "$RESULT_DIR/vllm_command.txt" |
| 159 | +"${VLLM_CMD[@]}" > "$SERVER_LOG" 2>&1 & |
| 160 | +SERVER_PID=$! |
| 161 | +echo "Server PID: $SERVER_PID" |
| 162 | + |
| 163 | +wait_for_server_ready --port "$PORT" --server-log "$SERVER_LOG" --server-pid "$SERVER_PID" |
| 164 | + |
| 165 | +if [ "${EVAL_ONLY}" = "true" ]; then |
| 166 | + run_eval --port "$PORT" |
| 167 | +else |
| 168 | + build_replay_cmd "$RESULT_DIR" |
| 169 | + run_agentic_replay_and_write_outputs "$RESULT_DIR" |
| 170 | +fi |
0 commit comments