← 回到知识图谱
概念持续完善

LLM 基础

本文总结从一次前向传播讲起:Attention 怎么算、KV Cache 占多少显存、温度和 top-p 怎么调、模型会在哪里编,每个公式都配了手算例子。

创建 2026-10-31更新 2026-11-02阅读约 96 分钟
Agent面试LLM

主线第 1 站 · 上一站:起点 · 下一站:Prompt / Context · 全程:主线

本站在主线里回答什么:员工发来一句「我上周出差打车超标了,还能报吗?能的话帮我提报销工单。」在检索、调接口之前,我们先要弄清楚一件事:只靠模型本身能走多远。模型在预训练里学到的是“报销一般要发票、超标一般要写说明”这类常识。它不知道这家公司市内打车的标准是 80 元,不知道这位上海 P6 员工上周有一张 118.5 元的发票 INV-102,不知道“上周”是哪几天(除非把今天的日期告诉它),更不可能自己去建工单。直接让它回答,它很可能顺口说一句“可以报,附上情况说明即可”。这句话听着像真的,背后却没有任何证据。这种回答就是幻觉。

本站没有上游输入,接收的就是这句原始请求。交给下一站的是一份能力边界清单:哪些信息必须由上下文注入(日期、身份、制度条款、发票记录),窗口和 token 预算有多大,关键证据该放在什么位置,解码参数怎么设才能让后面工具参数的 JSON 稳定。下一站 Prompt / Context 据此决定这一轮往上下文里装什么。

这一站考什么

面试里的 LLM 基础题,很少要你复述论文。面试官想确认三件事:

  1. 一次前向传播里,每个张量是什么形状、在做什么;
  2. 结构上的选择(头数、位置编码、MoE)怎样影响上下文长度、显存、延迟和幻觉;
  3. 你能把这些影响落回 Agent 的工程决策,比如“为什么工具参数要用低温度”。

下面的公式都配了手算例子。例子里的数字大多是为了好算而构造的,标了“示意”,不是哪个模型的实测值。

1. 一次前向传播到底发生了什么

文本
 → tokenizer → token ids
 → embedding
 → N × [RMSNorm → Self-Attention → 残差
        → RMSNorm → FFN → 残差]
 → final norm
 → lm_head → logits
 → 采样/解码 → 下一个 token

拿员工那句话走一遍(数字示意):假设它被切成 20 个 token,模型隐藏维度 d = 4096。embedding 之后得到一个 20×4096 的矩阵,每行代表一个 token。之后每一层的输入输出都还是 20×4096,层与层之间只改写数值、不改形状。最后 lm_head 把最后一行的 4096 维映射成词表大小(比如 32000 维)的 logits,也就是“下一个 token 是词表里每个词”的打分。

1.1 Self-Attention 在算什么

每个 token 的隐状态会投影出三组向量。可以用图书馆找书来类比:

  • Query(Q):我在找什么,相当于你写在检索单上的需求;
  • Key(K):我能提供什么线索,相当于每本书书脊上的标签;
  • Value(V):我实际携带的信息,相当于书里的内容。

每个 token 拿自己的 Q 去和所有 token 的 K 比,比出来的分数经过 softmax 变成一组加起来等于 1 的权重,再按这组权重把各个 token 的 V 混合起来,得到这个 token 的新表示。单头注意力写成:

Attention⁡(Q,K,V)=softmax⁡(QK⊤dk+M)V\operatorname{Attention}(Q,K,V)=\operatorname{softmax}\left(\frac{QK^\top}{\sqrt{d_k}}+M\right)V
符号 含义 形状(单头,n 个 token)
Q,KQ, K query 矩阵、key 矩阵,每行是一个 token 的向量 n×dkn\times d_k
VV value 矩阵 n×dvn\times d_v
K⊤K^\top K 的转置,读作“K 转置”:行和列互换 dk×nd_k\times n
QK⊤QK^\top 矩阵乘法,第 i 行第 j 列是第 i 个 query 和第 j 个 key 的点积 n×nn\times n
dkd_k 每个 query / key 向量的长度(维数) 一个数
MM 掩码:允许看的位置填 0,不允许看的填 −∞-\infty(负无穷) n×nn\times n
softmax 对一行数逐个取指数再除以总和,结果全为正、加起来等于 1 按行做

两个向量的点积是对应位置相乘再相加:q⋅k=∑i=1dkqikiq\cdot k=\sum_{i=1}^{d_k}q_ik_i。这里 ∑i=1dk\sum_{i=1}^{d_k} 读作“对 i 从 1 到 d_k 求和”。点积越大,说明两个向量方向越一致,query 和 key 越匹配。softmax 对一行分数 z1,…,znz_1,\dots,z_n 的计算是 ezj/∑lezle^{z_j}/\sum_{l} e^{z_l},其中 e≈2.718e\approx 2.718,eze^{z} 也写成 exp⁡(z)\exp(z)。

手算一遍(示意数字)。三个 token “打车”“超标”“能报”,dk=4d_k=4,dv=2d_v=2。我们只算最后一个 token“能报”的输出,它的 query 是 q3=[1,1,1,1]q_3=[1,1,1,1]:

token key value
打车 [2, 1, 1, 0] [10, 0]
超标 [1, 0, 1, 0] [0, 10]
能报 [1, −1, 0, 0] [4, 4]
  1. 点积:q3⋅k1=2+1+1+0=4q_3\cdot k_1=2+1+1+0=4,q3⋅k2=1+0+1+0=2q_3\cdot k_2=1+0+1+0=2,q3⋅k3=1−1+0+0=0q_3\cdot k_3=1-1+0+0=0。
  2. 缩放:dk=4=2\sqrt{d_k}=\sqrt 4=2,三个分数变成 2、1、0。
  3. 掩码:“能报”是最后一个 token,前面都能看,全部加 0,分数不变。
  4. softmax:e2≈7.389e^2\approx 7.389,e1≈2.718e^1\approx 2.718,e0=1e^0=1,总和约 11.107。权重依次是 7.389/11.107 ≈ 0.665、2.718/11.107 ≈ 0.245、1/11.107 ≈ 0.090,加起来是 1。
  5. 加权求和:0.665×[10,0]+0.245×[0,10]+0.090×[4,4]=[6.65+0.36, 2.45+0.36]=[7.01, 2.81]0.665\times[10,0]+0.245\times[0,10]+0.090\times[4,4]=[6.65+0.36,\ 2.45+0.36]=[7.01,\ 2.81]。

“能报”这个位置的新表示,大约三分之二的信息来自“打车”,四分之一来自“超标”。

第 2 步和第 3 步各有用处。除以 dk\sqrt{d_k} 是为了防止维度大时分数过大、softmax 被推到只剩一个赢家(见下文「为什么要除以 √d_k?」)。M 在 Decoder 里是因果掩码,保证第 t 个 token 看不到 t 之后的内容,下文「扩展」一节有一个 3×3 的例子。

为什么要过一道 softmax,而不是直接拿点积当权重?点积可正可负,也不归一,直接加权时输出的大小会随序列长度和分数尺度乱跳。softmax 保证权重非负、和为 1,输出永远是各个 V 的加权平均。它的副作用是权重总和必须等于 1:哪怕一行里没有一个 token 真正相关,权重也得分出去,后文的 attention sink 跟这一点有关。

复杂度是 O(n2d)O(n^2 d),读作“n 的平方乘 d 的量级”。原因是 n 个 query 各要和 n 个 key 算点积,每个点积要做 d 次乘法。1000 个 token 时每个头每层要算 100 万个分数,2000 个 token 时变成 400 万个,长度翻倍、这部分计算变成四倍。长上下文又贵又慢,这是原因之一。

1.2 多头、MQA、GQA

多头注意力把 Q/K/V 切成多组,每组各自做一遍上面的计算,学不同的关系模式,最后把各组结果拼起来再投影一次。以 7B 级别的常见配置为例,d = 4096 切成 32 个头,每头 128 维。头数不是越多越好:总维度固定时,头越多,每个头分到的维度越小,单头能表达的东西就越少。

推理时,随上下文长度和并发数增长的一大块显存是 KV Cache。生成第 t 个 token 时,前面 t−1 个 token 的 K 和 V 不会变,存下来就不用每步重算。除了它,显存里还有模型权重、激活和运行时开销。KV Cache 的大小可以这样估:

MKV≈2×L×HKV×dh×T×B×sM_{KV}\approx 2\times L\times H_{KV}\times d_h\times T\times B\times s
符号 含义 7B 示例取值
2 K 和 V 各存一份 2
LL 层数,每层都有自己的 K/V 32
HKVH_{KV} KV 头数(MHA 等于 query 头数,GQA/MQA 更少) 32
dhd_h 每个头的维度 128
TT 缓存了多少个 token(prompt 加已生成部分) 8192
BB 同时服务的序列数(batch / 并发) 1
ss 每个数占几个字节,FP16/BF16 是 2,FP8/INT8 是 1 2

分步代入。先算一个 token 在一层里占多少:2 × 32 × 128 × 2 = 16384 字节,也就是 16 KiB。乘以 32 层,一个 token 占 512 KiB,即 0.5 MiB。再乘以 8192 个 token,得到 4096 MiB = 4 GiB(4,294,967,296 字节,约 4.29 GB)。如果同时服务 4 条这样的请求,就是 16 GiB。

记住“这个配置下每多一个 token 就多 0.5 MiB”,很多显存问题就能心算。这个式子只估标准、未压缩的 K/V。用了 MLA、KV 量化或滑动窗口时要按实际结构重算,而且总显存还要加上权重和框架预留。

几种注意力的区别都落在 HKVH_{KV} 上。以 32 个 query 头为例:

  • MHA:32 个头各有一份 K/V,HKV=32H_{KV}=32,效果稳,KV Cache 最大;
  • GQA:比如分 8 组,每 4 个 query 头共用一份 K/V,HKV=8H_{KV}=8,缓存降到 1/4,是现在的主流折中;
  • MQA:32 个头共用 1 份 K/V,HKV=1H_{KV}=1,缓存只有 1/32,但效果可能掉;
  • MLA:把 K/V 压成一个低秩向量再缓存,DeepSeek-V2/V3 在用,缓存还能更小。

放回上面的数字:7B 模型 FP16 权重约 70 亿 × 2 字节 ≈ 14 GB,加上单条 8k 序列的 4.29 GB,已经约 18.3 GB,24 GB 的显卡只剩不到 6 GB 给激活和其他请求。换成 8 个 KV 头的 GQA,KV Cache 降到 1 GiB。长上下文和高并发会同时吃显存,原因就在这里。

1.3 位置编码

回头看 1.1 的例子:如果把前两个 token 换成“超标、打车、能报”,三个点积还是 4、2、0,只是换了位置,“能报”得到的输出仍是 [7.01, 2.81]。Attention 只比较向量之间的相似度,不管 token 的先后(因果掩码能透露一点先后,但远远不够),所以要另外注入位置信息:

  • 正弦式位置编码:早期 Transformer 的做法,把一组固定的正弦、余弦值加到输入上;
  • RoPE:按位置把 Q/K 旋转一个角度,点积里只剩两个位置的角度差,于是自然表达相对位置,主流 LLM 基本都用它。它在训练长度之外的原生外推能力有限,长窗口通常要配合下面的缩放方法;
  • ALiBi:直接在注意力分数上减去一个和距离成正比的数。比如某个头的斜率是 0.5(示意),相隔 4 个 token 的分数就减 2,越远扣得越多,外推做起来简单;
  • 长度外推需要额外处理:超出训练长度后注意力分布会漂移,常见做法是继续预训练、调整 RoPE base、YaRN 等。RoPE 的旋转角度怎么算、外推为什么会坏,见下文「位置编码有哪些」一题。

2. 训练目标与解码

Decoder-only 模型的训练目标是“给定前文,预测下一个 token”。翻译可以写成“把下面这句译成英文:……”,问答可以写成“问:……答:”,写代码可以写成“补全这个函数”,最后都变成续写,所以一个模型能做很多任务。

模型猜得好不好,用交叉熵损失衡量:

L=−1N∑t=1Nlog⁡P(xt∣x<t)\mathcal{L}=-\frac{1}{N}\sum_{t=1}^{N}\log P(x_t\mid x_{<t})
  • xtx_t 是第 t 个位置上真实出现的 token,x<tx_{<t} 是它前面的全部 token。
  • P(xt∣x<t)P(x_t\mid x_{<t}) 是条件概率,读作“在已知 x<tx_{<t} 的条件下,xtx_t 出现的概率”,这里就是模型给正确答案打的概率。
  • log⁡\log 是自然对数 ln。概率在 0 到 1 之间,取对数后是负数,前面加负号让损失变成正数:概率越接近 1,损失越接近 0;概率越小,损失越大。
  • NN 是参与计算的 token 数,1N∑\frac{1}{N}\sum 就是取平均。

