<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: yuan lei</title>
    <description>The latest articles on DEV Community by yuan lei (@yuan_lei_e631e36865ed370b).</description>
    <link>https://dev.to/yuan_lei_e631e36865ed370b</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4113920%2F3c261565-e341-4f17-85ce-c00a473f11ab.png</url>
      <title>DEV Community: yuan lei</title>
      <link>https://dev.to/yuan_lei_e631e36865ed370b</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yuan_lei_e631e36865ed370b"/>
    <language>en</language>
    <item>
      <title>How vLLM CUDA Kernels Write and Read the Paged KV Cache</title>
      <dc:creator>yuan lei</dc:creator>
      <pubDate>Fri, 09 Oct 2026 17:39:58 +0000</pubDate>
      <link>https://dev.to/yuan_lei_e631e36865ed370b/how-vllm-cuda-kernels-write-and-read-the-paged-kv-cache-4c2a</link>
      <guid>https://dev.to/yuan_lei_e631e36865ed370b/how-vllm-cuda-kernels-write-and-read-the-paged-kv-cache-4c2a</guid>
      <description>&lt;p&gt;A request sees its tokens in order. The GPU may store their keys and values in physical cache blocks scattered across a shared pool. How does attention find the right data without first copying the request into one contiguous buffer?&lt;/p&gt;

&lt;p&gt;The connection is easiest to follow through two pieces of metadata: &lt;code&gt;slot_mapping&lt;/code&gt; tells the cache-write kernel where to put each new token; &lt;code&gt;block_table&lt;/code&gt; tells attention where to read a request's logical blocks. They describe the same storage from opposite directions.&lt;/p&gt;

&lt;p&gt;This is Part 2 of the vLLM internals series. &lt;a href="https://dev.to/yuan_lei_e631e36865ed370b/inside-vllm-following-one-request-from-the-api-to-gpu-execution-1ja6"&gt;Part 1 follows a request from the API to GPU execution&lt;/a&gt;. You can follow the example below independently.&lt;/p&gt;

&lt;p&gt;The kernel details here refer to vLLM source commit &lt;code&gt;50ac1c7bab47f14d56d86967532574824d02260e&lt;/code&gt;, used in the original July 2026 walkthrough. Backend selection and layouts vary across versions. This article follows the FlashAttention cache path at that source revision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mapping one token to a physical slot
&lt;/h2&gt;

&lt;p&gt;Suppose a request has three logical KV blocks, with 16 token positions per block. The allocator has assigned physical blocks 7, 2, and 9:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Logical block      Physical block
      0                  7
      1                  2
      2                  9

block_table = [7, 2, 9]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are illustrative numbers, not a fixed vLLM block size or allocation policy.&lt;/p&gt;

&lt;p&gt;Token position 20 belongs to logical block &lt;code&gt;20 / 16 = 1&lt;/code&gt;, at offset &lt;code&gt;20 % 16 = 4&lt;/code&gt;. The block table maps logical block 1 to physical block 2. Its flattened physical slot is therefore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;physical slot = physical block * block size + offset
              = 2 * 16 + 4
              = 36
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If position 20 is being processed in the current step, its entry in &lt;code&gt;slot_mapping&lt;/code&gt; is 36. The write kernel receives that destination directly. Later, the attention read path can resolve position 20 through the request's block table and reach physical block 2, offset 4 again.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metadata&lt;/th&gt;
&lt;th&gt;What an entry identifies&lt;/th&gt;
&lt;th&gt;Used for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;slot_mapping&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The physical destination of a token processed in this step&lt;/td&gt;
&lt;td&gt;Writing newly computed K/V&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;block_table&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The physical block backing a request's logical KV block&lt;/td&gt;
&lt;td&gt;Reading cached K/V&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The arrays need different shapes because the operations need different information. A step may write only the newest token for a request while attention reads its whole visible history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing K and V into the cache
&lt;/h2&gt;

&lt;p&gt;In &lt;code&gt;csrc/libtorch_stable/cache_kernels.cu&lt;/code&gt;, the relevant kernel is &lt;code&gt;reshape_and_cache_flash_kernel&lt;/code&gt;. At this revision, one CUDA thread block handles one input token. The destination lookup starts with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int64_t&lt;/span&gt; &lt;span class="n"&gt;token_idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;blockIdx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int64_t&lt;/span&gt; &lt;span class="n"&gt;slot_idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;slot_mapping&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;token_idx&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slot_idx&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int64_t&lt;/span&gt; &lt;span class="n"&gt;block_idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;slot_idx&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;block_size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;int64_t&lt;/span&gt; &lt;span class="n"&gt;block_offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;slot_idx&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;block_size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A negative slot means that this input position has no cache destination. Padding for a fixed execution shape is one reason such positions can occur.&lt;/p&gt;

&lt;p&gt;The division and remainder undo the flattening from the example. Slot 36 becomes physical block 2 and offset 4. Strides then turn those coordinates into a memory address. Here is a simplified address sketch for the key tensor; strides are measured in elements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;key_src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;token_idx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;key_stride&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;key_dst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;key_cache&lt;/span&gt;
        &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;block_idx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;block_stride&lt;/span&gt;
        &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;block_offset&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;page_stride&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The remaining offsets select the KV head and feature within that token. The value tensor follows the same destination mapping.&lt;/p&gt;

&lt;p&gt;This also explains a common source of confusion: a CUDA block is a cooperating group of threads; a KV block is a storage allocation unit. One CUDA block writing one token does not mean it owns an entire KV block.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the cache layout changes the copy path
&lt;/h2&gt;

&lt;p&gt;Writing a token involves copying &lt;code&gt;num_heads * head_size&lt;/code&gt; key elements and the same number of value elements. How that work is divided depends on which values are adjacent in memory.&lt;/p&gt;

&lt;p&gt;Ignoring the outer physical-block dimension, compare these layouts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NHD: [token, head, feature]
     One token's heads can form one contiguous span.

HND: [head, token, feature]
     The same token's next head is separated by head_stride.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The kernel tests whether the heads are contiguous and whether conversion uses a shared scale. When &lt;code&gt;head_stride == head_size&lt;/code&gt; and &lt;code&gt;kv_scale_stride == 0&lt;/code&gt;, it can copy a token's heads as one span. Otherwise, it assigns work by head, with a warp cooperating on that head's copy.&lt;/p&gt;

&lt;p&gt;The scale condition matters even when the data is contiguous. A quantized cache with separate scales per head needs to apply the corresponding conversion to each head. Layout alone does not determine the path.&lt;/p&gt;

&lt;p&gt;The copy helper can move vector packs rather than individual elements. Adjacent lanes handling adjacent packs give the warp a concentrated address range. That is the useful connection to coalescing: inspect the addresses accessed by the active lanes during one instruction. A contiguous chunk within one lane is not enough to describe the whole warp's access pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading logical blocks during attention
&lt;/h2&gt;

&lt;p&gt;The attention kernel receives the query data, sequence metadata, the paged cache, and the block table. To read a logical token position, the addressing relationship is conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;logical block  = token position / block size
offset         = token position % block size
physical block = block_table[logical block]
read K/V at    = physical block, offset, head, feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an addressing sketch, not the production kernel's exact loop. A tiled kernel may resolve pages for a whole data region and reuse address calculations across many loads.&lt;/p&gt;

&lt;p&gt;The logical sequence remains ordered even though its physical blocks are scattered. The read kernel follows the block table as it loads K/V tiles into the attention computation. It does not need to first gather the whole request into a contiguous K/V tensor.&lt;/p&gt;

&lt;p&gt;Paged storage and tiled attention fit together here. Paging decides where the data lives. FlashAttention organizes how the kernel consumes it: retain a query tile, stream key/value tiles, compute a score tile, and update the output using online softmax. The complete score and probability matrices need not be written to GPU global memory.&lt;/p&gt;

&lt;p&gt;The write and read implementations must agree on the cache layout. Swapping in a read kernel that expects a different layout is not a matter of changing a function name. In particular, the older native &lt;code&gt;paged_attention_v1/v2&lt;/code&gt; kernels are not the read side of this FlashAttention path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Following the path in a debugger or source checkout
&lt;/h2&gt;

&lt;p&gt;Start with the selected attention backend and its cache layout. Then follow one token's &lt;code&gt;slot_mapping&lt;/code&gt; entry into the write kernel. On the read side, use the corresponding request's block table to resolve that token's logical block. Both should lead to the same physical cache location.&lt;/p&gt;

&lt;p&gt;The small example gives a useful checkpoint: logical token 20 maps to physical block 2, offset 4, while its write slot is 36. Once that relationship is clear, layout strides, vectorized copies, and attention tiles become separate questions you can inspect without losing the data flow.&lt;/p&gt;

&lt;p&gt;For the full kernel walkthrough and diagrams, see &lt;a href="https://jacklei0901.github.io/articles/inside-vllm-cuda-kernels/" rel="noopener noreferrer"&gt;the expanded article on my blog&lt;/a&gt;. The next part builds a verifiable FlashAttention forward in PyTorch and Triton, so the online-softmax state can be examined directly.&lt;/p&gt;

