moonshotai/Kimi-K3
Pre-release 2.8T-parameter native multimodal MoE with Kimi Delta Attention, Gated MLA, Attention Residuals, and a 1M-token context window
Pre-release TP8, TEP16, TP8xPP2, and disaggregated P/D profiles for the 2.8T MXFP4 checkpoint
Overview
Kimi K3 is a 2.8-trillion-parameter Mixture-of-Experts model (16 of 896 experts active per token) built on the Kimi Delta Attention (KDA) and Attention Residuals (AttnRes), with a 1M-token context window and native vision.
Prerequisites
- vLLM: Use the vllm/vllm-openai:kimi-k3 docker
- CUDA: The kimi-k3 image ships as a CUDA 13 (cu130) build only — there is no -cu129 tag, and the K3-enabled wheels are not on the cu129 nightly index. The host needs an r580+ NVIDIA driver; on a CUDA 12.9 (r575) host, upgrade the driver or build vLLM from the K3 branch against cu129 PyTorch yourself.
- Hardware: At least 8x GB300. Multi-node for real production traffic.
- ROCm: At least 8x MI355X/MI350X. On MI355X the command builder uses the ROCm 10.0 nightly image
vllm/vllm-openai-rocm:nightly-rocm100; the day-0vllm/vllm-openai-rocm:kimi-k3image predates the DSpark and MoE fixes that path needs (see the MI355X AgentX preset).
Client Usage
Once the vLLM server is running, consume it via the OpenAI-compatible API:
import time
from openai import OpenAI
client = OpenAI(
api_key="EMPTY",
base_url="http://localhost:8000/v1",
timeout=3600
)
messages = [
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {
"url": "https://ofasys-multimodal-wlcb-3-toshanghai.oss-accelerate.aliyuncs.com/wpf272043/keepme/image/receipt.png"
}
},
{
"type": "text",
"text": "Read all the text in the image."
}
]
}
]
start = time.time()
response = client.chat.completions.create(
model="moonshotai/Kimi-K3",
messages=messages,
max_tokens=2048
)
print(f"Response costs: {time.time() - start:.2f}s")
print(f"Generated text: {response.choices[0].message.content}")
Notes
- Cross node communitcation: Use
--all2all-backend deepep_v2for RDMA and--all2all-backend flashinfer_nvlink_one_sidedfor NVLink. - MoE backend: Recommend to use
deep_gemm_mega_moefor DEP deployments with cross-node NVLink e.g. GB200 and GB300. Note that DeepGEMM MegaMoE is not compatible with cross-node RDMA. - Rust Frontend: Selected by default in the command builder. Switch to Python if you encounter unsupported features or compatibility issues.
- Model Runner v2:
VLLM_USE_V2_MODEL_RUNNER=1: Model Runner v2 fully supports this model and can be enabled if needed. - Tool calling: K3 occasionally emit a tool-call format its own parser doesn't expect. Suggest to run do schema validation and retry.
- AMD (MI355X / MI350X, CDNA4 gfx950):
VLLM_ROCM_USE_AITER_MOE_SITUV2=1(set above) enables AITER's SiTUv2 a4w4 FlyDSL MoE path on builds that carry vllm-project/vllm#53940 (ROCm nightlies since 2026-09-16). v0.30.0 predates it: there the variable is ignored and the MoE runs the default a16w4 path. Unset it or set0for the default a16w4 path. On builds with #53940,VLLM_ROCM_USE_AITER_MOE_SITUV2_A8W4is a deprecated alias for the same a4w4 path and vLLM removesAITER_SITUV2_A8W4at startup while the toggle is on; on v0.30.0 and older,VLLM_ROCM_USE_AITER_MOE_SITUV2_A8W4=1selects the a8w4 path instead. Do not setAITER_SITUV2_A8W4=1by itself: vLLM does not derive its MoE weight layout from that AITER flag (vllm-project/vllm#53954 is open), so the two can disagree and corrupt output without an error. - max-model-len: Adjust max-model-len for different benchmark scenarios for best performance.
- RDMA: If RDMA is enabled, set
UCX_TLS="rc,cuda_copy"to make sure KV Cache transfer goes through RDMA. - MNNVL environments (GB200/GB300 NVL): recommend adding
NCCL_MNNVL_ENABLE=1,NCCL_CUMEM_ENABLE=1, andNCCL_NVLS_ENABLE=1. - mlx5 dmabuf registration failures: if engine init fails with "NCCL error: unhandled system error" and the log shows
mlx5dv_reg_dmabuf_mrerrno 524, the kernel/driver lacks mlx5 dmabuf support (NCCL 2.28 registers dmabuf by default). SetNCCL_DMABUF_ENABLE=0to fall back tonvidia_peermem(must be loaded on the nodes) — still GPUDirect RDMA. - FP8 KV: If FP8 KV cache is needed, please also add
--attention-config '{"use_prefill_query_quantization":true,"mla_prefill_backend":"TOKENSPEED_MLA"}'when serving vLLM.TOKENSPEED_MLAis what the Blackwell profile above emits (paired with--attention-backend TOKENSPEED_MLAfor decode), but it is not the only supported choice: vLLM 0.27.1 registers three MLA prefill backends —FLASHINFER,TRTLLM_RAGGEDandTOKENSPEED_MLA(seevllm/v1/attention/backends/mla/prefill/registry.py) — and any of them may be substituted formla_prefill_backendalongsideuse_prefill_query_quantization. Which one wins depends on the input-length distribution, so benchmark before switching. Those backends do not run on ROCm: on MI355X the builder pairs FP8 KV with--attention-config '{"mla_prefill_backend":"ROCM_AITER_FA"}'instead. - Prefix-match unit: K3 is a hybrid model: its MLA attention layers and KDA (Mamba-like) layers form two KV-cache groups under the hybrid KV-cache manager. With prefix caching on, vLLM pads the attention block size up to match the Mamba state page, so both groups resolve to one (large) block size and the default prefix-cache hit boundary lands on that block — very coarse. The Blackwell profile sets
--prefix-match-unit 128: the inflation formula always yields a multiple of 128, so 128 divides whatever block size results, and it aligns with the MLA kernel's native block boundary, giving finer prefix-hit granularity. The value is only effective when prefix caching (or a KV connector) is active; it is a no-op otherwise, and it is safe under all tensor-parallel layouts. - Decode context parallelism (DCP): the qualified DCP layouts are part of the presets that ran them: DCP8 on the GB200 aggregated TP8 x PP2 and GB300 disaggregated TP8 pools, DCP16 on the GB200 TP16 disaggregated pools, DCP16/DCP8 at the GB300 low-latency points, and DCP8 in the MI355X AgentX cells. Each pairs
--decode-context-parallel-size(a divisor of the tensor-parallel size) with--dcp-comm-backend a2aand an MLA attention backend that supports it (TOKENSPEED_MLAwithmla_prefill_backend: TRTLLM_RAGGEDon Blackwell,ROCM_AITER_MLAwithmla_prefill_backend: ROCM_AITER_FAon MI355X). On builds that carry them, theVLLM_USE_DIRECT_DCP_A2A,VLLM_USE_DIRECT_DCP_Q_GATHERandVLLM_USE_DIRECT_DCP_KV_GATHERknobs default to auto; set them to1to pin the direct DCP ops or0to disable them, as the MI355X cells do. - Ascend W4A8 checkpoint (ModelScope): https://www.modelscope.cn/models/Eco-Tech/Kimi-K3-w4a8
- vLLM Ascend tutorial: https://docs.vllm.ai/projects/ascend/en/v0.23.0/tutorials/models/Kimi-K3.html
AMD MI355X: disaggregated prefill and decode
This example uses one TP8/DCP8 prefill worker and one TP8/DCP8 decode worker on separate 8-GPU MI355X nodes. MoRIIO transfers the KV cache in READ mode; MultiConnector combines it with SimpleCPUOffloadConnector on prefill only. Both roles use the same target and DSpark draft checkpoints and FP8 KV.
These standalone commands are based on the 1P1D c48 configuration in InferenceX#4391, with real block verification for serving and accuracy evaluation. They are separate from the command builder's NIXL PD configuration.
Prerequisites
- Make moonshotai/Kimi-K3 and Inferact/Kimi-K3-DSpark available inside every worker container. The commands below use
/models/Kimi-K3and/models/Inferact-Kimi-K3-DSparkas example container paths; adjust them to match your deployment. Use identical checkpoint revisions on all workers. - Use the worker and router images below, with the same worker image build on every node. The worker image contains the required vLLM support; no source patches are needed.
- Expose all eight GPUs,
/dev/infiniband, host networking, and sufficient shared memory to each worker container. Allow locked memory for RDMA. The container's verbs provider must be compatible with the host NIC and driver. - The prefill offload budget is 2,399 GB of host DRAM across all eight ranks, in addition to weight-loading and runtime memory. Reduce
CPU_OFFLOAD_BYTESif the host cannot accommodate that allocation; the documented configurations use2399000000000. - Set
SOCKET_IFNAMEandRDMA_DEVICESto your network interface and HCA list.ROUTER_IPand eachWORKER_IPmust be reachable addresses, not0.0.0.0. Allow router TCP ports 8000 and 36367, worker HTTP port 6100, and MoRIIO handshake/notification ports 26000–26007 and 27000–27007, plus the site's RDMA traffic.
WORKER_IMAGE=vllm/vllm-openai-rocm:nightly
ROUTER_IMAGE=vllm/vllm-router:nightly
Start the router
Start one router before the workers. Discovery registers workers automatically; do not add static --prefill or --decode endpoints.
docker run --rm --network host --entrypoint vllm-router "$ROUTER_IMAGE" \
--vllm-pd-disaggregation \
--kv-connector moriio \
--vllm-discovery-address 0.0.0.0:36367 \
--host 0.0.0.0 --port 8000 \
--worker-startup-timeout-secs 7200 \
--policy consistent_hash \
--prefill-policy consistent_hash \
--decode-policy consistent_hash \
--log-level info
Start the workers
Save the following as kimi-k3-pd.sh on each worker. Run it with Bash inside the worker image, with the devices and model paths described above.
#!/usr/bin/env bash
set -euo pipefail
ROLE=${1:?Usage: bash kimi-k3-pd.sh prefill|decode}
: "${ROUTER_IP:?Set the router reachable IP}"
: "${WORKER_IP:?Set this worker reachable IP}"
: "${SOCKET_IFNAME:?Set the host network interface}"
: "${RDMA_DEVICES:?Set the comma-separated HCA list}"
CPU_OFFLOAD_BYTES=${CPU_OFFLOAD_BYTES:-2399000000000}
export NCCL_SOCKET_IFNAME="$SOCKET_IFNAME" GLOO_SOCKET_IFNAME="$SOCKET_IFNAME"
export NCCL_IB_HCA="$RDMA_DEVICES"
export VLLM_ROCM_USE_AITER=1 AITER_BF16_FP8_MOE_BOUND=0
export AITER_FLYDSL_STAGE2_FP8=1
export VLLM_ROCM_AITER_MLA_ASM_PADDING=asm VLLM_ROCM_AITER_MLA_DCP_VERIFY=asm
export VLLM_ROCM_QUICK_REDUCE_QUANTIZATION=INT4
export VLLM_ROCM_QUICK_REDUCE_MAX_SIZE_BYTES_MB=256
export SAFETENSORS_FAST_GPU=1
export VLLM_SSM_CONV_STATE_LAYOUT=DS VLLM_KV_CACHE_LAYOUT=HND
export NCCL_DMABUF_ENABLE=0 HSA_ENABLE_IPC_MODE_LEGACY=1 HIP_FORCE_DEV_KERNARG=1
export PYTHONHASHSEED=42 PYTHONNOUSERSITE=1
export VLLM_USE_DIRECT_DCP_A2A=0 VLLM_USE_DIRECT_DCP_Q_GATHER=0 VLLM_USE_DIRECT_DCP_KV_GATHER=0
export VLLM_ENGINE_READY_TIMEOUT_S=7200 VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=1200
export MORI_IO_SQ_BACKOFF_TIMEOUT_US=50000
export MORI_IO_QP_MAX_SEND_WR=16384 MORI_IO_QP_MAX_CQE=32768 MORI_IO_QP_MAX_SGE=2
export MORI_IO_TC_DISABLE=0
CUSTOM_OPS='["+fused_rms_norm_gated","+quant_fp8","+grouped_topk","+sparse_attn_indexer","none"]'
case "$ROLE" in
prefill)
MAX_SEQS=96
BATCHED=8192
KV_ROLE=kv_producer
export HSA_NO_SCRATCH_RECLAIM=0
COMPILATION="{\"mode\":3,\"cudagraph_mode\":\"NONE\",\"max_cudagraph_capture_size\":0,\"custom_ops\":$CUSTOM_OPS}"
;;
decode)
MAX_SEQS=96
BATCHED=512
KV_ROLE=kv_consumer
CAPTURES="$(seq -s, 1 128),$(seq -s, 130 10 240)"
COMPILATION="{\"mode\":3,\"cudagraph_mode\":\"FULL_DECODE_ONLY\",\"max_cudagraph_capture_size\":240,\"cudagraph_capture_sizes\":[$CAPTURES],\"custom_ops\":$CUSTOM_OPS}"
;;
*) echo "Role must be prefill or decode" >&2; exit 1 ;;
esac
MORI="{\"kv_connector\":\"MoRIIOConnector\",\"kv_role\":\"$KV_ROLE\",\"kv_load_failure_policy\":\"fail\",\"kv_connector_extra_config\":{\"backend\":\"rdma\",\"read_mode\":true,\"proxy_ip\":\"$ROUTER_IP\",\"proxy_ping_port\":\"36367\",\"host_ip\":\"$WORKER_IP\",\"http_port\":\"6100\",\"handshake_port\":\"26000\",\"notify_port\":\"27000\"}}"
KV_CONFIG=$MORI
if [ "$ROLE" = prefill ]; then
CPU="{\"kv_connector\":\"SimpleCPUOffloadConnector\",\"kv_role\":\"kv_both\",\"kv_connector_extra_config\":{\"cpu_bytes_to_use\":$CPU_OFFLOAD_BYTES,\"lazy_offload\":false}}"
KV_CONFIG="{\"kv_connector\":\"MultiConnector\",\"kv_role\":\"kv_both\",\"kv_load_failure_policy\":\"fail\",\"kv_connector_extra_config\":{\"connectors\":[$MORI,$CPU]}}"
fi
exec vllm serve /models/Kimi-K3 \
--host 0.0.0.0 --port 6100 \
--served-model-name moonshotai/Kimi-K3 \
--trust-remote-code \
--tensor-parallel-size 8 \
--decode-context-parallel-size 8 \
--cp-kv-cache-interleave-size 1 \
--dcp-comm-backend a2a \
--moe-backend auto \
--load-format fastsafetensors \
--gpu-memory-utilization 0.90 \
--language-model-only \
--enable-auto-tool-choice \
--tool-call-parser kimi_k3 --reasoning-parser kimi_k3 \
--max-model-len 1048576 \
--stream-interval 10 \
--enable-prefix-caching --prefix-match-unit 128 \
--kv-cache-dtype fp8 --mamba-ssm-cache-dtype bfloat16 \
--block-size 128 \
--attention-backend ROCM_AITER_MLA \
--attention-config '{"mla_prefill_backend":"ROCM_AITER_FA","use_prefill_query_quantization":true}' \
--speculative-config '{"model":"/models/Inferact-Kimi-K3-DSpark","method":"dspark","num_speculative_tokens":4,"attention_backend":"ROCM_AITER_MLA","kv_cache_dtype":"fp8","draft_sample_method":"probabilistic","rejection_sample_method":"block"}' \
--max-num-seqs "$MAX_SEQS" \
--max-num-batched-tokens "$BATCHED" \
--compilation-config "$COMPILATION" \
--kv-transfer-config "$KV_CONFIG"
On the prefill worker, replace the example addresses, interface, and HCAs with your site's values:
export ROUTER_IP=192.0.2.10 WORKER_IP=192.0.2.11
export SOCKET_IFNAME=eth0 RDMA_DEVICES=rdma0,rdma1,rdma2,rdma3,rdma4,rdma5,rdma6,rdma7
bash kimi-k3-pd.sh prefill
On the decode worker:
export ROUTER_IP=192.0.2.10 WORKER_IP=192.0.2.12
export SOCKET_IFNAME=eth0 RDMA_DEVICES=rdma0,rdma1,rdma2,rdma3,rdma4,rdma5,rdma6,rdma7
bash kimi-k3-pd.sh decode
Prefill uses an 8,192-token batch budget without CUDA graphs; decode uses a 512-token budget with FULL_DECODE_ONLY graphs. Both roles use a 96-sequence limit and four DSpark draft tokens, with BF16 SSM. INT4 QuickReduce is a quantized collective optimization; validate accuracy for your workload and keep the same setting on all workers. The RDMA queue backoff is 50,000 microseconds (50 ms).
Prefill scratch reclamation is enabled with HSA_NO_SCRATCH_RECLAIM=0, matching the c48 configurations.
Scaling and concurrency tuning
For 1P2D or 1P3D, run the decode command on additional nodes with their own WORKER_IP. Discovery registers each worker automatically; the port ranges can be reused on separate hosts. These are router-balanced replicas, not a cross-node TP group.
For the offload configurations, let C be global client concurrency, N the number of decode workers, and K the draft length:
P max-num-seqs = 2 * C
D max-num-seqs = ceil(2 * C / N)
D graph max G = (K + 1) * C
These are tuning starting points; sequence limits provide scheduling headroom. Keep P/D SSM dtypes, DCP settings, and cache layouts matched.
| Topology | Global concurrency | P / per-D max sequences | Draft tokens | SSM state | P CPU offload | D graph dense / max |
|---|---|---|---|---|---|---|
| 1P1D | 8 | 1 / 1 | 7 | FP32 | None | Explicit [1, 8] |
| 1P1D | 48 | 96 / 96 | 4 | BF16 | 2399 GB | 128 / 240 |
| 1P2D | 24 | 48 / 24 | 4 | BF16 | 2399 GB | 64 / 120 |
| 1P2D | 48 | 96 / 48 | 4 | BF16 | 2399 GB | 128 / 240 |
| 1P3D | 24 | 48 / 16 | 4 | BF16 | 2399 GB | 64 / 120 |
For c8, set both sequence limits to 1, use K7 and FP32 SSM, and remove prefill CPU offload (use MoRIIO alone). On decode only, set VLLM_USE_V2_MODEL_RUNNER=1, add --additional-config '{"decode_backend":"latency_specialized"}', and use graph sizes [1, 8] with maximum 8.
Benchmark concurrency sweeps and harness settings remain in the linked InferenceX configuration. Benchmark runs use its measured golden acceptance curve; the serving commands retain real block verification.
Verify routed inference
Wait for every worker to load and register with the router. Check each worker's /health endpoint and the router's /v1/models, then send a request through the router:
curl --fail-with-body "http://${ROUTER_IP}:8000/v1/chat/completions" \
-H 'Content-Type: application/json' \
-d '{"model":"moonshotai/Kimi-K3","messages":[{"role":"user","content":"What is 17 + 25?"}],"reasoning_effort":"low","temperature":0.6,"max_tokens":4096}'
Confirm that a final answer is returned, and inspect the prefill/decode logs for successful KV transfers and transfer errors. Health checks alone do not exercise RDMA. Preserve reasoning_content and tool calls when building multi-turn conversations, as described in the model card.
Configurations qualified on specific hardware, layered on the baseline command. Unless marked Opt-in, a preset applies automatically on its hardware and strategy; pick Baseline in the builder to drop it.
TP8 x PP2 + DCP8 across four GB200 trays with the DSpark K4 draft and an embedded Mooncake store.
7 points · concurrency 1–56 · image vllm/vllm-openai:nightly-e68efc68…
Runbook
The GB200 AgentX lanes in SemiAnalysis InferenceX (kimik3-fp4-gb200-dynamo-vllm-agentic-dspark-mooncake-*, SemiAnalysisAI/InferenceX#4322, pinned to b7eee97, which builds on #3748) serve the default moonshotai/Kimi-K3 checkpoint (MXFP4 weights) with DSpark speculative decoding and a Mooncake KV store on 4-GPU NVL trays. They are benchmark configurations; the settings below are the production-useful parts. The builder's Mooncake option generates the mooncake_master and store config; the lane-specific connector settings are below. Throughput runs use InferenceX's golden acceptance lengths for DSpark.
Served by vLLM's own frontend (no Dynamo) with an embedded RDMA Mooncake store (global_segment_size 200GB, mlx5_0..mlx5_3). The preset renders the TP8 x PP2 + DCP8 layout across four trays with the lane's K4/K7 draft, --max-num-seqs and CUDA graph sizes at each concurrency point. The builder's Mooncake KV Offload option renders a plain MooncakeStoreConnector with a 100GB segment; use the lane's connector and 200GB store from the command below.
export VLLM_USE_DIRECT_DCP_A2A=1 VLLM_USE_DIRECT_DCP_Q_GATHER=1 VLLM_USE_DIRECT_DCP_KV_GATHER=1
export VLLM_ALLREDUCE_USE_FLASHINFER=1 NCCL_CUMEM_ENABLE=1 VLLM_USE_V2_MODEL_RUNNER=1
export VLLM_MOONCAKE_LOAD_RECV_THREADS=4 MC_SLICE_SIZE=1048576 VLLM_PREFIX_CACHE_RETENTION_INTERVAL=0
export MC_STORE_MEMCPY=1 MC_ENABLE_DEST_DEVICE_AFFINITY=1 MC_WORKERS_PER_CTX=4
export UCX_MEMTYPE_CACHE=n UCX_MEMTYPE_REG_WHOLE=n VLLM_MEMORY_PROFILER_ESTIMATE_CUDAGRAPHS=0
export VLLM_ENGINE_READY_TIMEOUT_S=3600 VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=1800 VLLM_RPC_TIMEOUT=600000
vllm serve moonshotai/Kimi-K3 --trust-remote-code \
--tensor-parallel-size 8 --pipeline-parallel-size 2 \
--decode-context-parallel-size 8 --dcp-comm-backend a2a \
--attention-backend TOKENSPEED_MLA \
--attention-config '{"mla_prefill_backend":"TRTLLM_RAGGED","use_prefill_query_quantization":true}' \
--kv-cache-dtype fp8 --mamba-ssm-cache-dtype bfloat16 --enable-prefix-caching --prefix-match-unit 128 \
--max-model-len 1048576 --gpu-memory-utilization 0.88 --max-num-seqs <S> --max-num-batched-tokens 8192 \
--speculative-config '{"model":"Inferact/Kimi-K3-DSpark","method":"dspark","num_speculative_tokens":<K>,"attention_backend":"TOKENSPEED_MLA","draft_sample_method":"probabilistic"}' \
--compilation-config '{"cudagraph_mode":"FULL_AND_PIECEWISE","cudagraph_capture_sizes":[<sizes from the table>,256,512,1024,2048,4096,8192]}' \
--kv-transfer-config '{"kv_connector":"MooncakeStoreConnector","kv_role":"kv_both","kv_load_failure_policy":"recompute","kv_connector_extra_config":{"load_async":true,"lookup_async":true,"enable_offload":false}}' \
--load-format fastsafetensors --moe-backend auto --no-enable-flashinfer-autotune \
--tool-call-parser kimi_k3 --reasoning-parser kimi_k3 --enable-auto-tool-choice \
--language-model-only
One TP16 + DCP16 prefill pool and one decode pool across four GB200 trays each, served by Dynamo with the DSpark K4 draft.
3 points · concurrency 16–48 · image vllm/vllm-openai:nightly-e68efc68… · dynamo ffbbaaa6
Runbook
The GB200 AgentX lanes in SemiAnalysis InferenceX (kimik3-fp4-gb200-dynamo-vllm-agentic-dspark-mooncake-*, SemiAnalysisAI/InferenceX#4322, pinned to b7eee97, which builds on #3748) serve the default moonshotai/Kimi-K3 checkpoint (MXFP4 weights) with DSpark speculative decoding and a Mooncake KV store on 4-GPU NVL trays. They are benchmark configurations; the settings below are the production-useful parts. The builder's Mooncake option generates the mooncake_master and store config; the lane-specific connector settings are below. Throughput runs use InferenceX's golden acceptance lengths for DSpark.
Image vllm/vllm-openai:nightly-e68efc685ab1e6f1eda004ced7157e3ca411da2f with Dynamo ffbbaaa6c44370e798687881317535ac17d66a76, etcd and an embedded RDMA Mooncake store. The lane's request plane is TCP (DYN_REQUEST_PLANE=tcp) and it starts no NATS service. Each role is one worker across four trays.
Run each command on all four trays of the role with the same --master-addr (the first tray's address), --nnodes 4, --node-rank 0 to 3, and --headless on ranks 1-3. Prefill (P_GPU/P_CAP and S are the selected point's values):
python3 -m dynamo.vllm --model moonshotai/Kimi-K3 --served-model-name moonshotai/Kimi-K3 \
--disaggregation-mode prefill --request-plane tcp \
--nnodes 4 --node-rank <0-3> --master-addr <first-tray-ip> --device-ids 0,1,2,3 \
--tensor-parallel-size 16 --decode-context-parallel-size 16 --dcp-comm-backend a2a \
--attention-backend TOKENSPEED_MLA \
--attention-config '{"mla_prefill_backend":"TRTLLM_RAGGED","use_prefill_query_quantization":true}' \
--speculative-config '{"model":"Inferact/Kimi-K3-DSpark","attention_backend":"TOKENSPEED_MLA","method":"dspark","num_speculative_tokens":4,"draft_sample_method":"probabilistic"}' \
--kv-cache-dtype fp8 --mamba-ssm-cache-dtype bfloat16 --enable-prefix-caching --prefix-match-unit 128 --language-model-only \
--gpu-memory-utilization <P_GPU> --max-num-seqs <S> --max-cudagraph-capture-size <P_CAP> \
--enable-cumem-allocator --stream-interval 10 --load-format safetensors --safetensors-load-strategy lazy \
--trust-remote-code \
--kv-events-config '{"publisher":"zmq","topic":"kv-events","enable_kv_cache_events":true,"endpoint":"tcp://*:5200"}' \
--kv-transfer-config '{"kv_connector":"MultiConnector","kv_role":"kv_both","kv_load_failure_policy":"recompute","kv_connector_extra_config":{"connectors":[{"kv_connector":"NixlConnector","kv_role":"kv_both","kv_load_failure_policy":"fail","kv_buffer_device":"cuda","kv_connector_extra_config":{"enforce_handshake_compat":false,"enable_cross_layers_blocks":false}},{"kv_connector":"MooncakeStoreConnector","kv_role":"kv_both","kv_load_failure_policy":"recompute","kv_connector_extra_config":{"load_async":true,"lookup_async":true,"compact_group_io":true,"max_load_batch_keys":2,"enable_cross_layers_blocks":false,"enable_offload":false}}]}}'
Decode (D_GPU/D_CAP and S are the selected point's values). --max-num-batched-tokens 1024 bounds the FlashInfer MoE autotune to the token counts decode can reach (at most 480); at vLLM's 16384-token default the autotune bucket needs a 2.9 GiB workspace that runs out of memory at 0.92, and the other ranks hang:
python3 -m dynamo.vllm --model moonshotai/Kimi-K3 --served-model-name moonshotai/Kimi-K3 \
--disaggregation-mode decode --request-plane tcp \
--nnodes 4 --node-rank <0-3> --master-addr <first-tray-ip> --device-ids 0,1,2,3 \
--tensor-parallel-size 16 --decode-context-parallel-size 16 --dcp-comm-backend a2a \
--attention-backend TOKENSPEED_MLA \
--attention-config '{"mla_prefill_backend":"TRTLLM_RAGGED","use_prefill_query_quantization":true}' \
--speculative-config '{"model":"Inferact/Kimi-K3-DSpark","attention_backend":"TOKENSPEED_MLA","method":"dspark","num_speculative_tokens":4,"draft_sample_method":"probabilistic"}' \
--kv-cache-dtype fp8 --mamba-ssm-cache-dtype bfloat16 --enable-prefix-caching --prefix-match-unit 128 --language-model-only \
--gpu-memory-utilization <D_GPU> --max-num-seqs <S> --max-num-batched-tokens 1024 --max-cudagraph-capture-size <D_CAP> \
--enable-cumem-allocator --stream-interval 10 --load-format safetensors --safetensors-load-strategy lazy \
--trust-remote-code \
--kv-transfer-config '{"kv_connector":"MultiConnector","kv_role":"kv_both","kv_load_failure_policy":"recompute","kv_connector_extra_config":{"connectors":[{"kv_connector":"NixlConnector","kv_role":"kv_both","kv_load_failure_policy":"fail","kv_buffer_device":"cuda","kv_connector_extra_config":{"enforce_handshake_compat":false,"enable_cross_layers_blocks":false}},{"kv_connector":"MooncakeStoreConnector","kv_role":"kv_both","kv_load_failure_policy":"recompute","kv_connector_extra_config":{"load_async":true,"lookup_async":true,"compact_group_io":true,"max_load_batch_keys":2,"enable_cross_layers_blocks":false,"enable_offload":false}}]}}'
For tool calling and reasoning output, add --reasoning-parser kimi_k3 --dyn-tool-call-parser kimi_k3 --dyn-reasoning-parser kimi_k3 --dyn-enable-structural-tag to both roles. python3 -m dynamo.vllm parses the remaining arguments as vLLM engine arguments only, so --enable-auto-tool-choice and --tool-call-parser are rejected there.
- Environment (both roles): the three direct-DCP variables,
VLLM_ALLREDUCE_USE_FLASHINFER=1,VLLM_USE_V2_MODEL_RUNNER=1,VLLM_KIMI_K3_SHARD_SP_SHARED_EXPERT=0,VLLM_USE_NCCL_SYMM_MEM=0,VLLM_SSM_CONV_STATE_LAYOUT=DS,VLLM_PREFIX_CACHE_RETENTION_INTERVAL=0,NCCL_MNNVL_ENABLE=1,NCCL_NVLS_ENABLE=1,NCCL_CUMEM_ENABLE=1,NCCL_P2P_LEVEL=NVL,NCCL_NET_PLUGIN=none,VLLM_MOONCAKE_LOAD_RECV_THREADS=4,MC_SLICE_SIZE=1048576,MC_ENABLE_DEST_DEVICE_AFFINITY=1,UCX_MEMTYPE_CACHE=n,UCX_MEMTYPE_REG_WHOLE=n,UCX_RCACHE_MAX_UNRELEASED=1024,UCX_TCP_AF_PRIO=inet,VLLM_MEMORY_PROFILER_ESTIMATE_CUDAGRAPHS=0,VLLM_ENGINE_READY_TIMEOUT_S=3600,VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=1800,ETCD_LEASE_TTL=600,PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:512andDYN_REQUEST_PLANE=tcp. - Frontend: the Dynamo frontend runs with
--router-mode least-loaded --router-session-affinity-ttl-secs 900, so each conversation sticks to one worker and reuses its prefix cache. The Rust frontend is selected withVLLM_USE_RUST_FRONTEND=1, withDYN_ROUTER_ACTIVE_REQUEST_EXPIRY_SECS=3600andDYN_TOKENIZER_CACHE_BYTES=8589934592. AgentX sends the conversation ID in theX-Dynamo-Session-Idrequest header. - Mooncake master:
--default_kv_lease_ttl=60000 --eviction_high_watermark_ratio=0.95 --eviction_ratio=0.10, 128GB global segment, 4GB local buffer, RDMA overmlx5_0..mlx5_3.
TP8 + DCP8 prefill and decode workers across two GB300 trays each, served by Dynamo with the DSpark K4 draft.
image vllm/vllm-openai:nightly-e4a5b553… · dynamo a0de541e
RunbookServices · Environment (every worker) · Workers · Specialized decode (low concurrency) · Frontend
The GB300 AgentX disaggregated lane in SemiAnalysis InferenceX (kimik3-fp4-gb300-dynamo-vllm-agentic-dspark-mooncake-dcp8-disagg, SemiAnalysisAI/InferenceX#4047, points refined in #4373 and #4516) runs Dynamo + vLLM with several prefill and decode workers. Prefill and decode workers are both TP8 + DCP8 across two 4-GPU GB300 trays; prefill workers also use EP8 with MegaMoE, and decode workers use no expert parallelism. KV moves over NIXL, and all workers share an RDMA Mooncake store. Image vllm/vllm-openai:nightly-e4a5b5537344b103ed10de7764378ef09604ef3d, Dynamo a0de541ea8c861d5ac01d15953ca529bea68637b.
| Clients | Prefill workers | Decode workers | GPUs | Decode profile | Decode --max-num-seqs |
|---|---|---|---|---|---|
| 16 | 1 | 3 | 32 | Specialized | 12 |
| 24-32 | 1 | 2 | 24 | Specialized | 12 |
| 48-64 | 1 | 2 | 24 | Regular | 128 |
| 240 | 2 | 3 | 40 | Regular | 128 |
| 240 | 3 | 4 | 56 | Regular | 128 |
Prefill workers always use --max-num-seqs 32. The regular profile is the Workers commands below; the specialized one changes both roles as described under Specialized decode.
The preset renders one prefill pool and one decode pool of the specialized profile (the 1P2D c24-c32 row); its decode uses --max-num-seqs 12. For the regular points use the Workers commands below. It turns Spec decoding off, because the toggle's generic RedHatAI/Kimi-K3-speculator.dspark K8 draft was not qualified and specialized decode allows at most 7 draft tokens, and Tool calling off (see the parser note under Workers). This preset's DCP8 pools do not combine with a DEP decode pool, which renders TP1 with --decode-context-parallel-size 8 that vLLM rejects; the DEP16 decode lane is the AgentX 3P1D DEP16 preset.
Documented here only: worker counts beyond one pool per role, the Mooncake store connector (the builder's Mooncake option renders a plain MooncakeStoreConnector without the qualified NIXL MultiConnector, recompute policy and load options), the Mooncake services, prefill --kv-events-config, and the frontend's router mode and session affinity (the builder renders round-robin).
Services
Start etcd, NATS and a Mooncake master once:
etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://<infra-host>:2379
printf 'max_payload: 33554432\n' > nats.conf && nats-server -c nats.conf
mooncake_master --enable_http_metadata_server=true \
--default_kv_lease_ttl=60000 --eviction_high_watermark_ratio=0.95 --eviction_ratio=0.10
The workers' Mooncake store uses P2P handshake metadata, RDMA over the node's NICs, a 160GB global segment and a 4GB local buffer, with offload disabled.
Environment (every worker)
export VLLM_USE_DIRECT_DCP_A2A=1 VLLM_USE_DIRECT_DCP_Q_GATHER=1 VLLM_USE_DIRECT_DCP_KV_GATHER=1
export VLLM_ALLREDUCE_USE_FLASHINFER=1 VLLM_KIMI_K3_SHARD_SP_SHARED_EXPERT=0 VLLM_USE_NCCL_SYMM_MEM=0
export NCCL_CUMEM_ENABLE=1 NCCL_MNNVL_ENABLE=1 NCCL_NVLS_ENABLE=1 NCCL_P2P_LEVEL=NVL
export VLLM_USE_V2_MODEL_RUNNER=1 VLLM_USE_RUST_FRONTEND=1 VLLM_SSM_CONV_STATE_LAYOUT=DS
export VLLM_PREFIX_CACHE_RETENTION_INTERVAL=0
export VLLM_MOONCAKE_LOAD_RECV_THREADS=4 MC_SLICE_SIZE=1048576 MC_MAX_MR_SIZE=4294967296 MC_ENABLE_DEST_DEVICE_AFFINITY=1
export UCX_MEMTYPE_CACHE=n UCX_MEMTYPE_REG_WHOLE=n UCX_RCACHE_MAX_UNRELEASED=1024 UCX_TCP_AF_PRIO=inet
export TOKENSPEED_MLA_FP16_PARTIALS=0
export DYN_REQUEST_PLANE=tcp DYN_TCP_REQUEST_TIMEOUT=120 ETCD_LEASE_TTL=600
export VLLM_ENGINE_READY_TIMEOUT_S=3600 VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=1800 VLLM_RPC_TIMEOUT=600000
Set VLLM_ENABLE_GEMM_RS=1 on prefill workers and VLLM_ENABLE_GEMM_RS=0 on decode workers. Prefill workers also set DYN_BACKEND_ADMISSION_ENGINE_REQUEST_LIMIT=1024 and DYN_BACKEND_ADMISSION_ENGINE_REQUEST_WAITING_LIMIT=1024. Loading the 2.8T checkpoint across trays and autotuning takes a while, which is why the engine-ready, execute-model and RPC timeouts are raised.
Workers
Each worker spans two trays. Run the command on both trays with the same --master-addr (the first tray's address), --nnodes 2 and --node-rank 0 or 1.
Prefill:
python3 -m dynamo.vllm --model moonshotai/Kimi-K3 --served-model-name moonshotai/Kimi-K3 \
--disaggregation-mode prefill --request-plane tcp \
--nnodes 2 --node-rank <0|1> --master-addr <first-tray-ip> --device-ids 0,1,2,3 \
--tensor-parallel-size 8 --decode-context-parallel-size 8 --dcp-comm-backend a2a \
--enable-expert-parallel --moe-backend deep_gemm_mega_moe \
--attention-backend TOKENSPEED_MLA \
--attention-config '{"mla_prefill_backend":"TRTLLM_RAGGED","use_prefill_query_quantization":true,"trtllm_ragged_backend":"cute-dsl"}' \
--speculative-config '{"model":"Inferact/Kimi-K3-DSpark","attention_backend":"TOKENSPEED_MLA","method":"dspark","num_speculative_tokens":4,"draft_sample_method":"probabilistic","rejection_sample_method":"block"}' \
--kv-cache-dtype fp8 --mamba-ssm-cache-dtype bfloat16 \
--enable-prefix-caching --prefix-match-unit 128 \
--gpu-memory-utilization 0.95 --max-num-seqs 32 --max-cudagraph-capture-size 8192 \
--compilation-config '{"cudagraph_mode":"FULL_AND_PIECEWISE","cudagraph_capture_sizes":[4,8,12,16,20,24,32,40,48,64,80,96,128,160,256,384,512,640,768,896,1024,1152,1280,1408,1536,1664,1792,1920,2048,2304,2560,2816,3072,3328,3584,3840,4096,4608,5120,5632,6144,6656,7168,7680,8192]}' \
--enable-cumem-allocator --enable-flashinfer-autotune \
--load-format fastsafetensors --stream-interval 10 --language-model-only --trust-remote-code \
--kv-events-config '{"publisher":"zmq","topic":"kv-events","enable_kv_cache_events":true,"endpoint":"tcp://*:5200"}' \
--kv-transfer-config '{"kv_connector":"MultiConnector","kv_role":"kv_both","kv_load_failure_policy":"recompute","kv_connector_extra_config":{"connectors":[{"kv_connector":"NixlConnector","kv_role":"kv_both","kv_load_failure_policy":"fail","kv_buffer_device":"cuda","kv_connector_extra_config":{"enforce_handshake_compat":false,"enable_cross_layers_blocks":false}},{"kv_connector":"MooncakeStoreConnector","kv_role":"kv_both","kv_load_failure_policy":"recompute","kv_connector_extra_config":{"load_async":true,"lookup_async":true,"enable_cross_layers_blocks":false,"enable_offload":false}}]}}'
Decode (regular profile, --max-num-seqs 128):
python3 -m dynamo.vllm --model moonshotai/Kimi-K3 --served-model-name moonshotai/Kimi-K3 \
--disaggregation-mode decode --request-plane tcp \
--nnodes 2 --node-rank <0|1> --master-addr <first-tray-ip> --device-ids 0,1,2,3 \
--tensor-parallel-size 8 --decode-context-parallel-size 8 --dcp-comm-backend a2a \
--attention-backend TOKENSPEED_MLA \
--attention-config '{"mla_prefill_backend":"TRTLLM_RAGGED","use_prefill_query_quantization":true,"tokenspeed_mla_min_split_kv":8}' \
--speculative-config '{"model":"Inferact/Kimi-K3-DSpark","attention_backend":"TOKENSPEED_MLA","method":"dspark","num_speculative_tokens":4,"draft_sample_method":"probabilistic","rejection_sample_method":"block"}' \
--kv-cache-dtype fp8 --mamba-ssm-cache-dtype bfloat16 \
--enable-prefix-caching --prefix-match-unit 128 \
--gpu-memory-utilization 0.95 --max-num-seqs 128 --max-cudagraph-capture-size 512 --use-replayssm \
--enable-cumem-allocator --enable-flashinfer-autotune \
--load-format fastsafetensors --stream-interval 10 --language-model-only --trust-remote-code \
--kv-transfer-config '{"kv_connector":"MultiConnector","kv_role":"kv_both","kv_load_failure_policy":"recompute","kv_connector_extra_config":{"connectors":[{"kv_connector":"NixlConnector","kv_role":"kv_both","kv_load_failure_policy":"fail","kv_buffer_device":"cuda","kv_connector_extra_config":{"enforce_handshake_compat":false,"enable_cross_layers_blocks":false}},{"kv_connector":"MooncakeStoreConnector","kv_role":"kv_both","kv_load_failure_policy":"recompute","kv_connector_extra_config":{"load_async":true,"lookup_async":true,"enable_cross_layers_blocks":false,"enable_offload":false}}]}}'
For tool calling and reasoning output, add --reasoning-parser kimi_k3 --dyn-tool-call-parser kimi_k3 --dyn-reasoning-parser kimi_k3 --dyn-enable-structural-tag to both roles. python3 -m dynamo.vllm parses the remaining arguments as vLLM engine arguments only, so the vLLM API-server flags --enable-auto-tool-choice and --tool-call-parser are rejected there.
Specialized decode (low concurrency)
The c16-c32 points use the latency_specialized decode backend from GB300 low-latency preset on the decode workers (1P3D for c16, 1P2D for c24-c32), with the Workers commands above changed as follows. The backend's requirements are listed there: V2 model runner (set in Environment), FULL decode CUDA graphs, BF16 convolution state with FP32 recurrent state, DSpark with at most 7 draft tokens, no pipeline or expert parallelism, and DCP of 1 or TP. The decode workers already meet the last two.
Both roles:
--mamba-ssm-cache-dtype float32 --cp-kv-cache-interleave-size 1 --no-enable-flashinfer-autotune
# replacing --mamba-ssm-cache-dtype bfloat16 and --enable-flashinfer-autotune
Pin --cp-kv-cache-interleave-size 1 on both roles. Left unset, NIXL P/D raises the DCP KV interleave to the KV block size after the specialized decode has read it, so its spec-verify causal mask uses the stale value.
Decode only:
--additional-config '{"decode_backend":"latency_specialized"}' \
--attention-config '{"mla_prefill_backend":"TRTLLM_RAGGED","use_prefill_query_quantization":true}' \
--max-num-seqs 12 --max-cudagraph-capture-size 64 \
--compilation-config "{\"cudagraph_mode\":\"FULL_AND_PIECEWISE\",\"cudagraph_capture_sizes\":[$(seq -s, 1 64)]}"
# replacing the regular decode's --attention-config (it also sets tokenspeed_mla_min_split_kv),
# --max-num-seqs 128 and --max-cudagraph-capture-size 512, and without --use-replayssm
--max-num-seqs is 64 // (K + 1) with the K4 draft, so a full verify batch (12 x 5 = 60 tokens) fits the 64-token graph, and CUDA graphs are captured for every batch size up to 64. The prefill workers do not set --additional-config: the backend rejects the expert parallelism they use. The uncapped c48+ points keep the regular profile, so TTFT is not traded for ITL by a standing decode queue.
Frontend
export DYN_ROUTER_ACTIVE_REQUEST_EXPIRY_SECS=3600 DYN_TOKENIZER_CACHE_BYTES=8589934592 DYN_TCP_REQUEST_TIMEOUT=120
export DYN_DISABLE_OPENAI_STRICT_SCHEMA_VALIDATION=1
python3 -m dynamo.frontend --router-mode least-loaded --router-session-affinity-ttl-secs 1
DYN_DISABLE_OPENAI_STRICT_SCHEMA_VALIDATION=1 skips the frontend's preflight validation of tool parameter schemas marked strict: true. AgentX sends the conversation ID in the X-Dynamo-Session-Id request header.
Three TP8 + EP8 prefill workers across two GB300 trays each and one DEP16 decode worker across four trays, served by Dynamo with the DSpark K4 draft and online MXFP8 target projections.
image vllm/vllm-openai:nightly-e4a5b553… · dynamo a0de541e
RunbookServices · Environment (every worker) · Workers · Frontend
The GB300 AgentX 3P1D lane in SemiAnalysis InferenceX (kimik3-fp4-gb300-dynamo-vllm-agentic-dspark-mooncake-dep16-disagg, SemiAnalysisAI/InferenceX#4516) runs Dynamo + vLLM with three prefill workers and one decode worker at 336 clients (40 GPUs). Each prefill worker is TP8 + EP8 across two 4-GPU GB300 trays, without DCP. The decode worker is DEP16 (--tensor-parallel-size 1 --data-parallel-size 16 with expert parallelism) across four trays. Both roles use MegaMoE, --max-num-seqs 32 (per data-parallel rank on decode) and online MXFP8 for selected target projections. KV moves over NIXL, and all workers share an RDMA Mooncake store. Image vllm/vllm-openai:nightly-e4a5b5537344b103ed10de7764378ef09604ef3d, Dynamo a0de541ea8c861d5ac01d15953ca529bea68637b.
The preset renders one prefill pool and the decode pool. It turns Spec decoding off, because the toggle's generic RedHatAI/Kimi-K3-speculator.dspark K8 draft was not qualified, and Tool calling off (see the parser note under Workers).
Documented here only: the three prefill workers, the Mooncake store connector (the builder renders a plain NIXL connector, without the qualified MultiConnector, recompute policy and load options), the Mooncake services, prefill --kv-events-config, NVSHMEM_HCA_LIST, and the frontend's router mode and session affinity (the builder renders round-robin).
Target quantization: --quantization-config quantizes these target layers online to MXFP8: the KDA in_proj_qkvgfab and MLA fused_qkv_a_g_proj projections and the latent-MoE routed_expert_up_proj/routed_expert_down_proj projections on both roles, plus o_proj and the shared experts (gate_up_proj, down_proj) on decode. The DSpark draft is not quantized: vLLM builds the named draft from its own unquantized config, and the lane's logs show the online quantization pass only for the target layers.
Services
Start etcd, NATS and a Mooncake master once, as for the AgentX xPyD preset: mooncake_master --enable_http_metadata_server=true --default_kv_lease_ttl=60000 --eviction_high_watermark_ratio=0.95 --eviction_ratio=0.10. The workers' Mooncake store uses P2P handshake metadata, RDMA over the node's NICs, a 160GB global segment and a 4GB local buffer, with offload disabled.
Environment (every worker)
export VLLM_ALLREDUCE_USE_FLASHINFER=1 VLLM_ENABLE_GEMM_RS=1 VLLM_USE_NCCL_SYMM_MEM=0
export NCCL_CUMEM_ENABLE=1 NCCL_MNNVL_ENABLE=1 NCCL_NVLS_ENABLE=1 NCCL_P2P_LEVEL=NVL
export NVSHMEM_REMOTE_TRANSPORT=none NVSHMEM_DISABLE_MNNVL=0 NVSHMEM_IB_ENABLE_IBGDA=0 NVSHMEM_ENABLE_NIC_PE_MAPPING=1
export NVSHMEM_HCA_LIST=<the node's RDMA HCAs>
export VLLM_USE_V2_MODEL_RUNNER=1 VLLM_USE_RUST_FRONTEND=1 VLLM_SSM_CONV_STATE_LAYOUT=DS
export VLLM_MOONCAKE_LOAD_RECV_THREADS=4 MC_SLICE_SIZE=1048576 MC_MAX_MR_SIZE=4294967296 MC_ENABLE_DEST_DEVICE_AFFINITY=1
export UCX_MEMTYPE_CACHE=n UCX_MEMTYPE_REG_WHOLE=n UCX_RCACHE_MAX_UNRELEASED=1024 UCX_TCP_AF_PRIO=inet
export DYN_REQUEST_PLANE=tcp DYN_TCP_REQUEST_TIMEOUT=120 ETCD_LEASE_TTL=600
export VLLM_ENGINE_READY_TIMEOUT_S=3600 VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=1800 VLLM_RPC_TIMEOUT=600000
Prefill workers also set VLLM_KIMI_K3_SHARD_SP_SHARED_EXPERT=0, DYN_BACKEND_ADMISSION_ENGINE_REQUEST_LIMIT=1024 and DYN_BACKEND_ADMISSION_ENGINE_REQUEST_WAITING_LIMIT=1024.
Workers
Prefill (three workers; run each on its two trays with the same --master-addr, the first tray's address, --nnodes 2 and --node-rank 0 or 1):
python3 -m dynamo.vllm --model moonshotai/Kimi-K3 --served-model-name moonshotai/Kimi-K3 \
--disaggregation-mode prefill --request-plane tcp \
--nnodes 2 --node-rank <0|1> --master-addr <first-tray-ip> --device-ids 0,1,2,3 \
--tensor-parallel-size 8 --enable-expert-parallel --enable-ep-weight-filter --moe-backend deep_gemm_mega_moe \
--quantization-config '{"targets":{"*.in_proj_qkvgfab":"mxfp8","*.fused_qkv_a_g_proj":"mxfp8","*.routed_expert_down_proj":"mxfp8","*.routed_expert_up_proj":"mxfp8"}}' \
--attention-backend TOKENSPEED_MLA \
--attention-config '{"mla_prefill_backend":"TRTLLM_RAGGED","use_prefill_query_quantization":true,"trtllm_ragged_backend":"cute-dsl"}' \
--speculative-config '{"model":"Inferact/Kimi-K3-DSpark","attention_backend":"TOKENSPEED_MLA","method":"dspark","num_speculative_tokens":4,"draft_sample_method":"probabilistic","rejection_sample_method":"block"}' \
--kv-cache-dtype fp8 --mamba-ssm-cache-dtype bfloat16 \
--enable-prefix-caching --prefix-match-unit 128 \
--gpu-memory-utilization 0.95 --max-num-seqs 32 --max-num-batched-tokens 16384 --max-cudagraph-capture-size 8192 \
--compilation-config '{"cudagraph_mode":"FULL_AND_PIECEWISE","cudagraph_capture_sizes":[4,8,12,16,20,24,32,40,48,64,80,96,128,160,256,384,512,640,768,896,1024,1152,1280,1408,1536,1664,1792,1920,2048,2304,2560,2816,3072,3328,3584,3840,4096,4608,5120,5632,6144,6656,7168,7680,8192]}' \
--enable-cumem-allocator --enable-flashinfer-autotune \
--load-format fastsafetensors --stream-interval 10 --language-model-only --trust-remote-code \
--kv-events-config '{"publisher":"zmq","topic":"kv-events","enable_kv_cache_events":true,"endpoint":"tcp://*:5200"}' \
--kv-transfer-config '{"kv_connector":"MultiConnector","kv_role":"kv_both","kv_load_failure_policy":"recompute","kv_connector_extra_config":{"connectors":[{"kv_connector":"NixlConnector","kv_role":"kv_both","kv_load_failure_policy":"fail","kv_buffer_device":"cuda","kv_connector_extra_config":{"enforce_handshake_compat":false,"enable_cross_layers_blocks":false}},{"kv_connector":"MooncakeStoreConnector","kv_role":"kv_both","kv_load_failure_policy":"recompute","kv_connector_extra_config":{"load_async":true,"lookup_async":true,"enable_cross_layers_blocks":false,"enable_offload":false}}]}}'
Decode (one DEP16 worker; run on each of its four trays with --data-parallel-start-rank 0, 4, 8 and 12 and the same --data-parallel-address, the first tray's address):
python3 -m dynamo.vllm --model moonshotai/Kimi-K3 --served-model-name moonshotai/Kimi-K3 \
--disaggregation-mode decode --request-plane tcp --device-ids 0,1,2,3 \
--tensor-parallel-size 1 --data-parallel-size 16 --data-parallel-size-local 4 \
--data-parallel-start-rank <0|4|8|12> --data-parallel-address <first-tray-ip> --data-parallel-rpc-port <port> \
--data-parallel-hybrid-lb --enable-expert-parallel --enable-ep-weight-filter --moe-backend deep_gemm_mega_moe \
--quantization-config '{"targets":{"*.in_proj_qkvgfab":"mxfp8","*.fused_qkv_a_g_proj":"mxfp8","*.routed_expert_down_proj":"mxfp8","*.routed_expert_up_proj":"mxfp8","*.o_proj":"mxfp8","*.shared_experts.gate_up_proj":"mxfp8","*.shared_experts.down_proj":"mxfp8"}}' \
--attention-backend TOKENSPEED_MLA \
--attention-config '{"mla_prefill_backend":"TRTLLM_RAGGED","use_prefill_query_quantization":true,"tokenspeed_mla_min_split_kv":8}' \
--speculative-config '{"model":"Inferact/Kimi-K3-DSpark","attention_backend":"TOKENSPEED_MLA","method":"dspark","num_speculative_tokens":4,"draft_sample_method":"probabilistic","rejection_sample_method":"block"}' \
--kv-cache-dtype fp8 --mamba-ssm-cache-dtype bfloat16 \
--no-enable-prefix-caching --prefix-match-unit 128 \
--gpu-memory-utilization 0.95 --max-num-seqs 32 --max-num-batched-tokens 512 --max-cudagraph-capture-size 512 --use-replayssm \
--enable-cumem-allocator --enable-flashinfer-autotune \
--load-format fastsafetensors --stream-interval 10 --language-model-only --trust-remote-code \
--kv-transfer-config '{"kv_connector":"MultiConnector","kv_role":"kv_both","kv_load_failure_policy":"recompute","kv_connector_extra_config":{"connectors":[{"kv_connector":"NixlConnector","kv_role":"kv_both","kv_load_failure_policy":"fail","kv_buffer_device":"cuda","kv_connector_extra_config":{"enforce_handshake_compat":false,"enable_cross_layers_blocks":false}},{"kv_connector":"MooncakeStoreConnector","kv_role":"kv_consumer","kv_load_failure_policy":"recompute","kv_connector_extra_config":{"load_async":true,"lookup_async":true,"enable_lookup":false,"enable_cross_layers_blocks":false,"enable_offload":false}}]}}'
For tool calling and reasoning output, add --reasoning-parser kimi_k3 --dyn-tool-call-parser kimi_k3 --dyn-reasoning-parser kimi_k3 --dyn-enable-structural-tag to both roles; python3 -m dynamo.vllm rejects the vLLM API-server flags --enable-auto-tool-choice and --tool-call-parser.
Frontend
export DYN_ROUTER_ACTIVE_REQUEST_EXPIRY_SECS=3600 DYN_TOKENIZER_CACHE_BYTES=8589934592 DYN_TCP_REQUEST_TIMEOUT=120
export DYN_DISABLE_OPENAI_STRICT_SCHEMA_VALIDATION=1
python3 -m dynamo.frontend --router-mode least-loaded --router-session-affinity-ttl-secs 1
One aggregated engine on the latency_specialized decode backend with the DSpark draft: TP16/DCP16 over four trays at concurrency 1, TP8/DCP8 over two at 2-4.
2 points · concurrency 1–4 · image vllm/vllm-openai:nightly
Runbook
The latency_specialized decode backend provides a CuTe-based implementation for low-concurrency Kimi-K3 serving on Blackwell. The configurations below serve the default moonshotai/Kimi-K3 MXFP4 checkpoint with Inferact/Kimi-K3-DSpark, using one aggregated engine across 4-GPU GB300 NVLink compute nodes and the native Python vLLM frontend.
The explicit opt-in is --additional-config '{"decode_backend":"latency_specialized"}'; auto keeps the stock decode path in this image. The specialized path requires the V2 model runner, FULL decode CUDA graphs, and BF16 convolution state with FP32 recurrent state (--mamba-ssm-cache-dtype float32). Keep FULL_AND_PIECEWISE and the capture sizes together: the lists cover DSpark draft and verification shapes. Prefill and eager/piecewise steps still use the stock implementation. This recipe uses PP1, no expert parallelism, and at most seven draft tokens; do not combine it with --enforce-eager, ReplaySSM/RecoverSSM, or adaptive verification. See the pinned backend requirements.
Make the same target checkpoint available at the same local path on every node, and make Inferact/Kimi-K3-DSpark available through the mounted Hugging Face cache. Run the preset's commands inside the nightly image on every allocated node, with four visible GPUs, host networking, sufficient shared memory, and the devices and network access required by your NVLink/RDMA deployment. Use the reachable address of rank 0 for MASTER_ADDR on every node and a distinct NODE_RANK from 0 through NODES - 1. The lane served the checkpoint from a local path with --served-model-name moonshotai/Kimi-K3 and --device-ids 0,1,2,3; the builder serves the Hugging Face id.
The lane's every-concurrency settings on one MI355X node, for any concurrency; the per-concurrency cells are the AgentX cells preset.
Runbook
The single-node MI355X TP8 AgentX configuration follows InferenceX #4393: its master entry and lane recipe at dee7824. It serves text with tool-call and reasoning parsers, using DCP8/A2A at every point. There is no concurrency-2 point.
In the command builder. Settings the lane uses at every concurrency are part of the MI355X TP8 command: --gpu-memory-utilization 0.9, --max-model-len 1048576, --enable-prefix-caching, --kv-cache-dtype fp8, --attention-config '{"mla_prefill_backend":"ROCM_AITER_FA"}', --stream-interval 10, VLLM_ROCM_AITER_MLA_ASM_PADDING=asm, VLLM_ROCM_AITER_MLA_DCP_VERIFY=asm and AITER_FLYDSL_STAGE2_FP8=1. Weights load with vLLM's default loader (the AMD profile's --load-format auto): with fastsafetensors, the lane's servers ran out of GPU memory while loading weights on its image. The lane's --moe-backend auto is vLLM's default selection, so the builder leaves that flag unset. Other AMD GPUs keep the general AMD profile.
- Spec decoding serves the lane's draft, the published
Inferact/Kimi-K3-DSpark, with its attention onROCM_AITER_MLA(vllm-project/vllm#55966), FP8 KV, probabilistic drafting and block verification. Draft length is tuned per cell in the matrix below. - Target quantization is set per cell in the AgentX cells preset: resident c1 passes no online quantization config, preserving the BF16 target weights required by specialized decode. Every other cell quantizes only the KDA
in_proj_qkvgfabprojections to FP8 per-channel. Routed experts retain their native MXFP4 weights. The DSpark draft remains BF16 with FP8 KV: vLLM builds the named draft from its own unquantized config, so removing c1's target quantization does not change the draft's dtype. - DCP8 (
--decode-context-parallel-size 8,--attention-backend ROCM_AITER_MLA,--dcp-comm-backend a2a,ROCM_AITER_FAprefill, and the direct DCP ops off) is part of the AgentX cells preset; this preset serves without DCP. - The Simple KV-offload option emits the connector the lane uses (
SimpleCPUOffloadConnector,kv_both,lazy_offload: false).
MoE environment. The lane sets VLLM_ROCM_USE_AITER_MOE_SITUV2=1, as the AMD profile does, plus AITER_BF16_FP8_MOE_BOUND=0 and AITER_FLYDSL_STAGE2_FP8=1. The SiTUv2 path does not read AITER_BF16_FP8_MOE_BOUND. AITER_FLYDSL_STAGE2_FP8=1 makes the FlyDSL MoE down projection write its per-route outputs as MXFP8 (FP8 values with E8M0 scales) instead of BF16 before they are summed. It affects only the target's MoE layers; the DSpark draft's MLP is dense.
QuickReduce. The lane also enables INT4 QuickReduce (VLLM_ROCM_QUICK_REDUCE_QUANTIZATION=INT4) with VLLM_ROCM_QUICK_REDUCE_MIN_SIZE_BYTES_MB=16, so only tensor-parallel all-reduces of 16 MiB or more use the INT4 codec. INT4 all-reduce is lossy, so it is not in the builder. The DSpark draft's all-reduces carry only its draft-step tokens (at most 600 tokens × 7,168 BF16 values, about 8.2 MiB, at concurrency 60), and its context projection has no all-reduce, so the 16 MiB floor keeps the draft out of the INT4 path in every lane cell.
The lane's ten per-concurrency cells, with DCP8, the lane's draft and the qualified image, are the AgentX cells preset.
The lane's ten per-concurrency cells on one MI355X node: TP8 with DCP8 and the DSpark draft, GPU-resident or with host KV offload.
10 points · concurrency 1–60 · image vllm/vllm-openai-rocm:nightly-rocm100-df6603f0583375f244d1ac6d36265462b7490392
RunbookReproducing one lane cell · Throughput measurement only (synthetic acceptance) · Evidence and limits
The single-node MI355X TP8 AgentX configuration follows InferenceX #4473: its master entry and lane recipe at 8737db4. It serves text with tool-call and reasoning parsers, using DCP8/A2A at every point. There is no concurrency-2 point.
Per-concurrency tuning. The Point row renders the lane's ten cells. Every cell requests FULL_AND_PIECEWISE with breakable CUDA graphs disabled (VLLM_USE_BREAKABLE_CUDAGRAPH=0); because Kimi-K3 has no torch.compile submodule, vLLM resolves that mode to FULL_DECODE_ONLY at load. The AgentX TP8 preset keeps the builder's general values for any concurrency.
Every cell adds kda_prefill_backend: fused in --additional-config. Only resident c1 also sets decode_backend: latency_specialized and VLLM_USE_V2_MODEL_RUNNER=1. On the pinned image, this MI355X specialized path requires TP8, V2, gfx950, no KV connector and BF16 target projections. It handles pure speculative-verification batches of at most eight rows (c1 with K7); other steps use the normal layers. These lane-specific settings stay out of generic MI355X defaults.
The resident profile keeps KV on GPU; offload uses SimpleCPUOffloadConnector with 224.875 GB of host DRAM per rank. --max-num-seqs is 2 at c4 in both profiles, then 6/10/16/24 at offload c10/c16/c24/c32. Resident c1 stays at 1; offload c48/c52/c60 stay at twice concurrency. These smaller c4–c32 batches target p90 interactivity and can trade off queueing, TTFT and end-to-end latency. Every cell captures every even CUDA-graph size from 2 through max-num-seqs × (1 + draft tokens), except c1 whose maximum remains 16. Agentic benchmark runs replace block rejection with the golden acceptance length; synthetic acceptance is not suitable for serving or accuracy evaluation.
Image. This preset pins the builder and the commands below to vllm/vllm-openai-rocm:nightly-rocm100-df6603f0583375f244d1ac6d36265462b7490392 (vLLM df6603f0), the image qualified by #4393, reused by #4473 and carrying #60683. The compatibility limits above refer to this pinned runtime.
Reproducing one lane cell
Run each cell in a fresh shell inside the pinned image. This real-verification form is for serving and accuracy evaluation; PROFILE distinguishes the two concurrency-4/DCP8 cells:
PROFILE=resident
CLIENTS=1
case "$PROFILE:$CLIENTS" in
resident:1) K=7; BATCHED=16384; OFFLOAD=0 ;;
resident:4) K=7; BATCHED=8192; OFFLOAD=0 ;;
offload:4) K=5; BATCHED=8192; OFFLOAD=1 ;;
offload:10|offload:16|offload:24|offload:32|offload:48|offload:52|offload:60) K=4; BATCHED=8192; OFFLOAD=1 ;;
*) echo "PROFILE=$PROFILE CLIENTS=$CLIENTS is not a lane cell" >&2; exit 1 ;;
esac
export VLLM_ROCM_USE_AITER=1
export SAFETENSORS_FAST_GPU=1
export VLLM_ROCM_USE_AITER_MOE_SITUV2=1
export AITER_BF16_FP8_MOE_BOUND=0
export AITER_FLYDSL_STAGE2_FP8=1
export VLLM_ROCM_AITER_MLA_ASM_PADDING=asm
export VLLM_ROCM_AITER_MLA_DCP_VERIFY=asm
export VLLM_USE_DIRECT_DCP_A2A=0
export VLLM_USE_DIRECT_DCP_Q_GATHER=0
export VLLM_USE_DIRECT_DCP_KV_GATHER=0
# Lossy INT4-quantized all-reduce for messages of 16 MiB or more: not in the builder.
export VLLM_ROCM_QUICK_REDUCE_QUANTIZATION=INT4
export VLLM_ROCM_QUICK_REDUCE_MIN_SIZE_BYTES_MB=16
# 1.56 TB of weights from shared storage, and long agentic prefills.
export VLLM_ENGINE_READY_TIMEOUT_S=7200
export VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS=1200
export PYTHONNOUSERSITE=1
# Required on the qualification nodes' MEC firmware 38; fixed in firmware 177.
export HSA_NO_SCRATCH_RECLAIM=1
GRAPH_MODE=FULL_AND_PIECEWISE
export VLLM_USE_BREAKABLE_CUDAGRAPH=0
MAX_SEQS=$((CLIENTS == 1 ? 1 : 2 * CLIENTS))
case "$CLIENTS" in
4) MAX_SEQS=2 ;;
10) MAX_SEQS=6 ;;
16) MAX_SEQS=10 ;;
24) MAX_SEQS=16 ;;
32) MAX_SEQS=24 ;;
esac
MAX_CAPTURE=$(((CLIENTS == 1 ? 2 : MAX_SEQS) * (1 + K)))
COMPILATION="{\"mode\":3,\"cudagraph_mode\":\"$GRAPH_MODE\",\"max_cudagraph_capture_size\":$MAX_CAPTURE,\"custom_ops\":[\"+fused_rms_norm_gated\"],\"cudagraph_capture_sizes\":[$(seq -s, 2 2 "$MAX_CAPTURE")]}"
EXTRA=(--additional-config '{"kda_prefill_backend":"fused"}')
if [ "$PROFILE:$CLIENTS" = resident:1 ]; then
export VLLM_USE_V2_MODEL_RUNNER=1
EXTRA=(--additional-config '{"decode_backend":"latency_specialized","kda_prefill_backend":"fused"}')
else
EXTRA+=(--quantization-config '{"targets":{"*in_proj_qkvgfab":"fp8_per_channel"}}')
fi
if [ "$K" -gt 0 ]; then
EXTRA+=(--speculative-config "{\"model\":\"Inferact/Kimi-K3-DSpark\",\"num_speculative_tokens\":$K,\"method\":\"dspark\",\"attention_backend\":\"ROCM_AITER_MLA\",\"kv_cache_dtype\":\"fp8\",\"draft_sample_method\":\"probabilistic\",\"rejection_sample_method\":\"block\"}")
fi
if [ "$OFFLOAD" -eq 1 ]; then
# The lane's node gives the connector 1799 GB of host DRAM (224875000000 bytes
# per rank); size cpu_bytes_to_use_per_rank to your host. The fixed hash seed
# makes identical prefixes map to the same block keys on every rank.
export PYTHONHASHSEED=42
EXTRA+=(--kv-transfer-config '{"kv_connector":"SimpleCPUOffloadConnector","kv_role":"kv_both","kv_connector_extra_config":{"cpu_bytes_to_use_per_rank":224875000000,"lazy_offload":false}}')
fi
vllm serve moonshotai/Kimi-K3 \
--trust-remote-code \
--moe-backend auto \
--tensor-parallel-size 8 \
--decode-context-parallel-size 8 \
--dcp-comm-backend a2a \
--gpu-memory-utilization 0.9 \
--language-model-only \
--max-num-seqs "$MAX_SEQS" \
--enable-auto-tool-choice \
--tool-call-parser kimi_k3 \
--reasoning-parser kimi_k3 \
--max-model-len 1048576 \
--enable-prefix-caching \
--kv-cache-dtype fp8 \
--stream-interval 10 \
--max-num-batched-tokens "$BATCHED" \
--attention-config '{"mla_prefill_backend":"ROCM_AITER_FA"}' \
--compilation-config "$COMPILATION" \
"${EXTRA[@]}"
Outside this preset, the generic builder's Simple option uses 220 GiB per rank and does not set PYTHONHASHSEED.
Throughput measurement only (synthetic acceptance)
The lane's throughput runs replace block verification with synthetic acceptance, so every concurrency point is measured at InferenceX's golden acceptance length for this draft. Only the --speculative-config changes:
--speculative-config "{\"model\":\"Inferact/Kimi-K3-DSpark\",\"num_speculative_tokens\":$K,\"method\":\"dspark\",\"attention_backend\":\"ROCM_AITER_MLA\",\"kv_cache_dtype\":\"fp8\",\"draft_sample_method\":\"probabilistic\",\"rejection_sample_method\":\"synthetic\",\"synthetic_acceptance_length\":$AL}"
| Draft tokens | Golden acceptance length | Lane cells |
|---|---|---|
| 7 | 3.84 | resident concurrency 1 and 4, DCP8 |
| 5 | 3.62 | offload concurrency 4, DCP8 |
| 4 | 3.36 | dcp8 concurrency 10, 16, 24, 32, 48, 52 and 60 |
These lengths come from kimik3_dspark_probabilistic_sample_method_block_rejection_sample_method.yaml: SPEED-Bench coding prompts with thinking on, measured on B300 for Inferact/Kimi-K3-DSpark with probabilistic drafting and block verification. They hold only for that draft at those draft lengths, not for RedHatAI/Kimi-K3-speculator.dspark. Synthetic acceptance skips real verification: never use it to serve users or to evaluate accuracy.
Evidence and limits
- InferenceX run 40132624130 passed all ten performance jobs at source
684d891612596e4ed9dbdd2f402865e288214815. These throughput measurements use synthetic acceptance. The selected matrix, image and fully bound lane configurations match8737db4; this is reused source-run evidence, not a rerun of that head. - The run's ten accuracy jobs use real verification and cover nine distinct lane cells: offload c4 was not evaluated, and c60 has both vendor and lm-eval jobs. See the accuracy collection. Performance collection and comparison were skipped in this unofficial run; no across-the-board throughput or latency gain is claimed, and these results have not been reproduced locally for this recipe.
- Model policy (vLLM as the native engine,
Inferact/Kimi-K3-DSparkas the agreed draft):MODELS.md.