Skip to content

Commit 4346729

Browse files
authored
kimik3-fp4-b300-vllm-agentic: day-zero Kimi-K3 B300 agentic recipe (#2371)
1 parent fbade41 commit 4346729

3 files changed

Lines changed: 215 additions & 0 deletions

File tree

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
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

configs/nvidia-master.yaml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1578,6 +1578,37 @@ kimik2.5-fp4-b300-vllm-agentic-mtp:
15781578
- { tp: 4, ep: 1, spec-decoding: mtp, dcp-size: 4, kv-offloading: none, conc-list: [32, 64, 80, 96, 112, 128] }
15791579
- { tp: 4, ep: 1, spec-decoding: mtp, dcp-size: 4, kv-offloading: dram, kv-offload-backend: { name: native }, conc-list: [64, 80, 96, 112, 128, 144, 160] }
15801580

1581+
kimik3-fp4-b300-vllm-agentic:
1582+
# Day-zero Kimi-K3 recipe. `vllm/vllm-openai:kimi-k3` is the pre-release
1583+
# Kimi-K3 build (vLLM 0.1.dev19262+gb6bbf29dd, pushed 2026-07-27); K3 ships as
1584+
# the out-of-tree `vllm.models.kimi_k3` plugin package (DSpark MLA + KDA
1585+
# kernels), so a generic vllm-openai release cannot serve this checkpoint.
1586+
image: vllm/vllm-openai:kimi-k3
1587+
model: moonshotai/Kimi-K3
1588+
model-prefix: kimik3
1589+
runner: cluster:b300-nv
1590+
precision: fp4
1591+
framework: vllm
1592+
multinode: false
1593+
scenarios:
1594+
# Agentic-coding only: no fixed-seq-len (1k1k / 8k1k) arms for this recipe.
1595+
agentic-coding:
1596+
# 0.63 resolves to ~220 GiB of host DRAM per rank across the 8 TP ranks on
1597+
# cluster:b300-nv (total-cpu-dram-gb 1889), which is what the
1598+
# SimpleCPUOffloadConnector pool is sized to in the script.
1599+
- dram-utilization: 0.63
1600+
search-space:
1601+
# TP8 is the only single-node layout that fits: the MXFP4 checkpoint is
1602+
# ~1.5 TB (~188 GB/GPU across 8 B300s), and TP4 would need ~375 GB/GPU
1603+
# against 288 GB of HBM. The conc ceiling is set by the ~70 GB/GPU left
1604+
# for MLA KV after weights at gpu-memory-utilization 0.90.
1605+
# Both arms share one ladder so GPU-resident and DRAM-offload are directly
1606+
# comparable at equal concurrency (same shape as the kimik2.5 B300 sister).
1607+
# TP8 GPU-resident
1608+
- { tp: 8, ep: 1, kv-offloading: none, conc-list: [1, 2, 4, 8, 16, 24] }
1609+
# TP8 SimpleCPUOffload (host DRAM)
1610+
- { tp: 8, ep: 1, kv-offloading: dram, kv-offload-backend: { name: vllm-simple }, conc-list: [1, 2, 4, 8, 16, 24] }
1611+
15811612
dsr1-fp8-b200-trt:
15821613
image: nvcr.io#nvidia/tensorrt-llm/release:1.3.0rc14
15831614
model: deepseek-ai/DeepSeek-R1-0528

perf-changelog.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5145,3 +5145,17 @@
51455145
- "Add a one-node 4P1D TP1-prefill/TEP4-decode concurrency-4096 point on the refreshed image while preserving the legacy 4P2D DEP2-prefill/TEP4-decode point unchanged"
51465146
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2310
51475147

5148+
5149+
- config-keys:
5150+
- kimik3-fp4-b300-vllm-agentic
5151+
description:
5152+
- "Add day-zero single-node agentic-coding recipe for Kimi-K3 (MXFP4, 2.8T MoE, 896 experts, KDA/MLA hybrid, 1M native context) on B300 with vLLM. Agentic-coding scenario only -- no fixed-seq-len (1k1k / 8k1k) arms."
5153+
- "Image vllm/vllm-openai:kimi-k3 (vLLM 0.1.dev19262+gb6bbf29dd, pushed 2026-07-27): K3 ships as the out-of-tree vllm.models.kimi_k3 plugin package (DSpark MLA + KDA kernels, registering KimiK3ForConditionalGeneration / KimiLinearForCausalLM / KimiK3MTPModel), so a generic vllm-openai release cannot serve this checkpoint."
5154+
- "Weights are pre-staged node-local at /scratch/models/Kimi-K3 (96/96 shards, 1.5 TB, verified on all 18 b300 nodes); Kimi-K3 is already in the b300-nv launcher STAGED_MODELS list, so MODEL_PATH resolves to the read-only staged mount and the runner performs no download."
5155+
- "Search space is two TP8 arms at conc 1-24: GPU-resident (kv-offloading none) and host-DRAM offload (kv-offloading dram, SimpleCPUOffloadConnector via kv-offload-backend vllm-simple). Both share one ladder so the arms are directly comparable at equal concurrency. TP8 is the only single-node layout that fits: the MXFP4 checkpoint is ~1.5 TB (~188 GB/GPU across 8 B300s), whereas TP4 would need ~375 GB/GPU against 288 GB of HBM."
5156+
- "DRAM arm: dram-utilization 0.63 resolves to total-cpu-dram-gb 1889 on cluster:b300-nv, which the script divides across the 8 TP ranks into cpu_bytes_to_use_per_rank (~220 GiB/rank), with eager offload (lazy_offload false). Note K3 is a KDA/MLA hybrid -- the KDA layers hold a recurrent state rather than paged KV blocks, so only the MLA layers' paged blocks are expected to offload; the bring-up sweep is the check for KV-geometry errors at server init."
5157+
- "Serve flags follow the K3 production recipe already exercised by the DSpark AL collector: --load-format fastsafetensors, --moe-backend auto, --enable-prefix-caching, --kv-cache-dtype fp8 with --attention-config mla_prefill_backend=FLASHINFER + use_prefill_query_quantization, kimi_k3 reasoning/tool-call parsers, NCCL_DMABUF_ENABLE=0, VLLM_ALLREDUCE_USE_FLASHINFER=1, VLLM_USE_RUST_FRONTEND=1."
5158+
- "Sets VLLM_HTTP_TIMEOUT_KEEP_ALIVE=900 and AIPERF_HTTP_TCP_USER_TIMEOUT=900000. AIPerf pins one pooled keep-alive connection per agentic session and reuses it across turns, while the Rust frontend defaults to a 5s keep-alive; an inter-turn idle gap past that lets the client reuse a socket exactly as the server closes it, producing an aiohttp ServerDisconnectedError that AIPerf escalates to a terminal warmup failure. This aborted the dram c4 arm ~15 min into run 30324907690 against a healthy server. Same mitigation as glm5.2_fp4_b300_sglang.sh's SGLANG_TIMEOUT_KEEP_ALIVE=900."
5159+
- "Bring-up validated in run 30326393603: all 12 configs green, zero ServerDisconnectedError after the keep-alive fix. GPU KV resolves to 42.23 GiB / 3,249,215 tokens, i.e. ~3.1 max-length requests, against MAX_NUM_SEQS = 2*CONC."
5160+
- "Measured behaviour: below conc 8 the GPU-resident and DRAM arms are within run-to-run noise (1-5%). At conc 16 and 24 the GPU-resident arm thrashes -- prefix cache hit rate 2.7%, TTFT p50 86s and 191s, 49.6 and 54.9 output tok/s -- because the working set exceeds GPU KV and prefixes are recomputed. The DRAM arm holds TTFT p50 0.85s and 6.2s for 245.0 and 260.6 output tok/s (4-5x), with the CPU tier serving a 62% external prefix cache hit rate at conc 24. The high-conc GPU-resident points are retained deliberately as the honest baseline that makes the offload gain legible."
5161+
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2371

0 commit comments

Comments
 (0)