附加讲 A4 · 流式长文本与多模态 LLM:注意力汇聚、StreamingLLM、Flamingo · Streaming LLMs and Multimodal LLMs

Large Language Model Systems · 附录 A4。个人学习讲义与配套实验,按主题整理并持续修订。

课程源码 ZIP ↓

个人整理的学习笔记,非官方讲义;课程来源与整理说明见课程介绍。配套示例未逐一运行验证。

本篇目录

对应 Maps to:CMU 11-868 / 11-968 Large Language Model Systems(Spring 2026,Lei Li)课表末尾列出、本学期未排课的两个主题 Efficient Streaming Language Models with Attention Sinks 与 Multimodal LLMs(Flamingo)。这两个主题没有公开 slides。 阅读材料:Xiao et al. (2024) Efficient Streaming Language Models with Attention Sinks(ICLR);Alayrac et al. (2022) Flamingo: a Visual Language Model for Few-Shot Learning(NeurIPS)。 依据说明:因为没有 slides,本讲按两篇阅读材料组织。StreamingLLM 论文:流式部署的两个问题(KV 无限增长、超出训练长度);窗口注意力在开头的 token 被挤出后崩溃(Llama-2-13B 困惑度 5158 vs 留住 4 个 token 时 5.40;把开头 4 个 token 换成换行符也有 5.60);注意力汇聚的解释(softmax 必须加起来等于 1);滚动 KV 缓存 + 汇聚 token、在缓存内重新编号位置;4 个模型家族上稳定处理 400 万 token;比带重算的滑动窗口快最多 22.2 倍;预训练时加一个可学习的汇聚 token(1.6 亿参数的对照实验)。Flamingo 论文:冻结的视觉编码器与语言模型、Perceiver 重采样器(每张图 64 个输出)、门控交叉注意力(tanh 门,初始化为 0)、只看前一张图的掩码、图文交错的网页数据、消融实验(去掉门 −4.2%、每 4 层插一次训练快 66% 只掉 1.9%、微调 LM −8.0%)。LLaVA 的结构按其公开的设计(Liu et al. 2023)概述。本讲义的”触发复制”任务、城市 LLaMA 上的四种 KV 策略、合成图像上的 Flamingo / LLaVA 对比、视觉 token 的账,都是本讲义自己设计的;小模型上的数字只说明机制,规模化的结论以论文为准。 配套代码 Code:code/appendixA4/(2 个脚本 + streaming.py、flamingo.py;复用第 7、19、26 讲的模型与时间模型;第一次运行 01 号脚本会训练两个小模型约 1.5 分钟) 预计阅读时间:2.5 小时(含运行代码)


0. 本讲导航 · How to read this lecture

0.1 学习目标 Learning goals

  1. 说出让 LLM “一直说下去”的两个障碍(KV 无限增长、位置超出训练长度),比较四种 KV 策略:全保留、窗口、带重算的窗口、StreamingLLM。
  2. 解释注意力汇聚:它是什么、为什么会出现(softmax 必须加起来等于 1)、为什么偏偏是开头的 token,以及”汇聚是训练出来的习惯”。
  3. 说出 Flamingo 的三个设计(Perceiver 重采样器、门控交叉注意力、冻结的 LM)与 LLaVA 的设计(投影层 + 软 token),各自的取舍。
  4. 会算多模态输入的 token 数、KV 与预填充时间,说出服务多模态模型的主要瓶颈与对策。

0.2 先修与本讲范围

默认你已经会本讲只给直觉
KV 缓存、RoPE(第 6、7 讲);PagedAttention(第 24 讲)长上下文的训练(位置插值、YaRN)
量化里的离群值(第 19 讲)视觉编码器(ViT、对比学习)的训练
前缀缓存(第 22 讲);KV 的压缩与复用(第 27 讲)视频理解、语音等其他模态

0.3 本讲地图

§1  让模型一直说下去:四种 KV 策略                                          ← 代码 01 第 1、3 部分
§2  注意力汇聚:开头的 token 为什么这么重要                                  ← 代码 01 第 2 部分
§3  StreamingLLM:汇聚 token + 滚动窗口                                      ← 代码 01 第 3 部分
§4  多模态 LLM:给语言模型接上"眼睛"(Flamingo vs LLaVA)                   ← 代码 02 第 1–3 部分
§5  多模态的系统开销:图像变成多少 token                                     ← 代码 02 第 4 部分
§6  小结   §7 常见误区   §8 练习   §9 延伸阅读   §10 全课回顾

