<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://vllm-project.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://vllm-project.github.io/" rel="alternate" type="text/html" /><updated>2026-08-07T19:37:38+00:00</updated><id>https://vllm-project.github.io/feed.xml</id><title type="html">vLLM Blog</title><subtitle>vLLM is a fast and easy-to-use library for LLM inference and serving.
</subtitle><author><name>© 2026. vLLM Team. All rights reserved.</name></author><entry><title type="html">Efficient Decode Context Parallelism with vLLM for Long Context Workloads</title><link href="https://vllm-project.github.io/2026/08/07/decode-context-parallelism.html" rel="alternate" type="text/html" title="Efficient Decode Context Parallelism with vLLM for Long Context Workloads" /><published>2026-08-07T00:00:00+00:00</published><updated>2026-08-07T00:00:00+00:00</updated><id>https://vllm-project.github.io/2026/08/07/decode-context-parallelism</id><content type="html" xml:base="https://vllm-project.github.io/2026/08/07/decode-context-parallelism.html"><![CDATA[<h2 id="1-introduction">1. Introduction</h2>

<p>Long-context inference is becoming essential for agentic AI, where assistants may need to reason over large code repositories and long chat histories. Agent-trace benchmarks now run from 64K all the way to 1M tokens and their KV caches are correspondingly large. Under a baseline tensor-parallel (TP) setup, this KV cache is partitioned by attention head, which puts a hard floor on how much it can shrink.</p>

<p>Modern models use one of two attention schemes, and both hit this floor. Grouped-query attention (GQA) models store a small number of KV heads, and TP can only split the KV cache down to one head per GPU; once TP exceeds the number of KV heads, the cache starts duplicating across GPUs. Multi-head latent attention (MLA) models make this even worse: MLA compresses the Key/Value into a single low-rank <em>latent</em> vector shared across all query heads, so it effectively has only one KV head. Under normal TP there is nothing to split by head, meaning the latent KV cache is replicated in full across <em>every</em> TP rank. In both cases the duplicated KV cache eats into GPU memory, leaving very little room to serve additional requests. This caps the number of concurrent requests the system can handle, driving down throughput and pushing up cost per token.</p>

<p>Decode Context Parallelism addresses this by splitting KV cache across the GPUs so each GPU stores and reads only part of the KV cache. This frees up GPU memory, allowing each GPU to take on more requests and thus run at a larger batch size. On systems with high-bandwidth GPU-to-GPU interconnects, this helps preserve interactive responsiveness while serving many long-context agents at once.</p>

<p>vLLM has supported DCP for almost a year, but we are writing this blog now to highlight the feature, along with the recent improvements and advancements we have made to it, because the rise of long-context agentic use cases has made its benefits more relevant than ever.</p>

<p align="center">
<img src="/assets/figures/2026-07-27-decode-context-parallelism/kv-parallelism-overview.svg" alt="Overview of KV cache parallelism under TP versus DCP" width="100%" />
</p>

<p align="center"><em>Under plain TP, both attention schemes waste memory on duplicated KV cache: GQA can only split down to one KV head per GPU before it starts replicating, and MLA behaves like a single KV head, so its latent cache is replicated on every rank. DCP instead shards the KV cache along the sequence dimension, so each GPU holds a unique slice and no capacity is wasted on duplicates.</em></p>

<h2 id="2-performance-results">2. Performance Results</h2>

<p>To quantify the benefit of Decode Context Parallelism, we compared a baseline tensor-parallel deployment against DCP on an identical set of GPUs, holding the model, hardware, and workload fixed and varying only how the KV cache is sharded during decode.</p>

<p align="center">
<img src="/assets/figures/2026-07-27-decode-context-parallelism/figure-1.png" alt="Throughput comparison figure 1" width="100%" />
</p>

<p align="center">
<img src="/assets/figures/2026-07-27-decode-context-parallelism/figure-2.png" alt="Throughput comparison figure 2" width="100%" />
</p>

<h3 id="21-dataset">2.1 Dataset</h3>

<p>The dataset is a publicly available agentic long-context trace in Mooncake trace format, <a href="https://github.com/ai-dynamo/dynamo/blob/main/recipes/kimi-k2.6/perf/traces/64k_400_90kv_agent_new_noschedule_short_15perc.jsonl">published here</a>. See <a href="https://github.com/ai-dynamo/dynamo/blob/main/recipes/kimi-k2.6/perf/README.md#dataset">this section</a> for more details on the dataset. It ships as JSONL where each line is a single request with <code class="language-plaintext highlighter-rouge">input_length</code>, <code class="language-plaintext highlighter-rouge">output_length</code>, and <code class="language-plaintext highlighter-rouge">hash_ids</code> fields, so it can be replayed directly with any Mooncake-compatible harness (e.g. <code class="language-plaintext highlighter-rouge">aiperf --custom-dataset-type mooncake_trace</code>). The <code class="language-plaintext highlighter-rouge">hash_ids</code> field encodes shared prefix blocks, making it well-suited for benchmarking KV-cache reuse and prefix-caching behavior.</p>

<p>It’s an agentic multi-turn workload of long inputs paired with short generations, chosen to reflect realistic long-horizon agent behavior. Inputs are centered around a median of ~67k tokens and paired with short ~400-token outputs, but the input distribution is bimodal rather than uniformly huge: roughly half the requests sit at 64k+ (≈53%, with a heavy tail reaching ~1M tokens) and half are short-to-mid (≈47% under 64k, ~18% under 8k). About 8% of requests exceed 128k and ~3–4% exceed 256k.</p>

<h3 id="22-benefits-of-decode-context-parallelism">2.2 Benefits of Decode Context Parallelism</h3>

<p>We ran an experiment on a single 8×B200 node serving Kimi K2.6 in NVFP4 with vLLM, sweeping request concurrency from 16 to 512 (see table below). DCP sustains far higher concurrency and delivers markedly higher throughput per GPU across the entire throughput–interactivity Pareto frontier.</p>

<p align="center">
<img src="/assets/figures/2026-07-27-decode-context-parallelism/figure-3.png" alt="DCP vs TP throughput benchmark" width="100%" />
</p>

<p>The difference comes down to where the KV cache lives. Baseline TP replicates the KV cache on every GPU, so peak memory fills quickly. It reaches 100% at a concurrency of 64 and hits a wall, and throughput plateaus near 1,863 tok/s/GPU because no additional requests can fit. On the other hand, DCP shards the KV cache along the sequence dimension, so each GPU stores only 1/N of every request’s KV. This allows space on the GPU to support more incoming requests. As a result, even at high concurrencies DCP keeps scaling where TP hits a wall. DCP reaches 6,091 tok/s/GPU at c512 while still sitting at just 82% KV usage. <strong>The core value of DCP is that it sustains far higher concurrency, even on long-context runs, precisely the regime where replicated-KV TP runs out of memory first.</strong></p>

<h3 id="23-comparison-by-sequence-length">2.3 Comparison by Sequence Length</h3>

<p align="center">
<img src="/assets/figures/2026-07-27-decode-context-parallelism/figure-4.png" alt="DCP Pareto frontier across full sequence-length bands" width="100%" />
</p>

<p>We also plotted performance against full sequence length (input + output). The figure shows a single throughput–interactivity Pareto frontier with requests grouped into five length bands (&lt;32k, 32–64k, 64–128k, 128–200k, and 200k+) so we can see how performance shifts with context length. <strong>DCP keeps a high, stable frontier even in the 200k+ range</strong>, with the curves for short and long buckets nearly overlapping: throughput scales with concurrency while per-user speed stays usable at the long context lengths where the replicated-KV baseline runs out of memory and cannot scale.</p>

<h2 id="3-challenges-of-serving-long-contexts">3. Challenges of Serving Long Contexts</h2>

<p>Under tensor parallelism, the KV cache is partitioned <strong>by the attention head</strong>. Each KV head owns its own separate K and V tensors, and the head is the smallest unit you can hand to a GPU. A standard TP has no mechanism to slice a single head’s KV cache. So if you have K KV heads, you can give each GPU a distinct subset of those heads, but only down to the point where every GPU holds one head. Once TP goes beyond K, there aren’t enough distinct heads to go around, so two or more GPUs end up holding a copy of the same head’s KV cache instead of a unique slice.</p>

<h2 id="4-what-is-dcp">4. What is DCP?</h2>

<p>Unlike pure TP methods, DCP is able to split KV cache across GPUs by sequence (context) dimension. Each GPU is made responsible for the KV cache of a chunk of <em>token positions</em> from the same sequence. For a single 200K-token request, GPU 0 might hold the cache for tokens 0–50K, GPU 1 for tokens 50K–100K, GPU 2 for 100K–150K, and GPU 3 for 150K–200K. By sharding KV cache, the KV cache footprint per GPU keeps shrinking as you add GPUs, freeing the memory that lets you raise the batch size and serve higher concurrencies.</p>

<p align="center">
<img src="/assets/figures/2026-07-27-decode-context-parallelism/figure-5.png" alt="DCP sequence sharding diagram" width="100%" />
</p>

<h3 id="41-decode-context-parallelism-process">4.1 Decode Context Parallelism Process</h3>

<p>Standard Decode Context Parallelism keeps the communication pattern simple, following the rhythm <strong>AllGather Q → Compute → AllGather + ReduceScatter</strong>.</p>

<ul>
  <li>
    <p><strong>AllGather Q:</strong> Each GPU has computed only a fragment of the query, but attention requires the full query vector to score against any key. An all-gather across the DCP group assembles a complete copy of the query on every GPU. This is cheap during decode because the query is a single token. As an opt-in alternative for MLA, <a href="https://github.com/vllm-project/vllm/pull/45964">vLLM #45964</a> can replicate the (small) query projection within each DCP group at load time so decode skips this query all-gather entirely (<code class="language-plaintext highlighter-rouge">VLLM_DCP_Q_REPLICATE=1</code>).</p>
  </li>
  <li>
    <p><strong>Compute:</strong> Each GPU runs attention between the gathered query and its <em>local</em> slice of the KV cache. In vLLM this is <code class="language-plaintext highlighter-rouge">k_up</code> for MLA or <code class="language-plaintext highlighter-rouge">tensor_broadcast</code> for GQA.</p>
  </li>
  <li>
    <p><strong>AllGather + ReduceScatter (<code class="language-plaintext highlighter-rouge">cp_lse_ag_out_rs</code>):</strong> The partial results are combined into the true output. AllGather shares each GPU’s partial output and LSE; the LSE values reweight and merge the partials (the online-softmax trick), and ReduceScatter sums them while handing each GPU back only its own head-slice.</p>
  </li>
</ul>

<h2 id="5-vllm-usage">5. vLLM Usage</h2>

<p>DCP is enabled with a single extra argument, <code class="language-plaintext highlighter-rouge">decode_context_parallel_size</code>, alongside your existing tensor-parallel setting.</p>

<h3 id="51-offline">5.1 Offline</h3>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">vllm</span> <span class="kn">import</span> <span class="n">LLM</span><span class="p">,</span> <span class="n">SamplingParams</span>

<span class="n">prompts</span> <span class="o">=</span> <span class="p">[</span>
    <span class="s">"The future of AI is"</span><span class="p">,</span>
<span class="p">]</span>
<span class="n">sampling_params</span> <span class="o">=</span> <span class="n">SamplingParams</span><span class="p">(</span><span class="n">temperature</span><span class="o">=</span><span class="mf">0.8</span><span class="p">,</span> <span class="n">top_p</span><span class="o">=</span><span class="mf">0.95</span><span class="p">)</span>

<span class="n">llm</span> <span class="o">=</span> <span class="n">LLM</span><span class="p">(</span>
    <span class="n">model</span><span class="o">=</span><span class="s">"deepseek-ai/DeepSeek-V2-Lite"</span><span class="p">,</span>
    <span class="n">tensor_parallel_size</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span>
    <span class="n">decode_context_parallel_size</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span>
<span class="p">)</span>
<span class="n">outputs</span> <span class="o">=</span> <span class="n">llm</span><span class="p">.</span><span class="n">generate</span><span class="p">(</span><span class="n">prompts</span><span class="p">,</span> <span class="n">sampling_params</span><span class="p">)</span>
</code></pre></div></div>

<h3 id="52-online">5.2 Online</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vllm serve deepseek-ai/DeepSeek-V2-Lite <span class="se">\</span>
    <span class="nt">--tensor-parallel-size</span> 2 <span class="se">\</span>
    <span class="nt">--decode-context-parallel-size</span> 2
</code></pre></div></div>

<h3 id="53-mla-backend">5.3 MLA Backend</h3>

<p><strong>Models:</strong> DeepSeek-V2 / V3 / R1, Kimi K2.6 models using Multi-head Latent Attention.</p>

<p><strong>Why it’s different.</strong> MLA compresses the Key/Value into a single low-rank <em>latent</em> vector that is shared across all query heads — effectively one KV “head.” Under pure tensor parallelism there’s nothing to split by head, so that latent KV cache is replicated in full on <em>every</em> TP rank. TP does nothing to shrink it, which makes MLA the ideal candidate for DCP: the whole cache is redundant, so the whole cache can be sequence-split.</p>

<p><strong>What they do.</strong> DCP splits the latent KV cache along the sequence dimension, so each rank stores only its chunk of the latent; at attention time each rank up-projects its latent slice (the <code class="language-plaintext highlighter-rouge">k_up</code> step) to reconstruct the Keys/Values it needs. Because the effective KV-head count is 1, the sequence can be split up to the full TP degree — hence the constraints:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">tensor_parallel_size &gt;= decode_context_parallel_size</code></li>
  <li><code class="language-plaintext highlighter-rouge">tensor_parallel_size % decode_context_parallel_size == 0</code></li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vllm serve deepseek-ai/DeepSeek-R1 <span class="se">\</span>
    <span class="nt">--tensor-parallel-size</span> 8 <span class="se">\</span>
    <span class="nt">--decode-context-parallel-size</span> 8
</code></pre></div></div>

<h3 id="54-gqa-backend">5.4 GQA Backend</h3>

<p><strong>Example models:</strong> Qwen3-235B, and other Grouped-Query-Attention models (Llama-family, etc.).</p>

<p><strong>Why it’s different.</strong> GQA stores <code class="language-plaintext highlighter-rouge">num_key_value_heads</code> KV heads, and TP splits the KV cache by those heads first. That works cleanly only up to <code class="language-plaintext highlighter-rouge">num_key_value_heads</code>; once <code class="language-plaintext highlighter-rouge">tensor_parallel_size</code> exceeds it, the KV cache begins duplicating, with <code class="language-plaintext highlighter-rouge">tp // num_key_value_heads</code> identical copies across ranks.</p>

