第10讲 · 大模型推理:从算术强度到 KV Cache 优化

Stanford CS336《Language Modeling from Scratch》· Spring 2026 · Lecture 10: Inference - 本讲承接上一讲的 scaling laws(由 Tatsu 主讲),暂时离开"怎么训得更划算",转入"训完之后怎么用得起来"

视频地址 https://www.youtube.com/watch?v=EfM546A79aM
视频 ID EfM546A79aM 时长:约 5130 秒(约 85 分钟)

对应讲次:第10讲(Stanford CS336 语言建模)

制作模型:deepseek-flash(vb compose,据 run.log)

说明:本讲大量借用 Google 的 How to Scale Your Model(常被称为"scaling book")中的记号与图示;讲师称其中的插图"慷慨地被借用了"。该书链接见文末。


导览

这一讲老师想回答的核心问题,是他自己在开场用一个句子定下的:"推理这个问题其实非常简单——你训练好了一个模型,给定一个 prompt,你想产出回答,并且希望它尽可能准、尽可能快。"围绕这句话,他依次追问了四个问题,本讲也基本沿着这四个问题展开:

  1. 推理为什么值得单开一讲、为什么比过去更重要?
  2. "快"到底指什么?(他给出的答案是:快的定义本身就不唯一,甚至互相打架。)
  3. 为什么同一个模型,训练跑得动、推理却跑不快?瓶颈精确地落在哪一步?
  4. 既然瓶颈找到了,有哪些手段能在不伤精度的前提下绕过它?

难点在哪。 真正的卡点不在"用什么技巧",而在第三步的定位。讲师反复强调的一件事是:注意力的算术强度里,批维度 $B$ 是一个"被批处理的维度"而不是"被收缩的维度",所以增大 batch 无法提升注意力的算术强度——这一点和 MLP 的行为完全相反。谁没有把这一步想透,后面所有的技巧(GQA、MLA、滑窗、线性注意力……)就会变成一堆彼此无关的名词。第二步同样容易滑过去:延迟与吞吐在 batch 维度上是此消彼长的,很多人的直觉是"让一切变快",而讲师明确说这两者在 batch 上是"完全相反"的方向。

读者可能带着什么疑问进来——这里分两组列出,避免混为一谈:

① 老师真正提出的问题(他在课上自问自答的):

② 编者预判的读者疑问(编者按,非讲师原话):

本讲路线。 先用一段"为什么重要 + 定义快的指标"定调(约 0–7 分钟),然后用记号复习与算术强度推导把瓶颈精确定位(约 8–44 分钟,占了本讲一半篇幅),再进入手段部分:缩 KV cache 的架构改造 → 量化/剪枝/蒸馏 → 投机解码 → 动态负载下的服务系统,最后用一个总结收尾(约 85 分钟)。


一、为什么推理值得单开一讲

(参考时间: 00:05)

1.1 推理的定义

推理的问题陈述非常简单:你训练好了一个模型,给定一个 prompt,要产出回答,并且希望它尽可能准、尽可能快——讲师的用词是 "usually as accurately and as quickly as you can",准确与快速这两个目标本身就已经埋下了后面所有的张力。

推理以这种方式出现在很多地方:

编者拓展|为什么 RL 训练让推理的地位发生变化 传统监督微调(SFT)的每一步都要做一次前向,但前向是"读一批已经写好的 token",可以沿序列并行。而 RLHF / 可验证奖励 RL 里的 rollout 是自回归生成:policy 必须一个 token 一个 token 往外吐,吐完才能拿奖励。于是训练流程里第一次出现了一个"纯粹由推理主导、且无法回避"的环节。这也是为什么近两年像 vLLM、SGLang 这类推理引擎会反过来变成 RL 训练框架的核心组件。

1.2 训练是一次性成本,推理是重复成本

讲师用了一个非常直白的对照来说明量级:

他给了一个具体的数字来建立直觉:OpenAI 被估计每天要产出 8.6 万亿(8.6 trillion)token。作为参照,GPT-4 的训练数据量是 32 万亿 token。也就是说,不到 4 天,OpenAI 光是推理产生的 token 数(以及相应的算力)就至少等于 GPT-4 一整个预训练的规模。而前沿模型即便用了更多 token 来训练,推理侧生成的 token 数依然会更高。

编者说明:8.6 万亿/天、GPT-4 训练 32 万亿 token 这两个数字都是课堂上口头给出的外部估计,讲师没有标注出处。读者若要把它们写进文章,请另行核对来源。

1.3 Agent 时代重构了"够快"的标准

(参考时间: 00:02:40)

这一段是理解"为什么推理的重要性在过去一年还继续上涨"的关键论证,值得完整保留:

从前我们把语言模型主要理解成聊天机器人或助手:输入 prompt,拿回回答,而人类的目标是把这段回答读完。在这个设定下,推理的"快"是有上限的——人读不了那么快,所以超过某个点之后再快也没意义。

但当我们走向 agent 世界,情况变了:一个 query 进去,agent 会做一大堆事——它会思考、会推理、会调用工具、会自我内省,最后才产出一点给人类读的输出。也就是说:

agent 产出的大部分 token 根本不是给人读的。

所以正确的看法是,把生成的 token 数理解成花费的算力。而这件事没有上限:只要问题足够宏大,你就会需要更多的算力、更多的 token。在聊天机器人时代,"足够快"是有天花板的;在 agent 时代,把推理再压榨出一点价值,是没有尽头的。

1.4 商业与开源生态

讲师简要扫过了一圈生态:所有闭源 API 提供方都必须服务自己的模型,所以推理对他们是大生意;此外还有一批厂商在服务开放权重模型。开源社区里则有几类常用工具(正文首次出现处给出官方链接):

1.5 一句话立论

讲师用一句几乎是挑衅式的话给这一讲立论:"如果你能把推理做快一倍,哪怕只快 10%,这都是件大事。"


二、"快"是什么意思:三个指标

(参考时间: 00:05:05)

紧接着的问题是:"快(fast)到底是什么意思?" 讲师指出,有若干指标用来刻画"快"这个概念,它们适用于不同的场景,并且彼此之间存在权衡。

2.1 首 token 延迟(TTFT, Time To First Token)

这是"用户在任何生成发生之前要等多久"。你把一个 query 丢进 ChatGPT,过了若干毫秒,第一个 token 才出现——从发出请求到第一个 token 抵达的这段时间就是 TTFT。

它主要用于交互式应用:因为你干等着的这段延迟越长,体验越差。而一旦 token 开始流入,速度本身反而没那么重要——因为反正你也读不了那么快。

2.2 延迟(Latency)

从单个用户的立场看,一个 query 的 token 出现得有多快。它同样对交互式应用重要。直观地说,这就是"token 流出来的速度"。

