Back to columns
中文

QKV:
Transformer
Computation Atlas

Follow Q, K and V through the computations and data flow of a Transformer, from processing input to generating a response. KV caching reveals which results have already been computed, how they are stored and reused, and which steps can run in parallel or must wait. Diagrams, worked examples and reasoning exercises explain the mechanisms, benefits and costs of caching and inference optimizations.

October 2026 edition36 questions · 3 reasoning exercises · 39 mechanism diagramsCore references: 2026-09-30Original prepared: 2026-10-15API and code verification dates:see individual sources
FIG 04 · Cover preview

The practical benefits of KV caching

Contributing factors and outcome measures

Seven groups of factors

  • Agent architecture
  • Input and model configuration
  • Model architecture
  • Representation and compression
  • Inference framework
  • Cache storage and transfer
  • Hardware and workload
Evaluate benefits through actual outcomes

Eight outcomes to measure

  • Stored size and peak memory
  • Cache hits on repeated input
  • Restore time
  • Time to first token
  • Generation speed
  • Throughput under concurrent use
  • Total cost
  • Answer and task correctness

Open the full FIG 04 and explanation

Contents · 9 chapters · 36 questions
Start with the overview

Which layers of the system determine the practical benefits of KV caching?

A KV cache stores intermediate states already computed by the model so later computations can reuse them. Here, practical benefits include storing less data, avoiding repeated computation, reducing waiting and lowering the cost of a whole task. They may also let the same hardware handle longer inputs or more concurrent requests. All these benefits depend on the task still being completed correctly.

First distinguish model layers, attention heads and token positions

Consider a typical Transformer language model. The model is a sequence of computational modules, each called a model layer or decoder block. The first layer receives the initial numerical representations of tokens; later layers receive the previous layer's results. A layer contains attention, a feed-forward network and other operations. The attention heads are inside the attention component of a layer.

FIG 01Multiple heads within a layer; results pass from one layer to the next

The user supplied 5 tokens. We track only token 5, showing the first two layers with 4 query heads per layer.

From this point onward, magenta dashed boxes mark work that can run in parallel. Compact labels abbreviate “Parallel” as “Par.” Once their inputs are ready, boxes with the same number within this diagram can run concurrently. Operations inside each box still follow the arrows.

Numbers match boxes only within the current diagram. They are not token positions, layer numbers, head numbers or execution order. Arrows show dependencies. Only selected operations are marked; an unmarked operation is not necessarily sequential.

Token 5Convert to initial numerical values

Model layer 1

Attention: 4 query heads
Parallel ①
H 1Its own QRead K/V
Parallel ①
H 2Its own QRead K/V
Parallel ①
H 3Its own QRead K/V
Parallel ①
H 4Its own QRead K/V
Concatenate the heads' retrieved information
Apply the output transformation
Feed-forward and subsequent operationsContinue processing token 5's internal values
Token 5's result from layer 1Pass it to layer 2

Model layer 2

Attention: this layer's own 4 query heads
Parallel ②
H 1Its own QRead K/V
Parallel ②
H 2Its own QRead K/V
Parallel ②
H 3Its own QRead K/V
Parallel ②
H 4Its own QRead K/V
Concatenate the heads' retrieved information
Apply the output transformation
Feed-forward and subsequent operationsContinue processing token 5's internal values
Token 5's result from layer 2Pass it to subsequent model layers
This diagram isolates the relationship between layers and heads. A layer also includes normalization, which adjusts numerical scales, and residual connections, which add an input back to a computation's result; those are not drawn separately. After the final model layer, an output layer scores candidate tokens so token 6 can be selected. [11]

Later we distinguish two forms of sharing: heads within one layer sharing K/V, as in GQA/MQA, and different model layers sharing K/V, as in YOCO. Shared historical records do not prevent heads or layers from producing their own queries and outputs. See the comparison of MHA, GQA, MQA and YOCO. [7] [11]

01
Xiao Wang · asks

The previous diagram shows a feed-forward network after attention, continuing to process the current token's internal values. I have heard that MoE sends a token to a few selected experts.

How do these experts relate to attention heads? If two experts are selected, do we get two additional sets of historical K/V?

Da Wang · explains

First locate the operation. We are considering the common MoE design that replaces the feed-forward component with an expert network. Attention first retrieves information from the preceding context; the feed-forward component then uses learned parameters to further process the current token's internal values.

A mixture of experts (MoE) provides several feed-forward networks with different parameters, each called an expert. A small scoring and selection module, the router, examines the current token's internal representation, selects a few experts, runs them and merges their outputs. This increases the total parameters available to the model while executing only a few experts per token. Experts and attention heads occupy different parts of a model layer. [62]

FIG 02Heads read history; experts process the result; K/V stays in attention

Given 5 input tokens, track only token 5 in layer 1. The example has 4 query heads, each storing its own K/V, and 8 feed-forward experts, of which 2 are selected per token.

We reuse the magenta dashed boxes from FIG 01: matching numbers mark work that can run in parallel. Group ② must wait for group ①'s results. Unmarked work is not necessarily sequential.

Attention within this layer

On narrow screens, scroll the diagram sideways. The explanation follows below.

Four query heads read their own rows of historical K/V, then merge their outputsTrack token 5 in layer 1. The four rows on the right are the caches for heads 1–4, with K/V at input positions 1–5. On the left, each head uses token 5's Q to read its corresponding row. The four results merge into one internal representation, which goes to the feed-forward component.All process token 5This layer's K/V cacheColumns: input positions12345Par. ①H1 · its own QMatch and retrieveK/VK/VK/VK/VK/VPar. ①H2 · its own QMatch and retrieveK/VK/VK/VK/VK/VPar. ①H3 · its own QMatch and retrieveK/VK/VK/VK/VK/VPar. ①H4 · its own QMatch and retrieveK/VK/VK/VK/VK/VReadConcatenate four outputs, then transform
Still inside layer 1: continue processing token 5

The MoE feed-forward component

Colored experts are selected for this token; gray experts are not.

The router selects experts 2 and 7; their outputs are combined with weights 0.7 and 0.3After attention, token 5's internal representation goes to the router and selected experts. Only experts 2 and 7 of the eight execute for this token. Their outputs are multiplied by 0.7 and 0.3 and added element by element. After this layer finishes, the result enters layer 2 and influences its newly generated Q/K/V.After attention: one representationRouterScore → select 2 and 7Same input to two expertsExpert 1Expert 2Par. ②Expert 3Expert 4Expert 5Expert 6Expert 7Par. ②Expert 8× 0.7× 0.3+Scale each output, then add element by elementFinish layer → layer 2 → new Q/K/V
Four heads, eight experts and weights 0.7/0.3 are teaching choices; small colored bars represent internal values. Normalization and residual connections are not drawn individually. This example uses ordinary multi-head attention with one K/V set per head. GQA/MQA, discussed later, can share caches across heads; expert selection is a separate setting. [62]

Real models also configure these counts separately. Qwen3-30B-A3B has 128 experts and activates 8 per token, but its attention has 32 query heads and 4 KV heads. Historical state in each layer is therefore stored for 4 KV heads, rather than 8 or 128 experts. [63]

Xiao Wang Where do expert parameters come from? Who teaches the router? Does training data tell it to select experts 2 and 7?

Da Wang: Consider the common setup in which all components train together. Experts, router, attention and output layer start with initial parameters and jointly learn to predict the next token in training text. The data supplies the actual next token, usually not a “correct expert ID.”

Continue with the first five positions. The training text tells us token 6 is A, but the model may read only positions 1–5 when predicting it. Suppose a toy vocabulary contains A, B and C, with predicted probabilities 20%, 50% and 30%. Training directly evaluates the probability assigned to the true A: lower probability incurs a larger penalty. This penalty is the prediction loss. There is no need to sample a token first and mark it right or wrong.

FIG 03The true answer trains the whole model; expert IDs need no manual labels

We expand one MoE operation at position 5. Solid green: forward predictionDashed red: training feedback and parameter updates The actual token 6 scores the prediction; it is not inserted into its preceding context.

On narrow screens, scroll the diagram sideways. The explanation follows below.

Score the prediction against the true next token, then compute gradients and update parametersPositions 1–5 predict position 6, whose true token is A. One MoE component is expanded: the router selects experts 2 and 7, their outputs merge and subsequent layers produce A 20%, B 50%, C 30%. Training computes the loss against A, propagates gradients backward along this computation and updates parameters. No correct expert ID is supplied.Five positions used to predictTrue token 6A12345Position 5's values after attentionRouterSelect 2 and 7Expert 2Expert 7Output × 0.7Output × 0.3Merge → later layers → output layerPredicted next-token probabilitiesToy vocabulary: A, B, CA20%B50%C30%Answer for scoringCompare with true ACompute prediction lossBackpropagate along this computationFind parameter changes that reduce lossBatch gradients → update parametersExperts, router, attention, output layer, etc.Process subsequent training examples
The three candidates, 20%/50%/30% and experts 2 and 7 are teaching choices. The diagram shows one path through joint training; real training processes many positions and examples. Red arrows show gradients through the computed path, not a forward prediction peeking at the answer. [65]

Xiao Wang Without a “correct expert” label, how does the router know what to change? Isn't selecting an expert a discrete choice?

Da Wang: Distinguish the selected IDs from the mixing weights. IDs 2 and 7 determine which branches execute; 0.7 and 0.3 participate in numerical computation. If slightly increasing expert 2's mixing weight would reduce the downstream prediction loss, backpropagation can supply that feedback; the opposite effect gives the opposite feedback.

The router computes 0.7 and 0.3 from this input. Training changes the router parameters that produce scores and weights, while also training the selected experts to process inputs. In the Top-k routing used here, choosing the highest-scoring IDs itself is not directly differentiated. Feedback flows through the selected continuous weights and other differentiable operations. Later changes to scores may also change which experts are selected. [65]

Unselected experts are not individually trial-run at this position. The program does not directly observe what replacing an expert would have done. Only experts 2 and 7 receive task gradients through the expert branches shown here. Other experts may still be selected and trained at other positions or on other examples.

Xiao Wang Could the router keep choosing the same experts while the others never learn?

Da Wang: Yes. A batch with 100 positions and 2 experts per position has 200 assignments. If all choose 2 and 7, those two each receive 100 assignments and the other six receive none. Selected experts keep learning while idle ones get fewer opportunities. If experts reside on different devices, some devices may also be busy while others idle. This is expert load imbalance.

  • Penalize overly concentrated assignments. For example, Qwen3-MoE's public training path can add an auxiliary load-balancing loss to the prediction loss, discouraging persistent concentration on a few experts. It encourages balanced allocation rather than assigning a subject-specific expert to each input. [65]
  • Adjust selection scores according to utilization. During DeepSeek-V3 training, routing biases decrease for overloaded experts and increase for underused ones. These biases influence selection, while output mixing still uses the original affinity scores. That model also retains a small sequence-level balancing loss. [66]

As experts receive different data, their parameters develop different tendencies. However, “expert 2 handles math and expert 7 handles code” is not a universal predefined division of work. Specializations may overlap or correlate with lexical, syntactic or contextual features. [67]

How does MoE affect caching benefits?

  • A prefix hit can also avoid recomputing experts for old tokens. The historical K/V needed by later layers is already available, so the computation that produced those states usually need not run again. New tokens still require their own attention and expert computation.
  • Selecting few experts does not mean storing only those experts' parameters. Expert parameters and request-specific KV state are different data. More expert weights occupying GPU memory can leave less space for KV.
  • Subtract distribution and aggregation costs from the computation saved. If experts reside on different GPUs, token representations must be transferred and outputs combined. Actual speedup depends jointly on computation, storage and communication costs. [64]
FIG 04KV caching benefits: contributing factors and outcome measures

First identify what changes the costs; then measure what the task actually gains.

Seven groups of factors jointly affect cache use

Agent architecture

What information reaches the model, and for how long?

Tool results, tool loading, compaction and division of work

Input and model configuration

Are old states compatible with this computation?

Input prefix, model weights, positions and image processing

Model architecture

Which states must be retained? Which history is read at each step?

Sharing across heads or layers, local windows and sparse reads

Representation and compression

How many bytes per state? What does reconstruction cost?

Quantization, scales and reconstructed temporary data

Inference framework

How are data located, computations batched and requests scheduled?

Paged addressing, tiled computation and request scheduling

Cache storage and transfer

Where is state stored, shared, retrieved and reclaimed?

GPU memory, host RAM, SSD and cross-machine transfer

Hardware and workload

How much can hardware store and how fast can it read? How long, repetitive and frequent are requests?

Capacity and bandwidth · Input length · Concurrency · Repetition

Evaluate benefits through actual outcomes

Observe eight outcomes separately

Stored size and peak memory

How much data is stored in each place, and what is peak runtime memory?

Cache hits on repeated input

How much of this input reuses existing prefix state?

Restore time

How long does retrieving and preparing reusable state take?

Time to first token

How long after sending a request does the response begin?

Generation speed

Once generation begins, how many tokens per second does one response produce?

Throughput under concurrent use

How many tokens per second do concurrent requests produce in total? This is throughput.

Total cost

What does the whole task cost, including failures, retries and extra requests?

Answer and task correctness

Does the task still complete correctly after optimization?

The upper half shows contributing factors; the lower half shows outcomes. The hit rate on repeated input measures only how much old input was reused. Smaller state or faster execution does not necessarily mean a higher hit rate. [1][2][7][20][21]

Evaluate stored size and peak memory, cache hits on repeated input, restore time, time to first token, individual generation speed, concurrent throughput, total cost and task correctness together. Cheaper cache storage may lower cost but increase retrieval time; an extra keep-alive request may increase immediate cost while avoiding more expensive recomputation next round. The following chapters trace where these benefits and costs come from.

01 / COMPUTATION · Start with the calculation

First establish what K and V actually are

Caching avoids repeated computation. To understand that benefit, first distinguish reading an existing input from generating new content.

02
Xiao Wang · asks

When evaluating a model, I usually focus on answer quality and generation speed. But when it repeatedly reads code, runs tests and fixes errors, the same project material and an expanding conversation keep coming back.

Why does repeated input deserve separate attention? How do the computations avoided by caching affect a whole task's cost and duration?

Da Wang · explains

Imagine an assistant that reads code, uses a terminal and fixes errors. The first request supplies project material, the next returns test results and the third asks for another change. The same tool descriptions, project rules and conversation history reappear in successive requests.

The intuition from a single question

One input → one answer

It is easy to focus only on answer quality and output tokens per second.

→
The reality of a multi-round task

Return history → compute repeatedly → verify

Repeated context processing also determines whether the task is affordable.

Compare a model's efficiency on multi-round work using the whole task's total cost, elapsed time and failed retries under the same acceptance criteria. Such tasks repeatedly submit the same project material and conversation history. Whether caches can be reused, and how much computation that avoids, therefore changes the task's overall cost.

03
Xiao Wang · asks

“Cache what the model has already read” makes me think of saving the original text, or reusing its previous answer. But the stored objects here are called K and V.

What are K and V, what does each do, and exactly which computations can the next step avoid by retaining them?

Da Wang · explains

When processing a known token, an attention layer generates numerical vectors. Q is the query, K is used to compute matching scores, and V supplies the information retrieved using the resulting weights. The current token also produces its own K/V, which participates alongside earlier tokens' K/V in this layer. [1]

The internal numerical representation used while processing a token is its hidden state. It is not yet the output layer's scores for candidate tokens.

Fix one response throughout this example. Three user-input tokens occupy sequence positions 1–3. The model has already selected the first response token, at position 4. It is now fed back into the model to predict the second response token, at position 5.

FIG 05How is existing K/V reused within one response?
Read downward: existing input → process token 4 → select token 5
How is existing K/V reused within one response? A simplified single-response example: positions 1–3 are user input; position 4 is the first response token already selected; position 5 is the next token to generate. Input positions previously computed Q/K/V: their Q vectors served the earlier queries, while K/V was retained. One attention layer is expanded: reuse K/V at positions 1–3, compute Q/K/V for position 4, then finish the model's computation to select position 5. Three input tokens; continue generating the same response Blue: user input. Orange: this response. Numbers are positions in the full sequence. User input processed earlier: tokens 1–3 One response: tokens 4 and 5 token 1 Q₁ / K₁ / V₁ token 2 Q₂ / K₂ / V₂ token 3 Q₃ / K₃ / V₃ Q₁–Q₃ served input processing; K/V remains available for later reads. token 4 Response 1 · selected token 5 Response 2 · pending Now feed the selected token 4 back into the model to predict token 5. One attention layer is expanded: old Q has completed its queries; old K/V remains usable by token 4. Retained cache: K/V Input processing also computed Q; only reusable K/V is shown here. K₁ / V₁ From input token 1 K₂ / V₂ From input token 2 K₃ / V₃ From input token 3 New work: token 4 From token 4's representation in this layer: Query Q₄ Append K₄ / V₄ Match Q₄ against K₁–K₄; combine V₁–V₄ with the resulting weights Read old input K/V together with the current response token 4's new K/V. Token 4's result in this layer Continue through the rest of the model Later layers + output Score the next token Select token 5 The response's second token Both input and response tokens compute Q/K/V when processed in this layer. Later positions reuse K/V.
Positions 1–3 are user input; 4 and 5 belong to the same response. Input positions already computed Q/K/V. This step reuses their retained K/V, computes token 4's Q/K/V and selects token 5. Numbers are positions in a simplified sequence, not vocabulary IDs; system instructions and message-boundary markers are omitted. [1][11]
Attention(Q, K, V) = softmax(QKᵀ / √d) V

First match the current query against each key, then combine values using the resulting weights.

A call usually first processes its known input as prefill, then generates new tokens step by step in decode. Prefill can batch and parallelize many known positions. During generation, processing the current known token predicts the next token. Caching avoids recomputing old K/V; reading it and computing new content still cost work. [1]

Only one attention layer is expanded. Ordinary Transformer layers each generate their own K/V; attention heads and other dimensions are omitted here. Selecting token 5 does not mean its own K/V has already been computed. If generation continues, token 5 is fed back into the model on the next iteration to produce its corresponding K/V.

Reusing this history in another request also requires a matching prefix and an available compatible cache.

04
Xiao Wang · asks

With text-only input, the model generates Q, K and V from text positions' internal values. Now add a product photograph and ask it to describe clothing colors and read a label within the same response. Where does the image enter this computation?

How does inserting image vectors into the input sequence differ from preparing separate image K/V? Does the text query have more records to read?

Da Wang · explains

A network that processes pixels, the vision encoder, first transforms the image into several numerical vectors. A connector feeds them into the language model; an intermediate text description is not required. Two arrangements follow: image vectors occupy positions in the main sequence, or the text sequence stays separate and dedicated attention modules read the image. [70]

Distinguish two lengths. The number of elements in one Q vector is a model configuration choice. The number of K/V records it can match depends on what is available for that query. We compare the latter, starting with five text records.

FIG 06With an image, does Q read 9 records, or separately read 5 and 4?

Use 5 text tokens and 4 image vectors in every comparison. Track one query by the last text token, T5, in the relevant attention module. After all layers finish, its result predicts the first response token, R1. Each query shown reads every record it is permitted to access. Special markers, padding and head grouping are omitted; all image records are assumed visible.

Text positions / their K/VImage positions / their K/VTracked T5 and its QEqual-sized cells make record counts comparable

①No image: 5 text positions

Scroll sideways on narrow screens; notes follow the diagram.

T5's query reads five K/V records from the same sequenceThe text input has five positions. Each position's internal representation generates this layer's K/V; T5 also generates the current Q. Q matches all visible K/V, normalizes the scores and retrieves values. The result continues into the text computation.Layer input5 positionsLayer K/V5 recordsT1T1K/VT2T2K/VT3T3K/VT4T4K/VT5T5K/VScore and normalize 5 recordsRead V with those weightsT5's QFrom layer inputSeparate Q projection pathRetrieved information continues processing T5

②Image in the sequence: 4 image + 5 text positions = 9

Image first, text afterward; cells have the same size as above.

T5's query reads nine K/V records from the same sequenceFour image positions and five text positions form a nine-position sequence. Each position's internal values generate this layer's K/V; T5 also generates Q. Q matches all visible K/V, normalizes the scores and reads values, which continue into the text computation.Layer input9 positionsLayer K/V9 recordsI1I1K/VI2I2K/VI3I3K/VI4I4K/VT1T1K/VT2T2K/VT3T3K/VT4T4K/VT5T5K/VScore and normalize 9 recordsRead V with those weightsT5's QFrom layer inputSeparate Q projection pathRetrieved information continues processing T5

③Separate image memory: 5 text positions plus a distinct image read

The two attention operations are expanded separately; intervening computation is omitted.

Text self-attention reads five records; image cross-attention separately reads fourThe main text sequence retains five positions. Its self-attention Q reads five text K/V records. T5's updated values continue through computation and produce a separate Q at a designated cross-attention layer, reading four image K/V records. Each attention operation scores and normalizes separately; image information is merged into the text state.Text self-attentionFive K/V records in the main text sequenceT1K/VT2K/VT3K/VT4K/VT5K/VT5's layer inputSelf-attention Q5 records: scores → weightsWeighted read of text VT5's updated representationContinue intocross-attention layerProject a separate QThis cross-attention layer projects four image vectorsI1K/VI2K/VI3K/VI4K/VCross-attn Q4 records: new scores → weightsWeighted read of image VMerge image information into text state → continue computationText read: 5Image read: 4
One joint allocation across 9 records ≠ separate allocations across 5 and 4 records.

These are different model architectures. The second retains a shorter text sequence but adds dedicated image-reading modules. Image computation and caching have not disappeared.

The counts are teaching choices for comparing data flow and readable records. Real images do not invariably produce four vectors. LLaVA illustrates insertion into the sequence; Mllama illustrates separate image K/V. Each attention layer may have its own K/V; this diagram does not merge all model layers' records into one cache. [70]

Xiao Wang As the response continues, how do the text and image caches grow?

Da Wang: T5's final hidden state goes through the output layer to produce vocabulary scores and select R1. R1's own K/V is usually not computed yet. The next step feeds the selected R1 back into the model, appends its text K/V and uses the resulting computation to predict R2.

Scroll tables sideways on small screens.

Image connectionT5 predicts R1Feed R1 back to predict R2
Text onlySelf-attention: 5 recordsSelf-attention: 6 records
Image in the main sequenceSelf-attention: 9 records
4 image + 5 text
Self-attention: 10 records
Still 4 image; text grows to 6
Image read through cross-attentionText self-attention: 5 records
Image cross-attention: 4 records
Text self-attention: 6 records
Image cross-attention: still 4 records

Xiao Wang If image K/V stays unchanged, how can describing colors and reading a label retrieve different information?

Da Wang: If the image features are unchanged and the existing image K/V remains usable, those records can be reused. Later text positions generate different Q vectors, giving different matching scores against the same keys and different weights for reading values. Avoiding repeated preparation of image records does not eliminate each step's query, matching and retrieval. [70]

“Text K/V” means K/V generated at text positions, not K/V containing only textual information: preceding cross-attention may already have incorporated image information into those positions' states. With image vectors inserted into the sequence, changing text before an image can also change deep-layer K/V at image positions. Identical image files alone do not prove the whole language-model cache is reusable.

05
Xiao Wang · asks

Suppose I submit 1,000 known input tokens and request 1,000 new output tokens. Both compute Q/K/V when processed, yet APIs often charge different unit prices.

If both input and output compute Q/K/V, why can input be batched while output is usually more expensive?

Da Wang · explains

Start with ordinary new input that misses the old cache. It still computes Q/K/V, reads context and traverses the model layers. To understand efficient input processing, first resolve this puzzle: if later positions depend on earlier context, how can positions be computed together?

Temporarily replace 1,000 input tokens with three known tokens, A, B and C. D, E and F will denote generated response tokens. Distinguish values entering a layer, Q/K/V projected in that layer, and values after the layer finishes; they belong to different stages.

FIG 07Prepare each position's Q/K/V, then read preceding context together

Known input A, B and C occupies positions 1, 2 and 3. Only one attention head and the first two layers are expanded. Colored cells represent numerical vectors.

Parallel ① Dashed boxes with matching numbers can run concurrently; arrows still show dependencies. Numbers match only within this diagram.

① Known input: each position has its own starting point

Three known input tokens independently produce first-layer Q/K/VA, B and C have known vocabulary IDs. Their embeddings are looked up independently to form first-layer inputs and project Q/K/V. There are no horizontal waiting arrows between positions. Magenta Parallel ① boxes group these first-layer Q/K/V projections.Position 1AembeddingQ₁K₁V₁Position 2BembeddingQ₂K₂V₂Position 3CembeddingQ₃K₃V₃Par. ①Par. ①Par. ①

② Attention in this layer: restrict visibility without queuing rows

Three queries read the same records with different visibilityIn the triangular grid, Q1 reads K1/V1, Q2 reads positions 1–2 and Q3 reads positions 1–3. Future positions are crossed out. Independent rightward arrows indicate separate attention results rather than continuation from the row above. All three attention rows are Parallel ②: once K/V is ready, they can run concurrently. Cache-column headings are not computation boxes.Layer K/V: one position per columnK₁ / V₁K₂ / V₂K₃ / V₃Q₁Q₂Q₃Attentionresult● Readable × Future maskedPar. ②Par. ②Par. ②

③ Next layer: previous context is already in each hidden state

C reads B's K/V in this layer without waiting for B's attention outputB and C's complete layer-1 outputs enter layer 2 and generate new Q/K/V. A diagonal green arrow runs from B's layer-2 K/V to C's layer-2 attention. The attention operations share a row without an ordering arrow between them. Only B→C is highlighted; other readable K/V is omitted. Layer-2 projections are Parallel ③, and attention operations are Parallel ④. Arrows retain the actual dependencies.Position 2 · BLayer-1 outputLayer 2Q₂K₂V₂Position 2AttentionPosition 3 · CLayer-1 outputLayer 2Q₃K₃V₃Position 3AttentionPass to the next layerK₂ / V₂K/V ready → attention can run in parallelPar. ③Par. ③Par. ④Par. ④
This is an ordinary causal Transformer. Multiple positions reading the same K/V records differs from GQA's sharing across heads or YOCO's sharing across layers. The diagram follows the pinned implementation's dependencies: Q/K/V projection → attention → remaining layer operations → next layer. [40]

Why can't future response tokens also be processed in advance?

Compare the two cases. A, B and C are given at the start; during generation, D must first be selected before its embedding can be looked up and fed into the model. Both cases project Q/K/V before computing attention.

FIG 08How do input processing and generation each produce Q/K/V?

The same ordinary causal Transformer and attention head, expanding only layer 1. These are consecutive steps in one conversation: process input A/B/C, select D, then feed D back to predict E.

Parallel ① Dashed boxes with matching numbers can run concurrently; arrows still show dependencies. Numbers match only within this diagram.

Process input first: all three tokens are given

Process A, B and C, then select DThe three known tokens independently look up embeddings and generate layer-1 Q/K/V. Q1–Q3 each read permitted records. After all model layers, C's final representation goes through the output layer to select position-4 D. D has not been processed and has no Q/K/V yet. Input projections are Parallel ①; attention rows are Parallel ②. Generation occurs later and uses different group numbers.Position 1AembeddingQ₁K₁V₁Position 2BembeddingQ₂K₂V₂Position 3CembeddingQ₃K₃V₃Queries can run in parallel hereNew K/V to retain this roundK₁/V₁K₂/V₂K₃/V₃Q₁Q₂Q₃Finish layer → subsequent layersEvery layer produces its own Q/K/VTake C's final-layer outputOutput → vocabulary scoresDSelect position 4 hereD is selected; its own Q/K/V is not computed yetPar. ①Par. ①Par. ①Par. ②Par. ②Par. ②

Continue generation: D is this step's new input

Feed the selected D back to generate D's own Q/K/VPosition-4 D is known. Its embedding produces layer-1 Q₄/K₄/V₄. Old K/V goes directly to attention reads. Purple branches send the same Q₄ to four parallel matches against K₁–K₄, then merge the scores for softmax and the weighted V combination. D still traverses successive model layers before E at position 5 is selected. E cannot be processed before selection; even after selection, its Q/K/V awaits the next round. One head is expanded. Generation-side matches use Parallel ③; softmax is outside those boxes and depends on their scores.Position 4 · just selectedD?Position 5 unknownNot an input yetembeddingQ₄K₄V₄Q₄Reuse old layer-1 K/VK₁/V₁K₂/V₂K₃/V₃Append K/VK₄/V₄Layer 1 · parallel Q/K matchesQ₄·K₁Q₄·K₂Q₄·K₃Q₄·K₄Then softmax → weighted VFinish layer → subsequent layersEvery layer produces its own Q/K/VTake D's final-layer outputOutput → vocabulary scoresESelect position 5 hereE is selected; its own Q/K/V is not computed yetPar. ③Par. ③Par. ③Par. ③
The Q/K/V projection rule is the same; which tokens are given is different. Only layer 1 is expanded. Deeper layers project new Q/K/V from that position's preceding-layer output and read their own layer's K/V cache. Input processing uses C's final representation to predict D; generation uses D's to predict E. [40][11]
Xiao WangSo known input has dependencies between layers, but allows parallel work inside a layer?