困惑度(perplexity)是把这个平均损失还原成“相当于在几个选项里瞎猜”:

PPL⁡=exp⁡(L)\operatorname{PPL}=\exp(\mathcal{L})

手算:一个句子有 3 个位置,模型给正确 token 的概率分别是 0.5、0.25、0.125。对应的 −log⁡P-\log P 是 0.693、1.386、2.079,平均为 1.386,所以 L≈1.386\mathcal{L}\approx 1.386,PPL⁡=e1.386≈4.0\operatorname{PPL}=e^{1.386}\approx 4.0。可以理解为:平均每一步,模型像是在 4 个同样可能的候选里挑。如果每一步都给正确答案打满分 1,损失是 0,困惑度是 1。

为什么用对数而不是直接用概率平均?整句的概率是每一步概率连乘,取对数后乘法变加法,既好算,又不会因为连乘几百个小数而下溢成 0。但要记住两个边界:损失低只说明“像训练语料”,不说明事实正确;不同 tokenizer 切出来的 token 不一样,两个模型的困惑度不能直接比。

解码策略(具体数值例子见下文「解码策略有哪些」):

  • 贪心:每步取概率最大的 token,稳定但容易重复;
  • Beam Search:保留多条候选,适合翻译等有确定目标的任务;
  • Temperature:调概率分布的平滑或尖锐程度;
  • Top-k / Top-p:截掉长尾,控制多样性;
  • 结构化输出:JSON Schema、Function Calling、解析失败重试。

Agent 场景很少用 Beam,更看重格式稳定和采样参数可复现。温度太高,工具参数和 JSON 会抖,比如金额一次输出 118.5、一次输出 "118.50元";温度太低,规划容易原地打转,反复调用同一个工具。

3. MoE 与 Scaling

MoE 把 FFN 拆成多个专家,每个 token 只激活其中 top-k 个。比如 Mixtral 8x7B 每层有 8 个专家,每个 token 只走 2 个:

  • 总参数可以很大,但单 token 的计算量只跟被激活的参数有关;
  • Router 决定 token 去哪些专家,容易出现几个专家忙死、其他专家闲着的负载不均;
  • 所以需要 capacity factor、负载均衡损失、专家并行;
  • 推理时显存仍要放下全部专家。“参数多”不等于“单 token 一定更贵”,但显存门槛更高。

Scaling Law 说的是:损失随参数、数据、算力按幂律下降,规模每翻一倍,损失降一小截,而且降幅越来越小。涌现说的是:某些能力在小模型上接近随机,过了某个规模后明显出现。面试里别把 Scaling Law 说成“参数越大越好”,数据质量和后训练同样关键。两者的公式和算例见下文「Scaling Law 讲了什么」。

4. 这些结构对 Agent 的影响

  • 上下文长度:能塞更多资料,但 attention 成本、KV Cache 和 Lost in the Middle 都跟着变重。按 1.2 的配置,8k 上下文单条就是 4 GiB 的 KV Cache;
  • MoE 和量化:降低单 token 的计算成本,但显存门槛和部署复杂度上升;
  • 位置外推:决定长文档、长对话能不能稳住;
  • 解码参数:决定工具调用和 JSON 输出稳不稳,create_reimbursement_ticket 的金额字段多一个字就会建错单;
  • 幻觉:模型学的是生成看起来合理的文本,并不保证内容属实,所以 Agent 要用 RAG、工具校验和拒答兜底。

5. 60 秒回答模板

现在的 LLM 基本是 Decoder-only 的 Transformer:token 先查 embedding,再经过几十层 self-attention 和 FFN,最后输出下一个 token 的概率。Attention 用 Q 和 K 的点积算相关性,softmax 成权重后对 V 加权平均;多头让每个位置同时用几种方式汇总上下文,causal mask 保证不看未来,RoPE 用旋转角度表达相对位置。推理时历史 token 的 K/V 会缓存,7B 级模型 8k 上下文单条就要约 4 GiB,GQA 让多个 query 头共用 K/V 来压这块显存。长上下文的主要代价是 attention 的 O(n²) 和 KV Cache;MoE 用稀疏激活省计算,但显存门槛更高。这些结构一起划定了 Agent 在上下文、延迟和成本上的边界。

6. 常见追问(带答法)

为什么现在的 LLM 基本都是 Decoder-only?

简答:Encoder 并不差。“预测下一个 token”这个目标最简单、最能吃下数据和算力,一个模型就能把理解、生成、指令跟随统一成续写,推理时还天然适配 KV Cache。

细说:

  • 训练信号密:因果语言建模里每个位置都产生一次预测损失。一段 100 个 token 的文本,Decoder-only 能得到 99 个预测目标;BERT 式掩码语言模型只对被遮住的那部分 token(原论文约 15%)算损失,同一段文本只有大约 15 个。
  • 任务统一:问答、翻译、写代码、调用工具都能写成“给定前文,往下续写”,Prompt 和 few-shot 示例就是前文,in-context learning 顺势出现。
  • 推理友好:causal mask 下,历史 token 的 K/V 不受后续 token 影响,可以缓存复用;所有请求共享的系统提示还能做前缀缓存。双向注意力每来一个新 token,所有历史表示都会变,没法这样复用。
  • 路径依赖:GPT 系列验证了扩展规律之后,训练基础设施、推理框架、对齐方法都围绕 Decoder-only 优化,生态越滚越大。
架构 代表 现在主要用在
Encoder-only BERT 系 分类、向量表示、重排(RAG 里的 embedding / reranker 常见)
Encoder-Decoder T5、Whisper 翻译、摘要、语音识别等输入输出明确分离的任务
Decoder-only GPT、LLaMA、Qwen、DeepSeek 通用对话、推理、Agent

容易踩的坑:别说“Encoder-Decoder 被淘汰了”。输入很长、输出很短、又需要充分双向理解的任务里,它仍有效率优势,Whisper 做语音识别就是例子。也别把原因只归结为“效果好”,要分开讲训练目标、推理缓存、工程生态三层。网上还流传“因果注意力矩阵满秩、双向注意力容易低秩”的解释,可以当补充视角,但不是公认结论。

可能的追问:

  • Decoder-only 能做理解类任务吗?能,把分类写成“生成标签”即可,比如让模型在“差旅 / 采购 / 其他”里输出一个词。但做向量检索时,很多团队仍用专门训练的 embedding 模型,因为它们针对相似度目标做过对比学习。
  • Prefix LM 是什么?前缀部分双向可见、后续部分因果生成,是两种架构的折中,UniLM、早期 GLM 都用过类似思路。

放到报销案例里:报销助手回答“打车超标能不能报”,做的就是一次续写。续写得通顺,不代表那条制度存在。

为什么要除以 √d_k?

简答:为了让 softmax 的输入保持在合理尺度。不缩放时,点积的方差随 dkd_k 线性增大,softmax 会接近 one-hot(一个位置拿走几乎全部权重),梯度变得极小,训练不稳。

细说:先看点积为什么会“越加越大”。假设 q、k 的每个分量相互独立、均值 0、方差 1(初始化时大致如此),那么

q⋅k=∑i=1dkqiki,Var⁡(q⋅k)=dkq\cdot k=\sum_{i=1}^{d_k} q_i k_i,\qquad \operatorname{Var}(q\cdot k)=d_k
  • Var⁡(X)\operatorname{Var}(X) 是方差,衡量一个随机量围绕平均值晃得有多厉害,定义是 E[(X−EX)2]\mathbb{E}[(X-\mathbb{E}X)^2]。E[⋅]\mathbb{E}[\cdot] 读作“期望”,就是按概率加权的平均值。
  • 每一项 qikiq_ik_i 的均值是 0,方差是 E[qi2] E[ki2]=1×1=1\mathbb{E}[q_i^2]\,\mathbb{E}[k_i^2]=1\times 1=1。
  • 独立的量相加,方差也相加。dkd_k 项加起来,方差就是 dkd_k,标准差(方差开根号)是 dk\sqrt{d_k}。

最小的手算:dk=2d_k=2,每个分量等概率取 +1 或 −1。于是每个乘积 qikiq_ik_i 也是等概率的 +1 或 −1,两项之和只有四种等可能结果:2、0、0、−2。平均值是 0,方差是 (4 + 0 + 0 + 4) / 4 = 2,正好等于 dkd_k。换到 dk=128d_k=128,点积的标准差约是 128≈11.3\sqrt{128}\approx 11.3,分数随手就能差出十几。除以 dk\sqrt{d_k} 后,方差回到 1。

方差大为什么有害?softmax 对输入的差值是指数敏感的。最大的那个分数比其他分数大很多时,输出接近 one-hot,这时 softmax 的雅可比矩阵(输出对输入的偏导数表)

∂pi∂zj=pi(δij−pj)\frac{\partial p_i}{\partial z_j}=p_i(\delta_{ij}-p_j)

几乎全是 0,梯度传不回 Q/K 的投影矩阵。

  • zjz_j 是 softmax 的第 j 个输入分数,pip_i 是第 i 个输出概率。
  • ∂pi∂zj\frac{\partial p_i}{\partial z_j} 是偏导数,读作“zjz_j 变动一点点时,pip_i 跟着变多少”。梯度就是由这些偏导数组成的,训练时参数按梯度调整,梯度接近 0 就等于学不动。
  • δij\delta_{ij} 是克罗内克记号:i=ji=j 时为 1,否则为 0。所以对角线上是 pi(1−pi)p_i(1-p_i),非对角线上是 −pipj-p_ip_j。

代两组数。两个候选、softmax 输出 p=(0.73, 0.27)p=(0.73,\ 0.27) 时,∂p1/∂z1=0.73×0.27≈0.197\partial p_1/\partial z_1=0.73\times 0.27\approx 0.197,∂p1/∂z2≈−0.197\partial p_1/\partial z_2\approx -0.197,梯度很健康。输出变成 p=(0.99999, 0.00001)p=(0.99999,\ 0.00001) 时,∂p1/∂z1≈0.99999×0.00001≈0.00001\partial p_1/\partial z_1\approx 0.99999\times 0.00001\approx 0.00001,小了一万多倍。pip_i 接近 0 或 1 时乘积都接近 0,这种情况叫饱和。

容易踩的坑:

  • 缩放不是为了防数值溢出。溢出靠 softmax 实现里先减去最大值的技巧解决,缩放解决的是饱和和梯度。
  • dk\sqrt{d_k} 建立在“分量独立同分布”的初始化假设上,相当于一个固定温度。也有实现把缩放折进参数初始化,或先对 Q/K 做归一化(QK-Norm)再控制尺度。
  • 加性注意力对尺度没那么敏感,但点积注意力可以直接用高度优化的矩阵乘法,这是原论文选点积再加缩放的原因。

可能的追问:

  • 为什么除以 dk\sqrt{d_k} 而不是 dkd_k?目标是让标准差回到 1,而标准差是 dk\sqrt{d_k}。除以 dk=128d_k=128 会把分数再压小 11 倍多,分数之间几乎没有差别,注意力退化成近似平均。

数值直觉见下文「逐题作答」里的同名题。

MQA/GQA 省的是哪部分显存?

简答:省的是推理解码阶段每层要缓存、每步要读取的 K/V,权重几乎没省。省下的显存和带宽可以换成更长的上下文或更大的并发。

细说:

  • 解码第 t 步:新 token 只算自己的 Q/K/V,然后和前 t−1 个 token 的 K/V 做注意力。这些历史 K/V 存在 KV Cache 里,大小和 KV 头数成正比。
  • MHA 的 KV 头数等于 query 头数 H;GQA 把 H 个 query 头分成 G 组,每组共用一份 K/V,KV 头数为 G;MQA 只有 1 份。
  • 教学算例:32 个 query 头,GQA 取 8 个 KV 头,KV Cache 变为 MHA 的 8/32 = 1/4。按 1.2 节的 7B 配置,每个 token 从 0.5 MiB 降到 128 KiB,8k 上下文从 4 GiB 降到 1 GiB。
  • 更深一层:解码阶段通常受访存限制,每生成一个 token,都要把权重和全部 KV Cache 从显存读一遍。还是这个配置(示意),单条 8k 请求每步要读约 14 GB 权重加 4.29 GB KV;batch 为 8 时,KV 部分涨到约 34 GB,比权重还大。换成 8 个 KV 头后这部分只有约 8.6 GB。KV 头少了,每步读的字节就少,所以 GQA 不仅省显存,也会加快长上下文、大 batch 下的解码。
  • Prefill 阶段受计算限制。GQA 只少算了 K/V 投影,query 侧的注意力计算量基本不变,所以对首 token 延迟帮助有限。