2.3 吞吐(Throughput)

多个 query 合计每秒产出多少 token。

2.4 三者关系一览

指标 面向对象 主要适用场景 优化方向
TTFT 单个用户看到第一个 token 之前 交互式应用 缩小 prefill 时间 → 小 batch
Latency 单个用户的 token 速率 交互式应用 缩小单次内存 IO → 小 batch
Throughput 全体 query 的 token 速率 批量处理、离线任务 摊薄参数加载 → 大 batch

核心张力(讲师在后文会反复回到这一点):延迟要小 batch,吞吐要大 batch。


三、为什么推理和训练是两种负载

(参考时间: 00:07:05)

讲师说,这里"也许只需要记住一个高层要点":

训练时你一次看到所有 token;推理时你只能一个接一个地生成。

展开来说:

这就是推理之所以是与训练完全不同的负载的根本原因:你无法沿序列维度并行。而由此带来的直接后果是——很难获得高算术强度,也就很难把算力吃满。这一句话是整讲的题眼,后面所有推导都在给它加固。

graph TD
    A["同一个 Transformer 模型"] --> B["训练负载"]
    A --> C["推理负载"]
    B --> B1["一次看到全部 token"]
    B1 --> B2["序列维度可并行"]
    B2 --> B3["大张量 matmul<br/>高算术强度<br/>compute-bound"]
    C --> C1["必须逐个 token 生成"]
    C1 --> C2["序列维度不可并行"]
    C2 --> C3["薄矩阵 / 向量乘法<br/>低算术强度<br/>memory-bound"]

3.1 本讲路线预告

(参考时间: 00:08:25)

讲师给出的四个环节:

  1. 先做一些数学推导,理解 Transformer 的算术强度、吞吐与延迟该怎么想;
  2. 讲降低开销的技术:把 KV cache 做小、量化、剪枝;
  3. 讲投机解码(speculative decoding);
  4. 最后是一些实践层面的关切。

四、记号系统与算术强度复习

(参考时间: 00:08:55)

讲师说明这一部分"有点像是复习",主要是为了统一记号,并推荐大家去看 Google 的 scaling book——"写得非常好",本讲的不少图也来自那里。

4.1 记号约定

用符号来同时表示维度以及该维度的长度:

符号 含义
$B$ batch 维度,也即序列条数
$T$ 序列维度,也即 token 数(输出侧)
$S$ 序列维度(输入侧 / 已生成的 token 数)
$D$ 模型维度(model dimension)
$H$ 每个注意力头的维度(head dimension)
$N$ query 头数量
$K$ KV 头的数量 / 组数(见下方记号更正)
$G$ 每组内的 query 头数
$F$ MLP 的中间(上投影)维度

在这套记号里,一次张量运算可以这样读:

$$ \underbrace{[B, T, D]}_{\text{激活}} \times \underbrace{[D, H]}_{\text{权重}} \;\longrightarrow\; [B, T, H] $$

编者说明:课上的一处记号更正(重要) 讲到注意力这一步时,一位同学当场指出幻灯片上的记号问题。讲师确认后更正为:$K$ 应当是"组的数量(number of groups)",而 $G$ 是"每个组里包含多少个 query 头"。讲师说"我之后改一下"。这与后面的 GQA 讨论一致:GQA 保留 $N$ 个 query 头,但只保留 $K$ 组 key/value,每组内对应 $G$ 个 query。读者若对照原幻灯片发现记号对不上,以课上更正为准。

4.2 Transformer block:用张量形状写出来

讲师说,这一页"看起来像电路图、细节很多",但在他看来这大概是对 Transformer 最清晰的一种定义——因为你看到的不只是模糊的描述,而是每个张量的精确形状,以及由此可以推理出的依赖关系。

一个 block 的结构是:取一层的激活 $X$,先过注意力,再过 MLP。

注意力部分:

注意看那个蓝色的 $B$:讲师特意让人"先记住这一点"。正是这个 $B$ 只批不收缩的性质,让推理变难,也正是注意力成为瓶颈的原因。讲师说"我们稍后会回到为什么这个 $B$ 同时出现会让推理很难"。

MLP 部分就简单了:一堆 matmul,包括门控矩阵、上投影、下投影。

按惯例:

4.3 算术强度:从最基础的 matmul 开始

(参考时间: 00:14:25)

热身例子:一个 $B \times D$ 的矩阵乘一个 $D \times F$ 的矩阵。在 NLP 语境里,$B$ 是 batch,$D$ 是隐藏维度/模型维度,$F$ 是上投影维度。

算术强度的定义是:

$$ \text{算术强度} = \frac{\text{FLOPs}}{\text{传输的字节数}} $$

这个量我们希望它越高越好。

要读入 / 写出的内容(BF16,推理时的常规精度):

计算量:

$$ \text{FLOPs} = 2BDF $$

注意这里的结构:读写是"类似二次"的项,matmul 是"三次"的项。这正是高算术强度的来源。

于是:

$$ I = \frac{2BDF}{2BD + 2DF + 2BF} = \frac{BDF}{BD + DF + BF} $$

关键简化:假设 $B \ll D, F$。形式上令 $D = C \cdot B$、$F = C \cdot B$,再让 $C \to \infty$,即 $D, F$ 都很大而 $B$ 相对很小。代入后分母里主导项是 $DF$,于是:

$$ I \;\longrightarrow\; \frac{BDF}{DF} = B $$

结论:算术强度约等于 $B$。 这和之前讲矩阵乘法时得到的"$I \approx N/3$"是同一个道理,只不过现在矩阵不是方的。

4.4 计算受限还是内存受限:和硬件比一比

(参考时间: 00:17:15)

加速器的算术强度这样算:看规格表上的每秒 FLOPs,除以 HBM 的显存带宽。

$$ I_{\text{acc}} = \frac{\text{峰值 FLOPs/s}}{\text{内存带宽 (bytes/s)}} $$

然后把你的计算强度和加速器强度比:

graph TD
    A["算子算术强度<br/>I = FLOPs / Bytes"] --> B{"I 是否大于<br/>加速器强度 I_acc ?"}
    B -- 是 --> C["<b>compute-bound</b><br/>计算受限<br/>算力被打满 → 好"]
    B -- 否 --> D["<b>memory-bound</b><br/>内存受限<br/>算力闲置 → 坏"]

对 H100 而言,对这类 matmul 运算,batch size 大约要超过 295 才会进入计算受限。

极端情况:如果你只有一个样本($B = 1$),算术强度就是 1,而 1 远小于 295,于是严重内存受限。原因也很直观:你依然要把整个 $D \times F$ 的权重矩阵从 HBM 读进来,但 $B$ 只有 1,所以你实际上只做了 $2DF$ 这么点 FLOPs。