<p><strong>What they do.</strong> DCP takes those would-be-duplicate copies and fills them with <em>different</em> sequence chunks instead, while the shared KV heads are broadcast across their query heads (the “tensor broadcast for GQA” step). So the sequence-split degree is capped by the duplication factor <code class="language-plaintext highlighter-rouge">tp // num_key_value_heads</code>:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">(tensor_parallel_size // num_key_value_heads) &gt;= decode_context_parallel_size</code></li>
  <li><code class="language-plaintext highlighter-rouge">(tensor_parallel_size // num_key_value_heads) % decode_context_parallel_size == 0</code></li>
</ul>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Qwen3-235B has num_key_value_heads = 4; tp=8 gives 8//4 = 2 redundant copies,
# so dcp can be up to 2.
</span><span class="n">vllm</span> <span class="n">serve</span> <span class="n">Qwen</span><span class="o">/</span><span class="n">Qwen3</span><span class="o">-</span><span class="mi">235</span><span class="n">B</span><span class="o">-</span><span class="n">A22B</span> \
    <span class="o">--</span><span class="n">tensor</span><span class="o">-</span><span class="n">parallel</span><span class="o">-</span><span class="n">size</span> <span class="mi">8</span> \
    <span class="o">--</span><span class="n">decode</span><span class="o">-</span><span class="n">context</span><span class="o">-</span><span class="n">parallel</span><span class="o">-</span><span class="n">size</span> <span class="mi">2</span>
</code></pre></div></div>

<h2 id="6-future-work">6. Future Work</h2>

<p>Looking ahead, we plan to extend DCP along several main directions. We will add support for finer-grained parallelism sizes for both TP and DCP, giving users more precise control over their parallelism layout and reclaiming efficiency lost to over-provisioned sharding. We are also developing better DCP all-to-all (A2A) communication kernels for both multinode and single-node settings, reducing exposed communication and improving overlap with compute as context length and device count grow. We are working on better support for MTP and speculative decoding, so that DCP can deliver its efficiency gains without sacrificing the latency benefits of speculative methods, as well as hardening prefill/decode (P/D) disaggregation support to make DCP robust in disaggregated serving deployments. Finally, we aim to broaden DCP’s reach by extending support to a wider variety of backends and integrating it with hybrid models and Dynamic Chunked Pipeline Parallelism, so a much wider range of workloads can benefit from context-parallel efficiency gains.</p>

<p>The community is also expanding DCP to additional models such as GLM-5.2 and Kimi K3, and there is a longer roadmap for Prefill Context Parallelism (PCP). We are working on DCP performance benchmarking for the Kimi K3 model and plan to share those results as that work matures. For deployment guidance and historical notes on DCP, see the <a href="https://docs.vllm.ai/en/latest/serving/context_parallel_deployment/#decode-context-parallel">vLLM Decode Context Parallel docs</a>.</p>

<h2 id="7-conclusion">7. Conclusion</h2>

<p>Decode Context Parallelism represents a fundamental rethinking of how GPUs are organized for long-context inference. Rather than forcing GPUs to duplicate KV cache or sit underutilized, DCP puts every GPU to work: sharding the sequence during attention, then immediately reconfiguring those same GPUs to amortize FFN weight loading across the full pool. The result is a system that scales gracefully with context length rather than degrading under it.</p>

<p>With native support in vLLM, Decode Context Parallelism is ready to power the next generation of long-context agentic applications, from document reasoning to multi-session agentic pipelines, at the throughput and latency that production demands. It joins a broader industry move toward Decode Context Parallelism, a direction <a href="https://github.com/NVIDIA/TensorRT-LLM/blob/main/docs/source/blogs/tech_blog/blog22_Helix_Parallelism_Scaling_Multi_Million_Token_Decoding_with_KV_Cache_Sharding.md">NVIDIA has also pursued with Helix Parallelism</a> in TensorRT-LLM. We are also working on DCP performance benchmarking for the Kimi K3 model and plan to share those results as that work matures.</p>

<h2 id="about-us">About Us</h2>

<p>Special thanks to the NVIDIA team Anahita Bhiwandiwalla, Xin Li, Pavani Majety, Nidhi Bhatia, Roman Ageev, Pen Chung Li, and Chris Hoge for their reviews, benchmarking support, and engineering input throughout this study. We also thank <a href="https://www.moonshot.cn/">Moonshot AI</a> for the initial Decode Context Parallel work upstreamed in <a href="https://github.com/vllm-project/vllm/pull/23734">vLLM #23734</a>, and <a href="https://github.com/LucasWilkinson">Lucas Wilkinson</a> for substantial follow-up contributions that helped harden and extend DCP. We also thank the broader vLLM community, whose open-source engine and continued collaboration made this benchmarking effort possible. For more on DCP deployment and related history, see the <a href="https://docs.vllm.ai/en/latest/serving/context_parallel_deployment/#decode-context-parallel">vLLM Decode Context Parallel docs</a>.</p>

<p>The DCP results in this post were measured on NVIDIA B200 GPUs with Kimi K2.6 in NVFP4, and the recipes can be reproduced with current vLLM releases that support <code class="language-plaintext highlighter-rouge">--decode-context-parallel-size</code>. We are also working on DCP performance benchmarking for the Kimi K3 model and plan to share those results as that work matures.</p>]]></content><author><name>Seonghee Lee, Sungsoo Ha, Omri Almog (NVIDIA), Lucas Wilkinson (Red Hat AI)</name></author><category term="performance" /><category term="attention" /><category term="parallelism" /><summary type="html"><![CDATA[1. Introduction]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vllm-project.github.io/assets/figures/2026-07-27-decode-context-parallelism/figure-1.png" /><media:content medium="image" url="https://vllm-project.github.io/assets/figures/2026-07-27-decode-context-parallelism/figure-1.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">vLLM Reaches 25K Total TPS/GPU on Qwen3.5</title><link href="https://vllm-project.github.io/2026/08/06/qwen35-25k-tps.html" rel="alternate" type="text/html" title="vLLM Reaches 25K Total TPS/GPU on Qwen3.5" /><published>2026-08-06T00:00:00+00:00</published><updated>2026-08-06T00:00:00+00:00</updated><id>https://vllm-project.github.io/2026/08/06/qwen35-25k-tps</id><content type="html" xml:base="https://vllm-project.github.io/2026/08/06/qwen35-25k-tps.html"><![CDATA[<h2 id="introduction">Introduction</h2>

<p>Qwen3.5 was released in early 2026 and remains one of the most widely used models among customers. Because of its novel hybrid attention architecture, serving it in disaggregated mode brings in additional challenges and performance optimization opportunities. Thanks to the continuous contributions from the vLLM community, the disaggregated serving path for Qwen3.5 is now mature, and we are excited to report on the major contributions, latest performance on GB200 NVL72 systems, and recipes and best practices for you to reproduce. In this blog post we show how <strong>you</strong> can get over 25K total TPS/GPU performance.</p>

<h2 id="challenges-and-key-optimizations">Challenges and Key Optimizations</h2>

<p>Qwen3.5’s hybrid architecture combines full-attention layers with Gated Delta Network (GDN) layers. This creates two distinct optimization challenges: accelerating GDN computation on Blackwell GPUs and transferring heterogeneous attention/GDN state correctly between prefill and decode workers.</p>

<p>SSM support for P/D serving was driven by the vLLM community through the <a href="https://github.com/vllm-project/vllm/issues/33702">NIXL disaggregation roadmap</a>. For a deeper discussion of heterogeneous cache layouts, logical and physical block mapping, and tensor-parallel state transfer, see the detailed <a href="https://vllm.ai/blog/2026-04-21-hybrid-ssm-disagg">hybrid SSM disaggregation blog post</a>. We highlight the following contributions as particularly important to Qwen3.5 performance.</p>

<h3 id="1-blackwell-optimized-gdn-prefill">1. Blackwell-Optimized GDN Prefill</h3>

<p><a href="https://github.com/flashinfer-ai/flashinfer/pull/3001">FlashInfer: Add Blackwell GDN prefill kernel #3001</a></p>

<p>Compared with the previous FLA/Triton implementation, the new GDN kernel improves performance by approximately 1.02× to 5.78× across Qwen3.5 model sizes, tensor-parallel configurations, sequence lengths, and batch shapes.</p>

<p>The kernel was subsequently enabled on the prefill side in vLLM by <a href="https://github.com/vllm-project/vllm/pull/40717">vLLM PR #40717</a>. On an 8×B200 system running Qwen3.5-397B-A17B-NVFP4, the vLLM integration delivered:</p>

<ul>
  <li>Up to 5.92× higher GDN kernel performance in the tested microbenchmarks.</li>
  <li>1.13× higher end-to-end prefill throughput on a prefill-only workload (ISL/OSL = 8192/1).</li>
  <li>A 12% reduction in mean TTFT (prefill-only workload 8K/1).</li>
</ul>

<p>On supported Blackwell configurations, vLLM automatically selects the FlashInfer path when the GDN backend is set to <code class="language-plaintext highlighter-rouge">auto</code>. It can also be requested explicitly with:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>--gdn-prefill-backend flashinfer
</code></pre></div></div>

<h3 id="2-hybrid-cache-and-gdn-state-transfer">2. Hybrid Cache and GDN-State Transfer</h3>

<p>Disaggregated serving for hybrid SSM-attention models builds on <a href="https://github.com/vllm-project/vllm/pull/35758">[Core][KVConnector] Support HMA+NixlConnector #35758</a> and a stack of connector changes described in the <a href="https://vllm.ai/blog/2026-04-21-hybrid-ssm-disagg">hybrid SSM disaggregation blog post</a>. This PR is a necessary prerequisite: it maps HMA’s logical blocks onto the correct physical memory regions so NIXL can transfer only the cache regions belonging to each layer type, reducing transferred descriptors from 4,284 to 1,650 and improving throughput by up to approximately 7% in a small-scale intra-node H100 setup. But Mamba-style state differs enough in layout, size, and transfer semantics that HMA support alone would not have been sufficient for correct or efficient P/D serving.</p>

<p>The main PR for hybrid SSM-FA disaggregation is <a href="https://github.com/vllm-project/vllm/pull/36687">[PD][Nixl] Add support for hybrid SSM-FA models #36687</a>, which adds dual descriptor views and homogeneous-TP support so prefill and decode workers can transfer both full-attention KV cache and Mamba-style SSM state over NIXL. Related follow-ups in the same stack include:</p>

<ul>
  <li><a href="https://github.com/vllm-project/vllm/pull/37416">[Kernel] Mamba support different layout for Conv state #37416</a></li>
  <li><a href="https://github.com/vllm-project/vllm/pull/37635">[NIXL][Mamba][3/N] Heterogeneous TP: 3-read conv state transfer #37635</a></li>
  <li><a href="https://github.com/vllm-project/vllm/pull/37310">[SSM/Mamba] Follow-up: N-1 prefill for P/D disaggregation #37310</a></li>
</ul>

<p>See the <a href="https://vllm.ai/blog/2026-04-21-hybrid-ssm-disagg">hybrid SSM disaggregation blog post</a> for how dual descriptor views, physical/logical block bridging, and conv-state transfer fit together.</p>

<p>For Qwen3.5 specifically, <a href="https://github.com/vllm-project/vllm/pull/41869">PD disagg with NIXL Connector: GDN support (Qwen3.5) #41869</a> extends this path to GDN layers.</p>

<h3 id="3-race-free-async-scheduling">3. Race-Free Async Scheduling</h3>

<p>These two patches fix race conditions in KV block transfer that made async scheduling unusable — accuracy collapsed to zero with it enabled. Async scheduling turned out to be one of the key features behind crossing 25K tok/s/GPU, so both races had to be resolved.</p>

<ul>
  <li><a href="https://github.com/vllm-project/vllm/pull/48481">[KV Connector] Fix PD async scheduling race condition for hybrid attn models #48481</a></li>
  <li><a href="https://github.com/vllm-project/vllm/pull/45357">[Bugfix] Defer block freeing until in-flight steps finish under async scheduling + PD KV consumer #45357</a></li>
</ul>

<h2 id="performance">Performance</h2>

<h3 id="1-environment-setup">1. Environment Setup</h3>

<p>Measurements were conducted on a GB200 cluster connected via NVLink72. We used ISL/OSL = 8192/1024. The evaluated model was <a href="https://huggingface.co/nvidia/Qwen3.5-397B-A17B-NVFP4">Qwen3.5-397B-A17B-NVFP4</a>. Performance was measured on a fixed decode topology and a constant number of decode endpoints. In this setup, the decode side used one endpoint with DEP8 (Data Parallel + Expert Parallel across 8 GPUs). On the prefill side, we evaluated configurations ranging from 4 to 8 endpoints, each using a fixed DEP2 topology.</p>

<p>To reproduce the results, use the latest vLLM <a href="https://hub.docker.com/layers/vllm/vllm-openai/nightly-d223c900d85224c02f2162ee2c757a769e99f519/images/sha256-987393f42c48b8a649961a3484d95d400db184b64e4e1bb7f77cb91536d0f05e">vllm/vllm-openai:nightly-d223c90</a> Docker image, <a href="https://github.com/ai-dynamo/dynamo">Dynamo</a> <code class="language-plaintext highlighter-rouge">1.2.0.dev20260526</code>, and <a href="https://github.com/NVIDIA/srt-slurm">srt-slurm</a> <code class="language-plaintext highlighter-rouge">v1.0.32</code>. All recipes used in this article are available in the <a href="https://github.com/NVIDIA/srt-slurm-recipes">srt-slurm-recipes</a> repository.</p>

<h3 id="accuracy-results">Accuracy Results</h3>

<p>First, we measured accuracy for all serving configurations to ensure that the performance results are valid. For this purpose, we used the standard GSM8K (Grade School Math 8K) benchmark. Running GSM8K with srt-slurm is straightforward. To enable it, add the following benchmarking block to your recipe file:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">benchmark</span><span class="pi">:</span>
  <span class="na">type</span><span class="pi">:</span> <span class="s2">"</span><span class="s">gsm8k"</span>
</code></pre></div></div>

<p>Accuracy results for all five configurations are <strong>88%</strong>, which matches the accuracy we observe for the aggregated Qwen3.5 run.</p>

<h3 id="2-comments-on-recipe-settings-choice">2. Comments on Recipe Settings Choice</h3>

<p>For performance measurements we used a fixed input sequence length / output sequence length benchmark, on a random dataset with <code class="language-plaintext highlighter-rouge">random_range_ratio=0.8</code>. The recipes themselves, and the settings that matter most, are covered in <a href="#recipes--best-practices">Recipes &amp; best practices</a> below.</p>

<h3 id="3-performance-results">3. Performance Results</h3>

<p>Pareto curves for the individual configurations are shown in <a href="#figure-1">Figure 1</a>, and the final Pareto frontier obtained after combining all configurations is shown in <a href="#figure-2">Figure 2</a>. Total TPS per GPU reaches <strong>25,000</strong> tokens per second. Concurrency was swept from 64 up to 5120. We did not measure low concurrencies in the range of 1 to 32, since our focus here was the left part of the Pareto curve — maximizing the total TPS per GPU metric. At the other end, we did not go beyond 5120 because that is where we started running out of KV cache capacity on the decode side, which we deliberately fixed at a single 8×GB200 endpoint throughout these measurements. Pushing concurrency higher is entirely possible, but it requires adding GPUs on the decode side.</p>

<p><span id="figure-1"></span></p>

<p><img src="/assets/figures/2026-08-06-qwen35-25k-tps/pareto-curves-by-prefill-endpoints.png" alt="Figure 1: Pareto curves for disaggregated Qwen3.5 serving with different numbers of prefill instances." /></p>

<p><span id="figure-2"></span></p>

<p><img src="/assets/figures/2026-08-06-qwen35-25k-tps/pareto-frontier-qwen35-nvfp4.png" alt="Figure 2: Final Pareto frontier for disaggregated serving of Qwen3.5 NVFP4 in vLLM." /></p>

<h2 id="recipes--best-practices">Recipes &amp; best practices</h2>

<p>All recipes used in this article live in the <a href="https://github.com/NVIDIA/srt-slurm-recipes/tree/main/recipes/multi-node/Qwen3.5/GB200/8k1k/vllm/disagg">srt-slurm-recipes</a> repository, and each one is launched with a single command:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>srtctl run <span class="nt">--file</span> &lt;recipe&gt;.yaml
</code></pre></div></div>

<p>The naming scheme is <code class="language-plaintext highlighter-rouge">NxDEP2-1xDEP8</code>, where N is the number of prefill endpoints running DEP2 against a single DEP8 decode endpoint; there are five base configurations, from 4×DEP2 to 8×DEP2. Each comes with three derived variants: the base file sweeps sa-bench over concurrencies 64…3072, the <code class="language-plaintext highlighter-rouge">-acc</code> variant runs GSM8K five times on the same topology, and <code class="language-plaintext highlighter-rouge">-cc4096</code> / <code class="language-plaintext highlighter-rouge">-cc5120</code> each capture a single high-concurrency point with the decode-side <code class="language-plaintext highlighter-rouge">max-cudagraph-capture-size</code> raised to 640 and 768 respectively.</p>

<p>Most settings in the recipes are standard and shared across all configurations, but several are worth calling out:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">VLLM_SSM_CONV_STATE_LAYOUT=DS</code> — mandatory for SSM models in disaggregated serving; conv-state transfer does not work without it. Our recipes also passed <code class="language-plaintext highlighter-rouge">--no-disable-hybrid-kv-cache-manager</code>; HMA has since been enabled by default in vLLM for several versions, so that flag is no longer required.</li>
  <li><code class="language-plaintext highlighter-rouge">--async-scheduling</code> — one of the key features behind reaching 25K tok/s per GPU. It requires a vLLM build that already contains the race-condition fixes discussed above.</li>
  <li><code class="language-plaintext highlighter-rouge">--mamba-ssm-cache-dtype bfloat16</code> — significantly increases the effective KV cache capacity on the decode endpoint.</li>
  <li><code class="language-plaintext highlighter-rouge">--language-model-only</code> — Qwen3.5 is a multimodal model, and for a purely textual workload this flag not only disables multimodal inputs but also unlocks the fused QK-norm + RoPE + gate path in the attention layers.</li>
  <li><code class="language-plaintext highlighter-rouge">--max-num-batched-tokens 16384</code> on the prefill side, i.e. 2× ISL. With fewer prefill endpoints ({4, 5, 6}×DEP2) prefill became the bottleneck and left the decode side idling below its peak throughput, so we let each prefill step batch two full prompts instead of one — worth about <strong>+8%</strong> of total TPS per GPU at high concurrencies.</li>
  <li><code class="language-plaintext highlighter-rouge">--max-cudagraph-capture-size</code> on decode — raised to <code class="language-plaintext highlighter-rouge">cc/8 + 128</code> for the two highest concurrency points (640 at cc=4096, 768 at cc=5120), where 8 is the number of DP ranks on the decode endpoint. The vLLM default caps captured graphs at 512, which is enough up to cc=3072. We are not certain this is actually required for the Pareto numbers reported here, but we set it as a precaution.</li>
  <li>Prefix caching is disabled: it buys nothing on a random dataset.</li>
  <li><code class="language-plaintext highlighter-rouge">--stream-interval 100</code> — reduces frontend overhead at high concurrency. Note that it buffers streamed output in 100-token chunks, so it does affect measured per-token latency; keep that in mind if you are optimizing for ITL/TPOT rather than aggregate throughput.</li>
</ul>

<p>Finally, a couple of practical things that saved us a lot of time.</p>

<p><code class="language-plaintext highlighter-rouge">--api-server-count 1</code> is very useful while you are investigating a particular configuration. On a data-parallel endpoint vLLM defaults the API server count to the data-parallel size, and with more than one API server it disables its default stats logging altogether in order not to report incomplete numbers. Forcing the count to 1 brings that logging back: every 10 seconds — the interval is configurable through <code class="language-plaintext highlighter-rouge">VLLM_LOG_STATS_INTERVAL</code> — the server prints prompt and generation throughput along with KV cache utilization. Without these metrics we would hardly have identified the bottlenecks of the individual configurations, or understood why a particular option helps on our workload.</p>

<p>It is also worth setting three environment variables: <code class="language-plaintext highlighter-rouge">DYN_LOG=error</code>, <code class="language-plaintext highlighter-rouge">DYN_SDK_DISABLE_ANSI_LOGGING=1</code>, and <code class="language-plaintext highlighter-rouge">VLLM_LOGGING_COLOR=0</code>. The first one drastically cuts down the amount of Dynamo logs, while the other two suppress some (not all!) ANSI escape sequences in the log output. Without them your log files are very likely to be unreadable for a human, mostly because Dynamo produces an enormous amount of logging by default.</p>

<h2 id="whats-next">What’s next</h2>

<p>Our measurements so far have concentrated mostly on the left part of the Pareto curve, squeezing out as much total TPS per GPU as possible. Next, we plan to sweep for the PD configurations that maximize Gen TPS per user instead. Reaching that regime will require shifting away from DEP topologies towards TEP (Tensor Parallel + Expert Parallel) or just TP, which as a rule deliver better per-user performance. Increasing the number of GPUs in use is another lever we expect to pay off here.</p>

<h2 id="acknowledgements">Acknowledgements</h2>

<p>Artem Perevedentsev (NVIDIA), Vadim Gimpelson (NVIDIA), Jiangyun Zhu (Inferact), Nicolò Lucchesi (Mistral), Zhanqiu Hu (Red Hat), Nick Hill (Inferact), Linxuan Li (Alibaba), JingZe Cui (NVIDIA), Cyrus Chang (NVIDIA), Xin Li (NVIDIA)</p>]]></content><author><name>vLLM Team</name></author><category term="performance" /><category term="qwen3.5" /><category term="disaggregation" /><summary type="html"><![CDATA[Introduction]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vllm-project.github.io/assets/figures/2026-08-06-qwen35-25k-tps/pareto-curves-by-prefill-endpoints.png" /><media:content medium="image" url="https://vllm-project.github.io/assets/figures/2026-08-06-qwen35-25k-tps/pareto-curves-by-prefill-endpoints.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Optimizing vLLM on Arm CPUs</title><link href="https://vllm-project.github.io/2026/07/29/optimizing-vllm-on-arm-cpus.html" rel="alternate" type="text/html" title="Optimizing vLLM on Arm CPUs" /><published>2026-07-29T00:00:00+00:00</published><updated>2026-07-29T00:00:00+00:00</updated><id>https://vllm-project.github.io/2026/07/29/optimizing-vllm-on-arm-cpus</id><content type="html" xml:base="https://vllm-project.github.io/2026/07/29/optimizing-vllm-on-arm-cpus.html"><![CDATA[<h2 id="introduction">Introduction</h2>

<p>Large language model serving on CPUs is an important deployment option because CPUs offer lower deployment cost, simpler infrastructure, and broad availability across cloud and enterprise data centers. As Arm® Neoverse™-based servers become more widely deployed, improving the usability, feature coverage, and performance of open-source serving frameworks such as vLLM on Arm CPUs has become increasingly important.</p>

<p>Over the last several months, we have worked with the vLLM, PyTorch, oneDNN, and KleidiAI communities to make upstream improvements across the Arm CPU serving stack. The result is improved usability, broader model and feature support, and substantial performance gains that benefit any Arm Neoverse-based server running vLLM.</p>

<p>In this blog, we walk through the usability and coverage improvements first, then dig into the main performance optimizations and the end-to-end serving results.</p>

<h2 id="enablement">Enablement</h2>

<p>Alongside performance optimizations, we improved the usability and feature completeness of vLLM on Arm®-based CPUs, making it easier to deploy vLLM on Arm® servers.</p>

<p>Key enablement improvements include:</p>

<ul>
  <li>Pre-built <a href="https://docs.vllm.ai/en/latest/getting_started/installation/cpu/#arm-aarch64_2:~:text=venv/bin/activate-,Pre%2Dbuilt%20wheels,%C2%B6,-When%20specifying%20the">wheels</a> and <a href="https://docs.vllm.ai/en/latest/getting_started/installation/cpu/#arm-aarch64_4:~:text=%C2%B6-,Pre%2Dbuilt%20images,%C2%B6,-Intel/AMD%20x86">Docker images</a>.</li>
  <li>Bug fixes for crashes, accuracy issues, threading, and CPU utilization.</li>
  <li>Support for chunked prefill and prefix caching.</li>
  <li>Support for INT8 W8A8 and INT8 W4A8 inference.</li>
  <li>Model enablement for GPT-OSS, Whisper, and Qwen 3.5 / 3.6.</li>
  <li>Better integration with the <a href="https://github.com/pytorch/pytorch">PyTorch</a> and <a href="https://github.com/uxlfoundation">UXL</a> ecosystems.</li>
</ul>

<p>With these enablement improvements in place, we turned our attention to understanding and eliminating the performance bottlenecks.</p>

<h2 id="performance-improvements">Performance Improvements</h2>

<p>When we first benchmarked vLLM on Arm-based CPUs in October 2025, performance was much lower than expected given that roughly 80% of model runtime was spent in dense layers dispatched to highly optimized BF16 GEMMs. The standalone GEMM kernels behind those layers were already close to expected hardware efficiency, so the biggest gains were unlikely to come from GEMM kernels alone.</p>

<p>The profiles instead pointed to a broader optimization problem: allocator behavior, runtime synchronization, framework overheads, attention kernels, and quantized execution.</p>

<h3 id="memory-allocation">Memory Allocation</h3>

<p>LLM serving puts significant pressure on the CPU memory allocator. During prefill and decode, vLLM repeatedly allocates and releases tensors for scheduling, KV-cache management, and intermediate operator outputs. In our initial benchmarks, memory allocation showed up as a bottleneck, with poor reuse of large allocations causing a high number of page faults.</p>

<p>The root cause was PyTorch’s use of glibc <code class="language-plaintext highlighter-rouge">malloc</code>. Large allocations were not reused effectively across repeated inference steps, and allocation/free paths became a source of contention as thread counts increased. As a workaround, we initially recommended preloading a caching allocator, but that added manual setup and made performance depend on runtime configuration.</p>

<p>To improve out-of-the-box performance, we enabled <a href="https://github.com/microsoft/mimalloc">mimalloc</a> as the default allocator on Arm-based CPUs in PyTorch. Mimalloc is a caching allocator designed to scale under multi-threaded allocation pressure. We chose it because it delivered strong performance across a broad range of TorchBench workloads and was already integrated as a PyTorch dependency for non-Arm Linux builds.</p>

<p>This improved Llama 3.1 8B out-of-the-box offline throughput by 2.3× and delivered gains of approximately 7× in low-concurrency serving scenarios.</p>

<div class="markdown-alert markdown-alert-note"><p class="markdown-alert-title"><svg data-component="Octicon" class="octicon octicon-info" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg> Note</p><p>We exclude the allocator improvement from all performance plots in this post because its gains would dominate the scale and obscure the impact of the other optimizations. The plots therefore show improvements from the rest of the stack.</p>
</div>

<h3 id="synchronization-at-high-core-counts">Synchronization at High Core Counts</h3>

<p>After improving memory allocation, the next bottleneck appeared when scaling inference to higher core counts. Beyond a certain point, adding more cores did not improve throughput and could even regress performance.</p>

<p>To understand where the scaling broke down, we profiled individual layers at high thread counts. One profile showed that 74% of the paged attention time was spent in OpenMP dynamic scheduling:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>97.94% gomp_thread_start
  90.08% paged_attention_v1_impl
    74.07% gomp_iter_dynamic_next
     7.00% reduceValueBlock::lambda(int)
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">gomp_iter_dynamic_next</code> is part of libgomp’s dynamic loop scheduling path. In this path, the runtime uses an atomic fetch-add to assign loop chunks to worker threads. The libgomp runtime used by the PyTorch wheels implemented that atomic update with a load-linked / store-conditional retry loop:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="p">(;;)</span> <span class="p">{</span>
    <span class="kt">long</span> <span class="n">old</span> <span class="o">=</span> <span class="n">LDXR</span><span class="p">(</span><span class="n">p</span><span class="p">);</span>
    <span class="kt">long</span> <span class="n">newv</span> <span class="o">=</span> <span class="n">old</span> <span class="o">+</span> <span class="n">delta</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">fail</span> <span class="o">=</span> <span class="n">STLXR</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">newv</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">fail</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">DMB_ISH</span><span class="p">();</span>
        <span class="k">return</span> <span class="n">old</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>At high core counts, many worker threads contend on the same atomic update, leading to repeated failed store attempts and retry traffic.</p>

<p>Tracing this down to the assembly revealed a missed hardware optimization opportunity. The benchmark system used Neoverse™ V2 cores, which support <a href="https://learn.arm.com/learning-paths/servers-and-cloud-computing/lse/example/">Arm Large System Extensions (LSE)</a>. LSE provides hardware atomic instructions, such as <code class="language-plaintext highlighter-rouge">LDADDAL</code>, that replace the inefficient loop above. However, the OpenMP runtime used by PyTorch did not leverage LSE atomics.</p>

<p>We addressed this by building a libgomp runtime in PyTorch that uses LSE atomics on capable CPUs.</p>

<p>This improved Llama 3.1 8B offline throughput by 9% and reduced Time Per Output Token (TPOT) latency by 15% in low-concurrency serving scenarios.</p>

<h3 id="dense-layer-layout-overhead">Dense-Layer Layout Overhead</h3>

<p>Even after the allocator and runtime improvements, dense layers still left performance on the table. High-performance GEMM kernels are sensitive to weight layout: to run efficiently, weights need to be in a blocked format that matches the kernel’s vectorization and cache-access pattern. Without prepacking, each call can pay the cost of transforming weights from the framework tensor layout into the kernel-friendly format.</p>

<p>This is especially expensive at low concurrency, where the packing cost is not amortized over large batches. We addressed this by enabling a fast oneDNN path for dense layers, accelerated by the Compute Library for Arm Architecture. This path lets vLLM pack BF16 weights during model warmup into the format expected by the kernel, then reuse that packed representation during inference.</p>

<p>This improved Llama 3.1 8B offline throughput by 16% and reduced TPOT latency by 60% in low-concurrency serving scenarios.</p>

<h3 id="paged-attention">Paged Attention</h3>

<p>The CPU paged attention kernel was not optimized for Arm-based CPUs. The QK and PV matrix multiplications, along with the exponential in softmax, were falling back to reference implementations. As a result, we relied on PyTorch’s Scaled Dot-Product Attention kernel for prefill, which meant chunked prefill and prefix caching were not supported on the Arm CPU path.</p>

<p>We optimized the QK and PV paths with custom GEMM kernels using Arm <a href="https://developer.arm.com/community/arm-community-blogs/b/ai-blog/posts/bfloat16-processing-for-neural-networks-on-armv8_2d00_a">BFMMLA</a> Advanced SIMD instructions. We also optimized the softmax exponential with a fast vectorized third-degree polynomial approximation.</p>

<p>These changes made paged attention up to 4× faster and improved Llama 3.1 8B offline throughput by 12%. 
Furthermore, this allowed us to enable paged attention for prefill on Arm-based CPUs, unlocking support for chunked prefill and prefix caching.</p>

<h3 id="bf16-performance-improvements">BF16 Performance Improvements</h3>

<p>The synchronization, weight prepacking, and paged attention optimizations combine to create a stronger BF16 serving baseline than the one we started with in October 2025.</p>

<figure style="text-align: center;">
  <img src="/assets/figures/2026-07-29-arm-cpu/heatmap_bf16_optimized_vs_bf16_baseline.png" alt="Heatmap showing optimized BF16 serving relative to the October 2025 BF16 baseline" />
  <figcaption><em>Optimized BF16 serving relative to the October 2025 BF16 baseline.</em></figcaption>
</figure>

<h3 id="int8-w8a8-8-bit-weights-and-activations">INT8 W8A8 (8-bit weights and activations)</h3>

<p>LLM inference repeatedly reads large weight matrices during prefill and decode. Storing weights in INT8 instead of BF16 reduces memory bandwidth pressure and can allow larger models to fit within the same memory budget.</p>

<p>On Arm-based CPUs with I8MM, W8A8 also maps to <a href="https://developer.arm.com/documentation/dui0379/e/arm-and-thumb-instructions/smmla">SMMLA</a>, Arm’s signed INT8 matrix multiply-accumulate instruction, which provides twice the theoretical matrix-multiply throughput of BF16.</p>

<p>To take advantage of this, we accelerated the W8A8 quantization path with <a href="https://github.com/uxlfoundation/oneDNN">oneDNN</a> JIT kernels that use <code class="language-plaintext highlighter-rouge">SMMLA</code> instructions on SVE128 and SVE256.</p>

<p>As a result, multiple Hugging Face INT8 W8A8 checkpoints, including <code class="language-plaintext highlighter-rouge">RedHatAI/Meta-Llama-3.1-8B-quantized.w8a8</code> and <code class="language-plaintext highlighter-rouge">RedHatAI/whisper-large-v3-quantized.w8a8</code>, now perform well out of the box.</p>

<p>Compared to our optimized BF16 baseline, W8A8 with per-token activation quantization and channelwise weight quantization delivers as much as 88% higher throughput, 45% lower TPOT, and 54% lower TTFT, depending on concurrency.</p>

<figure style="text-align: center;">
  <img src="/assets/figures/2026-07-29-arm-cpu/heatmap_int8_vs_bf16_optimized.png" alt="Heatmap showing INT8 W8A8 serving relative to the optimized BF16 path" />
  <figcaption><em>INT8 W8A8 serving relative to the optimized BF16 path.</em></figcaption>
</figure>

<div class="markdown-alert markdown-alert-note"><p class="markdown-alert-title"><svg data-component="Octicon" class="octicon octicon-info" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg> Note</p><p>To learn more about INT8 W8A8 on Arm-based CPUs, try <a href="https://learn.arm.com/learning-paths/servers-and-cloud-computing/vllm-benchmark-quantisation/">this</a> Arm Learning Path.</p>
</div>

<h3 id="int8-w4a8-4-bit-weights-8-bit-activations">INT8 W4A8 (4-bit weights, 8-bit activations)</h3>

<p>W4A8 pushes the same idea further: quantize weights to INT4 to lower memory bandwidth pressure during inference. This is especially useful at low concurrency, where there is less batching to amortize the cost of reading model weights.</p>

<p>This path is accelerated through <a href="https://github.com/ARM-software/kleidiai">KleidiAI</a>’s INT4 micro-kernels.</p>

<p>Compared to the W8A8 baseline above, W4A8 with per-token activation quantization and channelwise weight quantization delivers as much as 29% higher throughput, 26% lower TPOT, and 18% lower TTFT, depending on concurrency.</p>

<p>As expected, the biggest W4A8 speedups appear in low-concurrency scenarios where inference is mostly memory-bound.</p>

<figure style="text-align: center;">
  <img src="/assets/figures/2026-07-29-arm-cpu/heatmap_int4_vs_int8.png" alt="Heatmap showing INT8 W4A8 serving relative to the INT8 W8A8 path" />
  <figcaption><em>INT8 W4A8 serving relative to the INT8 W8A8 path.</em></figcaption>
</figure>

<div class="markdown-alert markdown-alert-note"><p class="markdown-alert-title"><svg data-component="Octicon" class="octicon octicon-info" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg> Note</p><p>Please refer to <a href="https://docs.vllm.ai/en/latest/features/quantization/llm_compressor/int8_w4a8/">these docs</a> to learn how to quantize your models to INT8 W4A8 with llm-compressor.</p>
</div>

<h2 id="summary">Summary</h2>

<p>vLLM on Arm-based CPUs has seen dramatic improvements in usability, robustness, model and feature coverage, and performance.</p>

<p>Relative to the October 2025 BF16 baseline, the optimized BF16 path delivers up to <strong>2.7× the serving throughput</strong>. INT8 W8A8 reaches up to <strong>4.8× the baseline throughput</strong> and a <strong>5.7× TPOT speedup</strong>, while INT8 W4A8 delivers the best results with up to <strong>6.2× the baseline throughput</strong>, a <strong>7.8× TPOT speedup</strong>, and a <strong>2.6× TTFT speedup</strong>.</p>

<p>The gains came from optimizing the full CPU inference stack: memory allocation, OpenMP synchronization, dense-layer prepacking, paged attention, and quantization.</p>

<figure style="text-align: center;">
  <img src="/assets/figures/2026-07-29-arm-cpu/bars_all_vs_bf16_baseline.png" alt="Bar chart showing serving speedups for optimized BF16, INT8 W8A8, and INT8 W4A8 configurations relative to the October 2025 BF16 baseline" />
  <figcaption><em>Serving speedups for optimized BF16, INT8 W8A8, and INT8 W4A8 configurations relative to the October 2025 BF16 baseline.</em></figcaption>
</figure>

<p>Beyond the measured performance gains, these improvements make vLLM a more complete and production-ready inference stack for Arm Neoverse-based servers through broader feature coverage, better out-of-the-box usability, upstream integration, and expanded model support.</p>

<h2 id="acknowledgements">Acknowledgements</h2>

<p>We thank the vLLM community for their continued support and collaboration.</p>

<p>Special thanks to <a href="https://github.com/bigPYJ1151">Li Jiang</a> (Intel®) for maintaining the vLLM CPU backend and implementing much of the infrastructure this work builds on. We also thank <a href="https://github.com/sanketkaleoss">Sanket Kale</a> (Fujitsu) for the initial Arm CPU enablement in vLLM, and <a href="https://github.com/Shreyas-fuj">Shreyas</a> (Fujitsu) for contributing SVE256 INT8 kernels to oneDNN.</p>

<hr />

<p><small>
Arm is a registered trademark of Arm Limited (or its subsidiaries or affiliates).<br />
PyTorch is a trademark of The Linux Foundation.<br />
Intel and oneDNN are trademarks of Intel Corporation or its subsidiaries.<br /><br />
This blog post is Copyright 2026 Arm Limited and/or its affiliates &lt;open-source-office@arm.com&gt;
</small></p>]]></content><author><name>Arm Team</name></author><category term="hardware" /><category term="performance" /><summary type="html"><![CDATA[Introduction]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vllm-project.github.io/assets/figures/2026-07-29-arm-cpu/bars_all_vs_bf16_baseline.png" /><media:content medium="image" url="https://vllm-project.github.io/assets/figures/2026-07-29-arm-cpu/bars_all_vs_bf16_baseline.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Parallel All the Way Down: Beyond Single-Token Generation with Speculative Decoding</title><link href="https://vllm-project.github.io/2026/07/28/speculators-parallel-drafting.html" rel="alternate" type="text/html" title="Parallel All the Way Down: Beyond Single-Token Generation with Speculative Decoding" /><published>2026-07-28T00:00:00+00:00</published><updated>2026-07-28T00:00:00+00:00</updated><id>https://vllm-project.github.io/2026/07/28/speculators-parallel-drafting</id><content type="html" xml:base="https://vllm-project.github.io/2026/07/28/speculators-parallel-drafting.html"><![CDATA[<h1 id="1-introduction">1. Introduction</h1>

<p>Speculative decoding has emerged as a core optimization technique for mitigating memory-bandwidth bottlenecks in Large Language Model (LLM) serving. By validating multiple candidate tokens in a single verifier-model forward pass, it allows production systems to achieve substantial inference speedups.</p>

<p>However, as serving infrastructure evolves, traditional speculative frameworks face a structural ceiling rooted in the way draft tokens are generated. Today, we are excited to showcase how <a href="https://github.com/vllm-project/speculators">Speculators</a> and <a href="https://github.com/vllm-project/vllm">vLLM</a> are moving beyond these limitations by providing full open-source support for three state-of-the-art parallel drafting algorithms: <a href="https://arxiv.org/abs/2602.01469">P-EAGLE</a>, <a href="https://arxiv.org/abs/2602.06036">DFlash</a> and <a href="https://arxiv.org/abs/2607.05147">DSpark</a>.</p>

<p align="center">
<div class="artifact-image-grid">
<img src="/assets/figures/2026-07-28-speculators-parallel-drafting/compare_interactivity_qwen38b_math.png" width="31%" />
<img src="/assets/figures/2026-07-28-speculators-parallel-drafting/compare_interactivity_qwen330b_humaneval.png" width="31%" />
<img src="/assets/figures/2026-07-28-speculators-parallel-drafting/compare_interactivity_gemma431b_humaneval.png" width="31%" />
</div>


<br />
<em>Figure 1. Parallel drafting algorithms, such as P-EAGLE, DFlash and DSpark, provide significant performance gains when compared to autoregressive drafting algorithms such as EAGLE-3. Speculator models mentioned above can be found in the [Speculators Collection](https://huggingface.co/collections/RedHatAI/speculator-models) at the RedHatAI HuggingFace Hub.</em>
</p>

<h1 id="2-the-limits-of-recursive-drafting">2. The Limits of Recursive Drafting</h1>

<p>The introduction of frameworks like <a href="https://arxiv.org/abs/2401.15077">EAGLE</a> and <a href="https://arxiv.org/abs/2404.19737">MTP</a> marked a major paradigm shift in speculative decoding. Instead of forcing the speculator model to guess blindly from surface-level text, EAGLE demonstrated that a speculator architecture could tap directly into the verifier model’s rich internal hidden states, dramatically increasing token acceptance rates.</p>

<p>Despite this breakthrough, advanced iterations like <a href="https://arxiv.org/abs/2503.01840">EAGLE-3</a> still operate under a fundamental constraint: <strong>auto-regressive drafting</strong>. To propose a sequence of candidate tokens, the speculator architecture must generate them sequentially, executing a separate forward pass for every single token.</p>

<p>This auto-regressive design introduces two major trade-offs in production:</p>

<ul>
  <li><strong>Constraints on Model Size:</strong> Because the drafting cost scales linearly with the speculation length, speculator models are forced to remain extremely small and lightweight to avoid consuming the execution time saved during verifier-model verification.</li>
  <li><strong>Complex Operational Tuning:</strong> Linear scaling heavily limits the number of drafted tokens in practice. Choosing the optimal speculation length (K) becomes a sensitive variable that engineering teams must constantly adjust depending on the specific use case and real-time server loading.</li>
</ul>

<p align="center">
<img src="/assets/figures/2026-07-28-speculators-parallel-drafting/ar_vs_parallel.jpg" width="100%" />
<em>Figure 2. Parallel drafting generates multiple draft tokens in a single step, whereas auto-regressive drafting generates one draft token per step.
</em>
</p>

<h1 id="3-the-shift-to-parallel-drafting">3. The Shift to Parallel Drafting</h1>

<p>Parallel drafting fundamentally re-engineers this trade-off by eliminating sequential execution from the drafting phase entirely. Rather than looping through single-token generation steps, parallel drafting algorithms predict an entire candidate block of tokens concurrently.</p>

<p>By flattening the drafting phase into a single forward pass, the latency of generating proposals is decoupled from the number of tokens speculated. This architectural shift simplifies production serving in two distinct ways:</p>

<ul>
  <li>
    <p><strong>Capacity for Expressiveness:</strong> Because the speculator model only runs once per block, developers can utilize larger, more robust, and more expressive draft architectures. These deeper speculator models capture more complex context and yield higher acceptance rates without introducing a sequential latency penalty.</p>
  </li>
  <li>
    <p><strong>Simplified Parameter Tuning:</strong> Decoupling drafting cost from block length removes the operational burden of hyper-tuning speculation parameters based on fluctuating server loads.</p>
  </li>
</ul>

<p>Parallel drafting as a concept has been explored before — <a href="https://arxiv.org/abs/2401.10774">Medusa</a> and <a href="https://arxiv.org/abs/2504.18583">PARD</a> are notable earlier examples. P-EAGLE, DFlash, and DSpark build on this foundation by combining parallel execution with deep verifier-state conditioning, the insight that made EAGLE so successful.</p>

<h1 id="4-under-the-hood-inference--training-architecture">4. Under the Hood: Inference &amp; Training Architecture</h1>

<p><strong>P-EAGLE</strong>, <strong>DFlash</strong>, and <strong>DSpark</strong> all build upon the verifier model’s hidden states to generate draft tokens in parallel, but each takes a different path to get there. Figure 3 illustrates their architectures side-by-side.</p>

<p align="center">
<img src="/assets/figures/2026-07-28-speculators-parallel-drafting/diagram.jpg" width="100%" />
<em>Figure 3. Comparison between P-EAGLE, DFlash and DSpark. P-EAGLE ingests hidden states from the verifier as part of the speculator model inputs. DFlash projects hidden states into KV-cache. DSpark builds on a DFlash backbone and adds sequential correction and confidence estimator.
</em>
</p>

<p>A shared challenge across all three is training. Any parallel speculator must perform next-K prediction at every token position along a training sequence. For a sequence of length N and a lookahead window of K, naively computing losses across the full matrix causes memory and compute costs to scale prohibitively. Each algorithm addresses this differently.</p>

<h2 id="p-eagle"><strong>P-EAGLE</strong></h2>

<p>P-EAGLE builds directly on EAGLE’s foundation of using the verifier model’s hidden states as input features. Instead of consuming those features to predict tokens sequentially, P-EAGLE maps them across multiple future positions simultaneously, outputting an entire sequence of candidate tokens in a single parallel step.</p>

<p>To keep training tractable, P-EAGLE implements draft block sparsification: it drops tokens along the lookahead dimension (K) according to a decaying rate, concentrating optimization on the most critical immediate tokens while pruning distant future positions from the loss calculation.</p>

<h2 id="dflash"><strong>DFlash</strong></h2>

<p>DFlash routes verifier features differently. Rather than feeding hidden states in as standard inputs, DFlash projects them and injects them directly into the KV-cache of the speculator model. This tightly conditions the speculator’s attention mechanism on the verifier’s exact state without expanding the input sequence length, enabling it to generate a highly accurate block of candidate tokens via block diffusion.</p>

<p>For training, DFlash implements sequence length sparsification. Instead of calculating block loss at every token position across a sequence of length N, it selects random anchor points along the timeline and computes block predictions exclusively at these intersections — preserving GPU memory while maintaining representative coverage.</p>

<h2 id="dspark"><strong>DSpark</strong></h2>

<p>DSpark takes DFlash’s parallel backbone and layers two additional innovations on top. First, it augments the architecture with a lightweight autoregressive correction head, allowing future tokens to be more strongly conditioned on past tokens. This combines the throughput benefits of parallel generation with the sequential coherence of autoregressive refinement.</p>

<p>Second, DSpark addresses a downstream bottleneck: verification cost. Parallel drafting can generate many draft tokens inexpensively, but the verifier must still process all of them. DSpark introduces a confidence head that scores draft tokens before they reach the verifier, selectively forwarding only those likely to be accepted. This reduces wasted verification compute and improves end-to-end throughput.</p>

<h1 id="5-inference-performance">5. Inference Performance</h1>

<p>Figure 1 illustrates the performance gains provided by parallel drafting algorithms when compared to EAGLE-3. Three distinct models and parallel drafting algorithms are displayed:</p>

<table>
  <thead>
    <tr>
      <th>Model</th>
      <th>Algorithm</th>
      <th>Use case</th>
      <th>Hardware</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Qwen3-8B</td>
      <td><a href="https://huggingface.co/RedHatAI/Qwen3-8B-speculator.peagle">P-EAGLE</a></td>
      <td>Math reasoning (GSM8k)</td>
      <td>1xA100</td>
    </tr>
    <tr>
      <td>Qwen3-30B-A3B</td>
      <td><a href="https://huggingface.co/RedHatAI/Qwen3-30B-A3B-speculator.dflash">DFlash</a></td>
      <td>Coding (HumanEval)</td>
      <td>2xA100</td>
    </tr>
    <tr>
      <td>gemma-4-31B-it</td>
      <td><a href="https://huggingface.co/RedHatAI/gemma-4-31B-it-speculator.dspark">DSpark</a></td>
      <td>Coding (HumanEval)</td>
      <td>2xA100</td>
    </tr>
  </tbody>
</table>

<p>In all cases, parallel drafting shows significant improvement over EAGLE-3. Performance will vary across models, tasks, and hardware configurations — we encourage the community to benchmark on their own workloads.</p>

<h1 id="6-production-serving-with-vllm-and-speculators">6. Production Serving with vLLM and Speculators</h1>

<p>Integrating state-of-the-art parallel drafting algorithms into production requires a stable, optimized infrastructure stack. The Speculators repository provides a unified ecosystem to train and evaluate these next-gen models, fully integrated with <strong>vLLM</strong>.</p>

<p>Launching a parallel-backed speculative engine is as straightforward as passing the appropriate configuration flags at initialization:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vllm serve Qwen/Qwen3-30B-A3B <span class="se">\</span>
  <span class="nt">--tensor-parallel-size</span> 2 <span class="se">\</span>
  <span class="nt">--reasoning-parser</span> qwen3 <span class="se">\</span>
  <span class="nt">--speculative-config</span> <span class="s1">'{
    "model": "RedHatAI/Qwen3-30B-A3B-speculator.dflash",
    "num_speculative_tokens": 7,
    "method": "dflash"
  }'</span>
</code></pre></div></div>

<p>By moving from single-token generation to block-level parallel drafting, your inference pipeline becomes parallel all the way down—maximizing hardware utilization and delivering sustained, lossless acceleration. (Speculative decoding preserves the verifier model’s output distribution exactly via rejection sampling, so quality is mathematically identical to standard decoding.)</p>

<h1 id="7-get-started">7. Get Started</h1>

<p>Parallel drafting is fully supported, open-source, and production-ready today. We invite the community to explore the repository, utilize our documented training pathways to build your own parallel speculators, and benchmark them natively in vLLM.</p>

<ul>
  <li>Repository: <a href="http://github.com/vllm-project/speculators">Speculators</a></li>
  <li>Pre-trained speculators: <a href="https://huggingface.co/collections/RedHatAI/speculator-models">Speculators Collection on HuggingFace</a></li>
  <li>Training guides: <a href="https://github.com/vllm-project/speculators/blob/main/docs/user_guide/tutorials/index.md">Speculator tutorials</a></li>
</ul>

<h1 id="errata">Errata</h1>

<p>The plots in Figure 1 were updated on 7/29/26. The numbers in the original plots proved to be inconsistent with the reported benchmarking conditions due to an erroneous environment setup. However, the relative behavior between models was consistent and the conclusions in the blog are not changed.</p>]]></content><author><name>Alexandre Marques, Megan Flynn, Helen Zhao, Krishna Teja Chitty Venkata, Chibueze Ukachi (Red Hat AI)</name></author><category term="speculators" /><category term="speculative_decoding" /><category term="peagle" /><category term="dflash" /><category term="dspark" /><summary type="html"><![CDATA[1. Introduction]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vllm-project.github.io/assets/figures/2026-07-28-speculators-parallel-drafting/ar_vs_parallel.jpg" /><media:content medium="image" url="https://vllm-project.github.io/assets/figures/2026-07-28-speculators-parallel-drafting/ar_vs_parallel.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Kimi K3 Is Here: Efficient Day-0 Support on vLLM</title><link href="https://vllm-project.github.io/2026/07/27/k3.html" rel="alternate" type="text/html" title="Kimi K3 Is Here: Efficient Day-0 Support on vLLM" /><published>2026-07-27T00:00:00+00:00</published><updated>2026-07-27T00:00:00+00:00</updated><id>https://vllm-project.github.io/2026/07/27/k3</id><content type="html" xml:base="https://vllm-project.github.io/2026/07/27/k3.html"><![CDATA[<p>We’re thrilled to announce efficient day-0 vLLM support for Kimi K3, one of the most powerful open-weight models ever released.</p>

<p>Last week, <a href="https://vllm.ai/blog/2026-07-22-kimi-k3-preview">we previewed</a> the production-scale integration work for Kimi K3; today, Moonshot AI’s weights are public and the support is live.</p>

<p><img src="/assets/figures/2026-07-27-k3/social-preview.png" alt="Kimi K3 day-0 support on vLLM" /></p>

<p>Kimi K3 is a 2.8-trillion-parameter Mixture-of-Experts model (16 of 896 experts active per token) built on Kimi Delta Attention (KDA) and Attention Residuals (AttnRes), with a 1M-token context window and native vision. For us, the most exciting challenge Kimi K3 brings is making KDA, MXFP4 MoE, KV cache management, prefill/decode disaggregation, speculative decoding, and long-context deployment recipes work together in a runnable serving engine.</p>

<p>The <a href="https://vllm.ai/blog/2026-07-22-kimi-k3-preview">preview post</a> explained the kernel and cache architecture, in particular the challenge of prefix caching that works on recurrent state. This release post is the practical guide: how vLLM adapts to Kimi K3’s architecture, the kernel work behind the numbers, and what is ready on day 0.</p>

<h2 id="quick-start">Quick start</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># See the linked recipes for the exact Docker command.</span>
vllm serve moonshotai/Kimi-K3 <span class="se">\</span>
  <span class="nt">--tensor-parallel-size</span> 8 <span class="se">\</span>
  <span class="nt">--trust-remote-code</span> <span class="se">\</span>
  <span class="nt">--load-format</span> fastsafetensors <span class="se">\</span>
  <span class="nt">--enable-prefix-caching</span> <span class="se">\</span>
  <span class="nt">--enable-auto-tool-choice</span> <span class="se">\</span>
  <span class="nt">--tool-call-parser</span> kimi_k3 <span class="se">\</span>
  <span class="nt">--reasoning-parser</span> kimi_k3
</code></pre></div></div>

<p>The easiest way to run Kimi K3 is to use 8 NVIDIA B300 GPUs or 8 AMD MI355X GPUs with the above command.</p>

<p>Inferact has also trained and open-sourced a <a href="https://huggingface.co/Inferact/Kimi-K3-DSpark">DSpark speculator</a> for Kimi K3. Enable it by adding the following option to the serve command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">--speculative-config</span> <span class="s1">'{"model":"Inferact/Kimi-K3-DSpark","method":"dspark","num_speculative_tokens":7,"attention_backend":"FLASHINFER_MLA","draft_sample_method":"probabilistic","rejection_sample_method":"block"}'</span>
</code></pre></div></div>

<p>For more details, including Docker images for various platforms and deployment strategies, refer to the detailed <a href="https://recipes.vllm.ai/moonshotai/Kimi-K3">recipes</a>. Because of complicated dependencies, only Docker images are usable now. The Docker images depend on several pre-release dependencies, including <a href="https://github.com/flashinfer-ai/flashinfer">FlashInfer</a>.</p>

<h2 id="tldr">TL;DR</h2>

<ul>
  <li><strong>A 2.8-trillion-parameter multimodal MoE:</strong> Kimi K3 activates 16 of 896 experts per token, supports a context window of up to 1M tokens, and is built on Kimi Delta Attention, Attention Residuals, LatentMoE, and native MXFP4 (4-bit) weights.</li>
  <li><strong>Up to 370 tok/s per user:</strong> vLLM serves Kimi K3 at 118 tok/s without speculative decoding and 370 tok/s (a 3.14× improvement) with DSpark on 16 NVIDIA GB300 NVL72 GPUs, powered by extensive optimizations for Kimi K3’s architecture.</li>
  <li><strong>Broad production feature support:</strong> vLLM supports speculative decoding, prefill/decode disaggregation, agentic KV caching with Mooncake, tool calling, reasoning output, and structured output, with NVIDIA (Hopper and Blackwell) and AMD (MI355X) support at launch.</li>
  <li><strong>Open-source DSpark support:</strong> vLLM supports the state-of-the-art block-diffusion speculative decoding algorithm for Kimi K3, trained with vLLM and TorchSpec and open-sourced by Inferact.</li>
  <li><strong>Hybrid prefix caching:</strong> serving Kimi K3’s recurrent and full-attention design required a redesign of hybrid prefix caching over recurrent KDA state. This change now benefits every hybrid linear model.</li>
</ul>

<h2 id="kimi-k3s-architecture-and-how-vllm-serves-it">Kimi K3’s architecture, and how vLLM serves it</h2>

<p><img src="/assets/figures/2026-07-27-k3/architecture.png" alt="Kimi K3 architecture innovations" /></p>

<p><em>Kimi K3 architecture innovations, from the <a href="https://www.kimi.com/blog/kimi-k3">original release blog post</a>.</em></p>

<p>Kimi K3’s architecture departs from a standard Transformer in a few ways, and each one changes what a serving engine has to do. The <a href="https://vllm.ai/blog/2026-07-22-kimi-k3-preview">preview post</a> covers the internals in depth; here we recap what’s new and focus on how vLLM adapts to serve it.</p>

<h3 id="kimi-delta-attention-a-hybrid-recurrent--full-attention-stack">Kimi Delta Attention: a hybrid recurrent + full-attention stack</h3>

<p><strong>What’s new:</strong> Most of Kimi K3’s layers are KDA, a linear-attention mechanism that keeps a fixed-size recurrent state instead of a growing KV cache, interleaved with periodic full-attention layers that preserve exact global recall. That is what makes a 1M-token context affordable.</p>

<p><strong>How vLLM serves it:</strong> A single hybrid KV-cache manager holds two kinds of memory side by side under one scheduler: paged KV blocks for the full-attention layers, and compact recurrent-state blocks for the KDA layers. A dedicated KDA attention backend runs FlashKDA for prefill and a fused CUDA kernel (or the Flash-Linear-Attention/Triton path when running speculative decoding) for decode.</p>

<p>The hardest part is prefix caching across Kimi K3’s hybrid cache: full-attention layers store per-token KV, while KDA layers update recurrent and convolution state at every token but cannot afford to retain a snapshot at every possible prefix boundary. vLLM decouples the large physical KDA state blocks from fine-grained prefix matching, registering state snapshots within those blocks and copying them before extension so long shared prompts can reuse both KDA state and paged KV. This hybrid-cache machinery is <a href="https://vllm.ai/blog/2026-07-22-kimi-k3-preview">new in vLLM core</a> and now benefits every hybrid model similar to Kimi K3.</p>

<p><img src="/assets/figures/2026-07-27-k3/hybrid-cache.png" alt="Kimi K3's hybrid KDA and full-attention cache" /></p>

<p><em>Kimi K3 interleaves Kimi Delta Attention layers with periodic full-attention layers; vLLM’s hybrid cache manages recurrent state and paged KV together.</em></p>

<h3 id="attention-residuals-learned-mixing-of-residual-contributions-across-depth">Attention Residuals: learned mixing of residual contributions across depth</h3>

<p><strong>What’s new:</strong> For each token, Block AttnRes replaces ordinary residual accumulation with depth-wise attention: every Transformer sublayer uses a learned pseudo-query to weight RMS-normalized residual states from preceding layer blocks, then receives the corresponding weighted combination as its input.</p>

<p><strong>How vLLM serves it:</strong> vLLM uses optimized Triton and CUDA kernels to compute the depth-wise attention logits, softmax, and hidden-state aggregation in a single fused operation. Residual updates and output RMSNorm are folded into the same kernel where supported, reducing intermediate memory traffic and kernel-launch overhead in both prefill and decode.</p>

<h3 id="stable-latentmoe-quantile-balanced-latent-space-experts-at-16-of-896-sparsity">Stable LatentMoE: quantile-balanced latent-space experts at 16-of-896 sparsity</h3>

<p><strong>What’s new.</strong> <a href="https://research.nvidia.com/labs/nemotron/LatentMoE/">LatentMoE</a>, introduced by NVIDIA, projects dispatched token activations into a narrower latent dimension for routed-expert computation, then projects the combined expert output back to the model width—reducing expert-weight bandwidth and all-to-all traffic so more experts can be used at similar inference cost. Kimi K3’s <a href="https://www.kimi.com/blog/kimi-k3">Stable LatentMoE</a> scales this design to 896 experts with 16 active per token and uses <a href="https://kexue.fm/archives/11619">Quantile Balancing</a> to derive expert allocation from router-score quantiles instead of heuristic balancing updates.</p>

<p><strong>How vLLM serves it:</strong> Experts are sharded with expert parallelism. vLLM offers two MoE backends tuned for different topologies: TRT-LLM-Gen for tensor-parallel (TP &gt; 1) and MegaMoE for disaggregated/expert-parallel (DEP). It also supports optional Expert-Parallel Load Balancing (EPLB) to ensure each rank has a similar amount of compute. The weights execute natively in MXFP4 on the MoE path.</p>

<h3 id="chat-template-a-render-program-not-a-jinja-template">Chat template: a render program, not a Jinja template</h3>

<p><strong>What’s new:</strong> Kimi K3’s chat template must encode system, user, and assistant messages, multimodal content, tool definitions, and tool results using exact control tokens. Instead of the common approach of a <a href="https://huggingface.co/moonshotai/Kimi-K2.7-Code/blob/main/chat_template.jinja">Jinja chat template</a> that renders the request as text before tokenization, Kimi K3 uses a Python program to build the prompt token sequence directly. Its output likewise contains distinct regions for reasoning, answer text, and tool calls that must be parsed into an API response.</p>

<p><strong>How vLLM serves it:</strong> vLLM implements both the input renderer and streaming output parser in its Python and Rust frontends, preserving control-token boundaries while treating user-supplied and tool-supplied text as ordinary content. For tool calls and structured outputs, vLLM integrates Kimi K3’s format with <a href="https://xgrammar.mlc.ai/">XGrammar</a> so structured regions are constrained during decoding and returned as separate reasoning, content, and tool-call fields.</p>

<h2 id="built-for-production">Built for production</h2>

<p>Serving a 2.8T hybrid MoE well means being fast for each user, efficient for many concurrent sessions, and scalable for agents. vLLM ensures Kimi K3 is ready on all three.</p>

<h3 id="ultra-low-latency-speculative-decoding-with-dspark">Ultra-low latency: speculative decoding with DSpark</h3>

<p>To reach ultra-low latency on a 2.8T-parameter model like Kimi K3 without accuracy loss, speculative decoding is the natural choice. That is why vLLM supports DSpark, a state-of-the-art speculative decoding algorithm, from day 0—and why we trained and released a <a href="https://huggingface.co/Inferact/Kimi-K3-DSpark">DSpark speculator</a> for Kimi K3. The draft model is trained with vLLM using <a href="https://github.com/lightseekorg/TorchSpec">TorchSpec</a> to achieve full numerical parity between speculator inference and training.</p>

<p>DSpark uses a block-diffusion backbone to generate multiple speculative tokens in one parallel pass based on Kimi K3’s rich intermediate states, so drafting cost stays flat as the block deepens. A low-rank Markov head supplies the intra-block dependency, and a confidence head predicts the likelihood for each draft to be accepted. We made the draft MLA-native, mirroring Kimi K3’s own attention, so draft and target share a similar KV layout to be maximally compatible with advanced KV management and P/D-disaggregated setups.</p>

<p><img src="/assets/figures/2026-07-27-k3/dspark-acceptance-rates.png" alt="Kimi K3 DSpark positional acceptance rates" /></p>

<p><em>Kimi K3 DSpark positional acceptance rates across various datasets.</em></p>

<p>With DSpark, we achieve a 3.14× speedup on a single-user request, from 118 tok/s to 370 tok/s, measured using SPEED Bench. We also benchmarked the acceptance rate and speedups on different tasks, with the results shown above. For coding and other low-entropy tasks, we achieve around 4.73 accepted tokens per step. For high-entropy tasks such as creative writing, we achieve around 2.61 accepted tokens per step.</p>

<p>Confidence-based scheduling with DSpark is an ongoing effort in vLLM. Once enabled, it uses the confidence head included in the DSpark model to predict how likely each drafted token is to be accepted, prioritizing strong proposals and pruning weak ones so verification is not spent on tokens that will not survive.</p>

<p>Both the <a href="https://huggingface.co/Inferact/Kimi-K3-DSpark">draft model</a> and the inference support are open source as of this release. See the deployment guide below to enable it.</p>

<p><img src="/assets/figures/2026-07-27-k3/dspark-schematic.png" alt="DSpark draft-and-verify flow for Kimi K3" /></p>

<p><em>A lightweight DSpark draft proposes candidate tokens that Kimi K3 verifies in a single parallel pass, accelerating single-stream decode.</em></p>

<h3 id="sequence-parallelism-for-tep-prefill">Sequence parallelism for TEP prefill</h3>

<p><img src="/assets/figures/2026-07-27-k3/sequence-parallelism.jpg" alt="Sequence parallelism for TEP prefill" /></p>

<p><em>Sequence parallelism shards token ownership across ranks; the attention residual is applied per shard, and one all-gather rebuilds the full batch before the next layer’s QKV projection.</em></p>

<p>For the prefill phase, we combine attention tensor parallelism with MoE expert parallelism (TEP). Compared with pure TP, TEP reduces communication overhead and keeps whole experts on each rank, yielding more efficient expert GEMM shapes.</p>

<p>However, the naive TEP implementation performs two all-reduce operations per layer—one after the attention output projection and one after the MoE—so every rank materializes the full batch and redundantly applies the attention residual to all of it. To address this, we implement <a href="https://arxiv.org/abs/2205.05198">sequence parallelism</a>: the all-reduce after <code class="language-plaintext highlighter-rouge">o_proj</code> is replaced with a reduce-scatter so that each rank owns a shard of the tokens, the attention residual is applied per shard, the MoE’s all-to-all performs dispatch and combine, and a single all-gather restores the full batch before the next layer’s QKV projection.</p>

<p>This design provides two key advantages:</p>

<ul>
  <li><strong>Reduced communication overhead:</strong> Reduce-scatter + all-to-all dispatch + all-to-all combine + all-gather are theoretically cheaper than two all-reduces. In practice, however, NCCL’s reduce-scatter and all-gather are not optimized for prefill’s message sizes and yield no speedup. We therefore implement custom reduce-scatter and all-gather kernels that are 1.7×–4.5× faster than NCCL, especially at small-to-medium message sizes.</li>
  <li><strong>Sharded attention residual:</strong> The attention residual stays sharded across ranks throughout the layer, so each rank computes and maintains only its shard of the tokens rather than the entire batch. This matters especially for Kimi K3, where AttnRes turns the residual stream into persistent cross-layer state with its own compute and memory footprint.</li>
</ul>

<p>Sequence parallelism is enabled by default when appropriate: when using TP with the MegaMoE kernel, or when combining TP + DP + EP. No extra flags are needed.</p>

<h3 id="large-scale-serving-prefilldecode-disaggregation">Large-scale serving: prefill/decode disaggregation</h3>

<p>For high-throughput settings, vLLM serves Kimi K3 with expert and data parallelism across nodes and with prefill/decode (PD) disaggregation, which runs prefill-heavy and decode-heavy work on separate replicas so each is sized for its own bottleneck. One of our validated topologies routes TEP8 prefill to DEP16 decode, with NIXL as the KV transfer engine.</p>

<p>PD disaggregation is unforgiving for a hybrid model: the recurrent KDA state, the full-attention paged KV, and the block tables all have to arrive correctly. The NIXL connector treats the shared KV-cache page as two logical views: token-level MLA cache and request-level KDA state, including convolution and recurrent state. During the handshake, it exchanges the MLA/KDA metadata, then builds separate transfer descriptors for each transfer.</p>

<p>Under heterogeneous TP, vLLM’s hybrid allocator uses different block sizes for prefill and decode. To support that case, vLLM’s NIXL connector tracks the logical-to-physical block mapping and zeroes any untransferred tail regions, preventing stale data from previous requests from leaking through padding or layout gaps.</p>

<p><img src="/assets/figures/2026-07-27-k3/pd-disaggregation-animation.gif" alt="Prefill/decode disaggregation flow" /></p>

<h3 id="reconciling-partial-block-cache-hits-and-kv-cache-offloading">Reconciling partial block cache hits and KV cache offloading</h3>

<p>As described in the <a href="https://vllm.ai/blog/2026-07-22-kimi-k3-preview">Kimi K3 preview</a>, vLLM supports fine-grained prefix hits that may end inside a physical cache block. This introduces a subtle challenge for KV offloading: vLLM may first find a local GPU hit with a partial tail, then discover a longer prefix in an external store such as Mooncake. With full-block hits, remote reuse extends cleanly beyond the local prefix. A partial tail, however, can overlap with the remote result.</p>

<p>The vLLM scheduler therefore compares the exact reusable token lengths from both tiers and selects the longer prefix. If the remote hit wins, it releases the block reserved for the shorter local tail and reconciles all cache groups to the new prefix length.</p>

<p>Importantly, we built this mechanism entirely through the existing KV Connector APIs, which already provide all the required semantics. This allows <code class="language-plaintext highlighter-rouge">MooncakeStoreConnector</code>, <code class="language-plaintext highlighter-rouge">SimpleCPUOffloadConnector</code>, and other connectors to support multi-tier partial-prefix reuse without model-specific integration paths.</p>

<p>The design is tracked in the <a href="https://github.com/vllm-project/vllm/issues/45702">RFC</a> and implemented across <a href="https://github.com/vllm-project/vllm/pull/45939">PR #45939</a>, <a href="https://github.com/vllm-project/vllm/pull/46384">PR #46384</a>, and <a href="https://github.com/vllm-project/vllm/pull/49502">PR #49502</a>.</p>

<h3 id="agentic-serving-smarter-cache-retention-policies">Agentic serving: smarter cache retention policies</h3>

<p>Kimi K3’s linear-attention layers require only a constant-size KDA state, making them memory-efficient at long context lengths. A single layer’s KDA state is roughly equivalent to the MLA cache for a few thousand tokens. Although large, this state does not grow with sequence length, unlike a conventional KV cache. That distinction becomes significant for agentic workloads spanning hundreds of thousands to one million tokens.</p>

<p>The same design complicates prefix caching. KDA state is updated in place during decoding, so vLLM must copy the state at a selected prefix boundary before the next forward pass overwrites it. Caching at every token position would be prohibitively expensive: each KDA checkpoint is much larger than one token’s MLA cache and would quickly exhaust even a distributed cache pool.</p>

<p>To improve cache-space efficiency while preserving useful prefixes, vLLM supports two complementary retention policies.</p>

<h4 id="interval-based-retention">Interval-based retention</h4>

<p>Caching every KDA state is wasteful, but caching too sparsely forces the next request to recompute a large suffix. Interval-based retention balances these costs by treating selected positions as checkpoints—for example, one every 32K tokens.</p>

<p>Prompt boundaries are even better checkpoints. In agentic workloads, the next turn usually begins by replaying the previous turn’s prompt, so the state at the end of that prompt is especially likely to be reused. vLLM detects and retains these boundaries automatically.</p>

<p>Users can control periodic checkpointing with <code class="language-plaintext highlighter-rouge">VLLM_PREFIX_CACHE_RETENTION_INTERVAL</code>. Setting it to <code class="language-plaintext highlighter-rouge">0</code> disables periodic checkpoints and retains only prompt-end states, which is a good fit for workloads dominated by multi-turn conversations. Larger intervals trade some recomputation for lower cache usage.</p>

<p>Interval-based retention was introduced for DeepSeek V4 and hybrid sliding-window-attention models in <a href="https://github.com/vllm-project/vllm/pull/43447">PR #43447</a>, with day-0 support for Kimi K3 and hybrid linear-attention models added in <a href="https://github.com/vllm-project/vllm/pull/45845">PR #45845</a>.</p>

<p><img src="/assets/figures/2026-07-27-k3/interval-cache-retention.png" alt="Interval-based KDA cache retention" /></p>

<p><em>Interval-based cache retention. MLA caches KV for every block, while a KDA state is kept only at checkpoints: prompt ends (green) are always retained, and fixed-interval checkpoints (orange) are configurable.</em></p>

<h4 id="marconi-style-selective-retention">Marconi-style selective retention</h4>

<p>Prompt-end retention works well for conversational state, but valuable shared prefixes can appear elsewhere. A system prompt, repository snapshot, or tool specification may be reused across many requests without aligning with a prompt boundary.</p>

<p><a href="https://mlsys.org/virtual/2025/poster/3260">Marconi-style retention (MLSys ‘25)</a> handles these cases with a simple rule: cache on the second hit. The first observation provides evidence that the prefix exists; the second shows that it is actually shared. Only then does vLLM spend cache capacity on its KDA state.</p>

<p>This turns retention into a demand-driven decision. One-off prefixes do not crowd the cache, while recurring prefixes are promoted automatically—without requiring users to predict which parts of their workload will become hot.</p>

<p>Selective retention was introduced in <a href="https://github.com/vllm-project/vllm/pull/37898">PR #37898</a>, with day-0 Kimi K3 support added in <a href="https://github.com/vllm-project/vllm/pull/47782">PR #47782</a>.</p>

<p><img src="/assets/figures/2026-07-27-k3/selective-cache-retention.gif" alt="Selective KDA cache retention" /></p>

<p><em>Selective cache retention. Request 1 keeps a KDA state only at its own prompt end, past the shared prefix, so request 2 gets a KV hit but a KDA miss. That second sighting is evidence the prefix is shared, so a state is cached at the prefix boundary, and request 3 reuses it.</em></p>

<p>Together, the policies cover both predictable and emergent reuse: interval retention checkpoints structurally important boundaries, while Marconi-style retention learns which other prefixes are worth keeping.</p>

<h2 id="performance-optimizations">Performance optimizations</h2>

<p>Serving large models like Kimi K3 brings its own challenges because of its size. The entire model can barely fit in a single NVIDIA DGX B300 and requires a minimum of 16 NVIDIA B200/GB200 GPUs to serve on that hardware generation. Serving must trade off interactivity against total system throughput: tensor parallelism is good for interactivity but offers low overall throughput because effective KV cache size is limited, while large-scale expert parallelism can limit per-user output-token speed because of network-bandwidth bottlenecks. Here we highlight optimizations that improve performance in both cases so users can choose the recipe that suits their workload. Many of these optimizations are already covered in our <a href="https://vllm.ai/blog/2026-07-22-kimi-k3-preview">preview blog</a>.</p>

<h3 id="attention-residuals">Attention Residuals</h3>

<p>Kimi K3 uses Block AttnRes, attending over up to eight cached block representations plus the current within-block residual. For each token, vLLM computes logits from RMS-normalized sources, applies softmax across these depth-wise candidates, and aggregates their representations. Its implementation resembles FlashAttention’s online-softmax strategy but operates across model depth rather than sequence positions and has at most nine sources. vLLM performs this mixing in a single fused kernel, incorporating the residual update at the input and optionally applying RMSNorm to the output. A portable Triton implementation covers the general path, while a specialized CUDA kernel accelerates supported Blackwell configurations.</p>

<h3 id="kda-decode">KDA decode</h3>

<p><img src="/assets/figures/2026-07-27-k3/kda-decode.png" alt="Fused KDA decode kernel" /></p>

<p><em>The fused KDA decode kernel folds the causal convolution, recurrent update, and RMSNorm into a single launch instead of a chain of separate kernels.</em></p>

<p>A KDA layer involves many operations: input projections, causal 1D convolutions, QK norm, gate computation, KDA recurrent update, and output gated RMSNorm. On supported configurations, vLLM fuses the post-projection decode path—from the causal convolutions through gated RMSNorm—into a single specialized CUDA kernel. The kernel updates the convolution and recurrent states in place and writes the normalized output directly, avoiding intermediate tensors, repeated state traffic, and per-operation launch overhead across Kimi K3’s many KDA layers. Portable Triton fallback paths cover unsupported configurations.</p>

<h3 id="kda-prefill">KDA prefill</h3>

<p>KDA prefill became one of our favorite examples of open-source development in practice. Moonshot AI first released <a href="https://github.com/MoonshotAI/FlashKDA">FlashKDA</a>, a high-performance CUTLASS implementation of KDA. We quickly integrated it into vLLM and worked through less glamorous production details: broader GPU coverage, metadata dtypes, tensor layouts, and reliable vendoring. <a href="https://github.com/Itssshikhar">Shikhar Mishra</a> then optimized the kernels for H100 and published <a href="https://github.com/Itssshikhar/Flash-Flash-KDA">Flash-Flash-KDA</a>, improving data movement while preserving numerical correctness. Within a day, we validated the improvements on GB300 NVL72, refined the recurrence pipeline and synchronization, and folded them into our FlashKDA integration. The result was not a one-way handoff, but a continuous loop in which an open kernel was extended by the serving community, improved by an independent contributor, and quickly carried into production.</p>

<h3 id="kda-metadata-builder">KDA metadata builder</h3>

<p><img src="/assets/figures/2026-07-27-k3/kda-metadata-builder.png" alt="Nsight Systems traces before and after KDA metadata preparation optimization" /></p>

<p>During Kimi K3 DSpark bring-up, KDA metadata preparation emerged as a significant source of overhead. Kimi K3 initially reused the generic GDN metadata builder, which prepared FLA metadata that K3 does not consume and used sequences of small eager PyTorch operations to assemble and stage GPU metadata. We introduced a dedicated Kimi K3 KDA metadata builder that prunes the unused paths and replaces those operation sequences with fused Triton kernels, reducing each sequence to a single launch. At batch size 1, this reduced metadata-preparation latency by 96%, from 870 μs to 34 μs, and improved end-to-end DSpark latency by 6%.</p>

<h3 id="low-latency-bf16-gemm">Low-latency BF16 GEMM</h3>

<p>In low-batch-size, latency-sensitive settings, we replace generic BF16 GEMM—used in several linear projection layers—with our own <code class="language-plaintext highlighter-rouge">skinnyGEMM</code> implementation. Generic cuBLAS kernels do not achieve the best performance here because they are optimized for more general shapes. In the kernel, we bypass shared-memory data staging, load activations and weights directly into registers, and use CUDA Core FMA instructions to perform the math. This avoids the heavy TMA and Tensor Core setup phase used to achieve maximum throughput. Our microbenchmarks show kernel-level speedups ranging from 8% to 100% and an end-to-end latency reduction of about 10% in small-batch settings.</p>

<h3 id="low-latency-moe-tail-fusion">Low-latency MoE tail fusion</h3>

<p><img src="/assets/figures/2026-07-27-k3/latent-moe-tail-fusion.png" alt="LatentMoE tail-fusion optimization" /></p>

<p><em>The LatentMoE tail optimization replaces two all-reduces, RMSNorm, latent up-projection, and an elementwise add with three kernels to reduce compute and better overlap communication and computation.</em></p>

<p>vLLM uses a novel strategy to reduce latent-MoE tail latency in ultra-low-latency serving. At the end of LatentMoE, the reduced activation from routed experts must be normalized with RMSNorm and up-projected before it is added to the shared-expert output. In the normal TP case, this requires two all-reduces on the routed and shared experts—or one all-reduce with concatenation—and replicates the up-projection.</p>

<p>To avoid redundant compute in the replicated linear projection, vLLM instead performs reduce-scatter on the shared experts and keeps all-reduce on the routed experts because their activations need to be normalized. The replicated routed-expert activation then performs matrix multiplication with the up-projection in a column-parallel fashion and is added elementwise to the already-sharded shared-expert output. Finally, the results are all-gathered onto each rank using broadcast. We observe about a 20% latency reduction in this step and about a 7%–8% end-to-end speedup.</p>

<h2 id="quality-and-performance-benchmarks">Quality and Performance Benchmarks</h2>

<h3 id="accuracy-and-correctness-evaluation">Accuracy and correctness evaluation</h3>

<p>vLLM takes accuracy as seriously as speed. We validated Kimi K3 end to end through a served OpenAI-compatible endpoint, with exact configurations in the recipes, and it passes the accuracy evaluations cleanly. At the maximum reasoning-effort level, Kimi K3 on vLLM scores 0.976 on GSM8K, 0.939 on GPQA-Diamond, 0.889 on OCRBench, and 0.818 on MMMU Pro Vision.</p>

<p>One caveat worth knowing for evaluation: Kimi K3 thinks a lot before it answers. A low score is more often a truncated answer than a wrong one, so increase the reasoning effort, set <code class="language-plaintext highlighter-rouge">max_tokens</code> generously, and check for cut-off generations before debugging anything else.</p>

<h3 id="serving-performance">Serving performance</h3>

<p><img src="/assets/figures/2026-07-27-k3/serving-performance.png" alt="Kimi K3 single-user decode throughput" /></p>

<p><em>Kimi K3 decode throughput at batch size 1, measured on GB300 NVL72 GPUs in TP8 and TP16 configurations.</em></p>

<p>At launch, vLLM achieves 111 tok/s per user on TP8 and 118 tok/s per user on TP16 at batch size 1. DSpark speculative decoding boosts interactivity by roughly 3×, reaching 331 tok/s per user on TP8 and 370 tok/s per user on TP16.</p>

<p><img src="/assets/figures/2026-07-27-k3/pareto-gb300.png" alt="Kimi K3 GB300 NVL72 pareto curve" /></p>

<p>We also present initial Pareto-frontier performance results for serving Kimi K3 on GB300 NVL72 across a range of scenarios, from high-throughput serving at 2K+ TPGS to low-latency serving at 100+ TPS/user.</p>

<h3 id="reproduce-our-benchmark">Reproduce our benchmark</h3>

<p>Here are the full recipes to reproduce the decode throughput numbers above for TP8 with DSpark:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">NCCL_DMABUF_ENABLE</span><span class="o">=</span>0
<span class="nb">export </span><span class="nv">VLLM_ALLREDUCE_USE_FLASHINFER</span><span class="o">=</span>1
<span class="nb">export </span><span class="nv">VLLM_USE_RUST_FRONTEND</span><span class="o">=</span>1
<span class="nb">export </span><span class="nv">VLLM_ENGINE_READY_TIMEOUT_S</span><span class="o">=</span>3600
<span class="nb">export </span><span class="nv">HEAD_ADDR</span><span class="o">=</span>127.0.0.1  <span class="c"># Change if vllm-bench runs on another host.</span>

vllm serve moonshotai/Kimi-K3 <span class="se">\</span>
  <span class="nt">--enable-prefix-caching</span> <span class="se">\</span>
  <span class="nt">--tensor-parallel-size</span> 8 <span class="se">\</span>
  <span class="nt">--nnodes</span> 2 <span class="se">\</span>
  <span class="nt">--node-rank</span> 0 <span class="se">\</span>
  <span class="nt">--moe-backend</span> auto <span class="se">\</span>
  <span class="nt">--trust-remote-code</span> <span class="se">\</span>
  <span class="nt">--load-format</span> fastsafetensors <span class="se">\</span>
  <span class="nt">--max-num-seqs</span> 512 <span class="se">\</span>
  <span class="nt">--gpu-memory-utilization</span> 0.9 <span class="se">\</span>
  <span class="nt">--max-model-len</span> auto <span class="se">\</span>
  <span class="nt">--max-cudagraph-capture-size</span> 256 <span class="se">\</span>
  <span class="nt">--kv-cache-dtype</span> fp8 <span class="se">\</span>
  <span class="nt">--attention-config</span> <span class="s1">'{"mla_prefill_backend":"FLASHINFER","use_prefill_query_quantization":true}'</span> <span class="se">\</span>
  <span class="nt">--speculative-config</span> <span class="s1">'{"model":"Inferact/Kimi-K3-DSpark","method":"dspark","num_speculative_tokens":7,"attention_backend":"FLASHINFER_MLA","draft_sample_method":"probabilistic","rejection_sample_method":"block"}'</span>

<span class="c"># Batch size = 1, 8K/1K random (no speculative decoding)</span>
vllm-bench <span class="se">\</span>
  <span class="nt">--backend</span> openai <span class="se">\</span>
  <span class="nt">--base-url</span> <span class="s2">"http://</span><span class="k">${</span><span class="nv">HEAD_ADDR</span><span class="k">}</span><span class="s2">:8000"</span> <span class="se">\</span>
  <span class="nt">--model</span> moonshotai/Kimi-K3 <span class="se">\</span>
  <span class="nt">--dataset-name</span> random <span class="se">\</span>
  <span class="nt">--random-input-len</span> 8192 <span class="se">\</span>
  <span class="nt">--random-output-len</span> 1024 <span class="se">\</span>
  <span class="nt">--random-range-ratio</span> 0.8 <span class="se">\</span>
  <span class="nt">--prompt-token-ids</span> <span class="se">\</span>
  <span class="nt">--ignore-eos</span> <span class="se">\</span>
  <span class="nt">--sweep-max-concurrency</span> 1 <span class="se">\</span>
  <span class="nt">--sweep-num-prompts-factor</span> 10 <span class="se">\</span>
  <span class="nt">--seed</span> 42 <span class="se">\</span>
  <span class="nt">--percentile-metrics</span> <span class="s2">"ttft,tpot,itl,e2el"</span> <span class="se">\</span>
  <span class="nt">--metric-percentiles</span> <span class="s2">"50,90,99"</span> <span class="se">\</span>
  <span class="nt">--save-result</span>

<span class="c"># Batch size = 1, SPEED Bench (speculative decoding)</span>
vllm-bench <span class="se">\</span>
  <span class="nt">--backend</span> openai <span class="se">\</span>
  <span class="nt">--base-url</span> <span class="s2">"http://</span><span class="k">${</span><span class="nv">HEAD_ADDR</span><span class="k">}</span><span class="s2">:8000"</span> <span class="se">\</span>
  <span class="nt">--model</span> moonshotai/Kimi-K3 <span class="se">\</span>
  <span class="nt">--dataset-name</span> speed-bench <span class="se">\</span>
  <span class="nt">--speed-bench-config</span> throughput_16k <span class="se">\</span>
  <span class="nt">--speed-bench-max-input-len</span> 10240 <span class="se">\</span>
  <span class="nt">--speed-bench-category</span> low_entropy <span class="se">\</span>
  <span class="nt">--output-len</span> 1536 <span class="se">\</span>
  <span class="nt">--num-prompts</span> 10 <span class="se">\</span>
  <span class="nt">--no-oversample</span> <span class="se">\</span>
  <span class="nt">--max-concurrency</span> 1 <span class="se">\</span>
  <span class="nt">--temperature</span> 1.0 <span class="se">\</span>
  <span class="nt">--top-p</span> 0.95 <span class="se">\</span>
  <span class="nt">--save-result</span> <span class="se">\</span>
  <span class="nt">--save-detailed</span>
</code></pre></div></div>

<p>Full recipes, including multi-node, expert-parallel, and vision configurations, are in the <a href="https://recipes.vllm.ai/moonshotai/Kimi-K3">Kimi K3 recipes</a>.</p>

<h2 id="important-deployment-tips">Important Deployment Tips</h2>

<ol>
  <li><strong>Prefix caching:</strong> <code class="language-plaintext highlighter-rouge">--enable-prefix-caching</code> turns prefix caching on. Prefix caching is typically enabled by default in vLLM, but it is currently disabled by default for Kimi K3 while the hybrid-cache design continues to evolve. Pass the flag explicitly.</li>
  <li><strong>Tool calling:</strong> Validate on your own traffic before depending on it. We’ve occasionally seen K3 emit a tool-call format its own parser does not expect, yielding an empty <code class="language-plaintext highlighter-rouge">tool_calls</code> result, while clean probes on the same setup parse perfectly. It is prompt- and run-dependent, not a blanket failure, but production agents should validate against your schema, retry or fall back when <code class="language-plaintext highlighter-rouge">tool_calls</code> comes back empty, and consider strict or structured tool calling, which constrains the output grammar during generation.</li>
  <li><strong>All-to-all backend:</strong> <code class="language-plaintext highlighter-rouge">--all2all-backend</code> determines how the MoE backend communicates during expert parallelism. Use <code class="language-plaintext highlighter-rouge">flashinfer_nvlink_one_sided</code> for NVIDIA NVLink and <code class="language-plaintext highlighter-rouge">deepep_v2</code> for RDMA.</li>
  <li><strong>MoE backend:</strong> vLLM has several MoE backends for different scenarios. We recommend <code class="language-plaintext highlighter-rouge">deep_gemm_mega_moe</code> for any DEP environment.</li>
  <li><strong>Rust frontend:</strong> Set <code class="language-plaintext highlighter-rouge">VLLM_USE_RUST_FRONTEND=1</code> to enable the Rust frontend, which fully supports this model.</li>
  <li><strong>ViT parallelism:</strong> <code class="language-plaintext highlighter-rouge">--mm-encoder-tp-mode=data</code> is enabled by default. K3’s vision encoder has <code class="language-plaintext highlighter-rouge">head_size=12</code>, which cannot be sharded evenly under TP=8. Because K3’s vision encoder has fewer than 1B parameters while the backbone has about 2T, we enable ViT DP by default to avoid all-reduce overhead from the encoder.</li>
</ol>

<h2 id="kimi-k3-vllm-faq">Kimi K3 vLLM FAQ</h2>

<h3 id="how-many-gpus-do-i-need-to-serve-kimi-k3">How many GPUs do I need to serve Kimi K3?</h3>

<p>At least one 8× B300 (or GB300 NVL72) node is required; 16× B200 is also supported. Most production deployments run multi-node with expert and data parallelism, connected over RDMA or NVLink.</p>

<h3 id="how-do-i-enable-dspark-speculative-decoding">How do I enable DSpark speculative decoding?</h3>

<p>Add:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">--speculative-config</span> <span class="s1">'{"model":"Inferact/Kimi-K3-DSpark","method":"dspark","num_speculative_tokens":7,"attention_backend":"FLASHINFER_MLA","draft_sample_method":"probabilistic","rejection_sample_method":"block"}'</span>
</code></pre></div></div>

<p>It roughly triples single-stream decode on reasoning and coding workloads.</p>

<h3 id="which-moe-and-all-to-all-backend-should-i-use">Which MoE and all-to-all backend should I use?</h3>

<p>Use <code class="language-plaintext highlighter-rouge">deep_gemm_mega_moe</code> for disaggregated or expert-parallel (DEP) deployments and <code class="language-plaintext highlighter-rouge">flashinfer_trtllm</code> for TP &gt; 1. Choose the all-to-all backend to match your interconnect: <code class="language-plaintext highlighter-rouge">flashinfer_nvlink_one_sided</code> for NVLink and <code class="language-plaintext highlighter-rouge">deepep_v2</code> for RDMA.</p>

<h3 id="does-kimi-k3-support-prefix-caching-and-is-it-on-by-default">Does Kimi K3 support prefix caching, and is it on by default?</h3>

<p>It supports prefix caching over both full-attention KV and recurrent KDA state, but it is not enabled by default, so pass <code class="language-plaintext highlighter-rouge">--enable-prefix-caching</code>.</p>

<h3 id="does-vllm-support-kimi-k3-on-amd-gpus">Does vLLM support Kimi K3 on AMD GPUs?</h3>

<p>Yes. ROCm support ships at launch, with broader tuning on the roadmap.</p>

<h3 id="how-is-this-different-from-the-kimi-k3-preview-post">How is this different from the Kimi K3 preview post?</h3>

<p>The <a href="https://vllm.ai/blog/2026-07-22-kimi-k3-preview">preview</a> is the architecture and kernel deep dive, including how KDA prefix caching and the kernels are built. This post is the practical launch guide and includes the artifacts: how vLLM adapts to Kimi K3, recipes, flags, performance, and what Kimi K3 is ready for in production.</p>

<h2 id="roadmap-and-future-work">Roadmap and Future Work</h2>

<ul>
  <li><strong>RL support for Kimi K3:</strong> vLLM rollout support has already been added. We will work closely with RL ecosystem projects to support end-to-end RL training for Kimi K3.</li>
  <li><strong>Continuous performance improvement:</strong> continue improving performance after day 0.</li>
  <li><strong>Decode Context Parallelism (DCP):</strong> our prototype shows good speedup, and we will soon upstream the support. Early experiments show 40% higher throughput than TP8 under selected workloads.</li>
  <li><strong>Expert-Parallel Load Balancing (EPLB):</strong> improve EPLB performance.</li>
  <li><strong>Confidence-based scheduling:</strong> use the confidence head in DSpark to prune the number of draft tokens to verify.</li>
  <li><strong>Broader AMD ROCm tuning.</strong></li>
</ul>

<h2 id="quick-links">Quick links</h2>

<ul>
  <li><strong>Model:</strong> <a href="https://huggingface.co/moonshotai/Kimi-K3">moonshotai/Kimi-K3</a></li>
  <li><strong>DSpark draft:</strong> <a href="https://huggingface.co/Inferact/Kimi-K3-DSpark">Inferact/Kimi-K3-DSpark</a></li>
  <li><strong>Recipes and Docker images:</strong> <a href="https://recipes.vllm.ai/moonshotai/Kimi-K3">recipes.vllm.ai/moonshotai/Kimi-K3</a></li>
  <li><strong>Kimi K3 technical blog:</strong> <a href="https://www.kimi.com/blog/kimi-k3">kimi.com/blog/kimi-k3</a></li>
  <li><strong>vLLM design for K3:</strong> <a href="https://vllm.ai/blog/2026-07-22-kimi-k3-preview">the preview post</a></li>
</ul>

<h2 id="acknowledgements">Acknowledgements</h2>

<p>Thank you to Moonshot AI for creating K3, sharing the architecture ahead of release, and co-designing the KDA-aware caching; to the Inferact team for the end-to-end vLLM integration and deployment validation; to NVIDIA for the fused KDA decode, KDA prefill, and Attention Residual kernels and the MXFP4 MoE collaboration; to AMD for ROCm bring-up; to our inference partners, including Alibaba Cloud, Baseten, DigitalOcean, and Modal; to Shikhar for Flash-Flash-KDA; and to the vLLM community. The cache infrastructure built for Kimi K3 now belongs to every hybrid model with a similar architecture. We can’t wait to see what you serve.</p>]]></content><author><name>vLLM Team and Inferact</name></author><category term="models" /><category term="performance" /><category term="prefix caching" /><category term="multimodal" /><summary type="html"><![CDATA[We’re thrilled to announce efficient day-0 vLLM support for Kimi K3, one of the most powerful open-weight models ever released.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vllm-project.github.io/assets/figures/2026-07-27-k3/social-preview.png" /><media:content medium="image" url="https://vllm-project.github.io/assets/figures/2026-07-27-k3/social-preview.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">From Day 0 to Production SLAs: Serving GLM-5.2 on 24 NVIDIA B300 GPUs with vLLM</title><link href="https://vllm-project.github.io/2026/07/23/glm-5.2-nvfp4-b300-pd.html" rel="alternate" type="text/html" title="From Day 0 to Production SLAs: Serving GLM-5.2 on 24 NVIDIA B300 GPUs with vLLM" /><published>2026-07-23T00:00:00+00:00</published><updated>2026-07-23T00:00:00+00:00</updated><id>https://vllm-project.github.io/2026/07/23/glm-5.2-nvfp4-b300-pd</id><content type="html" xml:base="https://vllm-project.github.io/2026/07/23/glm-5.2-nvfp4-b300-pd.html"><![CDATA[<h2 id="tldr">TL;DR</h2>

<p>We deployed GLM-5.2-NVFP4 across three 8-GPU B300 servers (24 GPUs in total) using a disaggregated 4-Prefill + 1-Decode topology. Under our production SLA targets of mean TTFT ≤ 2.5 s and mean TPOT ≤ 20 ms, we measured the following:</p>

<p><img src="/assets/figures/2026-07-23-glm-5.2-nvfp4-b300-pd/02-results-overview.png" alt="GLM-5.2-NVFP4 on 24x B300, disaggregated 4P1D serving: total throughput, output throughput, TTFT and TPOT across input lengths from 8K to 256K" /></p>

<p>Our starting point on the same hardware was a mean TPOT of nearly 40 ms for 16K-token inputs — roughly twice our SLA limit. This post documents the full journey from 40 ms to 17 ms: what we changed at each step, how much each change contributed, and why the parallelism strategy we shipped is <em>not</em> the one with the highest raw throughput. A complete, reproducible set of <code class="language-plaintext highlighter-rouge">vllm serve</code> commands is included at the end.</p>

<h2 id="1-why-we-optimized-for-sla-compliance-not-peak-throughput">1. Why We Optimized for SLA Compliance, Not Peak Throughput</h2>

<p>In colocated serving, prefill chunks are interleaved into decode batches, and every long prompt entering a batch stretches the inter-token latency of every request already decoding. TPOT tail latency therefore becomes a function of the incoming prompt length distribution — something a serving system does not control. Disaggregation removes prefill work from the decode critical path entirely, making TPOT a function of decode batch composition alone. That is what makes a tight TPOT SLA achievable in the first place, and it is why the rest of this post treats P/D as the starting point rather than as one optimization among many.</p>

<p>Production services are not accepted on peak throughput alone. The real question is how much traffic the system can sustain <em>without</em> violating latency targets. Our requirements were explicit:</p>

<ul>
  <li>Typical context length: 16K–256K tokens</li>
  <li><strong>Mean TTFT ≤ 2.5 s</strong> — the maximum acceptable delay between a user action and the first visible token</li>
  <li><strong>Mean TPOT ≤ 20 ms</strong> — roughly 50 tokens/s of streaming output; below this rate, the reading experience degrades noticeably</li>
  <li>Subject to those two hard constraints, maximize throughput</li>
</ul>

<p>A note on batch size: we did not fix one. Load is applied as a request rate, and concurrency is whatever that rate produces under the SLA — deliberately as large as the latency budget allows. Because prefill cost grows with context length, concurrency settles lower as input length rises: roughly 700 concurrent requests at 8K, 300 at 16K, and 25 at 256K, all under a <code class="language-plaintext highlighter-rouge">--max-concurrency 1024</code> cap.</p>

<p>This objective shaped the methodology throughout: every configuration search was constraint-aware. A configuration that improves throughput by 30% but pushes mean TPOT past the SLA is not useful to us. Section 4 contains a representative example.</p>

<p>GLM-5.2 is a 744B-parameter MoE model with 40B active parameters. It uses DSA sparse attention and natively supports MTP speculative decoding, and vLLM already provides mature support for all three. Our work was to combine them effectively with P/D disaggregation, then tune parameters, topology, and scheduling around production SLA targets.</p>

<h2 id="2-starting-point-improving-decode-performance-under-pd-disaggregation">2. Starting Point: Improving Decode Performance under P/D Disaggregation</h2>

<p>With the initial configuration, the Prefill side already met its target and had ample TTFT headroom. The bottleneck was Decode: with 16K input tokens and 1K output tokens, mean TPOT was close to 40 ms, with substantial P99 jitter.</p>

<h3 id="21-root-cause-mixed-batches-at-the-pd-handoff">2.1 Root Cause: Mixed Batches at the P/D Handoff</h3>

<p>Speculative decoding has become a standard inference-time optimization for large MoE models, and production Decode deployments increasingly run it by default. Profiling revealed an issue that sits precisely at the interaction between P/D disaggregation and speculative decoding.</p>

<p>After a request transfers its prompt KV cache to a Decode node through KVConnector, its first Decode step needs to compute only one token. Existing requests on that Decode node, however, are scheduled with 1 + N tokens per step when MTP is enabled. Because the two request types have different shapes, the step becomes a mixed batch. It can no longer take the uniform-decode full-CUDA-Graph fast path and instead falls back to the more expensive piecewise or eager execution path.</p>

<p>Data parallelism amplifies the impact. Under DP, CUDA Graph mode and padding require coordination across ranks, so if any DP rank receives a newly transferred request, the remaining ranks follow it onto the same execution path. In steady-state P/D operation, new requests arrive at the Decode instance continuously, so the slow path is triggered constantly.</p>

<h3 id="22-optimization-speculative-padding-on-the-decode-side">2.2 Optimization: Speculative Padding on the Decode Side</h3>

<p>The fix is conceptually simple. On the first Decode step after a request arrives, dummy speculative tokens pad its shape to 1 + N, matching the other requests already in the Decode worker. This preserves uniform Decode execution and keeps the workload on the full-CUDA-Graph fast path.</p>

<p>It requires no transfer of generated tokens or draft tokens from the Prefill node. The optimization was merged by the vLLM community in <a href="https://github.com/vllm-project/vllm/pull/45237">PR #45237</a>.</p>

<h3 id="23-performance-gain">2.3 Performance Gain</h3>

<p>With the execution-path regression caused by mixed batches eliminated, end-to-end mean TPOT dropped from approximately 40 ms to approximately 22 ms — the single largest improvement of the entire effort.</p>

<p>The result carries a broader lesson for deployments that combine P/D disaggregation with speculative decoding: the largest performance loss may not live in any individual kernel. It can arise at the boundary between subsystems, where small inconsistencies in request state, scheduling shape, and CUDA Graph execution mode are amplified by DP scale and continuous traffic.</p>

<h2 id="3-further-decode-side-optimizations">3. Further Decode-Side Optimizations</h2>

<p>At 22 ms we were close to the SLA but had no safety margin, so we ran another round of configuration search.</p>

<h3 id="31-model-runner-v2-11-lower-tpot">3.1 Model Runner V2: 11% Lower TPOT</h3>

<p>vLLM Model Runner V2 (MRV2) refactors the runtime execution path. Since v0.25.0 it is the default for all dense models; GLM-5.2 is an MoE model, so it is not enabled by default and must be activated explicitly with <code class="language-plaintext highlighter-rouge">VLLM_USE_V2_MODEL_RUNNER=1</code>.</p>

<p>On our Decode configuration, MRV2 improved TPOT by approximately 11% over MRV1. Beyond the shorter execution path, MRV2 brings several capabilities that matter for predictable production latency:</p>

<ol>
  <li><a href="https://github.com/vllm-project/vllm/pull/47285">PR #47285</a> adds the GLM-5.2 DSA indexer prefill-metadata kernel to startup warmup, so the first production request no longer triggers Triton JIT compilation and a latency spike. This is easy to miss in benchmarks, where warmup absorbs it; in production it shows up as a cold-start spike after every rolling deployment.</li>
  <li><a href="https://github.com/vllm-project/vllm/pull/46448">PR #46448</a> adds local argmax reduction for multi-GPU MTP. With <code class="language-plaintext highlighter-rouge">use_local_argmax_reduction</code> enabled, draft-token generation no longer AllGathers full-vocabulary logits, reducing TP communication from a volume proportional to vocabulary size to approximately 2 × TP size. MTP, EAGLE, DFlash, and other speculators running under MRV2 all benefit.</li>
  <li><a href="https://github.com/vllm-project/vllm/pull/45953">PR #45953</a> lets dynamic speculative lengths work with full CUDA Graphs, reducing graph misses and eager fallbacks caused by changes in draft length.</li>
</ol>

<h3 id="32-all-to-all-backend-4-lower-tpot">3.2 All-to-All Backend: 4% Lower TPOT</h3>

<p>Because GLM-5.2 is an MoE model, the Decode side runs DEP8, which places expert dispatch and combine communication directly on the critical path. We replaced the default AllGather/ReduceScatter-based EP backend with the FlashInfer NVLink A2A backend; in our measurements, <code class="language-plaintext highlighter-rouge">flashinfer_nvlink_two_sided</code> cut TPOT by a further 4%.</p>

<p>vLLM now also ships the newer <code class="language-plaintext highlighter-rouge">flashinfer_nvlink_one_sided</code> backend, which is expected to perform better. This post keeps the two-sided backend because that is the configuration we actually measured. Evaluating the one-sided backend under the same Decode workload is on our list.</p>

<h3 id="33-cuda-graph-mode">3.3 CUDA Graph Mode</h3>

<p>The Decode instance runs <code class="language-plaintext highlighter-rouge">--compilation-config '{"cudagraph_mode":"FULL_DECODE_ONLY"}'</code> together with <code class="language-plaintext highlighter-rouge">--max-num-batched-tokens 1024</code>. The Decode side does not need graphs compiled for prefill shapes, and <code class="language-plaintext highlighter-rouge">FULL_DECODE_ONLY</code> gives complete graph coverage of the Decode path while cutting startup compilation time significantly.</p>

<h3 id="34-mtp-speculative-decoding">3.4 MTP Speculative Decoding</h3>

<p>We use <code class="language-plaintext highlighter-rouge">num_speculative_tokens=3</code> on the Decode side and <code class="language-plaintext highlighter-rouge">1</code> on the Prefill side. (MTP only becomes cost-effective on GLM-5.2 together with IndexerCache, discussed in Section 5.) The asymmetry is intentional: Prefill nodes should produce and hand off KV cache as fast as possible, so deeper speculation buys little there. Decode nodes sit on the latency-critical path, where deeper speculation amortizes the execution cost per token — provided the acceptance rate stays high.</p>

<h2 id="4-prefill-parallelism-why-we-did-not-choose-the-highest-throughput-configuration">4. Prefill Parallelism: Why We Did Not Choose the Highest-Throughput Configuration</h2>

<p>On the Prefill side we compared several parallelism strategies at 8K- and 32K-token inputs. Since the configurations use different numbers of GPUs, TGS — throughput per GPU — is the meaningful metric.</p>

<p align="center">
<img src="/assets/figures/2026-07-23-glm-5.2-nvfp4-b300-pd/04-prefill-parallelism-tgs.svg" width="95%" />
<br />
<em>Per-GPU prefill throughput for four parallelism strategies, at 8K and 32K input lengths.</em>
</p>

<p>In absolute terms TP1 DP4 EP is the fastest instance — 47,806 tok/s at 8K inputs — but only because it uses twice as many GPUs as the others; per GPU it lands second. Two conclusions stand out:</p>

<ol>
  <li><strong>TP2 + EP performed worse than plain TP2.</strong> At a scale of only two GPUs, the all-to-all overhead introduced by EP exceeds its benefit. EP needs enough experts spread across enough devices to amortize its communication cost.</li>
  <li><strong>TP1 DP2 EP achieved the best TGS, but we shipped TP1 DP4 EP.</strong></li>
</ol>

<p>The second point is where production engineering parts ways with benchmark chasing. TP1 DP2 EP delivered the best per-GPU efficiency, but each instance had only two GPUs, leaving too little KV-cache capacity for GLM-5.2’s 1M-token context capability. We were not willing to give up one of the model’s most important features for an 8% TGS advantage, so we chose TP1 DP4 EP — trading roughly 8% of per-GPU efficiency for the KV-cache capacity of four GPUs per instance.</p>

<h2 id="5-mtp--indexercache-how-we-improved-the-acceptance-rate">5. MTP + IndexerCache: How We Improved the Acceptance Rate</h2>

<p>vLLM has been extensively validated in production under conventional configurations. This deployment enabled three relatively new capabilities at once — P/D disaggregation, MTP, and MRV2 — which put us on a less frequently exercised combination path, and the tuning process surfaced several long-tail issues. Most fixes went from report to release within a few days; anyone on v0.26.0 already has all of them. This section records the work to show how MTP acceptance was stabilized step by step.</p>

<h3 id="51-indexercache-making-mtp-cost-effective-with-dsa">5.1 IndexerCache: Making MTP Cost-Effective with DSA</h3>

<p>IndexerCache (<a href="https://github.com/vllm-project/vllm/pull/44420">PR #44420</a>) is not a conventional KV cache — it reuses the Top-K sparse indices produced by the DSA indexer. A naive implementation reruns the indexer for every MTP draft step, and since sparse-retrieval cost grows with context length, that can consume much of the benefit expected from speculative decoding. PR #44420 introduced <code class="language-plaintext highlighter-rouge">index_share_for_mtp_iteration</code>, which lets the first draft step compute Top-K indices and subsequent draft steps reuse them. This is a prerequisite for MTP to be worthwhile on GLM-5.2 at all.</p>

<p>The community then completed three improvements around this mechanism, which together stabilized acceptance under high concurrency:</p>

<ul>
  <li><a href="https://github.com/vllm-project/vllm/pull/45895">PR #45895</a> improves indexer initialization when Top-K layers are skipped and fixes the GLM-5.2 MTP normalization loop. The PR reports that on GLM-5.2-FP8 with TP=8, mean accepted length rose from approximately 3 to approximately 4, at an average acceptance rate of about 60%, while IFBench held at 74.62.</li>
  <li><a href="https://github.com/vllm-project/vllm/pull/47238">PR #47238</a> optimizes the layout of the shared index buffer for batched requests: after the first draft step, it retains only the Top-K indices corresponding to each request’s final query token. This was the key step in extending index sharing from single-request execution to high-concurrency batching.</li>
  <li><a href="https://github.com/vllm-project/vllm/pull/47448">PR #47448</a> ensures the MTP loop reuses the post-final-norm hidden state.</li>
</ul>

<p>Taken together, IndexerCache is not merely a compute-saving optimization. It is also a key mechanism for holding MTP acceptance rates up under high concurrency.</p>

<h3 id="52-two-additional-fixes-for-the-combined-configuration">5.2 Two Additional Fixes for the Combined Configuration</h3>

<p><strong>MRV2 scheduling classification.</strong> After switching to MRV2, we observed excessive TPOT variance under a specific benchmark pattern and reported it in <a href="https://github.com/vllm-project/vllm/issues/47239">Issue #47239</a>. The community quickly traced it to uniform-decode ordering: speculative-decoding steps were being classified as prefill and therefore took a slower execution path. <a href="https://github.com/vllm-project/vllm/pull/47381">PR #47381</a> fixed it.</p>

<p><strong>Lookahead handling for asynchronous KV loading in P/D deployments.</strong> <a href="https://github.com/vllm-project/vllm/pull/46694">PR #46694</a> improves slot-allocation timing for the combined GLM-5.2 + NIXL P/D + MTP configuration. The Decoder now waits until remote KV transfer completes before allocating speculative-token slots, correctly handling the boundary case of a final partial KV block. This partial-block handoff is specific to the interaction between P/D disaggregation and speculative decoding, and fixing it was another step toward making the path production-ready.</p>

<p>Both fixes are included in v0.25.0 and later releases.</p>

<h3 id="53-accuracy-validation">5.3 Accuracy Validation</h3>

<p>We ran a set of public benchmarks with the final configuration to verify that combining NVFP4 quantization, MTP, and P/D disaggregation did not reduce output quality:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Test Item</th>
      <th style="text-align: left">Score</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">AIME 2025</td>
      <td style="text-align: left">86.67</td>
    </tr>
    <tr>
      <td style="text-align: left">GPQA</td>
      <td style="text-align: left">92.89</td>
    </tr>
    <tr>
      <td style="text-align: left">LongBench V2</td>
      <td style="text-align: left">64.01</td>
    </tr>
    <tr>
      <td style="text-align: left">MMLU-Pro</td>
      <td style="text-align: left">86.3</td>
    </tr>
    <tr>
      <td style="text-align: left">SWE-bench Verified (Agentic)</td>
      <td style="text-align: left">85.2</td>
    </tr>
  </tbody>
</table>

<p>The results are consistent with community-reported GLM-5.2 numbers. LongBench V2 mattered most to us because it directly exercises DSA sparse attention and IndexerCache index sharing under long contexts. A score of 64.01 indicates that index sharing holds up in long-context workloads, and that the speedup from speculative decoding did not come at the expense of output quality.</p>

<h2 id="6-upstream-progress">6. Upstream Progress</h2>

<p>The capabilities used in this deployment build on a broader set of optimizations coordinated by the vLLM community under the GLM-5.2 optimization tracking issue, <a href="https://github.com/vllm-project/vllm/issues/46654">Issue #46654</a>. Beyond the PRs referenced above, two developments are especially relevant.</p>

<p><strong>A secondary tier for P/D disaggregation.</strong> <a href="https://github.com/vllm-project/vllm/pull/42285">PR #42285</a> introduces a unified CPU KV-cache layout as an intermediate layer. <code class="language-plaintext highlighter-rouge">TieringManager</code> coordinates the primary cache tier and the P/D connector, reducing coupling between the transfer backend and the model execution path. Merged in v0.25.0.</p>

<p><strong>PCP virtual batching for long-context Prefill.</strong> <a href="https://github.com/vllm-project/vllm/pull/46570">PR #46570</a> splits requests into multiple virtual batch rows processed in parallel across context-parallel ranks, aggregating only the MLA latent cache and DSA indexer cache. In an initial 4-GPU GLM-5.2-NVFP4 / 32K Prefill test, TP=2 with PCP=2 raised prompt throughput from approximately 20.1K tok/s to approximately 27.3K tok/s. The PR was merged into <code class="language-plaintext highlighter-rouge">main</code> on July 19, 2026, and will ship in the next release. Since KV-cache capacity is exactly why we rejected the TGS-optimal configuration in Section 4, PCP may change that trade-off, and it is a key focus of our next validation phase.</p>

<h2 id="7-observability-verifying-the-sla-after-production-launch">7. Observability: Verifying the SLA after Production Launch</h2>

<p>Passing a benchmark does not make a system production-ready. Observability is harder for a disaggregated P/D deployment than for a single instance, because request latency is split across two resource pools: TTFT is determined mostly by the Prefill pool, TPOT by the Decode pool, with a KV transfer in between. When any stage degrades, users see only one symptom — the service got slower.</p>

<p>We built monitoring for the P/D topology with Prometheus and Grafana. In production we watch the following metric groups:</p>

<ul>
  <li><strong>Per-pool TTFT and TPOT percentiles</strong>, not just end-to-end aggregates. This is the first place to look to decide whether a problem belongs to Prefill or Decode.</li>
  <li><strong>MTP acceptance rate and mean accepted length.</strong> Easy to overlook, yet among the earliest warning signals available. A declining acceptance rate produces no error; TPOT simply degrades gradually. In practice this is the first dashboard we open for any Decode-side anomaly, and we recommend treating MTP acceptance rate as a first-class alerting metric.</li>
  <li><strong>KV-cache utilization versus GPU utilization</strong>, on both the Prefill and Decode sides. The central benefit of P/D disaggregation is that the two resource types scale independently; the relative levels of these curves are the scaling signal.</li>
  <li><strong>KV-transfer latency and queue depth</strong>, to determine whether the inter-node network has become the bottleneck.</li>
</ul>

<h3 id="71-a-problem-visible-only-during-long-running-stability-tests">7.1 A Problem Visible Only during Long-Running Stability Tests</h3>

<p>Short benchmarks validate performance, not long-term stability. Our production acceptance process includes multi-day continuous runs, and that step revealed persistent host-memory growth: vLLM process RSS increased linearly over tens of hours without reaching a plateau.</p>

<p align="center">
<img src="/assets/figures/2026-07-23-glm-5.2-nvfp4-b300-pd/03-host-memory-growth.png" width="90%" />
<br />
<em>Grafana Memory Usage (WSS) panel: vLLM container memory grew linearly from approximately 721 GiB to approximately 800 GiB over tens of hours.</em>
</p>

<p>Several characteristics explain why this issue needed production monitoring, rather than a benchmark, to be discovered:</p>

<ul>
  <li>The growth rate was slow and only visible over hours. A <code class="language-plaintext highlighter-rouge">vllm bench serve</code> run lasting seconds or minutes could not reveal it.</li>
  <li>The growth affected host memory, not GPU memory. Every GPU-side metric looked normal.</li>
  <li>Conventional memory-analysis tools could not see it. <code class="language-plaintext highlighter-rouge">EngineCore</code> calls <code class="language-plaintext highlighter-rouge">gc.freeze()</code> during startup, so the leaked objects never appeared in <code class="language-plaintext highlighter-rouge">gc.get_objects()</code> or <code class="language-plaintext highlighter-rouge">tracemalloc</code>; the symptom looked like allocator fragmentation.</li>
</ul>

<p>We reported the behavior and our initial diagnosis to the community in <a href="https://github.com/vllm-project/vllm/pull/47723">PR #47723</a>, and a maintainer incorporated it into <a href="https://github.com/vllm-project/vllm/pull/44490">PR #44490</a>.</p>

<p>The root cause was inconsistent gating between a producer and a consumer. <a href="https://github.com/vllm-project/vllm/pull/35219">PR #35219</a> had introduced <code class="language-plaintext highlighter-rouge">SingleTypeKVCacheManager.new_block_ids</code> for clearing Mamba SSM cache state. Entries were recorded based on the KV-cache spec type — <code class="language-plaintext highlighter-rouge">FullAttentionSpec</code>, <code class="language-plaintext highlighter-rouge">MLAAttentionSpec</code>, and so on — but cleared only when the model contained Mamba layers. For models without Mamba layers, which includes most standard attention models and GLM-5.2 with MLA, every block allocation was recorded and the list was never drained, so it grew without bound as request volume increased. The fix was to drain <code class="language-plaintext highlighter-rouge">take_new_block_ids()</code> unconditionally on every scheduling step and use its result only when clearing is actually required. Mamba behavior is unchanged.</p>

<h2 id="8-complete-deployment-recipe">8. Complete Deployment Recipe</h2>

<h3 id="81-environment">8.1 Environment</h3>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Deployment</th>
      <th style="text-align: left">Configuration</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">Hardware</td>
      <td style="text-align: left">3 × 8 B300 (24 GPUs)</td>
    </tr>
    <tr>
      <td style="text-align: left">Model</td>
      <td style="text-align: left">GLM-5.2-NVFP4</td>
    </tr>
    <tr>
      <td style="text-align: left">Topology</td>
      <td style="text-align: left">4 Prefill (TP1 DP4 EP, 4 GPUs each = 16) + 1 Decode (TP1 DP8 EP = 8)</td>
    </tr>
    <tr>
      <td style="text-align: left">KV Transfer</td>
      <td style="text-align: left">NIXL</td>
    </tr>
  </tbody>
</table>

<h3 id="82-prefill-node">8.2 Prefill Node</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">VLLM_USE_V2_MODEL_RUNNER</span><span class="o">=</span>1

vllm serve /mnt/model/glm/GLM-5.2-NVFP4 <span class="se">\</span>
    <span class="nt">--trust-remote-code</span> <span class="se">\</span>
    <span class="nt">--kv-transfer-config</span> <span class="s1">'{"kv_connector":"NixlConnector","kv_role":"kv_producer"}'</span> <span class="se">\</span>
    <span class="nt">--chat-template-content-format</span><span class="o">=</span>string <span class="se">\</span>
    <span class="nt">-ep</span> <span class="se">\</span>
    <span class="nt">-tp</span> 1 <span class="se">\</span>
    <span class="nt">-dp</span> 4 <span class="se">\</span>
    <span class="nt">--tool-call-parser</span> glm47 <span class="se">\</span>
    <span class="nt">--enable-auto-tool-choice</span> <span class="se">\</span>
    <span class="nt">--reasoning-parser</span> glm45 <span class="se">\</span>
    <span class="nt">--gpu-memory-utilization</span> 0.92 <span class="se">\</span>
    <span class="nt">--enable-prompt-tokens-details</span> <span class="se">\</span>
    <span class="nt">--speculative-config</span><span class="o">=</span><span class="s1">'{"method":"mtp","num_speculative_tokens":1}'</span> <span class="se">\</span>
    <span class="nt">--shutdown-timeout</span> 300 <span class="se">\</span>
    <span class="nt">--fingerprint-mode</span><span class="o">=</span>none
</code></pre></div></div>

<h3 id="83-decode-node">8.3 Decode Node</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">VLLM_USE_V2_MODEL_RUNNER</span><span class="o">=</span>1

vllm serve /mnt/model/glm/GLM-5.2-NVFP4 <span class="se">\</span>
    <span class="nt">--trust-remote-code</span> <span class="se">\</span>
    <span class="nt">--chat-template-content-format</span><span class="o">=</span>string <span class="se">\</span>
    <span class="nt">--kv-transfer-config</span> <span class="s1">'{"kv_connector":"NixlConnector","kv_role":"kv_consumer"}'</span> <span class="se">\</span>
    <span class="nt">--compilation-config</span> <span class="s1">'{"cudagraph_mode":"FULL_DECODE_ONLY"}'</span> <span class="se">\</span>
    <span class="nt">--max-num-batched-tokens</span> 1024 <span class="se">\</span>
    <span class="nt">-ep</span> <span class="se">\</span>
    <span class="nt">-tp</span> 1 <span class="se">\</span>
    <span class="nt">-dp</span> 8 <span class="se">\</span>
    <span class="nt">--tool-call-parser</span> glm47 <span class="se">\</span>
    <span class="nt">--enable-auto-tool-choice</span> <span class="se">\</span>
    <span class="nt">--reasoning-parser</span> glm45 <span class="se">\</span>
    <span class="nt">--gpu-memory-utilization</span> 0.90 <span class="se">\</span>
    <span class="nt">--enable-prompt-tokens-details</span> <span class="se">\</span>
    <span class="nt">--all2all-backend</span><span class="o">=</span>flashinfer_nvlink_two_sided <span class="se">\</span>
    <span class="nt">--speculative-config</span><span class="o">=</span><span class="s1">'{"method":"mtp","num_speculative_tokens":3}'</span> <span class="se">\</span>
    <span class="nt">--shutdown-timeout</span> 300 <span class="se">\</span>
    <span class="nt">--fingerprint-mode</span><span class="o">=</span>none
</code></pre></div></div>

<h3 id="84-benchmark-methodology">8.4 Benchmark Methodology</h3>

<p>All performance data was collected with the random dataset in <code class="language-plaintext highlighter-rouge">vllm bench serve</code>. There were no prefix-cache hits, so the TTFT figures represent worst-case, compute-only latency. Requests were injected at a fixed <code class="language-plaintext highlighter-rouge">--request-rate</code>, computed as target TPS divided by <code class="language-plaintext highlighter-rouge">(input_len + output_len)</code> and multiplied by a tuning factor.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vllm bench serve <span class="se">\</span>
  <span class="nt">--backend</span> openai-chat <span class="se">\</span>
  <span class="nt">--model</span> /mnt/model/glm/GLM-5.2-NVFP4 <span class="se">\</span>
  <span class="nt">--endpoint</span> /v1/chat/completions <span class="se">\</span>
  <span class="nt">--dataset-name</span> random <span class="se">\</span>
  <span class="nt">--random-input-len</span> 16384 <span class="se">\</span>
  <span class="nt">--random-output-len</span> 1000 <span class="se">\</span>
  <span class="nt">--request-rate</span> &lt;target TPS / <span class="o">(</span>input + output<span class="o">)</span> × tuning <span class="nb">factor</span><span class="o">&gt;</span> <span class="se">\</span>
  <span class="nt">--percentile-metrics</span> ttft,tpot,itl,e2el <span class="se">\</span>
  <span class="nt">--metric-percentiles</span> 50,90 <span class="se">\</span>
  <span class="nt">--save-result</span>
</code></pre></div></div>

<h2 id="9-what-comes-next">9. What Comes Next</h2>

<p>For GLM-5.2 and the larger MoE models that will follow, we see two major directions for Decode optimization. The following are forward-looking assessments rather than conclusions measured in this post.</p>

<p><strong>First, reduce the cost of each target forward pass.</strong> Beyond core kernels such as GEMM, Attention, and the Indexer, this includes exploring PDL, persistent kernels, localized megakernels, and coordinated computation and communication across MoE Dispatch, Expert GEMM, and Combine. As deployments scale across nodes, WideEP, hierarchical all2all, and overlap between inter-node communication and computation become increasingly important. The objective is to shorten the end-to-end critical path of a complete Decode step.</p>

<p><strong>Second, advance model–runtime co-optimization for speculative decoding.</strong> On the model side, stronger drafters such as DSpark can be trained on broader datasets to improve the accuracy of consecutive proposal tokens. On the runtime side, dynamic speculative decoding, per-request proposal lengths, and compact verification can keep verification cost in check across different workloads. The draft model determines how far the system can predict; the serving runtime determines how far prediction is actually economical.</p>

<p>In addition, the PCP virtual-batch work described in Section 6 has already landed in <code class="language-plaintext highlighter-rouge">main</code>. We will evaluate whether it removes the KV-cache-capacity versus per-GPU-efficiency trade-off discussed in Section 4.</p>

<h2 id="about-us">About Us</h2>

<p>This work was completed by the <a href="https://www.daocloud.io/">DaoCloud</a> team. We deliver full-stack platforms for enterprise LLM training and inference, including heterogeneous accelerator scheduling, inference-service orchestration, and observability. We shared our observations and validation results with the vLLM community throughout the tuning process, and the resulting improvements were merged upstream. The configuration in this post can be used directly with v0.26.0.</p>

<p>Special thanks to Nicolò Lucchesi (<a href="https://github.com/NickLucche">@NickLucche</a>) from <a href="https://github.com/mistralai">Mistral AI</a>, who suggested writing this post in the first place, reviewed it in detail, and kept it moving from a rough set of notes to what you have just read.</p>

<p>We also thank the vLLM community for its efficient collaboration under <a href="https://github.com/vllm-project/vllm/issues/46654">Issue #46654</a>. In most cases, fixes moved from initial report to a released version within one week.</p>]]></content><author><name>DaoCloud Team</name></author><category term="disaggregation" /><category term="performance" /><category term="speculative-decoding" /><category term="moe" /><category term="large-scale-serving" /><summary type="html"><![CDATA[TL;DR]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vllm-project.github.io/assets/figures/2026-07-23-glm-5.2-nvfp4-b300-pd/01-hero.png" /><media:content medium="image" url="https://vllm-project.github.io/assets/figures/2026-07-23-glm-5.2-nvfp4-b300-pd/01-hero.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Announcing vLLM AFD Plugin: Disaggregating Attention and FFN for Flexible MoE Serving</title><link href="https://vllm-project.github.io/2026/07/23/vllm-afd-plugin.html" rel="alternate" type="text/html" title="Announcing vLLM AFD Plugin: Disaggregating Attention and FFN for Flexible MoE Serving" /><published>2026-07-23T00:00:00+00:00</published><updated>2026-07-23T00:00:00+00:00</updated><id>https://vllm-project.github.io/2026/07/23/vllm-afd-plugin</id><content type="html" xml:base="https://vllm-project.github.io/2026/07/23/vllm-afd-plugin.html"><![CDATA[<p>We are excited to introduce <a href="https://github.com/vllm-project/afd-plugin"><strong>vLLM AFD Plugin</strong></a>, an experimental external plugin that brings <strong>Attention-FFN Disaggregation (AFD)</strong> to vLLM.</p>

<p>vLLM AFD Plugin brings AFD into Mixture-of-Experts (MoE) models by separating Attention and FFN into independently deployed services. The plugin preserves vLLM’s existing request lifecycle and OpenAI-compatible serving interface, while allowing the Attention and FFN paths to scale independently.</p>

<p>The project currently supports NVIDIA GPUs and Ascend NPUs, synchronous and asynchronous connectors, DeepSeek V2/V3-family model wrappers, and eager, graph, and dual-batch execution paths within clearly validated limits.</p>

<div class="markdown-alert markdown-alert-note"><p class="markdown-alert-title"><svg data-component="Octicon" class="octicon octicon-info" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg> Note</p><p>This project is still experimental and needs more large-scale testing across different hardware backends.</p>
</div>

<h2 id="why-attention-ffn-disaggregation">Why Attention-FFN Disaggregation?</h2>

<p>Mixture-of-Experts (MoE) inference combines two very different kinds of work inside every transformer layer. Attention is stateful and closely coupled to request scheduling and the KV cache, while the FFN or expert path is dominated by routed expert computation and all-to-all communication. When both paths share the same worker topology, the serving system must make one set of scaling and execution choices for workloads with very different requirements.</p>

<p>Making this separation practical requires addressing several system design challenges:</p>

<ol>
  <li><strong>Attention and FFN have different scaling requirements.</strong> Attention capacity follows request state, sequence length, and KV-cache pressure. Expert capacity follows token routing and expert load. The serving system should support independent scaling by allowing both paths to use different rank topologies, instead of requiring one shared layout.</li>
  <li><strong>Attention and FFN have different runtime responsibilities.</strong> Attention needs scheduling, KV-cache coordination, and sampling. FFN execution only needs activations, routing metadata, and a way to return expert outputs. Splitting the services lets the FFN side run as a lightweight connector-driven daemon.</li>
  <li><strong>Communication is backend-specific.</strong> CUDA and Ascend expose different collective libraries, graph runtimes, and optimized MoE operators. A common connector contract keeps the model-facing flow stable while allowing each backend to own its data path.</li>
  <li><strong>Communication and computation benefit from overlap.</strong> Asynchronous dispatch and MoE ubatching can overlap independent stages instead of serializing all expert work behind the Attention path.</li>
</ol>

<p>Together, these challenges define the core design goal of AFD: keep vLLM’s request-facing Attention path intact, while moving FFN execution behind a narrow connector interface that can scale, communicate, and execute independently.</p>

<h2 id="inside-the-architecture">Inside the Architecture</h2>

<p><img src="/assets/figures/2026-07-23-vllm-afd-plugin/vllm-afd-plugin-architecture.svg" alt="vLLM AFD Plugin runtime architecture" /></p>

<p>The plugin integrates through vLLM’s <code class="language-plaintext highlighter-rouge">vllm.general_plugins</code> entry point and the standard <code class="language-plaintext highlighter-rouge">--additional-config</code> channel. It does not require edits to the vLLM source tree.</p>

<p>The runtime has three main parts:</p>

<ul>
  <li><strong>Attention service.</strong> The Attention worker retains vLLM’s scheduler, KV cache, batching, model lifecycle, and sampling path. A plugin-owned model runner installs AFD metadata into the forward context and publishes data-parallel, ubatch, layer, and graph state to the FFN side.</li>
  <li><strong>FFN service.</strong> The FFN worker has no request traffic, scheduler, or KV cache. A background loop receives metadata and activations, invokes <code class="language-plaintext highlighter-rouge">compute_ffn_output()</code> on the plugin-owned model wrapper, and sends the result back to Attention. Requests are always sent to the Attention API server.</li>
  <li><strong>Connector layer.</strong> At each split layer, the connector transfers Attention hidden states together with the execution metadata required by the FFN service, then returns the computed FFN outputs. A backend-neutral connector interface defines this exchange while allowing each backend to implement its own communication and runtime optimizations.</li>
</ul>

<p>This integration surface is designed to be intentionally small. vLLM continues to own the serving control plane where its existing abstractions fit, while the plugin provides the implementations of AFD workers, model runners, connectors, metadata, model split points, and a small set of version-scoped compatibility patches.</p>

<h3 id="connector-and-backend-support">Connector and backend support</h3>

<table>
  <thead>
    <tr>
      <th>Connector</th>
      <th>Backend</th>
      <th>Execution</th>
      <th>Recommended stage</th>
      <th>Graph support</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">P2pNcclAFDConnector</code></td>
      <td>GPU</td>
      <td>Synchronous P2P</td>
      <td>Decode</td>
      <td><code class="language-plaintext highlighter-rouge">FULL_DECODE_ONLY</code> CUDA graph</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">CAMP2pAFDConnector</code></td>
      <td>NPU</td>
      <td>Synchronous CAMP2P/HCCL</td>
      <td>Decode</td>
      <td><code class="language-plaintext highlighter-rouge">FULL_DECODE_ONLY</code> ACL graph</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">CAMAsyncAFDConnector</code></td>
      <td>NPU</td>
      <td>Asynchronous CAM</td>
      <td>Prefill</td>
      <td>Not currently supported</td>
    </tr>
  </tbody>
</table>

<p>The same high-level exchange - Attention output to FFN, FFN output back to Attention - is shared across connectors. Backend packages remain separate so CUDA graph behavior, ACL graph behavior, NCCL communication, and Ascend custom operators do not leak into one another.</p>

<h3 id="supported-features">Supported features</h3>

<ul>
  <li><strong>Native vLLM serving surface.</strong> Existing vLLM users still launch with <code class="language-plaintext highlighter-rouge">vllm serve</code>, send requests to an OpenAI-compatible endpoint, and configure the runtime through <code class="language-plaintext highlighter-rouge">--additional-config</code>.</li>
  <li><strong>GPU and NPU implementations.</strong> GPU workers extend vLLM v1 classes, while NPU workers extend vLLM-Ascend classes directly. Shared behavior lives in configuration, topology, metadata, and connector contracts rather than cross-device inheritance.</li>
  <li><strong>Synchronous AFD for decode throughput.</strong> <code class="language-plaintext highlighter-rouge">P2pNcclAFDConnector</code> and <code class="language-plaintext highlighter-rouge">CAMP2pAFDConnector</code> synchronously exchange Attention activations and FFN outputs, allowing the two roles to scale independently in throughput-oriented decode deployments. Their current graph paths use <code class="language-plaintext highlighter-rouge">FULL_DECODE_ONLY</code> semantics on CUDA and ACL, respectively.</li>
  <li><strong>Asynchronous AFD for prefill.</strong> <code class="language-plaintext highlighter-rouge">CAMAsyncAFDConnector</code> uses CAM asynchronous dispatch and combine operators to decouple prefill Attention ranks from expert workers. Together with AFD-managed MoE ubatching, it overlaps independent Attention and FFN stages to reduce pipeline stalls. This path currently targets the prefill stage in a prefill/decode-disaggregated deployment and does not yet support graph execution.</li>
  <li><strong>MoE model integration.</strong> The plugin registers wrappers for DeepSeek V2/V3-family architectures, including DeepSeek V3.2, and GLM MoE DSA. The wrapper exposes separate Attention and FFN computations while reusing upstream layer implementations.</li>
  <li><strong>Graph and ubatching paths.</strong> The synchronous GPU and NPU connectors support decode-only graph capture. Dual Batch Overlap is supported with exactly two ubatches, and CAM async provides AFD-managed MoE ubatching for its prefill path.</li>
</ul>

<h2 id="a-performance-snapshot">A Performance Snapshot</h2>

<h3 id="synchronous-afd-decode-throughput-with-camp2pafdconnector">Synchronous AFD Decode Throughput with <code class="language-plaintext highlighter-rouge">CAMP2pAFDConnector</code></h3>

<p>The synchronous decode recipe in <a href="https://github.com/vllm-project/afd-plugin/pull/67">vllm-project/afd-plugin#67</a> compares a conventional EP64 deployment with <code class="language-plaintext highlighter-rouge">CAMP2pAFDConnector</code>-based AFD deployments for DeepSeek-V3.2 W8A8 on Ascend 910C. The benchmark measures saturated decode throughput rather than online-serving latency.</p>

<table>
  <thead>
    <tr>
      <th>Deployment</th>
      <th>Physical topology</th>
      <th style="text-align: right">Total dies</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>EP64</td>
      <td>DP64, EP64, TP1</td>
      <td style="text-align: right">64</td>
    </tr>
    <tr>
      <td>48A16F</td>
      <td>48 Attention ranks, 16 FFN ranks</td>
      <td style="text-align: right">64</td>
    </tr>
    <tr>
      <td>64A16F</td>
      <td>64 Attention ranks, 16 FFN ranks</td>
      <td style="text-align: right">80</td>
    </tr>
  </tbody>
</table>

<div class="markdown-alert markdown-alert-note"><p class="markdown-alert-title"><svg data-component="Octicon" class="octicon octicon-info" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path></svg> Note</p><p>These are controlled performance results, not accuracy or production-serving results. Due to limited machine availability, the physical 48A16F and 64A16F deployments simulate logical 192A64F and 256A64F scales. The experiment replaces natural routed expert IDs with a deterministic forced-balancing cycle, which changes model outputs. <code class="language-plaintext highlighter-rouge">AFDDecodeBenchConnector</code> supplies the decode-only KV state, and DBO is enabled for AFD.</p>
</div>

<p>Throughput is normalized by the total number of deployed dies:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tokens/s/die = aggregate output token throughput / total deployed dies
</code></pre></div></div>

<p>Both workloads use fixed-length inputs and uniformly distributed outputs from 512 to 1,536 tokens.</p>

<h4 id="16k-fixed-input">16K fixed input</h4>

<p><img src="/assets/figures/2026-07-23-vllm-afd-plugin/throughput_dsv3-2_16k.png" alt="DeepSeek-V3.2 16K decode throughput per die" /></p>

<p>EP64 achieves <strong>232.6 tokens/s/die</strong>, 48A16F achieves <strong>220.3 tokens/s/die</strong>, and 64A16F achieves <strong>258.9 tokens/s/die</strong>. Relative to EP64, the AFD results are <strong>-5.3%</strong> for 48A16F and <strong>+11.3%</strong> for 64A16F.</p>

<h4 id="32k-fixed-input">32K fixed input</h4>

<p><img src="/assets/figures/2026-07-23-vllm-afd-plugin/throughput_dsv3-2_32k.png" alt="DeepSeek-V3.2 32K decode throughput per die" /></p>

<p>EP64 achieves <strong>168.2 tokens/s/die</strong>, 48A16F achieves <strong>151.4 tokens/s/die</strong>, and 64A16F achieves <strong>183.3 tokens/s/die</strong>. Relative to EP64, the AFD results are <strong>-10.0%</strong> for 48A16F and <strong>+9.0%</strong> for 64A16F.</p>

<p>Across both input lengths, 48A16F is below the EP64 baseline, while 64A16F delivers the highest normalized throughput: <strong>+11.3% at 16K</strong> and <strong>+9.0% at 32K</strong>. This result shows that the Attention-to-FFN allocation matters; disaggregation alone does not guarantee a throughput gain.</p>

<p>Due to limited machine availability, we did not evaluate deployments with higher Attention-to-FFN ratios. The observed trend suggests that, at the ratios tested, the FFN ranks still have compute headroom rather than being compute-bound. Increasing the proportion of Attention ranks may therefore reveal further throughput gains.</p>

<h3 id="asynchronous-afd-prefill-performance-with-camasyncafdconnector">Asynchronous AFD Prefill Performance with <code class="language-plaintext highlighter-rouge">CAMAsyncAFDConnector</code></h3>

<p>The repository includes an early CAM async experiment on two Ascend 910C nodes using a DeepSeek V3.2 W8A8 model reduced to 10 layers. The comparison uses forced expert balancing and contrasts a <code class="language-plaintext highlighter-rouge">DP4PCP8 TP1</code> baseline with an AFD layout consisting of Attention <code class="language-plaintext highlighter-rouge">DP3PCP8 TP1</code> plus FFN <code class="language-plaintext highlighter-rouge">EP8</code>.</p>

<p><img src="/assets/figures/2026-07-23-vllm-afd-plugin/text_matched_dp_afd_median_ttft.png" alt="Median TTFT comparison for the CAM async experiment" /></p>

<p>Across the measured request rates, the AFD configuration lowers median/P50 time to first token. At 12 requests per second, median TTFT decreases from <strong>15.1 seconds to 8.0 seconds</strong>, a reduction of approximately <strong>47%</strong>. At both 10 and 12 requests per second, the measured gap is about 7.2 seconds.</p>

<p><strong>Note</strong>: These numbers are a focused validation of the CAM async execution path, not a general performance claim for full DeepSeek V3.2 or every AFD topology. The performance gains may also vary across workloads.</p>

<h2 id="getting-started">Getting Started</h2>

<p>The current implementation requires Python 3.10–3.13 and targets vLLM <code class="language-plaintext highlighter-rouge">0.19.1</code>.</p>

<h3 id="install">Install</h3>

<p>Check out the installation steps in our <a href="https://github.com/vllm-project/afd-plugin#install">README</a> for details.</p>

<h3 id="deployment-recipes">Deployment Recipes</h3>

<p>Deployment commands depend on the backend, connector, model, and rank topology. Instead of duplicating configurations here, use the maintained <a href="https://github.com/vllm-project/afd-plugin/tree/main/recipe">AFD Plugin recipes</a>:</p>

<ul>
  <li><strong>GPU synchronous AFD:</strong> the <a href="https://github.com/vllm-project/afd-plugin/tree/main/recipe/gpu/p2p_nccl/deepseek_v2_lite">DeepSeek V2 Lite P2P NCCL recipes</a> cover decode-oriented colocated and prefill/decode-disaggregated deployments, eager and CUDA graph execution, and multiple DP/TP layouts.</li>
  <li><strong>NPU asynchronous prefill AFD:</strong> the <a href="https://github.com/vllm-project/afd-plugin/blob/main/recipe/npu/cam_async/DeepSeek-V3.2.md">DeepSeek V3.2 CAM async recipe</a> documents the required environment, topology, AFD configuration, benchmark setup, and current limitations.</li>
</ul>

<p>Refer to the repository README and recipe directory for the latest supported connector matrix, configuration fields, and complete launch commands.</p>

<h2 id="current-scope-and-roadmap">Current Scope and Roadmap</h2>

<p>The project intentionally exposes its current boundaries: exact vLLM version pinning, model runner v1 only, full weights on both roles, decode-only graph modes, exactly two ubatches for DBO, and hardware-gated end-to-end testing.</p>

<p>The next phase of development will focus on:</p>

<ul>
  <li><strong>Broader vLLM compatibility and upstream alignment:</strong> track newer vLLM releases, evaluate model runner v2, keep compatibility patches minimal, and contribute generally useful abstractions upstream as they mature.</li>
  <li><strong>More flexible execution:</strong> extend graph modes, ubatch counts, asynchronous stages, and validated rank topologies.</li>
  <li><strong>Production-scale validation:</strong> publish repeatable accuracy, latency, throughput, stability, and multi-node results on full models and realistic workloads.</li>
  <li><strong>Expanded model and connector coverage:</strong> add MoE architectures and backend transports through the existing model-wrapper and connector interfaces, together with corresponding deployment recipes for each newly supported model and connector.</li>
  <li><strong>Multimodal and vLLM-Omni integration:</strong> explore how AFD can integrate with <a href="https://github.com/vllm-project/vllm-omni">vLLM-Omni</a> and heterogeneous multimodal pipelines, including its application within autoregressive (AR), Diffusion Transformer (DiT), and other stages that can benefit from independently scaled Attention and FFN execution.</li>
  <li><strong>Heterogeneous hardware and low-latency serving:</strong> explore deploying Attention and FFN roles across different accelerator types and interconnects, together with connector, scheduling, placement, and computation-communication overlap optimizations that reduce time to first token and inter-token latency.</li>
</ul>

<h2 id="join-the-community">Join the Community</h2>

<p>vLLM AFD Plugin is at an early stage, and feedback from model, serving, and hardware communities will shape its direction.</p>

<ul>
  <li><strong>Code and documentation:</strong> <a href="https://github.com/vllm-project/afd-plugin">github.com/vllm-project/afd-plugin</a></li>
  <li><strong>Runtime design docs:</strong> <a href="https://github.com/vllm-project/afd-plugin/tree/main/docs">GPU Attention/FFN and Ascend Attention/FFN designs</a></li>
  <li><strong>Issues and feature requests:</strong> <a href="https://github.com/vllm-project/afd-plugin/issues">GitHub Issues</a></li>
</ul>

<p>Let’s build a more composable and hardware-aware future for MoE serving together.</p>]]></content><author><name>AFD Plugin Contributors</name></author><category term="inference" /><category term="moe" /><category term="ecosystem" /><summary type="html"><![CDATA[We are excited to introduce vLLM AFD Plugin, an experimental external plugin that brings Attention-FFN Disaggregation (AFD) to vLLM.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vllm-project.github.io/assets/figures/2026-07-23-vllm-afd-plugin/vllm-afd-plugin-architecture.svg" /><media:content medium="image" url="https://vllm-project.github.io/assets/figures/2026-07-23-vllm-afd-plugin/vllm-afd-plugin-architecture.svg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">A Preview of Production-Scale Kimi K3 Support on vLLM</title><link href="https://vllm-project.github.io/2026/07/22/kimi-k3-preview.html" rel="alternate" type="text/html" title="A Preview of Production-Scale Kimi K3 Support on vLLM" /><published>2026-07-22T00:00:00+00:00</published><updated>2026-07-22T00:00:00+00:00</updated><id>https://vllm-project.github.io/2026/07/22/kimi-k3-preview</id><content type="html" xml:base="https://vllm-project.github.io/2026/07/22/kimi-k3-preview.html"><![CDATA[<p>Last week, Moonshot AI <a href="https://www.kimi.com/blog/kimi-k3">introduced Kimi K3</a>, a 2.8-trillion-parameter model with native vision support, a 1-million-token context window, Kimi Delta Attention (KDA), Attention Residuals (AttnRes), and a highly sparse Mixture-of-Experts architecture. The announcement immediately drew <a href="https://x.com/Kimi_Moonshot/status/2077830229968683203">global attention</a>, and the open-source community is extremely excited that open-weight models are advancing quickly to catch up with the best proprietary models.</p>

<p>Moonshot AI has announced that the full model weights will be released by July 27, 2026. In the meantime, vLLM, Moonshot AI, NVIDIA, AMD, and the broader community are working through the final integration and validation so the open-source community can serve Kimi K3 from day 0.</p>

<p>This post is a preview and performance optimization is ongoing, but the core model path, KDA-aware prefix caching, multimodal integration, tool calling parsers, and hardware-specific optimizations are already taking shape. Selected trusted partners, approved by both Moonshot AI and the vLLM/Inferact team, have also begun deployment validation using the same code that is being prepared for open source.</p>

<p>As stated in the announcement blog, KDA poses new challenges for conventional prefix caching, and the Moonshot AI team has contributed a corresponding implementation to the vLLM project, to be released alongside the model weights. We will dedicate a future blog post to explaining the design.</p>

<h2 id="tldr">TL;DR</h2>

<ul>
  <li><strong>Day-0 open-source serving:</strong> vLLM is preparing model implementation, Docker images, deployment recipes, and production validation for the Kimi K3 weight release.</li>
  <li><strong>A new hybrid architecture:</strong> Kimi K3 combines KDA-dominant linear attention with periodic full-attention layers, AttnRes across depth, Stable LatentMoE, and native vision support.</li>
  <li><strong>Prefix caching required core changes:</strong> vLLM now separates the physical KDA state-block size from prefix-match granularity, enabling useful partial prefix-cache hits without storing recurrent state at every small attention block.</li>
  <li><strong>Kernel work across the stack:</strong> the release branch includes FlashKDA integration, fused KDA decode, fused KDA projections and convolution, fused AttnRes, reimplemented MLA module, SiTU-enabled MXFP4 MoE execution, and optimized expert routing.</li>
  <li><strong>NVIDIA and AMD support:</strong> NVIDIA-specific kernels are under final tuning, while an initial AMD implementation with a FlyDSL MoE kernel is already in place and moving through broader validation.</li>
</ul>

<h2 id="kimi-k3-at-a-glance">Kimi K3 at a Glance</h2>

<p>Kimi K3 is not a larger version of Kimi K2. Kimi K3 changes the serving problem in several dimensions at once.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Property</th>
      <th style="text-align: left">Kimi K3 configuration</th>
      <th style="text-align: left">Serving implication</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>Model scale</strong></td>
      <td style="text-align: left"><strong>2.8T parameters</strong></td>
      <td style="text-align: left">Requires large-scale expert parallelism and high-bandwidth accelerator domains</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Context length</strong></td>
      <td style="text-align: left"><strong>1M tokens</strong></td>
      <td style="text-align: left">Makes cache capacity, prefix reuse, chunked prefill, and prefill/decode disaggregation first-order concerns</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Attention</strong></td>
      <td style="text-align: left"><strong>Hybrid KDA and full attention</strong></td>
      <td style="text-align: left">Requires both recurrent state caches and paged KV caches to advance on exactly the same logical prefix</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Depth</strong></td>
      <td style="text-align: left"><strong>Attention Residual</strong></td>
      <td style="text-align: left">Adds cross-layer representation reads and writes that need dedicated kernels</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>MoE</strong></td>
      <td style="text-align: left"><strong>896 routed experts, 16 active per token, plus shared experts</strong></td>
      <td style="text-align: left">Makes routing, dispatch, load balance, and MoE kernels central to end-to-end performance</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Quantization</strong></td>
      <td style="text-align: left"><strong>MXFP4 weights in the provided release configuration</strong></td>
      <td style="text-align: left">Needs an efficient FP4 MoE path with Kimi K3’s SiTU activation</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Multimodality</strong></td>
      <td style="text-align: left"><strong>Native vision with a vision tower</strong></td>
      <td style="text-align: left">Requires multimodal preprocessing (image-only) and a robust vision parallelism strategy</td>
    </tr>
  </tbody>
</table>

<p>For inference systems, each of these choices moves cost somewhere new. KDA reduces the need to retain a conventional KV pair for every past token, but introduces a large recurrent state. AttnRes reduces the limitations of a single residual stream, but creates additional cross-layer memory traffic. Extreme MoE sparsity avoids activating all 2.8T parameters for every token, but raises the stakes for routing and communication. vLLM’s job is to make all of these pieces work together behind one familiar serving API.</p>

<h2 id="a-collaboration-built-over-multiple-kimi-generations">A Collaboration Built Over Multiple Kimi Generations</h2>

<p>Kimi K3 continues a long collaboration between Moonshot AI and the vLLM community.</p>

<ul>
  <li>At <a href="https://china2024.gosim.org/schedules/vllm-in-moonshot.html">GOSIM 2024</a>, Moonshot AI engineers presented how vLLM was used at scale inside Moonshot AI and discussed the vLLM + Mooncake prefill/decode-disaggregated architecture.</li>
  <li>Moonshot AI later shared Kimi K2 training and inference practices at the <a href="https://pytorch.org/blog/vllm-beijing-meetup-advancing-large-scale-llm-deployment/">vLLM Beijing Meetup</a>, including operating under strict SLOs while serving online traffic and supporting reinforcement-learning workloads.</li>
  <li>vLLM has been a day-0 launch partner for Kimi K2, Kimi K2-Thinking, Kimi K2.5, Kimi Linear, and so on.</li>
  <li>vLLM has deep technical collaboration with Moonshot AI engineers, including <a href="https://vllm.ai/blog/Kimi-K2-Accuracy">Kimi K2 tool-calling accuracy</a> for correctness, <a href="https://vllm.ai/blog/improved-cuda-debugging">improved CUDA debugging</a> for development, <a href="https://github.com/vllm-project/vllm/pull/23734">decode context parallelism</a>, Mooncake-based PD disaggregation, and large-scale performance validation. Kimi K2.5 has also appeared in public <a href="https://inferencex.semianalysis.com/inference?g_rundate=2026-04-07&amp;g_model=Kimi-K2.5&amp;g_runid=24100518225&amp;i_gpus=gb200_dynamo-vllm&amp;i_dstart=2026-04-07&amp;i_dend=2026-04-07">InferenceX serving results</a>.</li>
</ul>

<p>That history matters. Day-0 support is rarely one pull request written after a release announcement. It comes from model and inference teams sharing architecture details early, testing real checkpoints under realistic parallelism, identifying gaps in the serving engine, and upstreaming improvements that remain useful after one launch. <strong>vLLM is proud to be a long-term partner of Moonshot AI and a popular inference engine for Kimi-series models.</strong></p>

<p>Now, let’s dive into one of the most interesting technical challenges we ran into.</p>

<h2 id="the-hardest-part-prefix-caching-for-kda">The Hardest Part: Prefix Caching for KDA</h2>

<p>Conventional full attention and KDA remember a prefix in very different ways.</p>

<p>In full attention, a prefix is represented by per-token key and value vectors. vLLM stores those vectors in paged blocks, hashes complete token blocks, and can reuse a matching sequence of blocks for another request.</p>

<p>KDA is recurrent. Instead of retaining a conventional KV pair for every token, each KDA layer advances a matrix-like recurrent state, together with a short convolution state. To resume from a cached prefix, the engine needs the KDA state <em>at the exact prefix boundary</em>. Replaying an earlier state to reach that boundary would erase much of the benefit of prefix caching.</p>

<p><img src="/assets/figures/2026-07-22-kimi-k3-preview/kda-prefix-state.png" alt="How conventional attention and KDA represent cached prefixes" /></p>

<p>The straightforward solution—store KDA state at every small attention-cache boundary—is too expensive. A KDA state is much larger than one ordinary token’s KV entry, so implementations use a relatively large physical state block to amortize storage. Before the current work, that physical block size also constrained where a prefix-cache hit could land. With a multi-thousand-token state block, two requests sharing almost the entire prompt could still miss the reusable prefix because their common boundary did not fill the same physical block.</p>

<p>The new vLLM design separates three concepts that used to move together:</p>

<ul>
  <li><strong>Physical block size:</strong> how KDA state and full-attention KV are allocated on the GPU.</li>
  <li><strong>Scheduler alignment:</strong> where execution must stop so all cache groups remain consistent.</li>
  <li><strong>Prefix-match unit:</strong> the finer token interval at which a shared prefix is hashed and may be matched.</li>
</ul>

<p><img src="/assets/figures/2026-07-22-kimi-k3-preview/fine-grained-prefix-cache.png" alt="Fine-grained prefix matching inside a larger physical KDA state block" /></p>

<p>This lets vLLM register a valid KDA state at a fine-grained boundary inside a larger physical state block. When a later request hits that partial block, the cached state is copied into a private destination before the request extends it. This copy-on-write rule preserves the shared cached prefix while allowing the new request to continue generation safely.</p>

<p>The implementation also handles details that are easy to miss:</p>

<ul>
  <li>The scheduler stops at the right block and hash boundaries so the recurrent state being registered really corresponds to the advertised token prefix.</li>
  <li>Full-attention and KDA cache groups agree on one <code class="language-plaintext highlighter-rouge">num_computed_tokens</code>, even though their physical block sizes differ.</li>
  <li>Partial cache entries use chained, fine-grained hashes so a boundary identifies the entire prefix, not only the tail tokens.</li>
  <li>Same-step reuse is deferred until the state copy is safe, avoiding races between cache registration and extension.</li>
  <li>Cache transfer and disaggregated prefill/decode paths can carry the same logical prefix across workers.</li>
</ul>

<p>This work was motivated by Kimi K3 and many other hybrid attention models, but it is core vLLM infrastructure rather than a model-specific shortcut. The vLLM team and the Moonshot AI team collaborated deeply on the design. The two teams will publish a separate post with the design, invariants, and benchmarks in more detail.</p>

<h2 id="performance-work-removing-the-new-bottlenecks">Performance Work: Removing the New Bottlenecks</h2>

<p>Our current progress can be summarized into this table:</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Area</th>
      <th style="text-align: left">Current status</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>Model and configuration</strong></td>
      <td style="text-align: left">Kimi K3 language and vision model definitions are integrated, with separate <strong>NVIDIA</strong> and <strong>AMD</strong> implementations where hardware paths differ</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Optimized MLA module for native PD disaggregation deployment</strong></td>
      <td style="text-align: left">Optimized MLA module with manual kernel fusion and separate prefill/decode paths. Gate projection runs in parallel with attention, with multi-stream support in decode and a fused epilogue in prefill—highly optimized for PD disaggregation deployment.</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Serving semantics</strong></td>
      <td style="text-align: left">Kimi K3 chat rendering, tokenizer integration, streaming parsing, tool calls, reasoning output, and structured-output paths are implemented and under <strong>final end-to-end validation</strong></td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>KDA prefill</strong></td>
      <td style="text-align: left">FlashKDA and Triton paths are integrated; final backend selection and numerical validation are <strong>in progress</strong></td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>KDA decode</strong></td>
      <td style="text-align: left">A fused <strong>NVIDIA</strong> decode kernel covering convolution, the recurrent KDA update, gating, and normalization is integrated, with portable fallback paths retained</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Prefix caching</strong></td>
      <td style="text-align: left">Fine-grained partial prefix hits for hybrid full-attention + recurrent-state caches are integrated; disaggregated and offload scenarios are <strong>being validated</strong></td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Attention Residuals</strong></td>
      <td style="text-align: left">Triton and <strong>NVIDIA</strong> kernels are integrated, including fusion of residual addition and output RMSNorm on supported shapes</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>MoE</strong></td>
      <td style="text-align: left">Kimi K3’s <strong>SiTU</strong> activation is wired into <strong>MXFP4 TRTLLM-Gen</strong> and <strong>DeepGEMM</strong> paths; optimized grouped top-k routing is integrated. <strong>AMD</strong> implements FlyDSL’s <strong>MLIR</strong> kernel stack with hardware-tuned <strong>A16W4/A8W4</strong> fused operators and <strong>SiTU</strong> activation</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Production stack</strong></td>
      <td style="text-align: left">Non-disaggregated serving is working; Dynamo + vLLM + Mooncake disaggregated serving, expert parallelism, and vendor verification are in the <strong>final validation loop</strong></td>
    </tr>
  </tbody>
</table>

<p>Kimi K3 changes the hot path, so the team has optimized more than the attention kernel itself. Below are details of the progress in each area.</p>

<h3 id="kda-prefill-and-decode">KDA prefill and decode</h3>

<p>The prefill path integrates FlashKDA and Flash Linear Attention (FLA). Around the core recurrence, vLLM fuses the input projections and causal convolution, and gathers initial recurrent states in one operation.</p>

<p>Decode uses a fused NVIDIA kernel on supported architectures and shapes. Instead of launching separate operations for the short convolution, KDA state update, output gate, and normalization for every generated token, the fused path performs them together. This is especially important because Kimi K3 contains many KDA layers; a small per-layer launch or memory penalty quickly becomes a large TPOT penalty.</p>

<h3 id="attention-residuals">Attention Residuals</h3>

<p>AttnRes retrieves from representations written by earlier layer blocks rather than relying on only one uniformly accumulated residual stream. A naive implementation creates extra reads, writes, reductions, and normalization launches throughout the 93-layer network.</p>

<p>The release branch includes a Triton implementation and an NVIDIA kernel that fuse residual update, AttnRes mixing, and output RMSNorm for supported cases. Sequence-parallel work also shards the attention-residual traffic across ranks. Early kernel-level results are encouraging, while end-to-end gains are still being measured across prefill lengths and parallel configurations.</p>

<h3 id="optimized-mla-module-for-native-pd-disaggregation-deployment">Optimized MLA module for native PD disaggregation deployment</h3>

<p>Kimi K3 still uses MLA attention every four layers. In the previous model, vLLM relied heavily on a <code class="language-plaintext highlighter-rouge">torch.compile</code> custom-fusion path to map small kernels into fused kernels, which slowed startup and still left many kernels unfused. In this release, we implement a new MLA module that fuses these kernels manually. MLA also requires different kernel launch orders for prefill and decode, so we implement two code paths with different fusion patterns, specialized for PD-disaggregated deployment. Furthermore, Kimi K3 introduces a gate projection that can execute in parallel with the main attention path. We optionally add multi-stream support for the gate projection in the decode path, while in the prefill path—where multi-stream overlap is not optimal—we fuse the elementwise multiply and sigmoid into the gate-projection epilogue.</p>

<h3 id="mxfp4-moe">MXFP4 MoE</h3>

<p>Kimi K3’s release configuration uses MXFP4 weights and the SiTU activation. Before this work, the MXFP4 TRTLLM-Gen path did not support SiTU and would fall back to a slower implementation. vLLM now maps Kimi K3’s SiTU parameters into the optimized FP4 expert path and also handles large token-by-top-k launch grids by safely chunking the workload.</p>

<p>This has already been validated on a 16-GPU DP16+EP16 configuration, where all ranks selected the optimized MXFP4 backend and passed correctness checks.</p>

<p>On the AMD side, Kimi K3 MoE is supported on FlyDSL’s MLIR Python kernel stack. This includes hardware-tuned A16W4/A8W4 quantized fused operators and a SiTU activation implementation, all built on FlyDSL’s modular abstractions.</p>

<h2 id="what-to-expect-on-open-source-day">What to Expect on Open-Source Day</h2>

<p>The planned day-0 package includes:</p>

<ul>
  <li>vLLM model, parser, cache, and kernel integration;</li>
  <li>initial open-source Docker images;</li>
  <li>validated launch recipes for NVIDIA configurations;</li>
  <li>an initial AMD path with FlyDSL MoE kernel, with more ROCm tuning to follow;</li>
  <li>multimodal, tool-use, reasoning, and structured-output examples;</li>
  <li>initial performance results.</li>
</ul>

<p>Trusted deployment partners are already exercising the release candidate under a dual-approval process from Moonshot AI and vLLM/Inferact. This provides real production feedback without distributing prerelease model artifacts broadly. It also gives us a chance to test the complete serving system—frontend semantics, batching, cache transfer, expert parallelism, observability, and failure handling—not only isolated kernels.</p>

<h2 id="acknowledgements">Acknowledgements</h2>

<p>Kimi K3 day-0 support is a joint effort across the model vendor, inference engine, and hardware communities.</p>

<p>We thank the <strong>Moonshot AI team</strong> for creating Kimi K3, sharing architecture details ahead of the weight release, contributing the initial model integration and KDA prefix-caching work, and collaborating closely on correctness and production validation.</p>

<p>We thank the <strong>Inferact team</strong> for integrating the model into vLLM, extending the core cache manager for partial hybrid prefix hits, implementing serving semantics and multimodal support, building deployment recipes, and driving end-to-end performance optimization.</p>

<p>We thank the <strong>NVIDIA team</strong> for KDA decode and Attention Residual kernels, MXFP4 MoE collaboration, and performance work across the board.</p>

<p>We thank the <strong>AMD team</strong> for initial day-0 ROCm support and for continuing to expand Kimi K3 across AMD GPUs.</p>

<p>Most importantly, we thank the broader open-source community for the anticipation, testing, and feedback already surrounding Kimi K3. We look forward to putting the weights and the inference engine support in your hands.</p>

<h2 id="one-more-thing-why-the-announcement-and-open-source-release-are-separated">One More Thing: Why the Announcement and Open-Source Release Are Separated</h2>

<p>Kimi K3 also features a release process that we hope more model vendors will consider: announce the model first, then release the weights and inference engine support later.</p>

<p>The vLLM team proposed this separation, and Moonshot AI agreed and executed. The reason is practical. A frontier-model announcement has unavoidable last-mile uncertainty. The model team is simultaneously stabilizing its own products, APIs, evaluations, safety work, documentation, and commercial launch. If open-source weights and open-source support must land at the exact same moment, a community project such as vLLM suffers from the moving deadline.</p>

<p>Separating the two timelines gives both sides a better contract:</p>

<ol>
  <li>The model vendor can concentrate on its product launch and freeze the final checkpoint, configuration, tokenizer, and serving semantics.</li>
  <li>The open-source inference engine team gets a stable integration window for correctness tests, performance tuning, Docker builds, and recipe validation.</li>
  <li>The community gets a public, bounded expectation instead of an ambiguous “coming soon.”</li>
</ol>

<p>The separation is not a retreat from day-0 support. It is a more sustainable way to deliver day-0 support against the artifact that users will actually download. We encourage more model vendors to follow!</p>]]></content><author><name>vLLM Team</name></author><category term="models" /><category term="performance" /><category term="prefix caching" /><category term="multimodal" /><summary type="html"><![CDATA[Last week, Moonshot AI introduced Kimi K3, a 2.8-trillion-parameter model with native vision support, a 1-million-token context window, Kimi Delta Attention (KDA), Attention Residuals (AttnRes), and a highly sparse Mixture-of-Experts architecture. The announcement immediately drew global attention, and the open-source community is extremely excited that open-weight models are advancing quickly to catch up with the best proprietary models.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vllm-project.github.io/assets/figures/2026-07-22-kimi-k3-preview/social-preview.png" /><media:content medium="image" url="https://vllm-project.github.io/assets/figures/2026-07-22-kimi-k3-preview/social-preview.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Beyond a Single Model: Building Mixture-of-Models Systems with vLLM Semantic Router</title><link href="https://vllm-project.github.io/2026/07/21/vllm-sr-new-chapter-mom.html" rel="alternate" type="text/html" title="Beyond a Single Model: Building Mixture-of-Models Systems with vLLM Semantic Router" /><published>2026-07-21T00:00:00+00:00</published><updated>2026-07-21T00:00:00+00:00</updated><id>https://vllm-project.github.io/2026/07/21/vllm-sr-new-chapter-mom</id><content type="html" xml:base="https://vllm-project.github.io/2026/07/21/vllm-sr-new-chapter-mom.html"><![CDATA[<p>Most AI applications are built around a single model endpoint. But as models, devices, and deployment constraints diversify, no single model is the best fit for every request or environment. The practical question is how multiple specialized models can be coordinated, evaluated, and served through one interface. We call this systems approach <strong>Mixture-of-Models</strong>.</p>

<p>In less than a year since its public launch, <a href="https://github.com/vllm-project/semantic-router">vLLM Semantic Router</a> has reached <strong>5,000 stars</strong>, <strong>150+ contributors</strong>, and <strong>more than 300,000 cumulative downloads</strong> across our Hugging Face model family. Across three major releases—<strong>Iris, Athena, and Themis</strong>—the system boundary moved from choosing a model, to governing multi-model inference, to preserving state and coordination across sessions. Those releases built the foundation for the MoM architecture envisioned from day 0.</p>

<p>This post describes the next step for vLLM Semantic Router: moving from routing among models to building dependable model systems from them. Under one versioned contract, independent models, policies, preferences, and execution paths become a system that can be trained, evaluated, exported, imported, deployed, and invoked through one interface. Our goal is to make vLLM Semantic Router a training, evaluation, and inference engine for Mixture-of-Models.</p>

<p align="center">
  <img src="/assets/figures/2026-07-21-vllm-sr-new-chapter/hero.png" alt="A model portfolio flows into a Mixture-of-Models training, evaluation, and inference engine and is exposed as one model" width="100%" />
  <br />
  <em>Figure 1: A Mixture-of-Models turns a heterogeneous model portfolio into one model experience.</em>
</p>

<h2 id="how-vllm-sr-got-here">How vLLM-SR Got Here</h2>

<p>The <a href="https://blog.vllm.ai/2025/09/11/semantic-router.html">first vLLM Semantic Router post</a> asked a practical question: why give simple and difficult requests the same reasoning budget? A lightweight classifier used fixed domain labels to choose between fast and reasoning paths, helping vLLM spend inference compute more selectively.</p>

<p>Production traffic quickly exposed the limit of that design. Domain alone could not represent privacy, safety, context, language, modality, tools, preferences, latency, and authorization. A static label also could not account for an endpoint that was cheap but overloaded, capable but remote, or unsafe to switch into midway through an agent session.</p>

<p>We rebuilt the classifier layer around modular model support, shared LoRA computation, Rust/Candle inference, and Go integration. We then replaced fixed classification with a Signal–Decision architecture that separated observed evidence from policy and execution. This became the spine of the next three releases.</p>

<table>
  <thead>
    <tr>
      <th>Milestone</th>
      <th>When</th>
      <th>What changed</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Incubation</strong></td>
      <td>Apr 2025</td>
      <td>Early semantic-routing prototypes began with Mixture-of-Models as the long-term system goal</td>
    </tr>
    <tr>
      <td><strong>Initial release</strong></td>
      <td>Sep 2025</td>
      <td>Intent-aware selection between fast and reasoning paths</td>
    </tr>
    <tr>
      <td><strong>v0.1 Iris</strong></td>
      <td>Jan 2026</td>
      <td>Signals, decisions, and route-scoped plugins replaced fixed classification</td>
    </tr>
    <tr>
      <td><strong>v0.2 Athena</strong></td>
      <td>Mar 2026</td>
      <td>Model selection, memory, RAG, long context, and multimodality expanded routing into an inference control system</td>
    </tr>
    <tr>
      <td><strong>v0.3 Themis</strong></td>
      <td>Jun 2026</td>
      <td>Stateful routing, projections, replay, protocol support, session continuity, and one production configuration contract made the system operable</td>
    </tr>
    <tr>
      <td><strong>Fusion and Micro-Agent</strong></td>
      <td>Jun 2026</td>
      <td>The router began choosing collaboration patterns, not only individual models</td>
    </tr>
  </tbody>
</table>

<p align="center">
  <img src="/assets/figures/2026-07-21-vllm-sr-new-chapter/evolution.png" alt="vLLM Semantic Router evolves from intent routing through Iris, Athena, and Themis into a Mixture-of-Models engine" width="100%" />
  <br />
  <em>Figure 2: Each stage changed the unit of control: model, decision, system, session, and finally the complete model lifecycle.</em>
</p>

<p><a href="https://blog.vllm.ai/2026/01/05/vllm-sr-iris.html">Iris</a> made routing composable. Domain, keyword, embedding, factuality, feedback, and preference signals fed explicit decisions, while safety, PII protection, caching, hallucination detection, and tool selection became route-scoped behavior. Iris also introduced the MoM model family and described vLLM-SR as “System Level Intelligence for Mixture-of-Models.”</p>

<p><a href="https://blog.vllm.ai/2026/03/10/v0.2-vllm-sr-athena-release.html">Athena</a> added first-class model selection, memory and RAG, a multilingual and multimodal model stack, ROCm acceleration, and an operating dashboard. The project was becoming the control system around multi-model inference, not just a classifier in front of vLLM.</p>

<p><a href="https://blog.vllm.ai/2026/06/05/v0.3-vllm-sr-themis-release.html">Themis</a> turned that broader system into an operable contract:</p>

<blockquote>
  <p><strong>Signals become projections. Projections feed decisions. Decisions choose algorithms. Algorithms select models.</strong></p>
</blockquote>

<p>Themis added session-aware agentic routing, replayable traces, stronger protocol support, an operator console, and runtime paths across AMD ROCm, NVIDIA CUDA, Intel OpenVINO, and CPU environments. It also made a route explainable: operators can see the evidence, policy, algorithm, and physical model behind each decision.</p>

<h3 id="from-signaldecision-to-workloadrouterpool">From Signal–Decision to Workload–Router–Pool</h3>

<p>The releases built the runtime. Two project papers explained the architecture behind it.</p>

<p>The <a href="https://vllm-sr.ai/white-paper/">white paper, <em>Signal Driven Decision Routing for Mixture-of-Modality Models</em></a>, formalized the separation between neural evidence and symbolic policy. Fast heuristics and learned classifiers turn prompts, context, identity, safety, and modality into a structured signal vector; a Boolean engine then composes those signals into auditable policy. A typed neural-symbolic DSL parses and validates that policy before compiling it into deployable configuration. When the paper was published, the system covered thirteen signal types and thirteen model-selection algorithms, with per-decision plugins for caching, RAG, memory, safety, provider handling, and response validation.</p>

<p>The <a href="https://vllm-sr.ai/vision-paper/">vision paper, <em>The Workload–Router–Pool Architecture for LLM Inference Optimization</em></a>, widened the frame. It argues that three variables have to be designed together:</p>

<ul>
  <li><strong>Workload:</strong> chat or agent, single-turn or multi-turn, warm or cold, prefill-heavy or decode-heavy</li>
  <li><strong>Router:</strong> static semantic policy, online feedback or bandit adaptation, RL-based selection, and quality-aware cascades</li>
  <li><strong>Pool:</strong> homogeneous or heterogeneous accelerators, prefill/decode topology, model placement, and KV-cache management</li>
</ul>

<p>Those variables cannot be optimized independently. Workload shape changes which routing policy works; routing policy changes the required pool size and topology; pool state changes which route is efficient. Safety and privacy cut across all three dimensions, while cost, quality, latency, and energy define the optimization frontier. The paper maps the project’s research into a 3 × 3 WRP matrix and identifies twenty-one open directions where those dimensions still need to meet.</p>

<p align="center">
  <img src="/assets/figures/2026-07-21-vllm-sr-new-chapter/research-arc.png" alt="The white paper formalizes signal decision routing while the vision paper connects workload router and pool co-design" width="100%" />
  <br />
  <em>Figure 3: The white paper defines the programmable routing engine; the vision paper connects it to workload and physical pool design.</em>
</p>

<p>Together, the papers made routing programmable and tied it to workload and hardware—the two foundations MoM brings under one model contract.</p>

<p>Meanwhile, the runtime was already moving beyond single-model selection. <a href="https://blog.vllm.ai/2026/06/16/vllm-sr-fusion-api.html">Fusion</a>, ReMoM, Confidence, Ratings, and bounded Workflows let one request invoke a controlled collaboration among models. As the <a href="https://blog.vllm.ai/2026/06/29/micro-agent-frontier-models.html">Micro-Agent work</a> showed, a client can call one model name while the serving layer selects a recipe, fans out to workers, verifies or synthesizes their results, and returns one ordinary response.</p>

<table>
  <thead>
    <tr>
      <th>First chapter</th>
      <th>New chapter</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Route a request</td>
      <td>Build a model system</td>
    </tr>
    <tr>
      <td>Choose a model or capability path</td>
      <td>Train, evaluate, and execute the whole MoM</td>
    </tr>
    <tr>
      <td>Configure runtime policy</td>
      <td>Package a portable, versioned model artifact</td>
    </tr>
    <tr>
      <td>Optimize a routing decision</td>
      <td>Optimize system intelligence across quality, cost, latency, safety, and energy</td>
    </tr>
    <tr>
      <td>Hide backend choice behind one API</td>
      <td>Make the complete multi-model system behave like one model</td>
    </tr>
  </tbody>
</table>

<p>Routing remains fundamental. It is how a Mixture-of-Models allocates work, applies policy, and coordinates its parts. But routing is the mechanism. <strong>The model system is the product.</strong></p>

<h2 id="why-the-model-boundary-has-to-move">Why the Model Boundary Has to Move</h2>

<p>Today’s AI stack is fragmented along four axes:</p>

<ul>
  <li>
    <p><strong>Models are fragmented.</strong> Closed frontier models, open general models, domain experts, compact local models, verifiers, and multimodal models will coexist. None wins simultaneously on quality, cost, latency, trust, privacy, and domain fit.</p>
  </li>
  <li>
    <p><strong>Compute is fragmented.</strong> GPUs, CPUs, specialized accelerators, edge devices, cloud capacity, and private clusters differ in memory, kernels, availability, price, and energy use. Model choice and placement are becoming the same decision.</p>
  </li>
  <li>
    <p><strong>Location is fragmented.</strong> Inference spans cloud, data center, and edge. Privacy or residency may rule out a stronger remote model, while a local workload may still need an on-demand cloud expert.</p>
  </li>
  <li>
    <p><strong>Preference is fragmented.</strong> There is no universal “best.” Products and users make different tradeoffs among accuracy, latency, price, privacy, safety, style, and multimodality. Those choices should shape execution directly.</p>
  </li>
</ul>

<p>Today, each application has to reconcile these fragments on its own.</p>

<p align="center">
  <img src="/assets/figures/2026-07-21-vllm-sr-new-chapter/fragmentation-before-mom.png" alt="Before Mixture-of-Models, each application owns separate routing glue across fragmented models, compute, locations, and preferences" width="100%" />
  <br />
  <em>Figure 4: Before MoM, fragmented intelligence becomes application-side routing glue.</em>
</p>

<p>Mixture-of-Models moves that responsibility behind one model boundary.</p>

<p>At that boundary, <strong>intelligent allocation</strong> becomes part of the model. The engine determines which models are eligible, where execution can run, whether models should collaborate, and how to satisfy hard constraints.</p>

<p>Energy makes allocation inseparable from efficiency. Hardware and inference engines improve the supply side by producing more tokens per watt per dollar. The allocation layer controls demand: which work deserves those tokens, and which model or collaboration can provide them within the required quality, latency, and energy budget.</p>

<p>The application selects one versioned model identity and receives one attributable response. Its physical realization can still span open and closed models, cloud and edge, and different accelerator generations. The fragmentation remains, but it becomes internal to the model system instead of leaking into every application.</p>

<p align="center">
  <img src="/assets/figures/2026-07-21-vllm-sr-new-chapter/fragmentation-after-mom.png" alt="With Mixture-of-Models, one model identity contains intelligent allocation across fragmented models, compute, locations, and preferences" width="100%" />
  <br />
  <em>Figure 5: With MoM, the same fragmented resources become the internal realization of one model.</em>
</p>

<h2 id="what-we-mean-by-mixture-of-models">What We Mean by Mixture-of-Models</h2>

<p>A <strong>Mixture-of-Models</strong> is a versioned composite model whose engine realizes each request through a preference-conditioned, resource-bounded path across independent models and operators. It is presented to the user through one model interface and returns one attributable result.</p>

<p>A multi-upstream gateway can forward traffic without owning system quality. An MoM owns an objective, an evaluation contract, a reproducible composition, and the runtime that executes it.</p>

<p>MoM also differs from Mixture-of-Experts. MoE routes tokens among internal experts during one forward pass; MoM coordinates independent models that may differ in architecture, owner, license, modality, protocol, context window, and hardware. An MoE checkpoint can itself be one MoM component.</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th>Conventional model</th>
      <th>Mixture-of-Models</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Unit of intelligence</td>
      <td>One checkpoint</td>
      <td>A governed system of models</td>
    </tr>
    <tr>
      <td>Specialization</td>
      <td>Primarily encoded in weights</td>
      <td>Composed across independent specialists</td>
    </tr>
    <tr>
      <td>Execution</td>
      <td>One generation path</td>
      <td>Selection, cascade, verification, fusion, or workflow</td>
    </tr>
    <tr>
      <td>Optimization target</td>
      <td>One model’s quality and efficiency</td>
      <td>The system frontier across quality, cost, latency, safety, privacy, and energy</td>
    </tr>
    <tr>
      <td>Deployment boundary</td>
      <td>One runtime</td>
      <td>Cloud, data center, and edge</td>
    </tr>
    <tr>
      <td>User contract</td>
      <td>One model identity</td>
      <td>One model identity</td>
    </tr>
  </tbody>
</table>

<p align="center">
  <img src="/assets/figures/2026-07-21-vllm-sr-new-chapter/execution-topologies.png" alt="One model call enters a Mixture-of-Models engine that may select, cascade, fuse, or execute a bounded workflow before returning one response" width="100%" />
  <br />
  <em>Figure 6: Selection is one MoM topology. Cascades, parallel fusion, and bounded workflows share the same model boundary.</em>
</p>

<p>A portable MoM therefore needs more than weights and configuration: it needs a component manifest, capability metadata, routing and collaboration recipes, policies, preferences, evaluation suites, runtime constraints, provenance, and version history.</p>

<p>Open checkpoints can travel with the artifact; closed models remain authenticated external references with explicit capability and policy contracts. Exporting an MoM does not make a proprietary checkpoint portable. It makes the <strong>model system</strong> reproducible.</p>

<h3 id="turn-preferences-into-models">Turn Preferences into Models</h3>

<p>Preferences become concrete when they are published as model identities. One MoM family can offer several operating points:</p>

<table>
  <thead>
    <tr>
      <th>Model identity</th>
      <th>Contract</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">vllm-sr/mom-v1-flash</code></td>
      <td>Minimize expected latency</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">vllm-sr/mom-v1-light</code></td>
      <td>Minimize cost above a quality floor</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">vllm-sr/mom-v1-ultra</code></td>
      <td>Maximize quality within a declared budget</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">vllm-sr/mom-v1-halu</code></td>
      <td>Require grounding checks and fail-closed fallback</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">vllm-sr/mom-v1-secu</code></td>
      <td>Enforce jailbreak and PII policy before execution</td>
    </tr>
  </tbody>
</table>

<p>Each name is a versioned model contract, not a router preset. The application chooses the behavior it needs; vLLM-SR selects and coordinates the models that deliver it while preserving hard privacy, residency, authorization, and safety constraints.</p>

<p>To an application, the full system remains an ordinary model call:</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"model"</span><span class="p">:</span><span class="w"> </span><span class="s2">"vllm-sr/mom-v1-ultra"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"messages"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
    </span><span class="p">{</span><span class="nl">"role"</span><span class="p">:</span><span class="w"> </span><span class="s2">"user"</span><span class="p">,</span><span class="w"> </span><span class="nl">"content"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Review this design and identify its weakest assumption."</span><span class="p">}</span><span class="w">
  </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>That identity may select one model, escalate through a cascade, compare parallel answers, require grounding, or run a bounded workflow—without changing the external interface, version, or response contract.</p>

<p align="center">
  <img src="/assets/figures/2026-07-21-vllm-sr-new-chapter/preference-models.png" alt="One Mixture-of-Models family exposes flash, light, ultra, grounding, and security variants as individually versioned model identities" width="100%" />
  <br />
  <em>Figure 7: Preferences are published as bounded, versioned model contracts—not hidden application-side routing presets.</em>
</p>

<p>Four planes separate ownership:</p>

<table>
  <thead>
    <tr>
      <th>Plane</th>
      <th>What it owns</th>
      <th>Foundation already in vLLM-SR</th>
      <th>Next step</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Artifact</strong></td>
      <td>Components, capabilities, objectives, policy, eval contract, provenance</td>
      <td>Canonical config, model references, DSL, versioned policy</td>
      <td>Portable MoM import/export specification</td>
    </tr>
    <tr>
      <td><strong>Learning</strong></td>
      <td>Router-owned models, preferences, outcomes, recipe improvement</td>
      <td>Training stack, Router Learning, replay, outcome APIs</td>
      <td>Joint training and system-level release gates</td>
    </tr>
    <tr>
      <td><strong>Execution</strong></td>
      <td>Signals, projections, decisions, selectors, loopers, plugins</td>
      <td>Signal–Decision runtime, Fusion, ReMoM, Workflows, safety and memory</td>
      <td>One lifecycle-aware MoM engine</td>
    </tr>
    <tr>
      <td><strong>Physical</strong></td>
      <td>Providers, model pools, accelerators, locality, cache and energy state</td>
      <td>vLLM backends, cloud providers, ROCm, CUDA, OpenVINO, CPU</td>
      <td>Portable placement across cloud, data center, edge, and local devices</td>
    </tr>
  </tbody>
</table>

<p align="center">
  <img src="/assets/figures/2026-07-21-vllm-sr-new-chapter/four-planes.png" alt="Artifact, learning, execution, and physical planes combine to define, improve, realize, and run one Mixture-of-Models identity" width="100%" />
  <br />
  <em>Figure 8: A complete MoM spans four planes: artifact, learning, execution, and physical realization.</em>
</p>

<p>A deployment must map logical requirements onto the models and machines available in its environment. The proposal uses four objects:</p>

<ol>
  <li>The <strong>bundle</strong> fixes the interface, graph, policies, behavior variant, bounds, and immutable semantic assets.</li>
  <li>The <strong>binding</strong> maps logical components to eligible deployments without changing the model’s decision semantics.</li>
  <li>The <strong>resolution lock</strong> freezes the constituent revisions, runtimes, images, accelerators, and provider observations.</li>
  <li>The <strong>run record</strong> attributes every decision, call, constraint check, cost, and outcome to the bundle, binding, and lock that produced it.</li>
</ol>

<p align="center">
  <img src="/assets/figures/2026-07-21-vllm-sr-new-chapter/artifact-resolution-lifecycle.png" alt="One Mixture-of-Models identity moves through bundle, binding, resolution lock, and run record without changing its logical model name" width="100%" />
  <br />
  <em>Figure 9: One stable model identity, from portable contract to attributable run.</em>
</p>

<p>This separation keeps portability honest. The same <code class="language-plaintext highlighter-rouge">mom-v1-ultra</code> can bind to ROCm, CUDA, a private CPU or NPU node, or a hybrid deployment without promising identical outputs from opaque providers. Instead, it preserves control semantics, exposes substitutions, and gives serving and evaluation the same resolved system.</p>

<h2 id="vllm-sr-as-the-mom-engine">vLLM-SR as the MoM Engine</h2>

<p>Training, evaluation, and inference must share one contract; otherwise research, benchmarks, and production drift into different systems.</p>

<h3 id="training-allocation-not-only-weights">Training allocation, not only weights</h3>

<p>MoM training covers router-owned embeddings, signal encoders, preference and safety models, and selectors. It also learns allocation and collaboration: which path fits a workload and budget, when a cascade should stop, how a panel should judge or synthesize, and when an agent session should switch models. Because constituents may be independent or closed, progress does not require gradients through all of them; policies, thresholds, pools, prompts, contracts, and topology can be optimized from traces and outcomes.</p>

<p>The target is a frontier across quality, latency, cost, safety, privacy, reliability, locality, and energy. Replay and outcomes feed production experience back into offline training without letting the hot path silently rewrite policy.</p>

<h3 id="evaluating-the-mom-as-one-model">Evaluating the MoM as one model</h3>

<p>Evaluation must score the model identity end to end; backend benchmarks are inputs, not the result. A versioned scorecard should measure routing regret, collaboration gain, recovery, session continuity, tail latency, cost, safety, privacy, and energy. It should stress provider failures, device loss, model disagreement, workload drift, and preference changes. Each declared operating point also needs its own test: <code class="language-plaintext highlighter-rouge">flash</code> on its latency–quality frontier, <code class="language-plaintext highlighter-rouge">light</code> against its quality floor, and <code class="language-plaintext highlighter-rouge">ultra</code> within its budget.</p>

<p>The scientific test is stricter than asking whether more calls improve a benchmark. Under matched active compute, can a conditional system exploit complementary strengths and failure modes better than the best fixed model? Without that control, MoM can hide brute-force scaling behind a clever graph. Evaluations must report calls, tokens, cost, latency, and energy alongside quality—and publish when composition does not help.</p>

<p align="center">
  <img src="/assets/figures/2026-07-21-vllm-sr-new-chapter/matched-compute-evaluation.png" alt="A best fixed model and a conditional Mixture-of-Models are compared under matched active compute using quality, calls, tokens, cost, latency, and energy" width="100%" />
  <br />
  <em>Figure 10: Composition gain is meaningful only under matched active compute, with quality reported alongside calls, tokens, cost, latency, and energy.</em>
</p>

<h3 id="executing-intelligence-at-inference-time">Executing intelligence at inference time</h3>

<p>At inference time, the engine decides whether one model is enough. It may choose a local specialist, preserve a warm session, escalate through a confidence cascade, require retrieval or verification, run a Fusion panel, or execute a bounded workflow. The runtime owns the budget, topology, fallback, trace, and response contract; the application makes a normal model call.</p>

<p align="center">
  <img src="/assets/figures/2026-07-21-vllm-sr-new-chapter/mom-lifecycle.png" alt="A portable Mixture-of-Models artifact moves through training evaluation inference and outcome feedback while preserving one model identity" width="100%" />
  <br />
  <em>Figure 11: MoM is a closed lifecycle: train the allocation policy, evaluate the full system, execute it, and turn outcomes into the next validated version.</em>
</p>

<h2 id="one-model-that-can-move">One Model That Can Move</h2>

<p>Our target is a complete MoM that can be <strong>built, exported, imported, versioned, evaluated, deployed, and invoked as a unified model</strong>. A logical specification compiles into an immutable bundle, binds to an environment, resolves the concrete deployment, and retains the same identity for serving and evaluation.</p>

<p>The artifact should run across developer machines, private clusters, cloud fleets, and edge environments while its physical realization changes. A specialist may resolve to an admissible local checkpoint or managed endpoint; an accelerator runtime may be replaced. If privacy makes a remote expert unavailable, the engine follows a declared fallback or abstention path. A binding cannot silently rewrite the graph, relax a guard, or turn a panel into a cascade—those changes require a new model version.</p>

<p>“Run on any hardware” is an architectural requirement, not a claim that every component is portable today. The project already supports paths across ROCm, CUDA, OpenVINO, and CPU. Next, hardware capability and placement become part of the MoM contract, allowing the engine to map the model system onto what is available.</p>

<p>The standard for the user experience is simple:</p>

<blockquote>
  <p><strong>One model identity. Many models. Any hardware.</strong></p>
</blockquote>

<p align="center">
  <img src="/assets/figures/2026-07-21-vllm-sr-new-chapter/portable-realizations.png" alt="One vLLM Semantic Router Mixture-of-Models identity is packaged as a bundle and bound to developer, data-center, cloud, and edge environments" width="100%" />
  <br />
  <em>Figure 12: One logical model identity can be realized across developer, data-center, cloud, and edge hardware.</em>
</p>

<p>If the application needs to know which provider owns every submodel, which device runs it, or which fallback graph to execute, the abstraction has leaked.</p>

<h2 id="what-changes-now">What Changes Now</h2>

<p>The next stage focuses on four connected areas:</p>

<ol>
  <li><strong>Define a portable MoM specification.</strong> Package components, objectives, policy, preferences, evaluation, constraints, and execution semantics as one versioned artifact.</li>
  <li><strong>Close the training–evaluation–inference loop.</strong> Improve models and recipes from evaluation and replay, then ship them through reviewable, rollback-safe releases.</li>
  <li><strong>Build a heterogeneous runtime.</strong> Map one MoM across cloud, data center, and edge using hardware, locality, energy, and data boundaries as inputs.</li>
  <li><strong>Keep the model interface boring.</strong> Make an MoM as easy to import, deploy, and invoke as a single model.</li>
</ol>

<p align="center">
  <img src="/assets/figures/2026-07-21-vllm-sr-new-chapter/next-stage-roadmap.png" alt="The next vLLM Semantic Router chapter connects a portable specification, a closed training evaluation inference loop, a heterogeneous runtime, and one model API" width="100%" />
  <br />
  <em>Figure 13: Four connected workstreams turn Mixture-of-Models from an execution pattern into the next model architecture.</em>
</p>

<p>This is a research program for how independent models should specialize, compete, verify, and collaborate; how to measure the resulting system; and how one model contract can survive across devices and environments. Our mission is:</p>

<blockquote>
  <p><strong>Advancing the science of intelligence across models, devices, and environments.</strong></p>
</blockquote>

<p>We will study when composition produces capabilities beyond a single checkpoint, treat placement and energy as part of intelligence, and carry the same model contract from edge to cloud and from research to production.</p>

<h2 id="build-it-with-us">Build It With Us</h2>

<p>Building Mixture-of-Models requires more than routing. The work spans model training, evaluation, serving systems, hardware, and production operations.</p>

<p>Iris, Athena, and Themis improved because contributors brought real workloads, added backends, trained models, published benchmarks, found failure cases, and argued for better interfaces. MoM needs the same range of work: learned allocation, preference optimization, model cooperation, energy-aware inference, portable artifacts, open evaluation, and heterogeneous runtimes.</p>

<p>If you work on these problems, we want to learn from your workloads and measurements. Build an operating point, add a runtime, test a collaboration recipe, or publish a case where composition fails. MoM will be stronger if its assumptions are tested in the open.</p>

<h3 id="acknowledgments">Acknowledgments</h3>

<p>vLLM-SR has grown through work across engineering, research, and the wider ecosystem. We thank <a href="https://www.linkedin.com/in/bitliu">Xunzhuo Liu</a>, <a href="https://www.linkedin.com/in/huaminchen">Huamin Chen</a>, <a href="https://www.linkedin.com/in/bowei-he-8a9450199/">Bowei He</a>, <a href="https://www.linkedin.com/in/yankai-chen-923001154/">Yankai Chen</a>, <a href="https://www.linkedin.com/in/fuyuan-lyu-560756167/">Fuyuan Lyu</a>, and <a href="https://ca.linkedin.com/in/xueliu">Steve Liu</a> for helping shape its technical and research direction. We also thank <a href="https://www.linkedin.com/in/andyluo77/">Andy Luo</a> and <a href="https://www.linkedin.com/in/haichen-zhang-9010b6382/">Haichen Zhang</a> for their work on ROCm enablement, router-model training, and open MoM experimentation.</p>

<p>The work has also been carried by <a href="https://github.com/FAUST-BENCHOU">FAUST</a>, <a href="https://www.linkedin.com/in/shraderdm/">David Shrader</a>, <a href="https://github.com/drivebyer">Yang Wu</a>, <a href="https://github.com/ramkrishs">Ramakrishnan Sathyavageeswaran</a>, <a href="https://github.com/WUKUNTAI-0211">Kuntai Wu</a>, <a href="https://github.com/AayushSaini101">Aayush Saini</a>, <a href="https://github.com/siloteemu">siloteemu</a>, <a href="https://www.linkedin.com/in/chenw615/">Chen Wang</a>, <a href="https://www.linkedin.com/in/yue-zhu-b26526a3/">Yue Zhu</a>, <a href="https://www.linkedin.com/in/senan-zedan-2041855b/">Senan Zedan</a>, <a href="https://www.linkedin.com/in/yossi-ovadia-336b314/">Yossi Ovadia</a>, <a href="https://www.linkedin.com/in/samzong">Samzong Lu</a>, <a href="https://www.linkedin.com/in/liav-weiss-2a0428208">Liav Weiss</a>, <a href="https://www.linkedin.com/in/asaad-balum-0928771a9/">Asaad Balum</a>, <a href="https://www.linkedin.com/in/yehuditkerido/">Yehudit</a>, <a href="https://www.linkedin.com/in/noalimoy/">Noa Limoy</a>, <a href="https://github.com/mkoushni">Marina Koushnir</a>, <a href="https://github.com/JaredforReal">Jared Wen</a>, <a href="https://www.linkedin.com/in/abdallah-samara">Abdallah Samara</a>, <a href="https://www.linkedin.com/in/henschwartz">Hen Schwartz</a>, <a href="https://www.linkedin.com/in/sriniabhiram">Srinivas A</a>, <a href="https://github.com/carlory">Yang Zhu</a>, <a href="https://www.linkedin.com/in/jintao-zhang-402645193/">Jintao Zhang</a>, <a href="https://github.com/yuluo-yx">yuluo-yx</a>, <a href="https://github.com/cryo-zd">cryo</a>, <a href="https://github.com/OneZero-Y">Bishen Yu</a>, <a href="https://github.com/aeft">Zhijie Wang</a>, <a href="https://github.com/haowu1234">Hao Wu</a>, and <a href="https://www.linkedin.com/in/qiping-pan-8662ab215/">Qiping Pan</a>. Their code, reviews, testing, documentation, and stewardship carried the project from one release to the next.</p>

<p>At this milestone, the project stands at <strong>1,734 commits</strong> and <strong>150+ contributors</strong>. We thank collaborators at MBZUAI, McGill University, Mila, and Rice University, and the broader vLLM, AMD, Intel, Meta, Red Hat, Microsoft, Google, IBM, NVIDIA, Hugging Face, NASA, Nutanix, DaoCloud, and open-source communities. This milestone belongs to everyone who helped turn an early router into a real system.</p>

<p align="center">
  <img src="/assets/figures/2026-07-21-vllm-sr-new-chapter/community.png" alt="Model researchers, evaluation researchers, systems engineers, hardware teams, model builders, and operators collaborate to build the Mixture-of-Models engine" width="100%" />
  <br />
  <em>Figure 14: Building the MoM engine is an open systems problem that needs the full model and infrastructure community.</em>
</p>

<p>Join us on <a href="https://github.com/vllm-project/semantic-router">GitHub</a>, explore the <a href="https://vllm-sr.ai">documentation</a>, try the <a href="https://huggingface.co/LLM-Semantic-Router">MoM model family</a>, and meet the community in the <code class="language-plaintext highlighter-rouge">#semantic-router</code> channel on <a href="https://vllm-dev.slack.com/archives/C09CTGF8KCN">vLLM Slack</a>.</p>

<p>vLLM Semantic Router began by helping infrastructure choose the right model for each request.</p>

<p>Now we are extending that foundation beyond a single model: toward systems that can coordinate, evaluate, and operate multiple models across devices and environments.</p>

<p>We invite the community to help build and test that approach in the open.</p>]]></content><author><name>vLLM Semantic Router Team</name></author><category term="ecosystem" /><category term="mixture-of-models" /><category term="semantic-router" /><summary type="html"><![CDATA[Most AI applications are built around a single model endpoint. But as models, devices, and deployment constraints diversify, no single model is the best fit for every request or environment. The practical question is how multiple specialized models can be coordinated, evaluated, and served through one interface. We call this systems approach Mixture-of-Models.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vllm-project.github.io/assets/figures/2026-07-21-vllm-sr-new-chapter/banner.png" /><media:content medium="image" url="https://vllm-project.github.io/assets/figures/2026-07-21-vllm-sr-new-chapter/banner.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Keeping vLLM Production Quality: A Look Inside CI, Benchmarking, and the Release Process</title><link href="https://vllm-project.github.io/2026/07/16/keeping-vllm-production-quality.html" rel="alternate" type="text/html" title="Keeping vLLM Production Quality: A Look Inside CI, Benchmarking, and the Release Process" /><published>2026-07-16T00:00:00+00:00</published><updated>2026-07-16T00:00:00+00:00</updated><id>https://vllm-project.github.io/2026/07/16/keeping-vllm-production-quality</id><content type="html" xml:base="https://vllm-project.github.io/2026/07/16/keeping-vllm-production-quality.html"><![CDATA[<p><img src="/assets/figures/2026-07-16-keeping-vllm-production-quality/00-production-quality-hero-airport.png" alt="vLLM pull requests passing through CI, performance and accuracy evaluation, and release gates" /></p>

<h2 id="intro">Intro</h2>

<p>vLLM is the most widely used open-source LLM inference engine. 86K+ GitHub stars. 5.6M+ monthly pip installs. 2.5M+ monthly image pulls, with support for 1000+ model architectures and 600+ accelerator types.</p>

<p>Supporting this many models and accelerators has always been one of vLLM’s biggest strengths. It’s also what makes it so hard to keep stable.</p>

<p>In June 2026, vLLM merged 1,918 commits into main — 64 a day on average, on par with other big OSS projects like PyTorch or Kubernetes. During this time, our CI ran 13 million job minutes with 1400 concurrent runners at peak.</p>

<p>Testing vLLM, especially at this pace, gets harder every day. A change that’s clean on an H100 might fail to compile on AMD, lose throughput on B200, or nudge a model’s outputs just enough on one backend to matter. The surface area that makes vLLM worth using is the exact surface area we have to defend on every commit.</p>

<p>In this post, I want to share how we keep vLLM releases stable at this pace: what works, what we’ve learned, and where we still fall short. It will mostly cover high-level processes rather than technical details; I’ll save those for another post.</p>

<p>A journey from pull requests to a new version release on vLLM has to go through three layers:</p>

<ul>
  <li>
    <p><strong>CI</strong> — how we catch what breaks loudly, on every PR</p>
  </li>
  <li>
    <p><strong>Performance benchmarking &amp; accuracy evaluation</strong> — how we catch what breaks silently, beyond what CI can cover</p>
  </li>
  <li>
    <p><strong>Release process</strong> — how we evaluate the signals, make the call, then build and ship artifacts to users safely.</p>
  </li>
</ul>

<h2 id="layer-1-ci">Layer 1: CI</h2>

<h3 id="extensive-unit-testing-on-every-component-of-the-codebase">Extensive unit testing on every component of the codebase</h3>

<p>Every PR starts with lightweight GitHub Actions checks—linting, formatting, and similar guardrails. Once a committer thinks the PR is ready to merge, the heavier unit testing then starts running on Buildkite, our CI platform.</p>

<p><img src="/assets/figures/2026-07-16-keeping-vllm-production-quality/01-ci-pipeline-and-selected-jobs.png" alt="vLLM CI flow from GitHub Actions checks to dynamically selected Buildkite jobs" /></p>

<p>Buildkite assembles each PR’s testing pipeline dynamically: a bootstrap step reads the job definitions, inspects the diff, and schedules only the relevant groups. Change only documentation and you may get a handful of jobs. Touch a few important kernels? Buckle up for 100+ jobs launching in parallel.</p>

<p>In total, the vLLM CI suite runs 37 test groups and 266 jobs, covering every major component and feature—from different kernels to speculative decoding to LoRA. Groups range from a couple of jobs to a few dozen, and many tests exercise several components at once. Here is a subset:</p>

<p><img src="/assets/figures/2026-07-16-keeping-vllm-production-quality/02-ci-test-groups-266-jobs.png" alt="vLLM CI test groups and example jobs" /></p>

<h3 id="ensuring-test-environment-is-consistent">Ensuring test environment is consistent</h3>

<p><strong>A test only means something if it runs the same way every time.</strong> We usually see two kinds of drift get in the way: the environment can differ across our CI runners, and dependencies can change under us over time. A shared container image removes the first; a pinned dependency graph removes the second.</p>

<p><strong>Same container image, every machine.</strong> With 266 jobs fanning out across dozens of machine types, the fastest way to a flaky, untrustworthy result is to let each job set up its own slightly different environment. To avoid this, the majority of our jobs run inside the same container image, built once at the start of a run and reused everywhere. Our Dockerfile builds in stages, each adding to the one below it.</p>

<p><img src="/assets/figures/2026-07-16-keeping-vllm-production-quality/03-container-build-stages.png" alt="Shared container build stages for vLLM CI and releases" /></p>

<p>A <code class="language-plaintext highlighter-rouge">base</code> stage provides the CUDA toolchain; a <code class="language-plaintext highlighter-rouge">build</code> stage compiles the wheels on top of it; and a <code class="language-plaintext highlighter-rouge">runtime</code> stage installs those wheels with their runtime dependencies.</p>

<p>From there, the build forks: one image adds the serving entrypoint and becomes the release image, while a separate <code class="language-plaintext highlighter-rouge">test</code> image adds the test dependencies and becomes the image CI jobs pull. That shared ancestry keeps what we test close to what we ship.</p>

<p>For jobs using that shared image, a kernel test on a B200 and an entrypoints test on an L4 pull the same container image, byte for byte, while running on different hardware. Building it once removes a major source of variation: failures are much less likely to come from per-job setup drift.</p>

<p><strong>Same versions, every run</strong>. Dependencies drift over time—and that’s the half we learned the hard way.</p>

<p>An unpinned dependency makes failures tricky to chase down: the same test passes on Monday and crashes on Wednesday. You read every code change in between; none of it looks related. Hours later, it clicks—FlashInfer shipped a new version on Wednesday, and the build quietly picked it up. And FlashInfer was never alone: nixl, transformers, and their transitive dependencies bit us the same way—each unannounced upgrade a fresh chance to break CI, with the cause buried a dependency layer down.</p>

<p>So vLLM CI locks its dependencies. We run the top-level dependencies through <code class="language-plaintext highlighter-rouge">pip-compile</code> to generate lock files that pin every package, including transitive dependencies.</p>

<p><img src="/assets/figures/2026-07-16-keeping-vllm-production-quality/04-pip-compiled-dependency-graph.png" alt="Top-level dependencies compiled into a fully pinned package graph" /></p>

<p>We update the locks periodically and run the full CI suite each time. Since we started pinning the full graph, dependency-caused breakages are no longer a recurring headache.</p>

<h3 id="scaling-ci-compute-across-a-heterogeneous-multi-provider-fleet">Scaling CI compute across a heterogeneous, multi-provider fleet</h3>

<p>Each job gets pushed to a runner queue on Buildkite — a pool of machines with a particular hardware profile. For example, the <code class="language-plaintext highlighter-rouge">gpu_1</code> queue is backed by individual VMs with L4 GPU; the <code class="language-plaintext highlighter-rouge">b200</code> queue is backed by a Kubernetes cluster with B200s inside. When a runner becomes available, it claims the next job in the queue, runs it, and reports the result back to Buildkite.</p>

<p>vLLM CI, at the time of writing, has 58 runner queues spanning a wide range of accelerators, and that hardware is provided by multiple partner organizations.</p>

<p><img src="/assets/figures/2026-07-16-keeping-vllm-production-quality/05-accelerator-runner-fleet.png" alt="vLLM CI runner queues across accelerator vendors and hardware types" /></p>

<p>There’s a limit on how much we can spend on compute. Even if we can afford it, managing all of these machines ourselves is a pretty tough job. CI coverage with this much diversity is only possible because of the generous support and collaboration from many of our amazing partners.</p>

<p>However, integration is a big challenge. Every partner has different requirements: some simply hand us access to everything, some prefer to manage their own hardware, some have very tight security guardrails.</p>

<p><strong>So how do we manage to plug them all into one CI pipeline?</strong></p>

<p>This is where <strong>Buildkite agent</strong> comes in. It runs inside the provider’s environment and connects outbound to Buildkite over HTTPS to receive work. Because Buildkite does not need to initiate connections to the agent, providers do not have to expose inbound ports, configure a VPN, or give us access to their network.</p>

<p>When the agent accepts a job, it runs the command, streams the logs back, and reports the final exit status. A persistent agent then waits for more work, while an ephemeral agent exits after completing its job.</p>

<p>There’s more than one way to run that agent, and providers pick whatever fits their setup.</p>

<p>The simplest is a standalone machine — our 8xA100 machine or Arm server. The provider installs the agent, points it at a runner queue, and it runs that loop forever.</p>

<p><img src="/assets/figures/2026-07-16-keeping-vllm-production-quality/06-standalone-buildkite-agent-flow.png" alt="A standalone Buildkite agent polling a runner queue and reporting results" /></p>

<p>For machines in a Kubernetes cluster, the same model works through the <a href="https://github.com/buildkite/agent-stack-k8s">Buildkite Agent Stack for Kubernetes</a>. The controller turns each matching job into a Kubernetes Job with a single Pod, which runs the test and reports back. We always recommend this way because it’s very scalable: you don’t need to install Buildkite agents on every single node, just add them into the cluster.</p>

<p><img src="/assets/figures/2026-07-16-keeping-vllm-production-quality/07-kubernetes-buildkite-agent-flow.png" alt="Buildkite Agent Stack for Kubernetes creating one pod per CI job" /></p>

<p>Either way, onboarding is simple on our end: we create a queue, provide a token, and the provider starts the agent themselves. We don’t need access to the machine. That’s what lets vLLM test on more hardware than we could ever afford to own—<strong>a donated fleet worth millions of dollars a year.</strong></p>

<h3 id="utilizing-hardware-is-challenging">Utilizing hardware is challenging</h3>

<p>There’s a lot of demand and a hard limit on compute, so we need to make sure none of it goes to waste.</p>

<p><strong>MIG-slice the big GPUs</strong></p>

<p><img src="/assets/figures/2026-07-16-keeping-vllm-production-quality/08-h200-mig-slices.png" alt="Eight H200 GPUs partitioned into 56 Multi-Instance GPU slices" /></p>

<p>Most CI jobs run with smaller models and need far less than a whole GPU. NVIDIA’s Multi-Instance GPU (MIG) lets us carve one card into several isolated slices — an H200 becomes seven 18 GB partitions — meaning 7 jobs can share one GPU at a time. We did some math here and realized that in many cases, slicing a big GPU is a lot cheaper than renting smaller GPUs for the same amount of workload!</p>

<p><strong>Autoscale from zero, one job per machine</strong></p>

<p>For the machines we rent by the hour, leaving them on and idle just wastes money. So each of those queues scales itself: when jobs are waiting, it starts more machines; when there’s nothing to run, it goes down to zero. Each machine picks up one job, runs it in a container, and shuts down. As a bonus, it also keeps tests clean: every job gets a fresh machine, so nothing left over from a past run can mess with it.</p>

<p><strong>Don’t rebuild what you can reuse</strong></p>

<p>The slowest, most repetitive, and most expensive parts of CI are:</p>

<ol>
  <li>
    <p>Building the standard Docker image used by whole CI pipeline</p>

    <ol>
      <li>
        <p>Compiling CUDA kernels</p>
      </li>
      <li>
        <p>Installing all dependencies</p>
      </li>
    </ol>
  </li>
  <li>
    <p>Downloading model weights from Hugging Face.</p>
  </li>
</ol>

<p>so we try not to:</p>

<ul>
  <li>
    <p><strong>Docker layers</strong>: we apply registry caching and reuse cached layers instead of rebuilding. This would include the dependencies.</p>
  </li>
  <li>
    <p><strong>Warm-cache AMI for builder</strong>: we have a nightly job to build the AMI used for our builder machines with the latest layers already pulled, so our builder machine starts as close to main as possible.</p>
  </li>
  <li>
    <p><strong>Compiler cache</strong>: we leverage <strong>sccache</strong> so that compiled C++/CUDA outputs are cached in an S3 bucket and reused across builds. Every builder machine can read from this bucket, but only builder machines used for the main branch can write to it.</p>
  </li>
  <li>
    <p><strong>Model weights</strong>: the models we test are huge, so for each of the clusters, we download them once to shared storage and every job reads from there, instead of pulling gigabytes each time.</p>
  </li>
</ul>

<h3 id="making-ci-health-visible">Making CI health visible</h3>

<p>With hundreds of CI runs a day, each running hundreds of jobs across different hardware, we also need to know whether the system itself is healthy.</p>

<p>A queue quietly backs up to hours of wait time. A test starts to flake one run in twenty. The job runs 10 minutes slower than last month. It’s not easy to track that.</p>

<p>We took inspiration from the incredible PyTorch CI HUD (<a href="https://hud.pytorch.org/">hud.pytorch.org</a>), built by our good friends at PyTorch, and created one of our own at <a href="https://ci.vllm.ai">ci.vllm.ai</a>.</p>

<p>Every 15 minutes, data from our Buildkite pipelines is ingested into Databricks and ClickHouse.</p>

<p>With all the available data and full control of the dashboard, we have so much flexibility on building out our observability stack. It gives us an easier time answering these typical questions:</p>

<!-- dashboard-carousel:start -->

<p><strong><em>Is main branch healthy right now?</em></strong></p>

<p><img src="/assets/figures/2026-07-16-keeping-vllm-production-quality/08-main-branch-health.png" alt="The CI dashboard showing main-branch health" /></p>

<p>For the past 3 days, no. And why did jobs take 10 hours!?</p>

<p><strong><em>Which test is broken or flaky, and since when?</em></strong></p>

<p><img src="/assets/figures/2026-07-16-keeping-vllm-production-quality/09-test-failure-history.png" alt="The CI dashboard showing test failures over time" /></p>

<p>This AMD hardware test group has been failing since PR #47329 was merged.</p>

<p>Basic correctness test failed once so it’s probably flaky.</p>

<p><strong><em>Is any runner queue congested?</em></strong></p>

<p><img src="/assets/figures/2026-07-16-keeping-vllm-production-quality/10-runner-queue-congestion.png" alt="The CI dashboard showing runner-queue congestion" /></p>

<p><code class="language-plaintext highlighter-rouge">small_cpu_queue_premerge</code> runner queue looks pretty congested… Its capacity probably maxed out at 5 instances, so let’s raise it.</p>

<p><strong><em>Which job takes the longest in CI? What’s its duration trend over the past two weeks?</em></strong></p>

<p><img src="/assets/figures/2026-07-16-keeping-vllm-production-quality/11-job-duration-trend.png" alt="The CI dashboard showing job-duration trends" /></p>

<!-- dashboard-carousel:end -->

<p>Those are just a few examples of what our dashboard can do. Modern coding agents have made this kind of tooling surprisingly approachable, even without deep front-end expertise.</p>

<h3 id="automating-failure-detection-and-response">Automating failure detection and response</h3>

<p>The dashboard helps us see problems. The next step is shortening the time from detection to diagnosis, and of course we have to leverage the powerful AI agents here.</p>

<p>Every night, a CI-analyzer bot runs the full suite and compares the results with the previous night’s run. If something newly failed, it reads the error logs, classifies the failure, and walks the intervening commits to find the culprit. It then posts a report to Slack with an auto-revert PR ready for maintainers to review and merge. That’s about 1.5 auto-revert PRs a day, with the right failure and culprit commit identified around 70% of the time—so the on-call reviewer usually starts from a correct diagnosis instead of a blank page.</p>

<p>The bot has become essential to catching breakages fast, alongside the community effort to fix issues as they land—shout-out to everyone that helps, especially the on-call rotation at Red Hat!</p>

<p><img src="/assets/figures/2026-07-16-keeping-vllm-production-quality/12-ci-analyzer-bot.png" alt="The CI analyzer bot reporting a regression and suggested revert" /></p>

<h3 id="what-a-green-check-cannot-tell-us">What a green check cannot tell us</h3>

<p>Put together, all of this is what lets us trust a green check on a PR: broad unit test coverage, run across a huge fleet of different accelerators, in consistent environments, and well-monitored. When CI passes, we’re confident merge risk is significantly reduced.</p>

<p>But CI doesn’t tell the whole story. A change can pass every test and still make a model slower or its output incorrect. To keep CI fast and affordable, we tend to skip a lot of e2e tests and, more importantly, not closely simulate what vLLM users go through every day. That’s what the next layer is for.</p>

<h2 id="layer-2-performance-benchmarking--accuracy-evaluation">Layer 2: Performance benchmarking &amp; accuracy evaluation</h2>

<p>In May, we shipped <code class="language-plaintext highlighter-rouge">v0.20.0</code> and within days had to cut two emergency patches, <code class="language-plaintext highlighter-rouge">v0.20.1</code> and <code class="language-plaintext highlighter-rouge">v0.20.2</code>. Two problems had slipped through: one broke <code class="language-plaintext highlighter-rouge">gpt-oss</code> on Blackwell when split across multiple GPUs (tensor parallelism &gt; 1), the other tanked <code class="language-plaintext highlighter-rouge">DeepSeek V4</code> throughput on GB200.</p>

<p>At the time we had no benchmarking pipeline; nothing ran these models end to end on that hardware to confirm they still worked and ran fast before we shipped. So both problems sailed past CI and reached users.</p>

<p>Performance regressions rarely crash. The server starts and requests succeed; users simply get fewer tokens per second or wait longer for the first token. Accuracy regressions are quiet: the model returns a valid response, but the answer is wrong.</p>

<p>We realized how important it is to run models end to end, with performance benchmarks and accuracy checks, so we invested a lot of time building this layer.</p>

<p>It now provides a lot of signals for our release process and has already caught several major regressions. We built the system that would’ve caught the problems on v0.20.0 before it was shipped.</p>

<h3 id="running-a-matrix-of-models-and-accelerators-every-night">Running a matrix of models and accelerators every night</h3>

<p>We maintain our pipeline at <a href="https://github.com/vllm-project/perf-eval">https://github.com/vllm-project/perf-eval</a>. Each config file describes a workload: how to start vLLM server, which arguments to use, which model to serve, which accelerator, and which tasks to run.</p>

<p>Each workload generally runs three tasks:</p>

<ul>
  <li>
    <p>Performance benchmark — measuring time-to-first-token (TTFT), time-per-output-token (TPOT), and many other metrics — using <code class="language-plaintext highlighter-rouge">vllm-bench</code></p>
  </li>
  <li>
    <p>Model accuracy on math and reasoning benchmarks (GSM8K, GPQA, AIME) using <code class="language-plaintext highlighter-rouge">lm-eval</code></p>
  </li>
  <li>
    <p>Function-calling accuracy via the Berkeley Function-Calling Leaderboard (BFCL)</p>
  </li>
</ul>

<p><img src="/assets/figures/2026-07-16-keeping-vllm-production-quality/14-nightly-perf-eval-workload.png" alt="A nightly vLLM workload running performance, accuracy, and function-calling evaluations" /></p>

<p>Every night, and for every release candidate, we run the full suite across selected models — DeepSeek V4 Pro/Flash, gpt-oss, Kimi K2.5, MiniMax M2.5 and M3, Qwen3.5, GLM 5.1, Gemma 4, and Nemotron 3 Super — on H200, B200, MI300X, and MI355X. That’s 17 model-hardware recipes in total right now, and the list keeps growing. We plan to add support for GB200/GB300, PD disaggregation, and more models very soon.</p>

<h3 id="is-it-always-fast">Is it always fast?</h3>

<p>After every run, the results are ingested into our database. Remember the CI dashboard from earlier? It has perf results too!</p>

<p>We turn the nightly numbers into charts that make regressions easy to spot over time.</p>

<p>For example, this is a view of our <a href="https://ci.vllm.ai/perf">Performance dashboard</a>:</p>

<p><img src="/assets/figures/2026-07-16-keeping-vllm-production-quality/14-performance-trends.svg" alt="Performance history for gpt-oss 120B on H200" /></p>

<p><em>Performance history for gpt-oss 120B on H200 with tensor parallelism 8, split by concurrency.</em></p>

<p>The <a href="https://ci.vllm.ai/compare">Compare view</a> lets us compare two vLLM images head-to-head — say, a release candidate against the last release.</p>

<p><img src="/assets/figures/2026-07-16-keeping-vllm-production-quality/15-compare-view.png" alt="Comparing two vLLM images in the performance dashboard" /></p>

<h3 id="is-it-always-correct">Is it always correct?</h3>

<p>If your vLLM instance is blazing fast but its output is garbage, that speed is worthless. Beyond performance, we make sure the model’s answers still hold up.</p>

<p>The <a href="https://ci.vllm.ai/eval">Evaluation dashboard</a> stores aggregate scores and error bars, then lets us open a run and inspect the underlying question, reference answer, raw response, extracted answer, and correctness result. That sample-level evidence is far more useful than debugging from a single aggregate number.</p>

<p><img src="/assets/figures/2026-07-16-keeping-vllm-production-quality/16-accuracy-sample-debugging.svg" alt="Inspecting an incorrect evaluation sample" /></p>

<p><em>An incorrect GSM8K sample exposes the exact question, expected answer, model response, and extraction result.</em></p>

<h2 id="layer-3-release-process">Layer 3: Release process</h2>

<h3 id="shipping-on-a-fast-cadence">Shipping on a fast cadence</h3>

<p>Since November 2025, we have been maintaining a two-week cadence on vLLM releases. Many projects of our size take a lot longer to ship. Why we keep this cadence:</p>

<ul>
  <li>
    <p><strong>Changes reach users fast.</strong> A new release is never far behind main.</p>
  </li>
  <li>
    <p><strong>It’s predictable.</strong> Users and downstream projects can plan around a steady schedule instead of guessing when the next release lands.</p>
  </li>
  <li>
    <p><strong>Managing features and tracing regressions are easier</strong> when there are 500 commits to bisect rather than a few thousand.</p>
  </li>
  <li>
    <p><strong>Less deadline pressure</strong>. Contributors no longer feel the need to rush their changes in before the train departs. They just catch the next one in two weeks.</p>
  </li>
  <li>
    <p><strong>Cherry-picks stay clean.</strong> A fix from just days ago is usually a simple pick, not a merge-conflict mess.</p>
  </li>
</ul>

<p>Every other Monday, we kick off release week. Here’s what that looks like:</p>

<p><img src="/assets/figures/2026-07-16-keeping-vllm-production-quality/18-release-candidate-loop.png" alt="The vLLM release candidate testing and publishing loop" /></p>

<h3 id="start-from-the-safest-commit">Start from the safest commit</h3>

<p>On Monday, the release manager reviews the most recent full-CI runs on the <code class="language-plaintext highlighter-rouge">main</code> branch and chooses the greenest commit. That gives the release branch the healthiest available starting point before any release-specific changes are added.</p>

<p>We cut <code class="language-plaintext highlighter-rouge">releases/vX.Y.Z</code> at that exact commit and announce the branch and release window.</p>

<h3 id="heavy-testing-on-every-release-candidate">Heavy testing on every release candidate</h3>

<p>From the branch cut through Wednesday, we review the cherry-pick requests, cherry-pick them into the release branch in batches, and tag the result as the next release candidate.</p>

<p>Every candidate goes through the same three gates:</p>

<ul>
  <li>
    <p>Full CI suite</p>
  </li>
  <li>
    <p>Performance benchmark suite</p>
  </li>
  <li>
    <p>Model accuracy evaluation suite</p>
  </li>
</ul>

<p>Each result is tied to a release candidate. When a later candidate changes CI health, performance, or evaluation quality, we can track down which candidate introduced the difference: they are just tens of commits away.</p>

<p>We end the cherry-pick window on Wednesday. Then, only fixes for existing issues on release candidates can be cherry-picked, followed by a new release-candidate tag and another run through the three gates, until one candidate meets the bar.</p>

<h3 id="no-compromise-for-the-bar">No compromise for the bar</h3>

<p>A candidate qualifies only when all three gates pass.</p>

<p>Sometimes there’s no qualifying candidate at the end of the week, and that’s okay. We try our best to release on time, but never compromise our bar just to make it. We treat our new version the way Rockstar treats GTA 6: it’s done when it’s done. We don’t take a decade though…</p>

<h3 id="ship-for-every-platform">Ship for every platform</h3>

<p>Once a candidate qualifies, we take that commit and start building all the artifacts for different hardware platforms and CUDA versions, ensuring that everyone out there can use vLLM natively. And before anything ships, we smoke test the built artifacts themselves.</p>

<p>At the time of writing, we are shipping these for every release:</p>

<ul>
  <li>
    <p><strong>7 Python wheels</strong>:</p>

    <ul>
      <li>
        <p>CUDA 12.9 x86_64/arm64</p>
      </li>
      <li>
        <p>CUDA 13.0 x86_64/arm64</p>
      </li>
      <li>
        <p>CPU x86_64/arm64</p>
      </li>
      <li>
        <p>ROCm</p>
      </li>
    </ul>
  </li>
  <li>
    <p><strong>11 Docker images</strong>:</p>

    <ul>
      <li>
        <p>CUDA 12.9, x86_64/arm64, Ubuntu 22.04/24.04</p>
      </li>
      <li>
        <p>CUDA 13.0, x86_64/arm64, Ubuntu 22.04/24.04</p>
      </li>
      <li>
        <p>ROCm</p>
      </li>
      <li>
        <p>CPU x86_64/arm64</p>
      </li>
    </ul>
  </li>
</ul>

<h2 id="whats-next">What’s next</h2>

<p>I’ve been bragging a lot about what we’ve built — but honestly, we still have a lot to do on our roadmap. Some of the big ones:</p>

<ul>
  <li>
    <p><strong>Automatic test selection.</strong> Today we pick which tests run for each PR from a hand-maintained mapping, and it goes stale fast. We want this to be automatic, and we’re trying a few angles: LLM-based selection, static analysis, dynamic analysis, and labeling source paths to match them to tests.</p>
  </li>
  <li>
    <p><strong>Faster time-to-signal.</strong> CI takes 1–2 hours on average to return a verdict; we’d love to get that under 30 minutes.</p>
  </li>
  <li>
    <p><strong>Leaner unit tests.</strong> A lot of our “unit” tests actually spin up a full vLLM server and fire real requests at it, which slows down CI a lot.</p>
  </li>
  <li>
    <p><strong>Better exit-code handling.</strong> Some jobs still return the wrong exit code when they fail, like reporting an infra problem as a failed test, making it hard to triage failures and alert/retry jobs.</p>
  </li>
  <li>
    <p><strong>Faster flaky-test detection and quarantine.</strong> We have plenty of flaky tests — from infra, upstream packages, or tests that just aren’t written safely — and we’d like to catch and quarantine them automatically.</p>
  </li>
  <li>
    <p><strong>Automatic detection for infra issues.</strong> Spot a bad machine quickly and pull it out of the CI fleet on its own, before it fails a pile of jobs.</p>
  </li>
  <li>
    <p><strong>Better alerting.</strong> We have some basic alerts for congested runner queues and regressions. It’s always nice to have more: high disk pressure on CI runners, jobs suddenly failing far faster than usual, broken dependency installs, etc.</p>
  </li>
  <li>
    <p><strong>Code-coverage reporting.</strong> Our coverage is broad, but we can’t yet say for sure that every corner of the codebase is actually exercised.</p>
  </li>
</ul>

<p>Working on CI is actually a lot more interesting than most people think. This post only covers the high-level process of how we keep vLLM releases stable; there are plenty of fun technical details I didn’t get to cover — maybe in another post :)</p>

<p>If any of these problems sound like your kind of fun, or you think we’re doing something wrong, come say hi in <code class="language-plaintext highlighter-rouge">#sig-ci</code> on the vLLM Slack. And if you’d like to work on this full-time, <a href="https://jobs.ashbyhq.com/Inferact/3dee433c-7121-458c-8408-c193b6326ffb">we’re hiring</a> at Inferact~!</p>

<h2 id="acknowledgements">Acknowledgements</h2>

<p>None of this is a solo effort. vLLM CI is built and kept alive by the whole community.</p>

<p>I’m deeply grateful to everyone who helped with CI along the way (listed alphabetically):</p>

<ul>
  <li>
    <p><strong>Amazon</strong>: Junpu Fan, Liangfu Chen, Omri Shiv,</p>
  </li>
  <li>
    <p><strong>AMD</strong>: Alexei Ivanov, Andreas Karatzas, Kenny Roche, Micah Williamson</p>
  </li>
  <li>
    <p><strong>Arm</strong>: Fadi Arafeh, Ioana Ghiban</p>
  </li>
  <li>
    <p><strong>EmbeddedLLM</strong>: Tun Jian Tan</p>
  </li>
  <li>
    <p><strong>Google</strong>: Brittany Rockwell, Jincheng Chen, Ming Huang, Qiliang Cui, Yarong Mu, Yiwei Wang</p>
  </li>
  <li>
    <p><strong>HuggingFace</strong>: Harry Mellor</p>
  </li>
  <li>
    <p><strong>Inferact</strong>: Harry Chen, Jiangyun Zhu, Kaichao You, Nick Hill, Roger Wang, Simon Mo, Zhewen Li</p>
  </li>
  <li>
    <p><strong>Intel</strong>: Chendi Xue, Jiang Li, Kunshang Ji, Wenjun Liu</p>
  </li>
  <li>
    <p><strong>Meta</strong>: Andrey Talman, Charlotte Qi, Eli Uriegas, Huamin Li, Huy Do, Orion Reblitz-Richardson, Reza Barazesh</p>
  </li>
  <li>
    <p><strong>NVIDIA</strong>: Alec Flowers, Benjamin Chislett, Mathew Wicks, Pen Chung Li, Stefano Castagnetta, Vadim Gimpelson, Xin Li</p>
  </li>
  <li>
    <p><strong>Red Hat</strong>: Andy Linfoot, Avinash Singh, Doug Smith, Edward Quarm, Flora Feng, Lucas Wilkinson, Luka Govedic, Matt Bonanni, Michael Goin, Nicolo Lucchesi, Robert Shaw, Russell Bryant, Tarun Kumar, Tyler Michael Smith, Wentao Ye</p>
  </li>
  <li>
    <p><strong>Reflection AI</strong>: Amr Mahdi (contribution made during his time at Meta)</p>
  </li>
  <li>
    <p><strong>Independent contributors</strong>: Cyrus Leung (DarkLight1337), Yuqi Wang (noooop), haosdent, Mohammad Angkad</p>
  </li>
</ul>

<p>the amazing partners:</p>

<ul>
  <li>
    <p><strong>AWS, Crusoe, LambdaLabs, Nebius, NVIDIA, Roblox, RunPod</strong> for sponsoring us with compute credits</p>
  </li>
  <li>
    <p><strong>Buildkite</strong> for letting us run CI free of charge on their platform &lt;3</p>
  </li>
</ul>

<p>and finally, two mentors who taught me a lot about CI during my time at Anyscale (Ray): <strong>Lonnie Liu</strong> (now at OpenAI) and <strong>Cuong Nguyen</strong> (now at NVIDIA).</p>]]></content><author><name>Kevin Luu (Inferact)</name></author><category term="ci" /><category term="performance" /><category term="evaluation" /><category term="release" /><summary type="html"><![CDATA[]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://vllm-project.github.io/assets/figures/2026-07-16-keeping-vllm-production-quality/00-production-quality-hero-airport.png" /><media:content medium="image" url="https://vllm-project.github.io/assets/figures/2026-07-16-keeping-vllm-production-quality/00-production-quality-hero-airport.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>