&lt;p&gt;Source: &lt;a href="https://github.com/vllm-project/vllm/blob/50ac1c7bab47f14d56d86967532574824d02260e/csrc/libtorch_stable/cache_kernels.cu" rel="noopener noreferrer"&gt;vLLM cache kernels at the referenced commit&lt;/a&gt;. The &lt;a href="https://zhuanlan.zhihu.com/p/2060410771461366321" rel="noopener noreferrer"&gt;Chinese edition&lt;/a&gt; contains the original walkthrough. This DEV edition is a shorter adaptation with a new illustrative mapping example.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;English adaptation prepared with AI assistance.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>vllm</category>
      <category>cuda</category>
      <category>llm</category>
      <category>ai</category>
    </item>
    <item>
      <title>Inside vLLM: Following One Request from the API to GPU Execution</title>
      <dc:creator>yuan lei</dc:creator>
      <pubDate>Mon, 07 Sep 2026 12:52:14 +0000</pubDate>
      <link>https://dev.to/yuan_lei_e631e36865ed370b/inside-vllm-following-one-request-from-the-api-to-gpu-execution-1ja6</link>
      <guid>https://dev.to/yuan_lei_e631e36865ed370b/inside-vllm-following-one-request-from-the-api-to-gpu-execution-1ja6</guid>
      <description>&lt;p&gt;Article 1 of 3 · vLLM Internals&lt;/p&gt;

&lt;p&gt;This English edition is adapted from the &lt;a href="https://zhuanlan.zhihu.com/p/2060033883849204419" rel="noopener noreferrer"&gt;published Chinese article on Zhihu&lt;/a&gt;. It preserves the source-code references, experimental boundaries, and reproducible artifacts while adapting the structure for an international engineering audience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; Part 1 · Request lifecycle · &lt;a href="https://jacklei0901.github.io/articles/inside-vllm-cuda-kernels/" rel="noopener noreferrer"&gt;Part 2 · CUDA kernels and paged attention&lt;/a&gt; · &lt;a href="https://jacklei0901.github.io/articles/building-flashattention-pytorch-triton/" rel="noopener noreferrer"&gt;Part 3 · FlashAttention from PyTorch to Triton&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This article follows one offline inference request through vLLM V1: from&lt;br&gt;
&lt;code&gt;LLM.generate()&lt;/code&gt; and inter-process communication to scheduling, input&lt;br&gt;
flattening, GPU model execution, paged KV-cache access, sampling, and resource&lt;br&gt;
reclamation. The goal is to answer one concrete question: what happens behind&lt;br&gt;
the call to &lt;code&gt;llm.generate()&lt;/code&gt; before the completed result reaches the caller?&lt;/p&gt;

&lt;p&gt;The discussion assumes familiarity with Transformer inference, including&lt;br&gt;
prefill, decode, KV caching, and autoregressive generation. It focuses on how&lt;br&gt;
those concepts appear in vLLM source code rather than reteaching the model&lt;br&gt;
architecture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Version scope.&lt;/strong&gt; The source references were verified against vLLM 0.22.0;&lt;br&gt;
this edition was checked on September 3, 2026. vLLM evolves quickly, so some&lt;br&gt;
filenames and call boundaries will move. The&lt;br&gt;
long-lived ideas—continuous batching, token budgets, paged KV allocation, and&lt;br&gt;
the separation between scheduling and execution—are the real subject of the&lt;br&gt;
article.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Read the Source Instead of Another API Guide?
&lt;/h2&gt;

&lt;p&gt;Many introductions stop at the useful analogy that PagedAttention manages the&lt;br&gt;
KV cache much like virtual memory manages pages. The analogy does not tell us&lt;br&gt;
how a request is admitted, how variable-length requests become a flat token&lt;br&gt;
batch, or what the page table looks like at the kernel boundary.&lt;/p&gt;

&lt;p&gt;The answers are in the source. This article follows vLLM 0.22's V1 execution&lt;br&gt;
path from the public entry point to the CUDA boundary. It is a source-code&lt;br&gt;
walkthrough, not an API tutorial.&lt;/p&gt;
&lt;h2&gt;
  
  
  Start with a System Map
&lt;/h2&gt;

&lt;p&gt;Start with the process boundary and the engine loop. In vLLM V1, EngineCore&lt;br&gt;
runs continuously in a child process. Requests enter and leave the active set&lt;br&gt;
between steps; the caller does not drive GPU execution one token at a time.&lt;/p&gt;

&lt;p&gt;One detail shapes the entire path: in this configuration, vLLM 0.22 separates&lt;br&gt;
the caller-facing engine from EngineCore with a process boundary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REQUEST SUBMISSION · main process -&amp;gt; EngineCore child

Caller
  └─ LLM.generate(prompts)
       └─ LLMEngine.add_request()
            └─ EngineCoreClient
                 └─ IPC input queue ──────────────▶ scheduler.add_request()

CONTINUOUS ENGINE LOOP · EngineCore child

run_busy_loop()
  ├─ receive newly submitted requests
  └─ repeat EngineCore.step()
       1. scheduler.schedule()
          └─ spend token / KV-cache budgets -&amp;gt; SchedulerOutput
       2. model_executor.execute_model()
          └─ GPU forward pass and sampling -&amp;gt; sampled tokens
       3. scheduler.update_from_output()
          ├─ update request state and release finished KV blocks
          └─ EngineCoreOutputs ───────────────▶ IPC output queue

RESULT CONSUMPTION · EngineCore child -&amp;gt; main process

IPC output queue
  └─ LLMEngine.step() / get_output()
       └─ detokenize and assemble RequestOutput
            └─ return completed results to LLM.generate() and the caller
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This map establishes three boundaries used throughout the article.&lt;/p&gt;

&lt;p&gt;First, the main process consumes results; EngineCore produces them. Calling&lt;br&gt;
&lt;code&gt;LLMEngine.step()&lt;/code&gt; does not tell the GPU to perform one step. It retrieves&lt;br&gt;
already-produced output through &lt;code&gt;get_output()&lt;/code&gt;, then detokenizes and assembles&lt;br&gt;
the user-facing result. Scheduling and model execution advance independently&lt;br&gt;
inside EngineCore's busy loop.&lt;/p&gt;

&lt;p&gt;Second, &lt;code&gt;EngineCore.step()&lt;/code&gt; has three core stages: scheduling, execution, and&lt;br&gt;
state update. The rest of the article expands those stages one at a time.&lt;/p&gt;

&lt;p&gt;Third, this loop implements continuous batching. The request set changes from&lt;br&gt;
one step to the next: stage 3 removes completed requests and releases their&lt;br&gt;
resources, while the next call to &lt;code&gt;schedule()&lt;/code&gt; can admit waiting work. The GPU&lt;br&gt;
therefore does not have to wait for one fixed batch to finish before admitting&lt;br&gt;
another request.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Path We Will Follow
&lt;/h2&gt;

&lt;p&gt;The rest of the article expands that loop into six parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Entry:&lt;/strong&gt; how &lt;code&gt;LLM&lt;/code&gt; and &lt;code&gt;LLMEngine&lt;/code&gt; submit work to EngineCore.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduling:&lt;/strong&gt; how &lt;code&gt;schedule()&lt;/code&gt; spends token and KV-cache budgets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Input preparation:&lt;/strong&gt; how variable-length requests become flat tensors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model execution:&lt;/strong&gt; how metadata reaches attention inside the forward pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PagedAttention:&lt;/strong&gt; how the write and read paths address KV-cache blocks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output:&lt;/strong&gt; how logits become tokens and completed requests release resources.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each part keeps only the code needed to connect one boundary to the next.&lt;/p&gt;
&lt;h2&gt;
  
  
  Part 1: The Entry Point Behind &lt;code&gt;generate()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Start with the public interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;vllm&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LLM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SamplingParams&lt;/span&gt;

&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LLM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;facebook/opt-125m&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Once upon a time&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nc"&gt;SamplingParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those two lines initialize the entire engine stack. Start with the &lt;code&gt;LLM&lt;/code&gt; class.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;LLM&lt;/code&gt; Is a Thin Wrapper
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;entrypoints/llm.py&lt;/code&gt;, &lt;code&gt;LLM&lt;/code&gt; delegates engine construction to one factory&lt;br&gt;
call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# entrypoints/llm.py
&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;llm_engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LLMEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_engine_args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;engine_args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;engine_args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;usage_context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;UsageContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LLM_CLASS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;LLM&lt;/code&gt; packages model, dtype, and other configuration into &lt;code&gt;EngineArgs&lt;/code&gt;, then&lt;br&gt;
uses the factory method to create &lt;code&gt;LLMEngine&lt;/code&gt;. It is the public facade;&lt;br&gt;
&lt;code&gt;LLMEngine&lt;/code&gt; owns the request-processing path followed below.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;generate()&lt;/code&gt; returns a &lt;code&gt;list[RequestOutput]&lt;/code&gt;, one complete result per request.&lt;br&gt;
The generated text is available as &lt;code&gt;output.outputs[0].text&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The distinction is useful throughout the article: &lt;code&gt;LLM&lt;/code&gt; is the facade, while&lt;br&gt;
&lt;code&gt;LLMEngine&lt;/code&gt; connects that facade to EngineCore.&lt;/p&gt;
&lt;h3&gt;
  
  
  At Its Core, &lt;code&gt;generate()&lt;/code&gt; Is a Loop
&lt;/h3&gt;

&lt;p&gt;The internal logic of &lt;code&gt;generate&lt;/code&gt;, after stripping away the details, is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Conceptual skeleton
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;prompts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;llm_engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;   &lt;span class="c1"&gt;# 1. Register the request with the engine
&lt;/span&gt;
&lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;llm_engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has_unfinished_requests&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;  &lt;span class="c1"&gt;# 2. While any request is unfinished
&lt;/span&gt;    &lt;span class="n"&gt;step_outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;llm_engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;         &lt;span class="c1"&gt;#    advance the engine by one step
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;step_outputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;finished&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;outputs&lt;/span&gt;                                     &lt;span class="c1"&gt;# 3. Return after collecting every result
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This &lt;code&gt;while ... step()&lt;/code&gt; loop continues until every submitted request is&lt;br&gt;
finished. Unlike a hand-written &lt;code&gt;for _ in range(max_tokens)&lt;/code&gt; loop over one&lt;br&gt;
sequence, it tracks a set of requests whose lengths and completion times differ.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;step()&lt;/code&gt; Does Not Submit the Request
&lt;/h3&gt;