讲师在这里点出了推理的本质特征:"这基本上就是你在推理里会看到的工作负载。你拿不到完整的大矩阵,你拿到的是非常薄的矩阵或张量。"

编者说明:$295 \approx 989\,\text{TFLOP/s} \;/\; 3.35\,\text{TB/s}$。也就是说,每从显存搬 1 字节,需要做约 295 次浮点运算,才能让 H100 的 Tensor Core 不闲着。这个数字是理解后面所有"内存受限"论断的锚点,建议记住它的量级(几百量级)。


五、KV Cache:推理优化的第一性原理

(参考时间: 00:20:30)

5.1 最朴素的做法,以及它为什么很糟

设 prompt 是 "Never going to give you"(讲师用的这个著名歌词前缀)。最朴素的做法是:

  1. 把 prompt 送进 Transformer;
  2. Transformer 生成 keys、values 和激活,最终产出输出词表上的 logits 分布;
  3. 从分布中采样一个 token;
  4. 把采样的 token 拼回 prompt,重复上述过程(先生成 "never",再拼回去,再生成……)。
graph TD
    A["输入 prompt"] --> B["Transformer 前向"]
    B --> C["输出词表 logits"]
    C --> D["采样一个 token"]
    D --> E["拼回 prompt"]
    E --> B

这个方法可行——因为一个 Transformer 本质上就是"吃一个序列、吐一个 token 分布的黑盒",你当然可以反复施加它。

但它非常糟糕:每生成一个 token,你实际花费的是 $O(T^2)$ 的时间($T$ 是到目前为止已生成的 token 数,因为注意力的计算量是 $T^2$)。于是生成 $T$ 个 token 的总代价是 $T^3$。

$$ \underbrace{O(T^2)}_{\text{单步注意力}} \times \underbrace{T}_{\text{步数}} = O(T^3) $$

5.2 关键观察:很多工作在 prefix 之间是共享的

观察:其实你不需要这样做。大量工作可以在不同前缀之间共享。

比如你在生成 "never" 和你在生成 "up" 时,"never going to give" 这些 token 对应的 key/value 其实是同一批计算。这些 token 的激活不应该变化。

为什么?因为这是因果(causal)Transformer:

有了这个观察,第一件显然的事就是:把 KV cache 存到 HBM 里,这样在相邻的两次 token 生成之间,可以直接复用。

5.3 两阶段:prefill 与 generation

有了 KV cache 之后,推理分成两个阶段:

阶段 输入 是否可并行 输出
Prefill(预填充) 给定 prompt 可以,和训练一样(你一次看到整个 prompt) 填充 KV cache;产出第一个 token 的 logits
Generation(生成) 单个新 token 不可以,逐个生成 新的 KV 项 + 下一个 token 的分布

具体流程是:prompt 进来,先把 KV cache 填充好(这就是那批 key-value 对),同时算出 logits,采样得到 "never";"never" 再喂进 Transformer,这次用上缓存,产出下一个 token 的分布以及一个新的 KV 项,追加到缓存里;如此往复。

关键的收益是:你再也不需要为"已经看过的那些 token"重新计算 KV cache。

带 KV Cache 的 prefill / generation 两阶段流程

📷 带 KV Cache 的 prefill / generation 两阶段流程
回到原视频 (00:23:20)

5.4 KV cache 的形状

形式上,KV cache 为:

5.5 MLP 的 FLOPs 与内存 IO

(参考时间: 00:25:30)

讲师先说明一个贯穿全程的简化:只盯着 matmul 看,因为 matmul 才是真正花工作量的部分;其余算子要么 FLOPs 很少,要么可以融合进 matmul。

MLP 这一层的流程是:读 $X$ → 读入全部参数 → 算上投影 → 写回 HBM → 算门控 → 写回 HBM → 算下投影 → 写回 HBM。

由此得到算术强度,再套用前面那个"$B \cdot T \ll D, F$"的假设,得到:

$$ I_{\text{MLP}} \;\approx\; B \cdot T $$

这在结构上就是矩阵乘法那套的类比——无非是矩阵更多、维度更多,本质上是一回事。这也非常好理解:MLP 本来就是一个大 matmul。

更重要的是这句话:

在 MLP 里,batch 维度和序列长度维度下所有东西都是独立的,它们之间不交互。

所以 MLP 这边是安全的:只要在 prefill 时把 $B \cdot T$ 做得够大(大 batch、长序列),你就能维持较高的算术强度。

5.6 注意力的 FLOPs 与内存 IO —— 问题出在这里

(参考时间: 00:29:20)

注意力层同样先看 matmul:

这仍然是 matmul,所以FLOPs 的阶数应当比传输字节数高一级,唯一的问题是系数长什么样。讲师给出的注意力系数是:

$$ \boxed{\ \frac{S \cdot T}{S + T}\ } $$

于是:

场景 代入 算术强度 判定
Prefill $T = S$ $\dfrac{S \cdot S}{2S} = \dfrac{S}{2}$ 良好。只要序列够长,注意力强度就高
Generation $T = 1$ $\dfrac{S \cdot 1}{S+1} \approx 1$ 灾难。小于 1

注意一个细节:在这个表达式里,批维度 $B$ 根本没有出现。讲师说这一点稍后解释,但它正是关键。

讲师对生成阶段的评价非常直接:"这是坏消息。" 因为算术强度约等于 1,而1 是很糟的——我们前面算过,要打满 H100 的算力,需要 295 这个量级。

"我们辛辛苦苦把 MLP、注意力、prefill、generation 都算了一遍,结果发现这里是个瓶颈。"

5.7 为什么注意力成为瓶颈:一个直观解释

(参考时间: 00:31:45)

讲师对比了 MLP 和注意力:

讲师把这个情形类比回他开场举过的那个例子:那个例子里算术强度也很差,因为它"本质上不是在做一个 matmul,而是在做一个点积"——而点积的算术强度极差。

一句话总结:

注意力里那个蓝色的 $B$(只批、不收缩),正是注意力的算术强度不随 $B$ 增长的原因,也正是它成为瓶颈的原因。

5.8 四种组合的总结表

(参考时间: 00:33:55)

场景 算术强度 判定
Prefill · MLP $B \cdot S$ 很好
Prefill · Attention $S/2$ 不是最好,但可用
Generation · MLP $B$ 可用,但要求并发请求足够多
Generation · Attention $\approx 1$ 根本性瓶颈

讲师的原话是:

"这就是那个瓶颈。你就得接受它——如果你继续用 Transformer,你没法真正改进这一点。"

那么,当你听到有人说"推理是内存受限的"时,你现在知道为什么了。