Da Wang: Yes, specifically multiple positions performing the same step together. A layer first prepares Q/K/V, then computes attention and finishes subsequent operations. Those stages still depend on one another. Within each stage, positions can be batched once the necessary inputs exist. The next layer still needs the preceding layer's outputs.

Xiao WangDoes generating a new token lose even the parallelism within a layer?

Da Wang: No. The generation side is processing position-4 D to select position-5 E. Multiple query heads within a layer can process D concurrently, each using its own Q. Within one head, Q₄'s matches against different keys can run in parallel; feed-forward operations also contain many parallel numerical computations.

Ordinary generation waits for E and F to be determined in sequence. Before E is selected, its embedding is unknown, so E cannot be processed. Compared with known input, there are fewer opportunities to process several new positions of one response together. Parallelism inside the current token's computation remains. [40]

Xiao WangThen does Q/K/V generation ignore history, with only attention depending on historical K/V?

Da Wang: Distinguish what a step reads directly from what its input already contains. Consider C in input A/B/C:

Layer 1: C projects Q/K/V from its own embedding, before attention reads A or B. Its layer-1 attention then reads A/B/C's K/V. After the remaining operations, C's layer-1 output can already contain information from A and B.

Layer 2: C projects new Q/K/V from its own layer-1 output, so those values can indirectly depend on A and B. Their projection does not first need to read A/B's layer-2 K/V or wait for A/B's layer-2 attention outputs.

Not directly reading historical K/V in this layer does not mean being independent of history. History may enter the preceding layer's output and thereby influence this layer's Q/K/V. [40]

The model first processes the whole input. To generate the first response token, many implementations apply the vocabulary output projection only to the final input position's final-layer representation. Earlier input positions still participate in the model computation; their own next-token vocabulary scores need not all be calculated. Logits are the output layer's raw candidate-token scores, before conversion to probabilities. [11]

Return to the original 1,000-token input. D/E/F still denote response tokens, but their full-sequence positions are now 1,001/1,002/1,003 rather than 4/5/6 in the three-token illustration.

FIG 09Input can be batched; response tokens are selected step by step

The same 1,000 known input tokens are followed by D/E/F. Both panels show model layers progressing downward. Red arrows identify sequential dependencies.

Parallel ① Dashed boxes with matching numbers can run concurrently; arrows still show dependencies. Numbers match only within this diagram.

1Input: process several known positions together

Known input: batch positions within a layer; advance through layers in order.Process 1,000 known input tokens. Columns are positions 1, 2 and 1,000. Layer 1 batches Q/K/V projections, computes each position's permitted attention and finishes its remaining operations. Those outputs feed layer 2. Usually only position 1,000's final hidden state is projected to vocabulary scores to select D. Within the input layer, projections are Parallel ① and attention is Parallel ②. Dependencies between layers remain.Columns: token positions · Downward: computationPosition 1embeddingPosition 2embeddingPosition 1,000embedding…Model layer 1Batch the same stepQ / K / VQ / K / VQ / K / VAttentionRead position 1Layer outputAttentionRead 1–2Layer outputAttentionRead 1–1,000Layer outputFinish this layer, then pass results to the nextModel layer 2Q/K/V → …Q/K/V → …Q/K/V → …Subsequent layers run in orderFinal layer: representations for each positionLayer outputLayer outputLayer outputNo separate predictions at 1–999Output scores → select DPar. ①Par. ①Par. ①Par. ②Par. ②Par. ②

Within a layer, multiple positions can perform the same operation together. Q/K/V projections precede attention and subsequent processing; these stages are not all simultaneous. The next layer uses the preceding layer's outputs.

After final representations exist for all input positions, selecting the first response token D usually needs vocabulary scores only for position 1,000.

2Generation: the current token still permits parallel work

Heads can run concurrently for the current token; future tokens await selection.D is already selected at position 1,001. Its model layers still execute in order, while heads within each layer can project Q/K/V and compute attention concurrently. After all layers, vocabulary scores select E; only then can E's embedding be retrieved for the next round. Each head uses its own historical K/V. Generation-side projections are Parallel ③ and attention is Parallel ④. E cannot enter the model before selection.D is selected: process position 1,001D's embeddingModel layer 1Only 3 heads shownQ / K / VHead 1 attentionQHead K/VQ / K / VHead 2 attentionQHead K/VQ / K / VHead 3 attentionQHead K/VMerge heads → finish the remaining layer operationsThe next layer still waits for this layer's resultLayer 2: parallel work remains within the layer…subsequent layersOutput scores → select E at position 1,002Select E firstE's embedding → model layers → select FPar. ③Par. ③Par. ③Par. ④Par. ④Par. ④

The three branches are different query heads at one position. They can run concurrently. A head's Q/key matches and many feed-forward calculations also allow parallel work.

The unknown identity of the next token causes the wait. Only after D traverses all layers and E is selected do we know which embedding to retrieve. E and F cannot be submitted together in advance as known input can.

3Why can Q/K/V depend on history without directly reading it?

Q/K/V comes from this position's layer input; deeper inputs can already contain history.Layer 1 projects Q/K/V from the current token's embedding, before reading historical K/V. Attention then reads preceding positions' K/V, and the rest of layer 1 produces a history-dependent representation. Layer 2 projects its Q/K/V from that representation, making them indirectly history-dependent.Track input token 1,000This token's embeddingModel layer 1QKVAttention reads this layer's K/VFinish the remaining layer operationsEarlier 999 positionsThis layer's K/VThis position's layer-1 outputAlready influenced by historyHistorical influenceContinues into deeper layersModel layer 2QKV

Start with the blue path. Layer 1 projects Q/K/V from this token's embedding without first reading earlier tokens' K/V.

Then follow the green arrows downward. Attention reads history and affects this position's layer-1 output. Layer 2 projects Q/K/V from that result, so they can indirectly incorporate preceding context.

No direct read of this layer's historical K/V does not mean independence from history. Deep-layer Q/K/V receives input that has already passed through preceding-layer attention.

This compares ordinary Transformer input processing with sequential generation of one response. Parallel work within a layer does not remove dependencies between stages or layers. Diagram layout does not represent actual elapsed time. [40][11][28]

For example, after a GPU loads a weight tile into faster memory used by computation, a batch can reuse it for several tokens. Sequential generation handles fewer new positions per step while still accessing weights and historical K/V, making data waits more significant. These weight transfers are mainly between GPU memory and compute units; the model is not reloaded from disk at every step. [28]

Batching does not turn 1,000 tokens' computation into one token's computation. It changes how work is scheduled and how per-token overhead is shared. Generation already uses KV caching. It avoids recomputing old positions, but cannot determine future tokens early or eliminate new-step computation and historical reads.

Output also keeps resources occupied. Until a request finishes, its state must remain available, and further generation adds state. Servers can batch decoding across users, but must balance GPU memory capacity against each user's waiting time.

02 / IDENTITY · Down to individual tokens

Must the entire prefix be stored in full?

The key distinction is between a prefix's identity and its computational state. Separate them, and hashing, token-level caching and block caching fit into one picture.

06
Xiao Wang · asks

Compare “I like apples” with “I hate apples.” Treat “apples” as the same single token at the same position; only the preceding verb changes.

If the current token and its position match, why can't we directly reuse the previous K/V for “apples”? How does earlier context affect it?

Da Wang · explains

Compare token 3, “apples.” In a standard model's first layer, before reading context, the same token at the same position can have identical K/V. First-layer attention then reads context containing either “like” or “hate,” potentially producing different outputs. Layer 2 projects K/V from those outputs, so its K/V may differ.

FIG 10The same “apples”: why can first-layer K/V match while second-layer K/V differs?
↔ Scroll sideways to view the full diagram
The same “apples”: why can first-layer K/V match while second-layer K/V differs? Two input sequences appear side by side; columns are token positions. Before first-layer attention reads context, position-3 apples can have identical K/V. Reading different context can change that position's first-layer output, making its second-layer K/V differ. Columns always denote token order; rows identify the K/V layer Both inputs place “apples” third, with the same model and vocabulary ID for “apples.” Input: I · like · apples token 1 I token 2 like token 3 apples Layer-1 K/V Before reading context K₁ / V₁ Can match K₂ / V₂ Verb changes K₃ / V₃ Can match Layer 1 reads context and continues “like” participates in this computation for “apples” Layer-2 K/V May differ Input: I · hate · apples token 1 I token 2 hate token 3 apples Layer-1 K/V Before reading context K₁ / V₁ Can match K₂ / V₂ Verb changes K₃ / V₃ Can match Layer 1 reads context and continues “hate” participates in this computation for “apples” Layer-2 K/V May differ Compare the same token's K/V at the same model layer. Contextual influence propagates through layers.
Each panel places three input tokens horizontally and distinguishes layer-1 and layer-2 K/V vertically. With standard positionwise input transformations, identical “apples” tokens and positions can produce identical first-layer K/V. After reading different context, second-layer K/V may differ. [1]
KVi(ℓ) = Fℓ(x1, …, xi; model and runtime conditions)

Subscript i is sequence position; superscript (ℓ) is model layer; xi is token i. Fℓ denotes the corresponding layer's computational relationship, not the hash function used to look up cache entries.

A general high-layer KV cache keyed only by token_id or (token_id, position) would therefore conflate different contexts. Earlier conditions must also be distinguished.

This does not yet imply storing the whole plaintext prefix inside every key. That is the next question.

07
Xiao Wang · asks

If K/V depends on context, could different histories receive different cache IDs? Let A, B and C each represent one token.

First create an ID for A. When processing B, combine A's ID with B to obtain a new ID for A→B. For C, combine the A→B ID with C to generate the next ID.

A hash map could then look up the cache by ID. Each short ID accounts for the preceding path without repeating all earlier text in every cache entry.

Can this correctly distinguish contexts? Does “include the entire prefix” mean storing its plaintext or storing an identifier that distinguishes it?

Da Wang · explains

Yes. The server derives an ID from the input prefix, model and other conditions, then looks up retained K/V. It does not have to recompute K/V before deciding whether reuse is possible. [2]

FIG 11Identify the prefix first, then use the ID to find its actual cache

Follow purple to distinguish histories, then green to find already-computed data.

↔ Scroll sideways on small screens; explanations follow each panel.

The same “apples” can have different prefix IDs

Prefix IDs branch, then locate their corresponding K/VThe branches share the ID for “I.” Combining that ID with “like” or “hate” produces different prefix IDs. Even though both then end in “apples,” they point to different cached histories. Purple denotes lookup identities; green denotes cached data.IID h₁I likeh₂ = Hash(h₁, like)I like applesID from h₂ and “apples”K/V for this prefixor its storage addressI hateh₂′ = Hash(h₁, hate)I hate applesID from h₂′ and “apples”K/V for this prefixor its storage address12
h denotes a lookup ID. Words are treated as individual teaching tokens; model and runtime conditions are fixed.

vLLM's hash_block_tokens hashes a parent block's hash, current block tokens and extra identity information. It follows the same idea as parent_hash + current_token, generally operating on blocks. [3]

08
Xiao Wang · asks

If every token position can have a cache entry, why not make all previously computed identical prefixes reusable?

Suppose a previous request processed 1,000 tokens. The next shares its first 999 tokens and changes only the last. If the old cache remains available and usable, a token-level entry lets us reuse 999 tokens and compute only one. Reusing full groups of 16 stops at 992, forcing recomputation of seven already-processed tokens.

Here “100%” means reusing all 999 identical tokens. New content still needs computation; the overall hit rate across the 1,000-token input remains 99.9%.

Why not make lookup fine-grained enough to reuse 100% of the matching prefix? What does grouped management save that justifies the extra computation?

Da Wang · explains

That is possible. Finer entries can reduce recomputation while increasing identification, lookup and management work. Whether they are worthwhile depends on the actual costs on both sides.

Xiao WangWhat work specifically increases when there are more entries?

Da Wang: First estimate the cost of creating entries, then the computation those entries can save on later requests.

FIG 12Sixteen times as many entries: how much extra computation is avoided?

Compare two direct schemes: one entry per token or one entry per 16 tokens. The grouped scheme reuses only complete groups. Both schemes' old caches are available.

↔ Scroll sideways on small screens; numbered explanations follow their panels.

Creation cost: first process the same million-token document

Two ID schemes for the same million-token documentOne ID per token means one million IDs and 32 MB. One per sixteen tokens means 62,500 IDs and 2 MB. The bars share a scale and differ by sixteen times. Only the IDs themselves are counted.One ID per token1,000,000 IDs · 32 MBOne ID per 16 tokens62,500 IDs · 2 MB1ID count and ID storage differ by 16×Bar lengths represent ID count; each ID occupies 32 bytes.

Reuse benefit: two divergence points in a 1,000-token request

The first 992 tokens are collapsed on the left; the last eight are shown individually. This is not proportional to the full sequence length.

Old: reuse retained resultsRedo: recompute identical contentNew: compute the suffix from the divergence
Finer entries save different amounts for two common-prefix lengthsPositions 1–992 are collapsed; 993–1,000 are expanded. With 992 matching tokens, both schemes reuse 992, so finer entries save zero extra tokens. With 999 matching tokens, token-level entries reuse seven extra tokens; groups of sixteen must recompute those seven because they do not form a full group. If the same seven-token difference occurs 10,000 times, 70,000 token computations are avoided.2First 992 match: divergence begins at token 993Tokens 1–9929939949959969979989991000Per tokenReuse 992NewNewNewNewNewNewNewNewPer 16Reuse 992NewNewNewNewNewNewNewNewBoth reuse 992 → finer entries save 0 extra tokens3First 999 match: only token 1,000 changesTokens 1–9929939949959969979989991000Per tokenReuse 992OldOldOldOldOldOldOldNewPer 16Reuse 992RedoRedoRedoRedoRedoRedoRedoNewThese 7 matching tokens must be recomputed4The same 7-token difference occurs 10,000 timesToken-level reuse → 70,000 fewer token computations
Under these assumptions, 1 MB = 1,000,000 bytes. The upper panel counts only ID storage; the lower counts input-token computations. Neither directly gives total cost or elapsed time.

In a chained hash scheme using the previous ID and current content, each ID depends on its predecessor. The entire chain cannot simply be generated simultaneously on first creation. The relevant vLLM code proceeds group by group, passing each hash to the next group. [13]

Xiao WangOnce IDs exist, what overhead remains beyond table lookup?

Da Wang: The system still tracks cache locations, current users and when data may be reclaimed.

If two requests share a cache, finishing one must not reclaim data the other is still reading. Starting and ending use therefore require corresponding bookkeeping updates.

vLLM's cache manager processes blocks individually: increase usage counts on a hit, decrease them when requests release blocks, and adjust which blocks can be reclaimed. [13]

If physical management units also shrink from 16 tokens to one, these operations become more numerous. Under concurrency, CPU cache-management time may offset the GPU work saved on a few tokens.

However, finer matching need not imply finer physical storage or management units. They can be designed separately.

Xiao WangCan token-level reuse be retained while maintaining fewer entries?

Da Wang: Yes. Better data structures avoid creating an independent entry for every position in advance.

For example, initially represent 1,000 consecutive tokens as one segment. If a later request matches 999, split the record after token 999. That preserves exact reuse of the first 999 without initially creating 1,000 separate tree nodes.

SGLang's cache tree uses this approach. A node can hold a contiguous segment and split when a match ends inside it. Its ordinary token-matching path supports single-token precision. The split reorganizes cache records; existing K/V remains usable. [5]

The practical comparison is therefore how much additional computation finer reuse saves versus the additional lookup, management and storage costs. Measure total time, memory and service capacity on the same request workload to choose between schemes.

03 / RESTORATION · From algorithms to services

What else must hold for an identical prefix to hit?

The directory must locate it, data must still exist, and retained state must be sufficient to continue from that point. API cache points sit at this service layer.

09
Xiao Wang · asks

I add cache_control at the end of a document in a Claude request, then append this round's question. The position is called a breakpoint; some translations make it sound as though later content is cut off.

What does this marker actually control? Does it cache only that block or everything from the request's start through that point? Is the new question after it still processed?

Da Wang · explains

Think of a cache boundary or restore point. A “cutoff point” misleadingly suggests later content is excluded from the model.

Putting cache_control on a content block marks the prefix from the beginning of the effective prompt through the end of that block as a caching candidate. Claude documents the order as tools → system → messages. [4]

FIG 13A cache point restores the prefix “through here”

The purple bracket spans from the start to the marker. Subsequent content still enters the model.

↔ Scroll sideways on small screens; the marker is explained below.

At the marker, distinguish model computation from service-level caching

cache_control marks a service-level cache boundaryTool definitions, system instructions and stable documents form the effective input prefix. The bracket marks the cached range from its start through the marker. The new question afterward still participates in computation. KV is produced as the model runs; the marker does not switch on KV generation.Effective prefix order: tools → system → messagesTool definitionsSystem instructionsStable document / historyReusable prefix entryStart through markercache_control1This round's question / tool resultContent after the markerStill processed by the model
The marked content block is included in the cached prefix. An API content block is different from a physical block storing K/V on a GPU. [4]

Several cache points can reflect different stability levels: tools and system instructions rarely change, project material changes occasionally and conversation grows continually. If a longer entry becomes unusable, a shorter one may remain valid. All still mean “from the start through this point”; matching text later in the request does not automatically become context-independent cache material.

10
Xiao Wang · asks

I submit the same long document three times with three different questions. DeepSeek does not ask me to mark where the document ends, so the server chooses what to retain.

How does it discover and store the common opening? Can the second request reuse the document, or might reuse begin only later?

Da Wang · explains

Fix the scenario: A is the entire document; B, C and D are different questions. Every request begins with A and replaces the question afterward.

Distinguish global K/V from local K/V first

Some models maintain both kinds of KV cache, including the DeepSeek V4/V4.1 discussed here. “Global” and “local” describe their context coverage.

Global K/V: older content remains readable

Records cover the processed long context, allowing the model to revisit earlier parts or the beginning of a report. They may be compressed or shared across model layers. “Global” does not require reading every record at every step.

Local K/V: recent state retained separately by each layer

Each layer retains the K/V it computed for recent tokens to read nearby context. This range is its local window. As new tokens are processed, new records enter and the oldest records leave the current window.

Reading within this recent range is sliding-window attention (SWA). A layer using both paths reads its local window and may select older information from global records, combining long-range information with layer-specific recent state. [12][14]

Layers may retain the same token range, but their local K/V comes from their own computations. Shared global records are also different data. Slicing the end of the global cache cannot simply substitute for every layer's local state. [12][14]

FIG 14The API documentation example: which request first reuses the document?
↔ Scroll sideways to view the full diagram
One document, three questions: when is the cache reusable? Three inputs share document A and append questions B/C/D. Assume no shorter usable prefix cache. Request 1 first computes A. Request 2 still misses and recomputes A, then separately stores A. Before request 3 arrives, A has been saved and can be reused. Repeating the whole first input is a different scenario. Each row is a request; A is a whole document, not one token No shorter usable prefix is assumed; A is fully saved before request 3 arrives. Request 1 Miss in this example Same document A Compute initially Different question B First save “document A + question B” Request 2 Miss in this example Same document A Must recompute A New question C Discover shared document A Then save A for request 3 Request 3 Hit on A Same document A Reuse existing K/V directly Different question D Reuse document A's cache Process new question D This example changes the question after a shared document. Repeating the entire first input may hit on request 2.
This is the official API documentation's A+B, A+C, A+D example, assuming no other usable prefix and A saved before request 3. It is neither a guarantee for all live requests nor a measurement in this edition. See the next diagram for global records and local restoration state. [6][14]

If A was computed already, why might C still be unable to continue from it?

The API documentation explicitly relates prefix-retention rules to SWA. The crucial distinction is that recognizing the same prefix does not prove all state needed to continue from its endpoint still exists. [6]

Use a teaching model with a local window of four tokens. Document A occupies tokens 1–4; the old question B occupies 5–8. After processing A+B, we want to replace the question at the end of A with C.

FIG 15Global records remain while local state may be missing

Two states at one moment: the previous request reached token 8; the new request wants a different continuation after token 4.

↔ Scroll sideways on small screens; explanations follow each panel.

Follow the numbers: retain, evict, restore

Global history survives, but local state at the document boundary may be absentA occupies tokens 1–4; old question B occupies 5–8. After token 8, global records 1–4 remain, but the local window contains only 5–8. Continuing from A requires its endpoint's local state: load a snapshot or recompute from an earlier usable state.Document A: tokens 1–4Old question B: tokens 5–8Global K/V12345678Local K/V1234567812Continuing A with C needs local state at A's endComplete snapshot at A's endLoad and continueNo such snapshotReplay from earlier usable state3
Numbers are token positions. Only one four-token local window is expanded; model layers have different local states.

Section 3.2.1 of the V4.1 report reviews the earlier V4 deployment: global K/V was retained throughout, while local-window snapshots were mainly kept at input and output endpoints. An intermediate prefix endpoint need not have a local snapshot. [14]

Combining the report with the API example suggests this mechanism: local restoration state at A's end was initially missing, requiring recomputation; once the common prefix was identified and stored, later requests could continue from it. This is a mechanism-based explanation; the API does not disclose every internal live-service step.

How does V4.1 change this constraint?

The bounded-replay method in §3.2.2 reconstructs missing local state by recomputing only a short suffix when global K/V is available. This reduces reliance on long-lived local snapshots. However, those tokens may originally have depended on earlier computation results; without those results, reconstructed state need not exactly match the original. [14]

04 / STORAGE · From cache IDs to actual hardware

How is KV stored, moved and used in computation?

Start with cache placement, follow GPU page-table reads, then examine how attention reduces transfers between GPU memory and on-chip storage.

11
Xiao Wang · asks

After the model computes K/V for a long document, must that state keep occupying GPU memory while the user is away?

When the user returns, how does the GPU find and correctly read a cache moved elsewhere or scattered across GPU memory?

Da Wang · explains

A cache need not permanently occupy GPU memory. The diagram connects storage, retrieval, reads and independent continuations of the same K/V.

FIG 16The same K/V: storage, reads and separate continuations

Follow three aspects of already-computed K/V while a user leaves and returns.

1Idle state can leave GPU memory

Possible temporary storageHost RAM · SSD
or another machine
Retrieve →← Offload
GPU memoryReady for computation to read

2Once in GPU memory, locate records through a table

Each page here holds K/V for four tokens.

Tokens 1–4First K/V page
GPU block 7This page physically resides here
Tokens 5–8Second K/V page
GPU block 2This page physically resides here

3Separate a writable tail page before two answers diverge

Tail page shared by two responses
Existing K/VFree slot
Separate before appending ↓
Response A
Existing K/VA's new K/V
Response B
Existing K/VB's new K/V
Changing storage locations does not reorder historical token positions. Sharing must not overwrite data another response still needs. [25][47]
Does “static” in StaticCache mean the content stops changing?

No. StaticCache reserves fixed capacity in advance, while new K/V is still written and valid length advances. DynamicCache expands storage as input grows. Fixed capacity works more naturally with fixed-shape compilation but may reserve unused space. This is a separate choice from a sliding window that retains only recent history. [41]

12
Xiao Wang · asks

While reading a long document, attention must do more than retain K/V: compute matching scores, convert them to retrieval weights and combine contributions from V.

Why do those temporary results consume so much GPU memory, and how does FlashAttention address it?

Da Wang · explains

The problem: two large temporary tables beyond K/V

Suppose the model processes 10,000 tokens at once. Consider one Q/K/V set in one layer. A separated implementation does the following:

  1. Compute matching scores. Every input Q scores the permitted keys according to the model's rule. Stacking all queries produces a score table.
  2. Convert scores to retrieval weights. Exponentiate each query's scores and divide by their total so the row sums to one. This is softmax; masked future positions are excluded.

    Ordinary Transformers already do this. The index vectors discussed later first select records; selected records are still scored again and normalized with softmax.

  3. Weight the contributions from V. Multiply each value vector by its weight, then add corresponding coordinates across records to form each output coordinate.

If a full square table is materialized in GPU memory, 10,000 × 10,000 is 100 million numbers. At two bytes each, one table takes 200 MB; retaining both scores and weights takes 400 MB. This example assumes full-square storage, including slots for masked future positions. It counts only one Q/K/V set in one layer, with decimal MB.

Capacity is only part of the problem; moving data between two places also costs time. GPU memory holds large datasets. Computation loads relevant portions into fast on-chip storage, such as registers and SRAM, for the compute units.

If scoring, softmax and V aggregation are separate operations, intermediate results may be written to GPU memory and read back on-chip by the next operation. The arrows below expose those transfers.

FlashAttention: finish a tile's consecutive operations on-chip

FlashAttention is an attention computation method. It loads small Q/K/V tiles on-chip and immediately uses temporary scores in subsequent computation, avoiding full score and weight tables in GPU memory. Compare the two paths to see the savings.

FIG 17Which GPU-memory round trips does FlashAttention avoid?

Both panels place GPU memory on the left and on-chip computation/storage on the right. Orange marks the full temporary tables retained by an ordinary separated implementation. Data moves in portions; the whole table is not loaded into on-chip memory at once.

Separated implementation: write a table to GPU memory, then read it back

GPU memoryInput and result storageOn the GPU chipCompute units and fast storage
Q、K
Read in
① Compute scoresQ and K
Matching scores
Full score table
Write out
The score table just produced
Read back
② softmaxConvert scores
to retrieval weights
Full weight table
Write out
Weights + V
Read back
③ Combine VMultiply by weights
Add corresponding coordinates
Attention output
Write out

FlashAttention: load a tile and continue its computation on-chip

GPU memoryInputs and outputs are still storedOn the GPU chipRegisters and on-chip SRAM
Q、K、VRetained in GPU memory
Read in tiles
Load a tile
Compute the current tile here
Q/K matching scores
Exponentiate; update accumulators
Multiply V; accumulate contributions
Keep accumulators hereLoad the next K/V tile,
repeat the computation ↻
When all needed tiles finishDivide to produce output
Attention output
Write out
The lower path tracks a forward attention pass for queries processing K/V in tiles. Computation and data movement are rearranged; the participating attention records are not reduced. [49]
Optional: how can tiled softmax produce the same result?

Reduce the example to three input tokens and track token 3's Q. Suppose exponentiated scores are 1, 1 and 3, with V values 0, 0 and 10. Each V is a scalar for this example.

Delay normalization and keep two accumulators: the sum of exponentials and the sum of exponential-weighted V:

  1. First tile, records 1–2: exponential sum 1 + 1 = 2; V contribution 1 × 0 + 1 × 0 = 0.
  2. Second tile, record 3: update them to 2 + 3 = 5 and 0 + 3 × 10 = 30.
  3. After all tiles: divide once: 30 ÷ 5 = 6, the same as combining V using weights 1/5, 1/5, 3/5.

Do not simply average independently normalized tile outputs. The first tile gives 0 and the second 10; averaging gives 5 and loses their proper relative weights.

What if exponentials become too large?

Large scores can make exponentials overflow. Subtract the largest score seen so far before exponentiating. This rescales all exponentials by the same factor. V contributions receive the same factor, leaving the final ratio unchanged.

If a later tile introduces a larger score, rescale previous accumulators too. In this example, divide all exponential terms by three when tile 2 arrives: the earlier 2 becomes 2/3, the third record's 3 becomes 1, and weighted V totals 10. Then 10 ÷ (2/3 + 1) = 6, still the same result.

Implementations therefore retain the maximum score, sum of exponentials and accumulated V contribution per query, update them per tile, and divide at the end. The mathematics is equivalent; finite-precision arithmetic can still introduce rounding differences when operation order changes. [49]

Optional: the accumulator update equations

m is the maximum processed matching score, ℓ the sum of exponentials after subtracting it, and z their weighted V sum. New-tile scores are sᵢ with values vᵢ:

m′ = max(m, max s)
α = exp(m − m′)
ℓ′ = αℓ + Σ exp(sᵢ − m′)
z′ = αz + Σ exp(sᵢ − m′)vᵢ

