Appearance
KV Cache 与批处理 — 代码走读
src/llama-kv-cache.cpp — KV Cache 管理
抽象接口
llama_memory_i/llama_memory_context_i定义在src/llama-memory.cpp/.h,而 KV Cache 的具体实现是一个类:src/llama-kv-cache.cpp/.h。
核心数据结构
KV Cache 现在是一个类(而非自由结构体),它继承自 llama_memory_i(src/llama-kv-cache.h:~20):
cpp
class llama_kv_cache : public llama_memory_i {
private:
const llama_model & model;
const llama_hparams & hparams;
// 每层的 K/V 存储张量(src/llama-kv-cache.h:~250)
struct kv_layer {
uint32_t il; // 该层在模型中的索引
ggml_tensor * k; // 该层的 K cache
ggml_tensor * v; // 该层的 V cache
// ... 多流(stream)相关视图
};
std::vector<kv_layer> layers;
// KV 单元管理(不再是裸 vector<llama_kv_cell>;单元扩展记录现在含 token id)
std::shared_ptr<llama_kv_cells_vec> v_cells_impl;
llama_kv_cells_vec & v_cells; // src/llama-kv-cache.h:~301
// find_slot 的环形缓冲搜索起点(每个 stream 一个)
std::vector<uint32_t> v_heads; // src/llama-kv-cache.h:~296
// ... 其它配置(n_seq_max、n_stream、SWA、旋转、name_tag 等)
};注意:并不存在 llama_kv_cell 这个类型(单元集合类型是 llama_kv_cells_vec),也没有 k_l/v_l/head/size/used 这些成员。单元的扩展记录 llama_kv_cell_ext 现含 llama_token tok(多模态/embedding 输入为 LLAMA_TOKEN_NULL),并参与状态保存。
Cache 写入
KV 的拷贝发生在计算图里,而不是一个 ggml_backend_tensor_set 循环。更新流程由内存接口 llama_memory_i::init_update()(src/llama-memory.h:~98)驱动,图构建阶段通过 cpy_k / cpy_v 把当前层的 K/V 节点接到 cache 张量上(src/llama-kv-cache.h:~193-194):
cpp
// 把当前 ubatch 的 K/V 写入 cache(图构建阶段,返回写入节点)
ggml_tensor * cpy_k(ggml_context * ctx, ggml_tensor * k_cur,
ggml_tensor * k_idxs, int32_t il,
const slot_info & sinfo) const;
ggml_tensor * cpy_v(ggml_context * ctx, ggml_tensor * v_cur,
ggml_tensor * v_idxs, int32_t il,
const slot_info & sinfo) const;Cache 查找与复用
槽位查找通过成员方法完成(src/llama-kv-cache.h:~209,.cpp:~898):
cpp
// 为 ubatch 在环形 cache 中找一个可容纳它的连续/可分槽位
// cont == true 时要求槽位连续;失败返回空的 slot_info
slot_info find_slot(const llama_ubatch & ubatch, bool cont) const;配套的 token 追踪 API(本窗口新增,src/llama-kv-cache.cpp:~1836):
cpp
// 取每个 ubatch token 在其序列中前面的 n 个 token(供 n-gram 嵌入使用)
bool get_prev_tokens(const llama_ubatch & ubatch, uint32_t n, std::vector<llama_token> & res);索引缓存(MSA 的组合式实现)
架构变化(本窗口
67d5978bb):indexer 缓存不再内嵌于llama_kv_cache——get_k_idx()/cpy_k_idx()/msa_strict_slots已从基类移除。
MiniMax-M3 的 MSA 现由顶层组合类 llama_kv_cache_msa : llama_memory_i(src/llama-kv-cache-msa.h:14)实现:内部持有两个 llama_kv_cache 实例(get_base() 为 K/V,get_idx() 为 indexer 张量),二者槽位保持同步;上下文侧是 llama_kv_cache_msa_context(暴露 get_base() / get_idx() / get_n_pos())。图侧输入类 llm_graph_input_attn_kv_msa 携带 indexer key cache 的目标下标(self_k_idxs_idx,I64 [n_batch]),模型图经 get_cells() 在位置空间做 MSA block 选择。
DeepSeek 侧的 DSA 仍是 llama_kv_cache_dsa 附加 lightning indexer;qwen4exp 的 QSA indexer 则由 llama_memory_hybrid_idx 以镜像 cache 实现(状态恢复时直接写回与父 cache 相同的 cell,不重跑 find_slot)。
src/llama-batch.cpp — 批处理编码
cpp
// 只接收 token 指针与数量,所有辅助指针一律置空(src/llama-batch.cpp:~931)
struct llama_batch llama_batch_get_one(
llama_token * tokens,
int32_t n_tokens) {
return {
/*n_tokens =*/ n_tokens,
/*tokens =*/ tokens,
/*embd =*/ nullptr,
/*pos =*/ nullptr,
/*n_seq_id =*/ nullptr,
/*seq_id =*/ nullptr,
/*logits =*/ nullptr,
};
}注意:llama_batch_get_one 不会填充 pos / n_seq_id / seq_id —— 这些由上层在解码前另行设置。如需手动逐 token 填充,应使用 llama_batch_init(...) 并为每个位置赋值(src/llama-batch.cpp:~945)。
src/llama-context.cpp — 推理上下文
cpp
// 核心推理入口(src/llama-context.cpp:~4247)
int32_t llama_decode(struct llama_context * ctx, struct llama_batch batch) {
const int ret = ctx->decode(batch);
// ... 错误处理 ...
return ret;
}下面这段是 ctx->decode(batch) 内部流程的示意性伪代码(仅说明阶段,并非逐行源码):
cpp
// 示意性伪代码(非真实源码):
// 1. 构建计算图
// auto * graph = llm_build_graph(*ctx, batch);
// 2. 分配后端资源
// ggml_backend_alloc_graph(backend, graph);
// 3. 执行计算
// ggml_backend_graph_compute(backend, graph);
// 4. 输出 logits 可供采样使用关键函数索引
| 函数 / 方法 | 文件 | 说明 |
|---|---|---|
llama_decode | llama-context.cpp | 执行一次 batch decode(转发到 ctx->decode) |
llama_kv_cache::init_update | llama-kv-cache.cpp | 通过 llama_memory_i 接口触发 cache 更新 |
llama_kv_cache::cpy_k / cpy_v | llama-kv-cache.cpp | 在计算图中把当前层 K/V 写入 cache |
llama_kv_cache::find_slot | llama-kv-cache.cpp:898 | 为 ubatch 查找可用槽位 |
llama_kv_cache::get_prev_tokens | llama-kv-cache.cpp:1836 | 取每个 token 前面的 n 个 token(新) |
llama_kv_cache_msa | llama-kv-cache-msa.cpp | MSA 组合体:K/V + indexer 两个 cache 实例(新) |
llama_kv_cache::clear | llama-kv-cache.cpp | 清空 cache(对应 C-API llama_memory_clear) |
llama_kv_cache::seq_rm | llama-kv-cache.cpp | 删除指定序列的 cache(对应 C-API llama_memory_seq_rm) |
llama_batch_get_one | llama-batch.cpp | 构建单序列 batch(辅助指针全为 nullptr) |
llama_batch_init | llama-batch.cpp | 分配可逐 token 填充的 batch |