0.4 本讲的三个小实验

  • 城市 LLaMA(第 19 讲,训练窗口 32):在一段 1000 个 token 的城市语言文本流上逐个生成,比较四种 KV 策略。
  • 触发复制任务(streaming.py):序列是随机 token,偶尔出现一个 ”@”,”@” 之后的 token 要复制 ”@” 前面那个 token;其余位置的下一个 token 完全随机,无从预测。一个 2 层、2 个头的小模型,训练窗口 64;训练两个版本:序列开头总有一个 <s>,和没有。
  • 合成图像(flamingo.py):一张”图”是视觉编码器输出的 16 个图块特征,5 个编码城市、5 个编码天气、6 个是噪声,顺序随机。任务是给图配字:“in <城市> it is often <天气> .”——城市与天气均匀抽取,包括城市语言里从没出现过的组合(比如 miami 下雪)。冻结的语言模型只会按先验猜,要答对必须真的”看图”。

1. 让模型一直说下去 · Streaming generation

1.1 直觉与背景

一个陪你聊了三天的助手、一个持续读入日志的监控程序——它们的输入是一条没有尽头的流。直接用 LLM 会遇到两个障碍:

  1. KV 无限增长:每生成一个 token,KV 缓存就多一份。LLaMA-3-8B 每个 token 的 KV 是 128 KiB,100 万个 token 就是 131 GB,一张 H100 放不下;而且 KV 越长,每一步解码要读的数据越多,越来越慢。
  2. 位置超出训练长度:模型训练时只见过 4K(Llama-2)或 8K 的位置。位置继续往上涨,RoPE 的旋转角是模型从没见过的,质量崩溃(长度外推问题)。

1.2 四种 KV 策略

dense       保留全部 KV,位置一直增长                 → 显存、时间无限增长;超出训练长度后质量崩溃
window      只留最近 W 个 token 的 KV                → 显存、时间固定;但……(§1.3)
recompute   每生成一个 token,把最近 W 个 token 重新预填充一遍    → 质量好;但每一步都是一次 W 个 token 的预填充,慢
sink        开头 S 个 token + 最近 W − S 个(StreamingLLM)    → 显存、时间固定,质量好(§3)

注意一个细节:window 与 sink 在缓存里按缓存中的序号重新编号位置(缓存里第 i 个 token 用位置 i),所以位置永远不超过 W,不会遇到长度外推的问题。dense 的位置一路增长。

1.3 窗口注意力为什么会崩

window 看起来很合理:语言模型预测下一个词,主要靠最近的上下文。可 StreamingLLM 论文发现,在真实的 LLM 上,最开头的 token 一被挤出窗口,困惑度就爆炸。Llama-2-13B 在 PG19 的一本书(6.5 万 token)上:

缓存配置(开头 x 个 + 最近 y 个)困惑度
0 + 1024(窗口)5158.07
4 + 10205.40
4 个换行符 “\n” + 10205.60

只要多留开头的 4 个 token,困惑度就从 5158 回到 5.40。更奇怪的是最后一行:把开头 4 个 token 换成毫无意义的换行符,效果几乎一样——重要的不是开头 token 的内容,而是它们的位置。其他模型也一样(缓存 2048 时,窗口 vs 留住开头的 token):Falcon-7B 17.90 → 12.12,MPT-7B 460.29 → 14.99,Pythia-12B 21.62 → 11.95。

1.4 在城市 LLaMA 上(01 号脚本第 1 部分)

W = 24、S = 4,在 1000 个 token 的流上逐个生成,按被预测的位置分段的交叉熵(nat):

KV 策略位置 32–127128–511512–999全部
dense0.6970.7570.9840.866
window0.6980.6600.6400.654
sink0.6980.6610.6400.654
recompute0.6980.6600.6400.654

dense 随位置变差(512–999 段 0.984):位置超出了训练窗口 32,这是长度外推的问题。可 window 没有崩,和 sink、recompute 几乎一样。为什么?看各层各头放在位置 0 上的注意力:0.014–0.037,不比均匀分配的 0.042 多——这个小模型没有注意力汇聚。它在随机截取的 32-token 窗口上训练,窗口的第一个 token 什么都可能是;它更倾向于看每句话开头的 <bos>。要理解汇聚从哪里来,需要一个专门的实验(§2.3)。


2. 注意力汇聚 · Attention sinks

2.1 现象

StreamingLLM 论文把 Llama-2-7B 各层的注意力图画出来:最底下两层以局部注意力为主(最近的 token 分到更多注意力);再往上,所有层、所有头都把大量注意力放在开头的 token 上,不管那个 token 是什么。它们被称为”注意力汇聚”(attention sink):内容上无关紧要,却吸走了大量注意力。

2.2 为什么会出现

直觉:注意力的 softmax 要求所有权重加起来等于 1。如果某个头在当前位置没什么可看的(当前 token 自己的信息已经够了,或者这个头负责的模式在这里没出现),它也不能”不看”——权重总得分给某些 token。最好的办法是有一个固定的”垃圾桶”:把多余的注意力倒进去,而这个 token 的 V 很小,倒进去也不往输出里加什么东西。