编者拓展|softmax 为什么可以忽略,以及 FlashAttention 之类的意义 讲师说 softmax "doesn't really matter",是因为 softmax 本身的 FLOPs 只是 $O(B \cdot N \cdot S \cdot T)$ 量级,比 QK^T 和 PV 两个 matmul 低一阶;而且它通常可以和前后算子融合(fuse),不会额外产生一次 HBM 往返。 但要注意,"可以忽略"的前提是不发生中间张量的物化。如果按最朴素的实现,注意力矩阵 $S \times S$ 要被显式写进 HBM 再读回来,那么它反而会吞掉大部分内存带宽——这正是 FlashAttention 系列工作要解决的问题:通过分块(tiling)把注意力矩阵留在 SRAM 上算完,避免 $O(S^2)$ 的 HBM 流量。本讲没有展开这部分,但它和"推理是内存受限的"是同一枚硬币的两面。

5.9 内存受限带来的好处:把时间简化为"搬了多少内存"

(参考时间: 00:35:20)

讲师说,如果推理是内存受限的,那么很多事情反而变简单了:你估计"要花多久"时,只要看需要搬多少内存。

前提是计算与通信可以被重叠(overlap)。在重叠成立时,瓶颈就是你要处理的内存总量。

讲师的原话带着一点自嘲:"从某些角度看这挺好的,因为更简单;但从另一些角度看挺让人沮丧的——你的加速器就坐在那里什么都不干。"

5.10 实例:Llama 2 13B 在 H100 上

(参考时间: 00:36:05)

以 Llama 2 13B 为例。模型的形状参数包括:序列长度、模型维度、前馈维度、query 头数、KV 头数(此处没有 GQA,所以 $N = K$)、头维度、层数、词表大小,再加上 H100 的内存带宽。

Llama 2 13B 在 H100 上的配置与推导

📷 Llama 2 13B 在 H100 上的配置与推导
回到原视频 (00:37:00)

第一步:参数量。 把 embedding、MLP 层、KQV 投影都加起来,得到一个参数量。自检结果约为 130 亿——和"13B 模型"的宣称一致。Llama 2 在推理时用 BF16,所以参数占的字节数是这个数乘 2。

第二步:KV cache。 KV cache 的大小是:

$$ \text{KV} = \underbrace{2}_{K,\,V} \times \underbrace{2}_{\text{BF16 字节}} \times B \times S \times K \times H \times L $$

第三步:总内存。

$$ \text{Memory} = \underbrace{2P}_{\text{参数(BF16)}} + B \cdot \underbrace{\text{KV}_{\text{per-seq}}}_{\text{每序列 KV cache}} $$

在课堂上这套配置下,每序列的 KV cache 约合 8.38 亿字节量级,所以总内存基本上是 $B$ 的线性函数再加上一项常数。

第四步:延迟与吞吐。

所以延迟与内存在形式上完全相同,吞吐则是"$B$ 除以一个关于 $B$ 的线性函数"。

编者说明:由"每序列约 8.38 亿字节"这一点反推,课堂上用的序列长度大约是 $S = 1024$:

$$2 \times 2 \times 1024 \times 40 \times 128 \times 40 = 838{,}860{,}800\ \text{字节}$$

而 Llama 2 13B 的官方上下文长度是 4096。课堂上大概率用的是一组教学简化配置。此点已记入文末"已知疑点"。

5.11 延迟与吞吐的张力,用具体数字看

(参考时间: 00:41:00)

Batch size = 1:

增大 batch:

继续增大 batch:

讲师在这里给出了一个很形象的类比(务必保留):

想象你在等公交:延迟就是"你等车"的时间——你得等,然后才能走,所以延迟不高;而公交的吞吐其实很好,因为它一次把所有人都拉走。

也就是说:

⚠️ 这是本讲最重要的一个权衡,讲师要求学生当场对齐认知:

小 batch 带来更好的延迟、更差的吞吐;大 batch 带来更好的吞吐、更差的延迟。

5.12 关于并行的一点补充与 TTFT

(参考时间: 00:44:30)

讲师明确说自己不会多讲并行,想深入的话去看 scaling book 里推理那一章。他只给了一个平凡的极端例子:"如果你启动 $M$ 份模型拷贝:延迟不变,吞吐乘以 $M$。"

TTFT 则是另一个维度:TTFT 本质上就是做 prefill 所需要的时间——因为 prefill 一做完,你就可以开始生成了。所以:

graph LR
    A["增大 batch size"] --> B["KV cache 变大"]
    B --> C["每次要读写的内存变多"]
    C --> D["Latency ↑(变差)"]
    A --> E["参数被更多序列摊薄"]
    E --> F["Throughput ↑(变好)"]
    E --> G["TTFT ↑(变差)"]
    C --> H["显存占用 ↑"]
    H --> I["最终撞上内存墙"]

六、让推理更快:一张技术全景图

(参考时间: 00:45:40)

有了"算术强度 + 吞吐 + 延迟"的概念框架之后,问题变成:怎么让它更快? 讲师强调,方法跨度很大——从改模型架构,到做系统优化,以及两者之间的一切。推理在某种意义上是一个非常丰富的交叉学科话题。

而最核心的一条主线,讲师希望"把它敲进你脑子里":

内存是推理的瓶颈,而 KV cache 占用了大量内存。在足够大的 batch 下,KV cache 甚至可能比参数量还大。所以试试把 KV cache 减小。

但要小心:减小 KV cache 是有损的,你必须确保精度掉得不多。

graph TD
    R["加速推理:在不伤精度的前提下降低开销"] --> A["减小 KV Cache"]
    R --> B["减小参数量"]
    R --> C["不改模型,靠系统"]
    A --> A1["GQA / MQA<br/>跨头共享 KV"]
    A --> A2["MLA:低秩压缩 KV"]
    A --> A3["CLA:跨层共享 KV"]
    A --> A4["滑窗 / 局部注意力<br/>压缩稀疏注意力"]
    A --> A5["线性注意力 / SSM"]
    B --> B1["量化:QAT / PTQ / GPTQ / 激活感知"]
    B --> B2["剪枝 + 后训练(蒸馏)"]
    C --> C1["投机解码(无损)"]
    C --> C2["连续批处理"]
    C --> C3["选择性批处理"]
    C --> C4["PagedAttention"]

6.1 Grouped Query Attention(GQA):跨头共享 KV

(参考时间: 00:46:45)

回忆 Multi-Head Attention(MHA):每个 token 都有一份 key、一份 value、一份 query。

在 GQA 里,你仍然计算同样数量的 query($N$ 个),但只保留更少数量的组,每组只有一份 key/value。也就是说:

三种形态对照:

形态 $K$ 说明
MHA $K = N$ 不做任何归约
MQA(multi-query attention) $K = 1$ 讲师说"没人用了,因为它太差"
GQA $1 < K < N$ 希望在这个区间找到精度与速度的平衡

