上一章《一致性模型与蒸馏》把扩散采样压到一步,但那仍是连续向量——这一章换成离散的「词」。 GPT 之所以能写文章,是因为文字天生是「词」——每个词都能反复用。 图像不是——一张 256×256 的图,颜色值就有近 20 万个,每个值只是一个颜色。 让语言模型生成图片,第一步是 造一套「视觉词汇表」:每 8×8 像素一个编号,256×256 的图变成 32×32 = 1024 个编号。
| 比什么 | 文本 | 图像 |
|---|---|---|
| 基本单位 | 词 / 子词,天然的离散符号 | 像素,0~255 的 256 档整数 |
| 词表 | 现成的(几万到十几万) | 没有——得自己造 |
| 「下一个」是什么意思 | 在几万个候选里挑一个的概率分布 | 也能做成 256 类分类(PixelCNN 就是逐个像素分类的模型),但一个像素只是一个颜色,没有可复用的意思 |
| 序列长度 | 几十到几千 | 一张 512×512 图 = 26 万个像素,太长了 |
所以这一章要做两件事。① 压缩——不能一个像素一个
像把一首钢琴曲变成乐谱。
原始音频是一串采样点——每个点只是一个瞬间的响度,没有「音符」这种能反复用的单位。
乐谱是离散的——「C、E、G」是从有限的音符表里挑出来的。
一旦变成乐谱,就能用语言模型那套「预测下一个音符」的办法来作曲了。
视觉 tokenizer 就是给图像写乐谱的那个东西。
互动 · 连续值切成有限档,到底丢掉了什么
档越少,「下一个」越好预测,但误差越大——这就是离散化那笔交易。
| 部件 | 输入 → 输出 | 在做什么 |
|---|---|---|
| 256×256×3 → 32×32×64 | 把图压成 1024 个 64 维的连续向量(每 8×8 一块) | |
| 码本 Codebook | 1024 个向量 → 1024 个整数 | 这就是「视觉词汇表」:512 个可学习的向量(这章默认按 VQ-VAE 原文的 512; DALL·E 这类大模型用到 8192)。每个连续向量换成离它最近的那个码本向量 |
| 32×32×64 → 256×256×3 | 把量化后的向量还原成图像 |
关键在「最近邻」这三个字:
互动 · 一块切多大,序列就有多长
块越大 → token 越少、越好训,但每一块里的细节也丢得越多。
演示只画了 16 个白点;真实码本想开多大,看任务。VQ-VAE 原文用 512,DALL·E 用到 8192。
拖动橙色圆点(代表
互动 · 真实计算欧氏距离与量化误差
背景那些浅色的块是真实算出来的Voronoi 分区(离哪个白点最近,就归哪一块)——
每个格子都被算了一遍「离哪个码本点最近」,按结果上色。
它告诉你一件重要的事:每个码本点「负责」的是一整片区域,不是一个点。
落在同一片区域里的所有向量,最后都会变成同一个编号——
这就是量化的信息损失从哪来的。
VQ-VAE 有一个看起来很致命的问题:argmin(挑出让距离最小的那个编号)是不可导的。
找最近邻这个操作,它的梯度在数学上处处是 0(除了跳变点)——
反向传播到这里就断了,
z = z_e + (z_q - z_e).detach().detach() 是 PyTorch 里「停止梯度」的写法:前向照常算,反向当它是常数,不往它身上传梯度。① 重建损失 ‖x − x̂‖²(这个记号读作「平方误差」)——
② 码本损失 ‖sg[ze] − ek‖²——让码本向量朝编码器输出靠。
sg 是 stop-gradient(停止梯度):前向照常算,反向当它是常数,所以这一项只推码本、不推编码器。
③ 承诺损失 β‖ze − sg[ek]‖²——让编码器输出朝码本靠,别乱跑。
这里的 β 是这一项的权重(不是别的章那个 β),原文取 0.25。
②拉码本、③拉编码器,两头往中间靠。
为什么反向「假装 zq 等于 ze」不会把编码器带歪?因为承诺损失一直在把 ze 往最近的码上拉, 两者本来就靠得很近;把一个当另一个用,方向和大小差不了多少。
上面两节说「查最近邻」「梯度从旁边抄过去」。这两件事加起来只有三个字母, 但中间有一步最容易卡住:「挑最近的那个」这个动作,梯度是过不去的。 先把这一整步画出来——图看懂了,公式就只剩下抄一遍。
动画 · 前向走一遍、反向再走回来(距离和梯度都是当场算的)
互动 · 公式里每个符号,在图上都是哪一块
悬停公式里的字母——上面那张图里对应的那块会亮(线越粗 = 梯度越大);
∂L/∂z_q 读作「损失对量化向量的导数」。卡上 16 个码是缩小演示,实际可以是 512、8192。
你手上有一本书,但图书馆只认书架上的编号。于是你一本一本比过去,
找到和你手上这本最像的那一册,把它的编号抄下来。
抄下来的编号 = k。后面的人拿到的是一串数字,不是你那本书。
现在的问题是:如果后面的人说「这编号不对」,你怎么知道该改哪本书?
编号和书之间没有连续的关系——这就是 argmin 不可导。
直通估计器的做法是:装作编号就是书本身,把批评原样转给你。
把上面的滑块从 0° 拖到 360°,让 z 在码本上走一圈。
看那个 k:它不会连续地变,而是隔一段突然跳一下。
跳的那一下,就是 z 从这个码的「地盘」走进了隔壁那个码的地盘——
微型图解里那 16 根柱子,最短的那根换人了。
这是 VQ-VAE 最著名的失败模式。想象一下:你造了 512 个词的词汇表,
训练完之后发现很多词根本分不到样本——它们从没被选中过。
原因是
互动 · 码本利用率监视器
困惑度:这些码「等效」有多少个在被均匀使用。
512 个码全都一样常用,困惑度就是 512;只用一个码,困惑度就是 1。
它比「被碰过的码的比例」更能说明问题:只用了 8 个码、但 8 个都很平均,困惑度就是 8(而不是 512)。
(公式是 exp(熵):exp 就是「e 的几次方」;熵衡量使用分布有多分散,均匀时最大,挤到一个码上时为零。)
所以论文里汇报的通常是困惑度——它衡量「有效词汇量」。
它低一定不好(大量码白占参数);高只说明码都用上了,不保证画得好:画质还要看重建损失那一头。
| 修法 | 怎么做 | 效果 |
|---|---|---|
| 码本重启 Codebook Restart | 监控每个码的使用次数,太久没用就把它的向量重置成一个随机 |
最直接有效。把死码「复活」成有用区域 |
| EMA(指数滑动平均)更新 | 不用梯度更新码本,而是用指数滑动平均统计每个码对应样本的均值 | 比梯度更稳。VQ-VAE-2 的默认做法 |
| 减小码本 | 码本开小一点(比如 8192 改 512) | 治标。相当于承认「用不到那么多词」 |
| 换掉量化方式 | 用 FSQ / LFQ(有限标量量化 / 无查找量化)——不需要码本,直接对每个维度做舍入 | 从结构上消灭坍塌问题。见谱系表 |
重启为什么不会马上又死?因为重置是扔到一个真实数据点上;下一批数据里总会有向量离它最近,它就会被用上、被拉着学。
互动 · 困惑度在量什么:有效词汇量
左边均匀 = 每个码都在干活;右边长尾 = 大多数码是摆设。困惑度就落在两者之间。 (演示用 64 个码;真实码本可以开到 512、8192。)
一旦图像变成了整数序列,所有为语言模型造的工具就立刻能用了—— 包括那个最简单的目标:预测下一个。
从图到「一句话」的完整流程
把 2D 的图拉成 1D 序列时,必须选一个顺序。最常用的是光栅顺序
(从左到右、从上到下)。
但这相当于告诉模型:「同一行的右边」比「下一行的左边」更近——
这是一个不太自然的先验(先验 = 做决定前就假定好的东西)。
后来的工作试过各种顺序(Hilbert 曲线、随机排列),
但大多数还是用光栅顺序,因为它最简单,而且效果够好。
互动 · 2D 的图,到底是怎么被拉成 1D 的
同一张图,换一种走法就是另一条序列——而模型只看得见那条序列。
| 模型 | 年份 | 做法 | 意义 |
|---|---|---|---|
| DALL·E 1 | 2021 | 离散 VAE(先把潜变量量化成离散码的自编码器)得到 1024 个 token,用 GPT-3 那一类自回归 Transformer 生成 | 证明了「图像可以当成一门语言来生成」 |
| Parti | 2022 | 同样的路线,但把 Transformer 放大到 200 亿 |
证明这条路也能 scale,但被扩散模型盖过了风头 |
| VQGAN | 2021 | 在 VQ-VAE 上加一个对抗损失(GAN 里判别器那份)和感知损失(拿预训练网络的特征比对),让重建更清晰 | 当时自回归方法里的最好水平;至今仍是很多模型的视觉 |
| Chameleon / Emu3 | 2024 | 用同一个 Transformer 处理文本 token 和视觉 token,真正统一 | 回到「一个架构统治一切」的路线。能同时看图、写文、生成图 |
| VILA-U | 2024 | 视觉 token 用对比学习对齐到文字空间,两类 token 走同一个自回归目标 | 少了一层翻译,理解与生成在同一个空间里发生 |
| 方法 | 怎么量化 | 优点 | 缺点 |
|---|---|---|---|
| VQ-VAE 2017 |
单层码本,最近邻查找 | 开山之作 | 码本容易坍塌(后续文献报告的),重建质量一般 |
| VQ-VAE-2 | 多个分辨率层级,各有一个码本 | 重建质量大幅提升(能生成 1024×1024 人脸) | 层级管理复杂 |
| VQGAN | VQ-VAE + 对抗损失 + 感知损失 | 重建锐利,成为事实标准 | 引入 GAN 就引入了不稳定 |
| RQ-VAE | 残差量化:先量一次,再对残差(这次没量准的那部分)量一次…… | 同样的码本大小下精度高很多 | 序列变长(一个向量变多个 token) |
| FSQ Finite Scalar Quantization |
干脆扔掉码本:把每个维度直接舍入到有限的几档 | 结构上就不会坍塌;实现极简;不需要任何辅助损失 | 能表示的组合数 = 各维档数相乘,只能按这种乘积来定,不好随意设成 8192 这样的数 |
| LFQ Lookup-Free Quantization |
用一个隐式的二值码本 | 训练稳定,比 VQ 少调几项(省掉了码本损失,仍要承诺损失) | 相对新,生态少 |
| 连续 latent(扩散路线) | 不量化,直接用连续向量 | 信息损失最小,重建质量最高 | 不能直接做「预测下一个」,通常配扩散模型 |
这张表其实是两条路线的分岔。离散路线(VQ 系列 → Chameleon):能接自回归,和语言模型统一,但量化天生丢信息。
连续路线(VAE → Stable Diffusion 的
互动 · 残差量化:一级不够,就把剩下的再量一次
拖级数:误差掉得很快,但序列也跟着变长——谱系表里 RQ 那一行就是这个权衡。
| 问题 | 具体表现 | 什么时候真的会痛 → 换什么 |
|---|---|---|
| ① 量化必然丢信息 | 把一个连续向量吸附到最近的码本点,误差一定存在;
这个误差会成为生成质量的天花板, |
细节要求高的场景(小字、纹理)→ 残差量化(RQ),或干脆走连续 latent + 扩散 |
| ② 码本大小是个死旋钮 | 开小了表达力不够,开大了大量码没人用、出现坍塌; 它是结构超参数,训练完不能改 | 数据集或任务变了也不能调 → FSQ(没有要学的码本,每维直接舍入) |
| ③ 序列太长 | 文本一个句子才几十个 token,一张 512×512 图要 1024 个(16×16 一块), 高分辨率更是上万;而自回归是串行的 | 要生成高分辨率图或实时出图 → 多尺度 / 分层 token(先用很少的 token 画出整张图的粗稿,再逐层加细节,每层只补一点),或换成扩散(不串行猜) |
| ④ 顺序是个不自然的先验 | 光栅顺序把 2D 结构压成 1D,等于告诉模型「同一行的右边比下一行的左边更近」 | 图上关系跨行跨列时(长距离依赖)→ 换顺序(Hilbert 曲线等)或扩散 |
互动 · 「序列太长」到底有多长
自回归是串行的:生成一张 512×512 图,要老老实实猜一千多次「下一个」。
2024 年之后,「视觉 tokenizer + 统一 Transformer」这条路线重新热了起来
(Chameleon、Emu3、VILA-U)。原因不是它生成质量更好——扩散模型在画质上仍然领先——
而是:
① 它能让「理解和生成」用同一套东西。扩散模型擅长画,但不擅长「看着图回答问题」。
② 训练目标极简。就是「预测下一个」,不需要第二套数学。
③
回到第 3 节那张图。用鼠标拖动那个橙色圆点,让它从白点附近走到两个白点之间的空隙上。
看右边的 d = …… 那个读数——它会从接近 0 涨到 0.13 以上;
同时下面「量化结果 = k」那个编号,会在你跨过分界线的一瞬间跳一下。
上面「④ 学习即压缩」是《第一性原理》那章列的七条主线之一;下面是这一章撞上的三条暗线(暗线编号和主线不是一套)。
| 暗线 | 这一章的回答 |
|---|---|
| B 什么被牺牲了 | 用细节换「可预测」。量化误差消不掉,只能取舍:码本开大误差小,
但字表变大更难学、更容易有没人用的码;再加上把 2D 拉成 1D 时选的那个光栅顺序,
等于额外告诉模型「同一行的右边比下一行的左边更近」。 换来的是:图像终于有了词表,可以用交叉熵目标训练了。 |
| D 跑在什么上 | 查码本这一步几乎不做计算,时间全花在把码本从显存读出来。
最近邻查找 = 拿一个 64 维向量和 512 个向量逐一比距离。查码本每读一个数只做一次比较,
读得多、算得少,时间就耗在读上;码本太大,放不进 GPU 上那块又小又快的缓存,
只能每次从显存里慢慢读。 反过来, |
| F 违背了哪个直觉 | 「梯度必须有定义才能训练」是错的。
argmin 的导数处处为 0,按教科书这根本不能训;
但直通估计器前向用真值、反向假装自己是恒等映射,照样训得出来。 这一章还有第二个反直觉:码本不用梯度下降、改用 EMA 统计更新,反而更稳。 两件事合起来说明:能训练的不是「可导」,而是「信号能传回去」。 |
它接住了上一章的什么:《一致性模型与蒸馏》把采样压到一步,但仍是连续向量。这一章换成离散的「词」—— 更早的《自编码器》和 《VAE 变分自编码器》是零件,只多一步「查字典」。
它给下一章留了什么:下一章 《生成模型家族图谱》 要把八章学到的路线摆到同一张桌子上比。这一章给出的是其中「离散 → 自回归」那一格的全部底牌—— 包括它为什么赢不到最后,以及为什么 2024 年之后又回来了。
任务只有两个:把图压成一串「视觉词」(好做「预测下一个」),同时把序列压短(好训练)。
三步是编码 → 查码本取最近邻 → 解码;argmin 不可导,所以靠直通估计器把梯度抄过去。
最大的坑是码本坍塌——大部分码没人用;修法是码本重启 / EMA 更新,或者干脆学 FSQ 把码本扔掉。
它和扩散模型代表两条分岔:离散 → 自回归,连续 → 扩散;
2024 年之后「统一 Transformer」又把它推回台前,因为理解与生成能共用一个模型。
上面讲的都是「够用」的版本。想往下挖,这里有三个入口—— 它们不是必修内容,是给想再往前走一步的读者准备的。