为什么偏偏是开头的 token:在因果注意力里,开头的 token 对后面每一个位置都看得见;训练时它出现在每个样本里、每个位置都能用到它,最容易被训练成垃圾桶。中间的 token 只对它后面的位置可见,而且内容每次都不同。

这也解释了窗口注意力为什么崩:垃圾桶被挤出窗口后,那些”无事可做”的头只好把注意力分给别的 token,把它们的 V 混进残差流——相当于往每一层注入噪声。

2.3 在小任务上重现(01 号脚本第 2 部分)

触发复制任务专门制造了”无事可做”的头:复制只在 ”@” 之后需要(去看前一个 token),其余位置的下一个 token 完全随机,注意力头最好什么都不输出。

模型层位置 0 的注意力:非触发位置(头 0 / 头 1)触发位置位置 0 的 |V|其他位置的 |V|
有 <s>00.02 / 0.360.99 / 0.000.7 / 2.03.6 / 4.8
有 <s>10.13 / 0.040.02 / 0.020.4 / 0.30.5 / 1.0
没有 <s>00.03 / 0.030.00 / 0.004.2 / 3.64.1 / 3.4
没有 <s>10.02 / 0.000.03 / 0.050.7 / 0.90.7 / 0.9

有 <s> 的模型把 <s> 学成了垃圾桶:第 0 层的头 1 在非触发位置把 0.36 的注意力倒给 <s>,头 0 在触发位置倒了 0.99(两个头分工不同,空闲时都去看 <s>);<s> 的 V 被学得很小(0.7 / 2.0,其他位置 3.6 / 4.8)。

在 600 个 token 的流上逐个生成(W = 32,S = 1):”@ 之后”是要复制的位置(理想是 0),“其他”是随机 token(下限 ln 32 = 3.466):

模型KV 策略@ 之后的交叉熵其他位置
有 <s>dense3.4743.398
有 <s>window3.6253.398
有 <s>sink0.0003.398
有 <s>recompute0.0003.398
没有 <s>dense10.4203.400
没有 <s>window0.0003.400
没有 <s>sink0.0003.400
没有 <s>recompute0.0003.400

有 <s> 的模型:把 <s> 挤出窗口,复制就失败了(3.625,和瞎猜一样);只要把 <s> 留在缓存里,就恢复到 0.000——StreamingLLM 在 Llama-2 上看到的现象,在一个 2 层的小模型上完整地重现了。

2.4 汇聚是训练出来的习惯

同一个任务,训练时开头没有 <s> 的模型另想了办法(它没有把位置 0 当垃圾桶),窗口注意力对它就没有影响(0.000)。汇聚不是 Transformer 的必然,而是训练时”有一个永远看得见的固定 token”养成的习惯。两个模型的 dense 都在位置超过 64 后失败——位置外推和注意力汇聚是两个独立的问题:StreamingLLM 用”缓存内重新编号”解决前者,用”留住汇聚 token”解决后者。

StreamingLLM 论文据此做了预训练的对照实验(1.6 亿参数,从头训练三个模型;困惑度,缓存配置 x + y):

模型0 + 10241 + 10232 + 10224 + 1020
普通 softmax27.8718.4918.0518.05
Zero Sink(SoftMax-off-by-one)2921419.9018.2718.01
开头加一个可学习的 sink token123518.0118.0118.02
  • Zero Sink:把 softmax 的分母加 1(等价于在开头放一个 K、V 全为 0 的 token),让注意力”可以什么都不看”。有一点帮助,但模型仍然依赖开头的几个 token。
  • 可学习的 sink token:每个训练样本的开头都放同一个专用的 token。模型把它学成唯一的垃圾桶,部署时只留这一个 token 就够了,困惑度还略有改善。论文建议以后的 LLM 都这样预训练。

注意力汇聚还和第 19 讲的一个现象有关:LLM 激活里的离群值(个别通道的值特别大)。为了把注意力几乎全部倒给汇聚 token,模型需要让那里的注意力分数特别高,这往往通过少数维度上的极端值实现——StreamingLLM 论文也提到了量化离群值与 SoftMax-off-by-one 的研究。


3. StreamingLLM:汇聚 token + 滚动窗口 · StreamingLLM

3.1 设计

KV 缓存 = [开头 4 个 token(注意力汇聚)] + [最近 W − 4 个 token(滚动窗口)]
          生成第 9 个 token 时,缓存里是原文的 [0 1 2 3 | 6 7 8],位置按缓存里的序号编为 [0 1 2 3 | 4 5 6],新 token 用 7