α rescales old accumulators to the new maximum. After all tiles, output z/ℓ. Initialize m = −∞, ℓ = 0 and z = 0. Include only readable positions; causally masked future tokens do not contribute.

What is eliminated, and what remains?

It avoids storing two full temporary tables and the GPU-memory reads/writes around them. Reduced movement can improve speed; it does not delete historical K/V or skip Q/K matches that should participate.

MethodMain savingWhat remains necessary
Prefix cachingAvoid regenerating K/V for identical inputNew queries still read history and compute
PagedAttentionAvoid one large contiguous allocation; reduce wasted spaceThe K/V records themselves still occupy memory
FlashAttentionAvoid full score and weight tables in GPU memoryStill read K/V participating in this attention operation

These techniques can be combined. FlashAttention's public cache interface accepts page tables, but support for combinations of paging, quantization and window attention must be checked for each implementation. [48]

05 / ARCHITECTURE · Make retained history cheaper

How do model architecture and numerical formats change cache costs?

Count the numerical vectors actually retained, then examine quantization, cross-layer sharing and compact state.

13
Xiao Wang · asks

A model has 32 attention heads. I calculated 32 copies of the cache, yet the framework reports several times less. Did it omit data, or did I count the wrong heads?

How should cache capacity be calculated? What do GQA and MQA store less of?

Da Wang · explains

The number of query heads need not equal the number of stored K/V groups. Fix one model layer and calculate per token. In this example, eight query heads share K/V in groups of four, giving two KV groups.

If each K and V has four numbers and each number takes two bytes, one pair occupies (4 + 4) × 2 = 16 bytes. Two groups total 32 bytes per token. Sum over retained positions and the actual per-layer shapes for the basic cache payload.

One KV group per query head is multi-head attention (MHA). One group shared by all query heads is multi-query attention (MQA). Between them, groups of query heads share K/V in grouped-query attention (GQA). This shares across heads, not requests or layers. [39]

Xiao WangDoes “several query heads share K/V” refer to sharing within a head, queries at different token positions reusing history, or different heads' queries for the same token sharing K/V?

Da Wang: Here it means different query heads within the same layer share K/V. Fix one token: it produces distinct Q vectors in different heads, which may read the same set of historical K/V.

With five input tokens, consider token 5 in this layer. It produces eight Q vectors, one per query head. Heads 1–4 read KV group A; heads 5–8 read group B. Each group still retains separate K/V for every position 1–5.

For a reminder of layers and heads, return to FIG 01: multiple heads per layer, with results passed between layers.

FIG 18Eight query heads can retain only two K/V groups

Fix one layer and five input tokens. All eight Q vectors belong to token 5, produced by heads 1–8. Both cache groups A and B retain distinct records for tokens 1–5.

Parallel ① Dashed boxes with matching numbers can run concurrently; arrows still show dependencies. Numbers match only within this diagram.

Query heads 1–4
Parallel ①
H 1Use their own QCompute attention
Parallel ①
H 2Use their own QCompute attention
Parallel ①
H 3Use their own QCompute attention
Parallel ①
H 4Use their own QCompute attention
Share historical K/V group A
Tokens 1–5 each retain their own K and V
Query heads 5–8
Parallel ①
H 5Use their own QCompute attention
Parallel ①
H 6Use their own QCompute attention
Parallel ①
H 7Use their own QCompute attention
Parallel ①
H 8Use their own QCompute attention
Share historical K/V group B
Tokens 1–5 each retain their own K and V

All eight heads are marked Parallel ①. Once this layer's Q/K/V is ready, these attention operations can run concurrently. A and B are cache groups, not sequential execution batches.

Shared K/V does not require identical Q. Heads query their group's records with their own Q, so scores, softmax weights and outputs may differ.

GQA groups query heads within a layer to share historical K/V, while retaining a separate record for each token. [39][40]

One “K/V group” is a history indexed by token position. Group A still contains K/V for token 1, token 2 and so on through token 5. Heads 1–4 share those records; the five tokens have not been merged into one K/V pair.

Xiao WangWhat about queries from different tokens within the same head reading the same history?

Da Wang: That happens too. Head 1's Q at token 4 and its Q at token 5 can both read token 2's retained K/V. This reuse along the sequence already exists in ordinary MHA. GQA and MQA additionally share history across query heads. [39][40]

For the same five tokens and eight query heads, MHA stores eight K/V pairs per token, 40 pairs total. Two-group GQA stores 10 pairs; MQA stores 5 pairs. The KV-head count falls; separate token records remain.

Teaching configuration: 32 layers, 32,768 tokens, head dimension 128, 2 bytes per numberKV headsBasic K/V payload
MHA: 32 query heads3216 GiB
GQA: still 32 query heads84 GiB
MQA: still 32 query heads10.5 GiB

Only K/V numbers are counted, excluding page tables, alignment and temporary workspaces. 1 GiB = 2³⁰ bytes. These are different architecture choices; deleting KV heads from an already-trained model does not preserve the same outputs. [39]

14
Xiao Wang · asks

A cache is labeled “4 bit,” so I expect one quarter of the memory. Yet the implementation has scales, unpacking and temporary tensors, and sometimes the new format is slower.

How does KV quantization save actual bytes, and why can smaller storage run more slowly?

Da Wang · explains

Representing a number with fewer bits is quantization. Think of replacing a fine scale with fewer discrete levels: store the level index plus information about the group's magnitude. Reconstructed values are usually approximations.

If one step represents 0.1, an original value of 0.32 can be stored as level 3, reconstructing 3 × 0.1 = 0.3. Fewer bits lose some precision. The scale describing “0.1 per step” must also be retained.

FIG 19Why does shrinking 32 bytes to 12 not imply an equivalent speedup?

Count actual stored bytes, then follow how compressed values participate in attention.

The same 16 numbers

BF16: 2 bytes per number32 bytes
4-bit data8 bytes+Scale4 bytes=12 bytes

Scales also occupy space. Here, 16 numbers share a four-byte scale indicating the step size. Level 3 with step 0.1 reconstructs as 0.3.

The reconstruction path also affects speed

Reconstruct the whole range first

Read low-bit data → reconstruct higher precision → write GPU memory → attention reads it

Adds storage and transfers for reconstructed data.
Reconstruct while reading

Read a tile → reconstruct and compute near the execution units → next tile

Avoids whole-range round trips but still requires unpacking and scaling.
Byte counts use Appendix C's teaching INT4 format. Other formats may have different scales and execution paths. [43][44]
Xiao WangIf the cache shrinks to one quarter, does generation become four times faster?

Da Wang: No. Reduced transfers can improve latency when cache-read bandwidth was limiting, the kernel effectively uses the compressed path and added overhead is small. For short context or ample memory, quantization may be slower. Include reconstruction workspaces in peak-memory measurements. [41]

Xiao WangK and V are both numerical vectors. Why quantize them differently?

Da Wang: K errors first change matching scores, then softmax retrieval proportions. V errors directly affect the weighted sum. Different quantization schemes may therefore suit K and V; suitability depends on the model and task. [42]

If all low-bit data is reconstructed into higher-precision tensors and retained, compressed-file size alone cannot predict runtime memory. Appendix C records actual packed-data and scale bytes separately.

15
Xiao Wang · asks

YOCO means You Only Cache Once. But tokens still pass through multiple model layers, so what exactly is counted “once”?

Does a token compute only once, or do several layers share historical K/V? Which layers share, and must earlier layers retain their own state?

Da Wang · explains

YOCO is a language-model architecture. Several later layers read global K/V produced by the earlier half, reducing duplicated storage and letting some input positions skip later computation. The tradeoff is that those later layers no longer independently produce global history; the model must be trained with this architecture. [7][8]

The paper divides the model into an earlier Self-decoder and later Cross-decoder. The first forms contextual representations and shared global K/V; each later layer reads this memory with its own query. [7]

Xiao WangDo the later layers sharing global K/V also have private local K/V?

Da Wang: In original YOCO, the later layers do not additionally retain private per-layer local K/V. They query the global K/V provided by the earlier half with their own Q.

The earlier half still retains its state. With sliding windows, each layer stores recent-token K/V. A recurrent variant instead retains an evolving state, which should not simply be called “local K/V for recent tokens.” [7][8]

No separate local cache does not mean no access to recent tokens. If the shared global cache covers tokens 1–1,001, recent positions 990–1,001 are included and remain readable by later layers.

Xiao WangDuring decoding, later layers still have their own queries, attention and feed-forward operations. Doesn't that create local K/V of their own?

Da Wang: Having a Q and further computation does not imply projecting private K/V. Ordinary Transformers usually project Q/K/V in each layer. YOCO changes that arrangement: later layers project Q, while the earlier half supplies K/V. Attention uses all three, but they need not all originate in the current layer.

Use the four-layer example, first processing 1,000 known inputs, then generating the response token by token. The main difference between these phases is which positions must traverse the later half.

FIG 20Original YOCO: what can prefill skip, and what must decoding still compute?

① First process 1,000 known tokens: prefill

The shared cache does not yet exist. To begin the response, only the final input position's prediction is needed to select the first response token.

↔ Scroll sideways; gray boxes mark positions that can skip the later half.
Initial input: every position prepares K/V; only the last continues through the later half All 1,000 known inputs traverse layers 1 and 2 to produce shared global K/V. Positions 1–999 do not traverse layers 3 and 4. Position 1,000 continues, using each later layer's Q to read the shared records for all 1,000 positions, then selecting response token 1,001 through the output layer. Earlier positions skip later-half computation, not K/V preparation. Known input: tokens 1–1,000All traverse earlier layers 1 and 2Internal values for 1,000 positionsBuild shared global K/VRetain records for tokens 1–1,000Every input token's K/V must be preparedProject K/VInput positions 1–999Can stop hereSkip later layers 3 and 4Input position 1,000Continue through layers 3 and 4Each layer's Q reads shared K/VReadOutput scores candidatesSelect token 1,001Skip later-half workPositions 1–999 skiplater Q, attention and FFN.Their K/V is not omitted.

② Feed selected token 1,001 back to predict 1,002: decode

The newly fed-back token 1,001 still traverses all four layers. The earlier half projects its K/V; later layers query the updated shared cache with their own Q and continue processing.

Blue: token 1,001's intermediate computationGreen: K/V writes and reads

↔ Scroll sideways; notes on the right correspond to layers on the left.
Original YOCO: later layers project their own Q and read the same global K/V Token 1,001 has been selected. Earlier layers 1–2 process it; layer 2's output projects new K/V into the shared cache. Blue carries the current token's state through layers 3–4, each projecting Q and performing attention and feed-forward computation. Green carries the same shared records for tokens 1–1,001 into both layers' attention. Neither creates private K/V. The output layer then selects token 1,002. Earlier half: layers 1 and 2Process selected token 1,001Layer-2 internal representationOne shared global K/V cacheExisting: records for tokens 1–1,000New: token 1,001's K/VSource: earlier layer 2's outputProject K/VLater half: model layer 3Its own QAttentionFeed-forward and other operationsLater half: model layer 4Its own QAttentionFeed-forward and other operationsLayer-2 resultLayer-3 resultOutput scoresSelect token 1,002ReadReadLayer 3: new Q and intermediate stateShared K/V arrives via green.Pass the computed state to the next layer.No private layer-3 K/V is projected.Layer 4: read again with its own QContinue processing layer 3's result,while reading the same K/V cache.No private layer-4 K/V is projected.New intermediate values ≠ private K/VThe shared cache remains for later tokens.
Both diagrams use the same four-layer teaching model. Prefill can skip the later half at positions 1–999 because only the final position's prediction is needed. All input positions' global K/V must still be prepared. In decode, each fed-back token traverses both halves. Normalization and residual connections are omitted. [7][8]

The reference implementation's later attention module has q_proj for Q and out_proj for its output; K and V are supplied externally. [8]

DeepSeek V4.1 Flash, discussed later, uses a different combination: shared global K/V plus private per-layer local-window K/V. Its reference code has window_kv_cache to generate and retain local records. Local caches exist because the architecture explicitly provides that path, not merely because later layers still execute attention and feed-forward operations. [12]

FIG 21How much space do two retention schemes use for the same input?
↔ Both bars start at zero and use the same linear scale
Memory comparison for a fixed 100,000-token input Assume 100,000 input tokens, 40 layers and 4,096 K/V bytes per token per layer. Forty complete per-layer histories occupy 16.384 GB. One shared global history plus a 128-token local window in each of the first 20 layers occupies about 0.420 GB. Both bars use the same zero and scale. Shared storage comprises 409.60 MB global K/V plus 10.49 MB local state, about one thirty-ninth of the per-layer scheme. This is a KV-size comparison, not a speedup measurement. Fixed 100,000-token input: two K/V retention schemes Same assumptions: 40 layers; 4,096 K/V bytes per token per layer. K/V memory usage (GB) 0 4 8 12 16 Per-layer history 40 complete histories 16.384 GB Shared scheme Shared global + local windows 0.420 GB The shared total consists of these two parts One global K/V history 409.60 MB Covers 100,000 tokens + Local windows in the first 20 layers 10.49 MB 128 recent tokens per layer In this example, shared K/V occupies about 1/39 of the per-layer total.
Teaching configuration: eight KV heads, dimension 128, separate K and V, two bytes per number. The shared scheme additionally retains local windows for the first 20 layers. Decimal units: 1 GB = 1,000 MB; displayed values are rounded. Only the specified K/V is counted, excluding weights, activations, workspace and metadata. A volume ratio is not a speed ratio; no real model was run.

As context grows, fixed-size local windows contribute a smaller proportion.

Both halves remain causal: a position can use only permitted history, and subsequent generated tokens traverse both halves. Describing the earlier half as encoder-like does not make it bidirectional or allow future reads. [7]

16
Xiao Wang · asks

An input has many tokens and a model has many layers. I understand that sharing caches reduces memory, but which tokens can skip layers and which must still traverse all of them?

Which tokens' computations, in which layers, does YOCO eliminate?

Da Wang · explains
FIG 22Which token positions can skip computation in which layers?

Process 1,000 known inputs to generate the first response token. The model has four computational layers. Columns denote token order; rows denote model layers.

“Compute” in a cell: retrieve information in this layer, then process the result through feed-forward operations.

“Project K/V” in a cell: generate this layer's cache from the previous layer's result, not retrieve an old cache.

Each layer is a complete block; input transformation and final output projection are separate. Four layers are used only for illustration.

↔ Scroll sideways; explanations follow each diagram.

Ordinary Transformer

Ordinary Transformer: why must token 999 still finish layer 3?Columns show positions 1–998 collapsed together, then 999 and 1,000. A collapsed column does not merge the tokens. Four model layers form the rows. Red traces token 999's layer-3 output to its layer-4 K/V, then to token 1,000's layer-4 computation. Blue tracks the last input through all four layers and the output projection to predict token 1,001. Model layers ↓ Input token order → Positions 1–998 Collapsed visually; computed separately Position 999 Position 1,000 End of input Layer 1 Compute each Compute Compute Layer 2 Compute each Compute Compute Layer 3 Compute each Compute Compute Layer 4 Project each K/V Project K/V Compute 1 Output scores select token 1,001 Earlier tokens need not each predict, but must supply K/V for deeper reads.

YOCO

YOCO: earlier positions prepare shared K/V, then skip later layersRows and columns match the previous diagram. Green arrows project a shared cache from every position's layer-2 output. It contains separate records for all 1,000 positions and is not a model layer. Green supplies that cache to token 1,000's attention in layers 3 and 4. Positions 1–999 skip those two layers. Blue still carries token 1,000 through all four layers, then predicts token 1,001. Model layers ↓ Input token order → Positions 1–998 Collapsed visually; computed separately Position 999 Position 1,000 End of input Layer 1 Compute each Compute Compute Layer 2 Compute each Compute Compute One shared K/V cache: all 1,000 tokens K₁/V₁ … K₉₉₈/V₉₉₈ K₉₉₉/V₉₉₉ K₁₀₀₀/V₁₀₀₀ This is a cache, not layer 3 or an added model layer. 2 Layer 3 Skip each Skip Compute Layer 4 Skip each Skip Compute 3 Positions 1–999: skip the layer-3 and layer-4 cells. Output scores select token 1,001

Next: continue the same response

Next generation step: feed token 1,001 back to predict 1,002Purple begins with the selected token 1,001, traverses model layers 1–4, updates the corresponding K/V along the way and uses the output layer to predict token 1,002. This continues the same response. Already selected token 1,001 Traverse all four layers 1 2 3 4 As each layer runs, update this layer's K/V Output layer predicts token 1,002 4 Small labels: layers. 1,001/1,002: token positions.
The first two diagrams share the same axes. Positions 1–998 are only visually collapsed; each retains distinct computation results and K/V. The green cache row is not another model layer. For old positions at layer 4 of the ordinary model, only the necessary K/V projection is shown. This comparison concerns generation following known input. If training or scoring requires predictions at multiple positions, their later-half computation cannot be skipped this way.

Which models have adopted these ideas?

The following are representative cases checked as of September 30, 2026. Relevant DeepSeek and Google models use modified YOCO or related cross-layer K/V sharing; GLM, Kimi and Qwen illustrate different approaches.

ProviderModel checkedAdoption and distinctions
DeepSeekDeepSeek-V4.1-FlashExplicitly inspired by YOCO, with modifications. The report states this influence and combines it with grouped sharing; public code reuses K/V produced by other layers. [14][12]
GLM / ZhipuGLM-5.3
GLM-5.3-Flash
The inspected implementation is not YOCO. GLM-5.3's IndexShare shares selected historical positions, while K/V remains per-layer. Flash mixes linear and sparse attention, with state maintained separately by each layer. [15]
Kimi / Moonshot AIKimi-K3The inspected implementation is not YOCO. It uses hybrid attention and distinct per-layer state; historical-K/V layers independently generate their caches. [16]
Qwen / AlibabaQwen3.8-2.4T-A95B
Qwen3.8-Flash-Next
The inspected implementation is not YOCO. It uses hybrid attention; historical-K/V layers produce their own K/V rather than having several later layers read one earlier-half cache. [17]
GoogleGemma 3nRelated cross-layer sharing is implemented. Intermediate layers produce local and global K/V for several subsequent layers to share. [18]

Comparisons apply only to the public models checked here. Related cross-layer sharing is not identical adoption of YOCO.

Reported evaluation: DeepSeek reports that its YOCO-inspired CED architecture nearly halves input-processing computation while maintaining performance close to the baseline. This is the vendor's reported result. [14]

Advanced reading: how can attention work with fewer stored numbers?
17
Xiao Wang · asks

Some models store a shorter numerical record per historical position and reconstruct K/V from it. If each read expands every record, does saved memory merely become extra computation?

How can attention compute with shorter historical records?

Da Wang · explains

Some architectures define K/V as fixed linear transformations of a shorter vector generated for each historical position. That shorter vector is a latent representation.

When the expansion rule allows it, work can move to the query side: transform the current query once, then match many compact records directly. Consider a three-element K generated from two stored numbers.

FIG 23Store one fewer number; move some work to the query

This example shows unscaled matching under a linear relationship, without positional rotation.

Model rule: two numbers generate a three-element K

Retain only [2, 1]; when expanding, the third element is the sum of the first two:K = [2, 1, 3]

The same Q = [1, 0, 2], two equivalent calculations

Expand each historical K, then match

1×2 + 0×1 + 2×3

Matching score: 8
Transform the query once, then match compact records

The query becomes [1 + 2, 0 + 2] = [3, 2].

3×2 + 2×1

Matching score: still 8

One transformed query can be reused across many compact records, avoiding expansion of every historical K for this query.

This teaching example assumes the specified model structure. It does not show that arbitrary existing K vectors can be shortened losslessly.

If V also comes from a linear transformation of compact records, first form their weighted sum, then expand that sum. The architecture must support these relationships; positional rotation and nonlinear operations cannot be reordered arbitrarily.

There is still one record per historical position, just shorter. Combining history into fixed-size state is a different architecture. The next question demonstrates writing and reading a small matrix; restoration conditions appear in Appendix A, Exercise 3.

18
Xiao Wang · asks

Shorter historical records do not change their count: every additional input token still adds one record. History continues growing with the text.

Could new information be merged into a state of constant size? Without individually retained history, how does a new Q retrieve information?

Da Wang · explains

A model can use a different attention rule. In one classic form of linear attention, prescribed functions transform Q and K before their similarity is computed. Its algebra lets historical contributions accumulate, so a new query directly reads accumulated state. “Linear” here means that, for fixed dimensions, the main attention computation over a sequence grows linearly with its length. [68]

Fix one head in one layer. With two-element transformed K and two-element V, retain a 2×2 matrix S and a two-element vector z. S accumulates outer products of transformed K with V, allowing Q to calculate the score-weighted V sum. z accumulates transformed K, letting the same Q calculate total matching score. Dividing the first by the second converts scores to retrieval weights summing to one. We give the transformed values directly to show the state updates and reads.

FIG 24Two writes, one query: six numbers replace an expanding record list

Two input tokens are given. Each position first incorporates its K/V into state, then reads it with Q. Initially S and z are zero. Cells are numerical components; matrix rows and columns are not token positions. The two panels show the same state at different times.

Position 1

Transformed K12
V20

Multiply each K component by each V component

1×21×02×22×0

Add those four products to S; add transformed K to z

S after this update2040z after this update12

Position 2

Transformed K21
V03

Multiply each K component by each V component

2×02×31×01×3

Add those four products to S; add transformed K to z

S after this update2643z after this update33

Position-2 query: transformed Q = [1, 2]

Read the weighted V sum from S and total score from z, using the same Q.

Read weighted sum from S
12×2643→1012

First coordinate: 1×2 + 2×4 = 10
Second coordinate: 1×6 + 2×3 = 12

Compute total score from z
12·33→9

1×3 + 2×3 = 9
z is [3, 3]; 9 is this Q's dot product with z.

Why divide by 9?

Expand the two records to interpret that 9. This only explains the same calculation; recurrent reads need not retain or rescan the two keys.

Record 1: match Q with K1×1+2×2=5Corresponding V = [2, 0]
Record 2: match Q with K1×2+2×1=4Corresponding V = [0, 3]
Total score: 5 + 4 = 9

Nine weight units: five for record 1, four for record 2

Record 1: 5/9 ≈ 55.6%Record 2: 4/9 ≈ 44.4%

Weighted read: 5/9 × [2, 0] + 4/9 × [0, 3]

Accumulate raw-score contributions, then divide by 9[10/9, 12/9] ≈ [1.11, 1.33]Equivalent to mixing the two V vectors with weights 5/9 and 4/9, then continuing computation

These nonnegative matching scores from transformed Q and K are normalized directly, without another softmax. Ordinary softmax attention also has weights summing to one, but first exponentiates the matching scores.

This teaching example follows the specified linear-attention rule and omits the small constant commonly added to avoid division by zero. State size depends on head count and vector dimensions; one head is shown. Constant size does not guarantee less memory than a few explicit records on short inputs. [68][69]

Xiao Wang What exactly is saved by merging history, and what can be lost?

Da Wang: Compare this linear-attention rule with ordinary full attention retaining per-position K/V. Hold layer count, heads and dimensions fixed and examine inference-time attention state and computation.

Scroll tables sideways on small screens.

AspectBenefitCost and conditions
How does retained history grow?Recurrence retains only current S/z, independent of token count. This head need not append records indefinitely in a long conversation.Contributions are mixed; an original-position record cannot be retrieved independently as in a full KV cache. Fixed-size, finite-precision state cannot guarantee lossless retention of every detail from arbitrarily long history.
How much is read and computed per generated step?A new Q reads fixed-size S/z rather than scanning all historical records. With fixed dimensions, attention computation over the input grows linearly with length.Matrix updates and feature transforms still cost work. For short inputs, a fixed matrix need not be smaller or faster than a few K/V records. Actual speed depends on implementation and hardware, not the word “linear.”
Can it directly replace an existing model?The same linear-attention rule supports either explicit summation or S/z recurrence, avoiding recomputation of old records for each query.It changes the ordinary softmax-attention rule and needs corresponding model design and training. Swapping only a cache container cannot guarantee unchanged answers or capabilities.

State shape, updates and reads can be checked in the authors' implementation; computational complexity is discussed in paper §§3.2–3.4. [68][69]

Returning to an earlier position requires old state or enough information to undo updates. Current S/z does not automatically preserve earlier versions. Save snapshots, recompute from an earlier point, or, for the additive example, retain contributions to undo the relevant updates. If speculative decoding tries four steps and accepts two, simply “delete the last two K/V records” is not the right operation here. See state rollback and the storage comparisons in Appendix A, Exercise 3.

Positions are shown sequentially to expose updates. Known input can still use batching and prefix accumulation; it need not run the entire model serially for every position. A hybrid model with full-history attention layers need not have constant-size total state.

06 / DeepSeek case study

How does DeepSeek-V4.1-Flash reduce cache and computation costs?

Long input has three costs: retaining history uses memory, processing it requires computation, and each generated token still searches history. DeepSeek-V4.1-Flash addresses each with corresponding tradeoffs.

19
Xiao Wang · asks

K determines what to attend to and V supplies the retrieved information. Those seem like different purposes, so I would expect two separately stored vectors.

How can the same vector serve both K and V? Can existing model caches simply be merged, or must the architecture and training support it?

Da Wang · explains
FIG 25One vector serves K and V: eliminate two separately stored payloads per record

Track historical token 3's record m₃: stored once, used by two different computations.

↔ Scroll sideways on small screens; explanations follow each panel.

One stored object feeds two paths

Two uses of the same historical vectorEach historical token retains its own vector; m3 is highlighted. One path uses the vectors as keys to match the current Q and obtain weights. The other uses the same vectors as values in the weighted sum. Tokens and layers are not merged into one state.Store one set of historical vectorsm₁m₂m₃…mₙ1Current tokenQuery QAs K: match QObtain retrieval weights2As V: the same vectorsCombine weighted values3The same dataWeightsm₃ is historical token 3's record; other records participate too.
Sharing a vector between K and V is separate from sharing a cache across model layers. See the next question for positional rotation and its inverse.
20
Xiao Wang · asks

My understanding is that RoPE applies positional encoding only to Q and K. With shared K/V, shouldn't a RoPE model retain rotated K and V without position information?

After reading the cache, do we inverse-rotate K according to the current query's position?

Is that correct? How do caching, retrieval and inverse rotation connect?

Da Wang · explains

“Rotated K plus V without this layer's rotation” is a common cache format, but reading it does not first inverse-rotate K. The current Q is rotated for its own position and matched directly with cached K. The inverse rotation discussed here happens later: first form a weighted sum of retrieved information, then transform that new result.

Clarify “sharing” first: heads or layers sharing a KV group does not mean K and V are the same vector. This question concerns the latter design from the previous question. Rotary positional embedding, RoPE, makes position affect Q/K matching; this special architecture also uses the rotated vector as V.

Basic RoPE equations and relative position

Fix one attention head. Let (a, b) be any pair of Q or K coordinates and p the token's sequence position. Basic RoPE rotates that pair as follows:

[a′b′]=[cos(pθ)−sin(pθ)sin(pθ)cos(pθ)][ab]

θ is the angular increment per position for this coordinate pair, in radians; different pairs can use different θ. p is sequence position, not vocabulary ID or the cache's GPU address.

Write the whole-vector rotation as R(p). Query position is m, key position n, and qm, kn are unrotated vectors. T denotes transpose. With fixed rotation frequencies:

(R(m)qm)T(R(n)kn)=qmTR(n−m)kn

Matching separately rotated vectors combines the rotations into relative position n−m. Moving cache storage does not change those positions. Changing preceding context can also change the unrotated vectors, so adjusting angles alone does not establish cache compatibility. [71]

FIG 26From cache to output: which numbers does inverse RoPE transform?

First distinguish two cache formats, then follow one vector serving K and V to calculate a new retrieved result.

1Cache format: separate K/V or one vector serving both?

Ordinary RoPE: K and V stored separately
KRotated at its historical position
VNo RoPE rotation from this layer

Rotate the current Q and match cached K directly. Read and combine V; there is no later output inverse rotation of the kind shown below. [11]

This architecture: one vector serves K and V
Store one rotated vector
As K: matching scoresAs V: retrieved information

The current Q still matches cached K directly. Because the values read as V also carry rotation, the newly retrieved result needs further transformation. [12]

Matching in both cases:
Rotate Q at its current positionMatch cached K directlyObtain record weights

Do not inverse-rotate K first. Relative Q/K rotation already appears in the match.

2Retrieve: weight each record, then add corresponding coordinates