GQA 论文(2023)的实测曲线:横轴与纵轴的综合指标是"每样本时间"(既关联延迟也关联吞吐)。

GQA 论文中不同 KV 组数下

📷 GQA 论文中不同 KV 组数下"每样本时间"的对比曲线
回到原视频 (00:48:05)

为什么 GQA 能改善延迟与吞吐? 因为它把 KV cache 减小了 $N/K$ 倍。而"减少内存占用 → 变快"这条因果链成立,正是因为我们是内存受限的。

代回刚才的 Llama 例子:

继续把 batch 加大。 之前 batch 256 会爆显存,现在能放下了。代价是延迟略降(因为 batch 变大),但吞吐近似线性地上升。

所以真实调参是联合地玩这些旋钮:你可以先缩小 KV cache,再用省下来的显存去换更大的 batch,从而换更高的吞吐——在另一条约束线上重新做权衡。

最后必须检查精度。 论文声称 GQA 在一系列评测上表现良好。但讲师立刻泼了冷水:

"对这些精度评测我总是要打个折扣。比如后来 DeepSeek 的论文就显示,GQA 其实是有损的。所以——凡不是纯数学的东西,在这里都要打个折扣。"

编者说明:GQA 的原始论文是 Ainslie 等人 2023 年的 GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints(arXiv:2305.13245)。它同时给出了"从已有的 MHA 检查点用 uptraining 转成 GQA"的方案——这是它被广泛采用的现实原因之一:不必从头训。

6.2 Multi-Head Latent Attention(MLA):低秩压缩 KV

(参考时间: 00:51:30)

讲师把主题再重申一遍:"降低 KV cache → 延迟和吞吐一起改善。"

具体做法:

于是你只需存 $C$(小得多),需要 K、V 的时候再把它物化出来(materialize)。

一个麻烦:MLA 与 RoPE 不兼容,因为 RoPE 直接作用在 key 和 value 上。DeepSeek 的做法是额外增加一些维度专门用来承载 RoPE。但整体上仍然是相当大的压缩。

效果:延迟和吞吐的改善"跟着简单的算术走"——KV cache 越小,你越快,几乎是线性地快下去,直到某个点为止。

精度对比(这里出现了一个和 GQA 论文相反的结论):

编者拓展|MLA 的数学形式(为什么"压缩"不是简单的降维) 设模型维度为 $d$,MLA 引入一个压缩维度 $d_c$(DeepSeek-V2 里 $d_c = 512$)。 - 下投影:$c = W^{DKV} x$,$c \in \mathbb{R}^{d_c}$,这一步的产物就是要缓存的 KV; - 上投影:$k = W^{UK} c$,$v = W^{UV} c$。

对应 KV cache 的大小从每 token 每层 $2 \times N \times H$ 个元素,降为 $d_c$ 个元素(再加一小段 RoPE 维度)。 值得注意的是:因为 $k = W^{UK}W^{DKV}x$ 可以预先吸收成一个等效矩阵,所以推理时甚至能避免显式物化 $k$,直接改写注意力形式。这也是 MLA 在工程实现上需要特殊 kernel 支持的原因。 参考:DeepSeek-V2 技术报告,arXiv:2405.04434。

回答一个学生提问:这跟"直接把模型维度变小"比起来如何?

学生问:这跟降低模型维度相比如何? 讲师答:很好的问题,但这组消融实验里没有这一项。我猜直接降低模型维度会更糟——因为那是不加区分地把所有东西都缩小。这一类技巧的关键,在于找到模型里"可以抠出油水"的地方。而这先验地没法知道,只能做一大堆实验去试。

MHA、GQA 与 MLA 在 KV cache 和精度上的对比

📷 MHA、GQA 与 MLA 在 KV cache 和精度上的对比
回到原视频 (00:51:45)

编者说明:这个回答其实点出了"结构化压缩 vs 粗暴降维"的分野。MLA 聪明的地方在于:它在KV 通路上压得极狠(512 维),却在上投影之后重新展开回原来的头维度,因此注意力计算本身的表达能力没被同等程度地压缩。而直接减 $d$ 会同时削弱 Q、K、V、MLP、残差通路,收益与代价不成比例。

6.3 Cross-Layer Attention(CLA):跨层共享 KV

(参考时间: 00:55:45)

想法很直接:正常每一层都有自己的 K 和 V。CLA 则只在部分层计算 KV,其他层直接复用上一层的 KV cache。

讲师的类比非常清楚:

GQA 是跨头共享 KV;CLA 是跨层共享 KV。

效果:论文显示这能改善 Pareto 前沿。

具体对比方法是:给定一种方法,你都可以通过调 $K$ 和头维度来扫出不同的 KV cache 大小(这某种意义上也回应了刚才那个"改模型维度"的问题);而在同样的 KV cache 预算下,CLA 的表现更好。

编者说明:CLA 的代表工作是 Reducing Transformer Key-Value Cache Size with Cross-Layer Attention(arXiv:2405.12981)。它的工程直觉是:相邻层的 KV 表示高度相关(表征相似度随层数平滑变化),因此共享的边际损失比"跨头共享"(GQA)更小。这也解释了为什么两者可以叠加。

6.4 局部 / 滑窗注意力,以及混合注意力

(参考时间: 00:56:55)

接下来是一段"快速巡礼"。

滑窗注意力(local / sliding window attention):想法相当老,也相当自然。

于是 KV cache 就与序列长度无关了:只取决于 $B$(乘以其他常量)。这对长上下文尤其友好。

而且由于多层堆叠,有效上下文长度其实大于名义窗口——信息可以沿层向更远处传播。

还可以做更花哨的版本:不必用稠密的选择,可以间隔取点;或者做 global + sliding window 的混合——对一组固定的 token 位置做全局注意力,同时保留一个局部滑窗。

代价:它仍然会伤精度。 滑窗降低了表达力,天下没有免费的午餐——讲师补了一句调侃:"或者说,这是一顿很贵的午餐。"

常见解法:把局部注意力与全局注意力交错(interleave)起来——某些层做全注意力,另一些层做局部注意力。这样能压下一些 KV cache,同时始终在精度与速度之间找平衡。

滑窗注意力与全局注意力的对比示意

📷 滑窗注意力与全局注意力的对比示意
回到原视频 (00:57:20)

关于线性注意力与滑窗的对比(一段课堂问答):

学生问:这些方法里,线性注意力变体和滑窗之间的权衡是什么? 讲师:我不打算细讲线性注意力,但可以很快说一下。 有一批方法不存 KV cache,而是计算历史的一个压缩表示。最朴素的线性注意力就是把所有 KV 求和成一个向量(因此必然与序列长度无关)。还有更花哨的,比如 GatedDeltaNet、DeltaNet、Mamba,它们试图压缩但尽量不遗忘。 这些方法也被用来替代滑窗注意力,并且有人拿到了不错的结果。你还可以把全注意力、滑窗注意力和线性注意力三者组合,因为它们捕捉的是不同侧面:如果你关心局部、高分辨率的信息,滑窗更好;如果你只想要对过去的宽泛摘要,线性注意力可能更好。

