自回归每步只新算一个 token,全靠把先前 token 的 K/V 缓存起来——这就是 KV cache(L03/L04 反复提到的那个"记忆")。这一课钻进 llama-kv-cache:cell 怎么管理、上下文满了怎么移位、多条序列怎么共存, 以及为长上下文准备的各种变体。它是第四部分的收尾,也是大模型"记得住前文"的物理基础。
为什么 KV cache 值得收尾一讲?因为它是显存大户,也是长上下文、多并发这些实际能力的关键。前面 L17 说 context 持有它、L18 说 batch 往里写——这一课把这个"记忆"本身彻底拆开看清楚。
先说清它解决什么。注意力要让"当前 token"去看"之前所有 token",需要每个历史 token 的 K(键)和 V(值)。没有缓存,每生成一个新词,都得把前面所有 token 的 K/V 重算一遍——长度每涨一点,重算量就平方级地涨。
有了缓存,先前每个 token 的 K/V 算过一次就存着,每步只需算新 token 的 K/V、把它追加进缓存,再读出全部历史 K/V 做注意力。于是每步的计算量从"重算整段"降成"只算一个",这正是 L04 证明过的:缓存和重算数值上完全等价,但快了一个数量级。
这也直接解释了 L03 说的"decode 为什么快":decode 每步只新增一个 token,靠 KV cache 复用历史,所以一步只做一个 token 的前向。可以说,没有 KV cache,就没有实用的自回归生成——它是把"理论上能算"变成"实际跑得动"的那块关键拼图。
先快速回忆 K/V 是什么(L04/L11):注意力里,当前 token 用自己的 Q(查询)去和每个历史 token 的 K(键)做点积、算出"该关注谁",再按这个权重把各历史 token 的 V(值)加权汇总。所以"看历史"这件事,需要的正是每个历史 token 的 K 和 V——把它们缓存下来,就不用每步重算。
prefill 阶段(L03)正是一次性把整段 prompt 的 K/V 填进缓存:把 prompt 的几十上百个 token 一批喂进去(L18),并行算出它们各自的 K/V、全部写进 cell。填完,缓存里就有了整段 prompt 的记忆,之后 decode 逐字生成时,每个新词都能直接读到这份历史,不必回头重算 prompt。
顺带把"K/V"这俩字母的来历也点一下:它们来自数据库式的"键-值"(key-value)类比——K 像索引(拿 Q 去和它匹配、找相关的位置),V 像被取出的内容(按匹配权重汇总)。这个类比不必抠太死,但它能帮你记住:缓存 K/V,就是缓存"每个历史位置的可被检索的内容"。
把"平方 vs 线性"的差距再具体感受一下:生成第 1000 个 token 时,没缓存的话要把前 999 个的 K/V 全重算一遍,越往后每步越慢、整体是平方级;有缓存则第 1000 步和第 10 步一样,都只算一个新 token,整体线性。对动辄几千 token 的长对话,这个差距就是"跑得动"和"卡死"的分界。
KV cache 内部是一格格的 cell,每个 cell 存一个位置的 K/V。管理这些 cell 的是 llama_kv_cells:它记着每个 cell 的位置 pos 和所属的序列 seq_id;缓存还有个滚动写指针 head,标记下一个该往哪写。
// 简化自 src/llama-kv-cells.h / src/llama-kv-cache.h class llama_kv_cells { // 管理一格格 cell std::vector<llama_pos> pos; // 每个 cell 的位置 // 每个 cell 还记: 属于哪些 seq_id }; class llama_kv_cache : llama_memory_i { // src/llama-kv-cache.h llama_kv_cells_vec v_cells; // 实际存储(可多序列) // head(): 滚动写指针 };
注意 llama_kv_cache 派生自 llama_memory_i——这个基接口很重要,后面讲变体时会回到它。每个 cell 带 pos,是因为注意力的 rope(L16)和因果掩码都要知道"这个 K/V 是第几位的";每个 cell 带 seq_id,是为了支持多序列(下面讲)。
每步 decode,build_attn(L16)算出新 token 的 K/V 后,就写进 head 指的那个 cell、把 head 往后挪一格;做注意力时,再从所有属于本序列的 cell 里读出历史 K/V。所以 KV cache 不是被动的存储,而是每步都在增长、每步都被读取的活动记忆。把连续几步画出来,"只新算一格"就一目了然:
cell 和 token 是一一对应的:序列里第 i 个 token,就占缓存里某个 cell,存着它(其实是每一层都有一份)的 K 和 V。所以"缓存有多大"约等于"能记多少个 token 的 K/V",这也是 n_ctx(上下文长度,L17)的含义——它就是缓存能容纳的 cell 数上限。
做注意力时"只读本序列的 cell"也值得说清:缓存里可能混着好几条序列的 cell,但当前 token 只该看自己这条序列、且位置在自己之前的 K/V。前者靠 seq_id 过滤、后者靠因果掩码(L11 的 soft_max_ext)。两道过滤一叠加,就保证了"各序列互不串味、每个 token 只看得到过去"。
再强调一句"每一层都有一份":一个 transformer 有几十层,每一层都有自己的注意力、各自要缓存一套 K/V。所以一个 token 的"记忆"其实是几十份(每层一份)K/V 的集合。这也是为什么深一点的模型 KV cache 特别大——层数直接乘进了缓存大小里(深挖 1 的乘式里那个"层数"就是它)。
KV cache 不只是"往里写",还能被编辑。一组序列操作让你删、复制、保留、平移某条序列的 K/V,对应公开 C API 的 llama_memory_seq_*(经 llama_get_memory 拿到记忆对象)。
# 伪代码: 序列操作(公开 C API) mem = llama_get_memory(ctx) llama_memory_seq_rm (mem, seq, p0, p1) # 删 [p0,p1) 这段 KV llama_memory_seq_add(mem, seq, p0, p1, d) # 上下文移位: 把 pos 平移 d
这里最值得理解的是上下文移位(context shift)。当对话长到超过 n_ctx,缓存满了怎么办?一种办法就是:丢掉最旧的一段 K/V,把剩下那些 token 的 pos 整体往前挪(seq_add 一个负的位移),腾出尾部空间继续生成。这样既不用"满了就停",也不必重算整段——只是把记忆的窗口往前滑了一下。
seq_cp(复制序列)有个巧妙用途:共享前缀。比如一个很长的 system prompt,要同时生成好几个不同回答,可以先把它算一遍、存进序列 0,再 seq_cp 复制给序列 1、2、3……于是几条序列共享同一段前缀的 KV,不用各算一遍。这是服务器省算力的常用招数。
再回到那次改名:从 llama_kv_self_* 到 llama_memory_seq_*,不只是换个名字,而是概念的抽象升级。早期只有一种"KV cache",所以 API 就叫 kv;后来出现了 recurrent 这种"不是 KV、但也是记忆"的东西,于是把名字提升到更一般的 memory。一次改名,记录的是这个引擎从"只支持 transformer"到"也能容纳别的架构"的成长。
那几个序列操作里常见的 p0、p1 是位置范围:很多操作不是对整条序列、而是对"第 p0 到 p1 位"这一段做。比如只删一段、只移一段。这种"按位置区间操作"的精细度,让引擎能做很多花活——比如只回滚最近几个 token(撤销)、只移动中间一段。把记忆做成可按区间编辑的,灵活性就出来了。
因为每个 cell 都带 seq_id,一个 KV cache 能同时装多条序列:它们的 cell 混在同一块缓存里、靠 seq_id 区分,各做各的注意力(只看自己序列的 cell)。这就是 L18 多序列、服务器多并发的内存基础。
| 变体 | 思路 | 文件 |
|---|---|---|
| 标准 | 全注意力,每个 token 都缓存 | llama-kv-cache |
| iSWA 滑窗 | 只保留最近一窗的 K/V | llama-kv-cache-iswa |
| recurrent | 固定大小状态,不随长度涨 | llama-memory-recurrent |
| hybrid | 混合上面几种 | llama-memory-hybrid |
为什么需要这么多变体?因为标准全注意力的 KV cache 虽然把计算降成了线性,内存却仍随长度线性增长——上下文越长越占显存。滑窗(iSWA)只留最近一窗、recurrent 用固定状态、hybrid 混搭,都是为长上下文省内存的不同取舍。它们都实现同一个基接口 llama_memory_i,于是可以整体替换、引擎其余部分不变。
多序列"不串味"再强调一遍,因为它是并发的关键:三条序列的 cell 虽然挤在同一块缓存里,但每条序列做注意力时,seq_id 过滤让它只看见自己的 cell,仿佛缓存里只有它一条。于是一块物理缓存,逻辑上被切成了互不可见的多份。这种"物理共享、逻辑隔离",和 L17 的 context 共享权重是同一种省内存哲学。
recurrent 变体则更彻底:它对应的是 Mamba 这类非 transformer 的架构,用一个固定大小的状态来概括"到目前为止的全部历史",状态大小完全不随上下文长度变。这从根本上绕开了 KV cache 随长度涨的问题,代价是状态是"压缩过的历史"、不像全注意力那样能精确回看每个 token。把它也纳入 llama_memory_i,正是这套抽象的威力——连"记忆的根本机制都不同"的架构,都能接进同一个引擎。
把 KV cache 放回整条推理链:L18 的 batch 把新 token 喂进来 -> L16 的 build_attn 算出它的 K/V、写进 KV cache,又从 KV cache 读出历史 -> 算完更新缓存、推进一步。KV cache 就是那块被每一步反复读写的记忆,是自回归循环里"承上启下"的状态核心。前面所有部件,最后都围着它转。
还有一点值得知道:KV cache 的内存布局,和 L11 提过的 flash attention 这类优化是配合的——把 K/V 在内存里排得规整,注意力内核才能高效地一块块读、边读边算。所以 KV cache 不只是"存得下"就行,它怎么排也直接影响注意力算得快不快。存储布局和计算内核,在这一层是互相迁就的一对。
因为它要存的东西很多:每个 token、每一层、每个 KV 头,都要存一份 K 和一份 V。把这些乘起来——上下文长度 × 层数 × KV 头数 × 每头维度 × 2(K 和 V)——就是 KV cache 的大小。上下文一长,这个乘积就很可观,常常比模型权重之外最大的那块内存还大。
所以有两条省显存的路(L17 cparams 里见过):一是把 KV 量化存(type_k/type_v 从 16 位降到 8 位甚至更低,直接减半再减半);二是减小 n_ctx(少缓存几个 token)。L15 还提过 GQA——让 KV 头数远少于 Q 头数,从源头上就把 KV cache 缩小了。
理解了"KV cache 大小 = 长度 × 层 × KV头 × ..."这个乘式,你就能从一个模型的超参,估出开多长上下文会吃多少显存——这是部署大模型时一笔最该会算的账。
场景是这样:你和模型聊了很久,token 数眼看要超过 n_ctx(缓存装不下了)。最朴素的做法是停下来,但那体验很差。上下文移位提供了另一条路:丢掉最旧的一段对话(删掉那些 cell),把剩下保留部分的 pos 整体往前移,让位置重新从小开始排,尾部就空出了新位置。
关键是这只动位置标记、不重算 K/V 本身——seq_add 把一段 token 的 pos 平移一个量,缓存里的 K/V 内容不变,只是它们"对应的位置"变了。配合 rope 的相对位置性质,移位后模型还能正常往下接。所以它是一种"用很小代价续命"的手段。
当然,丢掉最旧的对话意味着模型会"忘记"开头说过的话——这是滑动窗口式记忆的固有代价。要不要移位、丢多少,是在"无限对话"和"记住全部"之间的权衡。顺带一提,更激进的整理(defrag)在新版里已被简化移除,defrag_thold 参数也标了弃用。
因为"怎么记住前文"其实有很多种策略,而引擎的其余部分(建图、执行、采样)不该关心用的是哪种。把它们共同的行为(写入新 token、读出历史、删/移序列)抽象成一个基接口 llama_memory_i,标准 KV cache、滑窗、recurrent、hybrid 都去实现它。
于是"换一种记忆策略"就变成"换一个实现 llama_memory_i 的类",llama_context 持有的那个 memory(L17)指向哪个实现,引擎照常调同一套接口。这正是 L17 说"字段名叫 memory 而非 kv_cache"的原因——它留好了容纳各种记忆策略的余地。
这又是一处熟悉的解耦:和 L12 的 type_traits(用接口容纳几十种量化)、L16 的 build_arch_graph(用虚函数容纳几十种架构)一脉相承。把"会变的策略"收进一个统一接口,把"不变的主干"留在外面——这是贯穿整个 llama.cpp 的设计母题,到 KV cache 这里又见到一次。
Autoregression computes only one new token per step, all thanks to caching prior tokens' K/V - that is the KV cache (the "memory" L03/L04 kept mentioning). This lesson digs into llama-kv-cache: how cells are managed, how to shift when the context fills, how multiple sequences coexist, and the various variants for long context. It closes Part 4 and is the physical basis for a large model "remembering the earlier text".
Why close with the KV cache? Because it is a VRAM heavyweight and the key to real capabilities like long context and concurrency. L17 said the context holds it, L18 said the batch writes into it - this lesson takes that "memory" itself fully apart.
First, what it solves. Attention has "the current token" look at "all earlier tokens", needing each historical token's K (key) and V (value). Without a cache, generating each new word recomputes all prior tokens' K/V - and as length grows a bit, the recompute grows quadratically.
With a cache, each prior token's K/V is computed once and stored, and each step only computes the new token's K/V, appends it to the cache, and reads all historical K/V for attention. So per-step compute drops from "recompute the whole segment" to "compute one" - exactly what L04 proved: caching and recompute are numerically identical, but an order of magnitude faster.
This also directly explains L03's "why decode is fast": decode adds only one token per step, reusing history via the KV cache, so a step does just one token's forward. In short, without the KV cache there is no practical autoregressive generation - it is the key piece turning "computable in theory" into "actually runnable".
A quick recap of what K/V are (L04/L11): in attention, the current token uses its own Q (query) to dot-product with each historical token's K (key), computing "who to attend to", then weight-sums each historical token's V (value) by those weights. So "looking at history" needs exactly each historical token's K and V - cache them and you avoid recomputing each step.
The prefill phase (L03) is exactly filling the cache with the whole prompt's K/V at once: feed the prompt's tens-to-hundreds of tokens as a batch (L18), compute their K/V in parallel, and write them all into cells. Once filled, the cache holds the whole prompt's memory, and later when decode generates word by word, each new word reads this history directly without recomputing the prompt.
A note on where the letters "K/V" come from: a database-style "key-value" analogy - K is like an index (Q matches against it to find relevant positions), V like the retrieved content (summed by match weights). Do not press the analogy too hard, but it helps you remember: caching K/V is caching "each historical position's retrievable content".
Feel the "quadratic vs linear" gap concretely: generating the 1000th token, without a cache you would recompute all prior 999 tokens' K/V, each step slower than the last, quadratic overall; with a cache, step 1000 is like step 10, both computing just one new token, linear overall. For long conversations of thousands of tokens, this gap is the line between "runnable" and "frozen".
Inside, the KV cache is a grid of cells, each storing one position's K/V. Managing them is llama_kv_cells: it records each cell's position pos and the sequence seq_id it belongs to; the cache also has a rolling write pointer head, marking where to write next.
// simplified from src/llama-kv-cells.h / src/llama-kv-cache.h class llama_kv_cells { // manages the grid of cells std::vector<llama_pos> pos; // each cell's position // each cell also records: which seq_ids it belongs to }; class llama_kv_cache : llama_memory_i { // src/llama-kv-cache.h llama_kv_cells_vec v_cells; // actual storage(multi-sequence capable) // head(): rolling write pointer };
Note llama_kv_cache derives from llama_memory_i - this base interface matters, and we return to it for the variants. Each cell carries pos because attention's rope (L16) and causal mask both need to know "which position this K/V is"; each cell carries seq_id to support multiple sequences (below).
Each decode step, after build_attn (L16) computes the new token's K/V, it writes them into the cell head points to and advances head by one; for attention, it reads historical K/V from all cells belonging to this sequence. So the KV cache is not passive storage but an active memory that grows every step and is read every step. Draw a few consecutive steps and "only one new cell" becomes obvious:
Cells and tokens are one-to-one: the i-th token in a sequence occupies some cell in the cache, storing its (one per layer, actually) K and V. So "how big the cache is" roughly equals "how many tokens' K/V it can remember" - which is the meaning of n_ctx (context length, L17): the upper bound on cells the cache can hold.
"Reading only this sequence's cells" during attention is worth clarifying: the cache may mix several sequences' cells, but the current token should see only its own sequence's K/V at positions before itself. The former is filtered by seq_id, the latter by the causal mask (L11's soft_max_ext). The two filters together ensure "sequences do not mix flavors, and each token sees only the past".
Emphasize "one per layer": a transformer has dozens of layers, each with its own attention, each caching its own set of K/V. So a token's "memory" is actually a collection of dozens of K/V (one per layer). This is why a deeper model's KV cache is especially big - layer count multiplies straight into the cache size (the "layer count" in Dig-deeper 1's product is exactly this).
The KV cache is not just "write into" - it can be edited. A set of sequence operations lets you remove, copy, keep, or shift a sequence's K/V, corresponding to the public C API llama_memory_seq_* (obtaining the memory object via llama_get_memory).
# pseudocode: sequence operations (public C API) mem = llama_get_memory(ctx) llama_memory_seq_rm (mem, seq, p0, p1) # remove the [p0,p1) span of KV llama_memory_seq_add(mem, seq, p0, p1, d) # context shift: shift pos by d
The most worthwhile thing here is context shift. When a conversation grows past n_ctx and the cache fills, what then? One way: drop the oldest span of K/V, shift the remaining tokens' pos forward as a whole (a negative seq_add), and free tail space to keep generating. This avoids "stop when full" without recomputing the whole segment - it just slides the memory window forward a bit.
seq_cp (copy a sequence) has a clever use: shared prefix. Say a long system prompt must generate several different answers at once - compute it once into sequence 0, then seq_cp it to sequences 1, 2, 3... so several sequences share the same prefix's KV without each recomputing it. A common server compute-saving trick.
Back to that rename: from llama_kv_self_* to llama_memory_seq_* is not just a name change but a conceptual abstraction upgrade. Early on there was only one "KV cache", so the API was called kv; later came recurrent, a "not-KV but still memory" thing, so the name was lifted to the more general memory. One rename records this engine's growth from "supporting only transformers" to "also accommodating other architectures".
The p0, p1 common in those sequence operations are a position range: many operations act not on a whole sequence but on the "positions p0 to p1" span - removing only a span, shifting only a span. This "operate by position interval" granularity lets the engine do many tricks - rolling back only the last few tokens (undo), shifting only a middle span. Making memory editable by interval is where the flexibility comes from.
Because each cell carries seq_id, one KV cache can hold multiple sequences at once: their cells mix in the same cache, distinguished by seq_id, each doing its own attention (seeing only its own sequence's cells). This is the memory basis for L18's multi-sequence and a server's concurrency.
| variant | idea | file |
|---|---|---|
| standard | full attention, cache every token | llama-kv-cache |
| iSWA sliding window | keep only the most recent window's K/V | llama-kv-cache-iswa |
| recurrent | fixed-size state, not growing with length | llama-memory-recurrent |
| hybrid | a mix of the above | llama-memory-hybrid |
Why so many variants? Because while standard full-attention's KV cache makes compute linear, its memory still grows linearly with length - the longer the context, the more VRAM. Sliding window (iSWA) keeps only a recent window, recurrent uses fixed state, hybrid mixes - all different trade-offs to save memory for long context. They all implement the same base interface llama_memory_i, so they can be swapped wholesale with the rest of the engine unchanged.
Emphasize multi-sequence "no mixing" once more, as it is the key to concurrency: three sequences' cells crowd into the same cache, but when each sequence does attention, the seq_id filter lets it see only its own cells, as if the cache held only it. So one physical cache is logically split into mutually-invisible portions. This "physically shared, logically isolated" is the same memory-saving philosophy as L17's context sharing weights.
The recurrent variant goes further: it corresponds to non-transformer architectures like Mamba, using a fixed-size state to summarize "all history so far", the state size not changing with context length at all. This fundamentally sidesteps the KV cache's growth-with-length problem, at the cost of the state being "compressed history" - not able to look back precisely at each token as full attention can. Folding it too into llama_memory_i is exactly this abstraction's power - even architectures whose "fundamental memory mechanism differs" can plug into the same engine.
Put the KV cache back into the whole inference chain: L18's batch feeds a new token in -> L16's build_attn computes its K/V, writes them into the KV cache, and reads history back out -> after computing, updates the cache and advances one step. The KV cache is that memory read and written every step, the state core that "links past and future" in the autoregressive loop. All the earlier components, in the end, revolve around it.
One more thing worth knowing: the KV cache's memory layout works together with optimizations like flash attention (mentioned in L11) - laying K/V tidily in memory is what lets the attention kernel read block by block and compute as it reads efficiently. So the KV cache is not just about "fitting"; how it is laid out also directly affects how fast attention computes. Storage layout and compute kernel are a mutually-accommodating pair at this layer.
Because it stores a lot: every token, every layer, every KV head needs one K and one V. Multiply these - context length x layer count x KV head count x per-head dim x 2 (K and V) - and that is the KV cache size. As context grows long, this product is considerable, often the biggest memory block aside from the model weights.
So there are two routes to save VRAM (seen in L17's cparams): one, store KV quantized (type_k/type_v from 16-bit to 8-bit or lower, halving and halving again); two, reduce n_ctx (cache fewer tokens). L15 also mentioned GQA - making KV heads far fewer than Q heads, shrinking the KV cache at the source.
Understand "KV cache size = length x layers x KV heads x ..." and you can estimate from a model's hyperparameters how much VRAM a given context length eats - the most worth-knowing account when deploying a large model.
The scenario: you have chatted with the model for a while, and the token count is about to exceed n_ctx (the cache cannot hold more). The naive move is to stop, but that is a poor experience. Context shift offers another way: drop the oldest span of conversation (remove those cells), shift the remaining part's pos forward as a whole so positions restart from small, freeing new positions at the tail.
The key is this only moves position tags, not recomputing the K/V themselves - seq_add shifts a span of tokens' pos by an amount, the cached K/V content unchanged, only their "corresponding positions" changing. With rope's relative-position nature, the model continues normally after the shift. So it is a "buy more life at small cost" technique.
Of course, dropping the oldest conversation means the model "forgets" what was said at the start - the inherent cost of sliding-window memory. Whether to shift and how much to drop is a trade-off between "endless conversation" and "remember everything". By the way, more aggressive compaction (defrag) has been simplified away in newer versions, and the defrag_thold parameter is marked deprecated.
Because "how to remember the earlier text" actually has many strategies, and the rest of the engine (graph-building, execution, sampling) should not care which is used. Abstracting their common behavior (write a new token, read history, remove/shift a sequence) into a base interface llama_memory_i, the standard KV cache, sliding window, recurrent, and hybrid all implement it.
So "switch memory strategies" becomes "switch the class implementing llama_memory_i", and whichever implementation the memory held by llama_context (L17) points to, the engine calls the same interface as usual. This is exactly why L17 said "the field is named memory not kv_cache" - it left room to hold various memory strategies.
This is another familiar decoupling: of a piece with L12's type_traits (one interface holding dozens of quantizations) and L16's build_arch_graph (a virtual function holding dozens of architectures). Folding "the varying strategy" into a unified interface and keeping "the invariant trunk" outside - a design motif running through all of llama.cpp, seen once more at the KV cache.