Follow only the shared-vector architecture now. Process token 10 and, for illustration, read only records 8 and 9, each represented by two numbers. The table already contains rotated cached values.

Q and K first multiply and sum corresponding coordinates: dot-product scores. After required scaling and masking, softmax converts these into retrieval weights. Softmax is an attention operation, not an extra model layer. Given weights 0.75 and 0.25, examine their effect on V.

Record 8Cached values
[2, 0]
× 0.75Multiply both numbers by
[1.5, 0]2 × 0.75 = 1.5
0 × 0.75 = 0
Record 9Cached values
[0, 4]
× 0.25Multiply both numbers by
[0, 1]0 × 0.25 = 0
4 × 0.25 = 1
Add both contributions: blue with blue, green with green
Coordinate 1 of each vector1.5 + 0 = 1.5
Coordinate 2 of each vector0 + 1 = 1
The newly retrieved vector[1.5, 1]

This is weighted aggregation. Contributions from two records become one new two-element vector. Their caches are neither concatenated into four elements nor overwritten.

3Inverse rotation comes last, acting on the new vector

Continue the teaching rule of 10° per position. The current query is position 10. Rotate the two newly aggregated numbers by −100°, rather than modifying cached K.

Newly aggregated result[1.5, 1]
Inverse RoPERotate by −100°
New rotated result, approximately[0.724, −1.651]Continue downstream computation

−100° is the additional rotation, not the result's final orientation. Rotating the new sum once equals rotating each weighted contribution this way and then adding. Contributions are expressed relative to the current query position.

The original cache remains:Record 8 [2, 0]Record 9 [0, 4]Available for the next step's reads.
Vector values, weights and 10° increments are teaching choices; the two numbers are not token indices. Real implementations have more coordinates and records and inverse-rotate only the RoPE portion of the output. This does not recover an ordinary sum of unrotated V vectors. [12][36]

“V has no position information” should be narrowed to “V did not receive this layer's RoPE rotation.” V comes from intermediate computation and may already include position-related information from earlier layers. No rotation here does not mean no positional information at all.

Code comparison: cache writes, weighted summation and inverse output rotation

A common Llama implementation calls apply_rotary_pos_emb on Q/K before updating the cache, storing rotated K and V without that rotation. [11]

The shared-vector implementation first rotates and stores kv. sparse_attn uses it both to score Q and to aggregate weighted coordinates. Only then does apply_rotary_emb(..., inverse=True) transform the output. [12]

matching_scores = dot(current_rotated_Q, cached_K)
retrieval_weights = softmax(matching_scores)
retrieved = sum(weight[i] * cached_vector[i] for each record i)
next_input = inverse_rotate_RoPE_part(retrieved, current_position)

The model is designed and trained this way; merging arbitrary K/V does not reproduce it. num_key_value_heads = 1 indicates a small KV-head count, not that K and V are one vector.

21
Xiao Wang · asks

We usually append new content to an old conversation and submit the full history.

If the model receives all that information, why select historical records when generating the next token?

Da Wang · explains

Because history being available differs from history participating in this particular computation.

Assuming the conversation is neither truncated nor replaced by a summary, its full submitted history belongs to this context. The model computes K/V for it or reuses retained K/V.

Caching only avoids regenerating historical K/V. Every new token still produces a current Q, searches for historical information and combines the retrieved content.

Full attention includes every permitted historical record in this process. With long history, each step reads and computes over substantial data. Fitting in GPU memory does not guarantee fast reads.

One DeepSeek V4.1 Flash approach uses shorter index vectors to score and select some global K/V. Main attention reads those selected records together with the current layer's recent local-window K/V. [14]

What step does indexing add to ordinary Transformer attention?

Consider one query in one layer using already-prepared K/V. Compare with the earlier softmax explanation: select participating records first, then compute each V contribution.

ComparisonOrdinary Transformer
Full attention
DeepSeek V4.1 Flash
Index selection + main attention
Which records enter main attention?All K/V records permitted for this query.Selected global records plus this layer's recent local-window records.
How are scores calculated?Main Q directly matches the participating main K records.Shorter index Q/K first score and select records; main Q/K then rescore the records that actually participate.
How are V contributions determined?Softmax of main-attention scores supplies weights for combining V.Again, softmax of main-attention scores supplies retrieval weights. Index scores select records; they are not directly multiplied by V.
What computation remains?Full attention still scores a record and includes its weighted V contribution even if its eventual weight is tiny.An unselected record outside the local window does not enter this main-attention operation. Its K/V may remain cached for later queries.

Some DeepSeek layers reuse an earlier layer's selected list but still calculate retrieval weights with their own main Q. Compare ordinary attention with the original Transformer paper ↗, and selection with the pinned report and code. [12][14]

Submitting 100 conversation rounds does not require every generated step to perform the same full attention over every corresponding record. They can remain cached and be selected differently at different steps.

Selection reduces per-step historical work but may omit useful information. Indexing itself costs time and storage, so computing full attention can be cheaper on short history.

22
Xiao Wang · asks

You said index vectors are produced by transformations using learned coefficients. But training has never seen the document I submit today.

What are these coefficients, and how can they be learned before my document exists?

Da Wang · explains

What is learned in advance is a rule for processing input. Once your document arrives, the model derives new numerical representations from it and applies the learned transformations.

For example, reduce a record to [2, 1] and define one index coordinate as:

Multiply the first number by 0.6 and the second by −0.2, then add.
2 × 0.6 + 1 × (−0.2) = 1.0

2, 1 comes from this input; 0.6, −0.2 are retained trained parameters. A new record [5, 3] uses the same rule to give 5 × 0.6 + 3 × (−0.2) = 2.4. Content and results change; parameters do not change during ordinary inference.

1.0 or 2.4 is one index-vector coordinate, not the final relevance score. Several parameter sets produce complete index vectors, then historical index K is scored against current index Q. These numerical coefficients are illustrative, not actual model parameters. [12]

Distinguish two meanings of “weights”:

  • Model parameters: retained values such as 0.6, −0.2 that apply the same transformation to different inputs.
  • Retrieval weights: values such as 0.75, 0.25 in Question 20, calculated from this query and its history to determine V contributions now.

Explaining how parameters acquire those values requires the training feedback that corrects their judgments. The next question develops that process.

23
Xiao Wang · asks

I know masked-language tasks hide words and ask a model to recover them. For historical selection, who tells the indexer which records matter?

How does the model learn relevance? Did DeepSeek V4.1 Flash add a new training task for it?

Da Wang · explains

An indexer can have its own training objective to learn score allocation. First distinguish two operations both called masks:

  • Hide text and predict the original: replace “Xiao Wang lives in Beijing” with “Xiao Wang lives in [MASK]” and predict “Beijing.” This is masked language modeling, used by BERT. [38]
  • Prevent a position from looking ahead: predict the next token from preceding context and compare with the actual continuation. The causal attention mask forbids future-position reads. It defines visibility, not which historical records are most useful.

V4.1 Flash's language backbone is a causal next-token model. How its selector learns to choose history is a separate training question. [14]

DeepSeek V3.2 gives a concrete public example: compute full main attention, then train a small indexer to imitate its allocation. Main attention reads and combines K/V; the indexer cheaply preselects records. The diagram depicts the initial dense warm-up stage of V3.2 indexer training. [37]

The starting V3.1-Terminus model is already trained before the indexer is added. Main-model parameters are frozen only during indexer warm-up; training resumes afterward. [37]

FIG 27How does training reveal an indexer scoring the wrong records?

V3.2 dense warm-up: freeze the main model temporarily and update only the indexer. The three records and percentages are teaching choices.

Same training example · Same query positionUse history to answer: how much did sales grow this week?
1Main-attention reference2Current indexer allocation

Every full bar represents 100%; each allocation across A/B/C sums to 100%.

ALast week's sales: 80
Reference45%
Indexer10%
BThis week's sales: 100
Reference45%
Indexer20%
CThe office is on floor three3Clearly overallocated
Reference10%
Indexer70%

Convert that discrepancy into parameter-update feedback

KL divergence compares reference and indexer allocations

For this query position, pi is main attention's reference weight for historical record i; ri is the indexer's softmax weight. Each distribution sums to one.

DKL(p∥r)=∑ipiln(piri)

∑i sums over historical records; ln is the natural logarithm. KL divergence is nonnegative and zero for matching distributions. Its direction here is reference p relative to indexer r; reversing them generally changes the value.

With p = (0.45, 0.45, 0.10) and r = (0.10, 0.20, 0.70), the result is approximately 0.847. [37]

Compare two allocations

KL divergence measures their discrepancy as a training loss.

Calculate parameter influence

Backpropagate how the loss changes with parameters.

Update indexer parameters

Adjust indexing and scoring coefficients slightly toward the reference allocation.

Repeat computation, comparison and adjustment over many examples. Retain the resulting parameters to score new inputs.

These percentages allocate attention across historical records. They are not next-token vocabulary probabilities or probabilities that records are truly useful. The diagram illustrates V3.2's disclosed training procedure, not undisclosed V4.1 details. [37]

Xiao Wang The indexer imitates main attention using KL divergence. Next-token training often uses cross-entropy, and there is also entropy. Which training objectives suit each?

Start with the training goal, then choose the measure

Da Wang: Sometimes a model learns known answers, sometimes it imitates a probability distribution, and sometimes it should explore more choices. These quantities emphasize different things, but do not define three unrelated task categories. One training procedure can combine them.

Let ri be the model's probability for candidate i and pi an existing target probability. Candidates may be vocabulary tokens, classes or historical records, but one comparison must use the same candidate set. Equations use natural logarithms; ∑i sums candidate contributions.

Scroll tables sideways on small screens.

Quantity and meaningSuitable training goalHow it is used
EntropyH(r)=−∑irilnriInspect r itself: how dispersed are its choices?Encourage exploration. In reinforcement learning, for example, a policy should pursue reward without prematurely choosing only one action, retaining opportunities to try alternatives.Rewarding higher entropy is equivalent to subtracting weighted entropy from a minimized total loss. Low entropy means greater certainty, not greater correctness. A model confidently choosing the wrong answer also has low entropy. [73]
Cross-entropyCE(p,r)=−∑ipilnriUse target p to assess how much probability r gives desired outcomes.Learn known answers. Common uses include multi-class classification, next-token pretraining, masked-word prediction and supervised fine-tuning on demonstrations. The target can also be a full distribution rather than one answer.Minimize cross-entropy to raise target-required probabilities. If A is correct and receives 100% target probability, this example's loss is −ln rA. No sampled token must first be judged correct or incorrect. [72]
KL divergenceDKL(p ∥ r)How different are the distributions? Equation above.Learn a reference distribution. A student imitating a teacher's candidate probabilities is knowledge distillation. Here, the indexer imitates main attention's historical allocation.Fix reference p and adjust r to reduce DKL(p ∥ r). It compares complete distributions, not merely agreement on the top choice. Reversing the direction can change the training emphasis. [37][74]
Why can cross-entropy and KL sometimes be interchangeable?
CE(p,r)=H(p)+DKL(p∥r)

If target p is fixed and only predictions r change, H(p) stays constant. Under the same probability definitions and weighting, minimizing cross-entropy and KL in this direction gives the same update direction for r. H(p) is the target's entropy, not H(r), used above to encourage exploration. [72]

In the earlier MoE training example, the correct token is A with predicted probability 20%, giving cross-entropy −ln 0.2 ≈ 1.609. The target is “A 100%, others 0%,” with zero entropy, so KL also equals 1.609. If a teacher supplies probabilities across several candidates, cross-entropy can learn that distribution too.

Balanced MoE expert use does not simply mean maximizing entropy. Making every token equally uncertain across experts differs from distributing an entire batch's work sensibly. The earlier Qwen3-MoE implementation adds a dedicated balancing auxiliary loss to the prediction loss. [65]

Where did the main-attention reference come from? The backbone already learned text prediction: predict subsequent tokens from context and adjust parameters, including attention parameters, against the correct text. The indexer uses that learned retrieval allocation rather than manually labeled relevant/irrelevant historical records.

In V3.2's subsequent sparse training, the backbone resumes updates using text-prediction loss, while the indexer learns from allocation differences on selected records. The two training signals are separate. At inference, the indexer selects records before main attention; full main attention need not run first as a teacher for every answer. [37]

“Relevance” here means approximating main attention's historical selection. A high weight does not prove a record is essential to the correct answer. Task outcomes must reveal whether useful information was omitted.

An additional selector-training objective was already explicit in V3.2; it is not a new masked-word task. The V4.1 Flash report describes indexer optimization and gradient aggregation but does not disclose an equally complete indexer-loss definition. Participation in training is established; an identical complete procedure is not. [14]

24
Xiao Wang · asks

In ordinary token-by-token generation, token 9 was selected in the previous step. Feed it into the model now to generate token 10; valid K/V for positions 1–8 remains cached.

Suppose a layer's global attention selects records at positions 2 and 9.

Must unselected records 1 and 3–8 be recomputed? How do records 2 and 9 affect token 10?

Da Wang · explains

Neither unselected records 1 and 3–8 nor selected record 2 needs recomputation. Record 2 is read from the old cache. Records 1 and 3–8 remain retained but do not participate in that layer's global main attention.

Record 9 is newly prepared K/V for this step's input. Token 9 was just selected and is only now processed by the model. Its new state must not be conflated with the old cache for positions 1–8.

The query is Q₉ generated at position 9 in the current layer. Selected records affect position 9's subsequent internal values. The model eventually uses that position's computation to select token 10.

FIG 28How do reads of records 2 and 9 affect token 10?

Known: token 9. Goal: select token 10. The first two steps expand two records in one head of one layer; step 3 connects to subsequent model layers.

1Use K to determine how much to read

Q₉Position 9
Its query in this layer
K₂75%
K₉25%

Record 2 comes from the old cache; record 9 is prepared now. Matching Q₉ with K and applying softmax gives assumed retrieval weights of 75% and 25%.

2Weight V coordinates, then add

V₂[2, 0] × 0.75 = [1.5, 0]
V₉[0, 4] × 0.25 = [0, 1]
Add matching coordinates[1.5, 1]

The first coordinate is 1.5 + 0; the second is 0 + 1. This is retrieved information, not yet a token.

3Continue computation, then select token 10

Merge retrieved information intoPosition 9
Finish this layer and pass to subsequent layers
Later layers: still process position 9 to predict position 10
A later reindex layerMay select 5 and 8
A following reuse layerKeeps 5 and 8
Output layer scores the vocabularyToken 10
token

Purple boxes precede the output layer. Some layers reselect records; others reuse the list. Reads can change within the same token-generation step.

Even with the same list, each layer calculates weights using its own Q. Token 10 is selected only after all layers finish. Explanation below

Records 1 and 3–8, unselected here:Their old K/V remains available to later layers. Being unselected does not require recomputation.
Numbers and later-layer choices are illustrative; assume positions 5 and 8 are eligible. 75%/25% allocates reads across history, not token-10 vocabulary probabilities. Actual DeepSeek attention also reads the local window, omitted here. [12][14]

Retrieved information does not immediately become text. Attention output is transformed, merged with position 9's state, and processed by feed-forward and later layers. The output layer finally computes raw candidate vocabulary scores, logits, then argmax or sampling selects token 10. [12]

Different layers may read different history while generating the same token. Earlier layers change position 9's state, from which later layers form their own queries and potentially different scores. Selecting records 5 and 8 later is still part of predicting token 10, not token 11.

V4.1 does not reselect in every layer. Reindex shares historical cache but reselects with the current layer's index query. Reuse also reuses an earlier selection list. The former may change the list; the latter saves another selection operation. [14]

Reusing a list does not reuse its attention weights. Two layers reading records 2 and 9 might allocate 75%/25% and 40%/60%. Each still matches its own main-attention Q against K, allowing different weights and aggregates. These ratios are illustrative. [12]

Noncontiguous records can be read directly. Positions 2 and 9 keep their original indices; they are not renumbered 1 and 2. Reading record 2's already-computed state does not require recomputing the context that produced it. Selection neither deletes nor rewrites history.

The indexer may rescore old index features to choose records. If a later layer selects record 5, it reads the corresponding valid cache. Rescoring and changing the list are not regeneration of old K/V.

Further reading · Another kind of memory
25
Xiao Wang · asks

A KV cache retains results computed for a particular input. DeepSeek-V4.1-Flash also adds a memory module called Engram.

What is Engram, and how does it differ from a KV cache?

Da Wang · explains

Engram is a memory module inside the language model, trained with it. Its learned features reside in a numerical parameter table belonging to the model's weights. Each row, an entry, is a vector. [9][10]

It aims to reduce repeated work recognizing familiar combinations, such as names, places and fixed phrases. The table supplies learned representations before the backbone continues interpreting the sentence and reasoning. [10]

A local sequence of n tokens is an n-gram. Expand its lookup, then compare it with KV caching.

FIG 29Two memories: request state and a learned lookup table

Both paths retrieve data, but its origin differs. The lower panel expands the original “New York” example.

↔ Scroll sideways on small screens; explanations follow each panel.

KV cache: recover state computed for this history

Input identity locates computed state for this request historyThe complete prefix identity locates a cache entry and the K/V or restoration state for that input.This input'sfull prefix IDCache lookupFind retained stateDynamic K/VPer-request state1

Engram: read learned parameters, then use current context

Hash “New York” to table row 23, then incorporate current contextAssume “New York” is two tokens hashing to illustrative row 23. Rows 7, 23 and 108 are shown; row 23 contains −0.41, 0.27, 0.09. Read trained parameters by address, then project, gate and fuse them using the current context.Local tokensNew | YorkHash lookupAddress: row 232Learned parameter table7[0.12, -0.38, 0.65, …]23[-0.41, 0.27, 0.09, …]108[0.56, 0.13, -0.72, …]3Read row 23's numbers[-0.41, 0.27, 0.09, …]Current sentence'scomputed stateTransform, gate and mergeMerge into current computation4
Row numbers, vectors and two-token segmentation are teaching choices, not actual parameters or measurements. The general mechanism follows the official Engram demonstration, whose configuration is not treated as V4.1 production configuration.

“Store every n-gram” needs qualification: the hash table is finite, not a unique collision-free slot for every possible combination. Different combinations may share addresses; multi-head hashing and training address this compression. Unlike exact KV caching's aim to avoid mistaken identities, collisions here are part of the representation's design conditions. [9]

V4.1's official configuration uses 2/3/4-gram memory at layers 2 and 15, zero-based code indices 1 and 14. This is model-specific; the original Engram demo uses different settings. Combining static entries with current textual computation still produces context-dependent results. [12][9]

07 / INDUSTRIAL PRACTICE · Pi's caching tradeoffs

Industrial practice: a pinned Pi case study

Pi is an open-source coding agent that reads/writes code and runs tests through tools. Using version 0.99.1, released 2026-09-29, this chapter examines tool-result selection, changing tool sets, cache expiry and conversation compaction. [19]

26
Xiao Wang · asks

An agent fixing code reads three test logs, containing many successes and a few failures. The model must use them to locate errors.

Must every log enter the conversation in full? If Pi saves complete logs externally and returns only key evidence, how does the model know what happened?

Which tool-processed data is returned to the model?

Da Wang · explains

Distinguish raw data obtained by the program from content actually sent to the next model call. An agent can retain full data, return evidence needed for a decision first and read details later.

FIG 30Which tool-execution data enters the model conversation?

Scenario: read three test logs to locate failures. This example's script arranges storage and filtering. Arrows distinguish external files from model input.

↔ Scroll sideways on small screens; explanations follow each panel.

The model writes a script; Pi executes it and can organize results before returning them

How three logs become the next model input through a scriptThe model generates a script; Pi calls tools to obtain full logs. The script saves the full text and extracts failure evidence, exit status and file locations. The next model call receives prior history, the outer script and selected results. Saving an external file does not automatically insert it into model input.The model generates a Codemode scriptRead three logs and aggregate failure evidence1Pi runs the script and connected toolsTools return full logsRaw data from three logsScript filtersFailures, stack traces, statusSave full logs externallyand records their locations2text() emits key resultswith full-log locationsNext model call receivesPrior history + outer script + selected results3
Selected results retain failed cases, relevant traces, execution status and full-log locations for follow-up. Pi can also save oversized script output externally and return an excerpt with a reading location. [32]
27
Xiao Wang · asks

Pi has ten conversation rounds and a registered log-query tool. It was callable inside scripts; now the model wants to invoke it directly as a standalone tool.

What does “loading a tool” change? Does enabling this usage rewrite the opening directory and invalidate the old cache?

How can Pi add a tool invocation mode while keeping the original prefix stable?

Da Wang · explains

The change is the standalone tool declarations supplied to subsequent model calls. Pi records the change and, on supported interfaces, expands new descriptions in later messages while keeping the original Codemode directory.

FIG 31Which part changes when a tool is “loaded”?

Continue the log example. The teaching tool log_query is registered in Pi and already callable by Codemode. This round adds it to the tools the model can invoke independently.

↔ Scroll sideways on small screens; explanations follow each panel.

First separate execution capability from declaration: only the bottom row changes

Loading adds a standalone declaration; backend and script capability remain unchangedlog_query is registered and script-callable before loading. The backend can execute it, but the model lacks its complete standalone declaration. tool_search activates it for the next model request. Backend execution and script capability are not newly created.Example: Pi has registered log_queryBefore loadingAfter loadingPi backendRegistered and executablePi backendRegistered and executableUnchangedCodemode scriptAlready callableCodemode scriptAlready callableUnchanged2Model's standalone tool callNo full declaration supplied yetModel's standalone tool callName + argument schema1tool_search finds and activates it for the next request.

Now inspect input placement: activation does not rewrite the original directory

Tool declarations are added later while the Codemode directory remains stableThis Pi version puts a placeholder in round 1 to handle the author's observed first-enable cache miss. It is not mandatory for all dynamic tool loading. Existing Codemode descriptions, directory, system prompt and ten conversation rounds remain unchanged. After activation, the interface expands log_query's declaration in later messages instead of rewriting activation status into the original directory.Pi's adaptation: a placeholder in round 1Pi placeholder: defer_loading: true3Already-computed prefixCodemode directorySystem promptPrior rounds 1–104Expand log_query's declaration later this roundApplicable path: tool_reference / tool_addition5Keep the old prefix; append new tool information.
This diagram separates backend execution, script callability and a standalone model declaration. The API sketch follows the applicable Anthropic path in the pinned Pi version. Directory stability alone does not guarantee every request hits.
Xiao WangWhy not search for log_query only when needed and expand it later? Why predeclare a placeholder?

Da Wang: Specific tools can indeed be discovered when needed. Pi predeclares a never-activated placeholder so the server's accompanying deferred-loading prompt exists from the first round.

Pi's source comments report that the first defer_loading introduces a hidden prompt and that a complete cache miss was measured without the placeholder. Thus, expanding log_query later could still coincide with an earlier server-side change. This is Pi's adaptation to behavior reported by its author, not a universal requirement of dynamic tool loading. [20]

Xiao WangWhy is there a tool prompt? Does structured configuration require enabling deferred loading beforehand?

Da Wang: A tool prompt communicates available tools, argument formats and invocation syntax. Anthropic assembles tool definitions, settings and system instructions into model input as part of its interface implementation. JSON can itself communicate tool definitions; needing that information does not imply first enabling deferred loading must alter the prefix. [34]

Public sources do not provide the complete accompanying prompt or explain why enabling it first would change the beginning. Current interface documentation describes later expansion and cache preservation without listing the first-enable exception in Pi's comment. The evidence explains Pi's motivation, not an inevitable behavior of all current interfaces. [31]

Xiao WangIf designing an agent now, is there a better approach than placeholders?

For log_query, expose only two fixed entries from round 1: search_tools returns names, purposes and schemas; call_tool validates and executes a chosen tool. Keep their names, descriptions and argument formats stable within the session. The following is a teaching interface.

When log access is needed, search through the fixed entry:

search_tools({query: "read test logs"})

The agent returns log_query's purpose and argument information such as run_id in a later message. After reading it, the model invokes the other fixed entry:

call_tool({
  name: "log_query",
  arguments: {run_id: "run_42"}
})

log_query is an argument to call_tool; the model service still sees the same two tool entry points. The agent executes the query and appends its result. Provided entry definitions stay stable, changing business tools need not invoke first-time server-side defer_loading or require its placeholder adaptation.

A practical example is Cloudflare's API MCP service: search discovers API usage and execute runs invocation code. The full catalog stays server-side and relevant portions are retrieved on demand. Its code-execution entry differs from the generic call_tool sketch, but both keep a few stable entry points with operations discovered when needed. [35]

This comparison concerns control over tool changes: fixed entries reduce reliance on platform-native dynamic-tool mechanisms. They still require on-demand usage information, argument validation, permissions and error handling. This does not establish lower cost, faster execution or better correctness on every task than native deferred loading.

28
Xiao Wang · asks

The model has just processed a long context, then waits for a six-minute test. If the server cache expires after five minutes, the next request may recompute the old input.

How does Pi prevent expiry while waiting for tools, and is an extra keep-alive request worthwhile?

Da Wang · explains

Before expected expiry, Pi attempts to replay the original model request with an output limit of one token. This cache keep-alive reuses the input to try to preserve availability. Its response is not added to chat, but its usage is charged to the session. [21]

Introduced in 0.86.0, the feature's defaults are:

  • Keep alive while the task runs. Stop when the task ends. idle mode additionally tries during inactivity between tasks.
  • Estimate value first. Usable cache-lifetime information and safely replayable requests are required. Refresh only if estimated net savings reach at least $0.05.
  • Do not refresh indefinitely. Timing starts from the original real request: at most one hour while running and thirty minutes while idle. If the timer misses the safe refresh window, stop rather than risk rebuilding an expired cache just to keep it alive.
29
Xiao Wang · asks

Logs and discussion grow until the agent replaces older history with a summary. Summarizing reads old material; afterward the next request has a shorter but changed input.

Which caches survive replacing history with a summary, and what does generating that summary cost?

Da Wang · explains
FIG 32Two stages of compaction: write the summary, then continue with it

The agent has read logs and discussed them over several rounds. Replacing older history with a summary requires separate examination of the summary-generation request and the following request.

↔ Scroll sideways on small screens; explanations follow each panel.

Stage 1: does summary generation retain the original prefix?

Two summary-request arrangements have different prefixesThe public Claude Code approach retains the original tools, system instructions and history, then appends a summary request. The pinned Pi version uses dedicated summary instructions and reformatted dialogue with cacheRetention set to none. This panel compares summary generation.Retain the main-session prefix; append summary instructionsOriginal tools andSystem instructionsOriginal historyPlease summarize1Pi 0.99.1: a separate summary requestSummary instructionsPrior dialogue formatted as text2Separate request: cacheRetention: "none"

Stage 2: the summary exists and the next request's history changes

Replacing history with a summary prevents directly reconnecting unchanged later text to old KVBefore compaction: fixed instructions, full old history, recent rounds, current task. Afterward, fixed instructions remain while a summary replaces old history. Recent text is unchanged, but its preceding context differs, so its higher-layer KV is not an exact result for the new history.Before: continue from complete historyFixed promptComplete old historyRecent roundsCurrent taskAfter: continue from the summaryFixed promptStill reusable3Short summaryRecent roundsSame text4Current taskReplacePrepare this KV under the new contextSame recent text; different preceding context.
Measure three cost stages separately: summary generation, the first post-compaction request, and later requests. A cache hit while summarizing does not prove the whole old KV remains reusable afterward.

Context compaction changes content sent to the model; KV compression changes how inference state is stored. Replacing full logs with a summary reduces input tokens. Encoding KV numbers in fewer bytes does not automatically reduce API context-token counts.

08 / PRACTICE · Return to the original goal

How do these ideas apply to your own agent?

Compare whole-task cost and waiting time while preserving correctness. Hit rate helps explain changes; actual task outcomes establish whether they helped.

30
Xiao Wang · asks

I am building an API-based coding assistant that loads tools, waits for tests and summarizes long conversations.

Which operations can invalidate the old cache? Where should I look first, and how can I establish lower cost and time without harming completion?

Da Wang · explains

An API-based agent mainly controls input organization, tool loading, compaction, request scheduling and choice of model/service. Cache location, transport and internal K/V sharing usually belong to the service provider or a self-hosted deployment. See cache placement and reclamation.

Inspect the content actually sent first

Expand and compare adjacent requests' actual tools, system instructions and messages, marking the earliest change. An unchanged chat interface does not imply unchanged input: an agent might rewrite the tool directory or insert a timestamp at the beginning every round.

Keep stable material unchanged when appropriate and append questions/tool results in order. For different questions about one document, separate stable material from the changing question and follow the service's cache rules. Stabilize only content that truly need not change. Updated code or requirements must be reflected; cache hits do not justify stale facts.