学生追问:那对长上下文来说,线性注意力更好吗? 讲师:没有免费午餐。 假设上下文极长,你在解一个"大海捞针"问题——如果你必须把整段历史压进一个很小的状态,你一定会丢信息,于是可能根本捞不出那根针。

学生继续:我感觉大家现在走的是层级化(hierarchy)路线,但总还是需要某种"更长的注意力",我只是想理解滑窗 vs. Mamba/DeltaNet 这类层的具体取舍,比如 DeltaNet 层是否一致地优于滑窗? 讲师:我大概会说,Mamba 和 DeltaNet 比滑窗注意力更强大。你可以这么想:Mamba 大概能表示滑窗注意力的某些方面——因为它在做递推时,本来就能"看最后那个状态"。所以不妨把线性注意力及其扩展理解为更宽松、更有余地的类别;而一旦你用了滑窗,你就到此为止了,没有别的空间了。

6.5 DeepSeek 的压缩稀疏注意力

(参考时间: 01:02:15)

讲师说 DeepSeek 在注意力机制上持续创新:

不过看图就清楚了:

"这就是一个闪电般快速地判断该保留哪些 token 的办法。"

6.6 这一节的收束

(参考时间: 01:03:40)

本节的目标就是缩小 KV cache。因为 KV cache 关联到内存,而我们已经看到推理是内存受限的,所以这直接翻译成吞吐与延迟的改善。关键在于不伤精度。

可以动刀的地方包括:


七、量化、剪枝与蒸馏

7.1 量化

(参考时间: 01:04:20)

讲师把量化定位为"与其说是架构问题,不如说是系统视角"的问题。

核心想法:降低数字的精度。

逻辑链:更少的内存 → 更好的延迟与吞吐。当然,你必须担心精度。

可选范围从 BF16 一路降到 int4。

两条路线:

路线 做法 优点 缺点
量化感知训练(QAT) 训练时在前向里量化再反量化,把量化误差"模拟"进训练过程 权重会适应量化,效果更好 需要昂贵的大规模训练
训练后量化(PTQ) 先正常训练,事后再量化 便宜得多,是业界常规做法 精度风险更大

PTQ 的几种做法,从粗糙到精细:

  1. 朴素做法:对每个层或张量,各自确定缩放因子(scale)与零点(zero point),然后分开量化。讲师说这"一般来说效果不太好"。
  2. GPTQ:利用 Hessian 信息,逐层量化,并把量化误差记录、传播到尚未量化的权重上,从而修正误差。
  3. 激活感知的量化:更精细。核心观察是——某些激活通道的值特别大,与这些通道交互的权重就更重要,于是给这些权重分配更高的精度。

讲师用一页图解释第 3 种:你把一个 FP16 的权重矩阵量化到 int3;但你先统计出哪些激活通道在整体上数值偏大,于是对这几个通道保留 FP16,其余仍用 int3——少数关键通道用高精度。

激活感知量化:为激活值大的通道保留高精度

📷 激活感知量化:为激活值大的通道保留高精度
回到原视频 (01:07:05)

编者说明:讲师描述的"激活通道大 → 对应权重更重要 → 保留高精度"就是 AWQ(Activation-aware Weight Quantization) 的核心思想(arXiv:2306.00978)。它与 GPTQ 是同一时期的两条主流后训练量化路线:GPTQ 走二阶误差补偿,AWQ 走"保护显著通道"。实践中两者常被混用或叠加。

7.2 剪枝(pruning)

(参考时间: 01:07:35)

思路同样粗暴:拿一个大模型,把其中一部分直接撕掉,然后再修补它。讲师说"这是很粗糙的做法,但它确实有效"。

有一篇来自 Nvidia 的论文,大致流程是:

  1. 先估计模型不同部分的重要性;
  2. 挑出最重要的部分;
  3. 然后移除不同的隐藏单元,甚至整层;
  4. 此时模型当然"不会很好";
  5. 于是对它做后训练(post-train)——在你关心的数据或任务上再训一段,把它"治好"。

讲师把它概括为:

这在某种意义上是一次训练,其目的加权地指向"减小 KV cache",只是用一个大模型的部分权重来初始化。

效果:他们能把一个 15B 模型压到 8B,而精度下降并不太大,同时所需的训练量远小于从头训练。

编者拓展|为什么"撕掉再治"能奏效 大模型存在大量冗余(结构化的、通道级的、甚至整层级的)。剪枝之所以可行,是因为被剪掉的部分所承载的功能,往往可以被相邻单元的线性组合近似替代;而后训练的作用,就是让剩余的权重重新分工来补上损伤。 具体到"如何判断重要性",课上一位同学刚好追问了这一点,答案见 7.4。

7.3 这一部分的总结

(参考时间: 01:09:20)

这整场游戏就是:在不伤精度的前提下降低推理复杂度。 你可以把它理解为减少参数量,或者减少 KV cache。

两条路径:

  1. 设计一个更快的模型架构,从头训;
  2. 设计一个更快的架构,但用原模型的权重来初始化——虽然架构不同,本质上是在"拼一个弗兰肯斯坦",然后再用蒸馏把这个更快的模型修好。
graph TD
    A["目标:降低推理复杂度,不掉精度"] --> B["减少参数量"]
    A --> C["减少 KV Cache"]
    B --> B1["量化"]
    B --> B2["剪枝"]
    C --> C1["改架构:GQA / MLA / CLA / 滑窗 / 线性注意力"]
    B2 --> D["后训练 / 蒸馏修复"]
    B1 --> E["QAT 或 PTQ"]
    style A fill:#eef

7.4 课堂问答:怎么判断哪些层重要?

(参考时间: 01:09:45)

学生:你怎么区分重要层和不重要层? 讲师:一般来说,你会有一个校准集(calibration set),把输入过一遍模型,然后观察激活的幅度。有些单元基本是死的(接近 0),而那些大的你想保留。这就是高层的思路。

学生追问:可是激活值大为什么就说明它重要?万一某个神经元在所有样本上总是 100 呢?那它是真有意义,还是只是训练的副产物? 讲师:好问题。如果它总是 100,那意味着你不能把它删掉,删了什么都坏了;但你可以进一步看方差。如果它均值高、方差低,那也许可以有另一种做法——本质上把这部分吸收进偏置(bias)里。

学生继续:那如果所有激活都很大呢? 讲师:总体上这是一个经验观察——总有一些通道会显著高于其他通道。如果这个事实不成立,这些技术就不会奏效。但它恰好成立,因为这些模型训练出来就是这样,于是你就可以利用这一点。


