LLM 基础
本文总结从一次前向传播讲起:Attention 怎么算、KV Cache 占多少显存、温度和 top-p 怎么调、模型会在哪里编,每个公式都配了手算例子。
主线第 1 站 · 上一站:起点 · 下一站:Prompt / Context · 全程:主线
本站在主线里回答什么:员工发来一句「我上周出差打车超标了,还能报吗?能的话帮我提报销工单。」在检索、调接口之前,我们先要弄清楚一件事:只靠模型本身能走多远。模型在预训练里学到的是“报销一般要发票、超标一般要写说明”这类常识。它不知道这家公司市内打车的标准是 80 元,不知道这位上海 P6 员工上周有一张 118.5 元的发票 INV-102,不知道“上周”是哪几天(除非把今天的日期告诉它),更不可能自己去建工单。直接让它回答,它很可能顺口说一句“可以报,附上情况说明即可”。这句话听着像真的,背后却没有任何证据。这种回答就是幻觉。
本站没有上游输入,接收的就是这句原始请求。交给下一站的是一份能力边界清单:哪些信息必须由上下文注入(日期、身份、制度条款、发票记录),窗口和 token 预算有多大,关键证据该放在什么位置,解码参数怎么设才能让后面工具参数的 JSON 稳定。下一站 Prompt / Context 据此决定这一轮往上下文里装什么。
这一站考什么
面试里的 LLM 基础题,很少要你复述论文。面试官想确认三件事:
- 一次前向传播里,每个张量是什么形状、在做什么;
- 结构上的选择(头数、位置编码、MoE)怎样影响上下文长度、显存、延迟和幻觉;
- 你能把这些影响落回 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 的新表示。单头注意力写成:
| 符号 | 含义 | 形状(单头,n 个 token) |
|---|---|---|
| query 矩阵、key 矩阵,每行是一个 token 的向量 | ||
| value 矩阵 | ||
| K 的转置,读作“K 转置”:行和列互换 | ||
| 矩阵乘法,第 i 行第 j 列是第 i 个 query 和第 j 个 key 的点积 | ||
| 每个 query / key 向量的长度(维数) | 一个数 | |
| 掩码:允许看的位置填 0,不允许看的填 (负无穷) | ||
| softmax | 对一行数逐个取指数再除以总和,结果全为正、加起来等于 1 | 按行做 |
两个向量的点积是对应位置相乘再相加:。这里 读作“对 i 从 1 到 d_k 求和”。点积越大,说明两个向量方向越一致,query 和 key 越匹配。softmax 对一行分数 的计算是 ,其中 , 也写成 。
手算一遍(示意数字)。三个 token “打车”“超标”“能报”,,。我们只算最后一个 token“能报”的输出,它的 query 是 :
| token | key | value |
|---|---|---|
| 打车 | [2, 1, 1, 0] | [10, 0] |
| 超标 | [1, 0, 1, 0] | [0, 10] |
| 能报 | [1, −1, 0, 0] | [4, 4] |
- 点积:,,。
- 缩放:,三个分数变成 2、1、0。
- 掩码:“能报”是最后一个 token,前面都能看,全部加 0,分数不变。
- softmax:,,,总和约 11.107。权重依次是 7.389/11.107 ≈ 0.665、2.718/11.107 ≈ 0.245、1/11.107 ≈ 0.090,加起来是 1。
- 加权求和:。
“能报”这个位置的新表示,大约三分之二的信息来自“打车”,四分之一来自“超标”。
第 2 步和第 3 步各有用处。除以 是为了防止维度大时分数过大、softmax 被推到只剩一个赢家(见下文「为什么要除以 √d_k?」)。M 在 Decoder 里是因果掩码,保证第 t 个 token 看不到 t 之后的内容,下文「扩展」一节有一个 3×3 的例子。
为什么要过一道 softmax,而不是直接拿点积当权重?点积可正可负,也不归一,直接加权时输出的大小会随序列长度和分数尺度乱跳。softmax 保证权重非负、和为 1,输出永远是各个 V 的加权平均。它的副作用是权重总和必须等于 1:哪怕一行里没有一个 token 真正相关,权重也得分出去,后文的 attention sink 跟这一点有关。
复杂度是 ,读作“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 的大小可以这样估:
| 符号 | 含义 | 7B 示例取值 |
|---|---|---|
| 2 | K 和 V 各存一份 | 2 |
| 层数,每层都有自己的 K/V | 32 | |
| KV 头数(MHA 等于 query 头数,GQA/MQA 更少) | 32 | |
| 每个头的维度 | 128 | |
| 缓存了多少个 token(prompt 加已生成部分) | 8192 | |
| 同时服务的序列数(batch / 并发) | 1 | |
| 每个数占几个字节,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 量化或滑动窗口时要按实际结构重算,而且总显存还要加上权重和框架预留。
几种注意力的区别都落在 上。以 32 个 query 头为例:
- MHA:32 个头各有一份 K/V,,效果稳,KV Cache 最大;
- GQA:比如分 8 组,每 4 个 query 头共用一份 K/V,,缓存降到 1/4,是现在的主流折中;
- MQA:32 个头共用 1 份 K/V,,缓存只有 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”。翻译可以写成“把下面这句译成英文:……”,问答可以写成“问:……答:”,写代码可以写成“补全这个函数”,最后都变成续写,所以一个模型能做很多任务。
模型猜得好不好,用交叉熵损失衡量:
- 是第 t 个位置上真实出现的 token, 是它前面的全部 token。
- 是条件概率,读作“在已知 的条件下, 出现的概率”,这里就是模型给正确答案打的概率。
- 是自然对数 ln。概率在 0 到 1 之间,取对数后是负数,前面加负号让损失变成正数:概率越接近 1,损失越接近 0;概率越小,损失越大。
- 是参与计算的 token 数, 就是取平均。
困惑度(perplexity)是把这个平均损失还原成“相当于在几个选项里瞎猜”:
手算:一个句子有 3 个位置,模型给正确 token 的概率分别是 0.5、0.25、0.125。对应的 是 0.693、1.386、2.079,平均为 1.386,所以 ,。可以理解为:平均每一步,模型像是在 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 的输入保持在合理尺度。不缩放时,点积的方差随 线性增大,softmax 会接近 one-hot(一个位置拿走几乎全部权重),梯度变得极小,训练不稳。
细说:先看点积为什么会“越加越大”。假设 q、k 的每个分量相互独立、均值 0、方差 1(初始化时大致如此),那么
- 是方差,衡量一个随机量围绕平均值晃得有多厉害,定义是 。 读作“期望”,就是按概率加权的平均值。
- 每一项 的均值是 0,方差是 。
- 独立的量相加,方差也相加。 项加起来,方差就是 ,标准差(方差开根号)是 。
最小的手算:,每个分量等概率取 +1 或 −1。于是每个乘积 也是等概率的 +1 或 −1,两项之和只有四种等可能结果:2、0、0、−2。平均值是 0,方差是 (4 + 0 + 0 + 4) / 4 = 2,正好等于 。换到 ,点积的标准差约是 ,分数随手就能差出十几。除以 后,方差回到 1。
方差大为什么有害?softmax 对输入的差值是指数敏感的。最大的那个分数比其他分数大很多时,输出接近 one-hot,这时 softmax 的雅可比矩阵(输出对输入的偏导数表)
几乎全是 0,梯度传不回 Q/K 的投影矩阵。
- 是 softmax 的第 j 个输入分数, 是第 i 个输出概率。
- 是偏导数,读作“ 变动一点点时, 跟着变多少”。梯度就是由这些偏导数组成的,训练时参数按梯度调整,梯度接近 0 就等于学不动。
- 是克罗内克记号: 时为 1,否则为 0。所以对角线上是 ,非对角线上是 。
代两组数。两个候选、softmax 输出 时,,,梯度很健康。输出变成 时,,小了一万多倍。 接近 0 或 1 时乘积都接近 0,这种情况叫饱和。
容易踩的坑:
- 缩放不是为了防数值溢出。溢出靠 softmax 实现里先减去最大值的技巧解决,缩放解决的是饱和和梯度。
- 建立在“分量独立同分布”的初始化假设上,相当于一个固定温度。也有实现把缩放折进参数初始化,或先对 Q/K 做归一化(QK-Norm)再控制尺度。
- 加性注意力对尺度没那么敏感,但点积注意力可以直接用高度优化的矩阵乘法,这是原论文选点积再加缩放的原因。
可能的追问:
- 为什么除以 而不是 ?目标是让标准差回到 1,而标准差是 。除以 会把分数再压小 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 延迟帮助有限。
容易踩的坑:
- 权重只省了 、 的一部分。按 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)。
细说:通常的解释是几种因素叠加:
- 训练数据的位置偏置:文档里重要信息常在开头(标题、摘要)和结尾(结论、紧挨着问题的地方),模型学到了这种偏好。
- 注意力的结构偏置:因果注意力里,开头的 token 会被后面所有位置看到,容易形成“attention sink”吸走注意力;末尾的 token 离生成位置近,占了近因的便宜。RoPE 的远程衰减也让远处内容更难被精确关注。
- 有效长度小于标称长度:训练时长样本少,外推区间的注意力分布会漂移。“大海捞针”能找到藏在长文里的单个句子,不代表多跳推理、聚合统计也能做好,RULER 一类基准专门测这种差距。
- 噪声稀释:塞进来的无关片段越多,相关片段分到的注意力越少。回想 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,经相似度、掩码与归一化后聚合信息 · 点击放大
1.1 节直接给了 Q、K、V 的数值。它们是从哪来的?同一个输入矩阵 X 分别乘以三个可学习的矩阵:
X 的每一行是一个 token 的隐状态,形状 。、 的形状是 , 是 ,训练时学出来,推理时固定。乘完以后,Q、K 是 ,V 是 。
手算一个 token(示意):d = 2,某个 token 的隐状态 ,取
那么 ,。同一个 token,乘了不同的矩阵,得到的 q 和 k 就不一样。如果让 ,每个 token 的 q 和 k 会完全相同,打分矩阵变成对称的,“我关注你”和“你关注我”就被强行绑在一起了。
完整的注意力公式和 1.1 相同:
符号同 1.1 节的表。相似度矩阵 是 ,第 i 行表示第 i 个 token 对每个 token 的打分,softmax 沿每一行(被关注的 token 那一维)归一化。因果掩码 M 对未来位置加负无穷,对允许的位置加 0。3 个 token 时它长这样:
第 1 行只有第 1 列是 0:第 1 个 token 只能看自己。第 3 行全是 0:最后一个 token 能看所有人。,所以加了负无穷的位置在 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 的显存估算换成紧凑写法:
符号同 1.2 节的表:L 为层数, 为 KV 头数, 为头维度,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 头,只有 从 32 变成 8,结果按比例变成 1 GiB。再把 s 从 2 换成 1(KV 用 8 bit 存),又减半到 0.5 GiB。
这个式子对每个因子都是线性的,所以哪个因子翻倍,显存就翻倍,调参时很好推。它只估标准未压缩的 KV,不包含权重、激活和分配开销;MLA 的潜变量缓存要按它的实际结构计算(见下文 MLA 一题)。另外注意 GiB 和 GB 的区别:1 GiB = 字节,1 GB = 字节,显卡标称容量通常按 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 加权求和,得到每个位置“融合了上下文”的新表示。
细说:先把形状写清(单头):
- 读作“属于”。 表示“n 行 d 列、每个元素都是实数的矩阵”。所以第一项是说:X 是一个 n 行 d 列的矩阵。
- n 是 token 数,d 是模型隐藏维度,、 是单头里 Q/K 和 V 的维度。
- 代入 1.1 节的例子:n = 3,,;真实的 7B 模型里常见 d = 4096、,n 就是上下文长度。
这一行只是在约定形状,没有计算,但它决定了后面每一步能不能乘得起来: 是 乘 ,中间的 d 对上了,结果是 。面试时把形状说清楚,比背公式更能看出你是否真懂。
计算分六步:
- 投影:Q = XW_Q,K = XW_K,V = XW_V(手算见上文「扩展」一节);
- 打分:S = QKᵀ/√d_k,得到 n×n 矩阵,S[i][j] 表示位置 i 对位置 j 的关注程度;
- 掩码:Decoder 中对 j > i 的位置加 −∞,padding 位置同样屏蔽;
- 归一化:每一行做 softmax,行和为 1;
- 聚合:输出 = softmax(S)·V,形状 n×d_v;
- 多头时把各头输出拼接,再乘 W_O 投回 d 维。
把 1.1 的例子补全成三行(示意)。三个 token 的 key 不变,再给前两个 token 配上 query:,,。
| 行(谁在看) | 缩放后分数 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] |
第一行:、、,除以 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ᵀ 不再被迫对称。上面的例子里,“能报”对“打车”的点积是 ,反过来“打车”对“能报”是 ,“我关注你”不等于“你关注我”。
- 同一个 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,注意力在训练初期就过早“锁死”。
细说:教学算例(非实测):,点积的标准差约 。设某行只有两个候选,“标准化后”的分数差为 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 |
最后一列就是上文雅可比矩阵的对角元 ,梯度大致按它缩放:0.20 对 0.00001,差了四个数量级左右(精确算是约 1.6 万倍)。
也可以把缩放看成一个固定温度的 softmax:
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%。这里 只有 4,差距已经很明显; 时会极端得多。
容易踩的坑:
- 这个因子依赖“初始化时分量近似独立、方差为 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):
- 是第 t 个 token 在这一层的隐状态,d 维。
- 是下投影矩阵(D 表示 down),把 压成一个短得多的潜向量 ,长度记作 。
- 、 是上投影矩阵(U 表示 up),用到时再从 还原出各个头的 key 和 value。
缓存里只放 。按 DeepSeek-V2 论文的配置,128 个头、每头 128 维,MHA 每层每 token 要存 2 × 128 × 128 = 32768 个数;MLA 只存 维的潜向量,加上 64 维的 RoPE 分量,共 576 个数,约为前者的 1/57。
只存压缩版,用的时候还要不要每步把历史 K 解压出来?不用。打分可以改写成 ,先把上投影矩阵乘到 query 上,再直接和潜向量做点积。手算一个最小例子:取 ,,,。老老实实解压:,。先吸收:,再乘 ,也是 9。结果一样,但第二种做法里历史 token 只需要 。V 侧同理,可以吸收进输出投影。RoPE 的旋转矩阵随位置变化,夹在 q 和 中间时就没法预先乘好,所以 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 门控:
| 符号 | 含义 | 形状 |
|---|---|---|
| 一个 token 的隐状态 | d 维 | |
| 两个升维矩阵,一个走激活函数,一个当“门” | ||
| 降维矩阵,把结果投回 d 维 | ||
| 激活函数 ,其中 是 sigmoid,输出在 0 到 1 之间 | 逐元素 | |
| 逐元素相乘:两个等长向量对应位置相乘,不求和 | 逐元素 |
手算(示意):d = 1,。取 ,,,。
- 。SiLU(2) = 2 × σ(2) ≈ 2 × 0.881 = 1.762;SiLU(−2) = −2 × σ(−2) ≈ −2 × 0.119 = −0.238。
- 。
- 逐元素相乘:[1.762 × 4, −0.238 × 2] ≈ [7.046, −0.477]。
- 把两项加起来:7.046 − 0.477 ≈ 6.570。
这一路像一个音量旋钮,决定 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:
- 是向量 x 各分量的平均值, 是各分量的方差,两者都只在这一个 token 的 d 个数里算。
- 是一个很小的数(比如 ),防止分母为 0。
- 、 是可学习的逐维缩放和平移,初始化为 1 和 0。
- 叫均方根(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 组旋转 弧度,频率 ,i 从 0 数到 d/2 − 1,b 叫 base,常见取 10000,近年的模型常调得更大。一组二维坐标的旋转写成矩阵是:
把一个点 [x, y] 左乘 ,就是绕原点逆时针转 ,长度不变。
先看频率有多大差别。d = 128、b = 10000 时:第 0 组 ,每往后一个位置转 1 弧度(约 57°),大约 6.3 个位置转满一圈;第 32 组 ,要约 628 个位置才转一圈;最后一组 ,转一圈要约 5.4 万个位置。前面的组转得快,适合分辨“隔壁和隔两个”;后面的组转得慢,适合分辨“几百和几千”的远近。
旋转矩阵有个性质:,先倒转 m 再正转 n,等于直接转 n − m。所以
是内积,就是前文的点积 。左边是“位置 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:, 转 180° 得 [−2, −1],点积 = 4 − 1 = 3。
- q 在位置 3、k 在位置 4: 转 270° 得 [2, −1], 转 360° 回到 [2, 1],点积 = 4 − 1 = 3。
- 直接算右边:,。
两对 token 的绝对位置不同,但都相隔 1,分数都是 3。把间隔换成 2(q 在 1、k 在 3):,和 的点积是 −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 那题是同一个形式:
是模型给词表里第 i 个 token 的原始打分(logit),T 是温度, 是调整后选中第 i 个 token 的概率,分母对整个词表求和,保证所有 加起来是 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 那一行:,,,总和 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 的解码时间决定。两者瓶颈不同,优化手段也不同。
细说:一次请求的时间可以粗拆为
- :请求在服务端排队等 GPU 的时间;
- :一次性处理整个 prompt、建好 KV Cache 并产出第 1 个 token 的时间;
- :要生成的 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 计算量”拆开。难点在路由均衡、训练稳定和专家并行的通信。
细说:一种常见的路由形式:
- 是当前 token 的隐状态, 是路由矩阵,形状是“专家数 × d”, 给每个专家打一个分。
- 是 softmax 之后的路由概率,每个专家一个数,加起来是 1。
- 是 g 里最大的 k 个元素的下标集合, 表示只对这 k 个专家求和,其余专家根本不计算。
- 是第 i 个专家(一个独立的 FFN)对 x 的输出, 是 top-k 内重新归一化后的权重。各模型细节不同,有的不重归一化,有的改用 sigmoid 打分。
手算(示意):4 个专家,k = 2,路由打分 。
- softmax:,,,,总和约 12.124,得 g ≈ (0.609, 0.224, 0.136, 0.030)。
- Top-2 是专家 1 和专家 2。
- 重新归一化:0.609 / (0.609 + 0.224) ≈ 0.731,0.224 / 0.833 ≈ 0.269。
- 假设两个专家的输出是 、,则 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 倍:
- :生成或处理一个 token 需要的浮点运算次数。
- :这个 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 都不选它的概率是 ,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 数:
- :训练总算力,单位是 FLOPs(浮点运算次数)。
- :模型参数个数;:训练用的 token 总数。
代入 Chinchilla 本身的配置,N = 70B = ,D = 1.4T = :C ≈ 6 × 7×10¹⁰ × 1.4×10¹² = 5.88×10²³ FLOPs。这个式子让你在“同样一笔算力”下讨论怎么分给 N 和 D:N 翻倍、D 减半,C 不变。它忽略了注意力里随序列长度增长的那部分计算,上下文很长时会低估。
再看损失怎么随 N 和 D 变化。Chinchilla 形式的拟合(Hoffmann et al., 2022):
- :预训练损失,就是第 2 节的交叉熵,越低越好。
- :不可约损失,模型和数据再大也降不下去的部分,来自语言本身的随机性。
- :“模型太小”造成的损失,N 越大越小。 读作“N 的 α 次方”,α 小于 1,所以 N 翻倍这一项不会减半,只会缩小一点。
- :“数据太少”造成的损失,含义同上。
- :拟合出来的常数。论文其中一种拟合给出 E = 1.69,A = 406.4,B = 410.7,α = 0.34,β = 0.28。
手算(用上面这组常数,N = 70B,D = 1.4T):
- 模型项:,406.4 / 4868 ≈ 0.083。
- 数据项:,410.7 / 2517 ≈ 0.163。
- 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 翻倍,模型项只乘了 ;从 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,完全匹配率 却是 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 条”,而制度里根本没有这一条(示意)。
幻觉一般由下面几层原因叠加出来:
- 训练目标:最大似然奖励“像训练数据”,不奖励“承认不知道”。
- 数据:长尾事实出现次数少,参数里只有模糊印象;数据本身也有错误和矛盾。
- 知识截止与时效:训练之后的政策、价格、版本都不知道。不注入当前日期,它连“今天”是哪天都不确定。《差旅费管理办法》如果是今年改的版本,模型参数里大概率只有旧版或者根本没有。
- 解码与评测激励:采样会把低概率的错误放出来。有分析指出,只按对错打分、不给弃答计分的评测方式会变相鼓励模型去猜(Kalai et al., 2025)。算一笔示意账:某题模型猜对的概率是 30%。只按对错计分时,猜的期望得分是 0.3,弃答是 0,猜更划算;如果答错扣 1 分,猜的期望变成 0.3 × 1 + 0.7 × (−1) = −0.4,弃答的 0 分反而更高。
- 上下文问题:证据在中段被忽视、检索到错误片段、指令互相冲突。
治理要对症下药(详见 评测与安全):知识缺失用 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 这个差值最好也让代码算,模型只负责解释。
旁枝:本站和其他站怎么连
模型的能力边界和幻觉治理是什么关系?
简答:能力边界是幻觉治理的起点。先弄清模型凭参数能可靠回答什么、不能回答什么,才能决定哪些环节用检索补、哪些用工具算、哪些必须拒答或交给人。
细说:把任务按“模型自身是否可靠”分三类,括号里是报销助手里对应的例子:
- 通用语言能力(改写、摘要、意图识别):模型可以直接做,用评测把关。比如判断“我上周打车那张票能报吗”是在问报销。
- 依赖私有或时效知识(公司制度、发票、今天的日期):必须注入证据并要求引用。比如第 4.2 条的 80 元标准,要通过
search_policy拿到原文。 - 精确计算和有副作用的动作(金额比较、建工单):交给确定性工具,模型只负责填参数和解释结果。比如调
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 则是在模型能力边界之外补知识;幻觉的发现与兜底在 评测与安全。