ScenarioWhat to inspect in your agent
Loading tools on demandIs the opening directory rewritten? Could stable search/execution entries replace it? For native dynamic tools, check later expansion and first-enable behavior.
Large logs or detailed tool outputHow large is the raw output, and how much enters the model? Could filtering, external storage and on-demand reads reduce it?
Generating and using summariesMeasure input, cached usage, cost and task outcomes separately for summary generation, the first replacement round and later rounds.
Waiting for long testsWill the cache remain valid for the next round? Do later savings cover keep-alive costs?

Record usage and diagnose by observed symptoms

Record request purpose, input, cache reads/writes where available, output, cost and duration. Similarly named fields may differ across APIs; use each interface's definitions.

Expand Claude and DeepSeek usage-field details
Claude · Total input has three components
read  = usage.cache_read_input_tokens
write = usage.cache_creation_input_tokens
fresh = usage.input_tokens

total_input = read + write + fresh
hit_ratio = read / total_input if total_input else 0

Claude's input_tokens excludes the cache-read and cache-write components above. Server tools and multi-round aggregates also require their corresponding usage records. [4]

DeepSeek · Hit and miss counts
hit  = usage["prompt_cache_hit_tokens"]
miss = usage["prompt_cache_miss_tokens"]

hit_ratio = hit / (hit + miss) if hit + miss else 0

Field definitions follow DeepSeek's context-caching documentation. [6]

For an aggregate hit rate, sum hit tokens across requests and divide by total input tokens. Do not simply average percentages from requests of different lengths.

Reading old dialogue from cache while writing new dialogue is normal. Separate new-content writes from failed reuse of old content, then diagnose the symptoms below. [4]

Observed symptomFirst checks
Old history is not read from cache; the same long content is repeatedly writtenDid early input change? Is there a reusable cache point at the end of the stable document? After appending messages, is the previously written boundary still within the service's lookup range?
Only a short opening prefix hitsWhere is the earliest change? Were system instructions, tools or project material reordered or rewritten?
Identical content still missesDoes it meet minimum cache length? Has the first write become usable? Has TTL expired? Did the model, API service or workspace change?
High hit rate, but the context window fills quicklyAre obsolete tool results still carried forward? Which evidence must remain, and what can be archived or summarized? Cached tokens still occupy context.
High hit rate, but the task is slowTime the wait for the first response, generation and tool execution separately. Identify the dominant stage before changing it.

Include every request in whole-task cost

Include the main agent, subagents, summaries, keep-alive calls and retries within one task's accounting. Main-chat cost alone can omit overhead introduced to optimize it. Apply the service's actual billing rules, including applicable cache reads, writes, storage and outputs.

Compare before and after on the same tasks

Use the same tasks and completion criteria. First hold the model fixed and change one strategy at a time. Inspect first use, continuous dialogue and long idle gaps separately, then aggregate whole-task outcomes.

OutcomeQuestion it answers
Task pass rateDoes the work still finish correctly?
Total task costAfter extra requests, how much money is actually saved?
Start-to-completion timeHow much less does the user wait? Report typical and slower tasks separately.
Cache usage and stage timingsWhere did gains or losses occur?

You can also compare total cost of the task set ÷ accepted task completions, while reporting pass rate alongside it. Mark unavailable cache-size or internal-restoration measurements as not directly measurable; do not present estimates as measurements.

When changing models or services, repeat the same-task comparison of completion quality, cost and time.

09 / SPECULATIVE DECODING · Draft a segment, then verify

If generation is sequential, how can verification be batched?

Follow draft transfer, layer computation, verification and rollback, then examine acceleration conditions and its relationship with FlashAttention.

31
Xiao Wang · asks

Ordinary continuation computes a step, selects one token, then computes again. Speculative decoding adds a separate drafting stage.

What is speculative decoding, and how can doing extra draft work make generation faster?

Da Wang · explains

Use a cheaper method to propose candidates, then verify them with the target model responsible for the answer. This generation method is speculative decoding. Here, a smaller draft model assists the target model.

FIG 33Generate the same A, B and X while waiting for fewer target-model passes

Continue from the same committed prefix, choosing the target model's highest-scoring token at each step. A/B/C/D are draft candidates. The example accepts A/B and replaces C with X. Each letter is one token.

① Ordinary generation: three sequential target passes

Committed prefix
Target model · pass 1Select A
Target model · pass 2Select B
Target model · pass 3Select X
Committed new contentABX

② Speculation: cheaper draft + one target verification pass

The same committed prefix
Draft model · sequential unverified proposals
A→B→C→D
Send the candidate sequence to the target
Target model · one batched pass
A ✓B ✓Replace C with XDiscard D
Committed new contentABX
Acceleration is conditional:The draft must be cheap enough and enough candidates accepted to outweigh added work. Frequent rejection can make speculation slower.
This compares execution steps; box width is not elapsed time. Three passes becoming one does not imply 3× speedup. Candidates and verification outcomes are illustrative. The next question expands the batched computation. [54][60]
32
Xiao Wang · asks

The draft proposes A/B/C/D. I imagine the target uses the draft's Q/K/V to decide whether C should be X, allowing simultaneous verification.

Does the draft transmit tokens or already-computed Q/K/V?

Da Wang · explains

This chapter uses two independent models with different parameters. The draft supplies candidate token IDs. The target looks up its own embeddings and projects its own Q/K/V. Randomized verification additionally needs draft probabilities for acceptance and resampling; those are not draft K/V reused as the target's cache.

This example retains full historical K/V by position. Fix the starting state: tokens 1–9 are committed; the target cache reaches position 8; token 9 was just selected and has not been fed back. The draft proposes A/B/C/D at positions 10–13.

FIG 34Transmit candidate content; the target recomputes its own state

Purple denotes draft state; green denotes target state. Solid arrows show data transferred this round.

The draft supplies A/B/C/D token IDs. The target uses its own parameters and old cache to generate this round's Q/K/V.Draft modelKVDraft stateRetained by draftA10B11C12D131CandidatestokenSetPosition 9Target: own computation2KVCached 1–8Old target cache3QKVQ/K/V for five positions
The pinned implementation obtains candidate tokens and available draft scores, then calls the target to process candidates and obtain its own predictions. [61]

Ordinary generation waits for the target's next token to know the next input. Here, the draft already supplies A, B and so on. The target computes the predictions conditional on that proposed sequence. If an earlier candidate is rejected, later results depending on it cannot join the committed history.

33
Xiao Wang · asks

The target can batch a known candidate sequence. But ordinary prompt processing usually needs only the final position's prediction, while verification checks several positions.

Which vocabulary scores does each operation need? Why does checking C use B's output?

Da Wang · explains

Participating in model computation and producing prediction scores are different. Final hidden vectors are not vocabulary scores. Passing selected positions through the output layer produces raw candidate-token scores, or logits.

FIG 35The same row of hidden vectors, but different positions reach the output layer

Colored cells represent hidden vectors; small bars represent vocabulary scores. Column numbers are token positions.

Parallel ① Dashed boxes with matching numbers can run concurrently; arrows still show dependencies. Numbers match only within this diagram.

① Ordinary input: five input tokens, then the first response token

② Speculative verification: predictions around several candidate positions

Four draft candidates require four verification predictions, usually plus one bonus-token prediction. Other old cached positions need not all recompute their vocabulary scores. [61]

Implementations supporting the option set logits_to_keep = candidate_length + 1. Batched input does not imply retaining only the final prediction. The output layer processes more positions, and ultimately discarded candidates may already have incurred computation.

Vocabulary scores select the next token. Attention matching scores control a layer's historical reads. They are different scores.

34
Xiao Wang · asks

Earlier examples always selected the target's highest-scoring token. Now use probabilistic sampling while keeping cheaper drafts and batched target verification. We want faster generation while preserving the target's original selection rule.

How can randomized verification preserve the target model's sampling probabilities?

Da Wang · explains

Remove the draft first: how would the target choose?

Fix the preceding context and select one immediately following token. The target scores vocabulary candidates, then the sampling settings determine their selection probabilities.

Greedy decoding always selects the highest-probability candidate, as in the A/B/C→X example. Random sampling selects according to probabilities: likely candidates appear more often, but others remain possible. This question concerns random sampling.

What exactly does “20%” describe?

For illustration, this position has three candidate tokens: A, B and C. They are alternatives at one position, not three consecutive tokens. Probabilities below are teaching choices.

Candidate tokenTarget selection probability
A20%
B50%
C30%

Greedy decoding selects B, whereas random sampling may also select A or C. 20% means A's chance at this immediately following position, holding the prefix, model and sampling settings fixed.

It is not a 20% chance that A is correct, nor a requirement for twenty A tokens in a hundred-token response. Once a token is selected and generation continues, context changes and the next position gets a new distribution.

Adding a draft can change the probabilities

A cheaper draft proposes candidates before batched target verification. Because its parameters differ, it can assign different probabilities even for the same prefix:

Candidate tokenOriginal target probabilityDraft proposal probability
A20%60%
B50%30%
C30%10%

Accepting every proposal makes the final choice follow the draft column. A would rise from the target's 20% to 60%. Drafting would then change the generation rule.

What should verification preserve?

Use the draft's advance proposals while ultimately selecting according to the target's 20%, 50%, 30%. Three steps are needed:

  1. The draft samples a candidate from its own distribution.
  2. Using both models' probabilities, the verifier accepts or rejects it.
  3. On rejection, resample from a specifically corrected distribution.

Correction changes acceptance and resampling rules, not either model's trained parameters. Work through the same numbers to see which proposal mass survives and what replaces rejected mass. [54]

FIG 36Draft A: 60%. Target A: 20%. How do we correct it?

Target: A 20%, B 50%, C 30%. Every full bar represents 100% probability, not a token count.

① Accept: retain one third of A proposals, all B/C proposals

② Resample: split the rejected 40% equally between B and C

③ Combine: recover the target's 20% / 50% / 30%

These are worked probability calculations, not a model measurement. One draw selects one token at this position; the diagram shows each outcome's chance. Acceptance and resampling rules were checked against the paper and pinned implementation. [54][60]

From distribution mismatch to average one-step acceptance

Keep the same prefix and next-token position. Let p be the target distribution, d the draft distribution and i range over vocabulary candidates. Both distributions must already include temperature, truncation and other actual sampling settings.

Sum the absolute differences in candidate probabilities and divide by two to obtain total variation distance, TV: the probability mass not shared by the distributions.

TV(p,d)=12∑i|pi−di|

Under standard speculative sampling, before a candidate is drawn, average one-step acceptance α is:

α=∑imin(pi,di)=1−TV(p,d)

Candidate i is proposed with probability di and accepted conditionally with min(1, pi/di). Their product is min(pi, di). Summing these contributions gives α. [54]

Substitute the three candidates above

p=(0.20, 0.50, 0.30),d=(0.60, 0.30, 0.10)。

TV = (0.40 + 0.20 + 0.20) / 2 = 0.40; α = 1 − 0.40 = 60%, matching the diagram's directly accepted mass.

That does not mean every candidate has a 60% conditional acceptance rate. Once A is proposed, its chance remains 1/3. Average acceptance is also not actual speedup.

Paper §3.2 denotes this TV quantity by DLK. It differs from KL divergence's DKL discussed earlier; similar notation does not make them interchangeable. [54]

Each new position may change the prefix and both distributions. Verification retains only the consecutively accepted beginning. One position's TV is not a fixed acceptance rate for an entire draft. Consider accepted-prefix length together with execution costs.

Xiao WangIf target A is 60% and draft A 20%, their ratio is 3. Does that exceed 100%, and is A always accepted?

Da Wang: Yes: conditional on the draft having proposed A, accept it with certainty. Acceptance is capped at one, or 100%. There is no 300% acceptance probability.

Always divide target probability by draft probability, then cap at one: acceptance = min(1, target probability / draft probability). Here 60% / 20% = 3, giving 100% acceptance.

EventProbability here
The draft proposes A20%
Accept A, conditional on its proposal100%
Select A via proposal followed by direct acceptance20% × 100% = 20%

Always accepting a proposed A does not make every final token A. The draft proposes it only 20% of the time, so this path contributes 20% overall. The target needs 60%; the missing 40 percentage points come through resampling after other candidates are rejected. [54]

The earlier diagram fixes the opposite numbers: draft A 60%, target A 20%, giving 20% / 60% = 1/3 acceptance. The supplied probabilities changed; the direction of division did not.

35
Xiao Wang · asks

Return to the rejection example: A/B/C/D is drafted; the target accepts A/B and selects X instead of C. D is not used this round.

Which computed candidate K/V survives, and where is the newly selected X?

Da Wang · explains
FIG 37X is selected but does not yet have its own K/V

Observe immediately after verification: accept A/B, replace C with X. Horizontal numbers are token positions; aligned rows compare text and target K/V.

① After verification: committed text reaches 12, usable K/V reaches 11

② Next round: feed X into the target to create its K/V

Crossed-out records stop being read; whole memory pages are not necessarily cleared immediately. A page containing retained records or shared with another request cannot be reclaimed wholesale. See paging and copy-on-write. [30][47]

Prefix caching and speculative decoding can work together: the former reuses committed old input state; the latter tries to commit new content faster.

Xiao Wang Four draft steps were computed, but only two accepted. With explicit K/V, the final two records can be excluded. If all four updated the same state, how do we restore the moment after the first two, and what must be retained beforehand?

Da Wang: Still use A/B/C/D. Restore the per-layer state after processing B, then feed X. Fixed-size state has no four tail records to truncate. Choose a method from what was retained:

  • A snapshot after B: restore every layer's state from that moment.
  • Only a snapshot before A: restore it and replay A/B along the original computation.
  • Reversible updates with sufficient retained contributions: for the additive example, subtract C/D's per-layer matrix and vector contributions in real arithmetic. Floating-point subtraction need not recover the original bits, and other recurrent updates need not support this operation.

A snapshot must protect the old numerical values. Keeping a reference to an array later mutated in place does not preserve old state. Copy the data or use storage that protects the previous values. [69]

36
Xiao Wang · asks

The target still computes attention and vocabulary scores for candidates, perhaps including unused C/D. How can extra work be faster? Reusing one data tile across positions also sounds like FlashAttention.

Which waits does speculative decoding avoid, and what does it change compared with FlashAttention?

Da Wang · explains

Arithmetic work and waiting time need not change together. Batched verification reuses loaded parameters across positions and may exploit otherwise idle computation. Drafting, additional prediction positions and rejected computation add overhead.

What do KL, TV and actual speedup each tell us?

Draft–target KL divergence alone cannot establish actual speculative-decoding speedup. Let p denote target and d draft probabilities under the same prefix and actual sampling settings.

QuantityWhat it measuresHow to interpret it
KL divergenceA directional distribution discrepancy: the reference-weighted average log probability ratio.Useful for distribution matching, but not one-step acceptance and not a measure of runtime.
TVProbability mass not shared by the distributions.Under the standard sampling rule above, 1−TV is average one-step acceptance for the same prefix. It does not directly specify consecutively accepted draft length.
Actual speedupOrdinary-generation time divided by speculative-generation time under comparable tasks, hardware, concurrency, sampling and other conditions.Also account for accepted-prefix length, committed tokens per round, and drafting, target verification and state-management costs.

Execution costs include draft generation, actual batched target runtime, parallel efficiency, temporary candidate K/V and rollback, transfers, communication and scheduling waits. Additional memory may reduce concurrent capacity. None of these costs appears in KL or TV. [54]

Even identical distributions at every step, KL = TV = 0 and complete draft acceptance do not guarantee a speedup. If drafting already takes nearly as long as ordinary generation, verification and state management can make the total slower. Compare actual progress per round with its time below.

FIG 38How can computing more positions reduce waiting?

Parallel ① Dashed boxes with matching numbers can run concurrently; arrows still show dependencies. Numbers match only within this diagram.

① One loaded weight tile serves linear computations at several positions

Load one parameter tile W from GPU memory on-chip, then use the same W in matrix computations for several input-position vectors.GPU weightsSame WLoadGPURetain the tile on-chipPosition 10 · AUse the same WCompute herePosition 11 · BUse the same WCompute herePos. 12 · C…Use the same WCompute herePar. ①Par. ①Par. ①

② An illustrative time ledger: both commit A, B and X

Times are teaching assumptions, not measurements. Bars follow those assumed proportions. Actual benefit depends on acceptance, draft cost, hardware and concurrency. [54]

Xiao Wang A/B/C/D are available, so the target can process them as one input batch within every layer. Loaded weights can serve several positions rather than repeatedly reading data for each next generation step.

Can that reduce transfers between GPU memory and on-chip storage? FlashAttention also tiles computation and reuses loaded data on-chip. Do they avoid the same overhead? Is speculative decoding an upgraded FlashAttention?

Da Wang: Speculation can reduce some repeated reads, but the techniques optimize different stages; it is not a FlashAttention upgrade. Here we compare transfers between GPU memory and fast on-chip storage.

Separate learned model parameters W from Q/K/V, numerical intermediates computed for the current input.

ComparisonSpeculative decodingFlashAttention
Initial bottleneckFuture token identities are unknown, usually requiring selection before the next token is processed.Given Q/K/V, attention produces large intermediate scores and weights that move between GPU memory and the chip.
How transfers are reducedDraft candidates let the target process several positions in one pass, potentially sharing loaded parameters and historical K/V across them and avoiding repeated per-round reads. [54]Tile Q/K/V and consume scores in subsequent on-chip attention computation, avoiding full score/weight tables written to and read from GPU memory. [49]
ScopeGeneration flow: draft, verify, accept and roll back.Attention computation inside model layers.
Can they be combined?Yes. The target's attention can use FlashAttention during batched speculative verification.
Appendix A · Three reasoning exercises

Xiao Wang reasons first; Da Wang explains afterward

Use pen and paper: diagnose usage logs, review a cache implementation, then compare state storage, reads and restoration. Make a prediction before expanding additional facts and suggested analyses.

Exercise 1 · Usage logs Exercise 2 · Implementation review Exercise 3 · State and restoration

Exercise 1

Diagnose abnormal cache reuse from usage logs

Stage 1: inspect only the logs

Xiao Wang receives Claude API logs for a coding agent using Claude Opus 5. He does not yet know exactly what changed in each request. Start only with the following assumptions and rules.

  • Input order is tools, system, messages. Initially tools has 2,800 tokens and system 4,200.
  • One explicit breakpoint stays at the end of system; automatic caching adds another on the request's final content block. A breakpoint marks “cache through here.”
  • TTL is five minutes. Cache writes cost 1.25× ordinary input; reads cost 0.1×.
  • Only those two breakpoints exist. Table 1 lists every write and refresh; there are no hidden ones.

The keep-alive request is safely replayable: lowering output limits changes neither thinking budget, effort, effective prefix nor other match conditions; see Question 28. Deferred-loading fixes use the stipulated input increments. Additional discovery messages, requests and output costs are excluded from this exercise's ledger.

Rules available for reference
  1. Matching uses complete prefixes in tools → system → messages order. Each breakpoint writes one entry for the entire prefix through that point.
  2. Each breakpoint checks at most 20 positions backward, counting itself, for entries actually written earlier. Ordinary content blocks each count as one position; consecutive tool_use blocks form one, as do consecutive tool_result blocks.
  3. TTL is measured from the start of a request reading or writing that entry. Every read refreshes its timer.
  4. Other requests can read a newly written entry only after its writing request begins returning a response.
  5. Changing tools invalidates the old prefix. Changing tool_choice, which specifies whether this round must call a particular tool, preserves tools/system caching but invalidates messages.

Rules were checked against Anthropic's dynamic documentation on 2026-10-02; volumes and prices remain this exercise's fixed assumptions. [4] write and read count written and cached-read input tokens; their sum is this table's total input. Times start from the session origin.

Table 1 · Request usage log
RowStartDurationTotal inputwriteread
100:0020s8,5008,5000
200:3015s10,0001,5008,500
300:5518s11,5001,50010,000
401:2025s32,80021,30011,500
501:5515s34,65034,6500
602:20150s36,1501,50034,650
707:5520s37,65037,6500
808:2515s39,1501,50037,650
908:5015s40,65033,3007,350
1009:1515s42,1503,00039,150
1109:4020s51,87044,5207,350
1210:1025s82,37030,50051,870
1310:1025s82,37030,50051,870
1410:1025s82,37030,50051,870

Overall token hit rate, total read divided by total input, is approximately 52.6%.

  1. Under the stated accounting rule, subtract the input increase relative to the parent from write to obtain old content reprocessed. Exclude initial cache-building row 1. Rows 12–14 share parent row 11; other rows use the preceding row. Which rows are nonzero? Does row 4's roughly 35% hit rate indicate a problem?
  2. For nonzero rows, where does read stop? What can Table 1 alone establish, what remains uncertain, and what evidence is needed?
  3. Rows 12–14 yield zero under that rule. Could they still have a problem?
Old content reprocessed = write − (this row's total input − parent's total input)

This is the exercise's single-lineage incremental convention; row 1 is excluded.

Da Wang · Stage-1 analysis using only Table 1
Da Wang · Only the first-stage information

Question 1: row 5 is 32,800, row 7 36,150, row 9 31,800, row 10 1,500 and row 11 34,800. Exclude row 1; the remaining rows yield zero under the stipulated rule.

Row 4 does not thereby reveal an anomaly. Its 21,300 written tokens equal the input increase, and read 11,500 continues from row 3. A roughly 35% hit rate reflects much new content, not demonstrated waste.

Question 2: read helps locate a hit boundary; it does not directly encode a failure cause. Only system-end and request-end breakpoints exist. Complete requests contain at least 8,500 tokens, with no other writes. Therefore 7,350 must be the tools+system entry. It exceeds the initial 7,000 by 350, but the location and timing of that addition remain unknown.

Stage 1 · Hit boundaries and missing causal evidence
RowreadWhat can be establishedWhat still needs checking
50Input grows by 1,850 and starts just 35 seconds later. Natural five-minute expiry alone does not explain the miss. Compared with the later 7,350, the extra 350 is a priority lead.A total-volume change cannot locate the addition. Inspect tools, system, messages and configuration.
70335 seconds since row 6 started, but 185 since it finished. With start-based TTL and no other refresh, expiry is sufficient to explain the miss.Use request start time; do not generalize to all real-service cache entries expiring together.
97,350With minimum full input 8,500 and only two write boundaries, the hit is tools+system, 350 above its initial 7,000.Whether the 350 entered tools or system, when it entered and why messages missed remain unknown.
1039,150The full prefix from row 8 matches again, showing that older entry remains available.A temporary configuration change restored afterward or a temporary history rewrite restored afterward could both explain it; numbers alone cannot distinguish them.
117,350The hit again stops at tools+system; the next three requests can read this row's full prefix.Check history/configuration changes and whether new content blocks pushed the old entry outside the lookback range.

For example, row 9 might replace message text with equal-length content and row 10 restore it, first falling back to a short prefix then matching row 8. Stage 1 identifies tools+system boundaries but cannot split their internal sizes or uniquely attribute message misses to parameter changes.

Question 3: rows 12–14 each write 30,500 and start simultaneously. They might duplicate shared new content, but its overlap is not yet known. Inspect common segments, breakpoints and send order. A parent-row-11 incremental calculation cannot reveal duplication across sibling branches.

Stage 2: add facts, then determine causes and fixes

Expand additional facts before stage 2
Table 2 · Actual request changes
RowChange relative to the parent
1Initial request: tools + system + user task, 1,500 tokens
2、3、6–10Each adds a 1,500-token tool round: 300 model output tokens, text plus one tool_use, and a 1,200-token tool result
4Add a round whose tool result is a 21,000-token log
5Add a round; the agent wrapper inserts db_query, 350 tokens, into the middle of tools
6150 seconds from request start to response completion
7A local test runs for 3 min 05 sec before sending
9Change tool_choice from auto to forced submit_patch
10Restore tool_choice to auto
11Append 120 tokens of final model reply; user adds 24 files, one content block each, totaling 9,600
12–14Three subagents send in the same second. Each input = all row-11 input + the same 300-token model reply + the same 30,000-token repo map + its own 200-token instructions. Breakpoints are unchanged.
  1. Use these facts to identify each actual cause.
  2. How should each be changed? Which changes merely optimize, and which change invocation guarantees? What are the combined hit rate and input cost?
Da Wang · Causes and fixes for questions 4 and 5
Da Wang · Now use the additional facts

The new facts locate 350 tokens in tools, so only now can we establish 7,350 = revised tools 3,150 + system 4,200. Rows 9–10 are now known to involve changing and restoring tool_choice.

Stage 2 · Actual causes and conditional fixes
RowActual causeFixRevised write / readInput-equivalent saving when changed alone
5Inserting a tool definition breaks the original prefix.Stabilize the deferred-loading mechanism from round 1; add tools through supported discovery/reference paths later. Details follow the table.1,850 / 32,80036,915
7Row 6 starts at 02:20 and expires at 07:20; the next request starts at 07:55.Safely replay at 06:50 with unchanged cache identity, reading 36,150; bill using five-minute TTL.1,500 / 36,15037,957.5, after keep-alive cost
9、10Row 9 changes tool_choice; row 10 restores it and reconnects an older prefix.Removing forced invocation depends on the required guarantee; see below.1,500 / 39,150;1,500 / 40,65038,295, if the model complies
1125 new positions push the previous endpoint to lookback position 26, beyond the limit of 20.Combine files into one block, or explicitly mark the preceding request's final block in the history carried by the current request.9,720 / 42,15040,020
12~14Requests start together and lack a breakpoint at the common repo-map endpoint.Mark the common endpoint; send A first, then B/C after its entry becomes readable.A unchanged; B/C each 200 / 82,17069,690

“Input-equivalent” expresses cost in ordinary-input-token units. Outputs are excluded. The keep-alive row retains a half unit for ledger reconciliation. One-hour TTL is a different strategy requiring its corresponding write price; do not insert it into this five-minute ledger unchanged.

Row 5: establish loading support first, then discover tools

defer_loading: true retains complete tool definitions in the discoverable list without expanding them in the opening prompt. A discovery reference expands the definition at the current conversation position. Applicable Pi paths also enable tools using tool_addition references. The interface and agent wrapper must support this; appending plain text does not create invocation capability. [31][20]

Pi declares a never-called deferred placeholder from round 1. That version's author reports a hidden prompt introduced on first deferred use causing a full miss; the placeholder stabilizes the structure earlier. Prepare the mechanism, not foreknowledge of every future tool. See Question 27.

The table's 1,850 / 32,800 follows the stipulated increments. Real discovery/reference paths may add messages, requests and input. These exact API numbers are not guaranteed.

Rows 7 and 11: refresh the same identity; locate the old entry

Keep-alive must hit the original entry without changing thinking budget or prefix when lowering output limits. See Pi's checks in Question 28. This exercise explicitly satisfies that prerequisite.

The row-11 breakpoint belongs to history carried in the current request, marking the preceding request's last content block. Lookup can then find its automatically written entry. No already-submitted request is retroactively edited.

Subagents: both prerequisites are necessary

The 300-token reply and 30,000-token repo map are identical; each branch's 200-token instructions follow them. The common prefix is 82,170 tokens. Its endpoint needs a written cache entry, and that entry must be readable. Staggering alone cannot reuse a common prefix never separately retained.

Alternatively, send a nonstreaming max_tokens=0 warming request and wait before launching branches. The Claude documentation checked on 2026-10-02 excludes combining it with manual extended thinking, structured output, forced tool/any or Message Batches; cache identity must still match. Compared with sending A first, this adds an 82,170-token read here. The complete-fix ledger uses A first. [4]

Forced tool invocation: decide which guarantee must survive

Forced tool_choice requires submit_patch this round. auto plus a textual request does not preserve that single-call guarantee.

Use auto directly if hard enforcement is unnecessary. If required, the wrapper must verify invocation before proceeding; otherwise force a supported retry and check again. Failed requests or failed checks remain incomplete. Textual instructions alone do not prove compliance. Retry latency and tool side effects need separate accounting.

The exercise uses an Opus 5 configuration supporting forced tools, without incompatible manual extended thinking. Unsupported interfaces cannot use this forced-fallback ledger; the wrapper must still verify outcomes. [29]

Reprocessed old content is not necessarily avoidable waste. Row 10 is a secondary consequence; if forcing is required and auto fails, row 9's extra work may be the cost of meeting the requirement. Conversely, sibling duplication can escape the single-lineage incremental metric.

Da Wang · Conditional ledger after all fixes
Da Wang · State the ledger's assumptions first