两个要点:

  1. 留住开头 4 个 token:真实的 LLM 通常把开头几个 token 都用作汇聚(只留 1 个不够,论文的表 2:留 1–2 个不能完全恢复,4 个通常就够了,再多收益递减)。
  2. 在缓存内重新编号位置:不用原文中的位置,而用缓存中的序号。对 RoPE,缓存里存的是还没旋转的 K,每一步按缓存中的位置重新旋转;对 ALiBi,用连续的线性偏置而不是”跳跃”的偏置。这样位置永远不超过缓存大小,模型不用面对没见过的位置——论文强调这一点”对 StreamingLLM 的效果至关重要”。

不需要任何微调,适用于所有用相对位置编码(RoPE、ALiBi)的模型。论文在 Llama-2、MPT、Falcon、Pythia 上稳定处理了 400 万个 token 的文本。

3.2 系统的账(01 号脚本第 3 部分)

LLaMA-3-8B,1 张 H100,W = 4096,S = 4(第 26 讲的”纸面 GPU”模型):

已生成的 token 数dense:每 token 时间、KVStreamingLLMrecompute:每 token 时间
4,0967.2 ms,0.5 GB7.2 ms,0.54 GB105 ms
32,7688.6 ms,4.3 GB7.2 ms,0.54 GB105 ms
131,07213.4 ms,17.2 GB7.2 ms,0.54 GB105 ms
1,000,000放不下(131 GB)7.2 ms,0.54 GB105 ms
  • dense 的 KV 线性增长:H100 放下 8B 模型后只能存约 44 万个 token 的 KV;每一步要读的 KV 越来越多,越来越慢;位置超出训练长度后质量崩溃。
  • StreamingLLM 的显存与每步时间都是常数,和普通的解码步一样快。
  • recompute 质量好,但每生成一个 token 都要预填充 W 个 token,比 StreamingLLM 慢 15 倍。论文实测 StreamingLLM 比它快最多 22.2 倍。

3.3 局限:能一直说,不等于能一直记

StreamingLLM 只”记得”窗口里的内容(加上开头的汇聚 token)。窗口外的信息就丢了:你在第 1 万个 token 处问第 10 个 token 说了什么,它答不上来。它解决的是”让模型能一直稳定地生成”,不是”让模型读懂无限长的上下文”。后者需要别的手段:

  • 长上下文训练(位置插值、YaRN 等)把窗口本身变大;
  • 检索(附加讲 A3):把旧的内容存起来,需要时取回相关的几段;
  • KV 的压缩与选择:不是简单地按时间丢弃,而是保留”重要的” token 的 KV(比如按累积注意力分数挑选),或者像第 27 讲那样压缩 KV。StreamingLLM 的发现对这些方法同样重要:无论怎么挑,都要留住汇聚 token。

4. 多模态 LLM:给语言模型接上”眼睛” · Multimodal LLMs

4.1 背景与动机

我们已经有了很强的视觉模型(在海量图文对上用对比学习训练的图像编码器)和很强的语言模型。多模态 LLM 要做的是把两者接起来,让语言模型能”看图说话”、回答关于图片的问题、在图文交错的对话里推理。

难点在于:语言模型是花了巨大代价训练出来的,接上视觉之后不能把它弄坏。新接口要能学到视觉信息,又不能破坏语言模型已有的能力。两种主流接法代表了两种取舍。

4.2 Flamingo:新开一条通路

Flamingo(DeepMind,2022)的结构:

  1. 冻结的视觉编码器(NFNet-F6,用对比学习预训练):把图像(或视频的每一帧)变成一堆特征。
  2. Perceiver 重采样器:不管图像有多大、视频有多少帧,都压成固定的 64 个视觉 token。做法是学一组”潜向量查询”,让它们对视觉特征做交叉注意力。这样后面的计算量和图像大小无关。
  3. 门控交叉注意力层(GATED XATTN-DENSE):插在冻结的语言模型的层与层之间,从头训练。语言模型的每个 token 通过交叉注意力去看视觉 token:
y = y + tanh(α_xattn) · attention(q = y, kv = 视觉 token)      ← 新加的,α 初始化为 0
y = y + tanh(α_dense) · ffw(y)                                 ← 新加的,α 初始化为 0
y = y + frozen_attention(q = y, kv = y)                        ← 原来的 LM 层,冻结
y = y + frozen_ffw(y)

为什么门要初始化为 0? tanh(0) = 0,训练开始时新加的层对输出没有任何贡献,整个模型和原来的语言模型逐位相同。训练从”语言模型完好无损”出发,新通路一点点地把视觉信息加进来。

  1. 只看前一张图:图文交错的序列里,每个文字 token 的交叉注意力只看紧挨在它前面的那一张图(对之前图片的依赖通过 LM 的自注意力间接传递)。这让模型可以处理任意多张图。

