【infra之路】从 KV Cache 到逐层语义:理解 Decoder-Only LLM 的推理过程

本文记录了我学习 AI Infra(以 nano-vllm 为切入点)过程中,围绕 LLM 推理机制产生的几个核心问题及思考。


起因

在学习 nano-vllm 源码时,我遇到了一个看似简单但细想很深的问题:KV Cache 为什么只缓存 K 和 V,不缓存 Q? 这个问题把我拉回到了 Attention 机制本身,进而引出了对 Decoder-Only 架构逐层数据流的完整梳理。


问题一:KV Cache 为什么只存 K 和 V?

回到 Attention 公式

Attention(Qt,K,V)=softmax(Qt⋅K1,...,KtTd)⋅V1,...,Vt\text{Attention}(Q_t, K, V) = \text{softmax}\left(\frac{Q_t \cdot K_1, \\dots, K_t^T}{\sqrt{d}}\right) \cdot V_1, \\dots, V_tAttention(Qt,K,V)=softmax(d Qt⋅K1,...,KtT)⋅V1,...,Vt

关键不对称性:

矩阵 形状 含义
QtQ_tQt 1, d 只有当前 1 个 token 的 Query
K1..tK_{1..t}K1..t t, d 所有历史 token 的 Key
V1..tV_{1..t}V1..t t, d 所有历史 token 的 Value

用生成过程推演

假设已生成 "我 喜欢 吃",要生成第 4 个 token:

复制代码
生成第 4 个 token:
  Q₄ = x₄ · W_q     ← 只需要当前 1 个 Q
  Attention = softmax(Q₄ · [K₁,K₂,K₃,K₄]ᵀ / √d) · [V₁,V₂,V₃,V₄]

生成第 5 个 token:
  Q₅ = x₅ · W_q     ← Q₄ 再也不会被用到!
  Attention = softmax(Q₅ · [K₁,K₂,K₃,K₄,K₅]ᵀ / √d) · [V₁,V₂,V₃,V₄,V₅]
                              ↑ K₁~K₄ 又要用!              ↑ V₁~V₄ 又要用!

结论:

  • Q 是"一次性提问者",每步只需当前一个,用完即弃,无跨步复用需求
  • K 和 V 是"被反复查询的知识库",每一步生成都要回顾全部历史,必须缓存

一个类比

把 Attention 想象成图书馆查资料:

  • Q = 你手里的一次性提问纸条,问完就扔
  • K = 每本书封面上的索引标签,你需要翻所有书的标签
  • V = 书里的实际内容,找到后你要读所有相关书

图书馆(KV Cache)存的是书(K+V),不是你的纸条(Q)。

不缓存的代价

无 Cache(暴力重算) 有 KV Cache
每步计算量 O(t) O(1)
生成 n 个 token O(n²) O(n)
代价 无额外显存 显存占用

KV Cache 本质是用空间换时间


问题二:多层 Decoder 中,每层的输入输出到底是什么?

全局数据流

复制代码
Token IDs: [我, 喜, 欢, 吃]
     │
     ▼
 Embedding: [4, 4096]          ← 第一层的输入
     │
     ▼
 ┌─ Layer 1 ─┐  in: [4, 4096]  out: [4, 4096]
 ├─ Layer 2 ─┤  in: [4, 4096]  out: [4, 4096]
 ├─ Layer 3 ─┤  in: [4, 4096]  out: [4, 4096]
 │    ...    │
 └─ Layer N ─┘  in: [4, 4096]  out: [4, 4096]
     │
     ▼
 RMSNorm + LM Head: [4, 4096] → [4, vocab_size]
     │
     ▼
 取最后一个位置 → softmax → 下一个 token

核心事实:每一层的输入输出形状完全相同,都是 [seq_len, hidden_dim]。层与层之间变的不是形状,是语义的深度。

第一层的输入

Embedding 层的输出。每个 token 被映射为一个 4096 维向量,此时只包含该 token 自身的词义,尚未与任何其他 token 交互。

每层内部做了什么

复制代码
输入 x: [seq_len, 4096]
    │
    ├──→ RMSNorm → Self-Attention → + (残差)
    │                                 │
    │                                 ▼
    ├──→ RMSNorm → FFN (SwiGLU)  → + (残差)
                                      │
                                      ▼
                              输出: [seq_len, 4096]

两个子模块各司其职:

模块 作用 类比
Self-Attention token 之间"交流",横向融合上下文 开会讨论
FFN/MLP 每个 token 独立"思考",纵向深化表示 会后独立思考

Prefill vs Decode

Prefill(处理 prompt) Decode(逐 token 生成)
每层输入 n, 4096 1, 4096
Q n, 4096 1, 4096
K, V n, 4096,新算并存入 cache 1, 4096,新算并追加到 cache
参与计算的 K, V n, 4096 t, 4096(全部历史,从 cache 读)

问题三(我的核心感悟):逐层语义完善

把上面两个问题串起来,我得到的理解是:

每个 token 的向量从"孤立的词义"出发,每经过一层,通过 Attention 吸收上下文、通过 FFN 深化推理,维度不变但语义逐层丰富,最终变成一个"浓缩了全文信息、足以预测下一个词"的表示。

具体地:

复制代码
Embedding 后:  "吃" 只知道自己是"吃"
Layer 1 后:    "吃" 知道前面是"喜欢"
Layer 5 后:    "吃" 知道这是"主语+喜欢+吃"的结构
Layer 20 后:   "吃" 知道语境大概率要接一个食物名词
Layer N 后:    "吃" 的向量已经"准备好"去预测"苹果/米饭/火锅..."

三个让这个过程成立的关键设计:

  1. Causal Mask:每个 token 只能看到自己 + 前文,保证自回归生成的合法性
  2. 残差连接:每层不是推倒重来,而是在上一层基础上叠加一个"增量修正"
  3. 维度恒定:4096 维贯穿始终,让层与层之间可以无限堆叠

这就像画画:第一层打轮廓,后面每层加一点细节和色彩,最终成为一幅完整的画。

相关推荐
slacker-kian12 小时前
[实践]-本地大模型 + MCP + Agent 对接 SAP OData API 实现Web Chart APP
ai·llm·sap·agent·mcp·odata·chart bot
龙骑士baby13 小时前
重建 AI 认知第 6 篇:RAG——答案不在"检索 + 生成"这四个字里
ai·llm·rag
DO_Community15 小时前
Omarchy 将研发基础设施迁移至 DigitalOcean 云平台
linux·人工智能·llm·agent·omarchy
方方洛15 小时前
vllm教程-19-模型架构基础
人工智能·深度学习·llm
玉宇夕落15 小时前
Docker + Milvus,数据库永久存放记忆,让对话历史永不丢失
数据库·docker·llm
张彦峰ZYF17 小时前
Prefactor 从“看见 Agent”到“拦住 Agent”:实时评估如何重构 AI Agent 的生产可靠性
人工智能·llm·ai agent·evaluate·llm-as-a-judge·prefactor·金融agent
不好听61317 小时前
withStructuredOutput 一行封装的背后:method 三选一 + 方法总表
llm
武子康18 小时前
小智发出 abort 后,旧声音为什么还可能继续?
人工智能·llm·agent
七夜zippoe18 小时前
LLM 选型指南:Agent 场景下大模型的核心能力评估框架
ai·大模型·llm·agent·核心 能力
XLYcmy19 小时前
ReasoningBank: Scaling Agent Self-Evolving with Reasoning Memory论文阅读
论文阅读·google·llm·agent·记忆·harness·自进化