Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
b4fd077
feat: add Kimi-K3 MXFP4 B200 aggregated TP8xPP2 Dynamo-vLLM agentic r…
functionstackx Jul 27, 2026
4dbbdc8
docs: link PR #2355 in changelog entry and MODELS rows
functionstackx Jul 27, 2026
e8d42a7
fix: pin agentic srt-slurm to NVIDIA v1.0.36, dynamo 1.2.1, conc-8 sm…
functionstackx Jul 27, 2026
c1e2a56
fix: drop OpenAI-frontend tool-choice flags from dynamo-vllm worker args
functionstackx Jul 27, 2026
ef35fd1
fix: try dynamo wheel 1.2.0.dev20260426 for Kimi-K3 frontend tokenizer
functionstackx Jul 27, 2026
be6c56e
fix: pin dynamo to day-zero Kimi-K3 commit, restore kimi_k3 parser flags
functionstackx Jul 27, 2026
c6917e6
fix: use dynamo namespaced --dyn-* kimi_k3 parser args on the worker
functionstackx Jul 27, 2026
a26853a
fix: disable aiperf conv-aware routing (session_control 400-rejected)
functionstackx Jul 27, 2026
f61eafb
fix: patch kimi-k3 image mamba_hybrid index_fill_ dtype via setup_script
functionstackx Jul 27, 2026
8fd319c
feat: agentic experiment D — direct vllm serve via srt-slurm PR #278
functionstackx Jul 27, 2026
c0ace4d
docs: point changelog and MODELS rows at experiment PR #2359
functionstackx Jul 27, 2026
862024d
fix: drop gpu-memory-utilization to 0.90 (flashinfer MoE workspace OOM)
functionstackx Jul 27, 2026
4370988
fix: expandable_segments allocator, drop NCCL_CUMEM_ENABLE (prefill OOM)
functionstackx Jul 27, 2026
0c5fe11
Revert "fix: expandable_segments allocator, drop NCCL_CUMEM_ENABLE (p…
functionstackx Jul 27, 2026
e675dd2
feat: add VLLM_PREFIX_CACHE_RETENTION_INTERVAL=32768
functionstackx Jul 27, 2026
4b0c3a4
feat: widen agentic conc list to 1/8/16/32
functionstackx Jul 27, 2026
479b74b
fix: remove VLLM_PREFIX_CACHE_RETENTION_INTERVAL (K3 scheduler block)
functionstackx Jul 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion MODELS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ This document tracks every model benchmarked by InferenceX-e2e: when it was adde
| Model architecture class | Prefix | Date added | Active scenarios | Deprecated scenarios |
|---|---|---|---|---|
| Qwen3.8 2.4T | `qwen3.8` | TBD | Agentic coding | |
| Kimi-K3 | `kimik3` | 2026-07-27 | Agentic coding | |
| Kimi-K3 | `kimik3` | 2026-07-27 ([#2359](https://github.com/SemiAnalysisAI/InferenceX/pull/2359)) | Agentic coding | |
| GLM-5.2 | `glm5.2` | 2026-07-18 ([#2268](https://github.com/SemiAnalysisAI/InferenceX/pull/2268)) | Agentic coding | |
| MiniMax-M3 | `minimaxm3` | 2026-06-12 ([#1724](https://github.com/SemiAnalysisAI/InferenceX/pull/1724)) | Single-turn 8k1k, Agentic coding | Single-turn 1k1k |
| DeepSeek-V4-Pro | `dsv4` | 2026-04-24 ([#1130](https://github.com/SemiAnalysisAI/InferenceX/pull/1130)) | Single-turn 8k1k, Agentic coding | Single-turn 1k1k |
Expand Down
2 changes: 1 addition & 1 deletion MODELS_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
| 模型架构类别 | 前缀 | 加入日期 | 启用场景 | 已弃用场景 |
|---|---|---|---|---|
| Qwen3.8 2.4T | `qwen3.8` | 待定 | 智能体编码 | |
| Kimi-K3 | `kimik3` | 2026-07-27 | 智能体编码 | |
| Kimi-K3 | `kimik3` | 2026-07-27 ([#2359](https://github.com/SemiAnalysisAI/InferenceX/pull/2359)) | 智能体编码 | |
| GLM-5.2 | `glm5.2` | 2026-07-18([#2268](https://github.com/SemiAnalysisAI/InferenceX/pull/2268)) | 智能体编码 | |
| MiniMax-M3 | `minimaxm3` | 2026-06-12([#1724](https://github.com/SemiAnalysisAI/InferenceX/pull/1724)) | 单轮 8k1k、智能体编码 | 单轮 1k1k |
| DeepSeek-V4-Pro | `dsv4` | 2026-04-24([#1130](https://github.com/SemiAnalysisAI/InferenceX/pull/1130)) | 单轮 8k1k、智能体编码 | 单轮 1k1k |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/bash
# Setup script for the Kimi-K3 vLLM bring-up image (vllm/vllm-openai:kimi-k3).
# srt-slurm runs this in every worker container before dynamo install and
# worker startup (recipe field: setup_script).

set -euo pipefail

# The image's first decode step crashes in the KDA hybrid-state postprocess:
# vllm/v1/worker/gpu/model_states/mamba_hybrid.py, postprocess_state:
# IndexError: index_fill_(): Expected dtype int64 for index.
# torch's index_fill_ requires an int64 index tensor, but the runner passes
# the int32 idx_mapping (hit by moonshotai/Kimi-K3 agentic bring-up, first
# decode step, engine v0.1.dev19262+gb6bbf29dd). Coerce the index to int64.
# Idempotent: exits 0 if the patch is already applied.
python3 - <<'PY'
import pathlib
import re

import vllm.v1.worker.gpu.model_states.mamba_hybrid as mh

path = pathlib.Path(mh.__file__)
src = path.read_text()
if "idx_mapping.long()" in src:
print(f"mamba_hybrid index_fill_ patch already applied: {path}")
raise SystemExit(0)

new, n = re.subn(
r"index_fill_\(\s*0,\s*idx_mapping,",
"index_fill_(0, idx_mapping.long(),",
src,
)
if n != 1:
raise SystemExit(
f"expected exactly one index_fill_(0, idx_mapping, ...) call in "
f"{path}, found {n} — image layout changed, refusing to patch"
)
path.write_text(new)
print(f"Patched mamba_hybrid index_fill_ index dtype: {path}")
PY
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
diff --git a/src/srtctl/backends/vllm.py b/src/srtctl/backends/vllm.py
index 74f673b..377606a 100644
--- a/src/srtctl/backends/vllm.py
+++ b/src/srtctl/backends/vllm.py
@@ -716,25 +716,31 @@ class VLLMProtocol:
if frontend_type == "vllm":
if mode != "agg":
raise ValueError("frontend.type: vllm supports aggregate vLLM jobs only")
- if is_multi_node:
- raise ValueError("frontend.type: vllm currently supports single-node aggregate jobs only")

config.pop("host", None)
config.pop("port", None)
config.pop("connector", None)
config.setdefault("served-model-name", served_model_name)

- cmd.extend(
- [
- "vllm",
- "serve",
- model_arg,
- "--host",
- "0.0.0.0",
- "--port",
- str(runtime.frontend_port),
- ]
- )
+ node_rank = endpoint_nodes.index(process.node)
+ cmd.extend(["vllm", "serve", model_arg])
+ if node_rank == 0:
+ cmd.extend(["--host", "0.0.0.0", "--port", str(runtime.frontend_port)])
+ if is_multi_node:
+ # vLLM-native multi-node serve (torchrun-style): the leader owns
+ # the OpenAI server; other node ranks run headless engine workers.
+ cmd.extend(
+ [
+ "--master-addr",
+ leader_ip,
+ "--nnodes",
+ str(len(endpoint_nodes)),
+ "--node-rank",
+ str(node_rank),
+ ]
+ )
+ if node_rank > 0:
+ cmd.append("--headless")
if not self.set_cuda_visible_devices:
device_ids = ",".join(str(i) for i in sorted(process.gpu_indices))
if device_ids:
diff --git a/src/srtctl/core/schema.py b/src/srtctl/core/schema.py
index 1263ddc..0ef7ae4 100644
--- a/src/srtctl/core/schema.py
+++ b/src/srtctl/core/schema.py
@@ -1587,8 +1587,6 @@ class SrtConfig:
raise ValidationError("frontend.type: vllm supports aggregate jobs only, not disaggregated layouts")
if self.resources.num_agg < 1:
raise ValidationError("frontend.type: vllm requires resources.agg_workers >= 1")
- if (self.resources.agg_nodes or 1) != 1:
- raise ValidationError("frontend.type: vllm currently supports single-node aggregate jobs only")

def _validate_het_jobs(self):
"""When ``resources.het_jobs`` is set to True, enforce supported shape.
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
name: "kimik3-vllm-agg-b200-tp8pp2-agentic"

# Kimi-K3 MXFP4 B200 AGGREGATED TP8 x PP2 agentic recipe (2 nodes / 16 GPUs).
# The native MXFP4 checkpoint (2.8T total params, ~1.4TB of weights) does not
# fit one 8xB200 node, so TP8 shards attention/dense (/8) and PP2 splits the
# 93 layers (/2) across 16 GPUs. Plain TP (NOT TEP): expert parallelism is
# deliberately off, so the 896 routed experts are TP-sharded inside each
# pipeline stage. Node allocation = tp*pp/gpus_per_node = 8*2/8 = 2 nodes.
# Aggregated (single worker, decode num-worker 0) — no P/D split, no NIXL.
# VLLM_ENABLE_K3_LATENT_MOE_TAIL_FUSION fuses the K3 LatentMoE tail path in
# the kimi-k3 bring-up image.
model:
path: "kimik3"
container: "vllm/vllm-openai:kimi-k3"
precision: "fp4"

identity:
model:
repo: "moonshotai/Kimi-K3"
container:
image: "vllm/vllm-openai:kimi-k3"

# Direct vLLM serving (frontend.type: vllm, srt-slurm PR #278 + the
# InferenceX multinode patch): `vllm serve` owns the OpenAI port itself, so
# no Dynamo frontend/worker is involved and no dynamo install is needed.
dynamo:
install: false

# Patches the image's mamba_hybrid postprocess_state: torch index_fill_
# requires an int64 index but the runner passes the int32 idx_mapping,
# crashing the first decode step (IndexError: Expected dtype int64 for index).
setup_script: kimi-k3-container-deps.sh

slurm:
time_limit: "8:00:00"

health_check:
interval_seconds: 10
max_attempts: 1440
Comment on lines +37 to +39

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 The launcher's pre-existing sed at runners/launch_b200-dgxc.sh:300 (sed -i 's/^ max_attempts: [0-9]*/ max_attempts: 720/') unconditionally rewrites this recipe's health_check.max_attempts: 1440 (4h) down to 720 (2h) before every srtctl apply, since the recipe line matches the 2-space-indent regex. This is pre-existing launcher behavior surfaced by this PR's recipe, not a bug the PR introduces — and in this specific recipe it's inert since VLLM_ENGINE_READY_TIMEOUT_S: 3600 (1h) already caps engine startup well below even the clobbered 2h health-check window, so no premature abort is possible today. Still worth a follow-up so a future recipe that actually needs >2h isn't silently bitten (e.g. bump the sed's floor or make it a max()-style clamp).

Extended reasoning...

What happens: runners/launch_b200-dgxc.sh line ~300 runs, unconditionally on every multinode srtctl apply on this launcher:

sed -i 's/^  max_attempts: [0-9]*/  max_attempts: 720/' "${CONFIG_FILE%%:*}"

The new recipe agg-b200-tp8pp2-agentic.yaml sets health_check.max_attempts: 1440 with exactly 2-space indentation. The regex ^ max_attempts: [0-9]* matches that line (correct indent, [0-9]* matches 1440) and rewrites it to 720. So the recipe author's deliberately-sized 4-hour health-check budget (documented in the recipe's own comments as sized for the ~1.4TB MXFP4 checkpoint loading off shared Lustre across 2 nodes) silently becomes 2 hours at apply time. This is confirmed by three independent verifiers reading the same launcher code and recipe file — the clobber mechanism itself is not in question.

Why this doesn't actually cause the described harm here: the reported failure mode was 'health check declares the job dead and aborts mid weight-load on a cold cache.' But this recipe also sets VLLM_ENGINE_READY_TIMEOUT_S: \"3600\" (1 hour) in backend.aggregated_environment, with a comment stating this env var is the effective weight-load budget ('keep the engine-ready window generous'). Walking through the cases:

  • If weight loading finishes within the 1h engine-ready window, the vLLM /health endpoint comes up well inside even the clobbered 720-attempt (2h) ceiling — 720 vs. 1440 never matters.
  • If loading exceeds 1h, VLLM_ENGINE_READY_TIMEOUT_S=3600 fires first and the vLLM engine itself aborts at the 1-hour mark — again independent of whether the health-check ceiling is 2h or 4h, since 1h < 2h < 4h in both cases.

So the binding constraint on this recipe's cold-cache load time is the 1-hour engine-ready timeout, which sits comfortably below even the clobbered 2-hour health-check budget. The 1440→720 rewrite has no observable effect on this recipe's behavior today, and 720 attempts is also the value this same launcher already uses successfully for DSR1-FP8's ~680GB checkpoint.

Why it's still worth flagging (nit, not blocking): the recipe author explicitly set 1440 believing it would take effect, and it silently doesn't — that's a genuine, misleading gotcha for whoever revisits this recipe later (e.g. to widen the concurrency curve or bump VLLM_ENGINE_READY_TIMEOUT_S past 2h for a future larger checkpoint). At that point the same sed would silently reintroduce a real spurious-abort risk with no error or warning. The fix is cheap and low-risk: either raise the sed's forced floor (e.g. to 1440) or change it to a max()-style clamp (only bump up, never down) so it can never silently shrink a value a recipe author intentionally set higher.

Step-by-step proof of the clobber (not of harm):

  1. Recipe file contains the line health_check:\n max_attempts: 1440 (2-space indent under health_check:).
  2. Launcher clones/applies the recipe, then unconditionally runs sed -i 's/^ max_attempts: [0-9]*/ max_attempts: 720/' "${CONFIG_FILE%%:*}" on the resolved config path before srtctl apply.
  3. The regex anchors on exactly 2 leading spaces, which matches this recipe's indentation; [0-9]* greedily matches 1440.
  4. Post-sed, the file on disk contains max_attempts: 720, and srtctl apply reads that rewritten value — the 1440 the author wrote in source never reaches the running job.
  5. Given VLLM_ENGINE_READY_TIMEOUT_S=3600 in the same recipe, the 720-attempt (7200s) window is never the limiting factor in practice, so no user-visible regression results from this specific PR.


resources:
gpu_type: "b200"
gpus_per_node: 8
agg_nodes: 2
agg_workers: 1
gpus_per_agg: 16

infra:
etcd_nats_dedicated_node: false
nats_max_payload_mb: 32

frontend:
# Direct vLLM OpenAI server (srt-slurm PR #278): the vllm serve leader owns
# the public port; rank-1 runs a headless engine worker (vLLM-native
# multi-node TP8xPP2 via --master-addr/--nnodes/--node-rank, enabled by
# patches/srt-slurm-pr278-direct-vllm-multinode.patch).
type: vllm
enable_multiple_frontends: false

backend:
type: vllm
connector: null
aggregated_environment:
VLLM_ENABLE_K3_LATENT_MOE_TAIL_FUSION: "1"
VLLM_SERVER_DEV_MODE: "1"
# ~1.4TB of MXFP4 weights off shared Lustre: keep the engine-ready window
# generous, and let one long AgentX request hold a PP stage beyond vLLM's
# 300-second model-execution default.
VLLM_ENGINE_READY_TIMEOUT_S: "3600"
VLLM_EXECUTE_MODEL_TIMEOUT_SECONDS: "1800"
# No VLLM_PREFIX_CACHE_RETENTION_INTERVAL: the GB200/GB300 AgentX value
# (32768) hard-fails engine init on Kimi-K3 — the KDA hybrid gives it a
# scheduler_block_size of 3145728 and the interval must be a multiple of
# it ("VLLM_PREFIX_CACHE_RETENTION_INTERVAL (32768) must be non-negative
# and a multiple of scheduler_block_size (3145728)"). Default retention
# served fine in earlier runs.
NCCL_CUMEM_ENABLE: "1"
TILELANG_CLEANUP_TEMP_FILES: "1"
UCX_MEMTYPE_CACHE: "n"
UCX_MEMTYPE_REG_WHOLE: "n"
UCX_NET_DEVICES: "mlx5_0:1,mlx5_1:1,mlx5_2:1,mlx5_3:1,mlx5_4:1,mlx5_5:1,mlx5_10:1,mlx5_11:1"
HF_HUB_CACHE: "/hf_hub_cache"
HUGGINGFACE_HUB_CACHE: "/hf_hub_cache"
vllm_config:
aggregated:
served-model-name: "moonshotai/Kimi-K3"
tensor-parallel-size: 8
pipeline-parallel-size: 2
trust-remote-code: true
load-format: fastsafetensors
moe-backend: auto
# 0.90, not 0.95: the flashinfer trtllm MXFP4 MoE kernel allocates a
# ~1.6 GiB runtime workspace OUTSIDE vLLM's memory pool on the first
# forward; at 0.95 a 178 GiB B200 has only ~1.35 GiB free and the first
# warmup request OOMs (seen on the dynamo-frontend variants). 0.90
# matches the GB200/GB300 agentic recipes.
gpu-memory-utilization: 0.90
no-enable-flashinfer-autotune: true
# kimi_k3 parsers via the native vllm serve OpenAI-frontend flags —
# legitimate here because this recipe serves directly with vllm serve
# (frontend.type: vllm), not through the dynamo worker entrypoint that
# rejects them.
enable-auto-tool-choice: true
tool-call-parser: kimi_k3
reasoning-parser: kimi_k3
# No explicit max-model-len: let vLLM derive the native 1M window from
# the model config (agentic trajectories blow past any small cap, and
# K3's KDA layers keep per-token KV small — only the 24 gated-MLA
# layers hold cache). Prefix caching stays on (default) for trajectory
# reuse. Cap prefill chunks so a single long request cannot OOM a
# pipeline stage; let vLLM pick max-num-seqs.
max-num-batched-tokens: 8192

sbatch_directives:
segment: "1"

srun_options:
container-remap-root: ""

benchmark:
type: custom
command: bash /infmax-workspace/benchmarks/multi_node/agentic_srt.sh
env:
INFMAX_CONTAINER_WORKSPACE: "/infmax-workspace"
RESULT_DIR: "/logs/agentic"
PORT: "8000"
# Keep the aggregate worker in the multinode result schema so ingestion
# uses the zero decode-worker count instead of duplicating TP into P and D.
IS_MULTINODE: "true"
# aiperf's conv-aware routing emits nvext.session_control, a removed POC
# field this dynamo build 400-rejects at warmup (schema moved to
# router/routing_constraints/agent_hints). Same opt-out as the GB300
# aggregate AgentX recipes — and with a single aggregate worker there is
# no P/D routing to bind anyway.
AIPERF_USE_DYNAMO_CONV_AWARE_ROUTING: "0"
AIPERF_DATASET_MMAP_CACHE_DIR: "/aiperf_mmap_cache"
HF_HUB_CACHE: "/hf_hub_cache"
WEKA_LOADER_OVERRIDE: "semianalysis_cc_traces_weka_062126"
Comment on lines +129 to +138

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 This recipe is the first to pair the shared agentic_srt.sh inter-concurrency drain wait (which polls for the Dynamo-only metric dynamo_frontend_active_requests) with a no-Dynamo, direct-vllm serve frontend that never emits it, so the idle check is vacuously true from the first poll. It's dormant today because conc-list is a single point [8] (the drain wait only runs between points), but the recipe's own comment says the curve will be widened later — at that point later concurrency points would start against a still-draining server with no error surfaced. Worth wiring AIPERF_SERVER_METRICS_URLS or a vllm:* frontend check before widening the curve.

Extended reasoning...

benchmarks/multi_node/agentic_srt.sh's wait_for_agentic_servers_idle (used between concurrency points, per benchmark.command in this recipe) determines drain by polling the frontend's /metrics for dynamo_frontend_active_requests and, as a fallback, worker URLs from AIPERF_SERVER_METRICS_URLS for vllm:num_requests_running/vllm:num_requests_waiting. This recipe sets dynamo.install: false and frontend.type: vllm, which (via srt-slurm PR #278 + the multinode patch in this PR) runs a direct vllm serve OpenAI server. That server exposes vllm:* metrics, never dynamo_frontend_active_requests — a Dynamo-frontend-only metric name that a repo-wide grep finds nowhere else. metric_sum() (agentic_srt.sh:59-68) returns 0.0 for an absent metric name rather than raising, so frontend_active is silently always 0.\n\nThe fallback path is also dead here: AIPERF_SERVER_METRICS_URLS is exported only by the AMD path (benchmarks/multi_node/amd_utils/server_sglang.sh) and is never set anywhere in this recipe, its benchmark.env, or runners/launch_b200-dgxc.sh. So worker_urls is empty and worker_active also stays 0.0. The idle predicate frontend_active == 0 and worker_active == 0 (agentic_srt.sh:85) is therefore vacuously true from the very first successful poll; after three consecutive polls (~20-30s at the default 10s cadence) it declares the servers idle and returns — regardless of whether the server actually still has requests in flight. This fails silently: no exception, no warning surfaced to the sweep log beyond the routine per-poll status line.\n\nStep-by-step proof of the gap:\n1. Concurrency point N finishes its aiperf profile run and the driver calls wait_for_agentic_servers_idle (agentic_srt.sh:118-119, only when index < len(CONCURRENCIES)-1).\n2. The Python poller fetches http://localhost:/metrics from the direct vllm serve frontend and searches for a line matching dynamo_frontend_active_requests — none exists, so metric_sum returns 0.0.\n3. worker_urls is empty (parsed from the unset AIPERF_SERVER_METRICS_URLS), so the worker-metric loop never executes and worker_active stays 0.0.\n4. frontend_active == 0 and worker_active == 0 is true on poll 1 (and every poll), so idle_polls reaches 3 after ~30s and the function returns via SystemExit(0) — regardless of real server load.\n5. The driver immediately proceeds to build and launch concurrency point N+1's replay against a server that, absent this check, could still be finishing point N's in-flight requests.\n\nThis is provably inert in the PR as shipped: configs/nvidia-master.yaml's new kimik3-fp4-b200-dynamo-vllm-agentic entry sets conc-list: [8], a single-element list, so index < len-1 is 0 < 0 = false and wait_for_agentic_servers_idle is never invoked at all in this run — the refutation on this point is correct and I'm not disputing it. But the recipe's own comment explicitly states intent to 'widen the conc curve once the topology is proven green,' and this PR is what first pairs the Dynamo-oriented drain logic in agentic_srt.sh with a direct-vLLM frontend that can never satisfy it. The moment a second concurrency point is added — likely the very next iteration on this recipe — every point after the first would start against a server the drain wait incorrectly reports as idle, silently corrupting per-concurrency latency/throughput measurements with no error to flag the run as suspect.\n\nFix: before widening conc-list beyond one point, either set AIPERF_SERVER_METRICS_URLS to the frontend's own /metrics endpoint (so the existing vllm:num_requests_running/waiting check picks up real load), or add a frontend-side check for those same vllm:* metric names alongside dynamo_frontend_active_requests in wait_for_agentic_servers_idle. Since the shipped single-point config never executes the buggy path, this doesn't block merge of the bring-up PR — it's a heads-up to fix before the planned curve-widening follow-up.

45 changes: 45 additions & 0 deletions configs/nvidia-master.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8325,3 +8325,48 @@ qwen3.5-fp8-gb200-dynamo-sglang-mtp:
tp: 16
ep: 16
dp-attn: true

# Kimi-K3 MXFP4 B200 aggregated vLLM via Dynamo (TP8 x PP2, 2 nodes / 16
# GPUs), agentic bring-up. The native MXFP4 checkpoint (2.8T total params,
# ~1.4TB weights) does not fit one 8xB200 node, so TP8 shards attention/dense
# and PP2 splits layers. Plain TP (NOT TEP): ep 1, no expert parallelism —
# the 896 routed experts are TP-sharded within each pipeline stage. Node
# count = tp*pp/gpus_per_node = 8*2/8 = 2. Aggregated (prefill num-worker 1 +
# decode num-worker 0, RECIPES.md section 5) — the single worker serves both
# phases, so no P/D KV transfer. Dedicated kimi-k3 vLLM bring-up image with
# VLLM_ENABLE_K3_LATENT_MOE_TAIL_FUSION=1 and the kimi_k3 tool-call/reasoning
# parsers.
# Recipe: benchmarks/multi_node/srt-slurm-recipes/vllm/kimi-k3/agentic/agg-b200-tp8pp2-agentic.yaml
kimik3-fp4-b200-dynamo-vllm-agentic:
image: vllm/vllm-openai:kimi-k3
model: moonshotai/Kimi-K3
model-prefix: kimik3
runner: cluster:b200-dgxc
precision: fp4
# framework stays dynamo-vllm for launcher routing, but this variant serves
# DIRECTLY with vllm serve (srt-slurm PR #278 frontend.type: vllm + the
# InferenceX multinode patch) — no dynamo frontend/worker/router involved.
framework: dynamo-vllm
multinode: true
disagg: false
scenarios:
agentic-coding:
- search-space:
- spec-decoding: none
conc-list: [1, 8, 16, 32]
prefill:
num-worker: 1
tp: 8
pp: 2
ep: 1
dp-attn: false
additional-settings:
- "CONFIG_FILE=recipes/vllm/kimi-k3/agentic/agg-b200-tp8pp2-agentic.yaml"
# The aggregate worker also performs decode; keep the decode worker
# count at zero so result aggregation counts the 16 GPUs only once.
decode:
num-worker: 0
tp: 8
pp: 2
ep: 1
dp-attn: false
14 changes: 14 additions & 0 deletions perf-changelog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5116,3 +5116,17 @@
description:
- "Bump image from lmsysorg/sglang:v0.5.14-rocm720-mi35x to lmsysorg/sglang-rocm:v0.5.16-rocm720-mi35x-20260726"
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2349

- config-keys:
- kimik3-fp4-b200-dynamo-vllm-agentic
description:
- "Add Kimi-K3 MXFP4 B200 aggregated multinode Dynamo-vLLM agentic-coding bring-up (new model on B200; first kimik3 benchmark config)"
- "Aggregated TP8 x PP2 across 2 B200 nodes (16 GPUs), plain TP (NOT TEP: ep 1, no enable-expert-parallel) — the native MXFP4 checkpoint (2.8T total params, ~1.4TB weights) does not fit one 8xB200 node, so TP8 shards attention/dense and PP2 splits the 93 layers. Aggregated mode (prefill num-worker 1 + decode num-worker 0, RECIPES.md section 5): one worker serves prefill and decode, no P/D KV transfer"
- "Dedicated bring-up image vllm/vllm-openai:kimi-k3 with VLLM_ENABLE_K3_LATENT_MOE_TAIL_FUSION=1 (fuses the K3 LatentMoE tail path), --load-format fastsafetensors, --moe-backend auto, --gpu-memory-utilization 0.90 (0.95 OOMs: the flashinfer trtllm MXFP4 MoE kernel allocates a ~1.6 GiB runtime workspace outside vLLM's pool on the first forward), --no-enable-flashinfer-autotune, --trust-remote-code, --enable-auto-tool-choice, --tool-call-parser kimi_k3, --reasoning-parser kimi_k3 (agentic experiment Variant D: the OpenAI-frontend parser flags are legitimate here because serving is DIRECT vllm serve, not the dynamo worker entrypoint that rejects them)"
- "No explicit max-model-len (vLLM derives the native 1M window from the model config; K3's KDA layers keep per-token KV small — only the 24 gated-MLA layers hold cache), prefix caching on for trajectory reuse, max-num-batched-tokens 8192 so a single long prefill cannot OOM a pipeline stage; conc 1/8/16/32"
- "DIRECT vLLM serving via srt-slurm PR #278 (kylliang/direct-aggregate-vllm, frontend.type: vllm): vllm serve owns the OpenAI port itself, removing the dynamo frontend/worker/router entirely (dynamo install: false) and with it the kimi_k3 tiktoken tokenizer gap that 404'd every request on dynamo <=1.2.1. PR #278 validates single-node only, so patches/srt-slurm-pr278-direct-vllm-multinode.patch extends it to vLLM-native multi-node serve (--master-addr/--nnodes/--node-rank, headless non-leader ranks) for the 2-node TP8xPP2 topology"
- "Model pre-staged at /lustre/fsw/models/Kimi-K3 (moonshotai/Kimi-K3); launcher launch_b200-dgxc.sh gains the kimik3/fp4 model-path mapping, pins the agentic srt-slurm base to upstream NVIDIA/srt-slurm v1.0.36 (validated in #2302/#2341; replaces the cquil11/srt-slurm-nv fork branch whose older srtctl schema rejects newer recipe fields such as benchmark.aiperf_server_metrics), overlays the kimi-k3 agentic recipes onto the clone, and adds the agentic default_mounts (/aiperf_mmap_cache, /hf_hub_cache) already used by the GB200/GB300 agentic paths"
- "aiperf conv-aware routing disabled (AIPERF_USE_DYNAMO_CONV_AWARE_ROUTING=0, same opt-out as the GB300 aggregate AgentX recipes): aiperf's nvext.session_control is a removed POC field this dynamo build 400-rejects at warmup (fifth sweep attempt: tokenizer registered, engine served, all 5 warmup requests 400'd); with a single aggregate worker there is no P/D routing to bind"
- "In-container vLLM patch via setup_script kimi-k3-container-deps.sh: the kimi-k3 image's first decode step crashes in the KDA hybrid-state postprocess (vllm/v1/worker/gpu/model_states/mamba_hybrid.py postprocess_state, IndexError: index_fill_(): Expected dtype int64 for index — torch requires an int64 index but the runner passes the int32 idx_mapping; sixth sweep attempt, first warmup request 500s then the model 503s). The patch coerces the index with .long(), is idempotent, and refuses to run if the image layout changed"
- "Recipe: benchmarks/multi_node/srt-slurm-recipes/vllm/kimi-k3/agentic/agg-b200-tp8pp2-agentic.yaml on the cluster:b200-dgxc pool"
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/2359
Loading
Loading