Flamingo 在 1.4B、7B、70B 的 Chinchilla 语言模型上分别构建 Flamingo-3B、9B、80B;训练数据包括约 4300 万个网页里抽出的图文交错数据(M3W)、18 亿个图文对(ALIGN)、3.12 亿个更高质量的图文对(LTIP)和 2700 万个短视频(VTP)。图文交错的数据给了它少样本学习的能力:在 16 个多模态任务上,只给几个例子,Flamingo 就在其中 6 个上超过了专门微调的最好模型。

4.3 LLaVA:把图像变成”软 token”

LLaVA(Liu et al. 2023)的做法简单得多:视觉编码器(CLIP ViT-L/14)把图像切成图块,每个图块的特征经过一个投影层(线性层或小 MLP)变成和词嵌入同样维度的向量——“软 token”——直接拼在文字前面,送进语言模型的自注意力。训练分两个阶段:第一阶段只训练投影层(用图文对把视觉特征对齐到词嵌入空间);第二阶段投影层与语言模型一起在视觉指令数据上微调。

两者的对比:

FlamingoLLaVA
视觉信息怎么进去新加的交叉注意力层软 token,走原来的自注意力
语言模型冻结第二阶段一起微调
每张图的 token64 个(重采样后,不进自注意力的 KV)576 个(LLaVA-1.5)甚至更多,进 KV
推理引擎要支持新层的结构和纯文本模型几乎一样

4.4 在小模型上比较(02 号脚本第 1–3 部分)

冻结的城市 LLaMA(797,824 个参数),给合成图像配字,各适配器训练 800 步:

做法要训练的参数训练前:配字的交叉熵训练后:城市天气配字的交叉熵纯文字:超出下限(毫 nat / token)
冻结的 LM 自己(不看图)0—9.0%18.0%1.0423.0
Flamingo(门初始化为 0)338,9481.042100.0%100.0%0.0363.0
Flamingo(不加门)338,9445.75294.2%100.0%0.0953.0
LLaVA(只训练投影层)24,8321.0338.6%27.2%0.8463.0
LLaVA(投影层 + 微调整个 LM)822,6561.033100.0%100.0%0.0002367.4

逐行看:

  • 冻结的 LM 不看图,只能按语言的先验猜:城市 9%(总猜最常见的那几个)、天气 18%。
  • Flamingo(有门):训练前和原来的 LM 逐位相同(交叉熵也是 1.042);训练后城市、天气都 100%,连城市语言里不存在的组合也能说出来。语言模型一个参数都没动,纯文字的质量自然不变。学到的门 tanh(α) = +0.02、−0.21——新通路只需要往残差流里加一点点信息。
  • Flamingo(不加门,固定在 tanh(1) ≈ 0.76):随机初始化的交叉注意力从第一步起就往冻结 LM 的残差流里加噪声,训练前配字的交叉熵是 5.752(LM 自己 1.042);训练后城市 94.2%,比有门的差。Flamingo 论文的消融:去掉 0 初始化的 tanh 门,总分下降 4.2%,而且训练不稳定。
  • LLaVA 只训练投影层:几乎没学会(8.6% / 27.2%)。原因不在投影层,而在这个 LM:城市语言的句子彼此独立,它从没学过”读前文”——软 token 放在前面,它也不会去看。真实的 LLM 本来就会读上下文,所以 LLaVA 的第一阶段只训练投影层就能对齐。这一行和真实的 LLaVA 不同,是这个小模型的局限。
  • LLaVA 连 LM 一起微调:配字学会了(100%),可纯文字的质量从超出下限 3.0 变成 2367 毫 nat / token——它忘了城市语言。这是灾难性遗忘:在新目标上训练,模型逐渐忘掉预训练学到的东西。Flamingo 论文也验证了:微调语言模型使总分下降 8.0%,从头训练下降 12.9%。真实的 LLaVA 用混合的指令数据(包括纯文字)来缓解遗忘;Flamingo 的答案是干脆冻结 LM、只加新通路。

4.5 交叉注意力插多密?

交叉注意力层越多,视觉信息进得越充分,但新参数和计算也越多。Flamingo 论文的消融:每 4 层插一个比每层都插训练快 66%,总分只降 1.9%。最终 Flamingo-9B 每 4 层插一个,Flamingo-80B 每 7 层插一个(在硬件允许的范围内尽量多插)。


5. 多模态的系统开销 · System costs of multimodality

5.1 一张图是多少 token

02 号脚本第 4 部分(语言模型按 LLaMA-3-8B 算,每个 token 的 KV 128 KiB;1 张 H100,第 26 讲的屋顶线模型):