&lt;p&gt;The name &lt;code&gt;step()&lt;/code&gt; can suggest a synchronous "submit, execute, return" call.&lt;br&gt;
The implementation does something different:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# v1/engine/llm_engine.py (simplified)
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# 1) Fetch outputs already produced by EngineCore
&lt;/span&gt;    &lt;span class="n"&gt;outputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engine_core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_output&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# 2) Decode tokens and evaluate stopping conditions
&lt;/span&gt;    &lt;span class="n"&gt;processed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output_processor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process_outputs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;

    &lt;span class="c1"&gt;# 3) Handle requests terminated by a stop string
&lt;/span&gt;    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;engine_core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort_requests&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reqs_to_abort&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 4) Record statistics
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;processed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request_outputs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;step()&lt;/code&gt; never submits a request or directly drives model execution. It calls&lt;br&gt;
&lt;code&gt;get_output()&lt;/code&gt; to consume results. Submission already happened in&lt;br&gt;
&lt;code&gt;add_request()&lt;/code&gt;; the actual computation runs independently elsewhere.&lt;/p&gt;

&lt;p&gt;This description assumes the default multiprocess V1 configuration. If&lt;br&gt;
&lt;code&gt;VLLM_ENABLE_V1_MULTIPROCESSING=0&lt;/code&gt; is set for debugging, the in-process client&lt;br&gt;
drives EngineCore from &lt;code&gt;get_output()&lt;/code&gt; and the child-process producer/consumer&lt;br&gt;
boundary collapses. The scheduling, execution, and update stages discussed&lt;br&gt;
below still apply, but they no longer run in a separate EngineCore process.&lt;/p&gt;

&lt;p&gt;This producer-consumer split is asynchronous across the process boundary. In&lt;br&gt;
configurations that enable asynchronous scheduling, the startup log also states:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INFO ... Asynchronous scheduling is enabled.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That "elsewhere" is a separate child process. A running server prints a prefix&lt;br&gt;
such as &lt;code&gt;(EngineCore pid=8884)&lt;/code&gt; in its logs: this is the &lt;code&gt;EngineCore&lt;/code&gt; process&lt;br&gt;
that performs scheduling and model execution.&lt;/p&gt;

&lt;p&gt;The main process (&lt;code&gt;LLMEngine&lt;/code&gt;) is the consumer: &lt;code&gt;get_output()&lt;/code&gt; receives results,&lt;br&gt;
decodes them, and returns them to the caller. The child process (&lt;code&gt;EngineCore&lt;/code&gt;)&lt;br&gt;
is the producer: its busy loop schedules requests, runs the model, produces&lt;br&gt;
tokens, and writes results to an IPC queue.&lt;/p&gt;

&lt;p&gt;This lets host-side output processing overlap EngineCore execution. In an online&lt;br&gt;
server, request tokenization and admission can also proceed independently of an&lt;br&gt;
already-running GPU step. The IPC queues separate those execution timelines.&lt;/p&gt;
&lt;h3&gt;
  
  
  What This Part Established
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;LLM&lt;/code&gt; is the facade; &lt;code&gt;LLMEngine&lt;/code&gt; is the engine entry point.&lt;/li&gt;
&lt;li&gt;At the heart of &lt;code&gt;generate&lt;/code&gt; is the &lt;code&gt;while has_unfinished_requests(): step()&lt;/code&gt; loop.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LLMEngine.step()&lt;/code&gt; consumes and decodes output; the separate &lt;code&gt;EngineCore&lt;/code&gt;
process performs scheduling and execution asynchronously.&lt;/li&gt;
&lt;li&gt;IPC decouples production in the child process from consumption in the main
process, allowing CPU work to overlap GPU execution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next we cross the IPC boundary and enter &lt;code&gt;EngineCore&lt;/code&gt;, whose busy loop has three&lt;br&gt;
core stages: schedule, execute, and update.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb47r5ozypez00uwno2lh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fb47r5ozypez00uwno2lh.png" alt="fig2-class-structure" width="800" height="833"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Figure: ownership among vLLM's core classes. &lt;code&gt;LLM&lt;/code&gt; owns &lt;code&gt;LLMEngine&lt;/code&gt;, which&lt;br&gt;
communicates with &lt;code&gt;EngineCore&lt;/code&gt;; &lt;code&gt;EngineCore&lt;/code&gt; in turn owns the scheduler,&lt;br&gt;
model executor, and KV-cache manager. The horizontal line is the process&lt;br&gt;
boundary: main process/consumer above, child process/producer below.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Part 2: Scheduling and Continuous Batching
&lt;/h2&gt;

&lt;p&gt;The previous section stopped at the child-process boundary. Each iteration of&lt;br&gt;
the &lt;code&gt;EngineCore&lt;/code&gt; busy loop runs the three stages of &lt;code&gt;step()&lt;/code&gt;; this section&lt;br&gt;
starts with &lt;code&gt;scheduler.schedule()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Continuous batching is not a standalone module. It emerges from the decisions&lt;br&gt;
made on every call to &lt;code&gt;schedule()&lt;/code&gt;. One useful mental model is an operating-system&lt;br&gt;
scheduler whose scarce resources are token-compute budget and GPU KV-cache&lt;br&gt;
pages.&lt;/p&gt;

&lt;p&gt;The scheduler lives in &lt;code&gt;v1/core/sched/scheduler.py&lt;/code&gt;; its central method is&lt;br&gt;
&lt;code&gt;schedule()&lt;/code&gt; (&lt;code&gt;scheduler.py:329&lt;/code&gt;). The file is large, but this path only needs&lt;br&gt;
three pieces: the main loop, preemption, and the final packaged output.&lt;/p&gt;
&lt;h3&gt;
  
  
  A Mental Model: Two Budgets and Two Queues
&lt;/h3&gt;

&lt;p&gt;The V1 scheduler can be summarized in one sentence: during each engine step,&lt;br&gt;
advance as many requests as possible without exceeding the token budget or the&lt;br&gt;
available KV blocks.&lt;/p&gt;

&lt;p&gt;These concepts map naturally to familiar operating-system terms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;token_budget&lt;/code&gt;: the maximum number of tokens processed in one step
(&lt;code&gt;max_num_batched_tokens=8192&lt;/code&gt; in the example startup log), analogous to a
bounded compute time slice.&lt;/li&gt;
&lt;li&gt;KV-cache blocks: the free cache capacity in GPU memory, analogous to physical
page frames.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;waiting&lt;/code&gt;: requests waiting to begin or continue prefill, analogous to a ready
queue.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;running&lt;/code&gt;: admitted requests eligible to advance in the current step.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The comment at the start of &lt;code&gt;schedule()&lt;/code&gt; is the best guide to its model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# v1/core/sched/scheduler.py:330
# There's no "decoding phase" nor "prefill phase" in the scheduler.
# Each request just has the num_computed_tokens and num_tokens_with_spec.
# At each step, the scheduler tries to assign tokens to the requests
# so that each request's num_computed_tokens can catch up its
# num_tokens_with_spec.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The V1 scheduler does not maintain separate prefill and decode phases. Instead,&lt;br&gt;
it compares each request's &lt;code&gt;num_computed_tokens&lt;/code&gt; with&lt;br&gt;
&lt;code&gt;num_tokens_with_spec&lt;/code&gt; and decides how many tokens to advance in the current&lt;br&gt;
step. A decode request commonly advances by one token; a prefill request may&lt;br&gt;
advance by a larger chunk. The same progress model also accommodates chunked&lt;br&gt;
prefill and prefix-cache hits.&lt;/p&gt;

&lt;p&gt;The following sketch is a useful map of &lt;code&gt;schedule()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;One schedule() step:
  pass 1  RUNNING first (decode) ──── no free block → preempt from the tail
  pass 2  fill from WAITING (prefill) ── over budget → clamp or defer
  pack    SchedulerOutput (the execution plan)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmy9jfuu0wjh21sj7jg62.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmy9jfuu0wjh21sj7jg62.png" alt="fig3-schedule-flow" width="800" height="740"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Figure: one &lt;code&gt;schedule()&lt;/code&gt; step. The scheduler considers running requests,&lt;br&gt;
admits waiting requests with any remaining budget, handles allocation failure&lt;br&gt;
through preemption, and packages the result as &lt;code&gt;SchedulerOutput&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Pass 1: Why Running Requests Come First
&lt;/h3&gt;

