Kimi-K3 on 16× H200 141GB: Deployment Report

#136
by local0state - opened

Successfully deployed moonshotai/Kimi-K3 (2.8T MoE, MXFP4) on 2× nodes (8× NVIDIA H200 141GB each) using SGLang.
This report documents what worked, what didn't, and measured performance — hopefully
it saves others some time, since most official recipes assume Blackwell (B300/GB300)
and the H200 path is less traveled.

TL;DR

Hardware 2 nodes × 8× H200 141GB
Engine that worked SGLang (lmsysorg/sglang:kimi-k3 image), TP16 + EP16, marlin MoE backend
Engine that didn't vLLM kimi-k3 branch (vllm/vllm-openai:kimi-k3) — weight-loading memory bug on H200, details below
Weights per GPU 102.75 GB (SGLang, close to the 1561GB/16 ≈ 98GB theoretical)
KV cache per GPU 7.95 GB pool, bf16, 308,608 tokens
Server startup (cold) 13 min 02 s end-to-end (11.2 min weight load + 39 s CUDA graphs)
TTFT (short prompt) 0.37–0.54 s
Single-request decode 16.8 tok/s
Aggregate throughput ~147 tok/s at 54 concurrent requests (scaling not yet saturated)

Setup

  • Model: moonshotai/Kimi-K3 (~1.56 TB, 96 safetensors shards)
  • Launch config: the official SGLang cookbook h200 / unified / balanced profile:
NCCL_MNNVL_ENABLE=1 NCCL_CUMEM_ENABLE=1 SGLANG_ENABLE_TP_MEMORY_INBALANCE_CHECK=0 \
sglang serve \
  --trust-remote-code \
  --model-path /path/to/Kimi-K3 \
  --tp-size 16 --nnodes 2 --node-rank <0|1> --dist-init-addr <head-ip>:20000 \
  --ep-size 16 \
  --moe-runner-backend marlin \
  --decode-attention-backend flashmla \
  --enable-symm-mem \
  --mem-fraction-static 0.85 \
  --reasoning-parser kimi_k3 --tool-call-parser kimi_k3 \
  --host 0.0.0.0 --port 30000

Two environment fixes were needed on top of the official recipe:

  1. Pin the cross-node NIC on every rank (NCCL_SOCKET_IFNAME / GLOO_SOCKET_IFNAME).
    Our nodes have different interface names, so I detect it at runtime from
    /sys/class/net instead of hardcoding. Without this, Gloo bootstrap fails.
  2. Move the Triton JIT cache off the shared filesystem
    (TRITON_CACHE_DIR=/tmp/triton-cache-rank$RANK). With $HOME on shared fs, 16 ranks
    JIT-compiling the same kernel race on the cache's tmp-file + rename pattern and
    crash with FileNotFoundError during CUDA graph capture.

Measured performance (SGLang, TP16+EP16, marlin, bf16 KV)

Generation benchmark, 512-token completions, reasoning_effort=low:

Concurrency Aggregate tok/s Per-request latency (p50 / max)
1 16.8 28.3 s
4 30.0 ~28 s
8 43.9 ~31 s
16 76.9 ~43 s
32 84.4 57 s / 89 s
54 146.9 60 s / 102 s
  • TTFT: 0.37–0.54 s (short prompts, warm server)
  • Throughput was still scaling at 54 concurrent (the highest CUDA-graph batch size
    captured in this config) — with a larger --cuda-graph-max-bs or the
    high-throughput profile, expect more.
  • Note: Kimi-K3 is always-thinking; per-request latency includes reasoning tokens.
    16.8 tok/s single-stream is the marlin dequant cost on Hopper — Blackwell with
    native MXFP4 paths will be substantially faster.

What didn't work: vLLM kimi-k3 branch on H200

I tried the vLLM route first (vllm/vllm-openai:kimi-k3, since K3 support has not
landed in main/nightly yet). On H200 it currently has a weight-loading memory bug
that makes 141GB GPUs infeasible regardless of parallelism:

Parallelism Expected weights/GPU Measured (Model loading took)
TP16 (2 nodes) ~98 GB 130.2 GB
TP32 (4 nodes) ~49 GB 127.3 GB
TP16 + EP16 ~98 GB 130.2 GB
TP8 + PP4 ~49 GB 47 GB (but PP crashes, see below)