输入视觉 token 数KV这些 token 的预填充
LLaVA-1.5:一张 336×336 的图(24×24 个图块)57675.5 MB17 ms
LLaVA-NeXT:一张 672×672 的图(4 块切片 + 1 张缩略图)2,880377.5 MB73 ms
Qwen2-VL:一张 1344×1344 的图(每 28×28 像素 1 个 token)2,304302.0 MB59 ms
Qwen2-VL:一张 3840×2160 的 4K 图10,5491382.7 MB290 ms
视频:1 分钟、每秒 2 帧、每帧 64 个 token7,6801006.6 MB204 ms
Flamingo:一张图 → 64 个潜向量(走交叉注意力)642.1 MB(只在 8 个交叉注意力层里)≈ 0

对照:视觉编码器本身(ViT-L/14,约 3 亿参数)编码一张 336×336 的图约 0.35 TFLOP;LLaMA-3-8B 预填充这 576 个视觉 token 约 9.3 TFLOP。贵的通常不是视觉编码器,而是视觉 token 进入语言模型之后。

把图像变成 token、和文字一起送进自注意力(LLaVA、Qwen2-VL 一类)简单而有效,但 token 数随分辨率平方增长:一张 4K 图上万个 token,一分钟视频几千个。KV 缓存、预填充时间、能开的批大小都被视觉 token 占满。服务多模态模型的瓶颈常常在这里。

5.2 对策

  • 少一些 token:降低分辨率或动态分辨率;合并相邻图块(Qwen2-VL 把相邻 2×2 个图块合并成 1 个 token);视频按帧采样、合并相似帧的 token。
  • 压成固定数量:Flamingo 的 Perceiver 重采样器、BLIP-2 的 Q-Former,把任意大小的图像压成几十个 token。
  • 缓存:多轮对话里同一张图被反复引用时,它的视觉 token 的 KV 值得缓存(第 22 讲的前缀缓存、第 27 讲的 KV 复用);视觉编码器的输出也可以缓存。
  • 分阶段部署:视觉编码、预填充、解码的资源需求不同,可以像第 26 讲的预填充 / 解码分离那样分开部署、分别扩缩。

5.3 两种结构的系统取舍

Flamingo 的交叉注意力把视觉信息放在单独的通路里,自注意力的 KV 不增长,每张图只占很少的交叉注意力 KV;代价是要在 LM 里插新层,推理引擎要支持这种结构,图文交错的长对话需要专门的掩码。LLaVA 式的软 token 让多模态模型在推理引擎眼里”就是一个普通的 LLM”,第 22–27 讲的所有优化(连续批处理、PagedAttention、前缀缓存、预填充 / 解码分离)都直接可用——这是它在开源生态里流行的重要原因,代价是视觉 token 挤占了上下文与 KV。


6. 小结 · Summary

  1. 让 LLM 一直生成下去有两个障碍:KV 无限增长、位置超出训练长度。只留最近 W 个 token 的窗口注意力解决了显存问题,却在真实的 LLM 上崩溃。
  2. 崩溃的原因是注意力汇聚:softmax 要求权重加起来等于 1,“无事可做”的头把多余的注意力倒给一个永远看得见的 token(开头的 token),这个 token 的 V 很小。把它挤出窗口,多余的注意力就污染了输出。汇聚是训练出来的习惯:没有固定开头 token 的模型不会形成它。
  3. StreamingLLM = 开头 4 个汇聚 token + 滚动窗口 + 在缓存内重新编号位置:不需要微调,显存与每步时间恒定,稳定处理 400 万 token,比带重算的窗口快最多 22.2 倍。预训练时加一个专用的 sink token,部署时只需留它一个。它让模型能一直说下去,但不扩大模型能”记住”的范围。
  4. Flamingo 冻结视觉编码器与 LM,用 Perceiver 重采样器把图像压成 64 个 token,用 0 初始化的 tanh 门控交叉注意力把视觉信息逐步加进 LM——训练从”LM 完好无损”出发,不会遗忘。LLaVA 把图块投影成软 token 拼在文字前面,简单、与推理引擎兼容,但要微调 LM 并处理遗忘。
  5. 软 token 式的多模态模型,视觉 token 数随分辨率平方增长,主导了 KV 与预填充的开销;对策是少一些 token、压成固定数量、缓存与分阶段部署。