容易踩的坑:

  • 权重只省了 WKW_K、WVW_V 的一部分。按 d = 4096、32 层算,这两个矩阵合计从约 10.7 亿参数降到约 2.7 亿,少了约 8 亿,占 7B 的一成多;KV Cache 却直接降到四分之一。所以别说“GQA 让模型变小”,它的收益主要在缓存。
  • MQA 压得最狠,但质量和训练稳定性风险更大;GQA 是多数开源模型的默认折中。GQA 论文还给出从已有 MHA 检查点“uptrain”的方法:同组的 K/V 头取均值合并,再少量继续训练(Ainslie et al., 2023)。
  • 张量并行时,KV 头数最好能被卡数整除。8 个 KV 头放 8 张卡正好每卡一个;放 16 张卡就得在卡间复制 KV 头。

可能的追问:

  • MLA 和 GQA 有什么不同?GQA 减少头数;MLA 把每个 token 的 K/V 压成一个低秩潜向量来缓存,用的时候再上投影(且上投影矩阵可吸收进其他投影),见下文「逐题作答」。

长上下文为什么还会 Lost in the Middle?

简答:窗口装得下,不代表用得好。实验显示,关键信息放在上下文中段时,模型利用它的准确率明显低于放在开头或结尾,画出来是一条 U 形曲线(Liu et al., Lost in the Middle)。

细说:通常的解释是几种因素叠加:

  1. 训练数据的位置偏置:文档里重要信息常在开头(标题、摘要)和结尾(结论、紧挨着问题的地方),模型学到了这种偏好。
  2. 注意力的结构偏置:因果注意力里,开头的 token 会被后面所有位置看到,容易形成“attention sink”吸走注意力;末尾的 token 离生成位置近,占了近因的便宜。RoPE 的远程衰减也让远处内容更难被精确关注。
  3. 有效长度小于标称长度:训练时长样本少,外推区间的注意力分布会漂移。“大海捞针”能找到藏在长文里的单个句子,不代表多跳推理、聚合统计也能做好,RULER 一类基准专门测这种差距。
  4. 噪声稀释:塞进来的无关片段越多,相关片段分到的注意力越少。回想 1.1 的 softmax,权重总和固定是 1,候选从 3 个变成 300 个,每个片段能分到的份额自然被摊薄。

工程应对:

  • 关键指令和证据放首尾,在末尾再复述一次问题;
  • 检索结果先重排、去重、截断,把最强的证据放在两端;
  • 能检索就不硬塞,能摘要就不放全文;
  • 用自己的数据做位置敏感性测试。比如准备 20 段材料,只有一段含答案,把它依次放在第 1、5、10、15、20 位,每个位置跑同一批问题,画出准确率曲线(做法示意,曲线形状以你自己的结果为准)。

容易踩的坑:别答“换个窗口更长的模型就好”。窗口更长意味着成本更高、首 token 更慢,中段问题依旧在。也别说“模型完全不看中间”,只是利用率下降,程度随模型和任务差别很大。

可能的追问:

  • attention sink 是什么?很多模型会把大量注意力分给最开头的几个 token(哪怕它们没什么语义),softmax 里没处可分的权重大多落到这里。StreamingLLM 利用这一点,在滑动窗口时保留开头几个 token 来稳定生成(Xiao et al., 2023)。

放到报销案例里:如果把整本差旅制度塞进上下文,而第 4.2 条(市内交通)恰好落在中间,模型很可能视而不见(示意),转而用常识回答。正确做法是让 RAG 只取第 4.2 条等相关条款,放在显眼位置。

扩展:用张量和算例理解公式

缩放点积注意力:输入投影到 Q、K、V,经相似度、掩码与归一化后聚合信息缩放点积注意力:输入投影到 Q、K、V,经相似度、掩码与归一化后聚合信息 · 点击放大

1.1 节直接给了 Q、K、V 的数值。它们是从哪来的?同一个输入矩阵 X 分别乘以三个可学习的矩阵:

Q=XWQ,K=XWK,V=XWVQ=XW_Q,\quad K=XW_K,\quad V=XW_V

X 的每一行是一个 token 的隐状态,形状 n×dn\times d。WQW_Q、WKW_K 的形状是 d×dkd\times d_k,WVW_V 是 d×dvd\times d_v,训练时学出来,推理时固定。乘完以后,Q、K 是 n×dkn\times d_k,V 是 n×dvn\times d_v。

手算一个 token(示意):d = 2,某个 token 的隐状态 x=[1,2]x=[1,2],取

WQ=(1011),WK=(0110)W_Q=\begin{pmatrix}1&0\\1&1\end{pmatrix},\qquad W_K=\begin{pmatrix}0&1\\1&0\end{pmatrix}

那么 q=xWQ=[1×1+2×1, 1×0+2×1]=[3,2]q=xW_Q=[1\times1+2\times1,\ 1\times0+2\times1]=[3,2],k=xWK=[1×0+2×1, 1×1+2×0]=[2,1]k=xW_K=[1\times0+2\times1,\ 1\times1+2\times0]=[2,1]。同一个 token,乘了不同的矩阵,得到的 q 和 k 就不一样。如果让 WQ=WKW_Q=W_K,每个 token 的 q 和 k 会完全相同,打分矩阵变成对称的,“我关注你”和“你关注我”就被强行绑在一起了。

完整的注意力公式和 1.1 相同:

Attention⁡(Q,K,V)=softmax⁡(QK⊤dk+M)V\operatorname{Attention}(Q,K,V)=\operatorname{softmax}\left(\frac{QK^\top}{\sqrt{d_k}}+M\right)V

符号同 1.1 节的表。相似度矩阵 QK⊤QK^\top 是 n×nn\times n,第 i 行表示第 i 个 token 对每个 token 的打分,softmax 沿每一行(被关注的 token 那一维)归一化。因果掩码 M 对未来位置加负无穷,对允许的位置加 0。3 个 token 时它长这样:

M=(0−∞−∞00−∞000)M=\begin{pmatrix}0&-\infty&-\infty\\0&0&-\infty\\0&0&0\end{pmatrix}

第 1 行只有第 1 列是 0:第 1 个 token 只能看自己。第 3 行全是 0:最后一个 token 能看所有人。e−∞=0e^{-\infty}=0,所以加了负无穷的位置在 softmax 后权重恰好是 0,等于从求和里删掉。推理框架通常用等价实现,不会真的构造整个矩阵。

再做一个小练习(教学构造,非实测):一个 query 和两个 key 的点积都是 0,两个位置都允许看,那么 softmax(0, 0) = (1/2, 1/2),输出是两个 value 的平均。屏蔽第二个位置后,分数变成 (0, −∞),权重变成 (1, 0),输出只来自第一个 value。掩码只能删位置,不能让权重变负;如果误把 padding 位置留着没屏蔽,它们也会分走权重。公式依据 Attention Is All You Need,§3.2。

KV Cache 的显存估算换成紧凑写法:

MKV=2LHKVdhTBsM_{KV}=2L H_{KV}d_h T B s

符号同 1.2 节的表:L 为层数,HKVH_{KV} 为 KV 头数,dhd_h 为头维度,T 为缓存长度,B 为 batch,s 为每个元素的字节数。32 层、32 个 KV 头、128 维、8192 token、B = 1、s = 2 时,2 × 32 × 32 × 128 × 8192 × 1 × 2 = 4,294,967,296 字节,即 4 GiB(约 4.29 GB)。改成 8 个 KV 头,只有 HKVH_{KV} 从 32 变成 8,结果按比例变成 1 GiB。再把 s 从 2 换成 1(KV 用 8 bit 存),又减半到 0.5 GiB。

这个式子对每个因子都是线性的,所以哪个因子翻倍,显存就翻倍,调参时很好推。它只估标准未压缩的 KV,不包含权重、激活和分配开销;MLA 的潜变量缓存要按它的实际结构计算(见下文 MLA 一题)。另外注意 GiB 和 GB 的区别:1 GiB = 2302^{30} 字节,1 GB = 10910^9 字节,显卡标称容量通常按 GB 算。

逐题作答

按知识图谱主干 ① 的枝叶逐题展开;括号里的数字是该题在面经里出现的次数。已在上文「常见追问」完整回答的题(Decoder-only、MQA/GQA 省在哪、Lost in the Middle)不再重复。

模型机理

讲一下 Transformer 的整体数据流,Encoder 和 Decoder 各负责什么?(404)

简答:Transformer 把 token 变成向量后,经过多层残差块不断改写表示:注意力负责在 token 之间搬运信息,FFN 负责在每个 token 内部加工。原始结构里,Encoder 双向读懂输入;Decoder 因果地生成输出,并用交叉注意力去看 Encoder 的结果。今天的通用 LLM 只保留了 Decoder 那一半。

细说:原论文的 Encoder-Decoder 结构(Vaswani et al., 2017)如下。拿翻译举例:Encoder 读完“打车超标了”整句,Decoder 一边看 Encoder 的结果,一边逐个吐出 “The taxi fare exceeded ...”。

模块 子层 注意力能看哪里 作用
Encoder(×N) 自注意力 + FFN 整个输入,双向 把源序列编码成上下文化表示
Decoder(×N) 掩码自注意力 + 交叉注意力 + FFN 自注意力只看已生成部分;交叉注意力的 Q 来自 Decoder,K/V 来自 Encoder 输出 一边看源序列,一边逐 token 生成

每个子层外面包着“残差 + 归一化”。原论文用的是 Post-LN、正弦位置编码和 ReLU 的 FFN。

现代 Decoder-only LLM(LLaMA 一类)的数据流就是本页第 1 节那张图。相比原版,常见改动有:Pre-Norm + RMSNorm、RoPE、SwiGLU 的 FFN、GQA、去掉大部分 bias,部分模型让词嵌入和输出层共享权重。

张量形状(单层、省略 batch):输入是 n×d,注意力输出还是 n×d,FFN 中间层升到 n×d_ff,再回到 n×d;最后 lm_head 把 d 维映射到词表大小 |V|,得到 n×|V| 的 logits。代入一组数(示意):20 个 token、d = 4096、d_ff = 11008、词表 32000,形状依次是 20×4096、20×4096、20×11008、20×4096,最后得到 20×32000 的 logits。训练时 20 个位置都算损失,推理时只取最后一行。

训练和推理要分清:

  • 训练:teacher forcing,整段序列一次前向,causal mask 让所有位置并行地预测各自的下一个 token;
  • 推理:自回归。先 prefill,一次处理完整个 prompt 并建立 KV Cache;再 decode,逐 token 生成,每步只算新 token。比如 prompt 有 50 个 token,prefill 一次前向处理这 50 个,之后每生成一个字都只喂进 1 个 token。

容易踩的坑:

  • 常见错误:说 Decoder-only 模型里也有交叉注意力。它只有掩码自注意力;交叉注意力只存在于 Encoder-Decoder 或部分多模态结构中。
  • 别只背框图。面试官爱追问“推理时下一步喂进去的是什么”:是采样出的那个 token 的 id(再查嵌入表),不是概率分布。
  • “残差流”视角很有用:每层都在一条共享的表示流上读写增量,所以层剪枝、早退这类优化才有可能成立。

可能的追问:

  • Cross-Attention 和 Self-Attention 的区别?Self 的 Q/K/V 来自同一序列;Cross 的 Q 来自当前序列,K/V 来自另一个序列(Encoder 输出、图像特征等)。
  • 训练和推理时哪些组件行为不同?dropout 只在训练时生效;KV Cache 只在推理时使用;训练按位置并行,推理按步串行。

放到报销案例里:员工那句话被切成二十来个 token,经过几十层改写后,模型在“上周”“打车”“超标”之间建立了关联。但这些关联全部来自参数,没有一条来自公司制度。

Self-Attention 里的 Q、K、V 是怎么来的,具体怎么算?(211)

简答:Q、K、V 是同一组隐状态分别乘以三个可学习矩阵得到的。用 Q 和所有 K 的缩放点积算分数,softmax 归一化后对 V 加权求和,得到每个位置“融合了上下文”的新表示。

细说:先把形状写清(单头):

X∈Rn×d,WQ,WK∈Rd×dk,WV∈Rd×dvX\in\mathbb{R}^{n\times d},\quad W_Q,W_K\in\mathbb{R}^{d\times d_k},\quad W_V\in\mathbb{R}^{d\times d_v}
  • ∈\in 读作“属于”。Rn×d\mathbb{R}^{n\times d} 表示“n 行 d 列、每个元素都是实数的矩阵”。所以第一项是说:X 是一个 n 行 d 列的矩阵。
  • n 是 token 数,d 是模型隐藏维度,dkd_k、dvd_v 是单头里 Q/K 和 V 的维度。
  • 代入 1.1 节的例子:n = 3,dk=4d_k=4,dv=2d_v=2;真实的 7B 模型里常见 d = 4096、dk=dv=128d_k=d_v=128,n 就是上下文长度。