Use five-minute caching, one extra keep-alive and the first subagent creating a readable common entry. After removing temporary forcing, assume the model actually invokes the tool. This result does not hold for every failure probability.

Conditional ledger after all fixes
ItemOriginalAll fixes
Requests1415, including one extra keep-alive
Written tokens280,42082,770
Read tokens311,760545,560
Token hit rate52.65%86.83%
Input cost: ordinary-input-token equivalents381,701158,018.5
Input-equivalent cost = 1.25 × write + 0.1 × read

Counts only stipulated input increments, excluding real discovery overhead, output, retry waits and tool side effects.

Revised input cost is about 41.4% of the original, or 1/2.42. The keep-alive fully hits. Excluding only it from hit-rate statistics gives about 86.0% for the other requests, but its cost must remain in the bill.

Stage 3: change one condition and predict

After stage 2, expand four variations. Write predictions before reading explanations.

Expand the four variations of question 6

Xiao Wang · Question 6 Each variation starts from the original exercise and changes only the stated condition; do not combine fixes.

(a) Replace row 11's 24 files with 18 or 19. What is read in each case?

(b) Reduce row 6's start-to-completion duration from 150 to 60 seconds, keeping the test duration. What is row 7's read?

(c) Only stagger the three subagents by three seconds. How much do B/C each write?

(d) Try auto with instructions on row 9; if submit_patch is not invoked, force the same request again. Above what noncompliance probability is this more expensive than always forcing?

Da Wang · Variation (a)

With 18 files, read is 42,150; with 19, 7,350. The final model reply adds one more block. Since lookback includes the breakpoint itself, 18 files place the old entry at position 20, still reachable; 19 place it at position 21, beyond reach.

Da Wang · Variation (b)

Row 7 now starts at 06:25, only 245 seconds after row 6 began, giving read 36,150 and write 1,500. The test still takes 3 min 05 sec; less TTL has elapsed before it starts.

Da Wang · Variation (c)

B/C still each write 30,500. Staggering does not create a repo-map endpoint. A previous branch's complete entry includes its private instructions and cannot replace a separately retained common-prefix entry.

Da Wang · Variation (d)

The threshold is about 90.4%. First list the three supplied paths:

Variation (d) · Input cost of rounds 9 and 10 only
PathOrdinary-input-token equivalents
Always force50,025
Auto first; model complies11,730
Auto first; noncompliance, then forced retry54,090

Auto success saves 38,295 equivalents relative to always forcing. Auto failure with fallback costs 42,360 more than auto success. The break-even failure probability is:

(50,025 − 11,730) ÷ (54,090 − 11,730) ≈ 90.4%

Above that probability, this auto-plus-forced-fallback path costs more; at equality, input costs match.

This is not a whole-task optimum. Extra outputs, waiting and side effects are excluded, and the wrapper still must verify the tool was actually invoked.

Exercise 2

Review a minimal prefix cache

Xiao Wang implements a prefix cache for an open 0.6B-parameter model with 28 ordinary causal full-attention layers. Review the design and four experimental observations: which differences are explainable, and which indicate bugs?

Design

  • Each block holds 16 tokens; cache only full blocks.
  • Hash the preceding block ID together with this block's 16 tokens. Seed the first predecessor ID from model name and numerical precision.
  • Look up sequentially from block 1, stopping at the first missing block. Compute all later tokens afresh. Even if everything matches, leave at least the final token uncached.
  • Track the number of requests using each block. At capacity, evict the least recently used unused block. Registering or hitting a chain marks blocks recently used from first to last.
  • On a hit, first copy K/V into a private request-owned buffer. After copying completes, the request no longer reads the cache table.

Compare fp32, 32-bit floating point, with bf16, 16 bits with eight significant binary bits including the implicit bit. Logits are raw candidate-token scores before normalization to probabilities.

Experiment 1 · Numerical results

Two requests share a 1,210-token prefix; the second is 1,230 tokens long. Send the first, then the second, comparing the latter's next-token logits. Values are maximum absolute differences unless row ⑤ states otherwise. “Bitwise identical” means stored numerical values match bit for bit.

Experiment 1 · Maximum differences and prefix controls
Computational paths comparedfp32bf16
① Repeat the same algorithm twiceBitwise identicalBitwise identical
② Cached versus uncached, processing all 1,230 tokens at once3.7e-50.33
③ Without cache-table lookup, process the second request as 1,200 then 30 tokens versus all at once3.7e-50.33
④ Cached versus the split computation in ③Bitwise identical0.37
⑤ K/V for the first 1,200 tokens computed in request 1 versus request 2Bitwise identicalMedian relative error: 0.6%
⑥ First layer's first matrix product for the last 30 tokens: within 1,230 rows versus a standalone 30-row computation9.5e-6Not measured
⑦ Full-sequence computation alone versus batched with another request3.8e-6Bitwise identical

Experiment 2 · Generation

Both paths receive the same supplied generated history at every step, then compare this step's selection. ε is their maximum logit difference; m is the uncached top-two score gap. Five prompts × 150 steps = 750 comparisons.

Experiment 2 · 750 fixed-history comparisons
Observationfp32bf16
Per-step ε: median / maximum1.4e-5 / 5.4e-50.26 / 1.0
Steps with m ≤ 2ε, using greedy argmax095
Steps selecting different tokens, greedy08
Same, temperature-1 sampling with common random numbers09

A hit-length comparison for the same prompt first differs at step 32 with a 1,200-token hit, belonging to prompt 1 in the 750-step set. A 1,216-token hit first differs at step 154 in a separate run.

Experiment 3 · Splicing into the wrong context

Take a 1,191-token document's K/V from its original request and append it to a different prefix. Compare against correct recomputation in fp32. KL measures candidate-distribution discrepancy; zero means equality.

Experiment 3 · Results after changing preceding context
MethodKLMost likely token
Valid prefix cache, experiment 1 row ②5.8e-8Same
Append after different content of the same length3.1e-2Same
Append after a prefix of different length6.0e-2Changed

Experiment 4 · Capacity pressure

(a) Capacity is 12 blocks. Request A has 195 tokens, fills 12 blocks and finishes. Request C then needs one block. A returns and hits zero tokens.

(b) A request hits 192 tokens, finishes copying K/V and enters decode. Clearing the cache table now does not change its output.

(c) Another version retains K/V in a fixed shared-page pool, one block per page. Requests keep page IDs and continue reading those pages during decode. Evicting a page permits overwrite by another request. Compare fp32 with the same request under no pressure.

Experiment 4(c) · Direct shared-page reads
ScenarioMethodReported result
B arrives halfway through A's decode; insufficient free pagesNo ownership count, only recency orderB overwrites A's page; 12 of A's next 16 tokens differ, maximum logit difference 24.5
Same scenarioCount active request usersReject B until A finishes; A remains bitwise identical
A1/A2 share the first 11 pages; A1 finishes, then B arrivesOnly an “in use” booleanB overwrites a shared page; 19 of A2's next 20 tokens remain unchanged, maximum logit difference 5.2
Same scenarioCount active request usersBitwise identical
A returns after one page is evictedEvict the chain's earliest page firstHit 0/195; the remaining 11 pages become unreachable. Recomputation is bitwise identical to uncached execution.
Same scenarioEvict the chain's final page firstHit 176/195; maximum recomputation difference 6.3e-5, all 12 token choices identical

Xiao Wang · Six questions

  1. In experiment 1, how many tokens does request 2 hit and compute? What if it is sent again unchanged? Is “always recompute at least one token” mathematically necessary?
  2. Are valid cached and uncached computations mathematically equivalent? Why?
  3. Where does fp32 row ②'s difference originate? Which controls exclude corrupt prefix data? What additional source appears in bf16? Is uncached execution uniquely correct?
  4. Prove equal greedy choices when m > 2ε. Does the converse hold? Does 8/750 determine time to first divergence, and what information is missing? How does sampling change the criterion? What do the step-32 and step-154 observations imply for evaluation?
  5. Why are both experiment-3 splices invalid? What must cache identity include? Is chained hashing the only correct representation?
  6. In experiment 4: (a) why can evicting one block reduce hits to zero? (b) what does unchanged output after clearing prove, and does it remove the need for protection during copying? (c) why is 19/20 unchanged tokens still a bug? What should a pressure test require?
Da Wang · Question 1

Request 2 hits 1,200 tokens and computes 30: a 1,210-token shared prefix provides 75 complete blocks. Repeating request 2 finds 76 blocks, hitting 1,216 and computing 14.

The final-token rule changes behavior additionally when input length is a multiple of 16. With full-block-only reuse, it sacrifices an entire block, not just one token.

This is not unconditionally necessary mathematics. Next-token prediction needs the prefix endpoint's final hidden state or already-computed vocabulary scores. This design stores only K/V, so it recomputes to obtain that output. Retaining it enables another restoration design. The cited vLLM version also limits final-token reuse and notes a possible small performance benefit from removing that restriction. [30]

Da Wang · Question 2

Valid prefix reuse is mathematically equivalent. Fix weights, position rules and precision and consider a correctly matched prefix. Causal attention cannot read future positions, so position i's per-layer K/V depends on tokens 1–i and relevant settings, not on later appended content.

The same mathematical objects can therefore be reused. Finite-precision execution paths may still differ. Mathematical equivalence and bitwise identity are different acceptance criteria.

Da Wang · Question 3

These fp32 controls locate differences in the last 30 tokens' computation, rather than prefix data in the cache table:

  • ⑤: bitwise-identical prefix K/V supports consistent data for the first 1,200 tokens.
  • ④: cached and simple 1,200+30 split execution match bitwise, showing no additional lookup/splicing discrepancy in this record.
  • ⑥: before any K/V read, the first layer's first matrix product already differs by 9.5e-6. The same rows within different matrix shapes can follow different computation and accumulation paths. Floating-point ordering changes rounding; later operations produce a maximum difference of 3.7e-5.

bf16 adds another source: prefix K/V itself is no longer bitwise identical in ⑤, with median relative error 0.6%; row ④ reaches maximum difference 0.37.

Uncached execution is not uniquely canonical. Row ③ shows differences from split computation on the same scale here. Row ⑦ is another batching control: fp32 differences are smaller and bf16 matches bitwise in this environment. Do not generalize to equal error magnitudes for every batching arrangement.

Da Wang · Question 4
First prove when greedy selection is unchanged

Let a be the uncached winner and j any competitor. Its lead is at least m. Every score in the other path changes by at most ε. In the worst case a falls by ε while j rises by ε, retaining a gap of at least m − 2ε.

m > 2ε ⇒ m − 2ε > 0

Then a strictly beats every competitor, so both greedy choices match.

The converse fails. m ≤ 2ε permits a change but does not require it. bf16 has 95 such steps and only 8 different greedy choices. fp32 has none, so all 750 greedy choices agree.

A total of 8/750 does not determine the first-divergence time

Both paths receive the same fixed history at every comparison. Eight counts selection differences, not their time-to-first distribution. That requires each prompt's first-difference position and which prompts remain unchanged at the observation endpoint.

For a counterexample, all eight differences could occur in one prompt's final eight steps, or in four prompts' first two steps. Both yield 8/750 with very different first-divergence behavior. The total does not justify independent steps or a constant difference probability.

How should sampling and evaluation be treated?

The original m is a greedy score gap without randomness and cannot directly guarantee equal samples. Temperature-1 sampling can be represented by adding suitable random perturbations before argmax. With common perturbations, recompute the gap after perturbation before applying the criterion.

This sample has 9 sampling differences versus 8 greedy differences: similar observed counts only, not proof of equal probabilities. Different randomness adds sampling variation to individual text differences; distributions require separate comparison.

Step 32 belongs to prompt 1 here, hitting 1,200. Step 154 comes from another run hitting 1,216. Record cache state and hit length in evaluations, but do not mix the second run into the 750 comparisons.

Additional experiment record: answer question 4 before expanding

The comparison method is unchanged: five prompts × 150 steps, feeding the same supplied history to both paths at each step. They do not freely continue independently after the first difference.

Environment: Qwen3-0.6B, revision c1899de289a04d12100db370d81485cdf75e47ca; PyTorch 2.14.1 CPU; transformers 5.18.0; two threads; fp32/bf16.

Additional record · 150 comparisons per prompt
Observationbf16 greedyTemperature 1, common random numbers
Prompts with a first difference within 150 steps3 of 54 of 5
First-difference positions, sorted22、32、548、11、20、79
No difference observed by the endpoint2 prompts1 prompt
All difference counts in prompt order1, 0, 4, 3, 0; total 82, 2, 2, 3, 0; total 9

Position sets are sorted and cannot be mapped to prompt IDs in listed order. The only explicit correspondence is prompt 1: a 1,200-token hit and first difference at step 32.

Xiao Wang · Follow-up Which conclusions change with per-prompt data? How should prompts with no difference by the end be recorded? Can we now calculate every prompt's average waiting time or assume one universal probability?

Da Wang · What does the additional record establish?

We can describe these five prompts: greedy differs within 150 steps on three and not on two; shared-randomness sampling differs on four and not on one. This locates differences across prompts and time, beyond totals of eight and nine.

Unchanged prompts should be recorded as “no difference observed through step 150,” not “difference at step 150.” Their eventual outcome is unknown, so a complete average waiting time is unavailable. This also does not restore the earlier estimates of “about 90” or “about 65” steps.

Eight and nine remain fixed-history selection-difference counts. Five prompts do not establish a universal probability or predict divergence under free generation. The greedy-gap proof and sampling conditions still need their separate interpretations.

Da Wang · Question 5

From layer 2 onward, a token's K/V can depend on all permitted preceding content. Splicing after different content changes those dependencies; a different prefix length also changes positions. Matching document tokens alone do not preserve K/V validity.

The equal-length wrong splice has KL 3.1e-2, roughly 500,000 times valid-prefix caching's 5.8e-8. The same most-likely token does not establish correctness.

Identity must cover computation-changing conditions: complete prefix, position rules, weight revision, numerical precision and applicable LoRA or image content. Identical image placeholder tokens do not mean identical images.

Chained hashing is not required. A complete-prefix hash or exact token matching along a prefix tree can represent the same dependency identity. Chaining conveniently extends existing IDs one block at a time and shares matching blocks across requests.

Tenant authorization is a separate check. It determines who may share, not numerical compatibility. Compatible state does not grant cross-user sharing permission.

Da Wang · Question 6

(a) Lookup starts at block 1 and stops at a miss. Registering recency from first to last makes block 1 oldest, so it is evicted first; the remaining eleven occupy capacity but are unreachable by this lookup. Evicting the tail instead preserves eleven blocks and hits 176/195. The cited vLLM implementation releases blocks in reverse order, a related management choice. [30]

(b) Clearing occurs after copying finishes. Unchanged output shows the request no longer reads the source table afterward. It says nothing about safety during copying.

Until when must source data remain protected?
DesignWhen protection may end
Copy into private request-owned dataAfter copying completes and the request no longer depends on the source.
Read shared pages at every stepAfter the final user stops reading those pages.

Reference counts, locks or other ownership mechanisms can protect the source. A fully serial teaching implementation may need no concurrent protection because eviction cannot overlap copying. That follows from execution conditions, not from copying inherently eliminating the need.

(c) An in-use boolean cannot count remaining users. A1 marks pages idle on completion; B overwrites them while A2 still reads them. Even with 19 of 20 tokens unchanged, A2 reads incorrect data and maximum logit difference reaches 5.2.

Pressure tests check three things separately
  • Computational correctness: compare logits against the same request and computational conditions without pressure, not just text. Explain ordinary numerical differences using valid-cache and split-computation controls; do not use them to excuse wrong reads.
  • Data lifetime: remove counts or substitute a boolean as a failing control, verifying the test detects overwriting pages still in use.
  • Reuse efficiency: measure the prefix still reachable after eviction. Deleting the first block can recompute correctly while losing all hits. Correctness alone does not establish an efficient policy.
Da Wang · Classify the acceptance criteria

Normal cached/uncached paths in experiments 1–2 concern finite-precision differences between mathematically equivalent computations. Wrong splices in experiment 3 and overwritten shared pages in experiment 4 are correctness bugs, not floating-point effects. Bounded replay intentionally changes restoration computation and needs a separate task-quality evaluation.

Mostly unchanged text does not prove valid reads; poor hit rate need not mean incorrect computation. Verify correctness, lifetime and reuse efficiency separately.

Further rules and implementation references: [4][29][30] vLLM is a design comparison, not the implementation producing these numerical records.

Exercise 3

What fits, what can be read, and where can execution resume?

Xiao Wang is choosing state retention for long conversations. Smaller storage may still be repeatedly read by many layers; a tiny final state may not restore the document boundary. Compare three teaching configurations, not actual named models.

Fix the comparison conditions first

Each session has processed 4,096 tokens: 3,072 document tokens followed by a 1,024-token old question. All models have 32 layers and fp16 state, two bytes per number. 1 KiB = 1,024 bytes; 1 MiB = 1,024 KiB.

After weights, workspace and management overhead, 640 MiB remains for these per-session states, with no cross-session sharing. Exclude text, page tables, indices and temporary tensors. Original token IDs are available elsewhere for replay; there is no additional compressor state.

Given configurations · Three independent teaching designs
SchemeRetained stateWhat each layer reads next
A · Full historical K/VEach of 32 layers keeps full K/V. Per token, K and V each have two heads of 64 numbers.Each layer reads its own 4,096-token K/V history once.
B · Shared global + local windowsFour groups of eight layers each share a global K/V history shaped as in A. Every layer additionally has an independent 128-token local window with the same K/V shape.Every layer reads its group's entire global history and its own 128-token window. The two states are computed separately, not merged into one read.
C · Fixed-size recurrent stateEvery layer retains a 64×256 matrix, updating it from the current matrix and this layer's input for each processed token.Each layer reads its whole matrix once and produces its updated matrix, without per-token historical K/V reads.

Only the stated state is retained, without additional document-end snapshots. B's global and local K/V are distinct: global spans 4,096 positions; local retains only the final 128. The window means 128 old tokens, excluding the new token: read old state before append/update. C retains only final matrices. Initial matrices are zero and reconstructible without per-session storage. No inverse or rollback algorithm is provided.

Updates are deterministic, allowing exact restoration by replaying the same computational path from initial state. B additionally permits a short replay of the document's final 128 tokens with earlier local dependencies truncated. This intentionally accepts approximation and need not reconstruct the original state.

Xiao Wang · Three questions

  1. How much state does each scheme retain? How many such sessions fit within 640 MiB? Which parts grow with text length?
  2. Feed the next known token to predict the following one. Count only reads of the retained 4,096-token history, excluding new-state writes, current-token work and weights. How much does each scheme read per step? Does less storage guarantee fewer reads or faster execution?
  3. Replace the old question while keeping the document. Can each scheme continue directly from the document boundary? What is missing, where must recomputation begin, and what snapshot would change the answer?
Da Wang · Question 1: retained size and capacity

One token's K/V in one layer has two arrays, each 2×64 numbers at two bytes: 512 bytes. Thus 4,096 positions occupy 2 MiB; a 128-token window occupies 64 KiB.

Question 1 · Per-session state and capacity limits
SchemeStored sizeSessions within 640 MiB
A2 MiB per layer × 32 = 64 MiB10 same-length sessions
BFour global histories: 8 MiB; 32 local windows: 2 MiB; total 10 MiB64 sessions
C32 KiB per matrix × 32 layers = 1 MiB640 sessions

All of A and B's global portion grow with token count. B's filled windows and C's matrices stay fixed-size. The 10/64/640 figures are capacity-only upper bounds at this length, not concurrency levels meeting a latency target. Growing state during later generation consumes additional space.

Da Wang · Question 2: stored once may mean read repeatedly
Question 2 · Logical old-state reads per step
SchemeCalculationLogical read volume
A32 layers each read 2 MiB of history64 MiB/step
B32 layers each read 2 MiB shared global state plus 64 KiB local state66 MiB/step
C32 layers each read a 32 KiB current matrix1 MiB/step

B stores only four global histories, but eight layers read each one. The stored 8 MiB is therefore not its per-step global read volume. Here B stores much less than A but logically reads an extra 2 MiB of local windows.

Logical read volume sums required per-layer reads. Hardware may reuse nearer caches, while tiling, layout and transfers can add traffic. Without effective bandwidth, execution efficiency and queueing conditions, these volumes do not specify generation speed.

Da Wang · Question 3: restore the document boundary

The new question is already given input; we are not first predicting the token after the document. Check state needed to process that input without additionally requiring retained document-end vocabulary scores.

Question 3 · Replace the old question at the document boundary
SchemeWhere continuation is currently possibleSnapshot that would change the result
AEvery layer retains K/V for document positions 1–3,072. Take that prefix and exclude the old-question suffix to process the new question.No additional intermediate snapshot is missing; full history is already retained.
BGlobal document K/V remains, but each layer's window holds only old-question positions 3,969–4,096. Document-end local state is missing and must be reconstructed.Snapshot all 32 local windows after token 3,072: 2 MiB. Together with retained global prefix state, this restores the endpoint.
COnly matrices after token 4,096 remain; no permitted rollback is available. Sequentially replay the document's 3,072 tokens from initial matrices.Snapshot all 32 matrices after token 3,072: 1 MiB. Load them and process the new question.

Without snapshots, B can replay the document along the original path from initial state to reconstruct local state exactly. Its optional 128-token replay is cheaper but truncates older dependencies and is intentionally approximate. C's final matrix cannot be decomposed into arbitrary prefix states using the provided operations; this does not claim every recurrent structure is mathematically irreversible.

Exact restoration means recovering the state required for continued computation, not just matching final text. Retaining history, recomputing missing state and accepting approximation are distinct choices.

Appendix B · Optional API probes

Turn reasoned predictions into observable comparisons

Fix one provider and model, then observe which input remains cache-readable after controlled changes. These examples use explicit Claude API cache markers.

B0 · Basic hits B1 · Lookback range B2 · TTL B3 · Concurrent entry points

B0 · First establish working caching and usage records

The original four-request baseline tests changes before and after an explicit boundary. B1–B3 then test more easily confused predictions from Appendix A.

Save a public UTF-8 document as public_notes.txt, long enough for the chosen model's minimum cache requirement. Keep model and other settings fixed across the four requests and follow the table's order. Establish the cache before comparing changes. [4]

RequestThe only changeObservation
1 · Prepare cacheSubmit this experiment's document and ask for a one-sentence summary.Does cache-write usage appear?
2 · Confirm hitRepeat request 1's entire input.Does cache-read usage appear? Stop without a confirmed hit.
3 · Change questionKeep the document; replace the final question with “List three key points.”Is the document's cached prefix still read?
4 · Change the beginningKeep question 3; change only the document's opening version marker from A to B.How do reads and writes change after the early modification?

A unique run identifier is shared by the four requests, reducing accidental reuse of earlier experiments. The cache marker ends the document; the question follows in a user message. Prior answers are not carried forward, keeping comparisons clear.

Running this costs API credits. At most four requests are sent, each limited to 96 output tokens. Errors or an unconfirmed second-request hit stop execution without automatic retries. Code is provided without invented measurement results.

Expand the complete Python probe and instructions

Use Python 3's standard library only. Save the code as kv_cache_probe.py and replace YOUR_MODEL_ID with an available prompt-caching model ID. The API key comes from an environment variable or a non-echoing prompt.

python3 kv_cache_probe.py public_notes.txt --model YOUR_MODEL_ID > kv-probe.jsonl
#!/usr/bin/env python3
"""Four sequential Claude API requests; running this file incurs API charges."""
import argparse
import getpass
import json
import os
from pathlib import Path
import sys
import time
import urllib.error
import urllib.request
import uuid


def request_once(case, prefix, question, model, api_key):
    payload = {
        "model": model,
        "max_tokens": 96,
        "stream": True,
        "system": [{"type": "text", "text": prefix,
                    "cache_control": {"type": "ephemeral"}}],
        "messages": [{"role": "user", "content": question}],
    }
    request = urllib.request.Request(
        "https://api.anthropic.com/v1/messages",
        data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
        headers={"content-type": "application/json", "x-api-key": api_key,
                 "anthropic-version": "2023-06-01"},
        method="POST",
    )
    started = time.perf_counter()
    first_text = None
    usage = {}
    complete = False
    error = None
    response_model = None
    stop_reason = None
    try:
        with urllib.request.urlopen(request, timeout=180) as response:
            for line in response:
                if not line.startswith(b"data:"):
                    continue
                event = json.loads(line[5:].strip())
                kind = event.get("type")
                if kind == "message_start":
                    message = event["message"]
                    usage.update(message.get("usage", {}))
                    response_model = message.get("model")
                elif kind == "message_delta":
                    # Output usage is cumulative: replace it, do not add it again.
                    usage.update(event.get("usage", {}))
                    stop_reason = event.get("delta", {}).get("stop_reason")
                elif kind == "content_block_delta":
                    delta = event.get("delta", {})
                    if delta.get("type") == "text_delta" and delta.get("text"):
                        if first_text is None:
                            first_text = time.perf_counter() - started
                elif kind == "error":
                    error = event.get("error", {}).get("type", "stream_error")
                    break
                elif kind == "message_stop":
                    complete = True
                    break
    except urllib.error.HTTPError as exc:
        error = f"HTTP {exc.code}"
    except (OSError, ValueError) as exc:
        error = type(exc).__name__
    if not complete and error is None:
        error = "stream_ended_without_message_stop"
    inputs = [usage.get(k) for k in (
        "input_tokens", "cache_read_input_tokens", "cache_creation_input_tokens")]
    return {
        "case": case, "requested_model": model, "response_model": response_model,
        "first_text_s": round(first_text, 3) if first_text is not None else None,
        "request_s": round(time.perf_counter() - started, 3),
        "total_input_tokens": sum(inputs) if all(type(v) is int for v in inputs) else None,
        "usage": usage, "stop_reason": stop_reason,
        "complete": complete, "error": error,
    }


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("document", help="UTF-8 public reference text")
    parser.add_argument("--model", required=True, help="An available Claude model ID")
    args = parser.parse_args()
    document = Path(args.document).read_text(encoding="utf-8")
    if not document.strip():
        parser.error("The reference text is empty.")
    api_key = os.environ.get("ANTHROPIC_API_KEY") or getpass.getpass("API Key: ")
    if not api_key.strip():
        parser.error("An API key is required.")
    run_id = uuid.uuid4().hex
    prefix = f"Experiment ID: {run_id}\nDocument version: A\nAnswer using the following reference.\n{document}"
    changed = prefix.replace("Document version: A", "Document version: B", 1)
    cases = [
        ("1_prepare", prefix, "Summarize this reference in one sentence."),
        ("2_confirm", prefix, "Summarize this reference in one sentence."),
        ("3_new_question", prefix, "List three key points from this reference."),
        ("4_changed_prefix", changed, "List three key points from this reference."),
    ]
    for case, material, question in cases:
        result = request_once(case, material, question, args.model, api_key)
        print(json.dumps(result, ensure_ascii=False), flush=True)
        if not result["complete"]:
            print("Incomplete request; stopped. Known usage is retained; no automatic retry.", file=sys.stderr)
            return 1
        if case == "2_confirm" and not (result["usage"].get("cache_read_input_tokens") or 0):
            print("Cache hit not confirmed; stopped. Check document length, caching rules and TTL.", file=sys.stderr)
            return 1
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

Each result-file line describes one request. usage retains returned API usage; first_text_s measures request start to the first visible text fragment, and request_s the complete duration. Missing fields remain unknown, not zero. Incomplete requests retain usage received so far. Interrupted records may be incomplete; the service bill is authoritative.

Calculate charges using the selected model's actual ordinary-input, cache-write, cache-read and output prices, recording their date. Inspect requests separately before totaling the entire experiment, including initial cache creation.

If request 2 misses, check length, marker and TTL first. A hit on request 3 followed by reduced reads or renewed writes on request 4 supports prefix-dependent reuse in this run. One run does not establish stable speedup. This tests caching behavior; whole-task benefits require the earlier evaluation method.

Before continuing: none of the following three plans has been executed

B1–B3 are optional experimental designs, not measurements. Use a currently supported fixed model/configuration and sufficiently long material. Give each group a unique identifier at the common document's beginning. Bound cost and request count beforehand, stop on errors and do not automatically add runs.

Record model/interface; thinking, effort, tools, breakpoint and TTL settings; actual request structure; request start, first response, completion and probe times; and raw input/read/write fields. Claude's input_tokens excludes cached reads/writes; add both for total input. First response means the earliest response event, not merely first visible text. Leave unavailable results blank or unknown.

B1 · With 18 or 19 file blocks, is the old entry still reachable?