7. 常见误区 · Common pitfalls

  1. “开头的 token 被大量关注,说明它的内容很重要。” 把开头 4 个 token 换成换行符,效果几乎一样(5.60 vs 5.40)。重要的是位置:它是一个永远看得见的垃圾桶。
  2. “窗口注意力只是丢了远处的信息,质量会平滑地下降。” 真实的 LLM 上是崩溃(困惑度从 5.40 到 5158),因为丢的是汇聚 token,它影响的是每一步的注意力计算。
  3. “StreamingLLM 让模型有了无限长的上下文。” 它只记得窗口里的内容;窗口外的信息丢了。它解决的是稳定生成,不是长上下文理解。
  4. “每个 Transformer 都会有注意力汇聚。” 汇聚是训练出来的:01 号脚本的城市 LLaMA 没有,触发复制任务里开头没有 <s> 的模型也没有。但主流的 LLM 都有,部署时要假定它存在。
  5. “StreamingLLM 只要多留 4 个 token 的 KV,位置照原文编就行。” 必须在缓存内重新编号,否则位置会超出训练长度——01 号脚本的 dense 就是这样失败的。
  6. “多模态模型贵在视觉编码器。” 编码一张图约 0.35 TFLOP,而 LLM 预填充这张图的 576 个 token 约 9.3 TFLOP;高分辨率图像与视频的 token 数还会成倍增长。
  7. “给 LLM 加视觉能力,微调整个模型效果最好。” 微调会导致灾难性遗忘(02 号脚本:纯文字质量从 3.0 变成 2367 毫 nat;Flamingo 论文:−8.0%)。要么冻结 LM,要么在训练数据里混入纯文字。

8. 练习 · Exercises

  1. LLaMA-3-8B 有 32 层、8 个 KV 头、头维度 128,KV 用 BF16。每个 token 的 KV 多大?100 万个 token 呢?
  2. 带重算的滑动窗口每生成一个 token 要预填充 W 个 token。为什么它比 StreamingLLM 慢一两个数量级?01 号脚本里两者各是多少?
  3. 一个注意力头在某个位置的分数:汇聚 token 是 5,另外 31 个 token 都是 0。汇聚 token 分到多少注意力?如果汇聚 token 的 V 接近 0,这个头的输出大约是什么?
  4. 证明:SoftMax-off-by-one(分母加 1)等价于在序列开头放一个 K、V 全为 0 的 token。
  5. StreamingLLM 的缓存里是原文的 token [0, 1, 2, 3, 6, 7, 8],正在生成第 9 个 token。缓存里的 token 各用什么位置?新 token 呢?如果 K 是加过 RoPE 之后存的,会有什么问题?
  6. 01 号脚本的触发复制任务里,有 <s> 的模型在 dense 下”@ 之后”的交叉熵是 3.474,没有 <s> 的模型 dense 是 10.420。两个模型的 dense 失败的原因是什么?和窗口注意力的失败是同一个原因吗?
  7. Flamingo 的门 tanh(α) 在 α = 0 时为 0。训练开始时,交叉注意力层自己的参数能得到梯度吗?α 呢?这对训练过程意味着什么?
  8. 验证 02 号脚本第 4 部分的视觉 token 数:LLaVA-1.5(336×336,图块 14×14)、LLaVA-NeXT(672×672,4 块切片 + 1 张缩略图,每块 336×336)、Qwen2-VL(1344×1344 和 3840×2160,每 28×28 像素 1 个 token)、视频(1 分钟、每秒 2 帧、每帧 64 个 token)。
  9. Flamingo 每张图 64 个潜向量,只在 8 个交叉注意力层里有 KV(每层 8 个 KV 头、头维度 128、BF16)。一张图的交叉注意力 KV 多大?和 LLaVA-1.5 的 576 个软 token 在 32 层里的 KV 比是多少倍?