Per-GPU weight memory does not decrease when doubling GPUs (130 → 127 GB from
TP16 to TP32), which points to a large component being replicated per-rank instead
of sharded in the K3 branch's loader. SGLang loading the same checkpoint with the
same marlin backend lands at 102.75 GB/GPU, confirming the checkpoint itself is fine.
With 130GB weights + runtime overhead, KV cache comes up 1–2 GB short even at
gpu-memory-utilization 0.97 and max-model-len 65536 (vLLM estimated max context
22016 — unusable).

Other H200-specific landmines hit on the vLLM branch, for the record:

  • VLLM_ENABLE_K3_LATENT_MOE_TAIL_FUSION=1 (recommended in the recipe's Hopper
    profile) raises ValueError: K3 latent-MoE tail fusion requires SM100. — it's
    Blackwell-only; don't set it on H200.
  • Pipeline parallel (TP8+PP4) loads fine but crashes in CUDA graph capture with
    RuntimeError: cp_world_size must be positive from the MLA attention path —
    PP appears unsupported on the K3 branch (the recipe also lists no PP strategy).
  • --load-format fastsafetensors stages one full 16GB shard per rank with
    double-buffering (
    33 GB transient per GPU) — combined with the inflated weights
    it OOMs during loading. With the EP loader path it was worse (127 GB at 91%
    loading progress on a config whose steady state should be ~49 GB).

Notes for anyone reproducing

  • 8× B300/GB300 remains the intended single-node target for K3. On Hopper you pay
    the marlin W4A16 dequant tax and need 2× 8-GPU nodes minimum.
  • The SGLang H200 profiles are marked verified: false / in-progress in the
    cookbook as of 2026-07-28, but the balanced profile worked as-is.
  • Multi-turn users: remember K3 requires passing back the full assistant message
    including reasoning_content and tool_calls, not just content.
  • Startup timeline (cold start, weights on shared fs with warm FS cache):
    process launch → distributed init + model build 25 s → weight load 669 s
    (
    1.56 TB, load_format=auto) → decode CUDA graph capture 39 s (11 batch
    sizes up to bs=54) → warmup → ready in ~13 min total.

Following are the 2 scripts I use (generated):

run_node_sglang.sh

#!/bin/bash
# 在容器内由 srun 每节点执行一次。依赖外部传入:
#   NODE0_IP, MODEL_PATH, HOST_PORT, TP_SIZE, EP_SIZE, NNODES, MEM_FRACTION
set -euo pipefail

NODE_RANK=$SLURM_PROCID