这一行只是在约定形状,没有计算,但它决定了后面每一步能不能乘得起来:XWQX W_Q 是 (n×d)(n\times d) 乘 (d×dk)(d\times d_k),中间的 d 对上了,结果是 n×dkn\times d_k。面试时把形状说清楚,比背公式更能看出你是否真懂。

计算分六步:

  1. 投影:Q = XW_Q,K = XW_K,V = XW_V(手算见上文「扩展」一节);
  2. 打分:S = QKᵀ/√d_k,得到 n×n 矩阵,S[i][j] 表示位置 i 对位置 j 的关注程度;
  3. 掩码:Decoder 中对 j > i 的位置加 −∞,padding 位置同样屏蔽;
  4. 归一化:每一行做 softmax,行和为 1;
  5. 聚合:输出 = softmax(S)·V,形状 n×d_v;
  6. 多头时把各头输出拼接,再乘 W_O 投回 d 维。

把 1.1 的例子补全成三行(示意)。三个 token 的 key 不变,再给前两个 token 配上 query:q1=[1,0,0,0]q_1=[1,0,0,0],q2=[0,0,2,0]q_2=[0,0,2,0],q3=[1,1,1,1]q_3=[1,1,1,1]。

行(谁在看) 缩放后分数 S 加掩码后 softmax 权重 输出
打车 1, 0.5, 0.5 1, −∞, −∞ 1, 0, 0 [10, 0]
超标 1, 1, 0 1, 1, −∞ 0.5, 0.5, 0 [5, 5]
能报 2, 1, 0 2, 1, 0 0.665, 0.245, 0.090 [7.01, 2.81]

第一行:q1⋅k1=2q_1\cdot k_1=2、q1⋅k2=1q_1\cdot k_2=1、q1⋅k3=1q_1\cdot k_3=1,除以 2 得 1、0.5、0.5,但后两个被掩掉,“打车”只能看自己,输出就是自己的 value。第二行:点积是 2、2、0,缩放后 1、1、0,第三个被掩掉,softmax(1, 1) = (0.5, 0.5),输出是前两个 value 的平均。

复杂度:打分和聚合都是 O(n²·d);如果显式存注意力矩阵,显存是 O(n²)。32k 上下文时,一个头一层的 n×n 矩阵就有约 10.7 亿个数,按 2 字节存是 2 GiB。FlashAttention 把计算切块,在片上高速缓存里在线计算 softmax,不把完整的 n×n 矩阵写回显存。计算量不变,但显存读写大幅减少(Dao et al., 2022)。

容易踩的坑:

  • 为什么要三个矩阵,而不直接用 X?分开投影让“用来匹配的特征”和“被取走的内容”解耦,而且 QKᵀ 不再被迫对称。上面的例子里,“能报”对“打车”的点积是 q3⋅k1=4q_3\cdot k_1=4,反过来“打车”对“能报”是 q1⋅k3=1q_1\cdot k_3=1,“我关注你”不等于“你关注我”。
  • 同一个 token 的 Q、K、V 一般不相同,因为乘的是不同矩阵。
  • 别把注意力权重直接当可解释性:权重高不等于因果贡献大,只能作参考。

可能的追问:

  • Decoder 推理时 Q、K、V 各自来自哪?当前步的新 token 产生 Q、K、V;新的 K、V 追加进缓存,Q 和缓存里的全部 K 打分,再对全部 V 聚合。生成第 101 个 token 时,缓存里有 100 份 K/V,新 token 的 q 要和这 100 个 k 加上自己的 k 一起算分。
  • 稀疏注意力和线性注意力在解决什么?都在压 O(n²)。稀疏注意力只算局部窗口或选定位置,比如每个 token 只看前 4096 个;线性注意力用核函数近似 softmax,把计算顺序改成先算 KᵀV。代价是精确检索远处信息的能力可能下降,所以近年也有模型把全注意力层和滑窗层或线性注意力层交替使用。

多头注意力为什么有效?头数怎么选?(120)

简答:多头把表示空间切成若干子空间,让不同的头各学一种关注模式(相邻词、句法依存、指代、复制前文)。总计算量和一个大头差不多,表达却更丰富。

细说:

  • 做法:把 d_model 拆成 h 个头,每头维度 d_h = d_model / h,各自独立做注意力,拼接后乘 W_O 融合。7B 级模型常见 4096 / 32 = 128。
  • 为什么比一个大头好:单头的 softmax 每行只给出一种权重分布,每个位置只能用一种方式汇总上下文。多头让每个位置同时做 h 种加权平均。举个示意的例子:处理“超标”这个位置时,一个头可能主要看前一个词“打车”,另一个头看句首的“我”,两个结果拼在一起,信息比任何一种单独的平均都全。可解释性研究确实观察到过专门处理相对位置、句法关系的头,还有“在前文找到相同片段并复制其后续”的 induction head。
  • 计算量:投影矩阵的总大小和单头相同。以 d_model = 8、h = 2 为例,每头 d_h = 4,两个头的 W_Q 各是 8×4,拼起来正好是一个 8×8,参数都是 64 个。打分是 h 个 n×n 矩阵,每个只用 d_h 维算点积,总 FLOPs 同量级。
头数 好处 风险
偏少 单头维度大 关注模式单一
偏多 模式多样 单头维度过小,每个头的打分矩阵秩不超过 d_h,表达受限;kernel 效率下降

表里的“秩”可以这样理解:d_h = 4 时,即使序列有 1000 个 token、打分矩阵是 1000×1000,这个矩阵也最多只有 4 个独立方向,能表达的关注模式很有限。主流模型的 head_dim 多在 64 到 128 之间,头数随 d_model 增大而增加。

容易踩的坑:

  • 头不是越多越好,也不是每个头都有用。有研究发现,训练好的模型里相当一部分头剪掉后性能下降很小,说明头之间有冗余。
  • 别把“每个头学不同东西”讲成设计保证。它是训练中自然出现的倾向,没有任何约束强制它发生。
  • query 头数和 KV 头数是两回事:query 头可以多,KV 头可以少(GQA),后者影响的是推理显存。

可能的追问:

  • 多头在代码里怎么“拆”?并不真的拆矩阵。一次大矩阵乘得到 n×d,再 reshape 成 n×h×d_h、转置成 h×n×d_h 后批量计算,最后再变回来。比如 20 个 token:先从 20×4096 变成 20×32×128,再转成 32×20×128,32 个头一起算完,再拼回 20×4096。

缩放点积为什么要除以 √d_k?不除会怎样?(92)

简答:推导见上文「常见追问」,这里补数值直觉:不缩放时维度一大,softmax 几乎变成 argmax(只挑最大的那个),梯度接近 0,注意力在训练初期就过早“锁死”。

细说:教学算例(非实测):dk=128d_k=128,点积的标准差约 128≈11.3\sqrt{128}\approx 11.3。设某行只有两个候选,“标准化后”的分数差为 1。不缩放时,同样的差距会被放大约 11.3 倍:

情形 logits softmax p(1−p)
缩放 (0, 1) ≈ (0.27, 0.73) ≈ 0.20
不缩放 (0, 11.3) ≈ (0.00001, 0.99999) ≈ 0.00001

最后一列就是上文雅可比矩阵的对角元 p(1−p)p(1-p),梯度大致按它缩放:0.20 对 0.00001,差了四个数量级左右(精确算是约 1.6 万倍)。

也可以把缩放看成一个固定温度的 softmax:

softmax⁡(zτ),τ=dk\operatorname{softmax}\left(\frac{z}{\tau}\right),\qquad \tau=\sqrt{d_k}

z 是未缩放的点积分数向量,τ(读作 tau)是温度,所有分数都先除以它再做 softmax。τ 越大,分数之间的差距被压得越小,分布越平;τ 越小,差距越大,分布越尖。

拿 1.1 节的例子对照:未缩放的点积是 (4, 2, 0)。取 τ = √4 = 2,得到 (2, 1, 0),softmax 后是 (0.665, 0.245, 0.090)。如果不缩放,相当于 τ = 1,softmax(4, 2, 0) ≈ (0.867, 0.117, 0.016):“打车”一个就拿走了 87% 的权重,“能报”自己只剩 1.6%。这里 dkd_k 只有 4,差距已经很明显;dk=128d_k=128 时会极端得多。

容易踩的坑:

  • 这个因子依赖“初始化时分量近似独立、方差为 1”的假设。训练后分布会变化,所以一些新模型还会额外用 QK-Norm、注意力 logit 软截断等手段控制分数的尺度。
  • 别和解码时的 Temperature 混为一谈:这里的 τ 控制注意力在 token 之间怎么分配,解码温度控制输出词表上的分布,下文解码题里会用同一个形式的公式。

可能的追问:

  • 缩放因子能不能学?可以,有工作把它设成可学习参数,或配合 QK-Norm 一起用;但固定 √d_k 简单稳定,是默认选择。

MHA、MQA、GQA、MLA 有什么区别?和 KV Cache 什么关系?(55)

简答:四者都在“注意力的表达能力”和“推理时 KV Cache 的大小”之间找平衡。MHA 每个头一份 K/V,MQA 所有头共享一份,GQA 分组共享,MLA 把 K/V 压成一个低秩潜向量再缓存。

细说:KV Cache 为什么存在?自回归解码时,历史 token 的 K/V 在因果注意力下不会改变,缓存起来以后,每步只需为新 token 计算投影,不必整段重算。代价是显存随“层数 × KV 头数 × 头维度 × 长度 × 并发”线性增长(公式见第 1.2 节)。

方案 每层每 token 缓存什么 相对 MHA 的缓存 32 个 query 头时(示意) 代价
MHA H 份 K 和 V 1 32 份,每 token 0.5 MiB 显存最大
GQA G 份 K 和 V(分 G 组) G/H G = 8 时 8 份,128 KiB 轻微质量损失,主流默认
MQA 1 份 K 和 V 1/H 1 份,16 KiB 质量和稳定性风险更大
MLA 一个低秩潜向量,外加一小段单独承载位置的 RoPE 分量 取决于潜向量维度 见下面 DeepSeek-V2 的算例 结构复杂,需特殊处理 RoPE

(表中每 token 的数字沿用 1.2 节的 32 层、128 维、FP16 配置。)

MLA 的思路是先压缩、用时再展开(DeepSeek-V2):

ct=WDKV ht,kt=WUK ct,vt=WUV ctc_t = W_{DKV}\,h_t,\qquad k_t = W_{UK}\,c_t,\qquad v_t = W_{UV}\,c_t
  • hth_t 是第 t 个 token 在这一层的隐状态,d 维。
  • WDKVW_{DKV} 是下投影矩阵(D 表示 down),把 hth_t 压成一个短得多的潜向量 ctc_t,长度记作 dcd_c。
  • WUKW_{UK}、WUVW_{UV} 是上投影矩阵(U 表示 up),用到时再从 ctc_t 还原出各个头的 key 和 value。

缓存里只放 ctc_t。按 DeepSeek-V2 论文的配置,128 个头、每头 128 维,MHA 每层每 token 要存 2 × 128 × 128 = 32768 个数;MLA 只存 dc=512d_c=512 维的潜向量,加上 64 维的 RoPE 分量,共 576 个数,约为前者的 1/57。

只存压缩版,用的时候还要不要每步把历史 K 解压出来?不用。打分可以改写成 q⊤WUKct=(WUK⊤q)⊤ctq^\top W_{UK} c_t = (W_{UK}^\top q)^\top c_t,先把上投影矩阵乘到 query 上,再直接和潜向量做点积。手算一个最小例子:取 dc=1d_c=1,WUK=(21)W_{UK}=\begin{pmatrix}2\\1\end{pmatrix},ct=3c_t=3,q=[1,1]q=[1,1]。老老实实解压:kt=WUKct=[6,3]k_t=W_{UK}c_t=[6,3],q⋅kt=6+3=9q\cdot k_t=6+3=9。先吸收:WUK⊤q=2+1=3W_{UK}^\top q=2+1=3,再乘 ct=3c_t=3,也是 9。结果一样,但第二种做法里历史 token 只需要 ctc_t。V 侧同理,可以吸收进输出投影。RoPE 的旋转矩阵随位置变化,夹在 q 和 WUKW_{UK} 中间时就没法预先乘好,所以 MLA 额外拿一小部分维度单独承载位置信息(decoupled RoPE),也就是表里那 64 维。

容易踩的坑:

  • KV Cache 只消除重复计算,并不消除 O(n²):每步新 token 仍要和全部历史打分,长度越长,单步越慢。
  • 只算 KV Cache 会低估显存:权重、激活、框架预留和碎片都要算。vLLM 的 PagedAttention 就是为了解决 KV Cache 碎片、按页按需分配而设计的(Kwon et al., 2023)。
  • 其他降 KV 的手段和这四者正交:KV 量化、滑动窗口注意力、跨层共享 KV、按重要性丢弃 token。