Prediction: the previous endpoint has an automatically written entry. Append one assistant-text block and 18 or 19 file blocks. A 20-position lookback including the current breakpoint places the old entry at 20 or 21. An explicit marker at the old endpoint should change the latter outcome. [4]

Controls: count API content blocks, not tokens or physical cache pages. Keep text, token total, model and settings as constant as possible while splitting the same files into 18 or 19 blocks; record any actual token difference. Preserve the history and stay within TTL.

Schedule: four groups: 18/19 blocks × without/with an extra explicit marker, each with a unique prefix. Prepare each group's old endpoint, wait for completion and retain its write receipt, then probe. The explicit control marks the previous endpoint in the probe's carried history, without editing a submitted request. At most eight requests.

Record: appended block types/order, old endpoint's lookback index, markers, common-prefix length and read/write usage.

Interpretation: reuse with 18, loss with 19 and restoration with an explicit marker support the lookback explanation. If all four miss, check writes, length and identity before declaring the rule wrong.

B2 · Does five-minute TTL start with the request or its completion?

Prediction: start-based TTL includes response duration, so counting backward from completion overestimates remaining reuse time. [4]

Controls: unchanged prefix and matching settings; measure the actual fully received response. Do not delay reading intentionally or count a post-response sleep as generation time. Probe each entry once so the probe itself does not repeatedly refresh TTL.

Schedule: one independent prefix for a short-delay hit control and one for the timing probe, each with preparation and probe: at most four requests. Probe in a window where the explanations predict different outcomes. If a real request started 00:00 and finished 02:00, 05:30 is 330 seconds from start but 210 from completion. These are timing examples, not observations. If the real response is too short to distinguish the windows, record “not distinguished in this run.”

Record: use one monotonic clock for start, first response, completion and probe. Confirm no other request refreshed the same entry. Waiting schedules the probe; it does not alter measured response duration.

Interpretation: a short-delay control hit with a distinguishing-window miss is consistent with start-based TTL, but eviction or availability can also cause a miss. A hit requires checking unrecorded refreshes and timing error. Do not force a binary conclusion from incomplete conditions.

B3 · Is an entry existing the same as an entry being readable?

Prediction: staggering can address readiness but cannot create an unsaved common-prefix entry. A breakpoint alone also does not make an entry instantly readable to other requests. [4]

Controls: each pair shares a long document and fixed reply, differing only in final branch instructions. Preserve ordering, model and settings; use independent prefixes across groups.

Vary the two conditions independently to form four groups
Common-prefix endpoint markerSend timingQuestion being distinguished
AbsentSimultaneousNeither a dedicated entry nor a wait: baseline control.
AbsentSend B after A begins responding and provides a write receiptCan waiting alone reuse a common prefix that was never separately retained?
PresentSimultaneousThe endpoint is written this round, but is it readable when B begins processing?
PresentSend B after A begins responding and provides a write receiptAfter waiting for documented readability, can the full common prefix be reused?

Schedule: send A and B per group, at most eight requests, without separate warming. Use documented first-response/write-receipt evidence for readability, not “three seconds apart.” Simultaneous client sends may be serialized by the service, so send timestamps alone do not prove B could not read A's entry.

Record: endpoint-marker presence, A's first response and write receipt, B's send time and the prefix length B actually reuses.

Interpretation: compare timings under a fixed marker condition, then marker presence under fixed timing. A shorter hit after waiting may indicate a missing endpoint; a concurrent miss despite a marker may indicate unreadiness. Changing both at once does not isolate either cause.

These plans define evidence collection without filling in fictional results. Count cache establishment, controls and failed requests in total cost. Do not add requests indefinitely to obtain a desired hit.

Appendix C / RUN IT · Offline laboratory

Build a cache yourself: complete code and recorded results

Code and results come from a small randomly initialized model, testing state reuse and numerical computation rather than language capability or production speed. Original environment: Python 3.13.5, NumPy 2.3.5, CPU. These records remain separate from Appendix A's Qwen experiment.

Know what is being tested before running code

The experiment uses a three-layer causal Transformer: four Q heads, two KV heads, eight dimensions per head, RoPE, normalization, feed-forward and output layers. A fixed random seed initializes weights; inputs are integer vocabulary IDs.

FIG 39Turn expected equivalence into three executable controls

The minimal model initializes from scratch without model downloads. It exposes computation dependencies and cache-management mistakes.

↔ Scroll sideways; numbered notes follow their corresponding panels.

Fix weights, positions and inputs across three paths

Three paths compare the same predictionProcess all 47 tokens, split 32+15, or retrieve 32 cached tokens and compute 15. Compare the same endpoint's logits to avoid conflating free-generation divergence with cache correctness.First build request A: 42 shared tokens + 3 old suffix tokensThen B: the same 42 + 5 new suffix tokens = 47Full pass1Compute 47 tokens at onceSame endpoint logitsSplit only2Compute 32, then 15No cross-request cache lookupActual cache hit3Retrieve 32 old KV, compute 15Reuse full blocks from ACompare fixed-history endpoint logits, not free continuations.
42/45/47/32/15 are actual input constructions and recorded counts from the appendix program. Complete scripts, automated tests and raw JSON accompany Appendix C. [59]

PrefixCache stores sixteen-token blocks with weight identity, namespace and a chained prefix identity. Hits copy into private state. A separate PagePool simulator tests shared reference counts and copy-on-write. Keeping them separate avoids presenting a page-management class as an implemented GPU paging kernel.

Accompanying run resultsHitComputedCached vs full: maximum absolute differenceCached vs split: maximum absolute difference
float64321500
float32321500

Both recorded precisions show zero difference in these comparisons in the supplied environment. Reruns should retain their own values and test tolerances; different hardware and matrix shapes may produce small differences.

Other controlsSupplied resultWhat it establishes
Different equal-length context: maximum first-layer document-K difference0This model's first layer transforms positions independently before reading history
Maximum second-layer K difference for the same document2.112007Higher-layer state already depends on different context
Maximum next-token logit difference after an invalid splice1.311954Splicing can be wrong even with unchanged positions
Maximum difference after replaying the document under the new prefix0Full replay is the correct-repair control, not a selective-repair result
Maximum online-softmax vs full-result difference5.55e-17Checks global accumulator merging
INT4 payload + scales for 2,048 numbers1,024 + 512 = 1,536 bytesActual array bytes, excluding Python objects and runtime workspace
Xiao WangShould I read all the code before running it?

Da Wang: Predict hit length and which layer an invalid splice affects, then run tests. Read TinyDecoder.forward → PrefixCache.lookup → store, followed by online_attention and PagePool. Inspect one mechanism at a time rather than every implementation detail together.

Use the links to download the files. Opening this page or downloading a file does not execute its code.

Xiao WangHow should these files be arranged and run?

Da Wang: Put the model script, test script and requirements in one directory. NumPy need not be reinstalled if already available. Recorded results use Python 3.13.5, NumPy 2.3.5 and CPU. Run the following commands yourself in a terminal; opening this page does not run them.

python -m pip install -r requirements.txt
python -m unittest -v
python kv_lab.py --output my_results.json

The supplied run sets OPENBLAS_NUM_THREADS=1. Other platforms or thread counts can change floating-point accumulation. Retain your environment and results and use the tests' tolerances rather than requiring identical JSON bits. There is no BF16 experiment here, and CPU timing is not translated into GPU speedup.

Entry pointWhat to trace
TinyDecoder.forwardPositions include cached length; RoPE transforms current Q/K; append KV heads; attention cannot read future positions
PrefixCache.lookup / storeChained full-block identity; stop at first miss; private copies on hits; capacity and eviction
online_attentionPer-tile maximum, denominator and weighted accumulator; one final global normalization
pack_int4 / unpack_int4Actually pack two codes per byte; scales consume separate space; this is not NVFP4
PagePoolReference counts, release and copy-before-branch-write; this simulator does not drive model attention
Complete main script: model, cache and experiments

kv_lab.py · Same contents as the download above

#!/usr/bin/env python3
"""Small, offline KV-cache laboratory. This is NOT a pretrained language model.

Run: python kv_lab.py --output results.json
Requires Python >=3.10 and NumPy. No downloads, credentials, or API requests.
The model is a randomly initialized, 3-layer causal decoder with GQA and RoPE.
Its purpose is to test state reuse, not language ability or production speed.
"""
from __future__ import annotations

import argparse
import hashlib
import json
import math
import platform
import sys
from collections import OrderedDict
from dataclasses import dataclass
from pathlib import Path
from typing import Any

import numpy as np

Array = np.ndarray
State = list[tuple[Array, Array]]


def softmax(x: Array) -> Array:
    e = np.exp(x - x.max(axis=-1, keepdims=True))
    return e / e.sum(axis=-1, keepdims=True)


def rmsnorm(x: Array) -> Array:
    return x / np.sqrt(np.mean(x * x, axis=-1, keepdims=True) + 1e-6)


def rotate(x: Array, positions: Array) -> Array:
    """x: [heads, tokens, head_dim]; interleaved even/odd RoPE pairs."""
    d = x.shape[-1]
    freq = 10000.0 ** (-np.arange(0, d, 2, dtype=np.float64) / d)
    angles = positions[:, None] * freq[None, :]
    c, s = np.cos(angles).astype(x.dtype), np.sin(angles).astype(x.dtype)
    y = np.empty_like(x)
    y[..., 0::2] = x[..., 0::2] * c - x[..., 1::2] * s
    y[..., 1::2] = x[..., 0::2] * s + x[..., 1::2] * c
    return y


@dataclass(frozen=True)
class Config:
    vocab: int = 41
    width: int = 32
    layers: int = 3
    q_heads: int = 4
    kv_heads: int = 2
    head_dim: int = 8
    seed: int = 7
    dtype: str = "float64"