八、投机解码:一条无损的加速路径

(参考时间: 01:11:45)

讲师把前面所有方法归为一类:有损(lossy)——它们把 KV cache 狠狠压缩,但可能伤精度。接下来要讲的是一个优雅得多的、无损(lossless)的办法:投机采样 / 投机解码(speculative sampling / decoding)。

8.1 核心洞察:检查比生成快

于是:

检查(checking)比生成(generating)快。 你给我一个序列,让我判断它有多好,这比一个一个 token 生成快得多。

8.2 算法

利用这个不对称性:

  1. 用一个便宜的小草稿模型(draft model)$P$自回归地猜出若干个 token(比如 4 个);
  2. 用我们真正在意的目标模型(target model)$Q$并行地对这 $K$ 个 token 打分;
  3. 逐个决定接受或拒绝。

平衡点在哪:

一个具体画面(课上有段演示视频):如果直接用大模型逐 token 生成,会很慢;而做投机解码时,你会看到小模型先"哗"地生成一串 token,然后大模型对它们做点评,于是你得到一个突发的 token 串,接着又是下一个突发串。

投机解码:小模型成串输出、大模型并行校验

📷 投机解码:小模型成串输出、大模型并行校验
回到原视频 (01:13:20)

算法细节:

  1. 用草稿模型 $P$ 采样 $K$ 个 token;
  2. 用目标模型 $Q$ 并行计算这些草稿 token 的 logits;
  3. 接受准则:以如下概率接受

$$ \Pr(\text{accept}) = \min\!\left(1,\ \frac{Q(x)}{P(x)}\right) $$

也就是说,$Q$ 相对 $P$ 越大,我们越倾向于接受; 4. 否则,从残差分布中采样并退出。

这是拒绝采样(rejection sampling)的一个变体。区别在于:普通拒绝采样在拒绝时什么都得不到;而这里我们保证能拿到一个来自目标模型的精确样本。

讲师说他会跳过那个简单证明,并指出"它的论证与拒绝采样基本一致",用于证明它精确地等于目标模型的概率。

编者拓展|接受准则为什么能保证无损 记草稿分布为 $P$、目标分布为 $Q$。执行如下过程: 1. 从 $P$ 采样 $x$; 2. 以概率 $\min(1, Q(x)/P(x))$ 接受 $x$;否则拒绝。

那么对任意 $x$,接受并输出 $x$ 的概率为

$$P(x)\cdot\min\!\left(1,\frac{Q(x)}{P(x)}\right) = \min\bigl(P(x), Q(x)\bigr).$$

而被拒绝的总概率是

$$1 - \sum_x \min\bigl(P(x), Q(x)\bigr) = \sum_x \bigl(Q(x)-P(x)\bigr)_+,$$

其中 $(\cdot)_+$ 表示取正部。于是在拒绝时,我们从归一化的残差分布

