Skip to content

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_isrc/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_decodellama-context.cpp执行一次 batch decode(转发到 ctx->decode
llama_kv_cache::init_updatellama-kv-cache.cpp通过 llama_memory_i 接口触发 cache 更新
llama_kv_cache::cpy_k / cpy_vllama-kv-cache.cpp在计算图中把当前层 K/V 写入 cache
llama_kv_cache::find_slotllama-kv-cache.cpp:898为 ubatch 查找可用槽位
llama_kv_cache::get_prev_tokensllama-kv-cache.cpp:1836取每个 token 前面的 n 个 token(新)
llama_kv_cache_msallama-kv-cache-msa.cppMSA 组合体:K/V + indexer 两个 cache 实例(新)
llama_kv_cache::clearllama-kv-cache.cpp清空 cache(对应 C-API llama_memory_clear
llama_kv_cache::seq_rmllama-kv-cache.cpp删除指定序列的 cache(对应 C-API llama_memory_seq_rm
llama_batch_get_onellama-batch.cpp构建单序列 batch(辅助指针全为 nullptr
llama_batch_initllama-batch.cpp分配可逐 token 填充的 batch