llama.cpp:Decode、KV Memory 与 Graph 复用

llama.cpp:Decode、KV Memory 与 Graph 复用

「本系列第 7/17 章」。上一章把 llama_decode 当成黑盒:送进 batch,得到 0 / 1 / -2。本章沿同一条调用往下走,看一次 decode 如何切 batch、如何申请历史槽位、如何决定「重建图还是复用图」。读完应能把「KV 满了」和「这一步突然变慢」对应到具体阶段。

1. 一次 decode 的主轴

llama_decode 进入 llama_context::decode() 之后,主路径可以收成:

1
2
3
4
5
6
balloc.init
→ sched_reserve / memory_update
→ memory.init_batch
→ loop process_ubatch
apply / can_reuse / build_graph / graph_compute
→ output_reorder

balloc.init 先给用户提交的 llama_batch 做消毒:补全缺失的位置、序列号和 logits 标志。位置通常从该序列 seq_pos_max + 1 递增,logits 默认只打开最后一个 token。随后 memory.init_batch 在历史存储里为这批 token 找槽位;找不到就 memory_update(true) 做 defrag / shift,再失败才返回 1。槽位就绪后进入 process_ubatchapply 把分配写入 cells,再判断旧图能否复用;不能复用则 model.build_graph() + sched_alloc_graph(),最后 graph_compute。计算失败会 seq_rm 回滚刚写入的 KV,避免 cache 与图各走各的。

build_graph 本身也分层:build_arch_graphsrc/models/*.cpp 的三方法之一)→ build_poolingbuild_samplingbuild_dense_outset_outputs。新架构只要实现 load_arch_hparamsload_arch_tensorsbuild_graph,并在 llama-arch.h / llama-model.cpp 注册。

2. Batch 为什么要切成 ubatch

公开结构是 llama_batchtoken[]embd[] 二选一,再加上 posseq_idlogits。它不会直接变成计算图输入,而是经过 llama_batch_allocr,拆成更小的 llama_ubatchn_batch 是逻辑上限,n_ubatch 是单次 graph_compute 上限,且必须 n_ubatch <= n_batch。一次 prefill 可能有数百个 token,必须切开,否则峰值激活会先于权重把显存撑爆。

三种拆法对应三种存储形态:

  • split_simple:按 token 顺序切,单 stream KV 最常见
  • split_equal:各序列对齐取 token,多 stream 时用来保证 attention 对齐
  • split_seq:一次只推进一个序列集合,循环模型需要这种节奏

标准 KV 在 n_stream==1 时用 split_simple,多 stream 用 split_equal。非因果 attention 要求整个 batch 落在一个 ubatch 里。后端采样还限制「每条序列最多一个 output token」。LLAMA_BATCH_DEBUG=1 可以打印拆分结果。

3. Memory 工厂:一种接口,五类实现

历史状态并不总是「标准 KV cache」。llama_memory_i 统一 init_batchseq_rm / seq_cpstate_writellama_model::create_memory() 按架构分流:

  • BERT / Embedding 等 encode-only:返回 nullptrllama_decode 回退到 encode,输出向量
  • DeepSeek32:llama_kv_cache_dsa(MLA key + Lightning Indexer)
  • Mamba / RWKV:llama_memory_recurrent,定长循环状态,batch 用 split_seq
  • Jamba / Qwen3.5:llama_memory_hybrid,注意力层 KV,循环层 recurrent
  • 其余:llama_kv_cache;带滑动窗口则 llama_kv_cache_iswa(全上下文 + SWA 双实例)

标准 KV 的 prepare 是事务性的:先 find_slot(dry_run=true) 环形搜索连续空槽,全成功才 commit。server 的 slot fork 底层就是 llama_kv_cache_seq_cp。Qwen3.5 的 MTP context 会强制走 plain KV,不要按 hybrid 去猜。

4. 图复用:decode 快,快在少建图

逐步生成时,每步通常只有输入数据变了,拓扑没变。llm_graph_params.allow_reuse() 比较 ubatch 形状、序列集合、sampler 指针;llm_graph_result.can_reuse() 还要所有 input 节点同意。匹配就跳过建图和 buffer 分配,只 set_inputsgraph_computen_reused 可以从 llama_perf_context() 看到。

复用会失效的常见原因:LLAMA_GRAPH_REUSE_DISABLE=1、从长 prefill 切到单 token、并行序列数变化、KV defrag / shift、LoRA 或 Control Vector 变更。Pipeline parallel 复用前必须 ggml_backend_sched_synchronize(),否则 GPU 还在读上一份 input。这是正常路径,不是故障。

把这一章压成一句话:llama_decode 先让 batch 合法,再让 Memory 有位子,然后尽量复用旧图去计算。下一章《llama.cpp:llama-server 与生产部署》会把同一套 decode 接到 HTTP:队列、slot 与连续批处理,就是在多个会话之上调度这些 ubatch。

文章互动

阅读 --

留言

0 条留言

正在加载留言…