可能的追问:

  • 多轮对话能复用 KV Cache 吗?能复用相同的前缀。推理框架的 prefix caching 和云厂商的 prompt caching 都是这个思路,前提是前缀逐 token 完全一致,所以系统提示和工具定义要放在最前面并保持稳定。具体命中规则以各家文档为准。

放到报销案例里:报销助手每轮都带同一段系统提示和 query_invoices、query_tickets、create_reimbursement_ticket、search_policy 四个工具的定义。改之前,系统提示第一行写着“今天是 2026-11-02”,日期每天一变,后面的前缀就全部失配;改之后,把日期挪到用户消息之前,开头这一大段每天都能命中前缀缓存,首 token 延迟和成本都会下降。这笔账在 工程化 站细算。

FFN、残差、LayerNorm/RMSNorm、Pre-Norm/Post-Norm 分别起什么作用?(74)

简答:注意力负责在 token 之间搬运信息,FFN 负责在每个 token 内部做非线性加工,并存储大量知识。残差给信息和梯度留了一条直通路,归一化稳住每层输入的尺度,Pre-Norm 是让深层网络好训的主流摆放方式。

细说:

  • FFN:逐位置的 MLP,每个 token 各算各的,互不交流。经典形式是先从 d 升到 4d,再降回 d;LLaMA 一类用 SwiGLU 门控:
FFN⁡(x)=W2(SiLU⁡(W1x)⊙W3x)\operatorname{FFN}(x)=W_2\big(\operatorname{SiLU}(W_1x)\odot W_3x\big)
符号 含义 形状
xx 一个 token 的隐状态 d 维
W1,W3W_1, W_3 两个升维矩阵,一个走激活函数,一个当“门” dff×dd_{ff}\times d
W2W_2 降维矩阵,把结果投回 d 维 d×dffd\times d_{ff}
SiLU⁡(z)\operatorname{SiLU}(z) 激活函数 z⋅σ(z)z\cdot\sigma(z),其中 σ(z)=1/(1+e−z)\sigma(z)=1/(1+e^{-z}) 是 sigmoid,输出在 0 到 1 之间 逐元素
⊙\odot 逐元素相乘:两个等长向量对应位置相乘,不求和 逐元素

手算(示意):d = 1,dff=2d_{ff}=2。取 x=2x=2,W1=(1−1)W_1=\begin{pmatrix}1\\-1\end{pmatrix},W3=(21)W_3=\begin{pmatrix}2\\1\end{pmatrix},W2=[1,1]W_2=[1,1]。

  1. W1x=[2,−2]W_1x=[2,-2]。SiLU(2) = 2 × σ(2) ≈ 2 × 0.881 = 1.762;SiLU(−2) = −2 × σ(−2) ≈ −2 × 0.119 = −0.238。
  2. W3x=[4,2]W_3x=[4,2]。
  3. 逐元素相乘:[1.762 × 4, −0.238 × 2] ≈ [7.046, −0.477]。
  4. W2W_2 把两项加起来:7.046 − 0.477 ≈ 6.570。

W3xW_3x 这一路像一个音量旋钮,决定 SiLU 那一路的每个分量放大多少、甚至翻不翻号。门控比单纯的升维、ReLU、降维多了一个可学习的调节量,代价是多一个矩阵。

为了和经典 4d 的参数量持平,中间维常取约 8d/3:经典 FFN 有两个 d×4d 矩阵,共 8d²;SwiGLU 有三个 d×(8d/3) 矩阵,也是 8d²。d = 4096 时 8d/3 ≈ 10923,LLaMA 实际取了 11008(凑成硬件友好的整数倍)。以 MHA 为例,每层注意力约 4d² 参数(Q、K、V、O 四个 d×d 矩阵),FFN 约 8d²,所以 FFN 占 dense 模型参数的大头。按 d = 4096、d_ff = 11008 实算,每层 FFN 约 1.35 亿参数,注意力约 0.67 亿,FFN 占三分之二。也有研究把 FFN 解释为一种键值记忆。MoE 选择把 FFN 拆成专家,原因也在这里。

  • 残差:x + F(x)。比如 x = [1, 2],这一层学到的增量 F(x) = [0.1, −0.1],输出就是 [1.1, 1.9],原来的信息基本都在。即使 F 学得不好,信息也能原样传下去;反向传播时,x + F(x) 对 x 的导数里有一项恒为 1,梯度不会逐层衰减。
  • LayerNorm 与 RMSNorm:
LN⁡(x)=γ⊙x−μσ2+ϵ+β,RMSNorm⁡(x)=γ⊙x1d∑ixi2+ϵ\operatorname{LN}(x)=\gamma\odot\frac{x-\mu}{\sqrt{\sigma^2+\epsilon}}+\beta,\qquad \operatorname{RMSNorm}(x)=\gamma\odot\frac{x}{\sqrt{\frac{1}{d}\sum_i x_i^2+\epsilon}}
  • μ\mu 是向量 x 各分量的平均值,σ2\sigma^2 是各分量的方差,两者都只在这一个 token 的 d 个数里算。
  • ϵ\epsilon 是一个很小的数(比如 10−510^{-5}),防止分母为 0。
  • γ\gamma、β\beta 是可学习的逐维缩放和平移,初始化为 1 和 0。
  • 1d∑ixi2\sqrt{\frac{1}{d}\sum_i x_i^2} 叫均方根(RMS):各分量平方的平均再开根号。

手算(取 γ = 1、β = 0,忽略 ε):x = [2, 4, 4, 6]。

  • LayerNorm:μ = 4,σ² = (4 + 0 + 0 + 4) / 4 = 2,σ ≈ 1.414,输出 ≈ [−1.414, 0, 0, 1.414],平均值被拉到 0。
  • RMSNorm:RMS = √((4 + 16 + 16 + 36) / 4) = √18 ≈ 4.243,输出 ≈ [0.471, 0.943, 0.943, 1.414],不减均值,只把整体尺度缩到 1 左右。

RMSNorm 去掉了减均值和偏置 β,计算更省,实践效果相当(Zhang & Sennrich, 2019)。它的出发点是:稳定训练主要靠控制尺度,平移这一步可以省掉。

  • 为什么不用 BatchNorm:BN 沿 batch 维统计,而文本序列长度可变、batch 常常很小,自回归推理时一次可能只有一条请求,没有可靠的 batch 统计。LN 和 RMSNorm 只在单个 token 的特征维上统计,和 batch 无关。
摆放 形式 优点 缺点
Post-Norm(原论文) LN(x + F(x)) 每层输出都被归一;有研究认为能训稳时最终效果可能更好 层数一深梯度不稳,依赖 warmup,容易训崩
Pre-Norm(主流 LLM) x + F(LN(x)) 恒等路径干净,深层训练稳定 残差流幅度随层数增长,深层贡献被稀释;最后需要 final norm

容易踩的坑:

  • 常见错误:说 Pre-Norm“效果一定更好”。它的主要优势是好训;DeepNorm、Sandwich-Norm 等改法都在尝试兼顾两者。
  • 别漏掉 final norm:Pre-Norm 结构里最后一层的输出没有经过归一,要再加一层归一化才接 lm_head。

可能的追问:

  • FFN 的激活为什么换成 SwiGLU?门控结构在相同参数量下通常效果更好,这是经验结论;提出它的论文本身也承认缺少理论解释。

位置编码有哪些?RoPE 为什么能表达相对位置?长度外推怎么做?(53/52)

简答:注意力本身对顺序不敏感,位置编码负责注入“谁在前、谁在后”。RoPE 把位置变成 Q/K 上的旋转角度,两个向量做点积后只剩角度差,于是天然表达相对位置,是当前的主流。

细说:

方法 怎么注入 代表 外推表现
正弦绝对位置 固定函数,加到输入嵌入上 原始 Transformer 公式能算任意位置,实际外推差
可学习绝对位置 每个位置一个可训练向量 BERT、GPT-2 超出训练长度就没有向量
RoPE 旋转 Q 和 K LLaMA、Qwen、DeepSeek 等 中等,需配合缩放方法
ALiBi 注意力分数加上与距离成正比的负偏置 BLOOM 等 较好,但远程信息利用偏弱

RoPE 的做法:把 d 维向量两两分成一组,每组看作平面上的一个点。位置为 m 的 token,第 i 组旋转 mθim\theta_i 弧度,频率 θi=b−2i/d\theta_i = b^{-2i/d},i 从 0 数到 d/2 − 1,b 叫 base,常见取 10000,近年的模型常调得更大。一组二维坐标的旋转写成矩阵是:

Rm=(cos⁡mθ−sin⁡mθsin⁡mθcos⁡mθ)R_m=\begin{pmatrix}\cos m\theta & -\sin m\theta\\ \sin m\theta & \cos m\theta\end{pmatrix}

把一个点 [x, y] 左乘 RmR_m,就是绕原点逆时针转 mθm\theta,长度不变。

先看频率有多大差别。d = 128、b = 10000 时:第 0 组 θ0=100000=1\theta_0=10000^0=1,每往后一个位置转 1 弧度(约 57°),大约 6.3 个位置转满一圈;第 32 组 θ32=10000−0.5=0.01\theta_{32}=10000^{-0.5}=0.01,要约 628 个位置才转一圈;最后一组 θ63=10000−126/128≈0.000115\theta_{63}=10000^{-126/128}\approx 0.000115,转一圈要约 5.4 万个位置。前面的组转得快,适合分辨“隔壁和隔两个”;后面的组转得慢,适合分辨“几百和几千”的远近。

旋转矩阵有个性质:Rm⊤Rn=Rn−mR_m^\top R_n = R_{n-m},先倒转 m 再正转 n,等于直接转 n − m。所以

⟨Rmq,  Rnk⟩=q⊤Rm⊤Rn k=q⊤Rn−m k\langle R_m q,\; R_n k\rangle = q^\top R_m^\top R_n\, k = q^\top R_{n-m}\, k

⟨a,b⟩\langle a,b\rangle 是内积,就是前文的点积 a⊤ba^\top b。左边是“位置 m 的 query 转过之后”和“位置 n 的 key 转过之后”做点积,右边只剩 n − m。打分只依赖相对位移 n − m。

手算验证(示意,为了好算取 θ = 90°,真实模型第 0 组是 1 弧度)。q = [1, 2],k = [2, 1]。转 90° 就是把 [x, y] 变成 [−y, x]。

  • q 在位置 1、k 在位置 2:R1q=[−2,1]R_1q=[-2,1],R2kR_2k 转 180° 得 [−2, −1],点积 = 4 − 1 = 3。
  • q 在位置 3、k 在位置 4:R3qR_3q 转 270° 得 [2, −1],R4kR_4k 转 360° 回到 [2, 1],点积 = 4 − 1 = 3。
  • 直接算右边:R1k=[−1,2]R_1k=[-1,2],q⋅R1k=−1+4=3q\cdot R_1k=-1+4=3。

两对 token 的绝对位置不同,但都相隔 1,分数都是 3。把间隔换成 2(q 在 1、k 在 3):R3k=[1,−2]R_3k=[1,-2],和 R1q=[−2,1]R_1q=[-2,1] 的点积是 −2 − 2 = −4,分数变了。RoPE 只作用在 Q、K 上,不作用于 V,也不加到输入嵌入上。

长度外推为什么会坏?还是 d = 128、b = 10000 的配置,假设训练长度是 4096。最慢的那一组在训练中最多转到 0.000115 × 4096 ≈ 0.47 弧度(约 27°),它从没见过更大的角度。直接拿去跑 32768 长度,这一组会转到约 3.78 弧度(约 217°),是训练里完全没出现过的角度,注意力分布随之漂移。快的那几组没这个问题,训练时早已转过成百上千圈。常见方法:

  • 位置插值(PI):把位置按比例压缩回训练范围。要跑 32768,就把位置除以 8,最慢组又回到 27° 以内;代价是相邻 token 在最快那组的角度差也从 1 弧度变成 0.125 弧度,近处分辨率变差;
  • NTK-aware 缩放 / 调大 base:主要拉伸低频维度,尽量保留高频分辨率;
  • YaRN:按频段分别处理,并调整注意力温度(Peng et al., 2023);
  • 再配合少量长文本继续训练。很多模型的长窗口就是这样逐步扩出来的。

容易踩的坑:

  • 外推方法让模型“能跑”更长的序列,不等于“用得好”,还要看长文本训练数据和评测(见上文 Lost in the Middle)。
  • 常见错误:说 RoPE“不含绝对位置”。形式上它对每个位置做的是绝对旋转,效果上体现为相对位置。
  • 实现坑:相邻两维配对还是前后两半配对,不同代码库约定不同。比如 128 维里,有的实现把第 0、1 维配成一组,有的把第 0、64 维配成一组,加载权重时搞错会直接输出乱码。

可能的追问:

  • RoPE 为什么有远程衰减?RoFormer 论文分析了点积的一个上界,它随相对距离增大而下降,直观上模型更偏向近处。这是它的性质,也是长上下文中远处信息难用的原因之一。

