How does one token’s
cache become
a shared memory?
From the KV cache, prefix hits and Claude’s cache breakpoints, all the way to YOCO, shared state and sparse reading. Twenty-two questions and answers put “can we store it?” and “can we afford to compute with it?” into one picture.
By the end, you should be able to tell apart identity, reuse, the size of the state, and the cost of what each step reads.
Contents · 6 parts · 22 Q&As
Xiao Wang: “Compute one token, save one token, and find it again later with a hash. Why do we still need so many caching rules?”
Da Wang: “That plan can work. What follows answers four things in turn: which states get stored, how the index is organized, whether an old state can be restored, and how much history each step still has to read.”
Isn’t satisfied with jargon, and keeps asking whether each step can actually be built.
Takes the question apart layer by layer with arithmetic, code boundaries and counterexamples.
Our conversation starts from the efficiency of DeepSeek V4.1 Flash, passes through the Claude API’s cache_control, and runs into a key objection: if a prefix’s identity can be represented by a hash, why do people keep saying the cache “must contain the entire prefix”? Further on, YOCO pushes the question one level deeper: if many layers each have to keep the history, could they all share a single copy?
A, B, “apples” and the other tokens in the diagrams are teaching examples; how real text is split into tokens depends on the model’s tokenizer. Simulated numbers are kept clearly apart from real API metrics.
First: what exactly is KV?
A cache pays off by avoiding repeated computation. To see how, first separate “reading input that already exists” from “generating new content.”
Why does judging a new model end up as a conversation about caching?
Picture an assistant that reads code, runs a terminal and fixes errors. The first time, you give it the project files; the second time, it comes back with test results; the third time, it keeps editing. The same tool descriptions, project conventions and conversation history show up in request after request.
One input → one answer
It’s easy to look only at whether the answer is clever and how fast the words come out.
History sent back again and again → many computations → an acceptance check
The cost of reprocessing the same context also affects whether a task is affordable at all.
So a useful question to ask of Flash-class models is: at the same acceptance standard, how much did the whole task cost, how long did it take, and how many retries did its failures need? Cheaper input processing can change that answer.
What exactly does the KV cache store?
In plain words first: after a model reads a position, it leaves behind information that later positions can query. In attention, K (Key) helps decide “which position to attend to,” V (Value) supplies the content that gets read out, and Q (Query) stands for the current lookup. All three are vectors of numbers: not the original text, not a summary of an answer, and not an ordinary database record.[1]
First score how well the current query matches each Key, then add up the Values by those weights.
A single call usually does two kinds of work. Prefill processes a long input that is already given, and can usually compute many positions in parallel batches. Decode, in ordinary autoregressive generation, produces the next token from what came before. The cache lets later computation use the K/V already produced, instead of encoding the old input again and again.[1]
“Every token has its own state” describes a logical correspondence. It doesn’t mean Prefill runs the input one token at a time, and it doesn’t mean every token gets its own GPU memory allocation.
Why isn’t “same KV means a hit” precise enough?
As a goal for reuse, the sentence is fine. But the server has to decide whether it can reuse anything before it recomputes the KV. The common approach is to give the input prefix an identity that can be looked up quickly, such as a prefix tree or a hash, and then find the matching cached state.[2]
For a causal model with fixed weights and the same input representation, positions and attention rules, an identical prefix is a sufficient condition for exact reuse, and one that is easy to check. Real systems also have to consider the model or adapter identity, multimodal content, tenant isolation and similar conditions.[3]
A sufficient condition is not the only mathematically possible one. Different inputs can happen to produce the same intermediate values, and special models may have stronger state equivalences. But an ordinary prefix cache doesn’t need to prove every equivalence; it picks a lookup rule whose correctness is easy to guarantee.
Does the whole prefix really have to be “stored whole”?
The key distinction here is between a prefix’s identity and the state computed from it. Separate the two, and hashes, per-token caching and block caching all fit into one picture.
Isn’t a request either a “hit” or a “miss”?
A request can reuse just the first part of its input. Suppose a request has 110,000 input tokens in total: 100,000 of them come from an existing cache, and the remaining 10,000 have to be processed again. Counted by tokens, the hit rate is:
This is a teaching example. A real metric must first say what its numerator and denominator are, and what it covers.
“The share of requests with any hit at all” and “the share of all input tokens that hit” are two different metrics. If each of a hundred requests hits just one token, the first can still be 100%, without necessarily saving much computation.
Only position 3 of 16 changed, yet an ordinary exact prefix cache can directly reuse only the first 2. Identical tokens after it don’t make the diverged states merge again.
This also explains a common piece of engineering advice: as long as it doesn’t change what the task means, put stable material as early as possible, and put changing content, such as timestamps and this turn’s question, later. One dynamic field near the start can stop a long stretch of identical text after it from being reused directly.[4]
It’s the same “apples”. Why can’t it just use the same KV?
Compare “I like apples” with “I hate apples”. The last token is the same and sits in the same position, but when the model processes “apples”, it reads a different history. The representations produced by earlier attention layers become the input from which later layers compute K/V.
F stands for the model’s computation. It is a different thing from the hash function used to look up the directory.
So using only token_id or (token_id, position) as a general cache key for higher-layer KV lumps different contexts together. The key also has to tell apart what came before.
But this does not yet show that “the cache key must contain the entire prefix in plain text”. That is exactly the next question.
“B comes after A” can be represented with a hash map. Is that objection right?
Yes. Your parent-hash scheme works. A prefix’s identity can be represented recursively. Let h₀ encode the initial identity conditions, and for each token compute:
The key only has to stand for “this path through the history”; it doesn’t have to carry the entire prefix text again and again.
vLLM’s hash_block_tokens does exactly this: it hands the parent block’s hash, the current block’s tokens and extra identity information to the hash function together. That is the same idea as “parent_hash + current_token”, except that it usually works in blocks.[3]
Whose state is this?
Hash(parent, token) is a short label for lookup. A rigorous production implementation also has to deal with collision risk and isolation.
What does computing this history leave behind?
What the directory finds is the actual KV storage, or a reference to it. To keep running the model, the relevant numbers still have to be read.
The hash map you proposed can point to the real KV. The idea never required “replacing the entire model state with a few dozen bytes”. The key boundary is this: shortening the label is easy; letting the model keep working from only a small state needs a different computational structure.
Writing an equals sign between KV_B and Hash(A,B) confuses the two. The clear way to write it is cache[key] → KV, or a reference to a restorable state.
Then why not create a reusable entry every time a token is computed?
You can. SGLang’s public RadixCache code has a matching branch for page_size = 1; in the ordinary one-token-key mode, prefix matching is precise down to a single token. That is enough to show there is no general theoretical barrier to per-token indexing.[5]
A coarser granularity is usually a cost trade-off. Put a run of consecutive K/V into one block, and indexing, reference counting, transfer and eviction can all be handled as a group. In a simplified scheme that reuses only complete blocks, with a shared prefix of length p and a block size of b:
Extra recomputation caused only by block alignment = p − r < b
This bound only limits the loss caused by granularity. When a cache has expired or a state is incomplete, the loss can be far larger than b−1.
In theory 999 can be reused; aligned to blocks of 16, 992 can. 7 are recomputed extra; the last 1 belongs to the new suffix that starts at the first divergence.
vLLM’s prefix caching documentation describes a scheme that reuses complete blocks. With b = 16, 999 identical tokens can be reused only up to 992 under that rule: 7 extra tokens are computed in exchange for fewer directory entries.[2]
But don’t go on to picture “per-token indexing” as “one GPU memory allocation per token”. The physical memory pool, the matching granularity and the way tree nodes are represented can each be designed separately, and a compressed prefix tree need not give every token its own tree node. Smaller physical pages can even reduce the unused space at the tail; the costs have to be measured on the actual system.
Da Wang: A short key saves the space of storing the entire prefix over and over, but the chain still has to be built in order. vLLM’s hash_block_tokens takes a parent_block_hash, so a new block’s key needs its parent’s key first. For a sequence of length N that is being indexed for the first time, with block size b, there are about N/b key constructions, one after another. Changing b from 16 to 1 makes that chain about 16 times as long.[3]
| Block size b | Complete blocks when N = 1,000,000 | Most extra at one divergence boundary | Average when boundary remainders are uniformly distributed |
|---|---|---|---|
| 1 | 1,000,000 | 0 tokens | 0 tokens |
| 16 | 62,500 | 15 tokens | 7.5 tokens |
| 64 | 15,625 | 63 tokens | 31.5 tokens |
This holds only when the shared prefix’s remainder within a block is roughly uniformly distributed. The strict upper bound is b − 1; b/2 is just the order of magnitude of the average.
What is being compared here is the extra tokens at one divergence boundary against the indexing work for a whole new path. That is not enough to declare that “a constant saving can never beat a linear cost”: the attention cost of one token can also grow with the length of the history, and a cache may be reused by many requests.
Two more qualifications. Hashing still has to read the current block’s tokens, so the total work of scanning the input is usually still O(N); an existing session can continue incrementally from a parent hash it already has, so new indexing mainly corresponds to ΔN. Compressed trees, batching and different hashing schemes may also be implemented differently. So the serial chain should be seen as a concrete cost of this kind of implementation, not an inevitable flaw of every per-token cache.
Same prefix: what else does a hit need?
The cache directory has to find it, the data has to still be there, and the state left behind has to be enough to continue from that position. This is the layer where an API’s cache breakpoints live.
What exactly does Claude’s breakpoint “cut off”?
Better ways to think of it are as a cache boundary or a restore point. Reading “breakpoint” as a cut-off point makes it easy to assume that whatever comes after it is no longer sent to the model.
Putting cache_control on a content block says this: the prefix from the start of the whole effective prompt up to the end of this block becomes a candidate for caching. Claude’s documentation sets the order as tools → system → messages.[4]
Producing the underlying K/V doesn’t wait for this marker. The API marker decides how the service layer stores, looks up and bills; it tells you nothing about when the GPU starts having K/V.
Several cache breakpoints can match different degrees of stability: tools and system instructions almost never change, project material changes now and then, and the conversation keeps growing. When a longer entry becomes invalid, a shorter one may still work. But each of them means “from the start up to here”; none turns identical text later on into a cache segment that stands on its own, apart from what came before.
If there’s automatic caching, do I still need to place cache breakpoints by hand?
It depends on how your requests change. Claude’s current documentation supports a top-level cache_control, which puts the cache breakpoint on the last cacheable content block. For requests that keep appending to an old conversation, that is convenient.[4]
Request 1: A + B
Request 2: A + B + C
The old complete prefix is still there. The cache breakpoint moves forward automatically and easily finds what was written last time.
Request 1: A + question X
Request 2: A + question Y
If only A+X was ever stored, A by itself never became a cache entry, so the stable material may still miss.
In the second case, it is usually better to put an explicit cache breakpoint at the end of the stable material A. Automatic caching won’t find every stable segment for you and create entries for them. The current rule also looks back only so far: it checks at most 20 content-block positions around each boundary, and it looks for entries that were actually written before.[4]
response = client.messages.create(
model=model_id,
max_tokens=1024,
cache_control={"type": "ephemeral"},
system=stable_system,
messages=growing_history,
)
response = client.messages.create(
model=model_id,
max_tokens=1024,
system=stable_system,
messages=[{
"role": "user",
"content": [
{"type": "text", "text": stable_document,
"cache_control": {"type": "ephemeral"}},
{"type": "text", "text": current_question},
],
}],
)
The code shows the shape of the request; your application has to supply client, model_id and the content variables. The prefix also has to reach the model’s minimum cacheable length. Caches expire; the current documentation offers 5-minute and 1-hour options. For how to call it, its limits and what is supported, follow the documentation of the platform you use.
When does the clock start?
Claude’s current documentation starts the TTL when the request that writes or reads the cache begins. For example, if a response streams for 4 minutes, a default 5-minute TTL has only about 1 minute left; if you then also wait for a tool to run, you may be too late to reuse the same entry.[4]
No error, yet usage is all zeros. Why?
A cached prefix has to reach the model’s minimum length. Some of the thresholds in the current official documentation are below. A request below the threshold is still processed normally; it does not raise an error just because nothing was cached.[4]
| Model | Minimum cacheable prefix |
|---|---|
| Claude Opus 5 | 512 tokens |
| Claude Sonnet 5 / Sonnet 4.6 | 1,024 tokens |
| Claude Haiku 4.5 | 4,096 tokens |
Recorded on September 10, 2026. The threshold applies to the length of the marked cacheable prefix, not just to whether the whole request is long enough. If cache_creation_input_tokens and cache_read_input_tokens are both 0, check this condition first; platform differences and model versions should still be checked against the corresponding documentation.
How does DeepSeek raise its real cache hit rate?
DeepSeek’s context caching documentation names three kinds of places where it persists state: the end of a request’s input and output, the common prefix of several requests, and fixed token intervals within long inputs or outputs. A later request can reuse a saved cache prefix unit only if it matches that unit completely.[6]
An abstract formula makes this clear. Let p be the length of the prefix two requests actually share, and E the set of boundaries that have been saved and can be restored. Under this complete-unit rule, the furthest position you can restore to directly is:
For a given input, the caching policy does not decide p; it can change E, and so change the r you can actually restore to.
Request boundaries catch the natural points to continue from; common-prefix detection adds the reuse points that actually occur; fixed intervals give long sequences extra coverage. All of them add to the “states that can be found and used again”; none makes two prefixes that were different the same.
The diagram above explains the service’s behavior. DeepSeek does not publish the data structure of its full cache directory in this API documentation, so you can’t conclude from it that the server must use a particular radix tree, hash chain or checkpoint spacing.
Da Wang: Every extra entry you keep takes index space, write bandwidth and storage space, and some models also need extra restore snapshots. So something has to decide which states deserve to hold those resources. That is a cache admission policy.
A common prefix being used again is a stronger reuse signal than “it has appeared once”. When resources are limited, keeping such prefixes first reduces how much single-use content crowds out the hot cache. DeepSeek’s documentation also notes that building the cache takes a few seconds, so there may still be a delay between finishing the computation and the persisted entry becoming usable.[6]
This is a cost model for explaining the trade-off, not an admission formula that DeepSeek has published.
“More worth saving once it is used again” is related to the idea of cache-on-second-access. But DeepSeek also saves entries at request boundaries and fixed intervals, so the whole system can’t be summed up as a strict second-access cache, and you can’t claim that every prefix must show up twice before it is saved to disk.
If A+B is already cached, why can’t we just cut off B and get A?
This goes deeper than “too many index entries”. In a standard implementation that keeps the full history of K/V in every layer, the idea works: as long as the data is still there, just take the first A positions. Whether the corresponding entry can be found is a separate question.
But many long-context architectures deliberately throw away old local state. DeepSeek’s documentation also says explicitly that sliding window attention changes how its cache is stored and matched.[6]
So “a compressed restore state exists for A+B” does not automatically imply “the full restore state of A can still be sliced out of it”. The memory structure a model uses directly affects how fine-grained a cache system’s restore points can be.
And how do SSDs and KV compression affect the hit rate?
The same prefix can miss because the cache hasn’t finished writing, has been evicted or has expired, or because isolation conditions differ. DeepSeek’s current documentation describes a disk cache that is on by default, and says that building it takes time, that it works on a best-effort basis, and that it does not promise a 100% hit rate.[6]
The same state takes less space
This comes from designs such as KV quantization, compression and sharing; each works on a different dimension.
The same capacity holds more history
If everything else stays the same, less cached content gets evicted early.
It is more likely to still be there on the next visit
The real chance of a hit may go up; storage bandwidth and restore overhead still have to be counted.
The above is a conditional causal chain, not a measured result showing that a product’s hit rate went up. A smaller cache lets you keep more history, or lets you spend the resources on serving more concurrent requests; how they are finally allocated depends on the system’s policy.
These conditions have to hold at the same time. They affect one another, so you can’t just treat them as independent probabilities and multiply them.
At this point the hash has done what it is good at: identifying things quickly. The next question starts to touch the model architecture: why is the state that has to be kept so large?
YOCO: can many layers share one long-term memory?
A serving system answers “can past computation be found again?”. YOCO goes further and changes how many copies of long-term state the model leaves behind, and which history positions still need further computation.
Why does a traditional KV cache grow with “length × layers”?
In an ordinary full-history Transformer, each layer produces K/V from its own intermediate representation. Every layer has to remember the entire history, so the longer the sequence and the more layers there are, the bigger the cache.
The 2 is for K and V; L is the number of layers, N the length, H_KV the number of KV heads, d_head the dimension per head, and s the bytes per number. Batching, metadata and other state are ignored.
This formula describes only a simplified shape of standard full-history K/V. It also makes it easy to see where several optimizations act: fewer KV heads, fewer dimensions per state, lower numerical precision, fewer kept positions, or fewer layers that each store their own long-term KV. YOCO mainly goes after the last one.[7]
Fewer KV heads
Many query heads share a few KV heads, as in GQA / MQA.
Narrower state
Carry the history in smaller vectors or latent representations.
Fewer bits
Store numbers at low precision; the quantization scales take space too.
Fewer positions
Merge, compress, or keep only some of the history positions.
Fewer repeated layers
Let several layers share one source of long-term state.
One carrier for K and V
If the model has the same vector serve both purposes, the ×2 in front has to be rewritten too.
The sixth item has to be achieved through architecture and training. In an ordinary model, K and V can differ, and the server cannot merge the two vectors into one and keep the original computation. Question 18 uses a sharing scheme with positional rotation to make this boundary clear.
The “Once” in YOCO: what exactly is done only once?
YOCO stands for You Only Cache Once. The original paper splits the model into a first half, the Self-decoder, and a second half, the Cross-decoder. The first half produces the context representation, from which the shared global K/V is generated; each layer in the second half reads that memory with its own queries.[7]
“Once” means that only one set of global history K/V is kept, and several second-half layers reuse it. It doesn’t mean a token is computed only once in the whole model, it doesn’t mean the model runs only once across requests, and it doesn’t mean the first half has no state at all. The original paper makes that last point in a footnote.[7]
YOCO’s window variant: O(N + LselfW)
Heads, dimensions and precision are left out to bring length and layer count to the fore. W is the local window width; the shared global KV still grows with N.
With the same per-token state width, the long-term memory for 100,000 tokens goes from 40 copies to 1. The local windows still keep about 10.49 MB; every number here follows from this example’s assumptions.
So YOCO does not squeeze a history of a million tokens into a fixed-length hash either. What it reduces is the repeated storage of long-term memory from layer to layer. The longer the context, the smaller the share taken by the fixed-size local windows.
The two halves still run in causal order: the current position can use only the history it is allowed to see, and each later new token passes through both halves again. Saying “the first half is like an encoder” does not mean it can read in both directions and see future tokens.[7]
How can a shared KV also let Prefill skip some layers?
In an ordinary Transformer, history positions must pass through the earlier layers to produce the K/V that each later layer uses. Even if you don’t need those history positions’ outputs, it’s hard to skip that computation.
YOCO changes the dependencies: the history K/V that the second-half layers need comes directly from the first half’s output. So history positions that are already given no longer have to pass through the whole second half just to build a cache for later use. In the public implementation, CrossDecoder.forward first generates and appends the shared K/V, then uses skip_cross_decoder to decide whether to keep running the second-half layers.[8]
This design has to be understood position by position. If the prompt has N positions, getting the first new token’s probabilities still means running the second half on the last input position’s representation. What can be saved is the second-half computation for many earlier history positions.
You only need to keep generating from a known prefix
Most history positions only build the memory. Their second-half representations are neither output nor used to generate future K/V.
Training, or when every position must output scores
To compute predictions and losses at every position, the corresponding second-half computation is still needed. Whether a given interface can skip it depends on what it has to return.
Beyond that, using efficient local or recurrent structures in the first half also lowers the cost of processing long inputs. The speedups in the paper depend on the baseline model, the sequence length and the implementation; you can’t paste them straight onto another product.
From YOCO to V4.1: what to store, recompute, and read?
Put cross-layer sharing, the 890 bytes, bounded replay and sparse reading each under its own accounting basis. Every step separates design conditions, mathematical derivation and runtime evidence.
V4.1 case study · basis of the sources
The V4.1-specific configuration, code behavior and report statements below follow the technical excerpt that accompanies the article; this article recomputes memory from those parameters and derives dependencies and complexity. The original config.json, model.py, kernel.py and the report’s full text were not obtained this time, so these points are not marked as independently verified source-code facts, nor treated as runtime measurements. General mechanisms and the official API rules that were checked directly have their own sources.[9]
Where exactly is V4.1 “YOCO-like”?
It can be spelled out as: which layers produce history, which layers share it, and which state each layer still keeps for itself. Following the configuration in the technical excerpt, the 40 layers split into two parts, 0–19 and 20–39; the global KV sources are [2, 8, 14, 20], and the first two layers keep only a sliding window.[9]
The excerpt ties the second half’s global KV to the first half’s final representation, while each layer keeps its own local sliding-window state. That has a clear structural link to YOCO’s cross-layer memory sharing; inside the first half, layers can also share in groups. When comparing models, look at the actual dependency edges, not just the two names “Encoder / Decoder”.[7][9]
| Question | What these materials can show | Boundaries that still need separating |
|---|---|---|
| Who produces the global KV? | The excerpt lists 4 sources and their layer groups. | A representation from the same source, the same projection and the same cache object have to be told apart. |
| Can Prefill skip the second half? | The report, as paraphrased, describes a shortened path on the deployment side; the reference implementation, as paraphrased, still runs layer by layer. | “The architecture allows it”, “the report says it saves” and “public code implements it” are different kinds of evidence. |
| How is 890 B/token calculated? | It can be recomputed from the width, precision, scales and the four sources’ compression ratios. | It measures the slope of the global KV, not all GPU memory. |
| Can 128 tokens restore the state? | The excerpt describes Bounded Replay as an approximate state reconstruction. | Approximate restoration needs quality evaluation; it can’t be claimed to be numerically equivalent. |
890 bytes/token: what exactly does it save?
First, be clear about the unit: for each extra original input token, how much more global KV has to be kept. That is different from “how much GPU memory the whole sequence takes”. Under the given quantization and sharing conditions, each compressed position needs 356 bytes.[9]
= 890 N bytes
This is the linear term for long sequences; short sequences have to be calculated from the actual compression groups, rounding and memory layout.
The scales here are the factors needed to bring low-precision numbers back to their proper size. In FP4 each number takes 0.5 bytes, but you can’t just multiply by 0.5 and stop: the main KV’s scales add 32 bytes, and the indexer K’s scales add another 4. The report excerpt calls them E4M3 and UE8M0 scales respectively.[9]
Now the local windows: estimating with 40 layers, 128 positions per layer, 512 dimensions and an FP8 carrier at 1 byte per number, the numerical payload of SWA is:
= 2,621,440 bytes ≈ 2.62 MB
This term does not depend on the long-context length N; SWA’s scales, alignment, metadata and so on are not yet counted. 1 MB = 10⁶ bytes; 2,621,440 bytes is also 2.5 MiB.
Under the conditions given in the excerpt, the global KV for 1,000,000 tokens is 890.00 MB, and the local windows’ numerical payload adds 2.62 MB. Together they average 892.62 B/token; other memory is not counted.
A conditional calculation, not a measurement of a product’s GPU memory. When N gets shorter, the local-window term does not shrink with it, so the total divided by N is higher than 890 B/token. This assumes N covers at least a full window.
| What is counted | What it includes | What you can’t conclude from it |
|---|---|---|
| 890 B/token | The four sources’ main KV, indexer K and the listed scales, weighted by compression ratio. | Whole-model GPU memory, actual throughput or API prices. |
| About 2.62 MB/sequence | Under the assumptions above, the numerical payload of every layer’s fixed SWA window. | All local state and engineering overhead. |
| Size persisted to SSD | The states and snapshots actually chosen when writing to disk, and how they are encoded. | The in-GPU-memory 890 can’t automatically confirm “1/8 of the previous generation”. |
| Full deployment resources | Also add weights, Engram, activations, workspace, concurrency, communication buffers and so on. | A global KV under 1 GB doesn’t mean the model fits on a 1 GB GPU. |
Can K and V really be the same vector?
K and V describe two uses: one takes part in matching, the other in summing up. A model can be designed so that the same stored vector serves both uses; then one fewer set of numbers has to be stored. The technical excerpt says V4.1’s kv_shared is used for both matching and the output multiplication, and the ledger is calculated on that condition.[9]
Rotary position encoding (RoPE) rotates part of a vector according to the token’s position, so that matching can express relative position. If the rotated vector used as the Key is also the Value, the summed output carries those rotations too. Multiplying the output for query position i by Ri−1 expresses it in the current position’s coordinates.
= ∑j αij Rj−icj
With the same set of RoPE frequencies, the rotation matrices satisfy Rᵢ⁻¹Rⱼ = Rⱼ₋ᵢ. This equality is a linear-algebra derivation.
Likewise, num_key_value_heads = 1 by itself only says there are few KV heads; it cannot on its own prove that K and V are the same vector. Whether they are shared requires looking further at the actual projections, the tensor references, and the inputs of the two matrix multiplications.
If we replay only the last 128 tokens, can we restore the state exactly?
Look at it as two questions: how to bring back the local state that wasn’t saved, and how exact the restored state needs to be. A single layer looks only at the most recent W positions, but once layers are stacked, the theoretical range of dependence usually widens. For a simple stack of L layers whose windows include the current position, the receptive field of a single output position can reach 1 + L(W−1).
The technical excerpt describes V4.1’s SWA Bounded Replay like this: to avoid saving every layer’s SWA state, it replays only one window to restore the state approximately. The excerpt also notes that the report admits the actual effective dependence may be shorter than the theoretical receptive field, and lists this kind of restoration as a robustness boundary that needs further characterization.[9]
Da Wang: It does. YOCO’s visible reference implementation has skip_cross_decoder, which can follow the shared-KV dependencies to skip the second-half computation for many history positions. The V4.1 technical excerpt, on the other hand, separates two paths:
Runs layer by layer over the input positions
Transformer.forward still runs all 40 layers. This behavior shows the model’s computation; it isn’t enough to reproduce an early exit on the deployment side.
First-half encoding + a short tail replay
Skips the second half for large stretches of history positions, keeps the path needed to produce the first output, and uses bounded replay to fill in the local state.
A structural ledger of “how many layers × how many positions were run”, treating the work at each layer and position as roughly equal. It doesn’t automatically include all attention scans, sparse indexing, communication and queuing costs.
So getting from “the architecture allows a short path” to “the server is actually faster” still goes through the scheduling implementation and measurement. The last input position has to produce the probabilities of the first new token; scoring every position, or training, can’t directly use the skip that works when you only generate the next token.
If a million tokens fit in storage, can we necessarily afford to compute with them?
One more question decides speed: for every generated token, how much history state does it actually have to touch? Squeezing all the KV from 10 GB to 1 GB reduces the pressure on capacity; if many layers still scan the full history again and again, reading and matching stay expensive.
According to the technical excerpt, layer 20 first builds a candidate pool; several later indexers pick the top 512 only within that pool, and some other layers reuse the selection. That removes the repeated work of “every layer searching the whole history once”.[9]
C ≤ 16,384; m is the number of layers that rerank
This only expresses the structure. The specific candidate-selection algorithm, the Top-K kernel and the constants still decide the real time.
Sparse reading also trades off capability: if a relevant position doesn’t make it into the candidate pool, the later fine ranking usually can’t conjure it back. So you have to look at candidate recall, answer quality and real latency together. Shared KV solves “how many copies to store”, shared candidates solve “how many times to search”, and sparse reading solves “how much to read each time”.
Does Engram let a hash find “knowledge” directly?
Engram gives the model an ability to “read a learned vector when a certain local combination shows up”. The official public implementation first maps an n-gram to several hash addresses, fetches the embedding vectors, and then injects them into the backbone network through a projection, a gate controlled by the current hidden state, and local fusion.[10]
An n-gram here means a local combination of n consecutive tokens. Once the model and table size are fixed, each position reads only a limited number of table entries; the addresses depend only on local tokens, so they can be prepared before the corresponding deep-layer computation happens, which makes them suitable for prefetching. The official paper also discusses a system arrangement that keeps large tables in host memory.[11]
“Store every n-gram” also needs a qualification: the hash table has limited capacity, and it does not give every possible combination its own collision-free slot. Different combinations can land at the same address, and multi-head hashing and training together deal with that compression. Unlike an exact KV cache, which tries hard to avoid mismatched identities, here collisions are a condition the representation design itself has to face.[10]
The V4.1 technical excerpt lists 2/3/4-gram memory used at layers 1 and 14. That describes one particular assembly; the original Engram demo code has a different configuration. Whichever layers it is placed in, it does not get around the fact that “different context can mean different higher-layer KV”: once static table entries are fused with dynamic hidden states, they can still produce different results.[9][10]
So the “memory” inside a model includes at least two kinds of things: what this request computed in the past, and what the training stage learned for common patterns. One has its lifetime managed by the cache system; the other usually travels with the model weights. The two can exist side by side.
Now, how do you apply this to your own agent?
The final goal is concrete: get tasks done correctly, at lower cost and with a more acceptable wait. A good-looking cache metric is only intermediate evidence.
So when I actually call the API, what should I change, and what should I watch?
First, map out the real input order of every request and mark the earliest position that changes. For append-only conversations, keep existing content in its original order as far as possible; for different questions about fixed material, separate the stable material from the dynamic suffix, and place cache boundaries according to the service’s rules.
Then look at the actual usage, and tell apart “cache reads”, “cache writes” and “regular input”. Don’t assume that fields called input_tokens at different providers are the same kind of total.
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
In Claude’s documentation, this input_tokens does not include the cache-read and cache-write parts above. Server-side tools and multi-turn aggregation should also be checked against the corresponding usage structure.[4]
hit = usage["prompt_cache_hit_tokens"]
miss = usage["prompt_cache_miss_tokens"]
hit_ratio = hit / (hit + miss) if hit + miss else 0
The field definitions come from DeepSeek’s context caching documentation. For batch statistics, use “sum of all hit tokens / sum of all input tokens”; hit percentages of requests with different lengths shouldn’t simply be averaged.[6]
| What you see | Check first | Conclusions not to jump to |
|---|---|---|
| Lots of cache writes every turn | Is the boundary placed after the changing suffix? Is the old entry beyond the lookup range? | “The model isn’t caching.” |
| Only a short stretch hits | Where is the first differing token? Did the tools or system content change? | “The material after it is identical, so the server must have a bug.” |
| An identical request still misses | Whether the write finished, expiry, isolation/routing, restore state, service guarantees. | “Identical tokens must always hit.” |
| High hit rate, but still slow | Output length, waiting for tools, cache loading, the attention computation that follows, queuing. | “A cache should mean the whole request needs no computation.” |
Then put cost and acceptance back in the same table. As a pure input-price model that ignores the write premium, with input volume N and hit rate h:
Prices p are per token. A real bill also adds output, cache writes or storage and so on, and the input volume N itself changes too.
A more valuable final metric is total cost during the evaluation ÷ the number of tasks that pass acceptance, reported together with the success rate, the task types, and the median and slow tail of the time taken. Measure a cold cache and a warmed-up cache separately, so that different conditions aren’t blended into one good-looking average.
For schemes with approximate restoration or sparse reading, evaluation should also cover consistency before and after restoration, task failures caused by missed candidates, and how performance accumulates over long sessions. The optimization target is always to pass the same acceptance checks; the bytes and scans saved have to show up in the end as task cost and time to completion.
Xiao Wang’s one question finally opens into four layers
Identity: whose history is this?
Hashes and directories can tell paths apart with short keys, but behind each key there still has to be data you can actually use.
Reuse: where can we pick up the computation later?
The cache system handles entries, admission, retention and restoration; exact reuse and approximate reconstruction have to be evaluated separately.
State: how much must we keep?
Cross-layer sharing, K/V sharing, positional compression and quantization together decide how large the history is.
Reading: how much history does the next step touch?
Layered candidates, sparse attention and shared retrieval decide whether, once it fits, you can afford to read it.
Engram adds one more side path: put some learned patterns into an addressable parameter table.
How do we compute less repeated history? How do we find it again? How do we make it lighter? How do we read only the part we really need?
Sources, code and the basis of the calculations
Product rules were recorded on September 10, 2026. Official documentation, public code, the technical excerpt provided and this article’s own derivations are each labeled; links may change as the upstream projects update. All diagrams and interactions are there to explain mechanisms; the V4.1 conditional ledger is not a product measurement.
The basic mechanism of Q/K/V, step-by-step generation and the KV cache. This article’s explanation of how the first layer and higher layers depend on the history is derived from that computational structure.
Block caching, parent block hashes, identity information and the rule of reusing complete blocks. The actual engine version, attention backend and hybrid architectures may add further conditions.
Public code read directly; find hash_block_tokens to see the parent block hash combined with the current tokens. The current source also has some fields related to partial block length; the article’s complete-block example is deliberately limited to a scheme that reuses only whole blocks, and does not claim that every new version behaves only this way.
Complete prefixes, explicit and automatic caching, the 20-content-block lookback rule, lifetimes and the usage fields. API behavior may change between versions.
Where to look in the source: RadixKey.match, page_aligned, RadixCache.match_prefix. There is a page_size=1 branch; matching granularity and tree-node compression can be handled separately.
The official API’s three kinds of persistence points, matching of complete cache units, the A+B / A+C / A+D example, best-effort behavior and the usage fields. It describes the service contract; it is not the source code of the full cache system.
The original architecture, the shared global KV, the first half’s bounded state, and the complexity analysis. The paper’s speedups can’t be taken directly as measurements of DeepSeek V4.1.
Where to look in the source: SelfDecoder, CrossDecoder.forward, skip_cross_decoder. The article’s explanation of which positions Prefill can save matches its K/V generation and cross-layer read dependencies.
Taken from the external technical feedback this article is based on, recorded on September 10, 2026. The material paraphrases config.json, model.py, kernel.py and the technical report; the original files and a pinned version were not obtained this time. Model-specific fields and implementation descriptions are used as “conditions given in the excerpt”; the 890 bytes and the window numbers are this article’s own independent arithmetic, and the dependency and rotation formulas are conditional derivations.
Original model page: deepseek-ai/DeepSeek-V4.1-Flash ↗. Repository metadata can’t stand in for the file contents.
Show the parameters and claims of the accompanying technical excerpt
Below is a parameter list compiled from the technical feedback provided. It is not the complete config.json, and it does not claim to be an original file downloaded directly.
layers = 40
kv_source_layer_ids = [2, 8, 14, 20]
source_compress_ratios = [2, 2, 2, 1]
first_two_layers_global_kv = False
main_kv_width = 512
main_kv_bits = 4
main_scale_group = 16
main_scale_bytes = 1 # E4M3 (per the excerpt)
indexer_k_width = 128
indexer_k_bits = 4
index_scale_group = 32
index_scale_bytes = 1 # UE8M0 (per the excerpt)
sliding_window = 128
swa_payload_bytes_per_value = 1
index_source_layer_ids = [2, 8, 14, 20, 24, 28, 32, 36]
decoder_candidate_blocks = 2048
candidate_block_size = 8
decoder_top_k = 512
engram_layer_ids = [1, 14]
engram_ngram_orders = [2, 3, 4]Behavior as paraphrased: only the source layers keep shared global state of their own; the same kv_shared is used for K and V, and the output applies an inverse RoPE; the minimal inference loop does not implement the Prefill skip described in the report; SWA Bounded Replay reconstructs approximately, based on the effective dependency range; the report’s SSD size comparison uses a persistence basis different from HBM. These paraphrases are kept separate from the numbers this article can recompute independently.
Public code read directly. Where to look: NgramHashMapping._get_ngram_hashes, MultiHeadEmbedding, Engram.forward, TransformerBlock.forward. It shows multi-head modulo addressing, the trained vector table, projection, gating and short convolution; it is a demo implementation, not V4.1’s full inference implementation.
The paper’s public abstract and the official repository’s description: conditional memory, fixed-size n-gram lookup, deterministic addressing and prefetching from host memory. This article does not borrow the specific multipliers from its experiments to estimate V4.1’s gains.