参考答案

  1. 32 × 8 × 128 × 2(K 和 V)× 2 字节 = 131,072 字节 = 128 KiB。100 万个 token:1.31 × 10¹¹ 字节 ≈ 131 GB,一张 80 GB 的 H100 放不下。
  2. 解码一个 token 的主要开销是读一遍权重(受带宽限制),多处理几个 token 几乎不增加时间;而预填充 W = 4096 个 token 要做 4096 倍的矩阵乘计算,变成计算受限。01 号脚本:StreamingLLM 每 token 7.2 ms,recompute 105 ms,慢 15 倍;论文实测最多 22.2 倍。
  3. e⁵ / (e⁵ + 31) = 148.4 / 179.4 ≈ 0.83。输出 = 0.83 × V_sink + 0.17 × 其他 token 的 V 的平均;V_sink ≈ 0 时,输出约为其他 token 的 V 的平均的 0.17 倍——只有很小的贡献,头基本上”什么都没做”。分数差距更大时,输出更接近 0。
  4. 设 K₀ = 0,则它的分数 q·K₀ = 0,在 softmax 的分母里贡献 e⁰ = 1:权重变成 e^{x_i} / (1 + Σ_j e^{x_j}),正是 SoftMax-off-by-one;V₀ = 0,它分到的权重乘以 0,不往输出里加任何东西。
  5. 缓存里的 7 个 token 用位置 [0, 1, 2, 3, 4, 5, 6],新 token 用 7(而不是原文里的 [0, 1, 2, 3, 6, 7, 8] 和 9)。如果 K 存的是加过 RoPE 的,它的旋转角就固定在原文的位置上,无法再”改编号”;而且位置会随着生成一路增长,超出训练长度。所以要存旋转之前的 K,每一步按缓存里的位置重新旋转。
  6. dense 保留了全部 KV,位置一路增长到 600,远超训练窗口 64:RoPE 的旋转角是模型没见过的(长度外推失败)。这和窗口注意力的失败不是同一个原因:窗口注意力的位置在缓存内重新编号,没有外推问题,它失败是因为丢了汇聚 token(只发生在有 <s> 的模型上)。
  7. 层的输出乘以 tanh(α) = 0,所以交叉注意力层自己参数的梯度在一开始都是 0;α 的梯度是 tanh′(0) × (层的输出与损失梯度的内积),tanh′(0) = 1,一般不为 0。所以训练的第一步只有 α 在动;α 离开 0 之后,交叉注意力层的参数才开始得到梯度。新通路是”先打开一条缝,再慢慢学”。(交叉注意力层的参数是随机初始化的,所以它的输出不为 0,α 的梯度才不为 0。)
  8. 336 / 14 = 24,24² = 576。LLaVA-NeXT:(4 + 1) × 576 = 2,880。Qwen2-VL:(1344 / 28)² = 48² = 2,304;3840 / 28 ≈ 137、2160 / 28 ≈ 77,137 × 77 = 10,549。视频:60 × 2 × 64 = 7,680。
  9. 64 × 8 层 × 2 × 8 × 128 × 2 字节 = 2,097,152 字节 ≈ 2.1 MB。LLaVA-1.5:576 × 128 KiB = 75.5 MB,是 Flamingo 的 36 倍(576 / 64 × 32 / 8 = 36)。

9. 延伸阅读 · Further reading

  • StreamingLLM:Xiao et al. (2024),ICLR;代码 github.com/mit-han-lab/streaming-llm。
  • SoftMax-off-by-one:Miller (2023) Attention Is Off By One(博客);量化离群值与注意力的关系:Bondarenko et al. (2023) Quantizable Transformers。
  • KV 的选择与压缩:H2O(Zhang et al. 2023,按累积注意力保留”重要”token)、SnapKV(Li et al. 2024);与第 27 讲的 CacheGen 互补。
  • 长上下文:位置插值(Chen et al. 2023)、YaRN(Peng et al. 2023)。
  • Flamingo:Alayrac et al. (2022),NeurIPS;开源复现 OpenFlamingo。
  • LLaVA:Liu et al. (2023) Visual Instruction Tuning;BLIP-2:Li et al. (2023),Q-Former;Qwen2-VL:Wang et al. (2024),动态分辨率。
  • CLIP:Radford et al. (2021)——对比学习的视觉编码器,几乎所有多模态 LLM 的起点。

10. 全课回顾 · Course recap

这是本讲义的最后一篇。回头看,整门课沿着一条主线展开:一个 LLM 从训练到服务,每一步的瓶颈在哪里、怎样在硬件上把它挤掉。

  • 硬件与编程(第 2–5、12–13 讲):GPU 的线程层次与显存层次、屋顶线、自动微分;TPU 与 JAX / Pallas。几乎所有优化最终都归结为”少读写显存、多做有用的计算”。
  • 模型(第 6–9 讲):Transformer、预训练的 LLM、分词与嵌入、解码与推测解码。
  • 单卡加速(第 10–11、21 讲):算子融合、FlashAttention——同样的数学,换一种计算顺序,避开显存带宽的墙。
  • 分布式训练(第 14–18 讲):数据并行、流水线 / 张量并行、MoE、ZeRO——把模型和优化器状态切开,放到更多的卡上。
  • 压缩与微调(第 19–20、23 讲):量化、GPTQ、PEFT——用更少的位、更少的可训练参数。
  • 服务(第 22、24、26–27 讲,附加讲 A1–A2):连续批处理、RadixAttention、PagedAttention、预填充 / 解码分离、KV 缓存的存储与复用、异构硬件。
  • 对齐与应用(第 25 讲,附加讲 A3–A4):RLHF 的系统、检索增强、流式生成、多模态。

贯穿始终的几个想法:先算账(FLOP、字节、带宽、容量,算出瓶颈在哪里再动手);按阶段、按算术强度挑硬件与并行方式;能复用就别重算(KV 缓存、前缀缓存、检索结果的 KV);不要弄坏已经训练好的东西(LoRA、Flamingo 的 0 初始化门、冻结的 LM)。课程大纲与各讲的对应见 SYLLABUS.md。

← 返回课程目录