class TinyDecoder:
    def __init__(self, cfg: Config = Config()):
        if (cfg.width != cfg.q_heads * cfg.head_dim or
            cfg.q_heads % cfg.kv_heads or cfg.head_dim % 2):
            raise ValueError("Invalid head dimensions or GQA grouping")
        self.cfg = cfg
        dtype = np.dtype(cfg.dtype)
        if dtype not in (np.dtype('float32'), np.dtype('float64')):
            raise ValueError("This lab supports float32 and float64 only")
        rng = np.random.default_rng(cfg.seed)
        self.embed = (rng.normal(size=(cfg.vocab, cfg.width)) * 0.3).astype(dtype)
        shapes = [(cfg.width, cfg.q_heads * cfg.head_dim),
                  (cfg.width, cfg.kv_heads * cfg.head_dim),
                  (cfg.width, cfg.kv_heads * cfg.head_dim),
                  (cfg.width, cfg.width), (cfg.width, cfg.width * 2),
                  (cfg.width * 2, cfg.width)]
        self.weights = [[(rng.normal(size=sh) / math.sqrt(sh[0])).astype(dtype)
                         for sh in shapes] for _ in range(cfg.layers)]
        self.lm_head = (rng.normal(size=(cfg.width, cfg.vocab)) /
                        math.sqrt(cfg.width)).astype(dtype)
        h = hashlib.sha256(json.dumps(cfg.__dict__, sort_keys=True).encode())
        for a in [self.embed, self.lm_head] + [w for row in self.weights for w in row]:
            h.update(a.tobytes())
        # Includes actual weights, not just a model name. RoPE rule is fixed by code.
        self.identity = 'tiny-gqa-rope-v1:' + h.hexdigest()
        self.processed_positions = 0

    def empty_state(self) -> State:
        c = self.cfg
        return [(np.empty((c.kv_heads, 0, c.head_dim), dtype=c.dtype),
                 np.empty((c.kv_heads, 0, c.head_dim), dtype=c.dtype))
                for _ in range(c.layers)]

    def forward(self, tokens: list[int], past: State | None = None) -> tuple[Array, State]:
        """Processes ONLY tokens supplied here; past has already been computed.

        Token indices in the causal mask are ABSOLUTE, including the cached offset.
        This function returns new arrays; it never mutates a supplied cache.
        """
        c = self.cfg
        if not tokens or any(type(t) is not int or not 0 <= t < c.vocab for t in tokens):
            raise ValueError("Provide a nonempty list of integer IDs within the vocabulary")
        past = self.empty_state() if past is None else past
        if len(past) != c.layers:
            raise ValueError("Cache layer count mismatch")
        start = past[0][0].shape[1]
        for k, v in past:
            expected = (c.kv_heads, start, c.head_dim)
            if k.shape != expected or v.shape != expected:
                raise ValueError("Inconsistent cache shapes")
            if k.dtype != np.dtype(c.dtype) or v.dtype != np.dtype(c.dtype):
                raise ValueError("Cache dtype mismatch")
        pos = np.arange(start, start + len(tokens))
        x = self.embed[tokens].copy()
        out_state = []
        self.processed_positions += len(tokens)
        for layer, (wq, wk, wv, wo, w1, w2) in enumerate(self.weights):
            h = rmsnorm(x)
            q = (h @ wq).reshape(-1, c.q_heads, c.head_dim).transpose(1, 0, 2)
            k = (h @ wk).reshape(-1, c.kv_heads, c.head_dim).transpose(1, 0, 2)
            v = (h @ wv).reshape(-1, c.kv_heads, c.head_dim).transpose(1, 0, 2)
            q, k = rotate(q, pos), rotate(k, pos)
            k = np.concatenate([past[layer][0], k], axis=1)
            v = np.concatenate([past[layer][1], v], axis=1)
            out_state.append((k, v))  # Persist KV heads, not repeated Q heads.
            kr = np.repeat(k, c.q_heads // c.kv_heads, axis=0)
            vr = np.repeat(v, c.q_heads // c.kv_heads, axis=0)
            scores = (q @ kr.transpose(0, 2, 1)) / math.sqrt(c.head_dim)
            allowed = np.arange(k.shape[1])[None, :] <= pos[:, None]
            scores = np.where(allowed[None, :, :], scores, -np.inf)
            a = (softmax(scores) @ vr).transpose(1, 0, 2).reshape(-1, c.width)
            x = x + a @ wo
            z = rmsnorm(x) @ w1
            x = x + (z / (1 + np.exp(-z))) @ w2
        return rmsnorm(x) @ self.lm_head, out_state


@dataclass
class Entry:
    tokens: tuple[int, ...]  # Collision guard; each entry stores a full block only.
    state: State            # This block's K/V across all layers, never a whole prefix.


class PrefixCache:
    """Serial educational prefix cache. Hits are copied before model execution.

    Source blocks cannot be evicted midway because this implementation is serial.
    Production async transfers need ownership protection until the copy completes.
    This class stores neither final logits nor hidden states, so it leaves >=1 input.
    """
    def __init__(self, model: TinyDecoder, block_size: int = 16,
                 capacity_blocks: int = 128, namespace: str = "local-demo"):
        if block_size < 1 or capacity_blocks < 1:
            raise ValueError("Block size and capacity must be positive")
        self.model = model
        self.block_size = block_size
        self.capacity = capacity_blocks
        self.base = hashlib.sha256((model.identity + ':' + namespace).encode()).digest()
        self.blocks: OrderedDict[bytes, Entry] = OrderedDict()

    def keys(self, tokens: list[int]):
        parent = self.base
        b = self.block_size
        for end in range(b, len(tokens) + 1, b):
            block = tuple(tokens[end-b:end])
            raw = np.asarray(block, dtype='<i8').tobytes()
            key = hashlib.sha256(parent + raw).digest()
            yield end, key, block
            parent = key

    def lookup(self, tokens: list[int]) -> tuple[int, State | None]:
        rows: list[State] = []
        hit = 0
        for end, key, block in self.keys(tokens):
            if end >= len(tokens):
                break  # Need a final hidden state, not only K/V.
            entry = self.blocks.get(key)
            if entry is None or entry.tokens != block:
                break
            self.blocks.move_to_end(key)
            rows.append(entry.state)
            hit = end
        if not rows:
            return 0, None
        state = [(np.concatenate([r[i][0] for r in rows], axis=1).copy(),
                  np.concatenate([r[i][1] for r in rows], axis=1).copy())
                 for i in range(self.model.cfg.layers)]
        return hit, state

    def store(self, tokens: list[int], state: State) -> None:
        # Reverse insertion makes the beginning newer than the tail in this toy LRU.
        # It preserves a useful prefix when one old block must be evicted.
        for end, key, block in reversed(list(self.keys(tokens))):
            if key not in self.blocks:
                if len(self.blocks) >= self.capacity:
                    self.blocks.popitem(last=False)
                sliced = [(k[:, end-self.block_size:end].copy(),
                           v[:, end-self.block_size:end].copy()) for k, v in state]
                self.blocks[key] = Entry(block, sliced)
            self.blocks.move_to_end(key)

    def request(self, tokens: list[int]) -> tuple[Array, State, dict[str, int]]:
        hit, state = self.lookup(tokens)
        logits, state = self.model.forward(tokens[hit:], state)
        self.store(tokens, state)
        return logits[-1], state, {"input_tokens": len(tokens), "hit_tokens": hit,
                                   "computed_tokens": len(tokens)-hit}


def online_attention(q: Array, k: Array, v: Array, block: int = 7) -> Array:
    """One query, all keys visible. Online global softmax; not a GPU kernel."""
    m = -np.inf
    l = 0.0
    acc = np.zeros(v.shape[-1], dtype=np.float64)
    for i in range(0, len(k), block):
        s = k[i:i+block] @ q / math.sqrt(q.size)
        new_m = max(m, float(s.max()))
        alpha = math.exp(m-new_m)
        p = np.exp(s-new_m)
        acc = alpha * acc + p @ v[i:i+block]
        l = alpha * l + float(p.sum())
        m = new_m
    return acc / l


def pack_int4(x: Array, group: int = 16) -> tuple[Array, Array, tuple[int, ...]]:
    """Uniform signed INT4, NOT NVFP4. One FP32 scale per group, no zero point.
    Codes use [-7,7]; +8 offset enables two codes per uint8. Pad zeros at the tail.
    """
    if group < 2 or group % 2:
        raise ValueError("Group must be positive and even")
    f = np.asarray(x, dtype=np.float32).reshape(-1)
    if not np.isfinite(f).all():
        raise ValueError("Quantization requires finite input")
    n = ((len(f) + group - 1) // group) * group
    a = np.pad(f, (0, n-len(f))).reshape(-1, group)
    scales = np.max(np.abs(a), axis=1).astype(np.float32) / 7
    scales[scales == 0] = 1
    codes = (np.rint(a / scales[:, None]).clip(-7, 7).astype(np.int8) + 8).astype(np.uint8)
    codes = codes.reshape(-1)
    packed = codes[0::2] | (codes[1::2] << 4)
    return packed, scales, x.shape


def unpack_int4(packed: Array, scales: Array, shape: tuple[int, ...], group: int = 16) -> Array:
    codes = np.empty(packed.size * 2, dtype=np.int16)
    codes[0::2] = packed & 15
    codes[1::2] = packed >> 4
    a = ((codes - 8).reshape(-1, group) * scales[:, None]).reshape(-1)
    return a[:math.prod(shape)].reshape(shape)


class PagePool:
    """Small single-threaded page-ownership simulation (not model execution)."""
    def __init__(self, capacity: int = 4):
        self.capacity = capacity
        self.pages: dict[int, Array] = {}
        self.refs: dict[int, int] = {}

    def allocate(self, data: Array) -> int:
        for p in range(self.capacity):
            if self.refs.get(p, 0) == 0:
                self.pages[p] = data.copy()
                self.refs[p] = 1
                return p
        raise MemoryError("All pages are held by readers")

    def retain(self, p: int) -> None:
        if self.refs.get(p, 0) <= 0:
            raise RuntimeError("Cannot retain a free page")
        self.refs[p] += 1

    def release(self, p: int) -> None:
        if self.refs.get(p, 0) <= 0:
            raise RuntimeError("Double release or unknown page")
        self.refs[p] -= 1

    def private_for_write(self, p: int) -> int:
        if self.refs.get(p, 0) <= 0:
            raise RuntimeError("Cannot write a free page")
        if self.refs[p] == 1:
            return p
        # Allocate before dropping the old reference, so failure is safe.
        new_p = self.allocate(self.pages[p])
        self.release(p)
        return new_p


def max_error(a: Array, b: Array) -> float:
    return float(np.max(np.abs(a-b)))


def experiments() -> dict[str, Any]:
    rng = np.random.default_rng(20261009)
    prefix = rng.integers(0, 41, size=42).tolist()
    request_a = prefix + [4, 5, 6]
    request_b = prefix + [7, 8, 9, 10, 11]
    rows = []
    for dtype in ('float64', 'float32'):
        model = TinyDecoder(Config(dtype=dtype))
        cache = PrefixCache(model)
        cache.request(request_a)
        cached, _, usage = cache.request(request_b)
        full, _ = model.forward(request_b)
        _, pre = model.forward(request_b[:usage['hit_tokens']])
        split, _ = model.forward(request_b[usage['hit_tokens']:], pre)
        tolerance = 1e-10 if dtype == 'float64' else 2e-5
        assert max_error(cached, full[-1]) < tolerance
        assert max_error(cached, split[-1]) < tolerance
        rows.append({'dtype': dtype, **usage,
                     'cached_vs_full_max_abs': max_error(cached, full[-1]),
                     'split_vs_full_max_abs': max_error(split[-1], full[-1]),
                     'cached_vs_split_max_abs': max_error(cached, split[-1])})

    model = TinyDecoder()
    full, state = model.forward(request_b)
    _, old = model.forward([1, 2, 3, 4] + prefix)
    _, new = model.forward([5, 6, 7, 8] + prefix)
    first = max_error(old[0][0][:,4:], new[0][0][:,4:])
    higher = max_error(old[1][0][:,4:], new[1][0][:,4:])
    assert first < 1e-10 and higher > 1e-6
    # Same-length prefixes: isolate content dependency without a RoPE-offset change.
    _, lead = model.forward([5, 6, 7, 8])
    wrong = [(np.concatenate([lead[i][0], old[i][0][:,4:]], axis=1),
              np.concatenate([lead[i][1], old[i][1][:,4:]], axis=1))
             for i in range(model.cfg.layers)]
    wrong_logits, _ = model.forward([9], wrong)
    right_logits, _ = model.forward([9], new)
    wrong_error = max_error(wrong_logits, right_logits)
    assert wrong_error > 1e-6
    # Replay document under its new prefix: exact repair baseline, not CacheBlend.
    repaired_logits, repaired = model.forward(prefix, lead)
    repaired_next, _ = model.forward([9], repaired)
    assert max_error(repaired_next, right_logits) < 1e-10

    q = rng.normal(size=16)
    k = rng.normal(size=(37,16))
    v = rng.normal(size=(37,12))
    dense = softmax(k @ q / 4) @ v
    online = online_attention(q,k,v)
    online_error = max_error(dense, online)
    assert online_error < 1e-12
    data = rng.normal(size=(4,32,16)).astype(np.float32)
    packed, scales, shape = pack_int4(data)
    recovered = unpack_int4(packed, scales, shape)
    # Quantized model state errors are measured, never interpreted as task quality.
    quant_state = []
    for kk,vv in state:
        quant_state.append(tuple(unpack_int4(*pack_int4(a)).astype('float64') for a in (kk,vv)))
    ref_next,_ = model.forward([12],state)
    quant_next,_ = model.forward([12],quant_state)
    return {'lab_version':'1.0', 'run_date':'2026-10-09',
            'environment': {'python':platform.python_version(), 'numpy':np.__version__,
                            'platform':platform.platform(), 'device':'CPU'},
            'scope':'Random tiny model; numerical and state-management tests only. No API or pretrained-model benchmark.',
            'config':model.cfg.__dict__, 'model_identity':model.identity,
            'prefix_tests':rows,
            'context_dependency': {'first_layer_document_k_max_abs': first,
                                    'second_layer_document_k_max_abs': higher,
                                    'wrong_splice_next_logits_max_abs': wrong_error,
                                    'full_replay_repair_max_abs': max_error(repaired_next,right_logits)},
            'online_softmax_max_abs': online_error,
            'quantization': {'format':'uniform signed INT4; FP32 scale per 16 values',
                             'original_fp32_bytes':int(data.nbytes),
                             'hypothetical_fp16_bytes':int(data.size * 2),
                             'packed_data_bytes':int(packed.nbytes),
                             'scale_bytes':int(scales.nbytes),
                             'total_payload_bytes':int(packed.nbytes + scales.nbytes),
                             'reconstruction_max_abs':max_error(data,recovered),
                             'next_logits_max_abs':max_error(ref_next,quant_next)},
            'timing_note':'No latency benchmark: toy CPU timing is not evidence of GPU speedup.'}


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--output', type=Path, default=Path('results.json'))
    args = parser.parse_args()
    result = experiments()
    args.output.parent.mkdir(parents=True, exist_ok=True)
    args.output.write_text(json.dumps(result, ensure_ascii=False, indent=2)+'\n',encoding='utf-8')
    print(json.dumps(result, ensure_ascii=False, indent=2))


if __name__ == '__main__':
    main()
Complete automated tests: 15 cases

test_kv_lab.py · Same contents as the download above

"""Run with python -m unittest -v. Tests exercise deliberate failure controls."""
import unittest
import numpy as np
from kv_lab import (TinyDecoder, Config, PrefixCache, PagePool, softmax,
                    online_attention, pack_int4, unpack_int4, max_error)

class KVLabTests(unittest.TestCase):
    def setUp(self):
        self.model = TinyDecoder()
        self.tokens = (list(range(31)) * 2)[:53]

    def test_incremental_matches_full(self):
        full, _ = self.model.forward(self.tokens)
        _, pre = self.model.forward(self.tokens[:32])
        tail, _ = self.model.forward(self.tokens[32:], pre)
        self.assertLess(max_error(full[32:], tail), 1e-10)

    def test_prefix_hit_processes_only_suffix(self):
        cache = PrefixCache(self.model)
        cache.request(self.tokens)
        b = self.tokens[:42] + [39, 40, 38]
        before = self.model.processed_positions
        logits, _, usage = cache.request(b)
        self.assertEqual(usage['hit_tokens'], 32)
        self.assertEqual(self.model.processed_positions-before, 13)
        ref,_ = self.model.forward(b)
        self.assertLess(max_error(logits,ref[-1]),1e-10)

    def test_full_block_hit_leaves_one_block(self):
        cache = PrefixCache(self.model)
        cache.request(self.tokens[:48])
        _,_,u = cache.request(self.tokens[:48])
        self.assertEqual(u['hit_tokens'], 32)
        self.assertEqual(u['computed_tokens'], 16)

    def test_early_change_breaks_hash_chain(self):
        cache = PrefixCache(self.model)
        cache.request(self.tokens)
        changed = self.tokens.copy();changed[0]=40
        self.assertEqual(cache.lookup(changed)[0], 0)

    def test_identity_includes_weights_and_namespace(self):
        c1=PrefixCache(self.model,namespace='alpha')
        c2=PrefixCache(TinyDecoder(Config(seed=8)),namespace='alpha')
        c3=PrefixCache(self.model,namespace='beta')
        self.assertNotEqual(c1.base,c2.base)
        self.assertNotEqual(c1.base,c3.base)

    def test_past_not_mutated_and_no_private_alias(self):
        _, past = self.model.forward(self.tokens[:32])
        saved=past[0][0].copy()
        self.model.forward(self.tokens[32:],past)
        np.testing.assert_array_equal(saved,past[0][0])
        cache=PrefixCache(self.model);cache.request(self.tokens)
        _,hit=cache.lookup(self.tokens)
        hit[0][0][:]=0
        _,hit2=cache.lookup(self.tokens)
        self.assertGreater(np.max(np.abs(hit2[0][0])),0)

    def test_cache_shape_counts_kv_heads_not_q_heads(self):
        _,state=self.model.forward(self.tokens)
        c=self.model.cfg
        size=sum(k.nbytes+v.nbytes for k,v in state)
        self.assertEqual(size,2*c.layers*len(self.tokens)*c.kv_heads*c.head_dim*8)

    def test_online_global_softmax(self):
        rng=np.random.default_rng(2);q=rng.normal(size=8)
        k=rng.normal(size=(29,8));v=rng.normal(size=(29,5))
        ref=softmax(k@q/np.sqrt(8))@v
        for block in [1,3,7,29,50]:
            self.assertLess(max_error(ref,online_attention(q,k,v,block)),1e-12)

    def test_packed_int4_bytes_and_bound(self):
        x=np.linspace(-3,3,64,dtype=np.float32).reshape(4,16)
        packed,scales,shape=pack_int4(x)
        y=unpack_int4(packed,scales,shape)
        self.assertEqual(packed.nbytes,32)
        self.assertEqual(scales.nbytes,16)
        self.assertLessEqual(max_error(x,y),float(scales.max())/2+1e-6)
        z=np.zeros(17,dtype=np.float32)
        np.testing.assert_array_equal(unpack_int4(*pack_int4(z)),z)

    def test_reference_count_protects_second_reader(self):
        pool=PagePool(1);p=pool.allocate(np.array([1.0,2.0]))
        pool.retain(p);pool.release(p)
        with self.assertRaises(MemoryError):pool.allocate(np.array([9.0]))
        np.testing.assert_array_equal(pool.pages[p],[1.0,2.0])
        pool.release(p)
        self.assertEqual(pool.allocate(np.array([9.0])),p)

    def test_copy_on_write_separates_branches(self):
        pool=PagePool(2);p=pool.allocate(np.array([1.0,2.0,0.0]))
        pool.retain(p);branch=pool.private_for_write(p)
        pool.pages[branch][2]=9.0
        self.assertEqual(pool.pages[p][2],0.0)
        self.assertEqual(pool.refs[p],1);self.assertEqual(pool.refs[branch],1)

    def test_double_release_rejected(self):
        pool=PagePool();p=pool.allocate(np.zeros(3));pool.release(p)
        with self.assertRaises(RuntimeError):pool.release(p)

    def test_wrong_splice_is_detectable(self):
        doc=[8,9,10,11,12]
        _,old=self.model.forward([1,2]+doc)
        _,newlead=self.model.forward([3,4])
        wrong=[(np.concatenate([newlead[i][0],k[:,2:]],axis=1),
                np.concatenate([newlead[i][1],v[:,2:]],axis=1))
               for i,(k,v) in enumerate(old)]
        bad,_=self.model.forward([13],wrong)
        good,_=self.model.forward([3,4]+doc+[13])
        self.assertGreater(max_error(bad[-1],good[-1]),1e-6)

    def test_eviction_recovers_correctly(self):
        cache=PrefixCache(self.model,capacity_blocks=2)
        cache.request(self.tokens)
        self.assertLessEqual(len(cache.blocks),2)
        out,_,_=cache.request(self.tokens)
        full,_=self.model.forward(self.tokens)
        self.assertLess(max_error(out,full[-1]),1e-10)

    def test_invalid_inputs_rejected(self):
        for t in [[],[-1],[41],[1.2]]:
            with self.assertRaises(ValueError):self.model.forward(t)

if __name__=='__main__':unittest.main()
Raw measured results

results.json · Same contents as the download above

{
  "lab_version": "1.0",
  "run_date": "2026-10-09",
  "environment": {
    "python": "3.13.5",
    "numpy": "2.3.5",
    "platform": "Linux-6.18.44-x86_64-with-glibc2.41",
    "device": "CPU"
  },
  "scope": "Random tiny model; numerical and state-management tests only. No API or pretrained-model benchmark.",
  "config": {
    "vocab": 41,
    "width": 32,
    "layers": 3,
    "q_heads": 4,
    "kv_heads": 2,
    "head_dim": 8,
    "seed": 7,
    "dtype": "float64"
  },
  "model_identity": "tiny-gqa-rope-v1:d71a873dd3c45a619daae621b2fb66f86897f5f6b16f068a738f212adb3e60f2",
  "prefix_tests": [
    {
      "dtype": "float64",
      "input_tokens": 47,
      "hit_tokens": 32,
      "computed_tokens": 15,
      "cached_vs_full_max_abs": 0.0,
      "split_vs_full_max_abs": 0.0,
      "cached_vs_split_max_abs": 0.0
    },
    {
      "dtype": "float32",
      "input_tokens": 47,
      "hit_tokens": 32,
      "computed_tokens": 15,
      "cached_vs_full_max_abs": 0.0,
      "split_vs_full_max_abs": 0.0,
      "cached_vs_split_max_abs": 0.0
    }
  ],
  "context_dependency": {
    "first_layer_document_k_max_abs": 0.0,
    "second_layer_document_k_max_abs": 2.112007194820824,
    "wrong_splice_next_logits_max_abs": 1.3119541099416636,
    "full_replay_repair_max_abs": 0.0
  },
  "online_softmax_max_abs": 5.551115123125783e-17,
  "quantization": {
    "format": "uniform signed INT4; FP32 scale per 16 values",
    "original_fp32_bytes": 8192,
    "hypothetical_fp16_bytes": 4096,
    "packed_data_bytes": 1024,
    "scale_bytes": 512,
    "total_payload_bytes": 1536,
    "reconstruction_max_abs": 0.21534329652786255,
    "next_logits_max_abs": 0.09491987950877667
  },
  "timing_note": "No latency benchmark: toy CPU timing is not evidence of GPU speedup."
}
Raw test output

tests.txt · Same contents as the download above

test_cache_shape_counts_kv_heads_not_q_heads (test_kv_lab.KVLabTests.test_cache_shape_counts_kv_heads_not_q_heads) ... ok
test_copy_on_write_separates_branches (test_kv_lab.KVLabTests.test_copy_on_write_separates_branches) ... ok
test_double_release_rejected (test_kv_lab.KVLabTests.test_double_release_rejected) ... ok
test_early_change_breaks_hash_chain (test_kv_lab.KVLabTests.test_early_change_breaks_hash_chain) ... ok
test_eviction_recovers_correctly (test_kv_lab.KVLabTests.test_eviction_recovers_correctly) ... ok
test_full_block_hit_leaves_one_block (test_kv_lab.KVLabTests.test_full_block_hit_leaves_one_block) ... ok
test_identity_includes_weights_and_namespace (test_kv_lab.KVLabTests.test_identity_includes_weights_and_namespace) ... ok
test_incremental_matches_full (test_kv_lab.KVLabTests.test_incremental_matches_full) ... ok
test_invalid_inputs_rejected (test_kv_lab.KVLabTests.test_invalid_inputs_rejected) ... ok
test_online_global_softmax (test_kv_lab.KVLabTests.test_online_global_softmax) ... ok
test_packed_int4_bytes_and_bound (test_kv_lab.KVLabTests.test_packed_int4_bytes_and_bound) ... ok
test_past_not_mutated_and_no_private_alias (test_kv_lab.KVLabTests.test_past_not_mutated_and_no_private_alias) ... ok
test_prefix_hit_processes_only_suffix (test_kv_lab.KVLabTests.test_prefix_hit_processes_only_suffix) ... ok
test_reference_count_protects_second_reader (test_kv_lab.KVLabTests.test_reference_count_protects_second_reader) ... ok
test_wrong_splice_is_detectable (test_kv_lab.KVLabTests.test_wrong_splice_is_detectable) ... ok

----------------------------------------------------------------------
Ran 15 tests in 0.086s

OK
Complete running instructions

README.md · Same contents as the download above

# A minimal KV-cache laboratory: build state reuse first

This runnable companion laboratory, version 1.0, was prepared on 2026-10-09.
**It is neither a reproduction of Appendix A's Qwen3-0.6B experiment nor a pretrained language model.**

## What it runs

With a fixed random seed, `kv_lab.py` constructs a three-layer causal Transformer: four Q heads, two KV heads, head dimension eight, RoPE, RMSNorm, a feed-forward network and vocabulary projection. The vocabulary contains 41 integer IDs. There is no tokenizer, external dataset, model download or paid API.

It includes full-prefix caching in 16-token blocks with chained SHA-256, weight and namespace identities and private copies; online softmax; actually packed INT4; and a separate shared-page/copy-on-write simulator.

## Run

Python 3.10 or later is required. The supplied execution record used Python 3.13.5, NumPy 2.3.5 and CPU. If NumPy is installed, run directly. Install it yourself when needed:

```bash
python -m pip install numpy==2.3.5
python -m unittest -v
python kv_lab.py --output my_results.json
```

On macOS/Linux, `OPENBLAS_NUM_THREADS=1 python kv_lab.py --output my_results.json` fixes the BLAS thread count; the supplied record used that setting. Tests check FP64/FP32 tolerances rather than requiring every platform to match the supplied JSON bit for bit. NumPy 2.3.5's supported Python versions depend on the installation environment. Python 3.10 users may use a compatible NumPy version and must record the actual version.

## Predict before inspecting results

1. Two inputs share 42 tokens, while the cache retains only full 16-token blocks. The second input has 47 tokens: hit 32, compute 15, rather than reuse all 42.
2. Splitting the input into two passes without a cache-table lookup is an essential control. It separates execution-shape numerical differences from cache-management bugs.
3. Splicing old document K/V after different content of the same length can be invalid even with unchanged positions. Replaying the document is an exact-repair control; **CacheBlend is not implemented here**.
4. In `PagePool`, let two users share a page, then finish one. It must remain protected until the other finishes. Copy a shared writable tail before branching.
5. Two INT4 codes are actually packed into one byte. One FP32 scale per 16 numbers makes 2,048 numbers occupy 1,024 + 512 = 1,536 bytes. **This is not NVFP4** and excludes Python objects, alignment and workspace.

## Files

- `kv_lab.py`: complete model and cache experiments; writes the result JSON you specify.
- `test_kv_lab.py`: 15 automated tests.
- `results.json`: actual recorded values and environment, not invented examples.
- `tests.txt`: raw output from the recorded test run.
- `requirements.txt`: NumPy version used for that run.
- `SOURCE_NOTES.md`: mechanism references and experiment scope.

Download the executable scripts, requirements and raw records from the article's shared download links, and save them alongside these English instructions. English and Chinese editions use identical executable files and numerical records.

## Deliberately outside scope

There are no real GPU kernels, asynchronous transport, cross-process concurrency, language-quality evaluation, production security isolation, persistent caches or training. `PrefixCache` copying and eviction are fully serial. `PagePool` is an independent lifetime simulator, not `TinyDecoder`'s paged-attention backend. This laboratory does not establish a reproduction of vLLM, FlashAttention, NVFP4, CacheBlend or a cloud service.

Cache weight identity includes SHA-256 of the actual arrays, not just a model name. The local namespace example does not replace production authorization or a secret salt. JSON `run_date` is the record's preparation date. Keep your own execution date, versions and environment when rerunning.
READ THE SOURCE

Sources, code and worked-example assumptions

Pinned revisions and verification dates appear below. Teaching examples, Appendix A's existing experiment records, Appendix B's optional API probes and Appendix C's offline model are identified separately. Accompanying results retain their original execution environment.

01
Hugging Face Transformers · Caching ↗

Pinned to the 2026-09-30 commit for Q/K/V, sequential generation and KV-cache fundamentals. The explanations of first- versus deeper-layer dependencies follow from that computational structure.

02
vLLM · Automatic Prefix Caching design ↗

Pinned to the 2026-09-30 commit for block caches, parent hashes, identity and complete-block reuse. Engine versions, attention backends and hybrid architectures may add conditions.

03
vLLM · kv_cache_utils.py ↗

Public code inspected directly: hash_block_tokens combines the parent hash and current tokens. This pinned version also contains partial-block-length fields. The full-block teaching example intentionally describes a full-block-only scheme, not every newer implementation.

04
Anthropic · Prompt caching ↗

Checked 2026-10-02: complete prefixes, explicit/automatic breakpoints, a 20-position lookback including the breakpoint, start-based TTL, concurrent readability and warming constraints. This is a dynamic source, not a September snapshot. Appendix A fixes its own prices and token counts.

05
SGLang · Cache-tree matching and splitting ↗

Revision eb9c9ee99d47bf4c526a06cd84da59cd9cf4e2a5. RadixKey.match supports token-level precision; _insert_helper stores contiguous sequences in nodes, while _match_prefix_helper and _split_node split at required match boundaries. Tree nodes and actual K/V data are managed separately.

06
DeepSeek · Context Caching ↗

Checked 2026-10-02: API documentation explicitly connects SWA and prefix units, retains the A+B/A+C/A+D example and describes caching as best effort. Read with source 14 for restoration constraints; no live requests were executed.

07
YOCO · You Only Cache Once(v2,2024-05-09) ↗

Original architecture, shared global KV, bounded earlier-half state and complexity analysis. Its performance ratios are not measurements of DeepSeek V4.1.

08
Microsoft UniLM · Pinned YOCO reference implementation, 2024-05-09 ↗ Later-half attention implementation ↗

Relevant symbols: SelfDecoder, CrossDecoder.forward and skip_cross_decoder. The prefill-skipping explanation follows their K/V-production and cross-layer-read dependencies.

09
DeepSeek · Pinned Engram demonstration, 2026-01-14 ↗

Public code inspected directly: NgramHashMapping._get_ngram_hashes, MultiHeadEmbedding, Engram.forward and TransformerBlock.forward show multi-head modular addressing, learned vector tables, projections, gating and short convolution. It is a demonstration, not V4.1's complete inference implementation.

10
Engram · Conditional Memory via Scalable Lookup(v2,2026-07-12)↗

The paper's public abstract and official repository describe conditional memory, fixed-size n-gram lookup, deterministic addressing and host-memory prefetch. Its experimental speed ratios are not used to estimate V4.1 benefits.

11
Transformers · Llama intermediate computation and prediction outputs ↗

Pinned to the September 30, 2026 commit, last checked October 1. LlamaModel processes layers and prepares their K/V. LlamaForCausalLM can use logits_to_keep to project only the final input representation for next-token scores. The generation path ↗ defaults to 1 when supported and unspecified. Avoiding historical vocabulary predictions does not avoid intermediates needed for deeper K/V. LlamaAttention.forward rotates Q/K before cache update; V does not receive that layer's RoPE rotation.

12
DeepSeek V4.1 · Pinned public reference code ↗

Commit dba1be0a40aa45a94ad051997016db3960a90277. Configured K/V sources are code layers 2/8/14/20; index sources additionally include 24/28/32/36. Indexer.forward applies candidate masks after scoring; Transformer.forward still executes the supplied positions' backbone layers. Official configuration ↗ Model description ↗ Attention kernel at the same commit ↗ uses one kv array for matching and aggregation. Attention.forward in model.py then inverse-rotates the RoPE output portion. Selected IDs are sorted by original index before the kernel reads them; selection does not renumber historical positions.

13
vLLM · Cache identity and usage-state management ↗

Revision 866fa130fac1e1252330a680fbed45ad05eba64f. get_request_block_hasher extends the chain group by group after existing IDs. BlockPool.touch and free_blocks ↗ update usage counts and reclaim queues individually. These show identification and management work; counts and storage tables in the narrative are stipulated examples, not service-performance measurements.

14
DeepSeek-V4.1-Flash · Official technical report ↗

Pinned report: §2.2 covers encoder/decoder dependencies; §2.3.2 limits the candidate pool to the decoder; §3.2.1 reviews V4 global-cache/local-snapshot management; §3.2.2 describes approximate short-segment restoration. Its deployment scheme is distinguished from source 12's reference execution. §3.1.2 describes indexer optimization and gradient aggregation without a loss definition as complete as source 37. Checked 2026-10-09.

15
GLM-5.3 and GLM-5.3-Flash · Attention and cache implementations ↗

GLM-5.3's shared branch reuses selected historical positions while projecting K/V per layer. The GLM-5.3-Flash implementation ↗ and configuration ↗ show hybrid linear/sparse attention and private per-layer state. Code is pinned to the inspected revision.

16
Kimi-K3 · Official model code ↗

Historical-attention layers independently project K/V and update caches by layer index. The configuration ↗ specifies KDA/Gated MLA mixing; this hybrid design is not classified as YOCO here.

17
Qwen3.8-Flash-Next · Official model description ↗

Compare the Flash-Next implementation ↗ and Qwen3.8-2.4T-A95B architecture implementation ↗. Historical-K/V layers create and store private per-layer caches rather than original YOCO's shared later-half cache. Flash-Next's QSAIndexer scores and selects small historical blocks; its granularity differs from the single-record teaching example.

18
Google · Official Gemma 3n developer notes ↗

Local/global K/V produced by intermediate layers is shared by subsequent layers. The model call path ↗ and shared K/V read path ↗ show cross-layer reuse. This establishes a related sharing design, not identical adoption of original YOCO.

19
Pi · Release 0.99.1 ↗

Checked 2026-10-01 at commit d86654abb8862e201933517d6f1fce9f88dd117f. The changelog ↗ records dynamic-tool-loading, keep-alive and summarization changes.

20
Pi · Dynamic tools and stable prefixes ↗

The Anthropic adapter includes deferred placeholders and tool addition/removal paths. The server behavior motivating the placeholder is reported by the source author. Separately inspect OpenAI conversation tool additions ↗, Codemode directory stability ↗ and fallback conditions ↗.

21
Pi · Cache keep-alive implementation ↗

Checked isReplayable's thinking-budget guard, refresh timing, one-token output cap, minimum net saving, duration limits and late-timer exit. Usage enters session cost while returned content stays outside model dialogue. Defaults/settings ↗ and request integration ↗ provide related evidence.

22
Pi · Separate summarization requests and cache settings ↗

completeSummarization sets cacheRetention: "none". buildSummarizationContext and generateSummary use dedicated instructions and reformatted dialogue. The change description ↗ explains avoiding one-off summary cache-write charges.

23
Claude Code · Summarization while retaining the main-session prefix ↗

The 2026-04-30 engineering article describes cache-safe forking: preserve system prompt, tools and history, then append summary instructions to reuse the input prefix. It is a mechanism comparison with the pinned Pi version, not a measured cost comparison.

25
vLLM v0.30.0 · KV offload, reclamation and transfer protection ↗

Multi-tier offload uses host RAM to connect GPUs and other storage. Pinned block management ↗ distinguishes active/reclaimable blocks through counts; protection around loading ↗ prevents eviction during transfer; write completion ↗ handles success/failure separately. Narrative transfer times are teaching assumptions.

28
NVIDIA · Matrix data reuse and execution efficiency, 2023-02 v001 ↗

Document DU-09799-001_v001, chapter 02, connects GPU efficiency with arithmetic, traffic and matrix shape, including tiled loading/reuse. Used to explain amortized batching costs, not a specific API pricing multiplier.

29
Anthropic · Define tools ↗

Checked 2026-10-02: documented Opus 5 forced-tool invocation, with additional any/tool restrictions under manual extended thinking. Appendix A fixes a supported configuration and does not treat auto plus a textual instruction as enforcement.

30
vLLM v0.30.0 · kv_cache_manager.py ↗

Commit ced6857afa0ea7b2e3f0846a62e1394e90f15607: checked final-token recomputation limits and reverse block release. Appendix A's numerical environment is specified separately.

Same commit · Shared-page references and reclamation ↗

Checked 2026-10-12: increment references on shared-page hits, decrement on release and return to the free queue only at zero. This supports distinguishing discarded records from whole-page reclamation.

31
Anthropic · Tool reference ↗

Checked 2026-10-02: defer_loading and tool references keep deferred definitions outside the original tool prefix and expand them later. Pi's adaptation and placeholder are covered in source 20.

32
Pi · Codemode execution, filtering and external storage ↗

In the pinned version, internal-call results go to the script. Explicit script output, returns or errors enter model-visible content. Oversized output can be saved with a reading location returned. Script-callable tool definitions ↗ and searchTools/describeTool document scope. Checked 2026-10-03; diagram logs and token volumes are teaching choices.

33
Pi · tool_search discovery and activation ↗

searchAndLoad finds applicable undeclared tools, then setActiveTools enables their declarations for the next model request. Loading state is distinct from prior script-callability. Checked 2026-10-03.

34
Anthropic · Tool-prompt assembly ↗

Checked 2026-10-04: the API assembles tool definitions, settings and user system instructions. This explains the prompt's purpose, not why first enabling deferred loading must change the prefix; it does not disclose the full accompanying prompt referenced in Pi.

35
Cloudflare · Fixed search and execution entries ↗

Pinned 2026-09-28 commit: search ↗ executes catalog queries; execute ↗ performs calls. That version also exposes docs, so it is not described as invariably having only two tools. Search descriptions contain product overview material; fixed names alone do not ensure identical prompt text across versions. The 2026-02-20 announcement ↗ gives publication context. Code checked 2026-10-04; no cost or hit-rate comparison was run.

36
RoFormer · Original RoPE paper ↗

Pinned v5, §3.2: absolute-position rotations of Q/K make their dot product depend on relative position. Checked 2026-10-04. The two vectors, 0.75/0.25 weights and 10° increments demonstrate ordering, not real model parameters or measurements.

37
DeepSeek-V3.2 · Indexer reference and training objective ↗

Pinned v1, §2.1.1: the indexer learns main-attention distributions, with separate dense-warm-up and sparse-training objectives. Checked 2026-10-09. Three records/percentages are illustrative. Warm-up freezes the backbone; later training separates language-modeling and indexer losses. This does not establish V4.1's complete training procedure.

38
BERT · Original masked-language-modeling paper ↗

NAACL 2019 paper, §3.1: mask input tokens and predict the originals from context. Used to distinguish masked-word training from future-position attention masks. Checked 2026-10-09.

39
GQA · Original paper ↗

Submitted 2023-05-22. Used here to distinguish MHA/MQA/GQA architecture choices, not to present the paper's speed ratios as local measurements.

40
Transformers · Pinned Llama source ↗

Rechecked 2026-10-09: LlamaAttention projects and caches KV heads before repeat_kv in the ordinary attention path. Code tracing did not run the pretrained model.

41
Hugging Face · Cache strategies ↗

Dynamic cache-strategy documentation checked 2026-10-09 for DynamicCache, StaticCache, quantization and offload. “Static” does not mean unchanged input content.

42
KIVI · Asymmetric K/V quantization ↗

Pinned v2: K/V numerical characteristics, different quantization axes and retention of recent high-precision data. Its task metrics were not reproduced.

43
SGLang · Quantized caches and support conditions ↗

Checked 2026-10-09: distinguish NVFP4 and block16-compatible recipes, native reads, FP8 workspace, hardware and feature combinations. Dynamic implementation constraints are not permanent promises.

44
SGLang / Qwen / NVIDIA · NVFP4 data path ↗

2026-09-16 engineering article: distinct cache paths for initial input, appended input and decoding. Its reproduction pins commit c8b56b1f44d5c5370f47470ee490da3b04375e1c. Used only for execution-path explanation, not adopted performance ratios.

47
vLLM · Paged Attention ↗

Design/page-table addressing notes checked 2026-10-09. Page IDs, token counts and copy-on-write branches in the narrative are new teaching examples, not the source's performance results.

48
FlashAttention · KV-cache and page-table interface ↗

Public interface checked 2026-10-09: flash_attn_with_kvcache supports cache updates, GQA/MQA and block_table. No GPU kernel was run; local online softmax checks only the accumulator-merging relationship.

49
FlashAttention · IO-aware exact attention ↗
Pinned tiled accumulation, rescaling and final division source ↗
Same version: on-chip tiling and output writes ↗

Checked 2026-10-10 against paper §§2.2/3.1 and v2.8.3 softmax.h, commit 060c9188 dated 2025-08-14. Numerical examples were arithmetically checked; no GPU performance test was run.

54
Speculative decoding and cache cropping ↗

Fast Inference from Transformers via Speculative Decoding, v2. Checked 2026-10-12: §§2.1/3.3–3.4 cover batched verification, memory access and draft costs. Fewer passes are not equated with proportional speedup. Transformers cache_utils.py was also inspected for cropping and cache implementation ↗. A/B/C/D/X are teaching tokens; record truncation and page reclamation are distinguished.

Additional check 2026-10-15, §§3.1–3.3: the paper's DLK is TV, not KL. Average acceptance for fixed context is 1−TV. Accepted-length and theoretical-runtime analyses include further probabilistic and parallel-computation assumptions and are not actual speedup measurements.

59
Local experiment · Code and results ↗

2026-10-09: a custom three-layer random GQA model, Python 3.13.5 / NumPy 2.3.5 / CPU. Fifteen tests passed; complete code and raw results are supplied. This is neither a reproduction of the Qwen experiment nor production-model quality/latency measurement.

60
vLLM · Pinned speculative-verification implementation ↗

Commit ced6857afa0ea7b2e3f0846a62e1394e90f15607, checked 2026-10-12: greedy verification accepts a matching prefix, emits the target choice at the first mismatch and discards the remaining draft tail. The same file has random acceptance and corrected resampling. Tokens and cache lengths are illustrative with checked position relationships; no production model or speedup was measured.

Random-sampling checks: probabilistic acceptance, rejection and bonus tokens; resampling weights from target-minus-draft probabilities. A/B/C numbers are teaching choices checked with fractional arithmetic.

61
Transformers · Pinned candidate transfer and target verification ↗

Commit b1463f5b871aeb20d58384a98aad57905a690799, checked 2026-10-14: obtain candidate tokens/scores, run the target for candidate count plus one predictions, then decide acceptance. Source 40 covers per-layer Q/K/V and ordering. Only public code was traced; no pretrained model ran.

62
Qwen3-MoE · Attention caches, routing and within-layer ordering ↗

Pinned commit b1463f5b871aeb20d58384a98aad57905a690799, checked 2026-10-15: attention projects/updates K/V; the router selects and weights experts; merged expert output finishes the layer. Four heads and eight experts are teaching choices.

63
Qwen3-30B-A3B · Official configuration ↗

Model card checked 2026-10-15: 128 experts, eight activated per token; 32 query heads and four KV heads. Expert and KV counts describe different components.

64
DeepSeek-V3 · Expert parameter placement and output aggregation ↗

Pinned commit b15f0dbbbe6a4bc403306175698439ef380f5fb5, checked 2026-10-15: processes retain their assigned experts, execute selected experts and aggregate outputs. Attention and MoE execute successively within a layer.

65
Qwen3-MoE · Expert selection, continuous routing weights and training feedback ↗Same version: prediction and load-balancing auxiliary losses ↗

Pinned b1463f5b871aeb20d58384a98aad57905a690799, checked 2026-10-15: Top-k yields selected IDs and weights; weighted expert outputs are added. Labels enable prediction loss, with balancing terms under applicable settings. Diagram probabilities are illustrative; no model training ran.

66
DeepSeek-V3 report · Training-time load balancing ↗

Report v2, §2.1.2, checked 2026-10-15: routing biases adapt to batch load, affecting selection while output mixing uses original affinity scores. A small sequence-level balancing loss remains.

67
Mixtral of Experts · Routing and expert-specialization observations ↗

Paper v1 routing analysis checked 2026-10-15: selection can correlate with syntax, lexical features and positions. Expert IDs should not be treated as fixed math/code subject labels.

68
Transformers are RNNs · Classic linear attention ↗

Paper v3, §§3.2–3.4, checked 2026-10-15: changing similarity permits associative accumulation of S/z. Direct causal summation and recurrence use the same rule. Fixed state does not imply any softmax attention can be rewritten exactly in finite dimensions.

69
Authors' implementation · Accumulated-state updates and reads ↗

Pinned d82aca326eb228fafdf2942c9c26c343f6d1b8ef, checked 2026-10-15: update Si/Zi, query and normalize, return current state; inference mutates arrays in place. Narrative examples do not run a pretrained model.

70
LLaVA · Image features inserted into language input vectors ↗Mllama · Text Q, image K/V and cache reuse ↗

Transformers v4.57.1, checked 2026-10-15: LLaVA inserts image features into inputs_embeds. Mllama cross-attention forms Q from text hidden_states and K/V from cross_attention_states, reading that layer's existing image cache when available.

71
RoFormer · RoPE rotation and relative positions ↗

Original paper checked 2026-10-15: basic RoPE rotates coordinate pairs using sequence position. With fixed frequencies, Q/K rotations combine into relative rotation in their dot product. Physical cache addresses do not enter the equation.

72
PyTorch · Cross-entropy with class labels or target probabilities ↗Same version: KL-loss definition ↗Transformers · Next-token, masked-token and classification losses ↗

PyTorch v2.8.0 and pinned Transformers b1463f5b, checked 2026-10-15. The cross-entropy/KL relationship follows by expansion; update comparisons hold the target fixed. Teaching values were checked arithmetically without running training.

73
PyTorch · Entropy of a discrete distribution ↗Stable-Baselines3 · PPO entropy bonus for exploration ↗

Pinned implementation checked 2026-10-15: PPO adds negative mean policy entropy with a coefficient to the minimized loss. A positive coefficient encourages higher entropy. Per-token routing entropy is not equated with batch-level expert balancing.

74
PyTorch · Teacher probabilities guiding student training ↗

Dynamic tutorial and training code checked 2026-10-15: the teacher supplies probabilities without gradients; the student receives distribution-matching loss, optionally combined with label cross-entropy. This explains the training role without reproducing accuracy results.

From one token's cache to shared memoryEnglish edition · Diagrams and interactions · Downloadable experiments