放到报销案例里:员工请求、第 4.2 条制度原文、query_invoices 返回的发票记录拼起来可能上万 token。位置编码和外推质量决定了这段上下文能装多长、装进去后还准不准,这是下一站 上下文工程 的上限。

生成控制

Tokenizer 怎么工作?BPE、WordPiece、SentencePiece 有什么区别?(32)

简答:Tokenizer 把文本切成词表里的子词单元,再映射成 id。BPE 按频率合并,WordPiece 按似然增益合并,Unigram 从大词表出发按似然裁剪;SentencePiece 是一个把空格也当普通字符处理、可以承载 BPE 或 Unigram 的工具库。

细说:

算法 怎么训练词表 代表
BPE 从字符(或字节)开始,反复合并语料里最频繁的相邻对,直到词表达到目标大小 GPT 系列(字节级 BPE)
WordPiece 同样自底向上合并,但选“合并后语言模型似然提升最多”的对,可近似为 freq(ab) / (freq(a)·freq(b)) BERT(词内续接片段带 ## 前缀)
Unigram LM 先给一个很大的候选词表,迭代删除对整体似然影响最小的子词 T5 等(经由 SentencePiece)
SentencePiece 一个框架,本身不是新算法:直接处理原始字符流,空格用 ▁ 表示,语言无关,可选 BPE 或 Unigram T5、LLaMA 1/2 等

用一个小语料看 BPE 和 WordPiece 的差别(Hugging Face 课程里常用的教学例子)。语料里的词和出现次数是:hug × 10、pug × 5、pun × 12、bun × 4、hugs × 5,一开始全部拆成单个字母。

  • BPE 数相邻对出现的次数:“u g”出现在 hug、pug、hugs 里,共 10 + 5 + 5 = 20 次;“p u”17 次,“u n”16 次,“h u”15 次,“g s”5 次。第一轮合并次数最多的“ug”。
  • WordPiece 用 freq(ab) / (freq(a)·freq(b)) 打分,freq 是出现次数。字母 u 一共出现 36 次,g 出现 20 次,s 出现 5 次。“u g”的分数是 20 / (36 × 20) ≈ 0.028,“g s”是 5 / (20 × 5) = 0.05。WordPiece 第一轮反而先合并“gs”。

分母的作用是惩罚“两边本来就很常见”的组合:u 到处都是,ug 出现得多不稀奇;s 很少见,它几次出现都紧跟在 g 后面,这个搭配更值得变成一个整体。

字节级 BPE 以 256 个字节为基础单元,任何字符串都能编码,不存在未登录词。

词表大小的取舍:词表大,同样的文本切出来的 token 更少,同样的窗口能装更多内容,生成步数也更少;但嵌入层和 lm_head 更大,低频 token 训练不充分。近年模型的词表普遍从几万扩到十万级以上,用来覆盖多语言和代码。

容易踩的坑:

  • 中文、代码、数字的切分效率因模型而异,同一段话在不同模型上 token 数可能差不少。估成本和上下文预算必须用目标模型的 tokenizer 实测,不能按“一个汉字一个 token”拍脑袋。
  • 很多看起来很低级的错误源于切分:数单词里有几个字母、逐位算术、比较 9.11 和 9.9。模型看到的是子词块,看不到一个个字符;比如 9.11 可能被切成“9”“.”“11”(示意),模型就容易按“11 比 9 大”去比。
  • 特殊 token(BOS/EOS、对话角色标记、工具调用标记)由对话模板定义。模板拼错会让模型行为异常,伪造角色边界也是 Prompt Injection 的常见手法之一。

可能的追问:

  • 模型的输入到底是什么?是 token id 序列,查嵌入表得到向量。推理时下一步的输入是采样出的 token id,不是概率分布。
  • 为什么有时一个汉字占多个 token?字节级 BPE 下,常用汉字在 UTF-8 里占 3 个字节,比如“报”是 E6 8A A5。如果词表没有把这 3 个字节合并成一个完整 token,就会拆成多个字节 token。

放到报销案例里:金额 118.5、发票号 INV-102、工单号 R-55821 这类字符串会被切成零碎的 token,比如“INV”“-”“102”(示意),模型抄写时容易错一位。所以“118.5 比 80 超了多少”这种比较、编号是否存在的校验,都交给工具,不交给模型心算。

解码策略有哪些?Temperature、Top-k、Top-p 各怎么起作用?(34)

简答:模型每步只给出下一个 token 的概率分布,解码策略决定怎么从中选。贪心和 Beam 追求高概率;采样类方法用 Temperature 调分布形状,用 Top-k / Top-p 截掉长尾,在确定性和多样性之间取舍。

细说:Temperature 作用在 logits 上,和上文 √d_k 那题是同一个形式:

pi=exp⁡(zi/T)∑jexp⁡(zj/T)p_i=\frac{\exp(z_i/T)}{\sum_j \exp(z_j/T)}

ziz_i 是模型给词表里第 i 个 token 的原始打分(logit),T 是温度,pip_i 是调整后选中第 i 个 token 的概率,分母对整个词表求和,保证所有 pip_i 加起来是 1。

手算(示意):员工问“能报吗”,模型下一个词只考虑三个候选“可以”“需要”“不能”,logits 是 (2, 1, 0)。

温度 T 除以 T 后 可以 需要 不能
0.5 (4, 2, 0) 0.867 0.117 0.016
1 (2, 1, 0) 0.665 0.245 0.090
2 (1, 0.5, 0) 0.506 0.307 0.186

T = 2 那一行:e1≈2.718e^1\approx 2.718,e0.5≈1.649e^{0.5}\approx 1.649,e0=1e^0=1,总和 5.367,三个概率是 2.718/5.367、1.649/5.367、1/5.367。T 越小,最高分越占优,T 趋于 0 时接近贪心;T = 1 是原始分布;T 越大越平,“不能”从 1.6% 涨到 18.6%。温度只改变分布的胖瘦,不改变排序,第一名永远是“可以”。

方法 做法 适用
贪心 每步取最大概率 短答案、确定性抽取;长文本易复读
Beam Search 保留 B 条累计概率最高的候选,常加长度惩罚 翻译、摘要等有参考答案的任务;开放对话里容易平淡
Top-k 只在概率最高的 k 个里采样 简单;但 k 固定,分布尖时仍会放进噪声,分布平时又截得过狠
Top-p(nucleus) 取累计概率达到 p 的最小集合再采样 自适应截断,最常用(Holtzman et al., 2019)
惩罚项 重复惩罚、频率 / 存在惩罚 抑制复读

Top-k 和 Top-p 的差别,拿两组分布对比一下(示意)。

分布 A 比较平:5 个候选的概率是 0.50、0.25、0.15、0.06、0.04。

  • Top-p = 0.8:从高到低累加,依次是 0.50、0.75、0.90,到第 3 个才超过 0.8,所以保留前 3 个。重新归一化:0.50/0.90 ≈ 0.556,0.25/0.90 ≈ 0.278,0.15/0.90 ≈ 0.167。
  • Top-k = 2:只留前 2 个,归一化成 0.667 和 0.333,第三个 0.15 的合理候选被砍掉了。

分布 B 很尖:0.90、0.04、0.03、0.02、0.01。

  • Top-p = 0.8:第 1 个就有 0.90,只保留它,等于贪心。
  • Top-k = 3:还是留下 0.04 和 0.03 两个噪声候选,归一化后合计约 7%(0.07 / 0.97)的概率会抽到它们。

Top-p 的保留个数会随分布形状自动伸缩,k 是死的。常见实现是先除以温度,再做 top-k / top-p 截断,重新归一化后采样,但各框架的顺序和细节不完全相同,调参前看文档。

容易踩的坑:

  • “温度设 0 就完全确定”不对。GPU 并行计算和服务端动态批处理会带来浮点非确定性,同样的输入仍可能偶尔输出不同。要可复现,得固定模型版本和参数,评测时多次采样看方差。
  • “温度 0 就没有幻觉”也不对。温度只决定是不是总选最高概率的 token;如果模型把错误事实学成了高概率,贪心只会更稳定地输出错误。上面的表里,如果正确答案其实是“需要”(需要审批),T = 0.5 反而让模型更确定地说“可以”。
  • 推理模型常有厂商推荐的采样参数,部分接口甚至不允许调温度,别想当然套用老经验。

可能的追问:

  • Agent 里怎么设?工具选择、参数 JSON、分类路由用低温度甚至贪心,再配合结构化输出;头脑风暴、写文案用中等温度;同一任务在评测和线上保持同一组参数。
  • 结构化输出怎么保证 JSON 合法?强做法是约束解码:每一步根据 JSON Schema 或语法状态机,把会导致非法前缀的 token 的 logits 置为 −∞。比如已经输出了 {"amount": ,而 schema 规定 amount 是数字,那么这一步除数字和负号以外的 token 全部被屏蔽,模型想写 "118.5元" 也写不出来。弱做法是“提示 + 解析 + 失败重试”。即使格式合法,字段值是否正确仍要业务校验。

放到报销案例里:调用 create_reimbursement_ticket 时,金额 118.5、发票号 INV-102 必须一字不差,这一步用低温度加 schema 约束。给员工解释制度时可以稍微放开,但事实部分仍要来自第 4.2 条的引用。

流式输出怎么实现?首 token 延迟由什么决定、怎么优化?

简答:流式输出就是服务端每解码出一小段 token 就推给客户端(HTTP 场景常用 SSE)。首 token 延迟(TTFT)主要由排队和 prefill 决定,之后的速度由每个 token 的解码时间决定。两者瓶颈不同,优化手段也不同。

细说:一次请求的时间可以粗拆为

Ttotal≈Tqueue+Tprefill+Nout⋅TtokenT_{\text{total}}\approx T_{\text{queue}}+T_{\text{prefill}}+N_{\text{out}}\cdot T_{\text{token}}
  • TqueueT_{\text{queue}}:请求在服务端排队等 GPU 的时间;
  • TprefillT_{\text{prefill}}:一次性处理整个 prompt、建好 KV Cache 并产出第 1 个 token 的时间;
  • NoutN_{\text{out}}:要生成的 token 个数;
  • TtokenT_{\text{token}}:之后每生成一个 token 平均要多久。

前两项加起来大致就是 TTFT,第三项决定用户看着字一个个往外蹦的速度。

阶段 做什么 瓶颈 主要优化
Prefill 并行处理整个 prompt,建 KV Cache,产出第 1 个 token 计算受限,随 prompt 长度增长 缩短 prompt、前缀 / 提示缓存、分块 prefill、高效注意力 kernel
Decode 逐 token 生成 访存受限(每步读权重和 KV Cache) 量化、GQA/MLA、连续批处理、投机解码

教学算例(示意数字,非实测):prompt 6000 token、输出 300 token,假设不排队、prefill 耗时 0.8 秒、每 token 25 毫秒。TTFT ≈ 0.8 秒,总时长 ≈ 0.8 + 300 × 0.025 = 0.8 + 7.5 = 8.3 秒。流式让用户 0.8 秒就看到字,体感好得多,但总时长没变。

这个拆法告诉你该往哪使劲。想缩短 TTFT,就砍 prompt 或命中前缀缓存;想缩短总时长,输出越短越好,300 个 token 里有 7.5 秒都花在第三项上。它忽略了网络传输和客户端渲染,排队时间在高峰期也可能比 prefill 还长。

实现要点:服务端按 token 或按词边界 flush;客户端增量渲染;用户中途取消时要通知服务端停止生成、释放 KV Cache;流式内容的安全检查要按滑动窗口做,或先缓冲一小段再放出。

容易踩的坑:

  • 工具调用的参数也是流式到达的,必须等参数完整、通过校验才能执行,不能“边流边调”。收到 {"invoice_id": "INV-1 时就去查,查的是一张不存在的发票。
  • Agent 的延迟是多次模型调用之和:三步工具循环就有三次 TTFT。沿用上面的数字,三轮光 prefill 就是 2.4 秒,还没算每轮的输出和工具本身的耗时。与其抠单次 TTFT,不如减少轮数,并行执行互不依赖的工具调用。
  • 吞吐和延迟互相拉扯:batch 越大吞吐越高,但单请求每 token 的延迟会上升。

可能的追问:

  • 投机解码为什么能加速?用一个小模型(或模型自带的多 token 预测头)先草拟几个 token,大模型一次前向并行验证,接受的部分直接输出。在保持输出分布不变的前提下,减少了大模型的串行步数(Leviathan et al., 2023)。比如小模型一次草拟 4 个 token,大模型验证后接受了前 3 个,那么大模型这一次前向就产出了 3 个 token,外加它在第一个被拒位置重新采样的 1 个。
  • 线上怎么监控?分别记录 TTFT、每输出 token 时间、端到端时延的 P50/P95,别只看平均值,见 可观测性:追踪一条任务的代价。

放到报销案例里:员工问“能不能报”,系统可以先流式给出“正在查询差旅制度和你的发票”这类进度;真正的结论要等 search_policy 和 query_invoices 的结果回来后再生成。

结构效率

MoE 是什么?Router 怎么选专家、负载不均怎么解决?DeepSeek MoE 做了什么改进?(68/23)

简答:MoE 把每层的 FFN 换成一组专家加一个路由器,每个 token 只激活 top-k 个专家,用稀疏激活把“总参数量”和“单 token 计算量”拆开。难点在路由均衡、训练稳定和专家并行的通信。

细说:一种常见的路由形式:

g=softmax⁡(Wrx),y=∑i∈TopK⁡(g)g~i Ei(x)g=\operatorname{softmax}(W_r x),\qquad y=\sum_{i\in \operatorname{TopK}(g)} \tilde g_i\, E_i(x)
  • xx 是当前 token 的隐状态,WrW_r 是路由矩阵,形状是“专家数 × d”,WrxW_rx 给每个专家打一个分。
  • gg 是 softmax 之后的路由概率,每个专家一个数,加起来是 1。
  • TopK⁡(g)\operatorname{TopK}(g) 是 g 里最大的 k 个元素的下标集合,i∈TopK⁡(g)i\in\operatorname{TopK}(g) 表示只对这 k 个专家求和,其余专家根本不计算。
  • Ei(x)E_i(x) 是第 i 个专家(一个独立的 FFN)对 x 的输出,g~i\tilde g_i 是 top-k 内重新归一化后的权重。各模型细节不同,有的不重归一化,有的改用 sigmoid 打分。

手算(示意):4 个专家,k = 2,路由打分 Wrx=(2, 1, 0.5, −1)W_rx=(2,\ 1,\ 0.5,\ -1)。

  1. softmax:e2≈7.389e^2\approx 7.389,e1≈2.718e^1\approx 2.718,e0.5≈1.649e^{0.5}\approx 1.649,e−1≈0.368e^{-1}\approx 0.368,总和约 12.124,得 g ≈ (0.609, 0.224, 0.136, 0.030)。
  2. Top-2 是专家 1 和专家 2。
  3. 重新归一化:0.609 / (0.609 + 0.224) ≈ 0.731,0.224 / 0.833 ≈ 0.269。
  4. 假设两个专家的输出是 E1(x)=[1,0]E_1(x)=[1,0]、E2(x)=[0,1]E_2(x)=[0,1],则 y = 0.731 × [1, 0] + 0.269 × [0, 1] = [0.731, 0.269]。

专家 3 和 4 这一步完全没被调用,它们的参数在显存里,却没花一次乘法。MoE 总参数大、单 token 计算小,指的就是这种情况。

负载不均:训练初期 router 偏爱少数专家,被选中的专家学得更好、更容易再被选中,形成“路由崩塌”。专家并行时,某张卡过载还会拖慢整批计算。常用手段:

  • 辅助负载均衡损失:鼓励各专家实际接收的 token 比例和路由概率都趋于均匀(Switch Transformer 的做法);
  • capacity factor:每个专家每批最多处理一定数量的 token,溢出的丢弃或只走残差。比如一批 1024 个 token、8 个专家、top-2,共 2048 次分配,平均每个专家 256 个;capacity factor 取 1.25 时,每个专家最多收 320 个(示意);
  • router z-loss:抑制路由 logits 过大,提升稳定性;
  • 专家并行下的 all-to-all 通信优化和专家放置策略。

DeepSeek 系列的改进(以论文描述为准):

  • 细粒度专家:把专家切得更小、数量更多,每个 token 激活更多的小专家,组合更灵活;
  • 共享专家:少数始终激活的专家承载通用知识,减少路由专家之间的冗余(DeepSeekMoE);
  • DeepSeek-V3 的无辅助损失负载均衡:给每个专家的打分加一个只用于选专家的偏置项,按负载动态调节,避免辅助损失干扰主目标(另保留一个权重很小的序列级平衡损失),见技术报告。放到上面的例子里:如果专家 1 最近总是过载,就给它的偏置减一点,比如把它用于选择的分数从 2 压到 0.3,这一步的 top-2 就变成专家 2 和 3;但计算 y 时用的权重仍按原始打分来,偏置只影响“选谁”(示意)。

按官方公开数字,DeepSeek-V3 总参数 671B、每 token 激活约 37B;Mixtral 8x7B 总参数约 47B、激活约 13B。MoE 的卖点就是总参数和激活参数之间的这个差距。

容易踩的坑:

  • 常见错误:“每个专家擅长数学、代码等某个领域”。实际观察到的分工多是 token 级、句法级的,不一定对应人类可读的领域。
  • 训练更难:不稳定、通信重,微调时也更容易出现专家退化。
  • 部署门槛高:所有专家都要常驻显存(或做 offload),单卡往往放不下。

可能的追问:

  • 专家数怎么定?由算力、显存、通信拓扑共同决定。趋势是更多、更细的专家,但专家越多,路由越难学,all-to-all 通信越重,需要实验确定。

MoE 参数很多,为什么单 token 推理不一定更贵?

简答:单 token 的计算量只看被激活的参数,所以 FLOPs 远小于同等总参数的 dense 模型。但显存要装下全部专家,大 batch 时几乎所有专家都会被用到,还多了路由和通信开销。所以它“不一定更贵”,也“不一定更便宜”。

细说:粗略估计,前向时每个 token 的计算量约为激活参数量的 2 倍:

FLOPstoken≈2Nactive\text{FLOPs}_{\text{token}}\approx 2N_{\text{active}}
  • FLOPstoken\text{FLOPs}_{\text{token}}:生成或处理一个 token 需要的浮点运算次数。
  • NactiveN_{\text{active}}:这个 token 实际经过的参数个数。dense 模型就是全部参数;MoE 是共享部分加上被选中的专家。
  • 系数 2:每个参数在矩阵乘里参与一次乘法和一次加法。这里没算注意力里和上下文长度相关的那部分,短上下文时它占比不大。

代入官方公开的参数量:DeepSeek-V3 每 token 激活约 37B,计算量约 2 × 37B = 74 GFLOPs;假如有个 671B 的 dense 模型,每 token 要 2 × 671B = 1342 GFLOPs,约是前者的 18 倍。Mixtral 8x7B 激活约 13B,约 26 GFLOPs,和一个 13B dense 模型差不多。

显存是另一笔账。671B 参数就算每个只占 1 字节(8 bit),权重也要约 671 GB,这要按全部参数算,不能按 37B 算。Mixtral 的 47B 参数用 FP16(每个 2 字节)存,约 94 GB,一张 80 GB 的卡装不下。

大 batch 为什么会削弱优势?看一个示意:8 个专家、top-2,假设路由均匀且互相独立。一个 token 不选中某个专家的概率是 6/8 = 0.75。一批 16 个 token 都不选它的概率是 0.7516≈0.0100.75^{16}\approx 0.010,8 个专家里平均只有 0.08 个闲着。所以这一步几乎每个专家的权重都要从显存读一遍,只读一部分权重的省带宽效果基本没了。

成本要分维度看:

维度 由什么决定 MoE 表现
计算(FLOPs) 激活参数 明显省
显存容量 总参数 + KV Cache 不省,门槛更高
访存带宽 每步实际读到的权重 小 batch 时只读被选中专家,省;大 batch 时被激活专家的并集接近全部,优势缩小
通信 专家并行的 all-to-all 额外开销,跨机更明显

所以 MoE 在高并发、多卡、能把专家铺开的服务端最划算。在单卡、低并发的本地部署里,显存门槛常常让它不如效果相近的 dense 模型。

容易踩的坑:别只说“只激活一部分所以快”,面试官想听到计算省、显存不省、通信多这三点。也别把激活参数直接等同于效果:同激活参数下 MoE 通常更强,同总参数下通常不如 dense。

可能的追问:

  • 对 Agent 选型有什么影响?调云 API 时厂商已经把这笔账折进单价,比较单价、延迟和效果即可。自部署时先算显存能否装下全部专家,再看并发量能否把专家“喂饱”。

放到报销案例里:报销助手如果要自部署,选 MoE 还是 dense,由 工程化 站按并发和显卡来算。比如每天只有几百单报销、峰值并发个位数,那张 80 GB 的卡连 Mixtral 的 FP16 权重都放不下,一个 7B dense 模型反而更合适(示意)。本站只需讲清这笔账为什么不能只看参数量。

Scaling Law 讲了什么?涌现能力怎么理解?参数、数据、算力是什么关系?(13)

简答:Scaling Law 是经验规律:其他条件合理时,预训练损失随参数量 N、数据量 D、算力 C 按幂律平滑下降。Chinchilla 的结论是,给定算力时参数和数据应大致同比例扩展。“涌现”指某些任务指标随规模突然跃升,但这种“突然”有一部分来自指标的定义方式。

细说:先看算力怎么估。训练一个 token,前向约 2N 次浮点运算,反向约 4N 次,合起来每 token 约 6N,再乘上 token 数:

C≈6NDC\approx 6ND
  • CC:训练总算力,单位是 FLOPs(浮点运算次数)。
  • NN:模型参数个数;DD:训练用的 token 总数。

代入 Chinchilla 本身的配置,N = 70B = 7×10107\times10^{10},D = 1.4T = 1.4×10121.4\times10^{12}:C ≈ 6 × 7×10¹⁰ × 1.4×10¹² = 5.88×10²³ FLOPs。这个式子让你在“同样一笔算力”下讨论怎么分给 N 和 D:N 翻倍、D 减半,C 不变。它忽略了注意力里随序列长度增长的那部分计算,上下文很长时会低估。

再看损失怎么随 N 和 D 变化。Chinchilla 形式的拟合(Hoffmann et al., 2022):

L(N,D)=E+ANα+BDβL(N,D)=E+\frac{A}{N^{\alpha}}+\frac{B}{D^{\beta}}
  • LL:预训练损失,就是第 2 节的交叉熵,越低越好。
  • EE:不可约损失,模型和数据再大也降不下去的部分,来自语言本身的随机性。
  • ANα\frac{A}{N^{\alpha}}:“模型太小”造成的损失,N 越大越小。NαN^{\alpha} 读作“N 的 α 次方”,α 小于 1,所以 N 翻倍这一项不会减半,只会缩小一点。
  • BDβ\frac{B}{D^{\beta}}:“数据太少”造成的损失,含义同上。
  • A,B,α,βA,B,\alpha,\beta:拟合出来的常数。论文其中一种拟合给出 E = 1.69,A = 406.4,B = 410.7,α = 0.34,β = 0.28。

手算(用上面这组常数,N = 70B,D = 1.4T):

  1. 模型项:N0.34=e0.34ln⁡(7×1010)=e0.34×24.97≈e8.49≈4868N^{0.34}=e^{0.34\ln(7\times10^{10})}=e^{0.34\times 24.97}\approx e^{8.49}\approx 4868,406.4 / 4868 ≈ 0.083。
  2. 数据项:D0.28=e0.28×27.97≈e7.83≈2517D^{0.28}=e^{0.28\times 27.97}\approx e^{7.83}\approx 2517,410.7 / 2517 ≈ 0.163。
  3. L ≈ 1.69 + 0.083 + 0.163 ≈ 1.937。

改一个变量看看:

配置 模型项 数据项 L
70B,1.4T 0.083 0.163 1.937
140B,1.4T(N 翻倍) 0.066 0.163 1.919
70B,2.8T(D 翻倍) 0.083 0.134 1.908
7B,1.4T 0.183 0.163 2.036

N 翻倍,模型项只乘了 2−0.34≈0.792^{-0.34}\approx 0.79;从 70B 缩到 7B,模型项从 0.083 涨到 0.183。损失离 E 越近,再往下压一点要花的算力就越多。注意这里的损失数值只在论文的数据集和 tokenizer 下有意义,不能拿去和别的模型的损失直接比。

论文给出的经验比例大约是每个参数配 20 个左右训练 token 才算“算力最优”,70B 配 1.4T 正好是这个比例。更早的 Kaplan et al., 2020 结论更偏向优先放大参数。

几个延伸:

  • 算力最优不等于部署最优。为了推理便宜,很多开源模型故意用远超这个比例的数据训练较小的模型。比如 Meta 公开说 Llama 3 8B 训练了 15T 以上的 token,约每个参数 1875 个 token,远高于 20,用多花的训练算力换便宜的推理。
  • 涌现(Wei et al., 2022):多步算术、复杂指令跟随等在小模型上接近随机,过某个规模后明显提高。反方观点(Schaeffer et al., 2023)指出,用“完全匹配”这类不连续指标时显得突变,换成连续指标往往是平滑提升。一个示意:答案有 10 个 token,要全对才算分。单 token 正确率 p 平滑地从 0.5 涨到 0.95,完全匹配率 p10p^{10} 却是 0.001、0.107(p = 0.8)、0.349(p = 0.9)、0.599(p = 0.95),前面几乎为零,后面突然窜上来。
  • 新的扩展维度:后训练(指令微调、强化学习)和推理时计算(让模型多思考、多采样再挑选)也表现出“投入越多、效果越好”的趋势,推理模型就是沿这条轴扩展的。

容易踩的坑:

  • 别说成“参数越大越好”。数据质量、配比、去重以及后训练同样决定能力。
  • Scaling Law 预测的是损失,不直接预测某个下游任务的能力,用它估业务效果要谨慎。
  • 对应用岗,要把它转成决策:小模型配好数据、好检索,常常比单纯换大模型更划算。

可能的追问:

  • 高质量数据快用完了怎么办?常见方向是数据重复使用、合成数据、多模态数据、更强的数据筛选,以及把算力转向后训练和推理时计算。这些都还在研究中,没有定论。

能力边界

幻觉从哪里来?知识截止意味着什么?为什么不能全信模型?(13)

简答:模型被训练成“给出高概率的续写”,并没有被训练成“只说有证据的话”。知识缺失、过时或被问到长尾细节时,它照样给出流畅、自信的答案,这就是幻觉。知识截止让它对训练截止日之后的世界一无所知,而且它常常不知道自己不知道。

细说:两种常用分类:

  • 与给定上下文不一致:资料里写 A,模型说 B。比如检索片段写着“市内交通每日上限 80 元”,模型回答“上限 100 元”。
  • 无法被上下文或事实验证:编造出处、条款、API 参数。比如凭空引用“第 4.5 条”,而制度里根本没有这一条(示意)。

幻觉一般由下面几层原因叠加出来:

  1. 训练目标:最大似然奖励“像训练数据”,不奖励“承认不知道”。
  2. 数据:长尾事实出现次数少,参数里只有模糊印象;数据本身也有错误和矛盾。
  3. 知识截止与时效:训练之后的政策、价格、版本都不知道。不注入当前日期,它连“今天”是哪天都不确定。《差旅费管理办法》如果是今年改的版本,模型参数里大概率只有旧版或者根本没有。
  4. 解码与评测激励:采样会把低概率的错误放出来。有分析指出,只按对错打分、不给弃答计分的评测方式会变相鼓励模型去猜(Kalai et al., 2025)。算一笔示意账:某题模型猜对的概率是 30%。只按对错计分时,猜的期望得分是 0.3,弃答是 0,猜更划算;如果答错扣 1 分,猜的期望变成 0.3 × 1 + 0.7 × (−1) = −0.4,弃答的 0 分反而更高。
  5. 上下文问题:证据在中段被忽视、检索到错误片段、指令互相冲突。

治理要对症下药(详见 评测与安全):知识缺失用 RAG 和工具补,时效问题用实时工具,格式错误用约束解码和校验,事实性用引用核对、拒答阈值和人工确认。

容易踩的坑:

  • “能彻底消除幻觉吗?”不能。只能把关键场景的幻觉率压到可接受,并让错误能被发现、有兜底。
  • “上了 RAG 就没有幻觉”不对。模型仍可能不忠于检索内容,或在检索为空时自由发挥,所以要评测忠实度,并为空结果设计拒答。
  • 模型自报的置信度不一定校准良好,不能直接当正确率用。它说“我 90% 确定”,实际对的比例可能只有六七成(示意)。

可能的追问:

  • 怎么让模型“知道自己不知道”?提示里明确允许拒答;在 RAG 里设计“证据不足”的出口;用多次采样的一致性或外部验证器估计不确定性,比如同一问题采样 5 次,金额答案有 3 种,就别直接采信;评测里给弃答单独计分,避免逼模型猜。

放到报销案例里:员工问“打车超标还能报吗”,模型凭参数可能回答“可以,超出部分附说明即可”,听上去合理,但这家公司可能规定超标部分自付或需部门负责人审批(示意)。INV-102 超了 38.5 元,到底怎么处理,只能看第 4.2 条原文。所以主线必须在下一站注入上下文,在第 3 站补上制度引用。

推理能力和记忆能力有什么区别?模型规模和成本怎么权衡?

简答:记忆能力是参数里存了多少事实,推理能力是拿到信息后能否正确组合、多步推导。Agent 设计的基本原则是:事实交给检索和工具,模型负责理解、推理和编排,再按步骤难度给不同环节配不同规模的模型。

细说:拿报销里的一个问题来分。“上海 P6 市内交通每天能报多少”是记忆题,答案在制度里,模型不该凭印象答。“INV-102 金额 118.5 元,标准 80 元,超了多少,要不要走例外审批”是推理题:先算 118.5 − 80 = 38.5,再拿 38.5 去套第 4.2 条里的条件。

维度 记忆(参数知识) 推理
依赖什么 预训练数据覆盖、模型容量 模型规模、后训练、推理时计算(思考链、多次采样)
怎么失败 记错、过时、编造 步骤跳跃、算错、前后矛盾
工程补法 RAG、工具、数据库查询 拆解任务、给示例、结构化中间状态、用工具计算与校验、换更强或推理模型

规模与成本:

  • 大模型记得多、推理强、长指令跟随稳,但单价高、延迟大。
  • 小模型便宜、快,适合分类、抽取、路由、改写等边界清晰的任务。
  • 推理模型在复杂规划、数学、代码上更强,但会消耗大量思考 token(通常按输出计费),延迟高,简单任务上不划算。一句“把这张发票的金额抽出来”,普通模型几十个输出 token 就够,推理模型可能先想几百上千个 token(示意)。
  • 常见做法是分层路由:简单步骤走小模型,关键规划和最终回答走强模型,并用评测集确认路由没有拉低效果。在报销助手里,意图识别和发票字段抽取可以给小模型,判断是否符合例外条款、组织最终答复给强模型。

容易踩的坑:

  • 别用“模型能背出制度”证明它懂制度。私有知识本来就不该靠参数记,即使微调进去,也难以更新、无法引用、不便撤回。制度明年改成 90 元,微调过的模型还记着 80。
  • 模型内部并没有“推理模块”和“记忆模块”之分,两者纠缠在一起。看似推理出的正确答案,可能只是记住了相似题,这也是评测要防数据污染的原因。

可能的追问:

  • 什么时候值得上推理模型?任务需要多步规划、出错代价高、延迟预算又允许时。实时客服类的首轮回复,通常先用普通模型,需要时再升级。

放到报销案例里:判断“超标多少、是否符合例外条款”,要把条款和发票金额组合起来,这是推理。条款和金额本身必须来自 search_policy 和 query_invoices,不能靠模型记。38.5 这个差值最好也让代码算,模型只负责解释。

旁枝:本站和其他站怎么连

模型的能力边界和幻觉治理是什么关系?

简答:能力边界是幻觉治理的起点。先弄清模型凭参数能可靠回答什么、不能回答什么,才能决定哪些环节用检索补、哪些用工具算、哪些必须拒答或交给人。

细说:把任务按“模型自身是否可靠”分三类,括号里是报销助手里对应的例子:

  1. 通用语言能力(改写、摘要、意图识别):模型可以直接做,用评测把关。比如判断“我上周打车那张票能报吗”是在问报销。
  2. 依赖私有或时效知识(公司制度、发票、今天的日期):必须注入证据并要求引用。比如第 4.2 条的 80 元标准,要通过 search_policy 拿到原文。
  3. 精确计算和有副作用的动作(金额比较、建工单):交给确定性工具,模型只负责填参数和解释结果。比如调 create_reimbursement_ticket 生成 R-55821。

这套划分会一路传到后面各站:第 2 站决定注入什么,第 3 站提供证据,第 5 站提供工具,第 6 站验证并兜底。

容易踩的坑:边界不是固定的,换模型、换版本后要用回归评测重新确认。也别把所有事都推给工具,过度工具化会让延迟和失败点成倍增加:一个本来一轮就能答的问题,拆成四次工具调用,每次都可能超时。

可能的追问:

  • 怎么量化边界?准备一批带标准答案的样例,分别测“无检索直答”“有检索”“有检索加工具”三种配置的准确率和拒答率,差值就是每个环节的贡献。比如 100 道报销题,三种配置分别答对 40、75、90 道(示意),检索贡献了 35 道,工具又补了 15 道。做法可参考 Agent 评测:从测试集到发布门槛。

KV Cache、量化和推理吞吐之间是什么关系?

简答:解码阶段通常受显存容量和带宽约束。KV Cache 决定能同时服务多少请求、多长的上下文;量化同时缩小权重和(可选的)KV Cache。两者一起决定吞吐上限和每 token 成本。

细说:

  • 显存预算 ≈ 权重 + 所有并发请求的 KV Cache + 激活与预留。权重固定后,剩余空间除以单请求 KV Cache,大致就是最大并发。
  • 教学算例(沿用第 1.2 节的 7B 配置,示意):一张 80 GB 的卡,FP16 权重约 14 GB,激活和框架预留按 10 GB 算,剩给 KV Cache 的约 56 GB。
配置 单条 8k 请求的 KV 最多并发
MHA,16 bit 4 GiB ≈ 4.29 GB 56 / 4.29 ≈ 13
8 个 KV 头(GQA),16 bit 1 GiB ≈ 1.07 GB 约 52
GQA,KV 量化到 8 bit 约 0.54 GB 约 104
  • 权重量化:从 16 bit 降到 8 或 4 bit,显存和每步读取的字节都减少,解码更快。7B 权重用 4 bit 存约 3.5 GB,比 FP16 又空出 10 GB 多给 KV Cache。代价是精度损失,在长链推理、数学和工具参数精确性上可能更明显,必须用业务评测验证。比如把 118.5 读成 115.8,模型输出依然通顺,但金额错了。
  • 连续批处理、分页式 KV Cache 管理把显存利用率拉高,是吞吐提升的主要工程手段。

容易踩的坑:吞吐和单请求延迟要分开谈。用户感知的是 P95 延迟,成本关心的是每千次请求花多少钱,两者的最优配置不同:上表最后一行并发最高,但每个请求分到的带宽也最少,单条可能更慢。具体选型和压测放在 工程化。

可能的追问:

  • 为什么一个超长上下文请求会拖慢别人?它的 KV Cache 大、prefill 长,会挤占同批的显存和计算。按上表 MHA 配置,一条 64k 的请求要 32 GiB,一个人就占掉一半以上的 KV 空间。常用分块 prefill、按长度分桶或单独队列来隔离。

上下文窗口已经很大了,还需要上下文工程和 RAG 吗?

简答:需要。窗口大小和位置编码只决定“最多能放多少”。放进去能否用好、花多少钱、能否引用和更新、有没有越权,要靠上下文工程和 RAG。

细说:先算一笔示意账。整本差旅制度加附件约 15 万 token,和问题相关的只有第 4.2 条及前后两条,约 1500 token。每轮都塞整本,输入 token 是后者的 100 倍,prefill 时间也跟着涨,第 4.2 条还落在一大段文字的中间。

只靠长窗口 配合检索与上下文管理
每轮都为全部输入 token 付费,TTFT 随长度增长 只放相关片段,成本和延迟可控
中段信息利用率下降,噪声稀释注意力 证据经过重排,放在显眼位置
难以做权限过滤,不该看的也塞了进去 检索时按用户权限过滤
资料更新要重新拼接,难以定位来源 带文档 id 和条款号,可引用、可追溯

长窗口适合的场景:单份长文档的整体理解、大范围阅读代码、检索召回不确定时的宽松兜底。即便如此,前缀缓存和结构化组织仍然重要。

容易踩的坑:反过来也别走极端。为了省 token 把上下文切得太碎,模型会缺少必要的前后文,比如只给了“上限 80 元”这一句,却漏了上一句“以下标准适用于 P5 至 P7”。合理做法是检索相关单元,同时保留必要的上级上下文,细节见 RAG 和 RAG 实验室:把检索质量算清楚。

可能的追问:

  • 一轮到底放多少?先定 token 预算(窗口、成本、延迟三者取最紧的那个),再按优先级填充:系统指令和工具定义、当前任务与关键状态、证据片段、历史摘要。这是下一站 Prompt / Context 的主题。

放到报销案例里:把整本差旅制度和全部发票塞进一个大窗口当然“放得下”,但又贵又慢,还可能让模型看到别人的发票。本站把这个约束交给第 2 站去取舍。

连接

本站是 主线 的起点:这里给出的能力边界、token 预算和解码约束,交给下一站 Prompt / Context 决定这一轮往上下文里放什么。工程化 里的显存、KV Cache、量化,沿用这里的估算方法;RAG 则是在模型能力边界之外补知识;幻觉的发现与兜底在 评测与安全。

这是一篇可以继续生长的笔记。沿着关系继续阅读 ↗