&lt;p&gt;The first substantive loop in &lt;code&gt;schedule()&lt;/code&gt; traverses &lt;code&gt;running&lt;/code&gt;&lt;br&gt;
(&lt;code&gt;scheduler.py:364&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# v1/core/sched/scheduler.py:364
# First, schedule the RUNNING requests.
&lt;/span&gt;&lt;span class="n"&gt;req_index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;req_index&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;token_budget&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;req_index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;num_new_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;num_tokens_with_spec&lt;/span&gt;
                      &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;num_output_placeholders&lt;/span&gt;
                      &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;num_computed_tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;num_new_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_new_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token_budget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The source comments make the order explicit: running requests are considered&lt;br&gt;
before waiting requests. This ordering tends to protect inter-token latency for&lt;br&gt;
requests that are already generating, although the observed latency trade-off&lt;br&gt;
still depends on the configured scheduling policy and workload. It also lets&lt;br&gt;
requests that already hold KV blocks continue making progress toward completion&lt;br&gt;
and eventual reclamation. These are useful consequences of the order, not a&lt;br&gt;
claim that every running request always outranks every waiting request under all&lt;br&gt;
policies.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;waiting&lt;/code&gt; queue is an admission queue, not a tokenization cache. Requests in&lt;br&gt;
it are waiting for scheduler resources; prompt preprocessing has already&lt;br&gt;
happened before this point.&lt;/p&gt;
&lt;h3&gt;
  
  
  Pass 2: Chunked Prefill Is a Budget Clamp
&lt;/h3&gt;

&lt;p&gt;After considering running requests, the scheduler uses any remaining budget to&lt;br&gt;
admit work from &lt;code&gt;waiting&lt;/code&gt; (&lt;code&gt;scheduler.py:544&lt;/code&gt;). This is where the chunked-prefill&lt;br&gt;
budget clamp appears.&lt;/p&gt;

&lt;p&gt;A waiting request may have 5,000 prompt tokens while only 2,000 tokens remain&lt;br&gt;
in the current step's budget. The relevant control flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Budget clamp inside the waiting loop
&lt;/span&gt;&lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scheduler_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enable_chunked_prefill&lt;/span&gt;
        &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;num_new_tokens&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;token_budget&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# If chunked_prefill is disabled, we can stop the scheduling here.
&lt;/span&gt;    &lt;span class="k"&gt;break&lt;/span&gt;

&lt;span class="n"&gt;num_new_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num_new_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token_budget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;num_new_tokens&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details matter here.&lt;/p&gt;

&lt;p&gt;First, &lt;code&gt;break&lt;/code&gt; does not drop the request. It exits the waiting-loop because the&lt;br&gt;
remaining budget cannot admit another request in this step. The request stays&lt;br&gt;
in &lt;code&gt;waiting&lt;/code&gt; and is reconsidered the next time &lt;code&gt;schedule()&lt;/code&gt; runs.&lt;/p&gt;

&lt;p&gt;With chunked prefill enabled, &lt;code&gt;min()&lt;/code&gt; admits the portion that fits and leaves&lt;br&gt;
the rest for a later step. With it disabled, an over-budget request stays in&lt;br&gt;
&lt;code&gt;waiting&lt;/code&gt; and is retried as a whole. Neither path loses the request; removal&lt;br&gt;
happens only after normal completion or an explicit abort.&lt;/p&gt;

&lt;p&gt;A useful rule when reading schedulers is to ask where an item goes after a&lt;br&gt;
&lt;code&gt;break&lt;/code&gt; or &lt;code&gt;continue&lt;/code&gt;. Here it remains in the same queue for a later step, just&lt;br&gt;
as an OS process that misses one time slice remains runnable.&lt;/p&gt;

&lt;p&gt;Second, chunked prefill reduces to a budget clamp:&lt;br&gt;
&lt;code&gt;num_new_tokens = min(num_new_tokens, token_budget)&lt;/code&gt;. A long prompt contributes&lt;br&gt;
only the tokens that fit in the current step; the remainder is scheduled later.&lt;br&gt;
The full policy considers more state, but this clamp is what divides a long&lt;br&gt;
prefill across scheduler steps.&lt;/p&gt;

&lt;p&gt;(The running-request loop has the same clamp at &lt;code&gt;scheduler.py:392&lt;/code&gt;. Because V1&lt;br&gt;
does not maintain separate prefill and decode phases, the budget logic applies&lt;br&gt;
uniformly to both.)&lt;/p&gt;
&lt;h3&gt;
  
  
  Preemption: Discard the KV State and Recompute
&lt;/h3&gt;

&lt;p&gt;Another branch appears when a running request needs more KV blocks and&lt;br&gt;
&lt;code&gt;allocate_slots&lt;/code&gt; cannot satisfy the request. The allocation-retry loop beginning&lt;br&gt;
at &lt;code&gt;scheduler.py:443&lt;/code&gt; can then preempt a victim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# v1/core/sched/scheduler.py:443 (simplified)
&lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;new_blocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kv_cache_manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;allocate_slots&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num_new_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;new_blocks&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;                              &lt;span class="c1"&gt;# Allocation succeeded; schedule normally
&lt;/span&gt;    &lt;span class="c1"&gt;# Allocation failed; choose a victim to preempt
&lt;/span&gt;    &lt;span class="n"&gt;preempted_req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;      &lt;span class="c1"&gt;# Default policy: pop from the tail (LIFO)
&lt;/span&gt;    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_preempt_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;preempted_req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scheduled_timestamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;preempted_req&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;                              &lt;span class="c1"&gt;# No victim remains; defer this request
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under the default policy, &lt;code&gt;self.running.pop()&lt;/code&gt; selects a victim from the tail&lt;br&gt;
(LIFO). This usually victimizes a more recently admitted request with less&lt;br&gt;
accumulated progress, reducing the work discarded, although queue position is&lt;br&gt;
not a universal measure of recomputation cost. With &lt;code&gt;PRIORITY&lt;/code&gt;&lt;br&gt;
scheduling, victim selection also considers priority (&lt;code&gt;scheduler.py:456&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;_preempt_request&lt;/code&gt; (&lt;code&gt;scheduler.py:929&lt;/code&gt;) defines what happens to the preempted&lt;br&gt;
request's KV-cache state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# v1/core/sched/scheduler.py:929
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_preempt_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kv_cache_manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# ← release its KV blocks immediately
&lt;/span&gt;    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;encoder_cache_manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RequestStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PREEMPTED&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;num_computed_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;              &lt;span class="c1"&gt;# ← reset computed progress
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waiting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prepend_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;# ← prepend it to the waiting queue
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details matter here. First, &lt;code&gt;num_computed_tokens = 0&lt;/code&gt; records recomputation&lt;br&gt;
semantics: the scheduler no longer treats the request's previous progress as&lt;br&gt;
resident KV state. When rescheduled, it must rebuild the required state, subject&lt;br&gt;
to any cache reuse the current configuration can legitimately recover. This is&lt;br&gt;
different from swapping the victim's KV tensors to host memory and restoring&lt;br&gt;
them later.&lt;/p&gt;

&lt;p&gt;The trade-off is workload-dependent. Swapping consumes host-device bandwidth&lt;br&gt;
and host memory; recomputation consumes additional GPU work. vLLM V1 chooses&lt;br&gt;
the latter for this path, avoiding a KV transfer over PCIe at the cost of&lt;br&gt;
re-running model computation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi0i90htrtk6cg6m24973.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fi0i90htrtk6cg6m24973.png" alt="fig4-preemption" width="800" height="716"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Figure: two preemption strategies. Swapping preserves KV state through a&lt;br&gt;
host-device transfer; recomputation releases the blocks and later rebuilds&lt;br&gt;
the required state.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The second detail is &lt;code&gt;prepend_request&lt;/code&gt;, an anti-starvation measure. A preempted&lt;br&gt;
request returns to the head of &lt;code&gt;waiting&lt;/code&gt; rather than the tail. Appending it&lt;br&gt;
behind every newly arrived request could cause repeated preemption and&lt;br&gt;
indefinite delay; prepending gives the sacrificed request priority on the next&lt;br&gt;
admission pass.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Scheduler Produces &lt;code&gt;SchedulerOutput&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;schedule()&lt;/code&gt; packages its decisions into &lt;code&gt;SchedulerOutput&lt;/code&gt; (defined in&lt;br&gt;
&lt;code&gt;v1/core/sched/output.py:181&lt;/code&gt;) and hands that execution plan to the model&lt;br&gt;
runner. The execution layer consumes the decision; it does not reschedule the&lt;br&gt;
requests.&lt;/p&gt;

&lt;p&gt;Many fields support optional features such as speculative decoding,&lt;br&gt;
multimodality, structured output, and connectors. Four core fields are enough&lt;br&gt;
to enter the execution layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# v1/core/sched/output.py:181 (selected core fields)
&lt;/span&gt;&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SchedulerOutput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;scheduled_new_reqs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;NewRequestData&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;      &lt;span class="c1"&gt;# First scheduling: send complete request data
&lt;/span&gt;    &lt;span class="n"&gt;scheduled_cached_reqs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CachedRequestData&lt;/span&gt;      &lt;span class="c1"&gt;# Previously scheduled: send only the delta
&lt;/span&gt;    &lt;span class="n"&gt;num_scheduled_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;          &lt;span class="c1"&gt;# req_id -&amp;gt; tokens processed in this step
&lt;/span&gt;    &lt;span class="n"&gt;total_num_scheduled_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;finished_req_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                     &lt;span class="c1"&gt;# Completed requests; tell the worker to reclaim state
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;new_block_ids_to_zero&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="c1"&gt;# Newly allocated blocks that the worker must zero
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Their roles become clearer at the execution boundary.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;scheduled_new_reqs&lt;/code&gt; carries the state needed when a request is first scheduled;&lt;br&gt;
&lt;code&gt;scheduled_cached_reqs&lt;/code&gt; carries updates for requests already known to the model&lt;br&gt;
runner. This avoids rebuilding and transmitting the complete request state on&lt;br&gt;
every step—a state-cache plus incremental-update pattern.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;num_scheduled_tokens&lt;/code&gt; implements the unified progress model: it records how&lt;br&gt;
many tokens each request will process in this step. The execution layer can use&lt;br&gt;
those counts to pack prefill and decode work into one flat token batch.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;finished_req_ids&lt;/code&gt; tells the worker which completed requests can release their&lt;br&gt;
cached state, analogous to reclaiming OS resources after a process exits.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;new_block_ids_to_zero&lt;/code&gt; identifies freshly allocated cache blocks that must be&lt;br&gt;
cleared before the forward pass when KV-cache zeroing is enabled. Zeroing keeps&lt;br&gt;
stale data or NaNs from an earlier owner from corrupting attention or&lt;br&gt;
state-space-model computation. The field is conditional; not every&lt;br&gt;
configuration needs this step.&lt;/p&gt;
&lt;h3&gt;
  
  
  Mapping the Scheduler to OS Concepts
&lt;/h3&gt;

&lt;p&gt;Several scheduler concepts have useful operating-system analogies:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operating system concepts&lt;/th&gt;
&lt;th&gt;vLLM Scheduler correspondence&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ready Queue&lt;/td&gt;
&lt;td&gt;&lt;code&gt;waiting&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run Queue&lt;/td&gt;
&lt;td&gt;&lt;code&gt;running&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Physical page frame allocator&lt;/td&gt;
&lt;td&gt;&lt;code&gt;kv_cache_manager&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Page fault/out of memory&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;allocate_slots&lt;/code&gt; returns None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Process swap&lt;/td&gt;
&lt;td&gt;Preemption by releasing KV state and recomputing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;time slice&lt;/td&gt;
&lt;td&gt;token budget&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Priority compensation after preemption&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;prepend_request&lt;/code&gt; returns the victim to the head of &lt;code&gt;waiting&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output of scheduling decision&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SchedulerOutput&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Continuous batching follows from rebuilding the execution batch at every&lt;br&gt;
&lt;code&gt;schedule()&lt;/code&gt; call. Completed requests leave, waiting requests enter, long&lt;br&gt;
prompts may be split into chunks, and allocation pressure may trigger&lt;br&gt;
preemption. The engine can therefore reuse newly available capacity without&lt;br&gt;
waiting for an earlier fixed batch to finish in full.&lt;/p&gt;

&lt;p&gt;Next, follow this &lt;code&gt;SchedulerOutput&lt;/code&gt; plan into the execution layer and see how&lt;br&gt;
variable-length requests become flat tensors for the model.&lt;/p&gt;
&lt;h2&gt;
  
  
  Part 3: Flattening Variable-Length Requests
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;SchedulerOutput&lt;/code&gt; from the previous part reaches &lt;code&gt;GPUModelRunner&lt;/code&gt; in the&lt;br&gt;
worker. Suppose it assigns four tokens to request A and one token each to&lt;br&gt;
requests B and C. How does the runner turn those unequal slices into tensors for&lt;br&gt;
the GPU?&lt;/p&gt;

&lt;p&gt;A conventional implementation might pad every request to the same length and&lt;br&gt;
stack a &lt;code&gt;[batch, seq_len]&lt;/code&gt; tensor. This path instead packs the scheduled tokens.&lt;/p&gt;

&lt;p&gt;The path enters &lt;code&gt;GPUModelRunner.execute_model&lt;/code&gt;&lt;br&gt;
(&lt;code&gt;v1/worker/gpu_model_runner.py:3913&lt;/code&gt;) and calls &lt;code&gt;_prepare_inputs&lt;/code&gt;&lt;br&gt;
(&lt;code&gt;gpu_model_runner.py:1839&lt;/code&gt;). &lt;code&gt;InputBatch&lt;/code&gt;&lt;br&gt;
(&lt;code&gt;v1/worker/gpu/input_batch.py:36&lt;/code&gt;) holds the prepared state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# v1/worker/gpu/input_batch.py:36 (excerpt)
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InputBatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;req_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;num_reqs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;

    &lt;span class="c1"&gt;# [num_reqs] tokens processed for each request in this step
&lt;/span&gt;    &lt;span class="n"&gt;num_scheduled_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;
    &lt;span class="c1"&gt;# sum(num_scheduled_tokens): total tokens in this step
&lt;/span&gt;    &lt;span class="n"&gt;num_tokens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;

    &lt;span class="c1"&gt;# [num_reqs + 1] request boundaries in the flattened sequence (prefix sum)
&lt;/span&gt;    &lt;span class="n"&gt;query_start_loc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tensor&lt;/span&gt;
    &lt;span class="c1"&gt;# [num_reqs] current total length of each request, including history
&lt;/span&gt;    &lt;span class="n"&gt;seq_lens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tensor&lt;/span&gt;

    &lt;span class="c1"&gt;# [num_tokens] ← one-dimensional, not [batch, seq_len]
&lt;/span&gt;    &lt;span class="n"&gt;input_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tensor&lt;/span&gt;
    &lt;span class="c1"&gt;# [num_tokens]
&lt;/span&gt;    &lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tensor&lt;/span&gt;

    &lt;span class="c1"&gt;# [total_num_logits] positions that actually require logits
&lt;/span&gt;    &lt;span class="n"&gt;logits_indices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tensor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key shape is &lt;code&gt;input_ids: [num_tokens]&lt;/code&gt;: the scheduled tokens form one&lt;br&gt;
packed dimension rather than a padded batch matrix.&lt;/p&gt;
&lt;h3&gt;
  
  
  Flattening Requests into One Token Stream
&lt;/h3&gt;

&lt;p&gt;V1 concatenates the tokens selected for the current step into one packed&lt;br&gt;
sequence, regardless of which request they belong to or whether they represent&lt;br&gt;
prefill or decode work.&lt;/p&gt;

&lt;p&gt;In the example, A contributes a four-token prefill chunk, while B and C each&lt;br&gt;
contribute one decode token. If their current positions are 50 and 30, the&lt;br&gt;
packed tensors look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;input_ids&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;A0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;A1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;A2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;A3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;B0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;C0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# six values, one-dimensional
&lt;/span&gt;&lt;span class="n"&gt;positions&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# each token's position within its own request
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A starts at position 0; B and C retain positions from their own sequences rather&lt;br&gt;
than being renumbered within the packed batch. Here&lt;br&gt;
&lt;code&gt;num_scheduled_tokens = [4, 1, 1]&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;query_start_loc = [0, 4, 5, 6]&lt;/code&gt;. The latter is a prefix sum that marks each&lt;br&gt;
request's range. Attention combines these boundaries with &lt;code&gt;seq_lens&lt;/code&gt; to keep&lt;br&gt;
requests separate.&lt;/p&gt;

&lt;p&gt;This design has two advantages.&lt;/p&gt;

&lt;p&gt;First, packing avoids computation on padding positions. Every scheduled position&lt;br&gt;
corresponds to actual request work, although kernels may still pad the packed&lt;br&gt;
token count internally for CUDA graphs or alignment.&lt;/p&gt;

&lt;p&gt;Second, packing carries the scheduler's unified token-count abstraction into&lt;br&gt;
execution. The runner uses &lt;code&gt;num_scheduled_tokens&lt;/code&gt; to place each request's slice&lt;br&gt;
in the flat sequence. Prefill and decode work can therefore coexist in one&lt;br&gt;
step, while metadata preserves their separate boundaries.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsh50uq64weuvttlmbwkg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsh50uq64weuvttlmbwkg.png" alt="fig5-flatten-input" width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Figure: three variable-length requests—a prefill chunk and two decode&lt;br&gt;
requests—packed into one-dimensional &lt;code&gt;input_ids&lt;/code&gt;, &lt;code&gt;positions&lt;/code&gt;, and&lt;br&gt;
&lt;code&gt;slot_mapping&lt;/code&gt; arrays.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Two Values to Remember: &lt;code&gt;slot_mapping&lt;/code&gt; and &lt;code&gt;logits_indices&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Two additional fields connect this packed representation to later stages.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;slot_mapping&lt;/code&gt; is one-dimensional with shape &lt;code&gt;[num_tokens]&lt;/code&gt;, just like&lt;br&gt;
&lt;code&gt;input_ids&lt;/code&gt;. For every token in the current step, it records the destination&lt;br&gt;
slot for the newly computed K and V tensors. Part 5 follows this value into the&lt;br&gt;
KV-cache write kernel.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;logits_indices&lt;/code&gt; has shape &lt;code&gt;[total_num_logits]&lt;/code&gt;, which can be much smaller than&lt;br&gt;
&lt;code&gt;num_tokens&lt;/code&gt;. To generate the next token, vLLM usually needs only the final&lt;br&gt;
hidden state selected for each request. If a prefill chunk processes four&lt;br&gt;
tokens, the first three positions do not need logits. &lt;code&gt;logits_indices&lt;/code&gt; selects&lt;br&gt;
only the positions that feed the sampler; Part 6 follows that path.&lt;/p&gt;

&lt;p&gt;The model runner therefore produces a set of aligned flat views: &lt;code&gt;input_ids&lt;/code&gt;&lt;br&gt;
and &lt;code&gt;positions&lt;/code&gt; feed the forward pass; &lt;code&gt;query_start_loc&lt;/code&gt; and &lt;code&gt;seq_lens&lt;/code&gt; delimit&lt;br&gt;
requests for attention; &lt;code&gt;slot_mapping&lt;/code&gt; selects KV-cache destinations; and&lt;br&gt;
&lt;code&gt;logits_indices&lt;/code&gt; selects the positions that require logits.&lt;/p&gt;
&lt;h3&gt;
  
  
  What This Part Established
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The execution layer packs scheduled request slices into one-dimensional
&lt;code&gt;input_ids: [num_tokens]&lt;/code&gt; rather than a padded &lt;code&gt;[batch, seq]&lt;/code&gt; matrix.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;query_start_loc&lt;/code&gt; marks request boundaries in the packed query, while
&lt;code&gt;seq_lens&lt;/code&gt; supplies each request's visible KV length.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;num_scheduled_tokens&lt;/code&gt; connects scheduler decisions to the packed execution
batch, allowing prefill and decode work to coexist in one step.&lt;/li&gt;
&lt;li&gt;Two values connect later stages: &lt;code&gt;slot_mapping&lt;/code&gt; selects KV destinations in
Part 5, while &lt;code&gt;logits_indices&lt;/code&gt; selects the positions sampled in Part 6.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, the flat token sequence enters the model forward pass. The remaining&lt;br&gt;
question is how request boundaries and KV-block mappings reach attention&lt;br&gt;
operators buried inside many model layers.&lt;/p&gt;
&lt;h2&gt;
  
  
  Part 4: Passing Attention Metadata into Model Execution
&lt;/h2&gt;

&lt;p&gt;The packed sequence now enters the model forward pass. Attention still needs&lt;br&gt;
step-specific metadata that does not appear in an ordinary model signature.&lt;/p&gt;

&lt;p&gt;That metadata includes &lt;code&gt;block_table&lt;/code&gt;, &lt;code&gt;seq_lens&lt;/code&gt;, and &lt;code&gt;query_start_loc&lt;/code&gt;. The&lt;br&gt;
request set and lengths change each step, yet every attention layer needs access&lt;br&gt;
to the corresponding state. How does it reach operators buried inside the&lt;br&gt;
Transformer stack?&lt;/p&gt;

&lt;p&gt;A direct design would pass the metadata through every layer:&lt;br&gt;
&lt;code&gt;forward(input_ids, attn_metadata)&lt;/code&gt;, then &lt;code&gt;layer(x, attn_metadata)&lt;/code&gt;, then&lt;br&gt;
&lt;code&gt;attention(x, attn_metadata)&lt;/code&gt;. In the OPT model definition, however, the&lt;br&gt;
forward signature contains &lt;code&gt;input_ids&lt;/code&gt; and &lt;code&gt;positions&lt;/code&gt; but no&lt;br&gt;
&lt;code&gt;attn_metadata&lt;/code&gt;. The metadata enters through a different route.&lt;/p&gt;
&lt;h3&gt;
  
  
  Attention Metadata Lives in &lt;code&gt;forward_context&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The answer is in &lt;code&gt;execute_model&lt;/code&gt;. The model's forward is wrapped in a context manager and runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Conceptual skeleton corresponding to execute_model in v1/worker
&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;set_forward_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attn_metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vllm_config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...):&lt;/span&gt;
    &lt;span class="n"&gt;hidden_states&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_ids&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;input_ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;positions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;set_forward_context&lt;/code&gt; (&lt;code&gt;vllm/forward_context.py:250&lt;/code&gt;) installs&lt;br&gt;
&lt;code&gt;attn_metadata&lt;/code&gt;, &lt;code&gt;slot_mapping&lt;/code&gt;, and related state in a scoped&lt;br&gt;
&lt;code&gt;forward_context&lt;/code&gt; while the model executes, then restores the previous context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# vllm/forward_context.py:250 (structural excerpt)
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_forward_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attn_metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vllm_config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="n"&gt;slot_mapping&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...):&lt;/span&gt;
    &lt;span class="n"&gt;forward_context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_forward_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;attn_metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vllm_config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...,&lt;/span&gt; &lt;span class="n"&gt;slot_mapping&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;override_forward_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forward_context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;   &lt;span class="c1"&gt;# ← install the context globally