$$\frac{\bigl(Q(x)-P(x)\bigr)_+}{\sum_{x'}\bigl(Q(x')-P(x')\bigr)_+}$$

中采样,得到 $x$ 的概率恰好是 $\bigl(Q(x)-P(x)\bigr)_+$。

两路相加:

$$\min\bigl(P(x),Q(x)\bigr) + \bigl(Q(x)-P(x)\bigr)_+ = Q(x).$$

也就是说,最终输出的 marginal 分布精确等于 $Q$——这正是"无损"的含义。✅

参考:Leviathan 等,Fast Inference from Transformers via Speculative Decoding,arXiv:2211.17192(几乎同期还有 Chen 等人的独立工作)。

8.3 草稿 token 的数量:有最优点

8.4 草稿模型怎么来

一般来说,草稿模型远小于目标模型,并且理想情况下草稿分布要尽量接近目标分布——也就是说,你希望它是蒸馏出来的。而这恰好又回到了刚才那一整套思路:

先用各种"花招"把 KV cache 压下去;如果压出来的模型你满意,直接拿它当服务模型;如果不满意,它至少还能当草稿模型,再用你的主模型去把它"修好"。

讲师指出,投机解码目前已经有一整套文献在改进原始方法,他没有展开。


九、动态负载下的工程系统

(参考时间: 01:17:00)

9.1 现实场景有多乱

场景是:你在服务一个线上网站,用户陆续来和模型聊天。于是:

这离训练时那种"一批同样长度的 token 整块塞进去"的整齐形态差得很远。

9.2 连续批处理(continuous batching)

这里有一个很早就被提出的系统,叫 Orca,它引入了连续批处理的想法。

做法是:

之所以叫"连续批处理",是因为这个 batch 是在被动态地更新的:老序列出局、新序列进来。

graph LR
    subgraph "连续批处理的时间轴"
    S0["step 0<br/>A B C D"] --> S1["step 1<br/>A B C D"]
    S1 --> S2["step 2<br/>A B C D"]
    S2 --> S3["step 3<br/>A B C E<br/>D 结束,E 进入"]
    S3 --> S4["step 4<br/>A B C E"]
    S4 --> S5["step 5<br/>C E F<br/>A B 结束,F 进入"]
    end
连续批处理:batch 随请求进出而动态变化

📷 连续批处理:batch 随请求进出而动态变化
回到原视频 (01:18:00)

9.3 选择性批处理(selective batching)

问题来了:我们之前看到的批处理,前提是"所有序列的维度一致"——张量的每一个切片形状都一样。但这里每条请求长度不同,怎么办?

思路是分开处理:

9.4 PagedAttention

(参考时间: 01:19:35)

这一想法由 vLLM 论文引入(vLLM 现在当然还有一大堆别的特性,但这是它最初的核心想法)。

问题是:KV cache 到底怎么存?

请求不断进来,你总得把它们放进内存某处。于是就有了碎片(fragmentation)问题——这正是过去发生在你硬盘上的事,以前人们还得整理硬盘碎片(defrag)。

两类碎片:

  1. 内部碎片(internal fragmentation):你必须预先分配足够的缓冲区,因为你事先不知道序列什么时候停。假设最大 token 限制是 1024,你得把这么多内存全留出来,而且不能挪作他用,因为除非序列生成到上限,否则你不会停。这是极大的浪费。
  2. 外部碎片:不同请求之间可能留下空隙,而这些空隙可能小到无法被有效利用,于是就成为纯粹浪费的空间。

解法:这是一群系统出身的人,他们熟悉操作系统,于是说:"这个问题我们以前解决过一次,那就用同样的思路呗。"

具体是:把一条序列的 KV cache 切成非连续的块(block)。比如 "Four score and seven years ago our fathers brought forth" 这句话,就按块大小 4 切分。块放在哪里无所谓,但它们在块的边界上是对齐的——这带来某种统一性。

于是:

一个立竿见影的收益:系统提示(system prompt)。你可以在所有 query 之间共享 system prompt 的 KV cache,只算一次。

另一个收益:很多应用场景里,同一个 prompt 要生成多个回答。这时你也可以共享 prompt 的 KV cache,只让各个回答各自发散。

写时复制(copy-on-write)语义:比如你想从 "Four score and seven years ago ___" 生成多个续写:

  1. 先有 "Four score and seven";
  2. 接着生成 "years ago our";
  3. 这个前缀被保持住,然后分出两个采样分支;
  4. 如果它们恰好采到了同一个 token,就继续共用;
  5. 如果采到了不同的 token,就把这个块裂开(split),各自继续。

也就是说,你尽可能多地共享前缀 cache。

PagedAttention:把 KV cache 切成非连续块并按需共享

📷 PagedAttention:把 KV cache 切成非连续块并按需共享
回到原视频 (01:21:10)

讲师说他没时间讲别的优化(比如各种 kernel),但总的思路是:

用操作系统式的隐喻来管理推理。


十、总结

(参考时间: 01:23:45)

减小 KV cache,同时别把精度伤太多。

一条讲师特意留下的伏笔:有一件事没能展开讲——他认为新架构有巨大的改进潜力。像状态空间模型(state space models)、线性注意力、扩散模型这一类东西:

在某种意义上,KV cache 和注意力被构造的方式,根本上让它成为一种"对推理不友好的架构"。所以如果你能设计出一种为推理而生的新架构——而 Transformer 并不是这样设计的——那么可能会解锁很多东西。


附:参考与延伸阅读

编者补充

已知疑点

本节列出本讲重构过程中不确定或拿不准的地方,逐条给出依据或时间点。若某处确实没有疑点也会写明,以免读者误以为"没查"。

  1. 记号图中 $K$ 与 $G$ 的定义冲突。 依据:约 00:19:20–00:20:25 的课堂问答。一位同学当场指出幻灯片上的问题,讲师起初尝试解释、随后确认更正为:$K$ 是"组的数量(number of groups)",$G$ 是"每组内的 query 头数"。讲师原话是"我之后改一下"。因此原始幻灯片(或书面讲义)上可能仍写着 $K$ = KV 头数,与本书正文不一致。正文一律采用课上更正后的版本。

  2. Llama 2 13B 示例中的序列长度 $S$ 未在字幕里明确出现。 依据:约 00:38:45–00:39:10。讲师只提到 KV cache 的系数"大约是 8.38 亿,乘以 $B$"。编者按 $2 \times 2 \times S \times K \times H \times L$($K = 40$ 个 KV 头、$H = 128$、$L = 40$ 层)反推,得 $S \approx 1024$。但 Llama 2 13B 的官方上下文长度是 4096。这很可能是课堂上采用的教学简化配置,也可能是编者对某一项因子的理解有偏差。请以官方讲义为准。

  3. "8.38 亿"的口播形式与单位。 依据:约 00:39:00。原话是 "about 838 I guess million times B",没有说单位是字节还是元素个数。正文按字节处理(这样才能与"batch 256 时爆掉 80 GB H100"的情节自洽)。此推断已记入第 2 条。

  4. H100 的"295"这个门槛值。 依据:约 00:18:00。讲师说"对 H100、对这个特定的 matmul 运算,batch size 大于 295 就是计算受限"。编者按其逻辑($\text{BF16 密集算力} \div \text{HBM 带宽}$)复算,量级吻合,但具体数字取决于用哪一档算力(是否计入稀疏加速、是否用 FP8)。正文中作为量级锚点使用,不宜当作精确常数。

  5. "OpenAI 每天 8.6 万亿 token"。 依据:约 00:02:05。这是讲师口头给出的外部估计,未给出出处,也与后来公开口径可能存在出入。请勿直接引用为事实。

  6. "GPT-4 训练用了 32 万亿 token"。 依据:约 00:02:15。同上,属未经讲师标注出处的数字。另外讲师有一句口误式的表述——"GPT-4, which came out earlier this year",按上下文应指前一年(GPT-4 发布于 2023 年 3 月)。

  7. GQA 是否掉点:本讲内部存在两种相反证据。 依据:约 00:49:30。GQA 论文声称精度良好;讲师随即指出 DeepSeek 的论文显示 GQA 实际上是有损的。讲师本人并未裁决孰是孰非,只提醒"凡不是纯数学的东西都要打折扣"。本书保留了这一矛盾,没有替讲师下结论。

  8. DeepSeek 的注意力方法命名。 依据:约 01:02:30。字幕中出现 "compressed sparse attention" 与 "heavily compressed attention" 两个名字,讲师明确说自己记不住这些缩写。因此确切的方法名可能不准确,正文按字幕还原并加了保留语。

  9. Nvidia 剪枝论文的具体身份未确认。 依据:约 01:07:50。讲师只说"一篇来自 Nvidia 的论文",未点名。编者据其描述(重要性估计 → 剪掉单元/层 → 后训练修复 → 15B 压到 8B)推测这是 Minitron / LLM 结构化剪枝方向的工作,但无法确认,故正文未给出论文链接,仅记为待核。

  10. 投机解码的论文归属。 依据:约 01:13:50。讲师说"有几篇论文几乎在同一时期出现",未逐一点名。编者补充的 arXiv:2211.17192 是其中被引用最多的一篇,但并非唯一,同期还有 Chen 等人的独立工作。

  11. GPTQ 的机制描述。 依据:约 01:06:25。讲师说 GPTQ"利用 Hessian 信息,逐层量化,并把误差传播到未量化的权重上"。这一概括是准确的,但"Hessian"具体指层内二阶信息($XX^\top$ 近似),讲师未展开。编者未在正文中修正,仅在链接处提供出处。

  12. 激活感知量化的方法归属。 依据:约 01:07:00。讲师描述了"激活通道大 → 对应权重更重要 → 用高精度"这一机制,但没有点名方法。编者在拓展中标注为 AWQ,属编者补充,非讲师原话。

  13. 线性注意力部分的口径。 依据:约 00:59:30–01:02:10。这里是一段较长的即席问答,讲师用了"大概"、"也许"等非常不确定的措辞(例如"Mamba 大概能表示滑窗的某些方面")。这些属于讲师的个人判断,不是定论,本书按原意保留但不宜当作结论引用。

  14. 本讲未涉及的部分。 讲师明确说自己不会讲并行(只给了一个"启动 $M$ 份拷贝"的平凡例子),也没时间讲各种 kernel 优化。此外扩散模型只被一笔带过。读者若想补这些,需另行查阅 scaling book 与 vLLM 论文。

除上述各条外,本讲未发现其他明显疑点。