# 跨节点 NIC 必须在所有 rank 上显式指定 (SGLang H200 multiNodeHints 要求),
# 各节点网卡名不同, 运行时探测: 取默认路由所在接口。容器镜像可能无 ip 命令,
# 故从 /sys 读取而非依赖 iproute2。
detect_iface() {
    local best="" max=0 sz
    for d in /sys/class/net/*; do
        n=$(basename "$d")
        case "$n" in lo|docker*|veth*|br-*|virbr*) continue ;; esac
        [ -e "$d/device" ] || continue
        read -r sz < "$d/speed" 2>/dev/null || sz=0
        [ "$sz" -gt "$max" ] 2>/dev/null && { max=$sz; best=$n; }
        [ -z "$best" ] && best=$n
    done
    echo "$best"
}
IFACE=$(detect_iface)
LOCAL_IP=$(cat /sys/class/net/"$IFACE"/address >/dev/null 2>&1 && hostname -I | awk '{print $1}')

export NCCL_SOCKET_IFNAME="$IFACE"
export GLOO_SOCKET_IFNAME="$IFACE"
export SGLANG_HOST_IP="$LOCAL_IP"

# EFA: 用宿主机挂进来的 libfabric + efa
# export LD_LIBRARY_PATH to include efa related libs if needed
export FI_PROVIDER=efa
export FI_EFA_USE_DEVICE_RDMA=1

# Triton JIT 缓存放节点本地盘: 默认落到 $HOME 共享盘时, 多 rank 并发
# JIT 同一 kernel 的 tmp+rename 互踩, FileNotFoundError (job 36)
export TRITON_CACHE_DIR="/tmp/triton-cache-rank${NODE_RANK}"

# SGLang H200 unified/balanced 档指定的 env
export NCCL_MNNVL_ENABLE=1
export NCCL_CUMEM_ENABLE=1
export SGLANG_ENABLE_TP_MEMORY_INBALANCE_CHECK=0

echo "[rank ${NODE_RANK}] host=$(hostname) iface=${IFACE} ip=${LOCAL_IP}"

exec sglang serve \
    --trust-remote-code \
    --model-path "$MODEL_PATH" \
    --tp-size "$TP_SIZE" \
    --nnodes "$NNODES" \
    --node-rank "$NODE_RANK" \
    --dist-init-addr "${NODE0_IP}:20000" \
    --ep-size "$EP_SIZE" \
    --moe-runner-backend marlin \
    --decode-attention-backend flashmla \
    --enable-symm-mem \
    --mem-fraction-static "$MEM_FRACTION" \
    --reasoning-parser kimi_k3 \
    --tool-call-parser kimi_k3 \
    --host 0.0.0.0 \
    --port "$HOST_PORT"

serve_2node_sglang.slurm

#!/bin/bash
#SBATCH --job-name=k3-sgl
#SBATCH --partition=p5e
#SBATCH --nodes=2
#SBATCH --ntasks-per-node=1
#SBATCH --gpus-per-node=8
#SBATCH --cpus-per-task=96
#SBATCH --mem=0
#SBATCH --exclusive
#SBATCH --time=7-00:00:00
#SBATCH --output=/home/ubuntu/workspace/kimi-k3-deploy/logs/sgl_%j.out
#SBATCH --error=/home/ubuntu/workspace/kimi-k3-deploy/logs/sgl_%j.err

###############################################################################
# Kimi-K3 —— SGLang 引擎
#
# 参数全部取自 SGLang 官方 cookbook 的 h200 / unified / balanced 档
# (docs.sglang.io/cookbook/autoregressive/Moonshotai/Kimi-K3.md):
#   TP16 + EP16, moe-runner-backend marlin, decode-attention-backend flashmla,
#   --enable-symm-mem, --mem-fraction-static 0.85
# H200 官方节点数就是 2×8, 与 vLLM 侧 job 31 同规模, 便于对比。
#
# 注意 (相对 vLLM 侧的已知情况):
#   - SGLang H200 档同样用 marlin, 所以每卡权重大概率仍是 ~130GB
#     (H200 无原生 MXFP4 硬件, 必须重打包成 W4A16, 体积涨 ~33%)
#   - 官方 mem-fraction-static 只有 0.85, 比我vLLM 侧的 0.97 保守很多;
#     若 KV cache 不够, 先往上调这个值, 再考虑降 context
#   - 三档 H200 配置在官方文档里均为 verified:false / in-progress,
#     成熟度与 vLLM recipe 的 H200 相当, 不是"更稳"的选择
#
###############################################################################

set -euo pipefail

export MODEL_PATH="/home/ubuntu/models/Kimi-K3"
export HOST_PORT=30000
export TP_SIZE=16
export EP_SIZE=16
export NNODES=2
export MEM_FRACTION=0.85

IMAGE="docker://lmsysorg/sglang:kimi-k3"
RUN_SCRIPT=/home/ubuntu/workspace/kimi-k3-deploy/sglang/run_node_sglang.sh

HEAD_NODE=$(scontrol show hostnames "$SLURM_JOB_NODELIST" | head -n1)
export NODE0_IP=$(srun --nodes=1 --ntasks=1 -w "$HEAD_NODE" hostname -i | awk '{print $1}')

echo "============================================"
echo "Kimi-K3 SGLang 2-node TP16+EP16 (p5e)"
echo "Nodes: $SLURM_JOB_NODELIST"
echo "Head:  $HEAD_NODE ($NODE0_IP)"
echo "API:   http://${NODE0_IP}:${HOST_PORT}/v1"
echo "============================================"

srun --container-image="$IMAGE" \
     --container-mounts=/home:/home \
     --export=ALL \
     bash "$RUN_SCRIPT" \
     2>&1 | tee /home/ubuntu/workspace/kimi-k3-deploy/logs/sgl_server_${SLURM_JOB_ID}.log
local0state changed discussion title from Kimi-K3 on AWS EC2 P5e (16× H200 141GB): Deployment Report to Kimi-K3 on 16× H200 141GB: Deployment Report
local0state changed discussion status to closed

Hi @local0state , I see that you closed the discussion. Have you found a solution to get Kimi K3 working with vLLM with 2x8 H200 ?

@mateo-hug I don't think we have a solution at our user side, have to wait for vLLM to fix the memory leakage. Haven't tested afterwards, don't know if it's fixed or not yet.

Sign up or log in to comment