&lt;/span&gt;            &lt;span class="k"&gt;yield&lt;/span&gt;                                          &lt;span class="c1"&gt;# ← model forward executes inside this scope
&lt;/span&gt;    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;                                                &lt;span class="c1"&gt;# restore the previous context afterwards
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The metadata therefore travels through scoped context rather than through every&lt;br&gt;
model-layer signature. The ordinary forward path can keep tensor arguments such&lt;br&gt;
as &lt;code&gt;input_ids&lt;/code&gt; and &lt;code&gt;positions&lt;/code&gt; while attention retrieves its runtime metadata&lt;br&gt;
separately.&lt;/p&gt;

&lt;p&gt;Deep inside the model, the attention layer retrieves that context. Consider&lt;br&gt;
&lt;code&gt;unified_attention_with_output&lt;/code&gt;&lt;br&gt;
(&lt;code&gt;model_executor/layers/attention/attention.py:734&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# model_executor/layers/attention/attention.py:734 (excerpt)
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;unified_attention_with_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;layer_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...):&lt;/span&gt;
    &lt;span class="n"&gt;layer_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_resolve_layer_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layer_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# ↓ attn_metadata is retrieved rather than passed as an argument
&lt;/span&gt;    &lt;span class="n"&gt;attn_metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kv_cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_attention_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layer_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;impl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forward&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kv_cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attn_metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function receives &lt;code&gt;layer_name&lt;/code&gt;, not &lt;code&gt;attn_metadata&lt;/code&gt; directly.&lt;br&gt;
&lt;code&gt;get_attention_context(layer_name)&lt;/code&gt; resolves the layer and retrieves the&lt;br&gt;
runtime state from &lt;code&gt;forward_context&lt;/code&gt; (&lt;code&gt;attention.py:648&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# model_executor/layers/attention/attention.py:648 (excerpt)
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_attention_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layer_name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;forward_context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_forward_context&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;          &lt;span class="c1"&gt;# ← retrieve the global context
&lt;/span&gt;    &lt;span class="n"&gt;attn_metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;forward_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attn_metadata&lt;/span&gt;    &lt;span class="c1"&gt;# retrieve this layer's metadata by layer_name
&lt;/span&gt;    &lt;span class="n"&gt;attn_layer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;forward_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;no_compile_layers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;layer_name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;kv_cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;attn_layer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kv_cache&lt;/span&gt;
    &lt;span class="n"&gt;slot_mapping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;forward_context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slot_mapping&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;layer_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;attn_metadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attn_layer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kv_cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slot_mapping&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting path is: &lt;code&gt;execute_model&lt;/code&gt; installs the context, the model runs with&lt;br&gt;
its normal tensor inputs, and each attention operator uses &lt;code&gt;layer_name&lt;/code&gt; to&lt;br&gt;
retrieve &lt;code&gt;attn_metadata&lt;/code&gt;, &lt;code&gt;kv_cache&lt;/code&gt;, and &lt;code&gt;slot_mapping&lt;/code&gt; before dispatching to&lt;br&gt;
&lt;code&gt;self.impl.forward&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftetcybeucxl4jvpcseo7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftetcybeucxl4jvpcseo7.png" alt="fig6-forward-context" width="800" height="569"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Figure: &lt;code&gt;execute_model&lt;/code&gt; installs &lt;code&gt;attn_metadata&lt;/code&gt; in &lt;code&gt;forward_context&lt;/code&gt;; each&lt;br&gt;
attention layer retrieves its state by &lt;code&gt;layer_name&lt;/code&gt; before calling the backend.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Why This Indirection Helps &lt;code&gt;torch.compile&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Passing data through context is less explicit than ordinary arguments, but it&lt;br&gt;
serves the compiled execution path. &lt;code&gt;torch.compile&lt;/code&gt; benefits from stable graph&lt;br&gt;
inputs and shapes; threading a changing Python metadata object through every&lt;br&gt;
layer can introduce graph breaks, guards, or recompilation. Keeping that object&lt;br&gt;
outside the model signature lets the compiled graph consume the tensor state it&lt;br&gt;
needs through a controlled side channel. The exact compilation behavior still&lt;br&gt;
depends on the selected backend and configuration, so this should be read as&lt;br&gt;
the design goal rather than a guarantee that recompilation can never occur.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Dummy Dependency That Preserves Ordering
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;unified_attention_with_output&lt;/code&gt; also accepts an apparently unused parameter,&lt;br&gt;
&lt;code&gt;kv_cache_dummy_dep&lt;/code&gt;. It is intentionally present and is not discarded in the&lt;br&gt;
function body:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# attention.py:744
# kv_cache_dummy_dep is not used but accepting it creates a data dependency
# that ensures torch.compile preserves ordering between KV cache update and
# attention forward.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;KV-cache update must precede the attention read, but mutation alone may not&lt;br&gt;
expose that ordering to the compiler. The dummy tensor makes the dependency&lt;br&gt;
explicit in the graph: the update produces a value consumed by attention, which&lt;br&gt;
prevents the two operations from being reordered across that edge.&lt;/p&gt;
&lt;h3&gt;
  
  
  What This Part Established
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Step-specific &lt;code&gt;attn_metadata&lt;/code&gt; stays out of the ordinary model-forward
signature and is installed in a scoped &lt;code&gt;forward_context&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Each attention layer retrieves its metadata, KV cache, and &lt;code&gt;slot_mapping&lt;/code&gt;
through &lt;code&gt;layer_name&lt;/code&gt; before entering the backend.&lt;/li&gt;
&lt;li&gt;This boundary is designed to make the model graph friendlier to
&lt;code&gt;torch.compile&lt;/code&gt; while still exposing dynamic runtime state.&lt;/li&gt;
&lt;li&gt;A dummy tensor dependency preserves the required update-before-read ordering
for the KV cache.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The next part follows &lt;code&gt;slot_mapping&lt;/code&gt;, &lt;code&gt;block_table&lt;/code&gt;, and the KV cache into the&lt;br&gt;
write and read paths.&lt;/p&gt;
&lt;h2&gt;
  
  
  Part 5: Writing and Reading the Paged KV Cache
&lt;/h2&gt;

&lt;p&gt;PagedAttention is closely associated with vLLM, but the virtual-memory analogy&lt;br&gt;
alone does not identify the actual address structures. This part follows the&lt;br&gt;
write table and the read table in code.&lt;/p&gt;

&lt;p&gt;The KV cache is a pool of fixed-size blocks rather than one contiguous region&lt;br&gt;
per request. Each block stores K and V for &lt;code&gt;block_size&lt;/code&gt; tokens (use&lt;br&gt;
&lt;code&gt;block_size=16&lt;/code&gt; as a concrete example). A request's logical token sequence may&lt;br&gt;
span physically scattered blocks. This paging model removes the need to reserve&lt;br&gt;
one large contiguous allocation for every request and substantially reduces&lt;br&gt;
fragmentation from variable request lengths.&lt;/p&gt;

&lt;p&gt;Here, &lt;strong&gt;block&lt;/strong&gt; means a KV-cache allocation unit. Part 2 will also use CUDA&lt;br&gt;
thread blocks and matrix tiles; those are separate execution and computation&lt;br&gt;
concepts.&lt;/p&gt;

&lt;p&gt;The two paths use different address structures: newly computed K/V is written&lt;br&gt;
by token, while attention reads a request's history by logical cache block.&lt;/p&gt;
&lt;h3&gt;
  
  
  Write Path: One Token, One &lt;code&gt;slot_mapping&lt;/code&gt; Entry
&lt;/h3&gt;

&lt;p&gt;The previous part showed the attention layer retrieving &lt;code&gt;slot_mapping&lt;/code&gt; from&lt;br&gt;
&lt;code&gt;forward_context&lt;/code&gt;. We can now follow it into the write kernel.&lt;/p&gt;

&lt;p&gt;Each cache block contains &lt;code&gt;block_size&lt;/code&gt; token slots. Flattening physical block&lt;br&gt;
and in-block coordinates gives a cache-wide slot index:&lt;br&gt;
&lt;code&gt;physical_block_id × block_size + in_block_offset&lt;/code&gt;. &lt;code&gt;slot_mapping&lt;/code&gt;, with shape&lt;br&gt;
&lt;code&gt;[num_tokens]&lt;/code&gt;, gives that destination for every token processed in the step.&lt;/p&gt;

&lt;p&gt;Under the FlashAttention and FlashInfer paths, KV writing finally falls to &lt;code&gt;reshape_and_cache_flash&lt;/code&gt; (bound as &lt;code&gt;ops.reshape_and_cache_flash&lt;/code&gt; in &lt;code&gt;vllm/v1/attention/backends/fa_utils.py&lt;/code&gt;, defined in &lt;code&gt;vllm/_custom_ops.py:2744&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# vllm/_custom_ops.py:2744
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reshape_and_cache_flash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tensor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;# K produced in this step
&lt;/span&gt;    &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tensor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;# V produced in this step
&lt;/span&gt;    &lt;span class="n"&gt;key_cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tensor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;# K block pool
&lt;/span&gt;    &lt;span class="n"&gt;value_cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tensor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# V block pool
&lt;/span&gt;    &lt;span class="n"&gt;slot_mapping&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tensor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# ← destination slot for each token
&lt;/span&gt;    &lt;span class="n"&gt;kv_cache_dtype&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;k_scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tensor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;v_scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tensor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: vLLM provides both &lt;code&gt;reshape_and_cache&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;reshape_and_cache_flash&lt;/code&gt;, paired with different KV-cache layouts and read&lt;br&gt;
kernels. In V1, KV update is represented separately from the attention read;&lt;br&gt;
the dependency described in Part 4 preserves their required order under&lt;br&gt;
compilation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For token &lt;code&gt;i&lt;/code&gt;, the kernel reads the corresponding K and V vectors and writes&lt;br&gt;
them to &lt;code&gt;slot_mapping[i]&lt;/code&gt;. Decode commonly contributes one new position per&lt;br&gt;
request; prefill contributes a chunk. The mapping is a per-token destination&lt;br&gt;
table, not the data itself.&lt;/p&gt;
&lt;h3&gt;
  
  
  Read Path: One &lt;code&gt;block_table&lt;/code&gt; per Request
&lt;/h3&gt;

&lt;p&gt;Reading introduces logical-to-physical indirection. A query attends to visible&lt;br&gt;
historical K/V positions that may be scattered across physical cache blocks;&lt;br&gt;
&lt;code&gt;block_table&lt;/code&gt; locates those blocks.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;block_table&lt;/code&gt; is part of &lt;code&gt;attn_metadata&lt;/code&gt;. For each request, entry &lt;code&gt;j&lt;/code&gt; stores the&lt;br&gt;
physical cache block backing logical block &lt;code&gt;j&lt;/code&gt;. With &lt;code&gt;block_size=16&lt;/code&gt;, logical&lt;br&gt;
tokens 0–15 use the physical block named by &lt;code&gt;block_table[0]&lt;/code&gt;, tokens 16–31 use&lt;br&gt;
&lt;code&gt;block_table[1]&lt;/code&gt;, and so on. This logical-to-physical array is the concrete&lt;br&gt;
structure behind the page-table analogy.&lt;/p&gt;

&lt;p&gt;On the GPU, the actual reading occurs in &lt;code&gt;forward&lt;/code&gt; in the FlashAttention backend, which hands &lt;code&gt;block_table&lt;/code&gt; directly to the flash kernel (&lt;code&gt;vllm/v1/attention/backends/flash_attn.py:796&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# vllm/v1/attention/backends/flash_attn.py:758 (excerpt)
&lt;/span&gt;&lt;span class="n"&gt;cu_seqlens_q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;attn_metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query_start_loc&lt;/span&gt;
&lt;span class="n"&gt;seqused_k&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;attn_metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seq_lens&lt;/span&gt;
&lt;span class="n"&gt;block_table&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;attn_metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;block_table&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="nf"&gt;flash_attn_varlen_func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;num_actual_tokens&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key_cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;value_cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;num_actual_tokens&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;cu_seqlens_q&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cu_seqlens_q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# request boundaries in the flattened sequence (query_start_loc)
&lt;/span&gt;    &lt;span class="n"&gt;seqused_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;seqused_k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;# history length of each request (seq_lens)
&lt;/span&gt;    &lt;span class="n"&gt;block_table&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;block_table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;# ← page table: logical block → physical block
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The GPU kernel follows &lt;code&gt;block_table&lt;/code&gt; while loading the K/V history used by the&lt;br&gt;
current query. The equivalent address translation is easier to see in the CPU&lt;br&gt;
backend. The following excerpt is simplified from&lt;br&gt;
&lt;code&gt;csrc/cpu/cpu_attn_impl.hpp&lt;/code&gt;; the FlashAttention backend performs the same kind&lt;br&gt;
of logical-to-physical lookup within its own tiled implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified from the attention read loop in csrc/cpu/cpu_attn_impl.hpp&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block_idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start_block_idx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;block_idx&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;end_block_idx&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="n"&gt;block_idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;physical_block_idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;block_table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;block_idx&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;        &lt;span class="c1"&gt;// ← logical block number → physical block number&lt;/span&gt;
    &lt;span class="n"&gt;kv_cache_t&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;k_cache_block_ptr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="n"&gt;k_head_cache_ptr&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;physical_block_idx&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;kv_cache_num_blocks_stride&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// Compute Q@K using K from this block ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;physical_block_idx = block_table[block_idx]&lt;/code&gt; is the essential page-table&lt;br&gt;
lookup: traverse logical blocks in sequence, resolve each physical block, then&lt;br&gt;
load K and V from that location.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why the Write and Read Paths Use Different Addressing
&lt;/h3&gt;

&lt;p&gt;In PagedAttention, two sets of addressing with different granularity are used for writing and reading.&lt;/p&gt;

&lt;p&gt;Writes use &lt;code&gt;slot_mapping&lt;/code&gt; at token granularity: each newly computed token has&lt;br&gt;
one destination slot in the cache.&lt;/p&gt;

&lt;p&gt;Reads use &lt;code&gt;block_table&lt;/code&gt; at block granularity. Each request owns a sequence of&lt;br&gt;
logical blocks whose entries identify the physical blocks containing its KV&lt;br&gt;
history.&lt;/p&gt;

&lt;p&gt;Keeping the two addressing schemes separate is what makes paging practical.&lt;br&gt;
In the OS analogy, &lt;code&gt;block_table&lt;/code&gt; is the page table, the block pool is physical&lt;br&gt;
memory, and &lt;code&gt;slot_mapping&lt;/code&gt; gives the physical destination for a write. It is&lt;br&gt;
not a literal implementation of virtual memory, but the indirection is closely&lt;br&gt;
related.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fep8es8jeqwk8a121ndle.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fep8es8jeqwk8a121ndle.png" alt="fig7-paged-attention" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Figure: &lt;code&gt;slot_mapping&lt;/code&gt; sends each newly computed token to one physical cache&lt;br&gt;
slot, while &lt;code&gt;block_table&lt;/code&gt; maps a request's logical history to a sequence of&lt;br&gt;
physical blocks for reading.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  What This Part Established
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The KV cache is a pool of fixed-size blocks. A request may occupy physically
non-contiguous blocks. Paging substantially reduces external fragmentation,
although the final partially filled block can still waste capacity.&lt;/li&gt;
&lt;li&gt;On write, &lt;code&gt;reshape_and_cache_flash&lt;/code&gt; uses &lt;code&gt;slot_mapping: [num_tokens]&lt;/code&gt; to place
each newly computed K/V pair in its destination slot.&lt;/li&gt;
&lt;li&gt;Read: &lt;code&gt;block_table&lt;/code&gt; stores the physical block numbers for each request. The
GPU attention path uses it to resolve &lt;code&gt;physical = block_table[logical]&lt;/code&gt; while
gathering the request's KV history.&lt;/li&gt;
&lt;li&gt;Writes use token-slot addresses; reads use logical-to-physical block mapping.
The mechanism resembles virtual-memory paging, but it is a specialized KV
allocator rather than a full virtual-memory implementation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After attention consumes the updated cache, the model produces hidden states.&lt;br&gt;
The final part follows selected hidden states through logits and sampling, then&lt;br&gt;
shows how completed requests release their blocks.&lt;/p&gt;
&lt;h2&gt;
  
  
  Part 6: From Logits to Tokens and Block Reclamation
&lt;/h2&gt;

&lt;p&gt;The model forward returns a packed set of hidden states. Only selected positions&lt;br&gt;
need logits for next-token sampling.&lt;/p&gt;
&lt;h3&gt;
  
  
  Compute Logits Only for the Required Positions
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;logits_indices&lt;/code&gt; prepared in Part 3 now determines which hidden states are&lt;br&gt;
projected into logits.&lt;/p&gt;

&lt;p&gt;The model computes a hidden state for every scheduled token, but ordinary&lt;br&gt;
next-token generation needs logits only at selected positions—typically the&lt;br&gt;
last scheduled position for each request. For a four-token prefill chunk, the&lt;br&gt;
first three hidden states therefore need not pass through &lt;code&gt;lm_head&lt;/code&gt; for that&lt;br&gt;
sampling step.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;logits_indices&lt;/code&gt; selects those hidden states before the vocabulary projection.&lt;br&gt;
Because &lt;code&gt;lm_head&lt;/code&gt; projects into a vocabulary with tens or hundreds of thousands&lt;br&gt;
of entries, avoiding unused positions saves substantial matrix-multiplication&lt;br&gt;
work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hidden_states[num_tokens]  --index_select(logits_indices)--&amp;gt;  keep only the required final position for each request
                           --lm_head--&amp;gt;  logits[num_reqs, vocab_size]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sampling: From Logits to Token IDs
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Sampler&lt;/code&gt; converts each &lt;code&gt;vocab_size&lt;/code&gt;-dimensional logits vector into a token ID&lt;br&gt;
according to temperature, top-p, top-k, or greedy selection.&lt;/p&gt;

&lt;p&gt;The sampling results are packed into &lt;code&gt;ModelRunnerOutput&lt;/code&gt;—whose central field is&lt;br&gt;
&lt;code&gt;sampled_token_ids&lt;/code&gt;—and sent back to &lt;code&gt;EngineCore&lt;/code&gt; across the IPC boundary. This&lt;br&gt;
completes the worker-side work for the scheduler step.&lt;/p&gt;
&lt;h3&gt;
  
  
  Return to the Scheduler: Append, Finish, and Recycle
&lt;/h3&gt;

&lt;p&gt;When &lt;code&gt;sampled_token_ids&lt;/code&gt; returns to EngineCore,&lt;br&gt;
&lt;code&gt;scheduler.update_from_output&lt;/code&gt; (&lt;code&gt;v1/core/sched/scheduler.py:1283&lt;/code&gt;) performs the&lt;br&gt;
third stage of &lt;code&gt;EngineCore.step()&lt;/code&gt;: applying model output to request state.&lt;/p&gt;

&lt;p&gt;First, take out the new token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# scheduler.py:1363 (excerpt)
&lt;/span&gt;&lt;span class="n"&gt;req_index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model_runner_output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;req_id_to_index&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;req_id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;generated_token_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sampled_token_ids&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;req_index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sampled_token_ids&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second, &lt;code&gt;_update_request_with_output&lt;/code&gt; appends the new token and evaluates stop&lt;br&gt;
conditions such as EOS, &lt;code&gt;max_tokens&lt;/code&gt;, and configured stop strings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# scheduler.py:1407 (excerpt)
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;new_token_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;new_token_ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stopped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_update_request_with_output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_token_ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Third, when a request stops, its KV blocks are returned to the pool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# scheduler.py:1474 (excerpt)
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;stopped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;finish_reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_finished_reason&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;finished&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_handle_stopped_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;finished&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;kv_transfer_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_free_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# ← release its KV blocks
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;stopped_running_reqs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the cycle ends, remove these stopped requests from the &lt;code&gt;running&lt;/code&gt; queue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# scheduler.py:1528 (excerpt)
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;stopped_running_reqs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;remove_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stopped_running_reqs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This closes the continuous-batching loop. When a request finishes,&lt;br&gt;
&lt;code&gt;_free_request&lt;/code&gt; returns its blocks to the pool and removes one entry from&lt;br&gt;
&lt;code&gt;running&lt;/code&gt;. On the next step, &lt;code&gt;schedule()&lt;/code&gt; can spend those blocks on requests in&lt;br&gt;
&lt;code&gt;waiting&lt;/code&gt;. Resource reclamation is therefore part of admission control, not an&lt;br&gt;
unrelated cleanup detail.&lt;/p&gt;

&lt;p&gt;An unfinished request remains eligible in &lt;code&gt;running&lt;/code&gt;; a later scheduling step can&lt;br&gt;
advance it again.&lt;/p&gt;

&lt;p&gt;Finally, EngineCore packages the results as &lt;code&gt;EngineCoreOutputs&lt;/code&gt; and sends them&lt;br&gt;
to the main process over IPC. &lt;code&gt;LLMEngine.step()&lt;/code&gt; consumes them through&lt;br&gt;
&lt;code&gt;get_output()&lt;/code&gt;, detokenizes token IDs, assembles &lt;code&gt;RequestOutput&lt;/code&gt;, and returns it&lt;br&gt;
to the caller. That completes the path from an API request to generated text.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbaf6s8hrjwedhxxnczkc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbaf6s8hrjwedhxxnczkc.png" alt="fig8-output-sampling" width="799" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Figure: &lt;code&gt;logits_indices&lt;/code&gt; selects the required hidden states, &lt;code&gt;lm_head&lt;/code&gt;&lt;br&gt;
produces logits, and sampling yields new token IDs. Finished requests release&lt;br&gt;
their KV blocks; unfinished requests remain active.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  What This Part Established
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;logits_indices&lt;/code&gt; selects the positions that require vocabulary projection,
avoiding &lt;code&gt;lm_head&lt;/code&gt; work for unused intermediate positions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Sampler&lt;/code&gt; produces token IDs according to each request's sampling parameters
and returns them in &lt;code&gt;ModelRunnerOutput&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;update_from_output&lt;/code&gt; appends new tokens, evaluates stopping conditions, and
calls &lt;code&gt;_free_request&lt;/code&gt; for completed requests.&lt;/li&gt;
&lt;li&gt;Reclaimed KV capacity becomes available to later scheduler steps.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What to Carry Forward
&lt;/h2&gt;

&lt;p&gt;The six parts reduce vLLM's request path to a scheduler, a paged memory manager,&lt;br&gt;
and a GPU execution pipeline. Four design choices are especially reusable:&lt;/p&gt;

&lt;p&gt;First, the scheduler expresses both prefill and decode as per-request token&lt;br&gt;
progress. The execution layer then packs those scheduled tokens into one&lt;br&gt;
dimension. Chunked prefill and continuous batching build on the same contract&lt;br&gt;
instead of requiring separate batch formats.&lt;/p&gt;

&lt;p&gt;Second, V1 preempts by recomputing rather than swapping. Under KV-cache&lt;br&gt;
pressure, it frees the victim's blocks, resets &lt;code&gt;num_computed_tokens&lt;/code&gt;, and&lt;br&gt;
requeues the request for prefill instead of moving KV state to CPU memory and&lt;br&gt;
back over PCIe. This trades additional computation for less host-device data&lt;br&gt;
movement.&lt;/p&gt;

&lt;p&gt;Third, &lt;code&gt;forward_context&lt;/code&gt; keeps step-specific &lt;code&gt;attn_metadata&lt;/code&gt; out of the forward&lt;br&gt;
signature so &lt;code&gt;torch.compile&lt;/code&gt; can reuse a stable graph. A dummy tensor dependency&lt;br&gt;
then constrains operator ordering. The indirection is less obvious to read, but&lt;br&gt;
it serves compilation and execution correctness.&lt;/p&gt;

&lt;p&gt;Fourth, writes are addressed per token while reads are resolved per block.&lt;br&gt;
&lt;code&gt;slot_mapping&lt;/code&gt; selects each token's write slot; &lt;code&gt;block_table&lt;/code&gt; maps a request's&lt;br&gt;
logical KV blocks to physical blocks. At its core, the page-table lookup is&lt;br&gt;
&lt;code&gt;physical = block_table[logical]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The same discipline appears throughout the path: identify whether the scarce&lt;br&gt;
resource is compute, bandwidth, KV capacity, TTFT, or inter-token latency, then&lt;br&gt;
make the trade-off explicit. vLLM's throughput does not come from one isolated&lt;br&gt;
trick. It comes from small mechanisms that compose: a budget clamp expressed by&lt;br&gt;
&lt;code&gt;min&lt;/code&gt;, one level of &lt;code&gt;block_table&lt;/code&gt; indirection, and a deliberate&lt;br&gt;
&lt;code&gt;num_computed_tokens = 0&lt;/code&gt; on preemption.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this article doesn’t cover
&lt;/h2&gt;

&lt;p&gt;To keep the path traceable, this article stops at the Python/CUDA boundary. It&lt;br&gt;
does not cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CUDA and Triton kernel internals. We stop at the attention-backend call rather
than tracing tiling and online softmax.&lt;/li&gt;
&lt;li&gt;Block-allocation details inside &lt;code&gt;kv_cache_manager&lt;/code&gt;, including reference
counting and prefix-cache hash reuse. The article treats it only as a block
allocator.&lt;/li&gt;
&lt;li&gt;Multi-GPU parallelism: communication and partitioning under tensor (TP),
pipeline (PP), and data parallelism (DP).&lt;/li&gt;
&lt;li&gt;Advanced features such as speculative decoding, multimodality, and structured
output. Their state appears in &lt;code&gt;SchedulerOutput&lt;/code&gt;, but they are outside this
request path.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;This walkthrough targets the vLLM 0.22.0 V1 architecture. Source locations are&lt;br&gt;
included so readers can compare the excerpts with their installed version. The&lt;br&gt;
editable Excalidraw sources for Figures 2–8 are published with this site.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>vllm</category>
      <category>llm</category>
      <category>inference</category>
      <category>python</category>
    </item>
  </channel>
</rss>
