生成模型
是怎么长出来的
从一团噪声,到一张图、一段视频、一句话。
沿着 24 章的线索,亲手改参数、跑实验,
看见生成模型内部发生的事。
第0章 工具箱:这本书只要你会六件事
开篇故事第 0 集 · 玻璃门那边先亮起来的淡蓝色这模型也太真实了
来访登记表摊在沈砚的膝盖上,第二栏本该填门禁卡编号,此刻只是一道空着的横线,前台姑娘已经把电话打给物业两遍,得到的回复都一样,系统那边还没把他的名字同步过来。他索性把表格合上,抱着电脑包站在那扇没有把手的玻璃门前,机房那台空调外机就架在走廊尽头,嗡嗡的风扇声隔着墙都能听见,门里头没有窗,灯却亮着,隐约还能看见一块屏幕上停着一片淡蓝色,没人注意到外面站着个人。
这是「回声」复现计划开始的第一天,也是沈砚在这儿的第一天。他写了四年后端,离职那天把工位上的绿植送给了同事,只背走一个电脑包,面试时听对方说起,这个组要从零复现图像、视频、语音三条线,全部开源,手上还有一套自己搭的模拟系统,改一个系数就能重算一遍,他当场就动了心,此刻却连一张门禁卡都还没有。
敲到第三下,玻璃门被人从里面拉开,是显存刺客,手里端着一杯凉透的咖啡,说抱歉,账号那边卡在靳工那儿没批,门禁卡更麻烦,得等物业系统同步,这两天先借他的工位凑合用。沈砚跟着走进去,一眼瞥见桌上那块屏幕,显存刺客刚才显然在自己账号里试着调一个参数,屏幕上停着一行淡蓝色的字。
系统提示本次调整未通过:该方向使损失从 0.49 升至 0.53,建议沿梯度反方向重新调整。
“这就是往错的方向拧了一下。”显存刺客把鼠标推到他面前,说不信就自己拖着试试,沈砚伸手把那个数从 2.1 挪回 2,损失应声跌回 0.49,他这才明白,这块面板认的不是谁的感觉,是每一步算出来的方向到底对不对。
靳工从里屋探出头来,没什么寒暄,劈头就问他,一张 128×128 的黑白照片和一秒 16kHz 的录音,数字个数谁多谁少。沈砚心算了一下,黑白只有 1 个通道,128 乘 128 是 16,384,而 16kHz 本身就是每秒钟的数字个数,录音时长正好是 1 秒,直接就是 16,000,两边差不了多少,是同一个量级。靳工点点头没再追问,转身要走,又停下来问了一句,一个均值 10、标准差 2 的分布,上下各摆开一个标准差,也就是到 12 那道线为止,一百次里大概能落进去几次。沈砚想起前台桌上贴的一张告示,顺口答了句 68%。
“算对了,两个标准差以内是 95%,这两个数往后天天用得上。”靳工说完转身翻出自己账号里的一条记录,屏幕上又跳出一行新的字。
系统提示本次采样:均值 10.00,标准差 2.00,抽样结果 11.42,落在一个标准差以内。
那一行数字每刷新一次都不一样,沈砚这才明白,这台机器认真做的从来不是算出一个平均数,它是真的在掷一次骰子,掷出来的每一个都是实实在在的结果,不是一个笼统的期望。
给网络多加一层,算不算这一轮训练出的力气
1L炼丹废人今天想起个事,要是给网络多加一层,模型因为这样变强了,这算不算训练本身出的力,我总觉得说是也不是。
2L复现之鬼不算,这事材料里写得明明白白,训练自始至终只调整已经存在的旋钮,不会凭空多出新的旋钮,加一层是造网络的人在动手之前就定好的事。
3L显存刺客楼上说得对,不过我更在意加这一层显存够不够用,参数量翻倍这种事不提前算清楚,半夜跑着跑着就爆了。
沈砚在借来的工位上坐到很晚,胸口贴着的临时来访条一直没敢撕,他的《潜空》账号还没批下来,靳工临走前只丢下一句,账号批下来那天,他就能亲眼看见那块空着的白板第一次被填满。
系统结算 · 本章考点
- 一张 512×512 的彩色照片按「高×宽×通道」相乘,是 786,432 个数,通道数从 3 降到 1 就是黑白照片
- 128×128 的黑白照片只有 1 个通道,算出来是 16,384 个数,和 1 秒 16kHz 录音的 16,000 个数几乎是同一个量级
- 网络给一张猫照片打分 0.3,目标是 1.0,均方误差损失是 (0.3 − 1.0)² = 0.49
- 把这个数从 2 调到 2.1,损失反而从 0.49 涨到 0.53,说明这个方向调错了,该往回调,这就是梯度在起作用
- 高斯分布有两个锚点:68% 的采样落在均值±一个标准差以内,95% 落在±两个标准差以内
- 均值 10、标准差 2 的分布,上下扩到 12 那道线正好是一个标准差,100 次采样里大概有 68 次会落进去
翻开一篇生成模型的论文,你大概会在摘要还没读完的地方就撞见一个下标叠着下标的公式,旁边跟着几个从没见过的希腊字母,然后就想合上电脑。这一章要做的事很小:把接下来二十三章会反复用到的六件事,用二十分钟讲完,每件事配一道能马上自测的题。读完这一章,你不会变成数学家,但论文里那些公式会从一整页天书,变成几个认识的老朋友换了身新衣服。
学完这一章你应该能做到
- 说出一张 512×512 的照片和一秒 16kHz 的录音各自是多少个数
- 讲清向量、矩阵、通道这三个词分别指什么,矩阵乘法在做什么
- 用一句话说清神经网络训练时到底在调什么
- 说出损失函数和梯度各自是一个什么量
- 报出高斯分布的 68/95 这两个锚点数字
- 讲清为什么生成模型学的是一整个分布,不是一张图
0.1 一张照片、一段视频、一段录音,怎么变成一堆数字
把一张 512×512 的彩色照片放大到能看清单个像素,每个像素点其实是三个数:红、绿、蓝各自的亮度,通常是 0 到 255 之间的一个整数。整张照片因此是 512×512×3 = 786,432 个数,排成一个高、宽各 512、最后再叠三层的数字格子。这类可以有任意多个轴的数字格子,统一叫张量:一串孤零零的数是一维张量,一张表是二维,一张彩色照片这样的是三维。视频只是在这个立方体后面再接一个轴——时间,把很多张照片按拍摄顺序叠起来,变成四维。声音完全是另一回事:它不占宽和高,只有时间这一个轴,每一个瞬间记一个数,代表那一刻的气压。一秒钟、16000 次采样的单声道录音,就是 16,000 个数排成一条线,比刚才那张照片的格子扁得多,却也长得多。
张量(tensor):可以有任意多个轴的数字格子。一条数列是一维,一张表是二维,一张彩色照片这类多轴数组统称张量,轴的个数越多,说明这堆数被组织的方式越复杂。
一张 128×128 的黑白照片(黑白只有 1 个通道,不像彩色照片有 3 个)总共是多少个数?它比一段 1 秒 16kHz 的录音,数字个数是多还是少?
变式:如果这张照片不是黑白也不是彩色,而是带一个「透明度」通道的 4 通道图(常见的 PNG 格式),128×128 总共是多少个数?
0.2 向量、矩阵、通道是什么,矩阵乘法又在做什么
上一节把 512×512×3 这三个数分别叫了高、宽、通道,这一节把它们各自的名字钉牢。只有一串数、没有表格结构的一堆数,比如 [0.2, −1.4, 3.0],叫向量——可以把它想成「一个东西的若干个属性写成一排」。把好几个向量摞成一张有行有列的表,就是矩阵。而通道说的不是「另起一张表」,是在同一个行列位置上,往纵深方向再叠几层:512×512 这个格子里的每一个位置,都摞着三层数(红、绿、蓝),这三层就是三个通道。矩阵乘法在干什么:把一组数按一组权重重新混合一遍,输出的每一个数,都是输入那一整排数按各自份额凑出来的加权和。比如输入是 [2, 3],权重是 [0.5, 1],乘出来就是 2×0.5 + 3×1 = 4——一个新数,由两个旧数各自贡献一部分拼出来。后面几乎每一章的网络内部,都是成千上万次这种加权重新混合摞在一起。
向量(vector):一串有顺序的数。矩阵(matrix):把若干向量摞成的一张有行有列的表。
通道(channel):在同一个行列位置上纵深叠着的几层数,RGB 照片每个像素点上叠的那 3 层就是 3 个通道。
输入向量是 [4, 1],权重是 [0.25, 2],按「加权和」的算法,输出的这个数是多少?
变式:如果权重改成 [2, 0.25](两个权重对调),输出会变成多少?为什么和原来不一样?
0.3 神经网络训练的时候到底在调什么
神经网络说到底是一个函数:塞进去一串数,吐出来一串数。输入可以是刚才那张照片展开的 786,432 个数,输出可以是一个数——这张照片是猫的可能性有多大。中间发生的事,就是上一节那种加权重新混合,一层接一层地做很多次,每一层用到的权重都是一批独立的数,摞起来常常有几百万、几千万个。这些权重就是旋钮:最开始它们是随机填的,网络这时候输出的是胡言乱语。训练不改网络的结构,只改这些旋钮的数值——一次训练看一批例子,把每个旋钮往某个方向拧一点点,很多批之后,网络吐出来的数就越来越像该有的样子。学会认猫这句话翻译过来,就是找到一组能让网络在猫照片上稳定吐出高分的旋钮数值。
神经网络(neural network):由很多层加权重新混合堆起来的一个函数,输入一串数,输出一串数,层与层之间的权重就是训练时被调整的对象。
有人说:「给神经网络多加一层,就是训练的一部分,因为网络因此变强了。」这句话错在哪?
变式:如果两个网络结构完全相同,一个训练了十万步,一个只训练了十步,它们之间真正不同的是什么?
0.4 损失函数和梯度,怎么知道往哪拧
光知道要拧旋钮还不够,得知道往哪边拧、拧多少。损失函数负责第一件事:把现在的输出离目标差多远压缩成一个数。假如网络对一张猫照片给出的分数是 0.3,而目标是 1.0(满分确定是猫),一种常见的算法是把两者的差平方:(0.3 − 1.0)² = 0.49。这一个数越大,说明当前这组旋钮越差;越接近 0,说明网络吐出的答案越接近目标。梯度负责第二件事:对每一个旋钮单独回答「把你稍微调大一点,这个 0.49 会变大还是变小,变化多快」。把所有旋钮都按各自梯度给出的方向,反着挪一小步——冲着让损失变小的方向走——这一整套算损失、算梯度、反向挪一步的循环,重复几十万次,就是训练。这套循环有个名字,梯度下降。
损失函数(loss function):把「当前输出离目标差多远」压成的一个数,数越小说明当前这组旋钮越好。
梯度(gradient):对每个旋钮分别给出「往哪个方向调,能让损失这个数变小」的方向和快慢。反复按梯度的反方向调整旋钮,这套循环叫梯度下降(gradient descent)。
某个旋钮从 2 调到 2.1(调大了),损失从 0.49 涨到了 0.53。下一步该把这个旋钮调大还是调小?
变式:如果调大这个旋钮之后损失反而从 0.49 降到了 0.31,下一步该怎么调?
0.5 为什么说采样就是掷一次不均匀的骰子
高斯分布是那条最有名的钟形曲线:中间高,两边对称地往下滑,由两个数完全确定——均值(曲线最高点在哪)和标准差(曲线有多宽)。它有两个好记的锚点:反复从这条曲线上抽数字,大约 68% 的结果会落在均值±一个标准差这个范围里,大约 95% 会落在均值±两个标准差里。采样说的是真的抽一个数出来,不是去算这条曲线的平均值——就像掷一颗不均匀的骰子,六个面出现的概率不一样,但每次掷出来的,都是实实在在的某一面,不是六个面的加权平均。均值是 0、标准差是 1 的高斯分布是这本书里出现次数最多的一条曲线,后面每一章的加噪声、随机初始化,抽的都是它。
高斯分布(Gaussian distribution),也叫正态分布(normal distribution):由均值和标准差决定的钟形曲线,68% 的抽样落在一个标准差以内,95% 落在两个标准差以内。
采样(sampling):按照某个分布,实际抽出一个具体的数(或一整组数),不是计算这个分布的平均值。
某个高斯分布均值是 10,标准差是 2。连续采样 100 次,大概有多少次会落在 8 到 12 之间?
变式:同一个分布,4 到 16 这个范围大概能盖住采样结果的百分之多少?
0.6 期望、方差,以及为什么全书都在说分布
期望是如果采样无穷多次,平均下来会是多少——按每个结果出现的可能性加权平均,不是简单地把几个数加起来除以个数。方差衡量这些采样结果散得有多开:每个采样值离期望有多远,把这个距离平方之后再求期望,散得越开,方差越大。这里有一条后面反复用到的性质:两个互相独立的随机数相加,得到的新数,方差是两者的方差直接相加,不需要开根号也不需要打折。全书反复出现分布这个词,是因为生成模型要学的从来不是一张确定的图——给定画一只猫这个条件,世界上存在无数张不同的、都合理的猫的照片,模型要学会的是这一整群照片背后每一种猫长什么样、各自有多大可能性,也就是这群照片的分布,而不是死记住某一张。生成一张新图,做的正是上一节说的采样:从学到的分布里掷一次骰子,抽一个具体的结果出来。
期望(expectation):按每个结果出现的可能性加权平均之后的那个数,也叫均值。
方差(variance):采样结果离期望有多远的平方、再求期望,衡量散开的程度;两个独立随机数相加,方差直接相加。
两个互相独立的随机数 X 和 Y:X 的期望是 2、方差是 1;Y 的期望是 5、方差是 3。把两者加起来得到 Z = X + Y。Z 的期望和方差各是多少?
变式:如果 X 和 Y 不是独立的,比如 Y 就是 X 本身乘以 2,Z = X + Y 的方差还能直接把两个方差加起来算吗?
留给你的问题
如果一个神经网络的任务,是学会 0.6 节说的那种一整群图片背后的分布,那它面对同一个条件(比如「画一只猫」),每次的输出应不应该是同一张图?为什么?
不看材料,把这六件事各用一句话说清楚
图像、视频、声音都是数字数组,只是轴的个数不同;向量矩阵通道说的是数怎么排、怎么在纵深叠;神经网络是能被调的函数;损失函数量距离、梯度指方向;高斯分布是钟形曲线,采样是真的掷一次骰子;期望是加权平均,方差是散开的程度,分布才是生成模型真正要学的东西。
第1章 三条线,同一个问题
开篇故事第 1 集 · 靳工头一次说“材料里没写”这模型也太真实了
白板最上面那一行,334,540,800 这串数字刚写到一半,沈砚手里的粉笔头啪地一声断成两截,短的那半截飞出去,滚到了显存刺客的鞋边上。
这天一早,靳工在群里回了他一句,账号已经批下来,让他把图像、视频、语音这三条线各自的原始数字个数算一遍,写在白板上,算是进「回声」的第一份作业。复现计划推进到第三天,沈砚这才第一次真正点开自己的《潜空》账号,屏幕上一片空白,什么记录都还没有。
图像那条线他很快写完,1024×1024 的分辨率不难;轮到视频,他先算一帧——1280×720 的画面,三个通道乘起来是 2,764,800,这段素材是 5 秒、每秒 24 帧,121 帧乘上去,总数定格在 334,540,800,比同分辨率的一张照片直接多出两个数量级。旁边显存刺客随口添了一句,要是换成 1920×1080 的 1080p,边长各拉长 1.5 倍,总数字个数可不会只涨 1.5 倍,是要涨到 2.25 倍。
轮到语音,沈砚下意识地想按视频那一套算——先算一帧,再乘帧数,可这段素材是 10 秒、24kHz,靳工在旁边只说了一句,声音根本没有帧这个概念,直接拿采样率乘时长就是全部。10 秒乘 24000,是 240,000,反而比那张百万像素级的照片还要小。
系统提示张量校验:输入 10 秒、24kHz 单声道音频,总长度 240,000,未检测到宽、高或通道维度。
“视频说到底就是更大的图像,显卡堆得上去,这道墙迟早能硬扛过去。”显存刺客盯着白板上那串数字,说完自己先笑了一下,谁也没接这句话。
视频那道墙,到底是不是图像那道墙的放大版
1L显存刺客白天说的那句我认真想过,视频的数据量摆在那儿,两个数量级不是开玩笑的,只要卡够多、显存够堆,这道墙我看不出有什么过不去的。
2L复现之鬼楼上这话经不起问,你生成第 5 帧的那一刻,模型手里要是压根没有看过第 4 帧的通道,堆再多卡也补不上这条连接,两帧照样对不上。
3L炼丹废人说得对,我以前也觉得视频就是图片摞起来的事,后来才知道这堵墙不是变大了,是压根多出来一种关系。
沈砚看着白板上那三个数字,图像、视频、语音,忽然想起面试时听说的那句话——这套从简单分布走到复杂分布的路子,三条线都在走,那换成蛋白质结构或者别的三维数据,是不是也一样能走通,数据量又该有多大。靳工盯着那三个数字愣了几秒,这是沈砚头一次见他答不上来。
“材料里没写这个,没人告诉过我们答案,分子结构算不算同一套框架能装下的东西,回声眼下也验证不了。”靳工说完转身去接水,杯子在桌上磕出一声轻响。
沈砚把写第一版生成器的活揽了下来,说这几天就能写出来,靳工只回了一句,写完了记得叫上大家看,至于会写成什么样子,那天在场的人心里其实都还没数。
系统结算 · 本章考点
- 5 秒、720p(1280×720)的视频,一帧是 1280×720×3 = 2,764,800 个数,121 帧乘起来总共 334,540,800 个数
- 同一段视频换成 1920×1080 的 1080p,边长各拉长 1.5 倍,总数字个数不是涨 1.5 倍,是涨到 2.25 倍
- 10 秒、24kHz 的录音是 240,000 个数,比一张百万像素级的照片还要少,因为声音没有「帧」这个概念,没有宽和高
- 生成第 5 帧那一刻,模型手里要是没有看过第 4 帧的通道,堆再多卡也补不上这条连接,这才是视频真正多背的墙
- 图像、视频、语音之外还有没有第四条同样的线,比如蛋白质结构,这件事材料没有给出答案
设想有一个模型,见过一百万张猫的照片,没有两张长得一样。你让它画一只猫,它翻遍这一百万张记忆,得给你一个交代——但它该给你哪一张?世界上没有一张标准猫,这一百万张里的每一张都是合理的答案,也没有哪一张比别的更该被选中。这个看似只属于猫的小麻烦,翻译成数学语言之后,会变成后面二十二章几乎每一页都在解的同一个问题。
学完这一章你应该能做到
- 说清生成模型要学的是什么样的一件东西,为什么它面对同一个条件时每次输出都该不一样
- 算出 1024² 图像、5 秒 720p 视频、10 秒 24kHz 语音各自的原始数字个数
- 说出图像、视频、语音三条线各自在「从简单分布走到复杂分布」这条路上多背的是哪一堵墙
- 讲清本站三种证据的读法,以及为什么要分开读、分开写
- 看懂本站 24 章的知识依赖图,说出视频线和语音线各自在哪一点上分岔
- 写出一个能采样的一维混合分布,并用直方图验证采样结果落在该落的地方
这一层要给整本书搭上第一块能跑的砖:一个比真实图片分布简单无数倍、却已经没法用一个公式写全的一维分布,以及从它里面掷一次骰子的办法。第 0 章讲的都是零件,这里第一次把「分布」和「采样」这两个词用在一个真正需要它们的例子上。
1.1 让机器画一只猫,它该给你哪一张
一个模型见过一百万张不重复的猫的照片。你让它画一只猫,它应该输出什么?
答完再看
第一个选项的麻烦在于,一百万张猫里根本不存在一张公认的「最典型」——胖猫、瘦猫、趴着的猫、跳起来的猫,谁也不比谁更该被选中,这个「标准答案」从一开始就不存在。第二个选项听起来像是个稳妥的折中办法,第 2 章会把它认真走一遍,然后看着它露出马脚——逐点求平均得到的东西,接近的是一团模糊的色块,不是任何一只真实的猫。第三个选项才是这本书接下来要建的机器该干的事,而且「每次都不一样」不是缺陷,是必须做到的事。
选项三点破的,是这一整本书要处理的核心麻烦:给定一个条件(画一只猫、说一句话、拍一段海浪),符合条件的合理结果从来不是一个,而是一整群,模型要学的正是第0章讲过的分布——这一群结果背后,每一种具体样子各自有多大可能性。生成一张新图,做的是第0章那个动作:从学到的分布里采样一次,抽一个具体的结果出来,不是去计算这一群结果的平均值。
问题在于,模型不可能凭空生成这个分布,得有一个起点。这个起点必须简单到我们随时能从它身上掷一次骰子——最常用的选择就是第0章那条均值 0、标准差 1 的高斯分布,一个完全由两个数字决定、闭着眼睛都能采样的东西。终点则是真实世界里那一百万张猫的照片、或者一段真实的海浪录音,复杂到没有人能写出它的公式,我们手里只有一堆真实存在过的样本,没有描述它的解析表达式。画一只猫要解的问题,翻译成一句话就是:找一条从起点到终点的路,把「从简单分布采样」这件容易事,变成「从复杂分布采样」这件难事。图像、视频、语音这三条线,接下来二十二章要解的,都是这同一句话的不同版本。
起点:一个我们完全掌握的分布
均值 0、标准差 1 的高斯分布,由两个数字唯一确定,第 0 章那两条 68/95 的锚点就能描述它的全部形状,随时能采样,成本几乎为零。
终点:一个我们只见过样本、从没见过公式的分布
一百万张猫的照片,或者互联网上所有猫的照片,背后确实存在一个分布,但没有人写得出它的解析表达式——我们只看得到它抽出来的一个个具体样本,看不到它本身长什么样子。
差别在:一头是完全透明、随手可采的分布,另一头是只见其果、不见其形的分布,整本书要解的问题就是把这两头连起来。
如果一个生成模型每次面对同一个提示词「画一只猫」,都输出一模一样的一张图,这更接近「学会了分布再采样」,还是更接近「算出了一个平均值」?
变式:如果模型每次输出的图都不一样,但翻来覆去就那么五六种样子,这更像是学到了一个丰富的分布,还是学到了一个只有五六个点的分布?
1.2 图像、视频、语音,哪条线的原始数字个数最吓人
起点那条高斯分布,第0章已经讲完了它的全部细节。终点这一头呢?终点的第一层麻烦,甚至还轮不到「分布长什么样」,纯粹是「一条数据本身有多大」。这件事图像、视频、语音三条线的答案差得很远,而且差出来的方式会决定后面好几章的日程。
自己推一遍:从一张图的个数,推到一段视频、一段语音的个数
第0章算过一张 512×512 的彩色照片是多少个数。现在换成一张 1024×1024 的照片,总共是多少个数?
想好了再看
1024×1024×3 = 3,145,728。只是把第0章那条公式原样代入更大的高和宽,通道数还是 3 不变。
现在从一张照片变成一段视频——视频是很多张照片按时间顺序叠起来的。取一段 5 秒、24 帧每秒的 720p(1280×720)视频,帧数记成 121。该怎么把「一帧的个数」变成「整段视频的个数」,总共是多少?
想好了再看
先算一帧:1280×720×3 = 2,764,800。视频只是在这基础上再乘一个新的轴——帧数,所以整段是 2,764,800×121 = 334,540,800。从「一张图的个数」到「一段视频的个数」,动作只是多乘一个数,但乘出来的结果会因为这多出来的一个轴,膨胀得远比想象中快。
声音呢?一段 10 秒、24000 Hz 的录音,是不是也该「先算一帧、再乘帧数」这样算?
想好了再看
不是,声音根本没有「帧」这个概念,只有一条时间轴,每个采样点一个数。10×24000 = 240,000 个数,一步到位,不需要经过「先算一帧」这一层。图像和视频共享空间这两个轴,声音完全没有空间轴,这是它和前两者最本质的结构差异。
三个数字放在一起看,反差很扎眼:1024×1024 的图像是 3,145,728 个数;这段 5 秒 720p 视频是 334,540,800 个数,比同分辨率的一张图片多出两个数量级;10 秒钟的语音反而只有 240,000 个数,比那张百万像素级的图片还要少。视频用「多乘一个轴」这一个简单动作,就把体量甩开图像和语音一大截,这也是为什么第 9 到 13 章要用整整五章篇幅,专门处理怎么把这个数字压下去。
图像与视频:数字排成一个格子
1024×1024 的照片,数字按高、宽两个空间轴排列,边上再叠 3 个通道;视频只是在这基础上再接一个时间轴,变成四维。不管加多少轴,空间这两个维度一直都在。
语音:数字排成一条线
10 秒钟的录音只有时间这一个轴,没有高、没有宽,也没有通道,240,000 个数老老实实排成一条线。少了空间轴,也就少了左边和右边、上边和下边挨着这类关系需要处理。
差别在:图像和视频天生带着空间结构,语音天生没有——这个结构上的差异,会在下一节讲「三堵墙」的时候,决定各自最难啃的是哪一块。
如果把视频的分辨率从 720p(1280×720)换成 1080p(1920×1080),帧数和时长都不变,总数字个数会变成原来的多少倍?
变式:如果分辨率不变,帧数从 121 翻倍到 242(约合 10 秒),总数字个数变成原来的多少倍?
为什么计算一段语音的原始数字个数,不需要像视频那样先算「一帧」,再乘帧数?
变式:如果给这段语音也配上一段同步的画面,变成一段视频,这时候「一帧」这个概念还成立吗?
1.3 同一个问题,三条线各自多出来的是哪堵墙
图像、视频、语音这三件事,除了都要解「从简单分布走到复杂分布」这同一个问题,你觉得它们各自还多背了一个什么麻烦?
答完再看
第二个选项说的「存储更大」确实是真的,视频的显存开销后面会反复出现,但那只是上一节体量差异的自然结果,不是算法层面真正卡住人的地方——卡住人的从来不是「装不下」,是「装下了也不知道怎么让内部各部分互相对上」。第一个选项把视频简化成了「更大的图像」,漏掉了时间这个全新的轴带来的全新要求:一张图内部各处对得上,不代表一千张图按顺序排列还能对得上。
选项三点出的三堵墙,接下来分别看。图像要处理的是像素之间隔得很远也要互相呼应这件事——两只眼睛的颜色要配对,对称的花纹要对齐,这类关系能跨越大半张图,而最直接的做法往往一次只看得见一小块邻域,够不着这么远。视频把这堵墙原样继承下来,还要再加一条:这一帧和下一帧必须是同一个世界里合理的连续动作,一步走错,画面就会闪烁或者穿模。语音的两堵墙是完全不同的性质:相位是波形里人耳几乎听不出、却会让逐点比较的误差公式极其敏感的一个量;实时则是一条图像和视频生成过程里几乎不存在的硬指标——很多场景要求生成的速度追得上播放的速度,慢一拍都不行。
视频的墙:同一种关系,铺得更开
图像要处理的「像素之间要互相呼应」这件事,视频全部继承下来,还要再加一条「这一帧和下一帧要连得上」。麻烦的性质没变多少,只是关系的范围从一张图内部,扩大到了一整段时间上的每一帧之间,外加数据量先天就大两个数量级。
语音的墙:两件图像和视频都不用管的事
相位是波形里人耳几乎听不出、却会让逐点比较的误差爆炸的一个量,图像和视频完全没有这种肉眼看不出但数学上要命的维度。实时是另一件事:语音经常要求生成的速度追得上播放的速度,图像和视频的生成过程几乎从不背负这条硬指标。
差别在:视频的墙更像是图像的墙的放大版,语音的两堵墙是图像和视频都不需要处理的、全新的东西。
答辩:如果我是审稿人
你说视频的墙只是图像的墙的放大版,可视频要生成的是「运动」,这是静态图像里压根不存在的东西,这难道不是一种全新的墙,而不只是放大?
参考防守(先自己组织语言再看)
这个质疑站得住一部分。运动确实可以理解成「帧间关系」的一种具体表现,不是完全独立于「像素间关系」的新东西——一个物体在下一帧该出现在哪里,本质上还是一种「这个位置的像素该长什么样」的问题,只是条件里多了「上一帧长什么样」。但生成合理的运动,确实需要模型掌握某种物理直觉——运动要连贯、要符合惯性、不能违反重力——这比静态图像内部的空间一致性多了一层,算作放大之外的真实新增并不为过。更诚实的说法或许是:视频的墙一部分是放大(同一种关系铺得更开),一部分也是质变(时间上的因果与物理合理性,是静态图像完全不需要面对的一种全新维度)。这本书后面处理视频时,会看到工程上的解法(潜空间、时空 patch、注意力窗口)确实大量沿用图像线的机器,但「运动是否合理」这一层判断,靠的还是训练数据里大量真实的运动模式,不是单纯把图像的机器搬过来就自动继承的。
有人说:「只要用足够多的视频数据训练一个图像生成模型,让它见过很多一帧一帧的画面,它自然就能学会生成视频,不需要专门解决时间一致性这堵墙。」这个说法站不住脚的地方在哪?
变式:如果模型看的不是单独的一帧,而是一次性看两帧(当前帧和前一帧)作为输入,这样是否就完全解决了时间一致性这堵墙?
1.4 论文自己说的话,要跟本站自己推出来的话一样信吗
接下来二十二章会写满数字:某个调度在第几步跌破一半、某个采样器比另一个快多少倍、某个模型的 FID 分数是多少。这些数字的来路并不一样,读的时候也不该用一个尺度去信。本站把数字分成三种读法,不是为了摆出一套复杂的标注系统,是因为混着读会出事——把论文作者自己实验出来的一个分数,和本站自己用公式当场推出来的一个数字,用同一种口气讲出来,读者会分不清哪个数字旁边站着一个可以马上验证的按钮,哪个数字背后是别人做过、本站没有重新做过的一次实验。
第一种数字,本站在正文里直接给出,不加任何限定语,因为旁边往往就跟着一个能让你自己按一下验证的实验室;第二种数字是论文作者自己报告的实验结果,正文里照常写出来,不会句句加免责声明,但每一章末尾会有一处折叠起来的提醒,一次性说清这一章里哪几个数字属于这一类,没有经过第三方复现。这两种数字在正文里读起来几乎一样干脆,区别全在能不能现场验证。
第一种:本站自己算出来的
第 3 章会算出,按 DDPM 那套系数,加噪一千步之后原图的信号系数是 0.006353。这个数字任何人拿到同一套公式都能重新算出来,旁边就有一个实验室能现场按一下核对,不需要相信任何人。
第二种:论文作者自己报出来的
第 3 章会提到,论文作者说用线性调度训练的模型,跳过反向过程最多 20% 的步数,FID 几乎不变。这类数字来自作者自己的实验,本站没有重新跑一遍去验证,只能在章末的折叠提醒里说清楚。
差别在:一个数字背后站着一个现在就能按的按钮,另一个数字背后站着的是别人做过、没人重新做过的一次实验。
第三种数字是本站自己从多个来源交叉推出来的、或者论文没写清楚、要去翻配置文件才查得到的,这一类不会拖到章末才交代,而是在它出现的那一句话里就直接说明来路,比如「这个数论文没给,是从社区镜像仓库的配置文件里读到的」这样一句话,紧跟在数字后面。
某篇论文的作者写道:「我们的模型生成一段视频只需要 3 秒。」你手上没有这篇论文的代码和权重,只看到了这句话。这个「3 秒」属于哪一种读法?后来你自己下载了他们公开的代码,在自己的显卡上跑出来也是 3 秒左右,这句话在这本书的读法里会因此改变吗?
变式:如果这篇论文完全没提生成速度,是本站找到了他们的开源仓库,读代码算出来的耗时,这又该归到哪一种?
答辩:如果我是审稿人
把论文作者自己报的分数都当第二种数字直接写进正文,只在章末提一句没有第三方复现,这会不会让读者对论文数字过度信任?为什么不干脆都不用这些数字,只用本站自己能算出来的?
参考防守(先自己组织语言再看)
完全不用的代价太大:很多真正重要的事实——GAN 和扩散模型生成质量到底差多少、某个采样器到底比另一个快多少倍、某个声码器的合成速度提升了几个数量级——根本没法从零推导出来,只能来自实测,本站没有几百块 GPU 去重新跑一遍每一篇论文的实验。本站的选择是把「这类数字没有第三方复现」这件事本身当成读者应该知道的背景信息,用一次折叠的提醒交代清楚,而不是把每一条这样的数字都拒之门外,也不是假装本站验证过而其实没有。这确实要求读者多一分警惕,但完全不用这些数字,读者失去的信息只会更多,而不是更可信。
1.5 24 章怎么连起来,三条线在哪分岔
把接下来二十二章画成一张图,会看见一件从上一节就能猜到的事:图像线排在最前面,也压得最长;视频线要等图像线那一整套机器搭完才分岔;语音线在第 1 章这个分岔点上,几乎立刻就走了自己的路。
根据图 1 和上一节讲的三堵墙,解释为什么语音线选择在第 1 章之后就直接分岔,而视频线要等到第 8 章才分岔,而不是反过来?
变式:如果未来出现一种语音模型,直接照搬了图像线第 8 章的流匹配技术,这是否说明图 1 画的分岔位置需要改?
对你而言未知图像、视频、语音之外,还有没有第四条走同一套路子的线
这一章把图像、视频、语音三条线归到了同一个问题下面:从一个能随便采样的简单分布,走到一个复杂到写不出公式的真实分布。这套框架显然不止能装下这三样东西——分子结构、蛋白质折叠、三维场景,本质上都是复杂到没有公式、只有一堆真实样本的分布。这些领域是不是真的在用同一套加噪、去噪、流匹配的机器,还是各自的数据结构逼出了完全不同的解法,这本书不会展开,但这是判断这套框架边界到底有多宽的一个直接办法。
先做这一步:挑一个本书没有覆盖的模态(比如蛋白质结构或者分子图),找一篇用扩散模型或流匹配做生成的论文,对照本章「简单分布走向复杂分布」这条主线,看它的起点选的是什么分布、撞的墙是什么,和图像线像不像。
这一层要加什么:一个能采样的一维分布
为什么现在才加它:前面全部在讲是什么,还没有任何一行能跑的代码。这一层要造出本书能亲手搭的第一块砖——一个足够复杂到没法写成单一公式解、却又简单到能在脑子里画出来的一维分布,以及从它里面掷一次骰子的办法。
难点:这个分布只是两个高斯混在一起,比现实里的图像分布简单到不知道多少个数量级,但它已经具备了后面几章要处理的核心麻烦——它不是一个高斯,没有唯一的中心,一条简单的「抽一个通用样子」的公式写不出来,只能靠采样才能看清它长什么样。这正是选它当作第一块砖的原因:它复杂到足以让「直接算平均」这类偷懒的办法露馅,又简单到能在浏览器里几十毫秒内画出一万个点看个明白。
自己验:采样一万次,画出直方图,应该能看到两个分开的峰,一个峰的位置应该落在 −2 附近、另一个落在 +2 附近,误差都小于 0.05;两个峰之间、0 附近的位置,应该明显比两个峰低得多,几乎没有样本落在这里。
留给你的问题
这一章说,图像、视频、语音要解的是同一个问题的三个版本。如果有一天出现第四条线——比如直接生成三维场景——你会怎么套用这一章的框架,去猜它的起点该是什么分布、它比图像线多背的那堵新墙会是什么?
不看材料,写下这一章的三堵墙
第一堵:生成模型面对同一个条件该输出什么,答案不是一张固定的图,而是每次从一整个分布里重新采样一次。第二堵:三条线的原始数据体量完全不是一个量级,视频比图像大两个数量级,语音却比图像还小。第三堵:三条线除了共享的框架,各自还多背着一堵不一样的墙——图像是远距离的像素关系,视频是这堵墙的放大版外加数据量,语音是图像和视频都不用管的相位与实时。
第2章 为什么不能直接把图画出来
开篇故事第 2 集 · 两万个点,全挤成了一根针这模型也太真实了
《潜空》弹出来的那张直方图,沈砚已经盯着看了很久——两万个点全部挤在正中间那一小撮,粗成一根针,两边该立着两座山头的地方,空空荡荡,什么都没有。他把输出摊开来核对坐标,两万个数几乎全落在正负 0.5 这个巴掌大的范围里,而这一小块地方,真实数据的密度低得几乎可以忽略,两边真正热闹的地方,他一个点都没画到。
复现推进到第八天,这是他领下的第一份活。他没有取巧,是真的搭了一个小网络,拿两万个样本喂进去,用均方误差当损失,一轮一轮训练,机柜的风扇声从傍晚一直响到后半夜,他眼看着损失曲线跌得又快又干净,他心里想的是,跌到这个地步,画出来的东西总该有点像样了。他第一反应是自己哪里写错了,跑去把训练数据和输出结果的均值又核对一遍,误差小到不到 0.05,代码本身挑不出毛病。
系统提示采样校验:两万次采样,输出均值与理论值之差小于 0.05,判定通过,均方误差流程本身无异常。
“损失是跌下去了,可它跌向的终点,从一开始就不是你要的样子。”靳工点开那张直方图,指尖点在正中间那根针上,“均方误差认的是所有合理答案的平均,网络越训越准,只是越来越精确地卡进这个谁都不是的位置。”沈砚把自己那条推理摊开来讲了一遍——他以为损失够低就该输出足够像样,此刻才明白,这套尺子从一开始量的就不是清不清晰,是离哪个平均值近不近。
沈砚这才想起,靳工前几天提过另一条路——换一个网络当裁判,让生成器去骗它,这条路确实能躲开取平均这堵墙,代价是覆盖不全。论文那张评测表摆在那儿:ProjectedGAN 的 FID 是全表最低的 1.52,看着最像真的,可它的 Recall 只有 0.34,是全表最差;换成扩散模型 ADM,FID 涨到 1.90,Recall 却跳到 0.51,清晰和覆盖全这两件事像是拴在一起,顾得了这头就顾不了那头。
损失都已经跌到这个地步了,为什么还是不清晰
1L炼丹废人我瞄了眼沈砚那条损失曲线,跌得挺干净的,这种情况按我的经验,一般就是网络还不够大,再加深加宽练久一点,多半就能挤出细节来了。
2L显存刺客不是网络大小的事,这道题换多大的网络都一样,均方误差的最优解是条件期望,是所有合理答案的平均,网络加到再大,逼近的也只会是这同一个错的点,练得越久反而卡得越死。
3L沈砚我这两天就是活生生的例子,两万个样本喂进去,损失曲线跌得很漂亮,输出却死死焊在一个点上,跟网络大小真没关系。
这一版没画出该有的两座山头,沈砚没什么好狡辩的,把自己训练时那点侥幸心思原样摊开来讲了一遍。靳工说这周的进度贴就由他来写,把这团糊照实贴上去,别人少走一遍这条路。临走前靳工又添了一句,与其一路冲到底,不如改成每次只挪一点点看看——这句话他只起了个头,后面的还没说完,转身去接了个电话。
系统结算 · 本章考点
- 训练一个朴素回归生成器,均方误差把它的输出锁死在训练数据的均值附近,两万个输出几乎全挤在正负 0.5 的范围里
- 均值与理论值的误差小于 0.05,说明代码本身没有问题,问题出在均方误差这把尺子认的就是平均,不是清晰
- 均方误差的最优解是条件期望,也就是所有合理答案的平均,网络越大、训练越久,只会更精确地逼近这同一个错的点
- LDM 论文的评测表:ProjectedGAN 的 FID 全表最低,是 1.52,Recall 却只有 0.34,全表最差
- 换成扩散模型 ADM,FID 涨到 1.90,Recall 反而跳到 0.51,清晰和覆盖全这两件事很难同时占住
上一章说,让机器画一只猫,正确答案不是一张,是一整群,模型要学的是这一群猫背后的分布。这句话说起来轻巧,但真要让一个神经网络吐出一张图,第一反应几乎所有人都一样:找一堆猫的照片,把网络的输出和这些照片逐张比对,比对得越接近,损失越小,训练得越好。这条最直接的路,正是这一章要认真走一遍、然后看着它接连在两个不同的地方撞墙的路。
学完这一章你应该能做到
- 推出均方误差训练下,最优解为什么必然是所有合理答案的平均
- 说清 GAN 怎么绕开了模糊这堵墙,又在哪件事上撞了新墙
- 引用 LDM 论文的原话和 Precision/Recall 数据,说清 GAN 撞的这堵墙有多大
- 讲清拆成很多小步为什么能让均方误差重新变得可靠
- 说清 VAE 为什么没有解决模糊问题,又为什么没有被这条故事线丢弃
- 亲手训练一个朴素回归生成器,亲眼看它收敛到数据里不存在的中点
这一层要给第1级那个能采样的双峰分布,配上第一个生成器——虽然这个生成器会当场失败。前一级只关心真实数据长什么样,这一层第一次让某个模型尝试去模仿它,而失败的方式,会精确对上这一章要推出的那条数学结论。
2.1 为什么把网络调成直接吐像素,吐出来的是一团模糊
训练一个网络,输入「画一只猫」这个条件,直接输出一整张图片的像素值,用均方误差(网络输出和某张真实猫图片逐像素比较,取平方差)当损失函数训练很多轮。这个网络最后会稳定输出什么样的图?
答完再看
第一个选项想的是「记住最后一个例子」,但训练用的是几十万张不同的猫图,网络面对的是同一个条件反复对应着许许多多不同的目标,不会单纯锁死在某一张上。第二个选项说的正是我们希望生成模型做到的事——清晰、新颖、还合理——但均方误差这条训练路子恰恰做不到这一点,这一节要推的就是为什么。
第二个选项错在哪,值得认真走一遍这条路才看得清。给网络输入一个条件,直接输出一整张图的像素值,用均方误差衡量输出和某张真实图片逐像素的差距——这是最直接想到的办法,训练循环第0章已经讲过:算损失、算梯度、往让损失变小的方向挪一步。这条路真的能训,网络也确实会一直变得更准,问题出在准的方向,不是我们以为的那个方向。
自己推一遍:均方误差最小化到底在逼网络输出什么
假设同一个条件下,真实世界存在三个合理答案:1、3、8(先简化成一个数,不是一整张图)。网络对这个条件只能给出一个输出 y,损失是这个 y 和三个真实答案各自平方差的平均。如果 y 取这三个数的平均值 4,损失是多少?
想好了再看
[(4−1)² + (4−3)² + (4−8)²] / 3 = (9+1+16)/3 = 26/3 ≈ 8.67。
换一个 y 试试,比如 y = 3(三个真实答案里确实存在的一个),损失又是多少?比刚才大还是小?
想好了再看
[(3−1)² + (3−3)² + (3−8)²] / 3 = (4+0+25)/3 = 29/3 ≈ 9.67,比取平均值 4 时的 8.67 更大。选一个真实存在的答案,损失反而比选那个谁都不是的平均值更高。
这不是巧合。把 y 当成一个可以自由调节的变量,损失函数对 y 求导,令导数等于零,解出来的 y 会是什么?
想好了再看
损失是「y 与各个目标之差的平方」的平均,对 y 求导得到「y 与各个目标之差」的平均乘以 2,令它等于零,解出 y 等于所有真实答案的算术平均。均方误差损失的最优解,从数学上就是条件期望——不管真实答案们本身多合理,最小化平方误差硬性要求输出它们的平均值。
把这件事搬回图片上:给定「画一只猫」这个条件,真实世界里合理的猫的照片有几十万张,每一张的每个像素值都不一样。均方误差训练出来的网络,会把每个像素都朝着这几十万张图在这个像素位置上的平均值靠拢——花纹被平均掉了,胡须的位置被平均掉了,边缘变成一片渐变的灰色。这不是网络训练得不够、也不是网络不够大,是损失函数本身要的就是平均,换更大的网络、训练更久,只会让它更精确地逼近这个平均值,不会让它突然学会随机挑一张。这也不是一个为了教学才编出来的玩具问题:变分自编码器的解码器用的正是这种逐像素重建损失,这正是那类模型生成的图片一直偏模糊、偏平滑的数学根源,2.4 节会回来处理它。
条件只对应一个合理答案时,均方误差没有问题
如果任务是「给定这张加噪的图,猜它加噪之前的原图」,而加噪的过程完全已知、可逆,原图其实是唯一确定的,均方误差最优解——所有可能答案的平均——这时候只有一个数,平均值就是它本身,网络能学得又准又清晰。
条件对应一大群合理答案时,均方误差给出的是一张平均脸
「画一只猫」对应几十万张同样合理、却各不相同的图,均方误差最优解是这几十万张图的平均,而这个平均本身几乎从来不是其中任何一张真实的猫。答案群体越分散,平均出来的东西离任何一个真实答案就越远。
差别在:均方误差从来只对「唯一正确答案」的任务诚实,一旦条件对应的是一整群同样合理的答案,它给出的必然是模糊的平均,不是其中的任何一个。
如果一个任务不管输入是什么,真实答案永远只有一个(比如给一个数算它乘以 2 的结果),用均方误差训练网络,最后的输出会模糊吗?为什么?
变式:如果任务的真实答案永远是「输入的数乘以 2,再加上一个很小的随机噪声」,均方误差训练出来的网络还会精确输出唯一正确答案吗?
2.1 节说条件对应一大群合理答案时,均方误差必然给出模糊的平均。能不能构造一种情况:条件确实对应很多张不同的图,但均方误差训练出来的结果依然不模糊?
变式:如果条件对应的「一大群合理答案」其实只是同一张图加了不同强度的曝光(亮度整体不同,内容完全一样),均方误差训练出来的结果会模糊吗?
2.2 找一个裁判来打分,能不能绕开这堵墙
如果不用均方误差去逐像素比较,而是训练另一个网络专门当裁判——生成的图只要骗得过这个裁判、让它以为是真图就算过关,不再要求跟某一张具体的真图对齐。这样能不能避开上一节那种模糊的平均脸?
答完再看
第一个选项的误解在于,裁判的训练目标是「判断真假」这个分类任务,不是逐像素比较,模糊的图本身就是一种很容易被识破的假货特征,裁判会专门惩罚它,不会像均方误差那样对模糊视而不见。第二个选项说对了一半——这条路确实能避开模糊,这一节会认真走一遍——但过度乐观了,完整答案是第三个:清晰换来的是另一堵墙。
把这个裁判的想法认真搭起来,就是生成对抗网络的路子。一个生成器负责画图,一个判别器负责判断输入的图是真实数据还是生成器画出来的,两者对抗训练——生成器想办法骗过判别器,判别器想办法不被骗过。这条路真的走通过,而且走得很远:训出来的图确实能又清晰又逼真,因为判别器一旦发现某种一眼假的特征(比如模糊、渐变),就会专门惩罚它,生成器被逼着放弃这些容易被识破的偷懒办法。
生成对抗网络(Generative Adversarial Network, GAN):由一个生成器和一个判别器对抗训练出来的生成模型,生成器负责画,判别器负责判断真假,两者互相逼迫着变强。
裁判是「逐像素比较」这条固定规则
均方误差这把尺子只做一件事:把输出和某一张具体的真图,一个像素一个像素地比对着打分,分不清「这张图模糊」和「这张图选了另一张同样合理的猫」,两种情况在它眼里都只是「跟这张真图差了多少」,于是永远把两者都罚成模糊的平均。
裁判换成一个专门学「像不像真图」的网络
判别器不逐像素比对,只判断整体像不像真实照片;模糊、渐变这类一眼假的特征,恰恰是它最容易抓到的破绽,会被专门惩罚,这正是生成器被逼着放弃平均脸的原因。
差别在:均方误差这把尺子里没有「像不像真图」这一条,只有「跟这张具体的图差多少」;判别器这把尺子刚好倒过来,模糊因此不再是能蒙混过关的安全选项。
但清晰不等于覆盖全。LDM 论文在引言里说得很直接:GAN 令人满意的结果,一直被局限在变化幅度相对有限的数据上,因为它那套对抗训练的过程,没法轻松扩展到给复杂的、多模态的分布建模上——原文是「the promising results of GANs have been revealed to be mostly confined to data with comparably limited variability as their adversarial learning procedure does not easily scale to modeling complex, multi-modal distributions」。翻译成上一节的语言:GAN 确实能避开均方误差那种取平均的模糊,但它换来的麻烦是,判别器只要求生成器骗过自己,不要求生成器把真实分布里的每一种可能都覆盖到——生成器完全可以只在几种安全的、容易骗过判别器的样子上反复画,放弃那些判别器不容易判断、但确实存在于真实数据里的边缘情况。
均方误差的墙:清晰度
损失函数本身要求输出所有合理答案的平均,图必然模糊,这堵墙几乎没有绕过去的余地,只要还在用逐像素比较,就躲不开。
GAN 的墙:覆盖率
判别器只要求骗过自己,不要求覆盖所有真实存在的可能,生成器有足够的动机去挑容易骗过判别器的那部分安全区域反复画,代价是分布里其余同样真实、但更难骗过判别器的部分被系统性地忽略。
差别在:一堵墙是数学上无法绕开的必然结果,另一堵墙是训练动力学给出的一种系统性倾向——下面用论文自己的量化数据,把「系统性倾向」这四个字坐实。
这句话不是一句无法验证的评价。LDM 论文在 4.2 节专门用 Precision 和 Recall 两个指标量化过这件事:Precision 衡量生成的图有多像真的,Recall 衡量生成的图覆盖了真实分布里多大一部分。同一张 LSUN-Bedrooms 数据集的评测表里,一个叫 ProjectedGAN 的模型 FID 只有 1.52,是全表最低也就是最好,可它的 Recall 只有 0.34,是全表最低也就是最差;换成扩散模型 ADM,FID 涨到 1.90,Recall 却跳到 0.51。同一张表上,FID 说 ProjectedGAN 的图更像真的,Recall 说它覆盖的范围只有扩散模型的三分之二左右。论文自己的结论是,扩散模型这类基于似然、天然倾向覆盖全部模式的训练方式,在 Precision 和 Recall 上持续超过 GAN 类方法。
如果一个生成模型只学会了画三种颜色的猫(黑猫、白猫、橘猫),而真实世界里猫有几十种花色,但这三种颜色画得都非常逼真,这个模型的 Precision 和 Recall 大概会是什么样——两个都低,两个都高,还是一个高一个低?
变式:如果反过来,一个模型能画出几十种花色的猫,但每一种看起来都有点僵硬、不太像真照片,这时候 Precision 和 Recall 大概是什么样?
答辩:如果我是审稿人
你说 GAN 覆盖不全是靠 Precision 和 Recall 量出来的,可这两个指标本身也是靠一个预训练网络的特征空间去估计的,这个估计本身够不够可靠?如果指标本身有偏差,「GAN 覆盖不全」这个结论还站得住吗?
参考防守(先自己组织语言再看)
这个担心是合理的:Precision 和 Recall 确实依赖一个预训练网络把图像映射成特征向量,再在这个特征空间里判断「像不像」和「覆盖了没有」,如果这个特征空间本身没能捕捉到人眼真正在意的差异,指标就可能失真。但几件事让这个结论站得住:第一,同一张表里多个不同结构的模型用同一套指标横向比较,系统性偏差会同时压在每一行上,不太会单独放大或缩小某一行的相对排名;第二,GAN 覆盖不全这个现象不是只靠这一张表撑着,模式坍塌在 GAN 类模型的训练日志和生成样本里是能被肉眼直接看到的现象,多篇论文在不同数据集、不同架构上反复观察到同一个模式;第三,FID 这个更早、更常用的指标也依赖同样性质的特征空间,如果要因为这一点否定 Precision/Recall,同一套理由也会否定几乎全部图像生成论文用来横向比较的评测体系。指标不完美,但它和肉眼观察、和其他独立证据指向同一个方向,这比只信任任何单一证据更可靠。
2.3 一步太难,为什么拆成很多小步就不会错
一步到位会撞模糊,加个裁判又会漏掉一部分真实分布。如果把生成这件事拆成一千个小步骤,每一步只做很小的改动,会不会更容易训练成功?
答完再看
第一个选项没意识到,拆分改变的不是「总难度」,是每一步任务的性质。第三个选项部分对,但把原因说浅了——不只是「学的东西变少」,而是每一步面对的答案群体,从「天差地别的一大群」变成了「原地附近高度集中的一小撮」,这才是均方误差重新变得可靠的真正原因,具体机制下面推一遍。
往一张图里掺一点点高斯噪声,这件事简单到甚至不需要训练——第0章讲过怎么从高斯分布采样,往原图上直接加一份随机数就行,谁都会做,而且结果是唯一确定的:同一张原图、同一份噪声,加出来的结果就是那一个。这件容易到不会出错的事,倒过来问一句就翻了面:能不能训练一个网络,专门学怎么去掉一点点噪声?
为什么往一张图里加一点点噪声这件事,不需要训练任何网络就能做,而去掉一点点噪声却需要?
变式:如果给定的不是「加了多少噪声」这个信息缺失的观测,而是同时给出原图和加噪后的图,还需要训练网络才能算出加了多少噪声吗?
这正是扩散概率模型的起点。这类模型最早的思路借自非平衡态热力学:气体分子经过很多次微小的随机碰撞,从有序状态扩散成无序状态,这个过程原则上可以倒着推回去。加噪声对应扩散的方向,去噪声对应倒着走回去。DDPM 论文说清楚了倒着走为什么可行:当每一步扩散只添加很小量的高斯噪声时,只需要把采样链的每一步转移也设成条件高斯分布就够了,这样能得到一个特别简单的神经网络参数化——原文是「When the diffusion consists of small amounts of Gaussian noise, it is sufficient to set the sampling chain transitions to conditional Gaussians too, allowing for a particularly simple neural network parameterization」。「一步很小」是关键:小到网络这一步要做的,不再是从一堆天差地别的答案里选一个,而是在给定点附近,猜一个几乎唯一确定的方向。
一步到位:每一步面对一整群天差地别的答案
输入一份纯噪声,直接要求输出一张清晰的猫,符合要求的答案有几十万种可能,均方误差在这种任务上必然交出模糊的平均。
拆成一千步:每一步只面对原地附近这一小片答案
输入一张已经有点噪声的图,只要求去掉一点点噪声,答案高度集中在输入本身附近,不再是一整群天差地别的情况,均方误差在这种任务上是可靠的。
差别在:拆分并没有让总任务变简单,而是把一个多模态的任务,切成了一千个近似单模态的任务,而均方误差恰恰只在单模态任务上诚实。
答辩:如果我是审稿人
如果拆成很多小步这个思路这么好,为什么不用同样的思路去改进 GAN,比如让判别器也分很多步走,而不是干脆换成一整套新机器?
参考防守(先自己组织语言再看)
迭代式的改进思路确实在 GAN 里出现过,比如让训练过程逐步提高分辨率,但那解决的是训练稳定性问题,不是这一节说的「每一步任务是不是单模态」这个问题。GAN 判别器给出的信号,不管拆成多少步,本质上仍然是一个对抗信号——生成器要骗过判别器,判别器要不被骗过,这个博弈结构不会因为拆成小步就消失,每一步依然要面对判别器会不会满意这种不稳定的、非监督的反馈,而不是像扩散那样,每一步都有一个由加噪过程本身直接给出的、明确到几乎唯一的监督目标:这一步加了多少噪声,就该去掉多少。拆分这件事真正起作用的前提,是每一步都要有一个清晰、稳定、可以直接当作回归目标的答案,而这一点恰恰是前向加噪过程免费送上门的,不是随便把哪个方法拆成多步就能凭空获得的。
对你而言未知要拆成多少步,才能把取平均这件事的伤害压到可以忽略
这一章说,一步到位会撞上取平均这堵墙,拆成很多步能绕开它,但没有说清楚多少步才够。步数太少,每一步跨的距离太大,取平均的伤害就还在;步数太多,训练和采样的成本又线性往上涨。第 3 到 5 章会给出具体的调度和步数—质量曲线,但那些是某几个特定模型在特定数据集上实测出来的,不是一个能提前算出「这个分布需要多少步」的通用公式。
先做这一步:回到这一章末尾的实验室,把两个高斯峰之间的距离从 4(现在的 −2 到 +2)改成 8 或者 20,固定用「一步到位」的朴素回归,看输出的均方误差怎么随两峰的距离变化;再回头翻一遍第 5 章 DDIM 的步数—误差表,看误差随步数下降的曲线,和两峰距离拉开时误差上升的曲线,是不是存在某种对应关系。
2.4 VAE 是不是也这样,它后来去哪了
上一节的答案里藏着一个容易被忽略的老朋友。在扩散模型之前,另一条路子——变分自编码器,也在尝试走不逐像素比较这条路,只是走法不一样:它训练一个编码器,把图片压缩成一小串数,再训练一个解码器,把这串数还原回图片,压缩和还原绑在一起训练。但解码器还原图片时,用的依然是逐像素的重建损失,2.1 节推出来的那条结论原样成立——解码器输出的图,一样会因为均方误差的平均效应,带上模糊、偏平滑的痕迹。VAE 没有靠拆成很多步这条路子解决模糊问题,它的清晰度问题没有被这条思路本身解决。
变分自编码器(Variational Autoencoder, VAE):由编码器和解码器组成的一对网络。编码器(encoder)把输入压缩成一小串数(潜码),解码器(decoder)再把这串数还原回原来的样子,两者绑在一起训练,让压缩和复原互相成立。
解码器独自把图片从头生成到底
从一个随机潜码开始还原,这个潜码对应的合理图片依然是一大群,2.1 节的模糊结论原样成立。
解码器只负责最后一步「翻译」
潜码不是随机来的,是前面一整串生成过程算出来、已经很具体的一个点,解码器要做的只是把这个点翻译回像素,不用再从一大群候选里选。
差别在:模糊不模糊,不取决于解码器本身用没用均方误差,取决于它面对的输入对应一整群答案,还是几乎唯一的一个。
但 VAE 没有因此出局,它换了一个岗位。LDM 论文没有让扩散模型直接在像素空间里工作,而是先训练一个 VAE,把图片压缩到一个小得多的潜空间,再让扩散那一整套加噪、去噪的机器,在这个压缩过的空间里工作,最后再用 VAE 的解码器把结果还原回像素。论文把这一步叫感知压缩:VAE 负责删掉图片里那些人眼几乎看不出、却占了大部分数据体量的高频细节,扩散模型负责在剩下的、语义层面的空间里学真正复杂的那部分分布。VAE 解码器自己输出图片时依然偏模糊这件事,在这套组合里几乎不再是麻烦——因为最后拿去重建的不是一串从头训练的随机潜码,而是扩散模型精心迭代出来、专门对应某一张具体图的一个点,解码器在这个点附近的重建反而是准的。第 6 章会把这整套压缩器怎么训练、怎么和扩散模型接起来,仔细过一遍。
2.1 节说均方误差训练出来的解码器必然带模糊;这一节又说 VAE 解码器接在扩散模型后面依然管用。这两句话是不是矛盾?请说清楚为什么不矛盾,或者矛盾在哪。
变式:如果直接从潜空间里一个粗糙的、没有经过扩散模型精细定位的随机点,交给解码器还原,这时候 2.1 节的模糊结论还适不适用?
这一层要加什么:朴素生成器与它失败的证据
为什么现在才加它:第 1 级搭好了一个能采样的一维分布,但那时候只有抽真实样本这一件事,还没有任何模型试图去模仿这个分布。这一层要亲手训练一个最朴素的生成器,然后亲眼看着它撞上这一章推出来的那堵墙。
难点:这个生成器看起来蠢得过分——它甚至没有网络,直接输出训练数据的均值。但这正是均方误差训练会收敛到的终点,一个真正的神经网络,只要还是用均方误差在这个任务上训练,不管它多大、训练多久,理论最优解都是这同一个数,区别只是能不能精确逼近它。跳过网络本身、直接算出这个理论最优解,是最快看清这堵墙长什么样的办法。
自己验:均值 mu 应该落在 0 附近(两个峰分别在 −2 和 +2,理论均值精确是 0),误差应该小于 0.05;而真实数据在 −0.5 到 0.5 这个区间的密度,应该明显低于两个峰附近的密度——也就是说,这个生成器全部的输出,都落在了真实数据几乎不存在的地方,这就是模糊的数学来源。
读的时候要小心
关于本章数字的来路
本章引用的 LDM 论文 LSUN-Bedrooms 评测表(ProjectedGAN 与 ADM 的 FID、Recall)和「LDM 持续超过 GAN 类方法」这句结论,都是论文自己的实验结果,本站未重新训练复现;均方误差最优解的推导和建造台阶里的数字,本站当场可算,也鼓励你自己重算一遍。
留给你的问题
这一章撞了两堵墙——均方误差必然给出模糊的平均,GAN 必然漏掉一部分真实分布——又指出拆成很多小步能绕开第一堵墙。如果有人告诉你,扩散模型的训练其实也用了均方误差,只是不是直接对着整张图算,这和 2.1 节的结论矛盾吗?
不看材料,写下这一章的三堵墙
第一堵:均方误差的最优解是条件期望,给定条件对应一大群不同的合理答案时,这个平均几乎不是其中任何一个,一堆猫的平均不是猫。第二堵:换成对抗训练能避开模糊,但判别器只要求骗过自己,不要求覆盖全部真实分布,容易系统性地漏掉一部分同样真实的可能性。第三堵:把一步到位拆成一千个小步之后,均方误差不再是灾难,因为每一步面对的不再是一大群天差地别的答案,而是原地附近高度集中的一个方向。
第3章 加噪:一条可以随时跳的路
开篇故事第 3 集 · 方差滚到十一点零五的那一夜这模型也太真实了
凌晨一点四十,机柜的风扇声压过了办公室里所有别的动静,沈砚盯着《潜空》弹出来的那一行红字,已经看了快十分钟。
系统提示第 1000 步的样本标准差是 3.32,而它本该落在 1.00 附近,偏差超出阈值,本次前向过程校验未通过。
下午他把加噪那一段重写了一遍。原来的写法里,每一步都要先把整张图乘上一个略小于 1 的数,再往里掺噪声。沈砚盯着那一行看了很久,觉得那一乘毫无道理——不就是往图里加一点随机数吗,前面缩那一下图不就白白变暗了一点。于是他把乘法那一半删掉了,只留下加噪声的那一半,跑之前还在论坛上说了一句,说这样每一步少做一次乘法,一千步下来总能省一点时间。
那个乘法到底能不能删,谁来算一下一千步之后会怎样
1L显存刺客一千步省一次乘法,你估过能省多少吗,我这边按最粗的算法都算不到百分之一。
2L沈砚省时间是顺带的,我主要觉得那个 0.98 乘在那儿没有道理,噪声该加多少就加多少,为什么要先把原图缩小一点。
3L炼丹废人这个写法我以前也用过,一直没出过事。不过说实话我最多跑到两百步就停了,再往下没跑过。
4L复现之鬼楼上这个说法不对,两百步没出事不等于一千步没事,你们两个谁现在算一下,照沈砚那个写法跑完一千步方差会是多少?
复现之鬼那一楼发出来之后,论坛安静了两分钟。沈砚把笔记本翻到空白的一页,从最简单的情形往下推:每一步都往上叠一份互相独立的随机量,而独立的随机量相加,方差是直接相加的,不开根号。这一千步的 β 从 0.0001 一路排到 0.02,全部加起来是 10.05,再算上原图自己带的那一份,总共是 11.05。他按着计算器开了个根号,屏幕上跳出 3.32,和《潜空》那行红字上的数字一位不差。
“那一乘不是为了省时间,是为了守住方差。”靳工端着杯子从他背后绕过来,只看了一眼屏幕就说,“先乘上根号下一减 β,再加根号 β 那么多的噪声,两个系数各自平方再相加,正好等于一。所以不管你走多少步,总方差一直是一。你把前一半删掉,后一半就一直在往上垒。”
沈砚伸手要去把那行乘法加回去,靳工按住他的手腕,说既然已经推到这一步,顺手把一千步之后还剩多少原图也算出来。这一次沈砚推得快多了:每一步乘的都是同一类系数,一千步就是把它们连乘起来,不是相加,是相乘。连乘衰减得比他想象的快得多,走完一千步,原图前面那个系数只剩下 0.006353。
系统提示第 1000 步的最终校验:原图保留系数 0.006353,样本方差 1.00,本次前向过程校验通过。
系统提示附带校验:逐步加噪一千次与一步直接跳到第 1000 步,两者的均值与方差之差均小于 0.02。
“所以训练的时候根本不用真的走那一千步。”靳工把杯子放回桌上,“随机挑一个时刻,一次乘加直接跳过去就行。前向这一整段一个要训练的参数都没有,它只是一张事先写好的表。”
沈砚把那张表整个打印出来铺在桌上,用指甲顺着数字往下划。划到中间的时候他停住了:这张表是按等差数列填的,β 从头到尾匀速增长,可信号掉得一点都不匀——走到第 260 步,原图保留的那一份就已经和噪声一样多了,后面七百多步都在给一堆几乎全是噪声的东西继续加噪声。他把这一条发到论坛上,附上另一张按余弦曲线填的表:同样是一千步,这一张要到第 497 步,信号和噪声才打平。
复现之鬼只回了一句,说她明天一早要拿这两张表各训一版,看看差多少。靳工没有接这个话,他把椅子转回自己那边,说了一句别的:反过来走的那一步里,也有一个看上去同样多余的东西,你们迟早也会想把它删掉。
系统结算 · 本章考点
- 一步加噪要先乘一个略小于 1 的系数再掺噪声:两个系数的平方和恰好等于 1,方差因此守恒
- 把那个缩放删掉的代价:一千步之后方差累到 11.05,标准差 3.32
- 一千步的 β 从 0.0001 排到 0.02,逐步相乘而不是相加,原图系数最终只剩 0.006353
- 前向过程没有任何待训练参数,任意时刻都能用闭式解一次乘加跳到,与逐步加噪同分布
- 线性排的那张表在第 260 步信号与噪声等量,按余弦排的要到第 497 步
拿一张照片,给每个像素加上一个很小的随机数,看上去几乎没变;再加一次,还是几乎没变;加到第一千次,屏幕上只剩一片雪花。毁掉一张图这件事谁都会做,难的是反过来走。我们先把毁掉这条路认真走一遍,因为它容易到有一个公式,能让你跳过中间九百九十九次,一步算出第一千次的样子。
学完这一章你应该能做到
- 说清一步加噪里那个略小于 1 的缩放系数是干什么的,去掉它会发生什么
- 拿到一张 β 的表,算出任意时刻的 ᾱ、信噪比和 log 信噪比
- 说出线性调度和 cosine 调度的信噪比降到 1 的时刻各是第几步,并解释这个差别为什么和分辨率有关
- 自己写出前向加噪的闭式解,并验证它和逐步加噪一千次给出同一个分布
这一层要给第 1 级那个能采样的一维分布加上一条毁掉它的路:一个只有乘法和加法、一个待训练参数都没有的前向过程。前两级跑的都是「数据本身长什么样」,还没有任何东西随时间变化。
3.1 掺一千次噪声,图还剩下什么
每一步都往图里掺一点高斯噪声,掺一千次。掺完之后,原来那张图对结果还有多少贡献?
答完再看
按 DDPM 用的那套系数,第 1000 步上原图前面的系数是 0.006353。一个幅度为 1 的像素被压成 0.0064,大约是原来的千分之六;同一时刻噪声那一项的幅度是 0.99998。第二个选项把一千次小改动估成了线性累加,实际上缩放是连乘的,一千个略小于 1 的数乘起来会掉得非常快。第一个选项更值得拆:0.006353 小,但它不是零,而反向过程能不能走通,全部押在这个小数不等于零上。DDPM 自己给了一个量,说到了第 1000 步,xT 的分布和标准高斯之间的差距大约是每维 10−5 比特(§4)。
先把一步写下来。最朴素的写法就是直接加:新的数组等于旧的数组,加上一份高斯噪声,噪声的强度用一个小数 βt 控制。这个写法能用,它真的会把图毁掉,我们先认真走它一段。
麻烦出在方差上。方差衡量一组数散开的程度,两个互相独立的随机量相加,方差直接相加。所以每加一步,整个数组的方差就往上涨 βt。DDPM 用的那串 β 从 0.0001 线性涨到 0.02,一千个数加起来正好是 10.05。假设原图的像素值方差是 1,跑完一千步方差变成 11.05,标准差 3.32。
只加不缩:xt = xt−1 + √βt · ε
一千步之后方差 11.05,标准差 3.32。数组还带着原图的均值,散开的程度是起点的三倍多。这个终点我们自己造不出来,因为它的均值取决于那张我们还没画出来的图。
先缩再加:xt = √(1−βt) · xt−1 + √βt · ε
一千步之后方差还是 1,均值被压到 0.0064 倍,终点几乎就是标准高斯。这个终点我们闭着眼睛都能采样,第 0 章那个随机数发生器就够了。
差别在:缩放系数让每一步「腾出多少位置给噪声,就先从信号里让出多少」,方差因此被锁死在 1。
补救的办法就在右边那一栏里。加噪之前先把旧数组乘上一个略小于 1 的系数 c,这一步之后方差变成 c2 · 1 + βt。想让它还等于 1,就得让 c2 = 1 − βt,也就是 c = √(1−βt)。这个根号不是为了好看,它是方差守恒方程的唯一解。DDPM 论文把这一步写成一个条件分布(式 2),意思和下面这行一模一样。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| xt | 第 t 步之后的那个数组,形状和原图一样 | 被毁到第 t 成的图 |
| βt | 第 t 步的噪声强度,一个 0 到 1 之间的小数,事先写死 | 这一步腾出多少位置给噪声 |
| √(1 − βt) | 信号的缩放系数 | 为了腾位置,信号先让出这么多 |
| ε | 一份标准高斯噪声,每一步都新抽一次,彼此独立 | 这一步掺进去的随机数 |
| I | 单位矩阵,表示每个像素上的噪声互不相关 | 雪花点之间没有花纹 |
起点的方差是 1,用式 2 走一步,β1 = 0.0001。走完这一步方差是多少?如果起点的方差改成 4 呢?走完这一步往哪个方向动?
变式:把噪声那一项的系数从 √β 改成 β 本身(其余不动),这条递推的不动点跑到哪里去了?
顺带说一件容易被跳过去的事。式 2 里每一步的 ε 都是新抽的,和前面所有步抽到的都没关系。这个独立性是下一节那个闭式解的全部本钱,如果哪一步偷懒把上一步的噪声拿来再用一次,式 2 单看还是对的,闭式解就塌了。
有人图省事,把一千步的 β 全都设成同一个常数 0.02(也就是线性调度的终点值)。跑完一千步之后,原图前面的总系数还剩多少?和线性调度的 0.006353 比,哪一套毁得更快,快多少倍?
变式:想让常数调度在第 1000 步上也刚好落到 0.006353,这个常数该取多少?
3.2 训练时真要一步一步加一千次吗
训练一次是这样的:抽一张图,抽一个时刻 t,把图加噪到 t,让网络去猜加进去的是什么。t 是随机抽的,抽到 800 就得从原图老老实实走 800 次乘加。一个批次几百张图、每张抽一个不同的 t,一轮训练几十万个批次,光是加噪这件不需要学习的事就要吃掉大半的算力。
但是两步是可以并成一步的。把第二步式子里的 xt−1 换成它自己的展开式,看看会得到什么。记号上先做个简化,DDPM 定义 αt = 1 − βt,这样式 2 就是信号乘 √αt、噪声乘 √(1−αt)。
自己推一遍:从一步加噪到一步跳过去
把 xt = √αt xt−1 + √(1−αt) εt 里的 xt−1 换成它自己的展开式。先不要化简,你会得到几项,各是什么?
想好了再看
三项。一项带着 xt−2,系数是 √(αtαt−1);两项带着噪声,一项是 √(αt(1−αt−1))·εt−1,另一项是 √(1−αt)·εt。注意 εt−1 和 εt 是两次独立抽的。
两份独立的高斯噪声,系数分别是 √(αt(1−αt−1)) 和 √(1−αt)。它们能合成一份吗?合成之后的系数是多少?
想好了再看
能。独立的高斯相加还是高斯,方差直接相加,所以合成之后的方差是 αt(1−αt−1) + (1−αt),展开正好等于 1 − αtαt−1。当初能想到这一步,靠的就是前向过程每一步都新抽噪声这个设定——高斯在求和下是封闭的,这是它被选中的主要原因之一。
现在信号项的系数是 √(αtαt−1),噪声项的方差是 1 − αtαt−1。这两个数之间是什么关系?把这个关系一路推到第 t 步会得到什么?
想好了再看
信号系数的平方加上噪声方差正好等于 1,也就是方差守恒那件事在合并之后依然成立。一路递推下去,信号系数就是所有 α 的连乘开根号。给这个连乘起个名字 ᾱt = α1α2⋯αt,就得到了 DDPM 的式 4。
这个式子在 t = 0 上还成立吗?代进去看看。
想好了再看
成立。空的连乘约定等于 1,所以 ᾱ0 = 1,√ᾱ0 = 1,√(1−ᾱ0) = 0,式子退化成 x0 = x0。这个检查看着无聊,但实现的时候它能第一时间抓出下标错位——ᾱ 的数组到底从 0 开始还是从 1 开始,是这一段代码最常见的坑。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| x0 | 原图,一个像素值都还没被动过 | 起点 |
| ᾱt | 前 t 个 (1−β) 连乘的结果,从 1 一路单调掉到接近 0 | 信号还剩几成(按能量算) |
| √ᾱt | 原图在 xt 里的系数 | 信号还剩几成(按幅度算) |
| √(1 − ᾱt) | 噪声在 xt 里的系数 | 噪声占了几成 |
| ε | 一份标准高斯噪声,整段跳跃只抽这一次 | 把中间一千次的随机性打包成一次 |
老实走:造一张 x800 要 800 次乘加
抽到 t = 800 就得从原图走 800 步,每一步一次缩放一次加噪,还要抽 800 份噪声。一个批次里每张图抽到的 t 不一样,整批没法并行,得按各自最长的那条走。
跳过去:造一张 x800 要 1 次乘加
查表拿到 ᾱ800,抽一份噪声,一次乘加就位。t 是多少都一样快,整批可以各自抽各自的 t 然后一起算完。
差别在:一条是把随机性一份份累积起来,另一条是把累积的结果一次性写成公式。两条给出同一个分布,代价差 800 倍。
前向过程(forward process):把干净数据一步步加噪直到信号被毁掉的那条固定的路。论文里也叫扩散过程(diffusion process)。它没有参数,所以不参与训练,只是训练时用来现场造数据的工具。
训练循环因此长成 DDPM 里 Algorithm 1 的样子,短到能背下来:抽一张图,从 1 到 1000 之间均匀抽一个 t,抽一份 ε,用式 4 一次乘加得到 xt,然后让网络看着 xt 和 t 去猜那份 ε。整条前向链在训练里从来没有被真的跑过一次。
同一颗随机种子下,一边老老实实跑一千次单步加噪,一边用式 4 一步跳到 t = 1000。两边得到的数组会怎样?
答完再看
式 4 说的是两条路给出同一个分布,不是同一次抽样。逐步那条路一共抽了一千份噪声,闭式那条路只抽了一份,两边用掉的随机数个数都不一样,逐点当然对不上。各跑二十万个样本对比:逐步加噪得到均值 0.0057、方差 1.0001,一步跳过去得到均值 0.0080、方差 0.9975,理论值是均值 0.006353、方差 0.99997。第二个选项值得单独说一句——一千次乘加的浮点误差在 1e-13 量级,离 0.02 的统计涨落差了十一个数量级,它从来不是这里的麻烦。
式 4 保证每一个时刻 xt 的分布是对的。但它对一整条加噪轨迹说了什么吗?请构造一个错误实现,它满足「每个单独时刻的分布都正确」,却能被成对的 (xt, xt+1) 抓出来,并写出你会算哪个数来抓它。
变式:另一个实现两次都新抽 ε,但第二次不小心用了 t 的系数而不是 t+1 的。这次连单个时刻的分布都错了,你会算哪个数来抓它?
答辩:如果我是审稿人
闭式解成立,靠的是每一步加的都是高斯噪声、而且彼此独立。要是把高斯换成均匀分布,这条路就走不通了吗?如果走不通,DDPM 的整套训练循环要付出什么代价?
参考防守(先自己组织语言再看)
走不通。独立的均匀分布相加之后不再是均匀分布,两个相加得到三角分布,一千个相加趋近高斯,中间每一步的形状都不一样,写不出一个像式 4 那样简洁的闭式解。代价是训练时没法「随机挑一个 t 直接跳过去」,要么真的跑 t 步,要么放弃 t 的均匀采样。高斯在这里被选中,一半是因为它对现实噪声的刻画,另一半纯粹是因为它在求和与缩放下都封闭。DDPM 自己在 §2 说得更直接:当每步加的噪声量很小时,反向的每一步也可以用高斯来描述,这才让网络的参数化变得简单。整条链是围着高斯这个性质搭起来的。
3.3 β 按等差数列挑,哪里不对
调度这张表得有人来填。DDPM 的填法很朴素,T 取 1000,β 从 0.0001 线性涨到 0.02,一千个等差的数(§4)。挑这么小的数有两个理由,一是相对于压到 [−1, 1] 的数据要足够小,前向和反向才有近似相同的函数形式;二是要让终点尽量接近标准高斯,他们量出来的差距是每维 10−5 比特。
这套数在 256×256 的图上工作得很好。一年后 Improved DDPM 在 32×32 和 64×64 上重跑,发现它不对劲,原话是这条调度「掉向零掉得快得多,毁信息毁得比需要的更快」(§3.2)。这句话听起来像风格评价,但它是有数的。
| 时刻 t | 线性调度 ᾱt | cosine 调度 ᾱt |
|---|---|---|
| 200 | 0.659039 | 0.898706 |
| 500 | 0.078587 | 0.493844 |
| 800 | 0.001532 | 0.094046 |
| 1000 | 4.0358e-05 | 3.75e-33 |
| ᾱ 跌破 0.5 的那一步 | t = 260 | t = 497 |
cosine:一半的步数花在信号占优的那一段
ᾱ 到第 497 步才跌破 0.5,也就是说整条路的前一半,图里的信号都还比噪声多。t = 800 时还剩 0.094,网络在那里仍然能看见结构。
线性:四分之一的步数就把信号交出去了
ᾱ 在第 260 步就跌破 0.5。剩下的七百多步里图已经基本是噪声,t = 800 时只剩 0.0015。这七百步不是白费——在 256×256 上,图里的冗余多,需要这么长的尾巴才毁得干净。
差别在:同样一千步,把毁坏摊在哪一段上。分辨率越低,像素之间的冗余越少,毁得快就越容易把有用的结构一起毁掉。
Improved DDPM 的改法是掉头填另一张表。他们不再去填 β,而是直接规定 ᾱ 这条曲线长什么样,再倒推回 β。想要的形状是中间近似匀速下降、两头平缓,他们挑了一个余弦的平方。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| t/T | 走到全程的百分之几,一个 0 到 1 的数 | 横轴,归一化的时间 |
| s = 0.008 | 一个小偏移,把余弦的起点从正顶点挪开一点 | 让开头那几步不要噪声小到零 |
| f(0) | 分母,只是为了让 ᾱ0 正好等于 1 | 归一化 |
| cos(·)2 | 取平方让曲线从 1 平滑降到 0,中段近似直线 | 论文说这个选择是任意的 |
| βt | 由 ᾱ 倒推:1 − ᾱt/ᾱt−1,再截断到不超过 0.999 | 先定曲线,再反解每步强度 |
式 17 里的 s 如果取 0,曲线的起点正好落在余弦的顶点上,形状更干净。作者却取了 0.008。把 s = 0 代进去,算出 β1 大概是多少,再说说为什么这个数会让训练出问题。
变式:末尾那个「β 截断到不超过 0.999」的要求,如果换成截断到 0.5,曲线的终点会落在哪里?和标准高斯还对得上吗?
末尾那个截断同样有具体理由。不截断的话 β 在最后几步会顶到 1.0000,ᾱ 直接塌到 3.75e-33,而反向过程里要除以 √ᾱ,那一步就是除零。
把两条 ᾱ 曲线画在同一张图上。走到一半、也就是 t = 500 的时候,你觉得两条曲线差多少?
答完再看
t = 500 时线性给 0.078587,cosine 给 0.493844,差了 6.3 倍。第一个选项犯的错最典型——两条曲线端点一样、都单调,很容易默认中间也差不多,但决定训练难度的恰恰是中间那一路。第三个选项把因果弄反了,cosine 是为了毁得慢一点才被造出来的。把这两条曲线拉开看,还能顺手看出另一件事:线性调度末尾那三四百步,ᾱ 已经在 0.001 以下,图和纯噪声几乎没差别,那几百步的网络前向基本是白跑。Improved DDPM 量过这件事,用线性调度训出来的模型,采样时跳过前面 20% 的反向步数,FID 几乎不变(§3.2)。
训练时 t 是从 1 到 1000 均匀抽的。线性调度下,抽到的 t 里有多大比例落在「信号已经比噪声少」的那一段?换成 cosine 调度呢?这个比例的变化,对网络最后学会的东西意味着什么?
变式:不改调度,改成按 λ 均匀抽 t,也就是让训练样本在 log 信噪比这根尺上等距分布。线性调度下这么做,落在信号占劣势那一段的比例会变成多少?
答辩:如果我是审稿人
你们说 cosine 比线性好,可 Improved DDPM 自己写着线性调度「在高分辨率图上工作得很好」,实验又全部做在 32×32 和 64×64 上。那这个结论到底是「cosine 更好」,还是「在低分辨率上 cosine 更好」?而且作者承认 cos² 的选择是任意的,那这篇论文真正的贡献是那个余弦函数,还是别的什么?
参考防守(先自己组织语言再看)
只能说到「在 32×32 和 64×64 上更好」。论文自己划了界:线性调度对高分辨率是合适的,问题出在低分辨率,因为像素之间的冗余少,同样的噪声量毁掉的有效信息更多。至于贡献,余弦这个具体函数确实可以替换,作者自己说形状相似的函数应该都行。真正留下来的是那个判据——调度应该按 ᾱ 曲线的形状来设计,而不是按 β 的排列来设计,并且要让中段近似匀速下降。后来的模型换了各种各样的曲线,这个判据一直还在用。SD3 甚至把这件事推进了一步,让调度随分辨率平移,因为分辨率一变,同一个 t 上的有效噪声量就变了。
3.4 哪一刻信号和噪声一样多
我们想用一个数来说「第 t 步有多难」。最顺手的候选是 ᾱt,它从 1 掉到 0,看上去正合适。你觉得直接用它会遇到什么麻烦?
答完再看
线性调度下 ᾱ 从 t = 600 到 t = 1000 只从 0.0259 走到 0.000040,在一根 0 到 1 的坐标轴上这四百步全部糊成一条贴着底边的线。但这四百步里信噪比从 0.0266 掉到 0.000040,掉了六百多倍,网络面对的任务在这四百步里一直在剧烈变化。第二个选项的方向是对的,只是「拉直」要用对函数——真正该做的是换成对数坐标。
先把这个数定下来。xt 里信号那一项的系数是 √ᾱt,噪声那一项的系数是 √(1−ᾱt)。比较两者的能量,就是比较它们的平方,于是很自然地得到一个比值。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| SNR(t) | 信号能量除以噪声能量 | 图比雪花点响多少倍 |
| λt | SNR 取自然对数,随 t 单调下降 | 把跨九个数量级的比值压成一根能看的直尺 |
| αt、σt | 另一套论文里的记号,信号系数与噪声系数。换回 DDPM 的字母就是 √ᾱt 与 √(1−ᾱt) | 同一件事的两种写法 |
| SNR = 1 | 等价于 ᾱt = 0.5,也等价于 λt = 0 | 信号和噪声打平的那一刻 |
拿 t 当尺:600 到 1000 之间是一条贴着底边的平线
ᾱ 从 0.0259 走到 0.000040,在 0 到 1 的纵轴上这四百步全部糊在一起,看不出任何结构。而且这把尺换一套调度就作废,线性的 t = 500 和 cosine 的 t = 500 难度完全不同。
拿 λ 当尺:全程 19.33,每一段都看得见
同样这四百步,λ 从 −3.63 走到 −10.12,占了全程的三分之一,刻度均匀。而且这把尺跨调度通用:两套调度的 λ = 0 都是信号与噪声打平的那一刻,只是落在不同的 t 上。
差别在:t 是某一套调度内部的编号,λ 是所有调度共用的物理量。
把线性调度的 SNR 列出来,尺度感马上就有了:t = 1 时是 9999,t = 100 时 8.71,t = 400 时 0.242,t = 1000 时 0.0000404。九个数量级。取了对数之后 λ 从 +9.21 一路走到 −10.12,全程 19.33,这才是一根能画能读的尺。而 SNR = 1、也就是 λ = 0 的那一刻,线性调度落在 t = 260,cosine 落在 t = 497。上一节那张表里最有用的就是这两个数。
线性调度在 t = 800 时 ᾱ = 0.001532。算出这一刻的 SNR 和 λ,并用一句日常的话说清楚这时候图长什么样。
变式:换成 cosine 调度,同样在 t = 800 时 ᾱ = 0.094046,这时候的 λ 是多少?它相当于线性调度的第几步?
回头看这一章的标题。把 t 当成「第几步」是一种误解,t 真正的身份是这根尺上的一个刻度:给定调度,t 和 λ 之间是一一对应的,说「走到 t = 500」和说「走到 λ = −2.46」是同一句话。同一个模型换一套调度,同一个 t 对应的难度就变了,但同一个 λ 对应的难度不变。这是后面几章能把 t 换成别的参数、能跳着采样、能把两篇论文的记号对上的根本原因。
有人看完这一章提议:既然式 4 说 xT 和 x0 之间只隔着两个常数和一份噪声,那就训练一个网络,输入 xT,直接输出 x0,一步到位,一千步全省了。用一个平方误差损失去训它。这个网络训完之后会输出什么样的图?为什么?
变式:如果不从 xT 一步回到 x0,而是从 t = 100 一步回到 x0,糊的程度会差多少?用 ᾱ100 = 0.897 估一估。
对你而言未知调度曲线该长什么样,有没有一个能算出来的答案
Improved DDPM 自己说 cos² 是任意挑的,只要形状对就行。那「形状对」到底是什么意思,有没有一个不依赖试错的判据?目前能看到的是几条经验规律:ᾱ 中段要近似匀速下降;分辨率越高,同一个 t 需要的噪声越多(SD3 为此让整条调度随分辨率平移);采样步数很少时,步数应该按 λ 均匀分配而不是按 t 均匀分配。这些规律彼此不冲突,但没有被统一到一个能直接解出曲线的原则上。
先做这一步:在本章那个调度实验室里,把 cos² 换成 cos 的四次方和 cos 的一次方,各自读出 ᾱ 跌破 0.5 的时刻,以及 λ 在全程上的分布是否还接近均匀。然后回去读 Improved DDPM §3.2 最后一段和 SD3 的式 23,看看这两处对「形状对」各自下的是什么判据。
这一层要加什么:前向加噪的闭式解
为什么现在才加它:前两级手上只有一个静态的一维分布,没有任何东西随时间变化。这一层给它加上一条时间轴,而且这条轴上任意一点都能一步跳到,不用经过中间任何一点。
难点:cumprod 的下标和论文里的 t 差一位,这是这一层唯一会让你静静算错三天的地方。检查办法是代入 t = 0:ᾱ0 必须等于 1,q_sample(x0, 0, eps) 必须精确返回 x0。另一个坑是随机数的来路——q_sample 里那一份 eps 是一整段跳跃的全部随机性,它和 step 里逐步抽的那一千份不是同一批东西,两条路给出同一个分布,不给出同一次抽样。
自己验:取两万个样本,一路用 step 加噪一千次,另一路用 q_sample 直接跳到 t = 1000。两组的均值都应该落在 0.0064 附近、方差都应该落在 1.00 附近,各自与理论值 0.006353 和 0.99997 的差应该小于 0.02。再把 t 设成 0,q_sample 的输出必须和输入逐位相等,误差为 0 而不是 1e-16。
读的时候要小心
关于本章数字的来路
本章的 ᾱ、SNR、λ 和两条调度的跨越点,都是从论文给定的 β 直接算出来的,旁边的实验室里按一下就能复现。有两条不是:跳过 20% 反向步数后 FID 几乎不变,以及线性调度在低分辨率上的表现判断,都来自 Improved DDPM 作者自己的实验,没有第三方复现。
留给你的问题
噪声的总量由所有 β 的和决定,它们怎么分布在这一千步上则决定了中间那一路。现在给你一个固定预算:总共只能走 100 步,终点必须和走 1000 步时一样接近标准高斯。你会怎么分配这 100 个 β?把线性调度的 β 直接放大十倍,还是让这 100 步在 λ 这根尺上均匀分布?两种分法在 t = 50 时的 ᾱ 会差多少?
不看材料,写下这一章的三堵墙
第一堵:只加噪不缩放,方差涨到 11.05,终点成了一个我们自己造不出来的分布。第二堵:训练时真的跑 t 步加噪,算力全花在不需要学习的事上。第三堵:β 按等差数列填,低分辨率下 ᾱ 第 260 步就跌破 0.5,后面七百多步几乎白跑。
第4章 去噪:网络到底在学什么
开篇故事第 4 集 · 一千步全塌成同一张脸这模型也太真实了
凌晨两点一刻,机柜的风扇声又拔高了一档,炼丹废人第七次按下回车,屏幕上跳出来的还是那同一串数字——1.00316642,连小数点最后一位都不抖一下。他把七次运行的截图并排摆在桌面上,像摆了七张同一张脸的照片,怎么看都认不出哪张是哪张。
五天前靳工收工前撂下那句话——反过来走的那一步里,也有一个看上去同样多余的东西,你们迟早也会想把它删掉——被炼丹废人惦记了整整五天。今天下午他翻开采样循环那段代码,一眼就把嫌疑对象锁定在末尾那一行:算完这一步该走到的中心之后,还要再撒一份全新的随机数加上去。他觉得这份随机数和当初那个要乘的系数一样多余,删掉它图应该会更干净,没多想就把那一行整个注释掉了。
系统提示反向采样循环校验:随机项已关闭,从起点 2.0 出发跑满 1000 步,输出恒为 1.00316642,连续多次运行标准差为 0。
“这不是更干净,这是坏了。”显存刺客盯着那串小数点后六位一模一样的数字,声音都拔高了,“你告诉我这东西还算不算生成模型,它现在连随机数种子都不认了。”炼丹废人还想争辩说这正说明模型学得稳,话没说完,沈砚已经想起几天前算过的那笔账:独立的随机量往上叠,方差是相加的,他下意识觉得反过来删掉也该是同一个道理——少加了一千份小随机数,顶多是宽度窄一点,不该窄成一个点。
随机项删了到底算优化还是算把模型删坏了
1L炼丹废人我算过,每一步撒的那点随机数标准差都很小,一千步加起来顶多让边缘糊一点,删了应该只是让图更锐利。
2L显存刺客楼上你先回答我一个问题,你这七次跑出来的图,能一次性拿出七张不一样的吗?拿不出来就不是锐利,是死机。
3L沈砚我一开始也以为是宽度变窄,但窄和塌成一个点是两回事,塌成一个点说明这一千步已经不是在生成,是在求平均。
4L复现之鬼你们谁把 noise 重新打开,拿同一个起点跑两万次,看看均值和标准差落在哪,别在这里靠嘴吵。
复现之鬼那句话把炼丹废人问住了,他这才想起自己半小时前已经把这套“干净版”的输出打包发去了群里,说是随手挑了二十张给大家看看效果。沈砚把随机项重新打开,从同一个起点跑了两万次,这一次每一次的结果都不一样,摊开来看,均值落在 0.9989,标准差落在 0.4977,正好是那团数据本来该有的宽度。靳工端着杯子走过来,看了一眼两组数字,说这不是抵消,是塌陷:把每一步的随机项都拿掉,一千次连续取平均,最后合并成的还是同一个条件期望,跟十一天前那个朴素生成器画出来的糊脸,塌到的是同一个地方。
“留着它,一千步才能把这份宽度分次还回去;删掉它,走多少步都只是在描同一个点。”靳工说完把杯子放下,转头问了一句谁去把发到群里的那二十张图撤回来,没人应声——那条消息底下已经有三个人回复说效果真稳,还没人回过去说清楚这稳到底是稳在了哪儿。
系统提示重新打开随机项后两万次独立运行:均值 0.9989,标准差 0.4977,与数据分布基本一致。
炼丹废人把那一行代码原样加了回去,屏幕右下角的进度条又变回一千格那么长,跑完一轮要等的时间和五天前一模一样。沈砚盯着那根长条,忽然想起靳工没答完的那半句话——这份宽度非留不可,可留住它是不是只能靠老老实实走完这一千步,还没有人给出答案。
系统结算 · 本章考点
- 反向采样每一步先算出均值,再加回一份新的随机噪声:从起点 2.0 出发关掉这份随机项,跑满 1000 步,不管重复多少次,输出都恒为 1.00316642
- 这个恒定值不是巧合,它和给定起点 2.0 时原图的条件期望在小数点后六位上完全一致
- 重新打开随机项后独立跑两万次,均值落在 0.9989、标准差落在 0.4977,这才是数据分布本该有的宽度
- 删掉随机项不是「互相抵消」——独立噪声的方差是相加的;一千步连续取均值,靠全期望公式合并成同一个条件期望,这才是塌陷的真正原因
- 两万次独立运行如果每一次都分毫不差,那不是模型学得更稳,是随机项被删掉了,这条判据比盯着损失曲线更直接
上一章我们把一张图毁了一千次,每一步都有公式,一步都不用猜。现在把录像倒过来放。倒放的第一帧看起来一点都不难——屏幕上那团雪花只比前一帧多了一丁点噪声,按理说去掉那一丁点就行了。可到底去掉哪一丁点?这一章就在回答这个问题,而答案最后会塌成一个你在第 0 章就见过的东西:一个平方差。
学完这一章你应该能做到
- 说清反向一步为什么算不出来,以及多知道一个 x0 之后它为什么就能算了
- 把变分上界一路化到 Lsimple,并解释扔掉那个权重对训练产生了什么影响
- 解释预测噪声和预测原图在数学上等价、在训练上却差很远的原因,并说出这个差别在哪个 t 上最大
- 写出一维高斯数据的解析最优去噪器,用它跑完整条 DDPM 采样循环,不训练任何网络
这一层要给第 3 级那条加噪的路加上一个回头的走法:一个去噪器,外加一整条采样循环。前三级只会毁东西,还造不出任何新东西。
4.1 倒着走一步,凭什么猜得准
前向每一步都是一条确定的规则。现在反过来:给你 xt,要猜 xt−1。这件事有多难?
答完再看
DDPM 在 §2 把这件事说得很直接:当扩散由少量高斯噪声组成时,把采样链的转移也设成条件高斯就够了,这让网络的参数化变得特别简单。第一个选项错在式子里那个 ε——它是前向时随手抽掉的一份随机数,没人记下来。第二个选项把「不唯一」当成了「没有」。不唯一的东西有分布,分布有形状,形状窄到一定程度,报一个中心点就够用了。整条扩散模型的路,就是把一件做不到的事切成一千件窄到能做的事。
先把第二个选项错在哪讲清楚,因为它离真相只差一层。给定 xt,可能的 xt−1 确实有无穷多个,它们各自的可能性有大有小。要知道谁的可能性大,就得知道整个数据集里什么样的图更常见。这个分布写成符号是 q(xt−1 | xt),它需要遍历所有可能的图,算不出来。
DDPM 的做法是绕过去。如果我们额外知道这条轨迹的起点 x0,那么给定 xt 和 x0,中间那一步 xt−1 的分布就是一个能写出来的高斯,均值是 x0 和 xt 的一个加权平均(式 7)。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| μ̃t | 在既知道终点又知道起点的前提下,中间那一步的均值 | 两个已知点之间的落脚位置 |
| x0 前面的系数 | t 越小它越大,t 接近 T 时它趋近 0 | 越靠近终点,越听起点的 |
| xt 前面的系数 | t 越大它越接近 1 | 越靠近起点,越听当前这一帧的 |
| β̃t | 这个高斯的方差,也有闭式解,同样只依赖调度表 | 落脚点周围那团模糊的大小 |
只知道 xt:算不出来
要算 q(xt−1|xt),得把所有可能的原图都试一遍,按它们在真实数据里的常见程度加权。这个加权需要知道整个数据分布,也就是我们本来要学的那个东西。
再知道 x0:一行公式
多给一个起点,整条轨迹就被两端夹住了。式 7 里只出现调度表里的常数、x0 和 xt,没有任何需要遍历数据集的东西。
差别在:一个要知道所有图长什么样,另一个只要知道这一张图长什么样。
于是整件事被压缩成一句话:反向一步里唯一不知道的是 x0。而上一章的式 4 告诉我们,知道 x0 和知道那份被掺进去的 ε 是同一回事,因为给定 xt,两者可以互相解出来。网络要输出什么,到这里就被锁定了,剩下的只是选哪一种写法。
式 7 是 x0 和 xt 的加权平均。在 t 很小(比如 t = 2)和 t 很大(比如 t = 1000)这两端,哪一项占主导?用一句话解释这个变化为什么是合理的。
变式:把式 7 里的 ᾱt−1 换成 ᾱt(也就是下标写错一位),两个系数加起来还等于 1 吗?这个错误会让采样出来的图整体偏亮还是偏暗?
4.2 一堆吓人的数学,最后为什么塌成一个平方差
训练目标不是从天上掉下来的。DDPM 优化的是负对数似然的一个变分上界(式 3),写出来是一串对数和期望,第一眼看上去和「让网络猜噪声」毫无关系。中间发生了什么,值得走一遍,因为走完你会发现这一路上没有任何一步是魔法。
自己推一遍:从变分上界到一个平方差
式 3 是一整条链上所有步的对数比之和。要让它变得可算,第一件事该做什么?
想好了再看
把它按时刻拆开,拆成一项一项互相独立的东西。DDPM 的式 5 做的正是这件事:整个上界被改写成一个起点项、一串中间项、一个终点项。中间那一串里,第 t 项是 q(xt−1|xt, x0) 和网络给出的 pθ(xt−1|xt) 之间的 KL 散度。KL 散度衡量两个分布差多远,两个分布一样时它等于 0。拆开的好处是每一项都能单独算,训练时随机抽一项就行。
现在要算两个高斯之间的 KL。如果这两个高斯的方差是同一个固定值,只有均值不同,这个 KL 会长什么样?
想好了再看
就是两个均值之差的平方,除以两倍方差。所有的对数、积分、归一化常数全部抵消掉了。DDPM 的式 8 正是这个结果。能走到这一步,靠的是上一节那个发现——多条件一个 x0,前向的后验就成了高斯;再把网络的输出方差固定成一个和 t 有关的常数 σt²,两边就都是高斯了。论文在 §3.2 明说这个方差是不训练的时间相关常数。
式 8 是 μ̃ 和网络输出 μθ 之差的平方。但我们刚才说网络其实该输出 ε。怎么把这个平方差里的 μ 换成 ε?
想好了再看
把式 4 反解出 x0,代进式 7,μ̃ 就被改写成只含 xt 和 ε 的形式(式 10)。然后让网络的输出也用同样的形状写出来(式 11),两个 μ 相减时,那些共同的项全部抵消,只剩 ε 和 εθ 之差,外面挂着一个由调度表决定的系数。这就是式 12。当初会想到这么做,是因为式 10 摆在那里,括号里除了 ε 什么都是已知的——网络只要补上那一个未知量就够了。
式 12 前面挂着的那个系数 βt² / (2σt²αt(1−ᾱt)),把它扔掉,变成对所有 t 一视同仁。这样做合法吗?
想好了再看
扔掉之后它就不再是那个变分上界了,而是一个加了权的变分上界,权重换成了别的。DDPM 在 §3.4 的说法很朴实:这样做对样本质量有好处,而且实现起来更简单。代价写在 §4.3——真正优化变分上界得到的码长更好,但样本质量更差。这是全篇最诚实的一处取舍。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| 𝔼t, x0, ε | 对三件事取平均:从 1 到 T 均匀抽的 t、从数据集里抽的图、新抽的噪声 | 训练时这三样各抽一次就是一个样本 |
| ε | 真正掺进去的那份噪声,训练时是我们自己抽的,所以它就是标准答案 | 标签不用人标,自己造 |
| εθ(·, t) | 网络,输入是加噪后的图和时刻 t,输出和图同样大小的一份猜测 | 唯一带参数的东西 |
| 括号里那一长串 | 就是式 4,加噪后的 xt | 现场造出来的输入 |
| ‖·‖2 | 逐元素相减再平方求和 | 第 0 章那个平方差 |
完整变分界 L:每一项前面挂着自己的权重
它是负对数似然的真上界,优化它得到的码长最好。代价是 t 小的那些项权重极大,而那些项训练的是「去掉几乎不存在的噪声」,把网络的力气吸走了。
简化目标 Lsimple:所有 t 一视同仁
它不再是似然的上界,而是一个换了权重的变分界。t 小的项被相对压低,t 大的硬活得到更多关注。代价是码长变差,样本质量变好。
差别在:一个按似然该有的比例分配注意力,一个按人眼在意的比例分配注意力。
扔掉权重这件事还有一个副作用值得看。原来的权重 βt²/(2σt²αt(1−ᾱt)) 在 t 小的时候特别大,扔掉之后等于把小 t 的那些项降了权。DDPM 自己解释说这是好事:t 小时网络面对的是「去掉极小量噪声」这种几乎没有难度的任务,把它们压下去,网络才能把力气花在 t 大的硬活上。
按式 14 走一次训练,你需要哪几样东西?其中哪些要从数据集里取,哪些是现场抽的,哪些查表就有?另外,这里面有没有任何一项需要跑一遍完整的一千步?
变式:如果把 t 的抽法从均匀改成只抽 900 到 1000 之间,训练出来的模型在采样时会在哪一段出问题?
答辩:如果我是审稿人
你们从一个有理论保证的变分上界出发,推到式 12,然后把权重扔了。扔掉之后它不再是似然的上界,你们自己在 §4.3 也承认真正优化上界能得到更好的码长。那么这篇论文到底证明了什么?是「扩散模型有好的似然」,还是「把一个带理论的目标改坏了反而更好看」?
参考防守(先自己组织语言再看)
论文并不声称似然更好,摘要和 §4.3 都直说它们的对数似然比不过其他基于似然的模型,而且 CIFAR10 上超过一半的码长花在人眼看不见的细节上。它声称的是样本质量,Table 2 给出了对照:同样是简化目标,ε 参数化得到 FID 3.17,μ̃ 参数化直接训不稳定。所以可防守的说法是,Lsimple 是一个换了权重的变分上界,这个新权重更贴近人眼在意的东西。真正的软肋在别处——权重是被整体抹平的,抹平这件事本身没有理由,只有结果。后来 Improved DDPM 把权重重新捡回来一部分,做成 Lhybrid,就是在这个缺口上做的。
4.3 预测噪声和预测原图明明等价,为什么差这么多
由式 4,知道 ε 就能算出 x0,反过来也一样。那训练时让网络输出哪一个,最后的模型会有差别吗?
答完再看
换算是可逆的,但「可逆」说的是信息不丢,没说误差不放大。从 ε 换到 x0 要除以 √ᾱt,这个除数在 t = 100 时是 0.947,到 t = 1000 时是 0.006353,倒数从 1.056 一路涨到 157.4。同样一点预测误差,在 t 小时几乎原样传过去,在 t 大时被放大一百五十多倍。损失函数衡量的是误差,而误差在哪个空间里量,直接决定了梯度往哪儿走。DDPM 在 §3.2 用一句括号交代了他们试过预测 x0:早期实验里样本质量更差。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| x̂0 | 由当前这一帧和网络的噪声预测,反推出来的原图猜测 | 网络心里那张图 |
| 1 / √ᾱt | 放大系数。t = 100 时 1.056,t = 500 时 3.567,t = 1000 时 157.4 | 这一步的误差被乘几倍 |
| εθ | 网络输出,和 xt 同样大小 | 唯一的未知量 |
让网络输出 ε:目标尺度全程不变
不管 t 是 1 还是 1000,标准答案都是一份标准高斯噪声,每个分量的典型大小都是 1。损失的量级在所有 t 上差不多,一个统一的学习率就能用。
让网络输出 x0:目标尺度不变,可用的信息在变
标准答案始终是那张图,可输入里关于它的线索在 t = 1000 时只剩 0.6%。这时最好的答案就是整个数据集的平均,损失等于数据本身的方差——一个压不下去的大常数,横在平均损失里。
差别在:前者问的问题在每个 t 上都同样难,后者问的问题在 t 大的时候根本没有答案。
DDPM 把这件事做成了消融实验,两种参数化配两种目标,四格里有三格能跑出来。
| 网络输出 | 训练目标 | IS | FID |
|---|---|---|---|
| μ̃ | 完整变分界,固定各向同性方差 | 8.06 | 13.22 |
| μ̃ | 简化目标(去掉权重的平方差) | 训练不稳定,跑不出有效分数 | |
| ε | 完整变分界,固定各向同性方差 | 7.67 | 13.51 |
| ε | 简化目标,也就是 Lsimple | 9.46 | 3.17 |
这张表的读法不是「ε 更好」。前两行和第三行说明,在完整变分界下两种参数化打成平手(13.22 对 13.51)。差别是在换上简化目标之后才炸开的:ε 配简化目标把 FID 从 13.51 压到 3.17,μ̃ 配简化目标直接训不起来。也就是说,简化目标和 ε 参数化是一对,拆开任何一个都不成立。
上面的论证说「ε 的目标在所有 t 上尺度都一样,所以更好」。请构造一个这条论证失效的情形,并说明在那种情形下 ε 参数化会出什么问题。提示:把 t 推到比 1000 更远的地方想。
变式:另一头呢?把 t 推到 ᾱ 精确等于 1 的地方,也就是完全没有加噪。这时候 ε 参数化和 x0 参数化谁会先出问题?
4.4 网络在逼近的那个东西,能不能直接写出来
式 14 要网络最小化 ‖ε − εθ‖2。平方误差有一个所有人都该知道的性质:它的最小值点是条件期望。也就是说,训练到极限时,网络输出的已经不再是那一次抽到的那份噪声,它给出的是在看到这个 xt 和这个 t 的前提下,噪声的平均值。
这句话有个很实在的后果。那个平均值是一个确定的函数,由数据分布和调度表共同决定,和网络长什么样、用什么优化器、训多少步都没有关系。网络只是在逼近它。既然如此,只要数据分布简单到能算,我们就能把这个函数直接写出来,看一眼网络在追的到底是什么。第 1 章那个一维高斯正合适。
数据是一维高斯:两个数就够,函数写得出来
均值 1、标准差 0.5,整个数据分布说完了。条件期望有闭式解,是 x 的一次函数。不需要训练,不需要网络,一行算式就是那个「最优模型」。
数据是真实图片:没人写得出那个函数
要描述「什么样的像素组合看起来像一张真实照片」,需要的不是两个数。条件期望依然存在、依然唯一,只是没有任何人能把它写下来。网络就是用来顶替这个写不出来的东西的。
差别在:最优去噪器一直都在那里,区别只是你能不能把它写成公式。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| m, s | 数据分布的均值和标准差,这里取 m = 1、s = 0.5 | 整个数据集就这两个数 |
| x | 当前这一帧的值,一个实数 | 网络的输入 |
| 分母里的 ᾱts2 + 1 − ᾱt | xt 自己的方差 | 这一刻的总能量 |
| 整体 | x 的一次函数,斜率和截距只看 t | 一条随 t 转动的直线 |
分子分母里那对 (1−ᾱ) 和 √(1−ᾱ) 可以约掉一层,写成 √(1−ᾱt)(x − √ᾱtm) / (ᾱts2 + 1 − ᾱt),两种写法完全一样。重要的是它的形状:对 x 而言这是一条直线。斜率在 t = 1 时只有 0.0400,到 t = 500 时 1.0200,到 t = 1000 时 1.0000,截距则从 −0.0400 走到 −0.006353。整个「网络」就是一次乘法加一次加法。
知道了最优去噪器是一条直线,那在 t = 1000、也就是输入几乎是纯噪声的地方,这条直线大概长什么样?
答完再看
t = 1000 时斜率 1.0000、截距 −0.006353,几乎就是恒等映射。原因不难想:这时候 xt 里 99.4% 都是那份 ε 本身,最省事也最准的猜法就是把输入报回去。第一个选项把「对原图一无所知」和「对噪声一无所知」搞混了——网络被问的是噪声,而噪声恰恰在这一刻占据了输入的全部。第三个选项把两个不同的系数接错了地方:157.4 是把 ε 的误差换算到 x0 时的放大倍数,不是 ε 预测器本身的斜率。反过来看 t = 1 那一端,斜率只有 0.0400,因为那时输入里几乎全是信号,噪声的线索极其微弱。
既然最优去噪器有解析式,为什么真实的图像生成还需要一个几千万参数的网络?请从「这个解析式用到了什么」出发回答,并说明把数据从一维高斯换成两个高斯的混合之后,解析式还写不写得出来、形状会变成什么样。
变式:把数据分布换成一个均匀分布,最优噪声预测器还写得出来吗?它在 x 很大的地方会趋近什么?
答辩:如果我是审稿人
你们说网络逼近的是条件期望,还拿一维高斯把那个函数写了出来。可真实训练里网络既没有无穷容量,也没有训到最优,梯度下降还会停在某个局部解上。那么「网络在逼近条件期望」这句话,除了给读者一个好看的图像,还剩多少约束力?
参考防守(先自己组织语言再看)
它剩下的是一个判据。条件期望这个说法给出的是目标函数的唯一最小值点,而不是训练一定会到达的地方。它的用处在于:任何时候你怀疑模型出了问题,都可以问一句「它离那个点还有多远、在哪个 t 上偏得最厉害」,而不是只能盯着 loss 曲线。一维玩具正是为此存在的——那里的最优解写得出来,你可以把网络的输出和它逐点相减,看误差长什么样。真实图像上这个差算不出来,但同一套诊断思路还能用在低维投影上。承认这一点也就承认了它的边界:这个说法解释不了训练动力学,也预测不了收敛速度。DDPM 在 §4.2 给出的是实验证据,不是收敛性证明,两者不能互相替代。
4.5 那个加回去的随机项,删掉会发生什么
DDPM 的采样循环每一步先算出一个均值,然后再加回去一份全新的随机噪声 σtz。我们辛辛苦苦就是为了去噪,现在又往回加。把这一项删掉会怎样?
答完再看
用一维那个玩具跑一遍就看见了。从 xT = 2.0 出发,删掉随机项,跑满一千步,每一次都精确得到 1.00316642,一位小数都不差。而这个数不是随便一个数:在给定 xT = 2.0 的条件下,原图的条件期望解析算出来正是 1.00316642。也就是说,删掉随机项之后,整条一千步的循环退化成了一次条件期望——第 2 章那张糊成一团的平均脸,原封不动地回来了。打开随机项再跑两万次,得到的均值 0.9989、标准差 0.4977,恰好是数据本身那个 N(1, 0.52)。第二个选项说的「互相抵消」正好说反了:独立噪声的和方差是相加的,不是抵消的,它们累出来的宽度就是数据分布该有的宽度。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| 1/√αt | 把上一步缩小过的幅度放回去,αt = 1 − βt | 撤销前向那一次缩放 |
| (1−αt)/√(1−ᾱt) | 要从当前帧里扣掉多少份网络预测的噪声 | 只扣这一步该扣的那点 |
| σt | 不训练的常数,取 √βt 或者 √β̃t | 这一步重新撒多少随机性 |
| z | 新抽的标准高斯,t = 1 时规定为 0 | 最后一步不再撒 |
σt 有两个现成的取法,DDPM §3.2 说两者实验结果相近,并且各自在一个极端情形下是最优的:取 βt 对 x0 本身就是标准高斯的情形最优,取 β̃t 对 x0 固定成一个点的情形最优。两者的差别在 t 大的时候几乎为零(t = 1000 时都是 0.14142),在 t 小的时候才拉开(t = 2 时是 0.01095 对 0.00738)。
删掉 σtz:一千步走成一次平均
每一步都只走到那一团可能位置的正中心,一千步下来,中心的中心还是中心。起点定了,终点就定了,两万次跑出两万个一模一样的 1.00316642。
留着 σtz:一千步走成一次抽样
每一步走到中心之后,再按那一团的宽度随机挪一下。一千次挪动累积起来的宽度,正好补回被每一步平均掉的那部分多样性,两万次跑出标准差 0.4977 的一片结果。
差别在:去噪的每一步都在报一个平均值,而平均值是要还回去的,还的方式就是按该有的宽度重新撒一次。
删掉随机项之后,一千步的采样循环给出的是条件期望。这个结论和第 2 章那条「直接回归会模糊」的结论是同一件事的两种说法吗?如果是,为什么一个发生在一步之内、另一个发生在一千步之后,结果却一样?如果不是,差在哪?
变式:如果只在前五百步保留随机项、后五百步删掉,输出会落在哪里?它还是一个固定的数吗?
下一章会把这件事再翻一次。DDIM 也要把随机项删掉,删完之后却不会塌成平均脸,因为它在删掉这一项的同时改了另外两项,让整条路不再是「逐步取平均」,而是沿着一条固定的轨道滑过去。这两件事看起来都叫「确定性采样」,得到的东西完全不同。
照着式 11 下面那行手算一步:xt 给定,网络输出的 εθ 给定,写出这一步要做哪几次乘除、哪几个数要查表。如果有人把 1/√αt 误写成 1/√ᾱt,第一步就会发生什么?
变式:反过来,如果把 (1−αt) 误写成 (1−ᾱt),结果不会溢出,而是会变成什么样?
对你而言未知方差该取 βt、β̃t,还是让网络自己学
DDPM 把 σt 固定成常数,理由是两种取法效果相近。Improved DDPM 不同意,他们让网络输出一个插值系数,在 log 域上把 βt 和 β̃t 插起来(式 15),并报告说这件事明显改善了对数似然,却没怎么改善 FID。到底什么时候该学这个方差、学了之后为什么似然涨而样本质量不涨,这两篇论文都没给出机制层面的解释。
先做这一步:在本章那个一维实验室里,把 σt 分别设成 √βt、√β̃t 和两者的几何平均,各跑两万次,比较输出的标准差和真实的 0.5 差多少。再回去读 Improved DDPM §3.1 和它的式 15,看看他们为什么选择在 log 域插值而不是直接插值。
这一层要加什么:解析最优去噪器 + DDPM 采样循环
为什么现在才加它:第 3 级只会往前毁,手上没有任何能往回走的东西。这一层补上回头的那一半,而且用解析式顶替网络,先把循环本身调通——等到后面真的接上网络时,你已经有一条确定对的参照路径。
难点:这一层最容易写出一个「看起来在跑、其实没在生成」的东西。把随机项去掉之后循环照样跑完、照样收敛到一个漂亮的数,而那个数是所有答案的平均。这种错误不报错、不发散、损失曲线也正常,只有把输出的分布和数据的分布对一下才看得出来。另一个坑是下标:beta[t] 与 abar[t] 必须来自同一个 t,而循环里的 t 又要和上一层那张表对上。
自己验:把 noise 关掉,从 xT = 2.0 出发跑满 1000 步,输出应该是 1.00316642,而且重复多少次都是同一个数;这个数应该和解析的条件期望 E[x0 | xT = 2.0] 在小数点后六位上一致。再把 noise 打开跑两万次,样本均值应该落在 1.00 附近、标准差应该落在 0.50 附近(参考值 0.9989 与 0.4977),也就是把数据本身的 N(1, 0.52) 还原出来。两个都对上,这一层就成了。
读的时候要小心
关于本章数字的来路
一维玩具上的那些数(1.00316642、0.9989、0.4977、斜率截距、157.4)都是从解析式直接算出来的,旁边的实验室按一下就能复现。表 1 那四行分数来自 DDPM 作者自己在 CIFAR10 上的实验,没有第三方复现。
留给你的问题
删掉随机项之后,一千步塌成一次条件期望。那如果反过来,把 σt 调大一倍,会发生什么?输出的标准差是会跟着变成 1.0,还是会变成别的数?动手算之前先想清楚一件事:每一步多撒的那些随机性,下一步的去噪器会不会把它当成真的噪声、再老老实实扣掉一部分?
不看材料,写下这一章的三堵墙
第一堵:反向一步的真实分布要遍历整个数据集才能算,只有多条件一个 x0 之后它才变成能写出来的高斯。第二堵:ε 和 x0 可以互相换算,但换算里那个 1/√ᾱ 在 t = 1000 处是 157.4,等价的两个目标在训练上完全不等价。第三堵:把采样循环里的随机项删掉,一千步退化成一次条件期望,第 2 章那张平均脸原样回来。
第5章 一千步太慢
开篇故事第 5 集 · 同一个 α,两个不同的数这模型也太真实了
倒计时的红字跳到 00:01 的时候,屏幕已经把图吐出来了——不是还在等,是一整块饱和到发烫的紫红色,边缘一点过渡都没有。沈砚盯着这块颜色,又看了眼旁边计时器定格的数字:九百多毫秒,离他嘴里夸下的“一秒”只差一口气,图却先一步塌成了这个样子。
七天前那桩随机项的乌龙才刚消停,机房里那几张卡的风扇声一直没停过,半小时前这场赌就是在这样的背景音里,被沈砚自己挑起来的。显存刺客咬定跳步采样没那么便宜,步子跳得越大,网络就越是在回答一个从没见过的问题,不专门重训一版小一点的网络,出不来能看的图。沈砚翻着白眼说训练目标里压根没写过“下一步是谁”这种约束,同一套权重换个走法照样能用,当场拍板:今晚就用现在这套没动过的模型,把一张图从五十秒压到一秒,做不到,明天这条训练队列的头名让给显存刺客;做到了,显存刺客替他值三天夜班。
系统提示跳步采样第一步校验:预测原图这一项的取值远超 [-1, 1],本次输出标记为饱和,未触发数值溢出报错。
“图是废的。”显存刺客几乎是同时开口,“这把你已经输了。”沈砚没接这句话,转身把日志整段贴进了论坛,等着有人来说清楚这图到底废在哪一步。
这张饱和图到底是跳步跳崩了,还是哪里接错了
1L沈砚图是废的,但我觉得这不该怪步子跳太大,二十步老老实实跑完了,数组从第一步就已经冲出范围了。
2L显存刺客步子跳得越大网络就越是在瞎猜,二十步换一千步本来就是在赌能不能认出这个跨度,这把你已经输了。
3L复现之鬼不对,猜得糙顶多是糊、是伪影,不是第一步就整块顶到饱和这种死法,你们谁把每一步的数值范围打出来看看。
4L炼丹废人我这边先打了个日志,好家伙,第一步过后这个范围已经窜到几十往上了,往后每一步全在这个基础上接着放大。
沈砚把那份日志摊开,顺着数值往回查,查到第一步取用的那个系数时后背一下子凉了——他抄那条跳步更新式时图省事,直接从自己写好的数组里取了同名的那一个,取的是逐步的 α,可另一篇论文里那个 α 说的其实是自己代码里连乘出来的那个 ᾱ。t 在 1000 附近,逐步的 α 是 0.98,连乘的 ᾱ 却只有 4.0358e-05,开根号后一个是 0.99,一个是 0.006353——这串数字他并不陌生,上个月算前向过程时它就在同一个位置出现过,那次它是答案,这次是他自己把它接错了地方。
“两篇论文各挑了一套记号,第二篇图省事把连乘那个也叫作 α,两边一撞,同一个字母就成了两个不同量级的数。”靳工瞥了一眼那两个数字,“接错的那一项乘在预测出来的原图上,一步就被放大了一百多倍,图冲出范围,它不报错,也不会给你一个 NaN 提醒,就是安安静静给你一张饱和的图。”
沈砚把取数那一行换成连乘那张表,从同一个起点重新跑了一次,这回红字的倒计时刚好跳过一秒,图稳稳当当落在该有的范围里,显存刺客盯着屏幕,把已经开口说了一半的“三天夜班”又咽了回去。没人来得及为这场赌高兴太久——机房那几张卡的占用率一直没降下来,显存刺客盯着监控面板嘟囔了一句,说二十步再快,每一步还是要把整张图从头到尾算一遍,真正吃显存的那部分压根没被这次的赌局碰到,监控面板右上角那个警告图标还亮着,还没有人点开去看它到底在说什么。
系统结算 · 本章考点
- 同一个训练好的网络不需要重新训练就能配跳步采样器:训练目标只认「在噪声水平 t 上认出噪声」,从没规定过要走多少步、跳到哪一个时刻
- DDPM 的记号里 α_t 是单步系数,t = 1000 时约 0.98;另一篇论文里的 α_t 说的却是 DDPM 自己那个连乘 ᾱ_t,同一位置只有 4.0358e-05
- 两者开根号后一个是 0.99、一个是 0.006353,相差约 156 倍,把逐步的那个错接成连乘的那个,第一步就会把预测原图那一项按这个倍数放大
- 这种接错不会报错也不会出 NaN,只会让输出冲出 [-1, 1] 的范围,变成一张饱和的纯色图,查它得打印每一步的数值范围
- 1000 步、每步 50 毫秒,出一张图要 50 秒;砍到 20 步,同样是 50 毫秒一步,算下来就是 1 秒,这笔账只看步数,不看图有几张
上一章那条采样循环是对的,它也确实能造出东西来。问题是你按下回车之后要等。等的原因不在显卡不够快,在于那一千步必须一步一步来——第 700 步的输入是第 701 步的输出,一步不算完,下一步的网络前向根本没有输入可喂。整章要解决的就是这一件事。
学完这一章你应该能做到
- 说清为什么采样的一千次网络前向没法并行,而训练的一千个样本可以
- 解释同一个训练好的网络为什么能直接用于跳步采样,不需要任何重训
- 逐项读懂 DDIM 的更新式,说出三项各自在干什么,以及 η 调的是哪一项
- 在两篇论文的记号之间来回换算,并认出把 ᾱ 错当成 α 会造成什么后果
- 区分「论文实测的 FID 表」和「你自己能按出来的误差曲线」,说清两者各能支持什么结论
这一层要给第 4 级那条采样循环加上跳步的能力:让它在不碰网络、不碰训练的前提下,从一千步缩到几十步。前四级跑的都是「每一步都必须走」的情况,还没有任何东西可以被跳过。
5.1 一千步慢在哪里
先把账算清楚。DDPM 在 §4 写明所有实验都取 T = 1000,理由是这样采样时需要的网络前向次数和当时的同类工作对得上。训练时网络被调用一次,采样时网络被调用一千次,这两个一千的性质完全不同。
训练的一千个样本:可以一起算
每个样本抽自己的 t、自己的噪声,彼此没有依赖。一张卡上塞进一个批次,一千个样本一次前向就算完,显卡的并行度用得满满当当。
采样的一千步:只能排队
第 700 步要用第 701 步的输出当输入。不管你有多少张卡,这一千次前向都得按顺序来。加卡只能让你同时生成更多张图,不能让任何一张图更早出现。
差别在:一个是吞吐量问题,加机器就能解决;另一个是延迟问题,加机器一点用都没有。
数量级是什么样的?Improved DDPM 把 T 提到 4000 之后在 §4 里说,生成单张样本要在一块当时的现代 GPU 上花几分钟。DDIM 论文则给了一条更有用的规律(§5.1):生成一张图所需的时间,随采样轨迹的长度线性增长。线性这件事既是坏消息也是好消息——坏在一千步就是一千倍,好在只要能把步数砍到二十,时间也就跟着砍到五十分之一。
一个模型在一张卡上一次能并行处理 64 张图的网络前向,每次前向耗时 50 毫秒。用 T = 1000 的 DDPM 采样,生成 64 张图要多久?生成 1 张图要多久?把卡换成两张,这两个数各变成多少?
变式:如果换成把网络做小一半、单次前向降到 25 毫秒,但步数不变,生成 1 张图的时间变成多少?和把步数砍一半相比,哪种做法更划算?
5.2 反向过程凭什么一步都不能跳
手上有一个训练好的 DDPM,T = 1000。现在只走 t = 1000, 980, 960, …, 20, 0 这 50 个时刻,中间的全跳过。会发生什么?
答完再看
回头看式 14,里面只有 xt、t 和 ε 三样东西,xt−1 一次都没出现。网络被训练的是「在噪声水平 t 上认出噪声」,不是「从 t 走到 t−1」。第一个选项把训练目标记成了采样规则;第二个选项把调度表当成了网络的一部分。DDIM 在 §5 说得很干脆:用的是同一个训练好的模型,训练流程不需要任何改动,改的只是怎么从模型里产生样本。
这个观察值得再往下挖一层,因为它解释的不只是跳步。上一章我们从一条马尔可夫链出发,一路推到式 14。DDIM 反过来问:如果扔掉「前向必须是一条一步接一步的链」这个假设,只保留式 4 那些边缘分布,还剩多少自由度?
答案是剩下一整族。可以构造出一大批不同的前向过程,它们每一个时刻的边缘分布都和式 4 一模一样,只是把 xt−1 同时依赖 xt 和 x0,不再是一条链。这一族里的每一个成员对应一个采样过程,而 DDIM 的定理 1 说:这一族的训练目标,彼此之间只差一个权重和一个常数。
以为要改训练:换一个采样器就重训一次
如果训练目标真的绑在「相邻两步」上,那么 50 步、20 步、10 步各要一个模型,每个都得从头训几天。加速这件事就变成了一次次重新付出训练成本。
实际只改采样:一个模型配无数个采样器
训练目标只关心「给定噪声水平,认出噪声」。同一套权重可以配 1000 步的采样器、50 步的、10 步的,甚至配上确定性的或者随机的,互相之间随时切换。
差别在:训练学的是一个逐点的函数,采样定的是怎么用这个函数走路。这两件事从一开始就是分开的,只是 DDPM 的推导把它们写在了一起。
这条结论的形状比它的推导重要得多。它说的是:训练和采样之间的接口,只有「在噪声水平 t 上的噪声预测」这一个函数。接口之外的一切——走几步、每步跨多远、要不要撒随机——都是采样时才决定的事。后面几章还会再用到它,因为流匹配、蒸馏、各种求解器,做的都是在同一个接口上换一种走法。
打开式 14 逐个符号检查:哪些东西在训练时被固定下来了,哪些东西训练时根本没被提到、因而采样时可以自由决定?把「调度表 ᾱ」也放进去一起判断。
变式:如果采样时把 ᾱ 表整体换成 cosine 调度的那一张,而模型是用线性调度训的,网络会在哪个 t 上错得最厉害?
答辩:如果我是审稿人
「同一个训练好的模型直接就能跳步采样」这句话说得太满了。Improved DDPM 复现你们的方法时报告了两件事:DDIM 在训练早期表现明显更差,要到训练后期才追上其他采样器;而且他们自己那套取子序列的方式会让 DDIM 的表现大幅下降,只好改用固定间隔。那「不用改训练」到底成立在什么条件下?
参考防守(先自己组织语言再看)
成立的是「不用改训练流程」,不是「训练怎么样都无所谓」。这两件事都指向同一处:跳步采样对噪声预测函数的精度要求比逐步采样高。逐步采样每一步只前进一点点,某一步预测得差一些,后面还有几百步可以修;跳步采样一跳就跨过几十个时刻,误差没有被稀释的机会。训练早期网络本来就预测得粗,所以 DDIM 吃亏更大;子序列选得不好,某一跳跨过的信噪比区间过大,同样吃亏。可防守的结论是:训练流程确实一个字都不用改,但跳步这件事把原本被步数掩盖掉的模型误差重新暴露了出来。这也正好解释了为什么后来会出现蒸馏——如果要把步数压到个位数,光换采样器不够,必须动训练。
5.3 两篇论文的 α 是不是同一个东西
在读 DDIM 的公式之前,必须先处理一件会让你调试三天的事。DDPM 里 αt = 1 − βt 是单步的系数,连乘起来才是 ᾱt。DDIM 论文里没有 ᾱ 这个记号,它直接用 αt 表示 DDPM 的 ᾱt。
记号重载:DDIM 论文中的 αt,等于 DDPM 论文中的 ᾱt,也就是前 t 个 (1−β) 的连乘。DDIM 在附录 C.2 自己用一段彩色标注说明了这件事,并给了三条理由:这样只需要选一组超参数,减少派生变量之间的来回引用;便于引入推广形式与加速形式,因为它的推断过程不再由扩散过程推动;以及 α1:T 和 1 到 T 之间存在一一对应,而 βt 没有这个性质。
DDPM 的字母表
βt 是单步噪声强度,αt = 1 − βt 是单步信号系数,ᾱt 是连乘。t = 1000 时 αt = 0.98,ᾱt = 4.0358e-05。三个字母,三个量级完全不同的数。
DDIM 的字母表
只有一个 αt,它就是上面那个 ᾱt。t = 1000 时 αt = 4.0358e-05。需要 β 时再从 α 反解,附录 C.2 给的是 βt = 1 − αt/αt−1。
差别在:同一个希腊字母,一边指 0.98,一边指 0.000040,相差两万四千倍。
有人照着 DDIM 式 12 写代码,把里面的 αt−1 接成了自己代码里的 alpha[t-1](也就是 1 − β),而不是 abar[t-1]。第一步采样之后,数组的量级会变成什么样?这个错误会报错吗?
alpha[t-1] 而不是 abar[t-1])比正确的数大出多少倍,这个倍数会原样乘到哪一项上。alpha[t-1] 在 t=1000 附近是 0.98,abar[t-1] 是 4.0358e-05,开根号后分别是 0.99 和 0.006353,相差约 156 倍。式 12 第一项 √αt−1 乘的是预测出来的原图,这一项会被放大多少倍?alpha 是 0.98,abar 是 4.0358e-05,开根号之后一个是 0.99,一个是 0.006353,差 156 倍。式 12 第一项里 √αt−1 乘的是预测出来的原图,第二项里 √(1−αt−1) 乘的是噪声预测。接错之后,原图那一项被放大 156 倍、噪声那一项几乎归零,采样出来的东西在第一步就远远超出 [−1, 1] 的范围。它不会报错,不会出 NaN,只会给你一张纯色的图或者一堆饱和的色块——这正是它难查的原因。查它的办法是打印每一步的数组范围:正确的实现里这个范围应该一路缓慢收窄到 [−1, 1] 附近。变式:反过来,有人读 DDPM 的代码时把 ᾱ 当成了单步的 α,在计算 1/√αt 时用了 ᾱ。这个错误会以什么形式暴露?
5.4 跳一步的公式长什么样
有了前面两节,更新式几乎可以自己推出来。既然网络能在任意噪声水平上认出噪声,那么站在 xt 上想去 xs(s 比 t 小,中间隔多远都行),最自然的动作只有两步。
自己推一遍:从「先猜原图」到 DDIM 的更新式
站在 xt 上,网络给了你一份 ε 的预测。第一个动作该做什么?
想好了再看
把原图猜出来。式 15 已经给了:x̂0 = (xt − √(1−ᾱt)·εθ) / √ᾱt。DDIM 把这个量单独起了名字叫「去噪后的观测」,并在式 12 里把这一项直接标注成 predicted x0。注意这一步跟 t 跳多远完全无关,它只用到当前这一帧。
现在手上有一个 x̂0,目标是造出噪声水平为 s 的 xs。怎么造?
想好了再看
照着式 4 重新加噪:xs = √ᾱs·x̂0 + √(1−ᾱs)·(一份噪声)。整件事就是「猜回去再毁一点点」。关键在最后那份噪声用什么——这正是 DDIM 和 DDPM 分道扬镳的地方。
最省事的办法是新抽一份噪声。但我们手上已经有一份了,就是网络刚刚预测出来的 εθ。用它有什么好处?
想好了再看
用它就不引入任何新的随机性,整条路变成一个确定的映射:给定 xT,终点就定了。更深一层的理由是,εθ 正是当前这一帧里那份噪声的最佳估计,沿着它走相当于「不改变这条轨迹的身份,只改变它走到了哪个噪声水平」。DDIM 把这一项标注成 direction pointing to xt,方向的含义就在这里。
如果既想用手上那份 εθ,又想撒一点新的随机性,两份噪声的系数该怎么配?
想好了再看
总的噪声方差必须还是 1−ᾱs,否则造出来的 xs 的边缘分布就不对了。所以给新噪声分配 σt² 之后,留给 εθ 的系数只能是 √(1−ᾱs−σt²)。这条「方差加起来要等于 1−ᾱs」的约束,和第 3 章那条方差守恒是同一件事,只是这次守的是噪声那一半。
| 符号 | 是什么 | 论文给它起的名字 |
|---|---|---|
| √ᾱs · x̂0 | 先把原图猜出来(式 15),再按目标噪声水平缩放 | predicted x0 |
| √(1−ᾱs−σt2) · εθ | 沿着网络预测的那份噪声方向,把噪声重新加回到 s 的水平 | direction pointing to xt |
| σt ε | 新抽的随机噪声,σt = 0 时整项消失 | random noise |
| s | 下一个要落脚的时刻,可以是 t−1,也可以是 t−20 | —— |
| ᾱ0 | 约定为 1,最后一步落回干净图 | —— |
DDPM 的一步:在当前这一帧上做减法
式 11 的形状是「把 xt 减掉一点噪声,再除以 √αt 把幅度放回去」。整个动作绑死在 t 和 t−1 之间,因为那两个系数是按单步的 βt 配出来的。
DDIM 的一跳:先离开这一帧,再重新落点
式 12 的形状是「先回到 x0 那一端,再按 ᾱs 重新出发」。中间不经过任何一个时刻,所以 s 取多远都一样。两个式子在 s = t−1 且 η = 1 时重合。
差别在:一个在原地修修补补,一个每一步都先回到起点再重新落点。
DDIM 在 §4.2 给出跳步的具体做法:从 1 到 T 里挑出一个递增子序列,长度为 S,只在这些时刻上走。子序列上每一点的边缘分布仍然由式 4 保证是对的,所以中间跳过的那些时刻从来没有被需要过。§4.3 还指出,把式 12 改写一下就能看出它是某个常微分方程的欧拉法积分——欧拉法是数值求解里最朴素的那一种,把一条曲线近似成一串直线段,步子越短越准。这句话后面会兑现成一条很干净的误差规律。
有人说:既然每一步都是「猜回原图再毁一点」,那走一步和走一千步应该没区别,反正第一步就已经猜出原图了。请用一个具体的数值反例说明这话错在哪,并解释多出来的那些步到底买到了什么。
变式:把步数从 1 加到 2,输出从 1.00316642 变成 1.02309485,只动了 0.02。为什么第二步买到的东西这么少,而从 10 步到 20 步却能把误差砍掉一半?
5.5 η 从 0 调到 1,中间是什么
式 12 里那个 σt 可以自由选。取 0 得到确定性采样,取得和 DDPM 一样大就退回上一章那个采样器。在只走 10 步的情况下,你觉得哪一头的图更好看?
答完再看
DDIM 的 Table 1 在 CIFAR10 上给了完整的一条:走 10 步时,η = 0 的 FID 是 13.36,η = 0.2 是 14.04,η = 0.5 是 16.66,η = 1.0 是 41.07。从 0 到 1,FID 涨了三倍。第二个选项忽略了一件事——随机项撒进去之后,要靠后面的步数把它重新去掉,步数只剩十步时根本来不及。而到了 1000 步这一端,四个数分别是 4.04、4.09、4.29、4.73,差距几乎抹平。所以 η 不是一个「好坏」的旋钮,它是一个和步数耦合的旋钮。
DDIM 把 σt 的取法参数化成一个数 η,写在式 16 里。这个式子的作用是把两个已知的端点连起来,中间的部分按比例插值。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| η | 一个不小于 0 的旋钮。η = 0 是 DDIM,η = 1 是原来的 DDPM | 这一步要撒多少随机性 |
| τi、τi−1 | 子序列上相邻的两个时刻,中间可以隔很远 | 这一跳的起点和落点 |
| 第一个根号 | 两个噪声水平的比值,把方差换算到落点的尺度上 | 按落点该有的宽度折算 |
| 第二个根号 | 这一跳自身新增的噪声量,跳得越远它越大 | 跳得远,可撒的也多 |
| η = 1 的整体 | 正好是第 4 章那个 β̃t 开根号,只是把相邻两步换成了相邻两个落点 | DDPM 是这一族里的一个成员 |
η = 0:同一个起点,永远同一个结果
一维玩具上走 50 步,两万次跑出来的标准差精确是 0,每一次都是 1.94083222。xT 成了这张图的完整编码,DDIM 论文 §5.2 正是靠这一点做插值和重建。
η = 1:同一个起点,每次都不一样
同样走 50 步,两万次的均值 1.0097、标准差 0.4429。中间几档是连续的:η = 0.2 时标准差 0.2689,η = 0.5 时 0.4469。旋钮转到哪,抖到哪。
差别在:η = 0 把 xT 当成一个坐标,η = 1 把 xT 当成一个起点。
Table 1 里 η = 1.0 那一行,从 10 步到 1000 步 FID 从 41.07 降到 4.73;η = 0 那一行从 13.36 降到 4.04。为什么随机的那一条曲线下降得更陡?用「随机性要靠后续步数修回来」这个说法给出一个机制上的解释。
变式:Table 1 里还有一行 σ̂,它的随机性比 η = 1 还大,10 步时 FID 是 367.43,1000 步时降到 3.17,比 η = 0 的 4.04 还低。这一行说明了什么?
5.6 两张表,为什么只有一张你能自己验
接下来会摆出两张表:一张是 DDIM 论文在 CIFAR10 上测的 FID 随步数变化,一张是本站在一维玩具上算的采样误差随步数变化。你觉得这两张表的关系是什么?
答完再看
两张表能支持的结论不一样,混着用会得出错误的推断。玩具那张表的误差从 50 步到 100 步精确地减半,因为它量的是数值积分的截断误差,而欧拉法的误差正比于步长。FID 那张表不会这样减半,因为 FID 量的是两个分布之间的距离,它有一个由模型本身决定的地板,步数再多也下不去。第一个选项的问题在于「趋势一样」算不上复现——一维玩具里根本没有网络,也就谈不上复现一个训练出来的模型。
| 步数 S | 10 | 20 | 50 | 100 | 1000 |
|---|---|---|---|---|---|
| η = 0(DDIM) | 13.36 | 6.84 | 4.67 | 4.16 | 4.04 |
| η = 1.0(DDPM) | 41.07 | 18.36 | 8.01 | 5.78 | 4.73 |
这张表支持的结论是 DDIM 论文 §5.1 自己下的那一句:DDIM 在 20 到 100 步之内达到 1000 步模型的质量,相当于 10 倍到 50 倍的加速。你相信这些数字,靠的是你相信作者;本站没有 CIFAR10,没有那个训练好的 UNet,也跑不动 FID 所需要的五万张样本。它是一份转述。
表 1 这一类:你信它,是因为你信作者
需要一个训练好的模型、一个数据集、五万张采样、一套 FID 实现。本站一样都没有。它能告诉你真实图像上会发生什么,代价是你无法亲自检验任何一个数字。
表 2 这一类:你信它,是因为你自己按过
一个解析式加一段循环,浏览器里就能跑完。它给不了任何关于图像质量的结论,但它给出的规律是精确的,而且你可以改参数去试探这条规律什么时候失效。
差别在:一个覆盖你真正关心的场景却不可复算,一个可复算却只覆盖一个玩具。两个都要,而且不能互相冒充。
| 步数 S | 输出 x0 | 绝对误差 |
|---|---|---|
| 1 | 1.00316642 | 9.907e-01 |
| 2 | 1.02309485 | 9.707e-01 |
| 5 | 1.44874215 | 5.451e-01 |
| 10 | 1.72092619 | 2.729e-01 |
| 20 | 1.85816878 | 1.357e-01 |
| 50 | 1.94083222 | 5.299e-02 |
| 100 | 1.96860868 | 2.522e-02 |
第二张表你可以自己按出来,而且它给出了第一张表给不了的东西:一条精确的规律。步数每翻一倍,误差就减半(5.299e-02 除以 2.522e-02 等于 2.10),这正是欧拉法一阶精度的签名,和 DDIM §4.3 那句「式 12 可以看成某个常微分方程的欧拉积分」严丝合缝。它能支持的结论是关于求解器的;它不能支持任何关于图像质量的结论。
把表 2 的误差画成曲线,横轴步数、纵轴误差。在双对数坐标下,这条曲线会是什么形状?
答完再看
误差正比于 1/S 意味着 log(误差) = −log(S) + 常数,在双对数坐标下就是斜率 −1 的直线。表 2 从 10 步到 100 步的四个点完全落在这条线上。第一个选项描述的是 FID 曲线的形状,它确实有地板,因为模型本身的偏差不会随步数消失;玩具这条曲线没有地板,因为参考解正是同一个求解器在 1000 步下的结果,两者之间只差截断误差。把这两种形状认混,就会得出「步数加到一万还能继续变好」这种推不出来的结论。
要把步数从 1000 砍到 20,除了改采样器,还有一件事可以一起调:这 20 步该落在哪 20 个时刻上。均匀地取 t = 1000, 947, 894, … 是最顺手的做法。请说明这个做法在什么情况下会吃亏,并给出一个更合理的取法。
变式:如果模型是用 cosine 调度训的,按 t 均匀取 20 步的吃亏程度会更大还是更小?为什么?
答辩:如果我是审稿人
你们说 DDIM 不用重训,可 Table 1 里 η = 0 那一列在 1000 步时是 4.04,而 Ho 等人原始的 DDPM 在 1000 步时报的是 3.17。也就是说在步数充足的时候,你们的确定性采样器反而更差。那 DDIM 的优势到底是加速,还是换来了一个上限更低的采样器?
参考防守(先自己组织语言再看)
这个对照是成立的,而且论文自己把它摆在了 Table 1 最后一行的 σ̂ 那一栏:1000 步时 σ̂ 得到 3.17,确实低于 η = 0 的 4.04。可防守的说法是,DDIM 从来没有声称在充足步数下最优,它声称的是在少步数下最优,论文 §5.1 明确写着 σ̂ 在小步数时质量差得多(10 步时 367.43),不适合短轨迹。所以正确的读法是两条曲线在不同区间各有归属:要 1000 步的极致质量,用 σ̂;要 20 到 100 步的实用速度,用 η = 0。真正的软肋在于论文没有解释这个交叉为什么存在——为什么额外的随机性在长轨迹上会带来收益,这件事在 DDIM 里只有现象,没有机制。
对你而言未知FID 曲线为什么比误差曲线更早触底
表 2 那条误差曲线一路按 1/S 下降,看不到地板;表 1 那条 FID 曲线在 50 步之后基本就平了(4.67、4.16、4.04)。也就是说,从 50 步到 1000 步,轨迹的数值误差还在减小二十倍,而图像质量几乎不再变化。两者之间差的那部分是什么,本站的材料里没有给出答案:可能是模型自身的偏差已经主导了 FID,可能是 FID 这个指标对残余的积分误差不敏感,也可能两者都有。
先做这一步:在本章的步数实验室里,把步数从 10 一路扫到 1000,把误差曲线和表 1 的 FID 曲线画在同一张双对数图上,找出两条线开始分家的那个步数。然后回去读 Progressive Distillation 的 §2 和 §3——那篇论文正是从「数值积分误差」这个角度重新组织了整件事,看看它对这条地板给了什么说法。
这一层要加什么:DDIM 跳步采样器
为什么现在才加它:第 4 级那条循环必须一步不漏地走完一千步,因为它的每一步都写死了「从 t 到 t−1」。这一层把落脚点从「下一个整数」换成「子序列里的下一个」,顺便把随机项做成一个可调的旋钮。
难点:这一层有两个下标要同时对上,而且它们错位之后都不会报错。一个是子序列本身——tau 必须包含 T−1 这个端点,漏了它第一步就从一个错误的噪声水平起跳,而输出看起来仍然是一张正常的图。另一个是 ap,它取的是子序列里的前一个时刻,不是 t-1;照着 DDIM 论文抄的人经常在这里写成 abar[t-1],于是每一跳都只前进一步,S 这个参数形同虚设,跑得和一千步一样慢却更不准。第三个坑在上一节讲过:论文里的 α 是这里的 abar。
自己验:三件事。第一,从 xT = 2.0 出发,η = 0 跑 50 步应该得到 1.94083222、跑 100 步应该得到 1.96860868,与 1000 步参考解 1.9938237161 的绝对误差分别是 5.299e-02 和 2.522e-02,后者约为前者的一半(比值 2.10);步数每翻倍误差减半这条规律要在 10 到 100 之间一路成立。第二,η = 0 时同一个 xT 反复跑,输出必须逐位相同,标准差精确为 0;把 η 调到 1.0,同样走 50 步跑两万次,标准差应该跳到 0.44 附近(参考值 0.4429)。第三,把 S 设成 1000,输出应该和第 4 级那条循环关掉随机项之后的结果不同(1.9938 对 1.0032),这一条能抓出把 DDIM 写成了确定性 DDPM 的实现。
读的时候要小心
关于本章数字的来路
表 2 和所有一维玩具上的数都是解析式加求解器直接算出来的,实验室里按一下就能复现。表 1 那些 FID、「10 倍到 50 倍加速」以及 σ̂ 那一行,全部来自 DDIM 作者自己的实验,没有第三方复现。
留给你的问题
误差按 1/S 下降是欧拉法的签名。数值分析里有比它更准的方法,比如把两次函数求值组合起来的二阶方法,误差按 1/S² 下降。照这个说法,把式 12 换成二阶方法,20 步就能达到现在 400 步的精度。在动手之前先想清楚一件事:二阶方法每一步要调用两次网络,而网络前向正是这里唯一昂贵的东西。在网络调用总次数固定为 50 的前提下,是走 50 步一阶,还是走 25 步二阶?
不看材料,写下这一章的三堵墙
第一堵:一千次网络前向必须串行,加卡只增加吞吐,不减少一张图的等待时间。第二堵:以为跳步要重训,其实训练目标里从来没有出现过「下一步是谁」。第三堵:两篇论文的 α 指的不是同一个量,接错之后不报错,只是悄悄给你一张饱和的图。
第6章 潜空间:把算力花在看得见的地方
开篇故事第 6 集 · 说好的六十四倍,只到账四十八倍这模型也太真实了
训练日志刚刷出前几行,显存占用那根横条已经蹿到了显存刺客昨晚画的那条红线附近——他昨晚信誓旦旦保证过,压缩之后这根线绝不会再碰红线。沈砚的手指扣在触控板边缘一直没挪开,像是想在它真正撞线之前抢先按下暂停。
事情要从昨天下午说起。距离那场压到一秒的赌局刚过去七天,组里一直在 256×256 这个小尺寸上打磨,真要出图,分辨率得提到 1024×1024,像素数从 65,536 一路涨到 1,048,576,整整 16 倍,昨晚那台机器根本扛不住,任务半夜就被系统自己杀掉了。显存刺客翻出一篇论文,说与其在原图这个尺寸上死扛,不如先把图压小——编码器把边长压到八分之一,1024×1024 的图就变成 128×128 的格子,格子数只有原图的六十四分之一,他当场在群里宣布,这一步能把显存直接省下六十四倍,今晚的任务可以放心把批次调大。
系统提示潜空间数值统计:格子数 16,384,与原图像素数之比为 1/64;潜空间每格 4 通道、原图每像素 3 通道,数值个数之比为 48 倍。
显存刺客坚持自己没算错,格子数明明白白摆在那儿;复现之鬼当场在论坛上把这两个数字并排贴了出来,问是不是有人把「格子少了多少」和「数字少了多少」这两件事当成了同一件事。
六十四倍和四十八倍,这两个数到底该信哪一个
1L复现之鬼显存刺客,你那个六十四倍是拿格子数算的,还是拿真正过网络的数值算的,现在这俩对不上号?
2L炼丹废人我倒觉得六十四倍就六十四倍,格子少了那么多,显存不该跟着差不多降吗。
3L显存刺客格子从一百多万降到一万六千多,账我摆在这儿了,不是六十四倍是什么,你们非要在字眼上较劲。
4L沈砚不是较字眼,是格子里存的通道数从三变成了四,你数的是位置,真正塞进网络的是数值,这两笔账从来不是同一件事。
沈砚把两组数字摆到一起:压成 128×128 之后是 16,384 个格子,只有原图格子数的六十四分之一,这一步显存刺客确实没算错。可潜空间里每个格子存的不是一个数,是四个数;原图每个像素才三个数。格子数乘上各自的通道数,潜空间总共是 65,536 个数值,原图总共是 3,145,728 个数值,两边一除,真正的压缩倍数是 48,不是六十四。
“格子数比只问位置少了多少,跟通道数没关系;数值个数比才是真正要存、要算的数字少了多少。”靳工把两组数字并排摆好,“通道从三涨到四,六十四倍的位置压缩,最后只换来四十八倍的数值压缩,这两笔账从来划不了等号。”
显存刺客脸有点白:他按六十四倍留出来的余量,本来就没留够,批次调大之后,实际要算的数值只比原图少了四十八倍,那根横条这才一路蹿到了红线附近。沈砚赶在它撞线前一把摁停任务,把批大小砍掉一档重新提交,显存刺客申请到的那个整点档期已经过去,任务被系统自动扔到了排队末尾。省下来的显存该先花在哪儿,谁都还没商量出结果——靳工只留下一句,这几层网络里到底是卷积贵,还是那一层管跨模态的注意力贵,答案还没人真正算过。
系统结算 · 本章考点
- 从 256×256 换到 1024×1024,像素数从 65,536 涨到 1,048,576,整整 16 倍,这笔账不会因为显卡不够就打折扣
- 把边长压缩到 1/8,1024×1024 的图在潜空间里变成 128×128 的网格,只有 16,384 个格子,格子数比是 1/64
- 潜空间每个格子是 4 通道,原图每个像素是 3 通道:潜空间总数值 65,536,原图总数值 3,145,728,两者相除的数值压缩倍数是 48,不是 64
- 格子数比只问位置少了多少,跟通道数无关;数值个数比才是真正要存、要算的数字少了多少,通道数一变,两个比值就分道扬镳
- 网络处理的是数值,不是抽象的位置:凡是要算显存、算计算量的账,用的都得是数值个数比这一条,不是格子数比
把一张照片存成 JPEG,压缩到十分之一体积,拿去和原图放在一起看,多数人分不出哪张是哪张。丢掉的那九成数据,丢的是你的眼睛原本就分辨不出的东西——一小块几乎同色的天空里那些像素到像素的微弱抖动,人从来没有认真看过。上一章那条一千步的采样循环,跑在整张图的每一个像素上,正好踩在同一个账上:它没工夫区分哪块是天空、哪块是发丝,每个位置都按同样的力气去猜。
学完这一章你应该能做到
- 说清扩散模型在像素空间里跑为什么贵,贵在哪一类计算上
- 算出给定分辨率和压缩倍数 f 下,潜空间的格子数,并分清「格子数比」和「数值个数比」这两个不是同一回事的数
- 说出潜空间通道数从 4 涨到 16、32 分别在解决什么问题、又带来了什么新麻烦
- 写出 cross-attention 的 Q/K/V 分别来自哪里,并解释为什么不能反过来
- 说清 SDXL 那句「3 倍大的 UNet」具体涨在了哪,以及这条手工设计的路线在哪里开始吃力
这一层要给第 4、5 级那个已经能跑、能跳步的采样循环换一个战场:不再直接对着整张图工作,而是先把图压小,让整条加噪、去噪的机器在压小后的空间里跑,最后再翻译回像素。前五级的每一步计算都摊在原始分辨率上,还没有人问过这些计算是不是都花得值。
6.1 压缩掉的九成,你看得出少了什么吗
一张照片压缩成十分之一大小的 JPEG,和原图放在一起,你觉得多数人能不能一眼分出哪张被压过?
答完再看
LDM 论文把这件事画成了一张图,图注写得很直接:数字图像里的大部分比特,对应的是人眼感知不到的细节(Figure 2 caption)。压缩算法不是在赌运气,它专门找这些「删了也看不出」的地方下手,删剩下的才是真正决定这张图「看起来是什么」的那部分。第一个选项把「压缩」和「压得太狠」混成了一件事——JPEG 到十分之一体积的典型压缩比,通常还没有逼近这条底线。第三个选项的直觉是对的一半:确实有内容会影响可压缩的空间,但「一眼分不出」这件事对绝大多数自然照片都成立,不需要挑图。
扩散模型在像素空间里干的事,恰好没有这么讲究。第 3 到第 5 章那条加噪、去噪的机器,不管某个位置是发丝还是天空,都按同一套公式去处理:一样的噪声、一样的一次网络前向、一样多的算力。这不是网络偷懒,是像素空间这个战场本身没有给它「跳过不重要的地方」这个选项——它拿到的输入就是一整张等大的数组,式 4 和式 14 对数组里的每一个位置都一视同仁。
先把量级摆出来。第 3 章那个 256×256 的例子有 65,536 个像素;换成 1024×1024,像素数变成 1,048,576,整整 16 倍。第 5 章说清楚了这一千步没法并行,每一步的网络前向都得老老实实排队;而每一步要处理的位置数又跟着分辨率涨了 16 倍,涨出来的这部分不会因为「步数不能并行」就有任何折扣,它老老实实叠在每一步头上,一千步一起承受。LDM 论文引用 ADM 的说法印证了这笔账有多贵:训练最强的那批扩散模型常常要花掉数百个 GPU 天,具体数字是 150 到 1000 个 V100 天(LDM §1)。这还是在没有算上把分辨率继续推高的情况下。
把训练分辨率从 512×512 换成 2048×2048(每条边涨 4 倍),像素总数涨了多少倍?如果扩散模型每一步的计算量大致跟像素数成正比,一千步下来这笔账会怎样变化?
变式:如果反过来,把分辨率降到四分之一(边长减半两次),采样一千步的总计算量大致降到原来的几分之一?
LDM 论文把学习一个数据分布这件事拆成两段来看(LDM §1):第一段是感知压缩,删掉高频细节,但几乎不涉及语义上的变化;第二段才是真正的生成模型去学数据的语义和结构组成。像素空间里的扩散模型,被迫把这两段工作压在同一套网络、同一份计算里。
第一段:感知压缩
删掉高频细节、几乎不涉及语义变化的那部分工作——JPEG 干的就是这件事,用一个不需要理解「这是什么」的算法就能做到。
第二段:语义/概念压缩
学会数据真正的结构和组成——「这是一只猫」「猫在草地上」这类需要理解内容的工作,只有生成模型才干得了。
差别在:像素空间扩散把这两段工作压在同一套跑一千步的网络里,大部分算力其实花在第一段——而第一段本来不需要一个这么贵的生成模型来做。
6.2 压缩到多小才合适,怎么试出来的
第 2 章已经埋下了这件事的雏形:VAE 训练一个编码器把图压成一小串数,一个解码器把它还原回去,两者绑在一起训练。LDM 把这套压缩器接到扩散模型前面——编码器先把图压进一个小得多的潜空间,扩散的加噪、去噪整条机器只在这个潜空间里工作,最后用解码器把结果翻译回像素。压缩多少合适,不是拍脑袋定的,论文把从几乎不压到压得很狠的整个区间都跑了一遍。
潜空间(latent space):编码器把原始数据压缩后落脚的那个更小的空间,里面的每一个点仍然对应一张(近似)合法的图,只是坐标数少了很多。扩散模型在这里训练、在这里采样,只有最后一步才经过解码器翻回像素。
论文把压缩倍数记成 f,编码器把边长压缩到原来的 1/f,试了 f ∈ {1, 2, 4, 8, 16, 32} 这整整六档(LDM §4.1),其中 f = 1 就是完全不压缩、退化回像素空间扩散。两头都不好:f 太小,潜空间和像素空间没差多少,训练进度慢,第 6.1 节那笔账基本没省下来;f 太大,压缩器把太多信息一起删掉了,扩散模型能力再强也补不回来,样本质量早早地就卡在一个不高的地方不再上涨。
压得太轻:LDM-1、LDM-2
潜空间和原图几乎一样大,扩散模型还是要在接近原始分辨率的战场上跑,训练进度明显慢,6.1 节那笔账基本没省。
压得太狠:LDM-32 这一档
压缩器在训练早期就替扩散模型删掉了太多信息,样本的逼真程度早早地卡住不再往上涨——这不是扩散模型学得不够好,是它面对的输入本身已经丢了不该丢的东西。
差别在:一头是没有把该省的算力省下来,另一头是把不该丢的信息一起丢了。中间才是论文真正要找的区间。
论文自己的结论写得很直接:LDM-4 和 LDM-8 提供了取得高质量合成结果的最佳条件(LDM §4.1)。这不是一个可以从头推导出来的数,是把六档都跑一遍、拿 FID 和采样速度一起比较之后选出来的经验区间——后面大多数基于 LDM 这条路线的模型,用的压缩倍数都落在这个区间里。
f = 32 这一档训练早期就卡住了,样本质量不再随训练涨。这和第 2 章「直接回归会模糊」那堵墙是不是同一个机制?请说清楚是,或者说清楚不是。
变式:如果把 f = 32 换成用一个容量小十倍的扩散模型去学 f = 4 的潜空间,会撞上哪一种问题,还是两种都不会撞上?
答辩:如果我是审稿人
LDM-4、LDM-8 之所以看起来「最佳」,会不会只是因为整条评测流程的上限被压缩器自己先卡住了?解码器重建原图都做不到完全无损,那后面报告的所有 FID,衡量的到底是扩散模型学得好不好,还是压缩器本身漏掉了多少信息?
参考防守(先自己组织语言再看)
这个担心是成立的,而且论文自己承认了这一点——图 1 的标题就叫「用更温和的下采样提升可达到的质量上限」,「上限」两个字说明作者清楚压缩器决定了一条天花板,扩散模型的分数不可能超过这条线。可防守的说法是:这条天花板确实存在,但论文单独测过压缩器自身的重建质量(用重建 FID 和 PSNR),把这部分和「扩散模型在潜空间里学得好不好」分开衡量,不是笼统地把两件事混在一起报一个数。真正没有解决的是另一个更麻烦的问题——一旦选定了某个 f,不同下游任务对「上限该定多高」的偏好可能完全不同,论文在 256×256 这一档图像上找到的 LDM-4、LDM-8 区间,换到别的分辨率或者别的数据类型上是不是还成立,材料本身没有给出一个可以直接套用的判据,只能重新跑一遍同样的扫描。
6.3 「压缩了六十四倍」和「压缩了四十八倍」,说的是同一件事吗
把 f = 8 这一档的具体数字摆出来,一个容易被混着说的地方马上就会露出来。一张 1024×1024 的图,边长压缩到 1/8,潜空间的空间网格变成 128×128,也就是 16,384 个格子——这是空间上的压缩,1,048,576 个像素格子变成 16,384 个潜空间格子,比例是 1/64。
但潜空间的每个格子不是一个数,是一个小向量。原始像素每个位置有 3 个通道(红绿蓝),LDM 常用的潜空间设定是每个格子 4 个通道。真正要数的是这张潜空间数组总共装了多少个数值:16,384 个格子乘 4 个通道,是 65,536 个数值;而原图是 1,048,576 个像素乘 3 个通道,是 3,145,728 个数值。两边一除,压缩倍数是 48 倍,不是 64 倍。
格子数比:1/64
问的是「空间位置」少了多少——128×128 个格子对 1024×1024 个像素,只看格子这一层,跟通道数完全无关。这个比例只由 f 决定,f=8 就是 1/f² = 1/64。
数值个数比:48 倍
问的是真正要存、要算的数字总共少了多少——把通道数也算进去。通道从 3 变成 4,抵消掉了一部分格子数的收益,64 倍的格子压缩,最后只换来 48 倍的数值压缩。
差别在:一个只数「有多少个位置」,一个数「总共有多少个数」。位置少了 64 倍,但每个位置存的数变多了(3 个变 4 个),两笔账不能划等号。
这两个数字都是真的,都能查到出处,问题只出在把哪一个安在「压缩了多少倍」这句话上。说「潜空间比像素空间小 64 倍」,说的是格子数;说「小 48 倍」,说的是真正要过网络的数值总量。后面几章但凡涉及算力账,用的都是数值个数这一条,因为网络处理的是数值,不是抽象的「位置」。
| 分辨率 | 像素数 | 潜空间格子数 | 格子数比 | 数值个数(像素→潜空间) | 数值压缩倍数 |
|---|---|---|---|---|---|
| 256² | 65,536 | 1,024 | 1/64 | 196,608 → 4,096 | 48× |
| 512² | 262,144 | 4,096 | 1/64 | 786,432 → 16,384 | 48× |
| 1024² | 1,048,576 | 16,384 | 1/64 | 3,145,728 → 65,536 | 48× |
表 1 里格子数比永远是 1/64,数值个数比永远是 48 倍,两者不相等。能不能找一组 f 和潜空间通道数,让这两个比值——格子数比的倒数(也就是压缩倍数)和数值压缩倍数——恰好相等?给出条件,并说明为什么。
变式:把潜空间通道数换成 16(下一节会看到这确实发生了),f=8 时数值压缩倍数会变成多少?
6.4 通道数从 4 涨到 16,又涨到 32,图什么
SD1.5、SDXL 用的潜空间通道数是 4,后来的 SD3、FLUX.1 涨到了 16,FLUX.2 涨到了 32。通道数涨了,数值压缩倍数会跟着往下掉(上一节的公式已经说明这一点)。既然涨通道数在压缩率上是倒退,为什么后面的模型反而愿意涨?
答完再看
SD3 论文把理由写得很直接:他们选择 d = 16(论文原话「we thus choose d = 16」,SD3 §5.2.1)。背后的判断是,在更高分辨率下,4 通道的自编码器成了瓶颈——4 个数装不下那么多空间位置该有的细节,重建质量先于生成质量掉了下来。第一个选项忽略了通道数确实会拖累数值压缩倍数,6.3 节的公式已经算过这笔账。第三个选项低估了这是一个真实的工程判断,不是营销。
问题出在潜空间的「带宽」上。f 固定之后,空间上的格子数也就固定了,但装进每个格子里的信息量是有限的——4 个数能表达的细节,天生比 16 个数少。分辨率越高,同一个格子在原图上对应的区域携带的信息量通常也越丰富,4 通道这个瓶颈在低分辨率上还够用,到了高分辨率就先漏了。涨通道数是在说:宁可数值压缩倍数往下掉一些,也要保证每个格子能装下足够的信息,不让压缩器自己先漏成六三章那个 f=32 的下场。
| 通道数 | 使用的模型 |
|---|---|
| 4 | SD1.5、SDXL |
| 16 | SD3、SD3.5、FLUX.1、Lumina、Qwen-Image |
| 32 | FLUX.2 |
接着 6.3 节的例子:f=8,1024² 分辨率。把潜空间通道数从 4 改成 16,数值压缩倍数(不是格子数比)会变成多少?
变式:FLUX.2 用 32 通道,同样条件下数值压缩倍数是多少?
涨通道数不是没有代价的。SD3 论文自己的对照实验显示:在 16 通道的潜空间上训练,需要更大的模型容量才能达到跟 8 通道相近的表现;深度加到 22 层时,8 通道和 16 通道之间的差距才变得可以忽略(Figure 10 caption)。换句话说,16 通道那份多出来的信息,扩散模型必须有足够的深度才吃得下去——深度不够的模型,学 16 通道反而不如学 8 通道来得准。通道数变多解决了「编码器漏信息」这一头的问题,把难题原样甩给了「扩散模型有没有本事学会这么复杂的潜空间」这一头。
通道数不够:漏在编码这一步
4 个数装不下该有的细节,不管后面的扩散模型多强,重建质量已经先卡住了,跟 6.2 节 f=32 那种「原始材料不够」是同一类麻烦。
通道数涨太快:漏在学习这一步
信息是保住了,但扩散模型的深度如果跟不上,学不动这么复杂的潜空间分布,16 通道在浅层模型上反而不如 8 通道稳。
差别在:一个是压缩器本身没把信息留住,另一个是信息留住了,但生成模型的容量没跟上去学会它。两种失效都会让最终图像变差,但病根长在流水线的不同环节。
Figure 10 的结论是「深度加到 22 层,8 通道和 16 通道的差距才可以忽略」。如果一个团队手上的算力只够训练 10 层深的模型,这时候该选 8 通道还是 16 通道?说明理由。
变式:如果算力足够训练 30 层深的模型,8 通道和 16 通道这时候该怎么选?
对你而言未知压缩率和生成质量之间,有没有一个能提前算出来的最优点
从 f 的六档扫描到通道数的 4/16/32,这一章看到的每一次「压缩多少合适」都是靠训练很多组模型、比较结果才定下来的,不是从某个公式里解出来的。压缩率、模型深度、目标分辨率这三者之间,有没有一种关系能在开始训练之前就估算出一个大致合适的区间,而不必每次都重新扫描?本章的材料里没有给出这样一个判据,只有一组组具体的实验结果。
先做这一步:在本章的潜空间计算器里,把 f、通道数、目标分辨率都当成自变量,把「数值个数比」画成一张随这三个量变化的曲面,看看有没有哪个组合让曲线明显拐弯;再去读 SD3 附录里关于 Figure 10 那组对照实验的训练设置,确认深度和通道数这两个变量是不是被同时改变的,还是分开控制的。
6.5 一张图和一句话,该谁问谁答?
现在潜空间里跑着一份图像数据,要让它按一句文字提示生成内容,图像和文字这两边需要互相「看」对方。如果用注意力机制把两边接起来,你觉得应该是图像去查文字,还是文字去查图像?
答完再看
LDM §3.3 给出的机制里,图像那一侧提供查询,文字那一侧提供被查的内容,输出的形状因此天然和图像一致——这正是扩散模型需要的:预测出来的噪声必须逐位置对应到潜空间的每一个格子上。第一个选项把「谁是生成目标」和「谁发起查询」搞反了:目标决定了要优化什么,不决定注意力从哪一端发起。第三个选项低估了这里的方向不是训练能补回来的,是输出形状本身被查询方定死了。
注意力机制(attention)在这里要解决的问题是:给潜空间里的每一个位置,配上一份从文字提示里挑出来的、跟它相关的信息。第 0 章讲过向量和矩阵,这里要做的事可以先用一个不严谨但管用的比方理解——每个位置带着一个问题去文字那边打听,文字那边给出一份和这个问题匹配程度不同的回答,匹配程度高的回答占的权重就大。
自己推一遍:Q、K、V 该分别接给谁
注意力的输出,形状(有多少行)是由谁决定的——是发起查询的那一方,还是被查的那一方?
想好了再看
是发起查询的那一方。注意力的核心动作是「每一行查询,去和所有的被查项比对,算出一行输出」,有多少行查询,就有多少行输出;被查的那一方不管有多少项,最后都被汇总进同一行里,不会撑大输出的行数。
扩散模型这里要预测的是潜空间里每个位置的噪声,输出必须和谁的形状保持一致?
想好了再看
必须和潜空间的格子逐一对应——有多少个格子,就要输出多少份噪声预测,一个都不能多,一个都不能少。这是上一步结论能派上用场的地方:既然输出的行数由查询方决定,要让输出的形状跟潜空间对得上,查询就必须来自潜空间本身。
那文字提示那一侧呢?它的 token 数量(一句话可能是 20 个词,也可能是 5 个词)会不会影响输出的形状?
想好了再看
不会。文字提供的是被查项——不管一句话有多少个词,注意力都会把它们按匹配程度加权汇总成一份,配到每一行查询上。这正是为什么文字的长度可以随便变,而不需要重新设计模型结构:它从来不出现在输出的行数里。
如果反过来,让文字发起查询、图像被查,会发生什么?
想好了再看
输出的行数会变成文字的 token 数,而不是潜空间的格子数。这样一份输出,没有办法直接拿去当作「每个潜空间位置的噪声预测」,整套去噪循环也就没法进行下去。这就是为什么这个方向不能反过来——不是效果好坏的问题,是形状对不对得上的问题。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| Q | 查询,来自潜空间里的图像特征,一个位置一行 | 每个潜空间格子提出的问题 |
| K | 键,来自文字编码器输出,一个 token 一行 | 文字这边每个词能被匹配到的「标签」 |
| V | 值,同样来自文字编码器输出,和 K 逐行对应 | 匹配上之后真正要拿走的内容 |
| QKT/√d | 每个查询和每个键的匹配程度,除以 √d 是为了防止数值随维度增大而失控 | 问题和标签有多合拍 |
| softmax(·) | 把匹配程度转成一组加起来等于 1 的权重 | 该听哪个词多一点、哪个词少一点 |
cross-attention:查询来自一种模态、键和值来自另一种模态的注意力机制。潜空间的每个位置发起查询,文字提示提供键和值,输出逐位置对应潜空间,形状和图像一致,文字有多长都不影响这个形状。
cross-attention:图查文字
Q 只来自图像,K、V 只来自文字,输出从一开始就天然和潜空间的格子数一致,文字提示多长都不改变这个形状。
把图像和文字拼成一条序列,做自注意力
Q、K、V 三者都同时来自「图像 token + 文字 token」拼起来的这条混合序列,输出的形状是两者长度之和,还需要再把属于图像的那部分单独取出来,而且文字变长,整条序列的计算量也跟着涨。
差别在:cross-attention 从一开始就把「谁决定形状」锁死成图像;拼接式的自注意力让图像和文字在同一条序列里互相看,换来的是双方都能影响对方,代价是形状和计算量都跟文字长度绑在了一起。第 8 章会看到 SD3 选的正是后一条路。
如果要用同一套 cross-attention 机制,让文字生成也反过来「看」图像里已经生成的内容(比如做图生文字的任务),这时候 Q、K、V 该怎么摆?
变式:如果要做的任务是让图像和文字互相看对方、两边都要更新,一套单向的 cross-attention 够不够用?
6.6 SDXL 把这条路线拉到多满?
SDXL 的 UNet 参数量是 2.6B,官方说法是比早期 Stable Diffusion 的 860M 大了 3 倍左右。你觉得这多出来的参数,是均匀地摊在原来每一层上,还是集中砸在了某几层?
答完再看
SDXL 的改动是刻意不均匀的:论文原话是砍掉了最高分辨率那一级的 transformer block(效率原因),在两个较低层级分别放 2 个和 10 个 block,并且把 UNet 里 8 倍下采样那一级整个拿掉了(SDXL §2.1)。多出来的参数几乎全砸在了中低分辨率那几级,而不是平均分给每一层。
SDXL 沿着这一整章的路线继续往前走,同时在好几个旋钮上一起使力。第一个旋钮是 UNet 本身的结构:早期 Stable Diffusion 在每个分辨率级别上都塞一个 transformer block(结构是 [1,1,1,1]),SDXL 把这套安排换成了一个不均匀的分布——最高分辨率那一级直接不放 transformer block,中间两级分别放 2 个和 10 个,同时把原来最低的那一级(8 倍下采样)整个去掉(SDXL §2.1)。这样调整之后 UNet 参数量涨到 2.6B,但这不是简单的整体放大三倍,是把计算力从「处理精细空间结构」的层挪到了「处理语义」的层。
| 项 | SDXL | SD 1.4/1.5 |
|---|---|---|
| UNet 参数量 | 2.6B | 860M |
| Transformer block 分布 | [0, 2, 10] | [1, 1, 1, 1] |
| 文本编码器 | CLIP ViT-L + OpenCLIP ViT-bigG(共 817M) | CLIP ViT-L |
6.4 节说过,16 通道潜空间要配上足够的模型深度才划算,深度不够反而不如 8 通道。SDXL 的通道数其实还是 4,没有变,但 UNet 参数从 860M 涨到了 2.6B,而且涨的方式是把 transformer block 集中挪到中低分辨率层,不是均匀摊开。如果 SDXL 当初选了和 SD3 一样的 16 通道潜空间,又把这 2.6B 参数按早期版本 [1,1,1,1] 那种均匀分布去加,你预期会撞上本章讲过的哪一种失效,为什么?
变式:反过来,如果保持 SDXL 现在这套 [0,2,10] 的不均匀分布,把通道数从 4 改成 16,你觉得会不会比均匀分布 + 16 通道的组合更好一些?
第二个旋钮是文本编码器:SDXL 同时用了两个文本编码器(CLIP ViT-L 和 OpenCLIP ViT-bigG),把两者的输出在通道维度拼起来接进 6.5 节那套 cross-attention,另外还把 OpenCLIP 的池化文本向量额外接进网络(SDXL §2.1)。这部分文本编码器加起来是 817M 参数,本身不算进 UNet 的 2.6B 里,但一起决定了整个模型的部署体积。
第三个旋钮是两条跟分辨率、构图相关的微条件——之所以叫「微」,是因为它们不改变模型结构,只是往时间步嵌入上加了两小段额外信息。第一段是图像原始尺寸:LDM 这类两阶段模型训练时通常需要丢掉过小的图,SDXL 论文算过一笔账,如果直接按预训练分辨率过滤,会扔掉 39% 的数据(SDXL §2.2);与其扔图,不如把每张图缩放前的真实高宽也编码成一份条件喂给网络,让模型自己学会「在什么原始尺寸下,图该长什么样」,推理时再把这个条件设成目标分辨率。
SDXL 论文的实验(Table 2)显示:只用满足最小分辨率要求的 70k 张图训练,FID 反而比用全部数据加尺寸条件训练更差。这说明尺寸条件解决的核心问题是什么?
变式:如果训练时把裁剪坐标固定成 (0, 0)(也就是从不做随机裁剪),还需要裁剪条件吗?
第二段是裁剪坐标:训练时批量处理图片常常需要随机裁剪,裁剪的位置如果不告诉模型,模型会把「裁剪导致的构图残缺」错当成一种正常的生成结果,SDXL 把裁剪的起始坐标也编码成条件喂进去,推理时把这个条件设成 (0, 0),就能拿到构图完整、主体居中的图。
不告诉模型裁剪坐标
训练数据里随机裁剪产生的残缺构图(比如猫的头被切掉一部分)被模型当成一种正常范式学了进去,生成时偶尔也会给出主体被截断的图。
把裁剪坐标当条件喂进去
模型学会了「这张图是在哪个位置被裁的」和「裁完之后长什么样」之间的关系,推理时把条件设成不裁剪,构图残缺的现象就不再是模型的默认行为。
差别在:残缺构图本身不是模型能力不够,是训练数据里的一个规律没有被显式告诉模型,模型就把它当成了目标分布的一部分。
把这三个旋钮放在一起看,能看出这条路线走到了什么位置。SDXL 的每一处改动都是设计者盯着一个具体的失效现象——训练进度慢、小图被扔掉、裁剪构图残缺——手工调出来的答案,调法一个比一个精细:先是给不同分辨率级别分别配不同数量的 transformer block,接着让两个文本编码器分工,再往时间步嵌入里塞进两种和图像几何相关的条件。每一步都有效,但每一步也都需要有人先看出这个具体的失效现象,再设计一种专门的机制去堵它。
答辩:如果我是审稿人
SDXL 的用户偏好研究里,加了精修阶段的完整两阶段流水线胜率是 48.44%,只用 SDXL 基础模型是 36.93%。这两个数字差了 11 个百分点,说明精修阶段本身贡献了相当一部分的质量提升。那「3 倍大的 UNet 带来质量飞跃」这个说法,是不是把精修阶段的功劳也算了进去?
参考防守(先自己组织语言再看)
这个混淆是真实存在的。论文的用户研究确实把两阶段流水线和单独的 SDXL 基础模型分开报告了胜率,36.93% 这个数字才是对应本章讲的这套架构改动(更大的 UNet、双文本编码器、微条件)单独的贡献,48.44% 里额外叠加了一个专门的精修模型对潜码做二次去噪。把这两个数字分开看之后,「3 倍大的 UNet 带来质量提升」这句话依然成立——36.93% 本身已经明显超过了旧版 Stable Diffusion 的对照胜率——只是它不能用来解释精修阶段带来的那 11 个百分点,这部分功劳要单独记在一个不属于本章讨论范围的独立模型上。真正没有回答的问题是,如果把同样多的参数和算力直接加到基础 UNet 里、而不是拆成两阶段,胜率会不会追上 48.44%,论文没有做这组对照。
这就是这条路线目前撞上的墙:手工设计已经能榨出实实在在的收益,但每一次收益都要靠人盯着具体现象去调,调的地方越来越细——从压缩倍数,到通道数,到 transformer block 怎么分布,到往时间步嵌入里塞几段跟分辨率、裁剪相关的编码。继续往前走,边际收益正在变薄,而设计的复杂度还在涨。下一章要处理的问题就是:如果把这整套手工设计的 UNet 换掉,换成一种规模化本身就能决定该怎么长的结构,会发生什么。
读的时候要小心
关于本章数字的来路
本章的格子数比、数值个数比、通道压缩倍数,都是按定义直接算出来的,实验室里能自己验。LDM 各档 f 的 FID 对比、SD3 16 通道对 8 通道的深度对照(Figure 10)、SDXL 的用户偏好胜率,都是论文作者自己训练、自己评测出来的结果,本站没有重新训练复现。
这一层要加什么:编码器与解码器
为什么现在才加它:前五级那条采样循环一直直接对着数据本身工作。这一层要在数据和扩散模型之间插进一对可训练的翻译器——编码器把数据压小,解码器把压小的表示翻译回去——让第 7 级开始,扩散能在这个小得多的空间里运行。
难点:瓶颈维度不是越小越好,也不是越大越好——这正是本章从头到尾在讲的那件事的最小版本。瓶颈定得太大,编码器几乎不用压缩就能重建,等于没有起到压缩的作用;定得太小,原始数据里的结构会被挤没,解码器不管训多久都还原不回来,和 6.2 节 f=32 那一档是同一种失效。另一个容易漏掉的地方是,编码器和解码器必须绑在一起训练——只训练编码器、或者只训练解码器,两边的表示对不上号,重建误差不会往下降。
自己验:拿一份二维双峰分布(就是第 1 章那个),把它当成两维向量喂给这对编码器、解码器,瓶颈维度从 2 逐步降到 1 再降到没有(0),重建误差应该单调上升,而不是先降后升或者一直不变;瓶颈压到 1 维时,原本两个分开的峰应该在重建结果里塌成一团几乎分不开的点——这正是信息在瓶颈处丢失的直接证据,丢失的量能从重建误差里读出来,不需要另外定义一套新的判据。
留给你的问题
SDXL 把这一章能想到的手工旋钮几乎都拧了一遍:压缩倍数、通道数、transformer block 怎么在各层分布、文本编码器配几个、往时间步嵌入里塞几段跟几何相关的条件。每一个旋钮都要有人先盯着一个具体的失效现象,才能设计出对应的机制去堵它。如果不再靠人一个一个去看现象、去设计机制,而是让一种更规整、更容易随规模放大的结构自己决定每一层该长什么样,这样的结构要满足哪些条件,才能不依赖 UNet 这套下采样、上采样、跳跃连接拼出来的手工设计?
不看材料,写下这一章的三堵墙
第一堵:像素空间扩散不分场合地对每个位置一视同仁地花算力,而图像里大部分信息是人眼看不出来的高频细节,这部分算力本可以省下来。第二堵:「压缩了多少倍」这句话背后有两个不同的数——格子数比和数值个数比,通道数一变两者就分道扬镳,混着用会算错账。第三堵:以为模型变大就是均匀放大,SDXL 的参数增长集中在特定几层,配合两个文本编码器和两段微条件,是一整套针对具体失效现象手工调出来的设计,不是简单的等比例放大。
第7章 扔掉 UNet
开篇故事第 7 集 · 百分之三点六,全组都算反的那道账这模型也太真实了
《潜空》刚弹出来的那行淡蓝色字还停在屏幕正中央,沈砚的手指悬在触控板上一动不动——他半小时前押的是完全相反的方向,现在数字明明白白摆在那里,机柜风扇的声音把整间屋子填得满满的,谁都没先开口。
图像这条线扔掉 UNet、换成纯注意力网络,到今天已经是第四十一天。半小时前,显存刺客提议把下一次训练的切块方式,从粗的一档换成更细的一档,说这样能多留住一些画面细节。沈砚立刻反对,他前几天在论坛上翻到一句被反复引用的话——注意力那一步是平方复杂度,token 一多,这部分开销早晚会成为主要成本,切得越细、token 越多,等于主动往这个坑里跳。他跟显存刺客打了个赌:真按这个换法,这周剩下的算力大头会被注意力吃掉;算错的人,下周排 GPU 使用顺序的活儿就归他。
切块切细一半,注意力是不是真的要吃掉算力的大头
1L复现之鬼这话我也耳熟,但耳熟不代表对,你们谁把三类计算量拆开真算过,没算过的都不作数。
2L沈砚道理我都懂,可我还是觉得注意力那步是平方项,切细一半等于结构上往上翻,这个方向不用算也站得住。
3L显存刺客楼上这话我原来也信,可你要让我现在就把百分比报出来,我是报不出的,光凭方向对不对不算数。
4L炼丹废人那还等什么,直接把《潜空》的乘加账单拉出来,谁也别再靠印象吵了。
靳工没接这个话,直接把当前的训练配置敲进《潜空》的计算面板:二百五十六个 token,二十八层网络,隐藏维度用的是最大那一档。面板转了两秒,把三类计算各自的份额吐了出来。
系统提示当前配置总计算量 118.64 Gflops,注意力矩阵这一步只占 3.6%,其余全部来自逐 token 的线性投影与前馈网络,本次校验通过。
沈砚盯着那个百分之三点六,半天没说出话——他以为二次项会先吃掉大头,算出来的结果却正好反过来,占比只有 3.6%,真正把算力吃掉一大半的,是随 token 数直接线性往上涨的那两项。
“二次项确实存在,只是这个规模上它还是零头。”靳工把杯子放下,“你们从十六个 token 加到二百五十六个,算力涨了十六倍,这十六倍几乎全来自跟 token 数一起线性往上走的那两项,参数量压根没跟着变,一直卡在六百七十五兆上下。”
沈砚认了这局,下周排 GPU 使用顺序的活儿归他;显存刺客在论坛里又补了一刀,说这场账算得比训练日志还精彩,看得他都想截图收藏。
沈砚刚要伸手去点论坛右上角新跳出来的提醒,靳工已经把训练日志的窗口切到了最前面——那条提醒他还没来得及点开,标题却先一步被看清了:一步就能生成,是真的,还是以讹传讹。
系统结算 · 本章考点
- DiT 把 UNet 换成标准 transformer,图先切块再拉直成 token:256×256 切出 256 个,512×512 切出 1,024 个,1024×1024 切出 4,096 个,一路到 1536×1536 的 9,216 个、2048×2048 的 16,384 个
- patch 边长砍掉一半,token 数直接涨到 4 倍以上,DiT-XL 从 16 个 token 的 7.39 Gflops,涨到 256 个 token 的 118.64 Gflops,相差 16 倍,参数量却几乎不变,一直卡在 675M 上下
- 真正算出来的账:256 个 token 时,注意力矩阵那一步只占总计算量的 3.6%;token 更少的档位,这个比例低到 0.2%,都不是决定总开销的那一项
- 条件注入挑的是 adaLN-Zero:把额外那组缩放初始化成零,训练起步时整个 block 等价于什么都没做,比精细的 cross-attention 更省又更稳
- 潜空间格子边长的验证:1536 除以(压缩倍数 8 乘 patch 边长 2)等于 96,96 的平方正好是 9,216,和实测的 token 数对得上
上一章那堵墙是这样的:SDXL 每往前挪一步,都要先有人盯着一个具体的失效现象——训练慢、小图被扔、构图被裁掉一块——再手工设计一种机制去堵它。这个流程本身就在提示一件事:UNet 那一整套下采样、上采样、跳跃连接,从一开始就是为图像量身定做的结构,量身定做的代价是每次改动都得重新量一遍尺寸。DiT 这篇论文问了一个更直接的问题:如果把这套量身定做的结构整个扔掉,换成一种几乎不针对图像做任何特殊设计的标准结构,会发生什么?
学完这一章你应该能做到
- 说清 UNet 的下采样、上采样这套结构对扩散模型的效果是不是必需的
- 算出给定图像分辨率、压缩倍数、patch 大小时的 token 数,并说出 patch 减半为什么会让计算量涨到四倍以上
- 说出 adaLN-Zero、cross-attention、in-context 三种条件注入方式各自的代价和效果,解释零初始化这个技巧在起什么作用
- 按 token 数、隐藏维度写出注意力层里三类计算各自的开销,算出二次项什么时候才会追上线性项
这一层要把第 6 级那个手工设计的去噪器,换成一套几乎不含图像专属结构的注意力网络。前六级的去噪器不管长什么样,骨架里都带着卷积、下采样、上采样这些专门为图像设计的部件,还没有人验证过这些部件是不是真的不可替代。
7.1 UNet 那套结构,是不是真的少不了?
把 UNet 换成一个几乎没有针对图像做特殊设计的标准 transformer——没有下采样、没有上采样、没有跳跃连接,清一色的自注意力层加 MLP。你觉得生成质量会怎样?
答完再看
DiT 论文的结论写得很直接:U-Net 的归纳偏置对扩散模型的表现并不是必需的,完全可以换成标准的 transformer 设计(DiT §1)。第一个选项把「这套结构方便」当成了「这套结构必需」。第三个选项的担心是合理的直觉,但 DiT 的实验恰恰是在相对复杂的 ImageNet 类别条件生成任务上做的,不是什么简化过的玩具数据集。
UNet 里那套下采样、上采样、跳跃连接,不是随手加上去的,它编码了一条关于图像的假设:图像的结构是分层级的,局部的纹理、中等尺度的物体轮廓、整体的构图,各自适合在不同的空间分辨率上处理,所以网络也应该分层级地压缩、再分层级地放大,把不同层级的信息通过跳跃连接直接传过去。这条假设写进架构本身,就是归纳偏置(inductive bias)——网络在还没看到一个训练样本之前,就已经被这套结构逼着去用某种特定的方式处理数据。归纳偏置不是坏事:假设选对了,模型能用更少的数据学得更快;假设选得不合适,或者假设本身限制了规模化的空间,它就会变成一副越长越紧的枷锁。
UNet:把「图像有多尺度结构」这条假设写进架构
下采样、上采样、跳跃连接的具体安排,都要靠人设计。想改变模型的容量分配(就像第 6 章 SDXL 那样),只能去调这些结构本身——层数、每层的通道数、block 怎么分布,每一次调整都要重新设计。
标准 transformer:把图像先拆成一串 token,剩下的交给数据
网络看到的只是一串向量,不知道它们原来在空间上是怎么排列的。多尺度结构如果对生成有用,得靠训练数据和模型规模让网络自己学出来,不是架构提前替它假定好的。
差别在:一个把「图像该怎么处理」的经验预先焊进结构里,另一个把这条经验完全交给规模化去决定要不要学。第 6 章看到的正是前一条路线:每一次改进都要人工重新设计结构。
第 6 章 SDXL 里,「把 8 倍下采样那一级从 UNet 里整个去掉」算不算一次归纳偏置上的改动?为什么?
变式:如果只是把某一层的通道数从 320 调到 640,这算不算归纳偏置上的改动?
答辩:如果我是审稿人
DiT 的实验全部做在 ImageNet 类别条件生成上,条件只是一个类别标签。UNet 真正的强项会不会恰恰是在需要精细空间对应的任务上,比如超分辨率、图像编辑这类逐像素都要对应的任务?DiT 有没有资格声称「归纳偏置不重要」这么大的一句话?
参考防守(先自己组织语言再看)
这个限制是真实的。DiT 论文验证的场景是类别条件的图像生成,输出和输入之间不存在逐像素对应关系,跳跃连接原本用来传递精细空间细节的那部分作用,在这个任务里没有被充分考验到。论文的说法确切地讲是「U-Net 的归纳偏置对扩散模型的表现不是必需的」,这句话的证据范围就是它做实验的那类任务——生成质量随 Gflops 提升,而不是「所有依赖精细空间对应的任务都不需要 UNet」。真正没有回答的问题是,把 DiT 用到超分辨率、图像编辑这类需要逐像素精细对应的任务上,跳跃连接被舍弃之后损失的那部分信息,要靠更深的网络、更多的 token,还是靠别的机制补回来,这篇论文没有给出答案。
7.2 图要先切成方块,切小一半代价涨多少?
标准 transformer 处理的是一串 token,不是一张二维网格,所以第一件事是把潜空间的网格切成小方块,每块变成一个 token。如果把方块的边长砍掉一半(切得更细),你觉得计算量会涨多少倍?
答完再看
DiT 论文说得很明确:patch 大小减半会让 token 数变成四倍,因此至少让 transformer 的总 Gflops 也变成四倍(DiT Figure 4 caption)。「至少」两个字很关键——这只是从 token 数直接带来的涨幅,注意力层里还有一项计算量随 token 数的平方增长,第 7.4 节会把这部分账算清楚。
把潜空间的网格切成小方块,每一块拉平、投影成一个固定长度的向量,这就是 patchify。给定 patch 边长 p,潜空间网格边长 I/f(I 是第 6 章那个原始图像边长,f 是 VAE 的压缩倍数),切出来的 token 数是:
| 符号 | 是什么 | 直觉 |
|---|---|---|
| I | 原始图像边长(比如 1024) | 你设置的生成分辨率 |
| f | VAE 把边长压缩到原来的 1/f(第 6 章) | 第一层压缩,人眼看不出的细节先被删掉 |
| p | patch 边长,潜空间网格再按这个边长切块 | 第二层压缩,把网格切成 token |
| T | 最终喂给 transformer 的 token 总数 | 模型这次要同时看多少个「格子」 |
| 分辨率 | 256² | 512² | 1024² | 1536² | 2048² |
|---|---|---|---|---|---|
| token 数 T | 256 | 1,024 | 4,096 | 9,216 | 16,384 |
表 1 里 1536² 这一档给出的 token 数是 9,216。自己代入公式 T=(I/(f·p))²(I=1536,f=8,p=2),能不能算出同一个数?
变式:用同一个公式验证表 1 里 2048² 这一档的 16,384 是怎么算出来的。
patchify:把潜空间网格切成 p×p 的小方块,每一块拉平后线性投影成一个固定维度的向量,作为 transformer 的一个输入 token。切完之后再按标准 ViT 的做法加上位置编码(DiT 用的是正弦-余弦版本),因为 token 一旦被拉直成一条序列,原本的二维排列关系就丢了,需要额外告诉网络每个 token 原来在哪个位置。
DiT 论文特别指出一件容易被忽略的事:改变 p 对模型的参数量几乎没有影响,因为投影层的权重形状只取决于每块的像素数和目标维度,跟切出多少块无关;p 影响的只是 token 数,进而影响计算量(DiT §3.2)。这意味着同一套权重规模,通过调 p 就能换出好几种计算量档位,DiT 的实验里 XL 配置分出 /8、/4、/2 三档,参数量几乎相同(676M、675M、675M),Gflops 却分别是 7.39、29.05、118.64——差了 16 倍。
p 大:token 少,算得快
DiT-XL/8 在 256×256 上只切出 16 个 token,7.39 Gflops,训练和推理都便宜,但每个 token 揉进了更大一块空间,细节的精细程度受限。
p 小:token 多,算得细
DiT-XL/2 切出 256 个 token,118.64 Gflops,是前者的 16 倍,换来的是每个 token 对应的空间范围更小,模型能处理的空间细节更精。
差别在:patch 大小是一个纯粹关于「算力换细节」的旋钮,跟模型参数量几乎脱钩——这和第 6 章通道数那个旋钮不一样,通道数变化会直接改变要不要重新配模型深度。
用一个 f=8 的 VAE,在 512×512 分辨率上,分别取 p=1 和 p=2,token 数各是多少?结合第 6 章「压缩倍数」的算法,p=1 这一档相当于在潜空间上完全不做第二层压缩,此时数值个数比(不是格子数比)是多少?
变式:如果换用第 6 章提到的 f=4(比 8 更温和的压缩),同样 512×512、p=2,token 数会变成多少?
7.3 时间步和类别这两个条件,喂给谁听?
DiT 需要把噪声时刻 t 和类别标签这两个条件接进网络。除了第 6 章学过的 cross-attention,DiT 论文还试了另外两种:把 t、c 当成两个额外 token 直接拼进序列(in-context),以及让 t、c 去决定每一层归一化的缩放和偏移(adaptive layer norm)。三种里,你觉得哪一种在生成质量和计算开销上会双双胜出?
答完再看
DiT §5 的结果是:adaLN-Zero 在训练的各个阶段都比 cross-attention 和 in-context 取得更低的 FID,同时是三者里计算效率最高的(DiT Figure 5 caption)。cross-attention 的直觉不能算错——它确实更精细——但精细不等于更好训、更好收敛,第 7.3 节剩下的部分会讲清楚这中间差在哪。
三种机制注入条件的方式完全不同。in-context 条件:把 t、c 的向量当成两个额外的 token,直接拼进图像 token 的序列里,跟处理 cls token 一样,不需要改动 transformer block 本身,几乎不增加新的计算量(DiT §3.2)。cross-attention 条件:把 t、c 单独编码成一个长度为 2 的序列,在每个 transformer block 里插入一层专门的 cross-attention(第 6 章那套机制的原样搬用),这是三者里开销最大的,大约给模型增加 15% 的计算量(DiT §3.2)。
如果一个模型的条件只有一个时间步 t,没有类别、没有文字,你觉得该用 in-context 还是 cross-attention?如果条件换成一段几十个词的文字描述呢?两种情况下的选择理由分别是什么?
变式:如果条件是一段文字,但只用来控制整体风格(比如「水彩画风格」),不涉及空间位置,in-context 是不是又重新变得够用?
第三种最值得细讲。自适应归一化(adaptive layer norm, adaLN)要解决的是另一件事:每一层在做归一化的时候,都会把数据缩放、平移到某个统一的范围,这中间有一组缩放系数和平移量。普通的归一化层里,这组系数是训练时学出来的固定参数,每次前向都一样;adaLN 的做法是不直接学这组系数,而是让网络看着 t 和 c 现场算出这组系数——同一层网络,喂进不同的条件,缩放和平移就不一样,整层的行为跟着条件一起变。这组系数只有两个:一个缩放、一个偏移,不管这一层有多少个 token,全部 token 共用同一组,这是它相对 cross-attention「粗糙」的地方。
adaLN 由条件算出来的那组缩放和偏移,是这一层里每个 token 各自算一份,还是全部 token 共用同一份?
变式:cross-attention 算出来的「每个图像 token 该关注文字的哪部分」,是共用一份,还是每个 token 各自不同?
adaLN-Zero在这个基础上多做一步:额外算出第三组系数——一个逐通道的缩放,乘在这一层输出、送入残差连接之前,并且把这组系数的初始化方式定成输出恒为零(DiT §3.2)。零初始化意味着训练刚开始的时候,这一整个 transformer block 的输出被这组系数乘成零,残差连接直接把输入原样传出去,整个 block 在训练第一步的行为等价于什么都没做。DiT 论文指出这个技巧借鉴自 ResNet 一系的做法——把每个残差块在初始化时设成恒等函数,能明显加速大规模训练(DiT §3.2)。
adaLN-Zero:起步等于什么都没做
训练刚开始,每个 block 的额外贡献被强制置零,网络等价于一串恒等映射,梯度不会一上来就被条件信息搅乱,后面再逐步学会怎么利用条件。
cross-attention:起步就带着随机噪声
额外的 cross-attention 层从第一步起就用随机初始化的权重去关注条件,给主干网络叠加了一层还没学会怎么用的随机扰动,同时这层本身也是最贵的一块计算。
差别在:adaLN-Zero 用一个几乎不花代价的初始化技巧,换来了「训练起步更稳」这个好处;cross-attention 更精细,但精细的代价既体现在算力上,也体现在训练初期的稳定性上。
| 方式 | in-context | cross-attention | adaLN | adaLN-Zero |
|---|---|---|---|---|
| Gflops | 119.4 | 137.6 | 118.6 | 118.6 |
adaLN-Zero 的条件注入是「全体 token 共用同一组缩放和偏移」。DiT 的条件只是一个类别标签,信息量很小。如果条件换成一整句话的文字描述——每个词对不同的空间位置重要性可能完全不同——这种「全体共用一组系数」的注入方式还够用吗?构造一个具体场景说明可能的问题。
变式:如果把 adaLN 的一组全局系数,改成按图像的四个象限分别算四组系数,能不能部分缓解这个问题?
答辩:如果我是审稿人
DiT 比较三种条件注入方式,用的条件全部是稀疏、低维的信息(一个时间步、一个类别标签)。adaLN-Zero 赢的这个结论,边界在哪里——它会不会只是在「条件信息量很小」这个特定场景下才成立,一旦条件变成一段需要逐 token 精细对应的文字,adaLN-Zero 的优势会不会反转?
参考防守(先自己组织语言再看)
这个边界确实存在,而且后来的模型用实际选择回答了这个问题。DiT 论文的比较严格地限定在类别条件生成这个场景,条件是一个离散标签,本身就不携带需要逐位置对应的信息,adaLN-Zero「全体 token 共用一组系数」的粗糙性在这里几乎不是代价。第 8 章会看到,SD3 这类文字到图像模型确实没有单纯沿用 adaLN-Zero 处理文字条件——文字序列走的是一套逐 token 的联合注意力机制,只有对文字整体做池化之后得到的那个粗粒度向量,才继续沿用类似 adaLN 的调制方式。换句话说,adaLN-Zero 赢的那部分结论保留了下来,但只保留在它原本被验证过的场景——粗粒度、低维的条件——细粒度的条件信息,后来的模型另外配了机制去处理,不是指望 adaLN-Zero 一个人扛下来。
7.4 注意力的平方项,到底什么时候才追上线性项?
注意力机制里有一步要计算每两个 token 之间的匹配程度,这一步的计算量随 token 数的平方增长,其余部分(把每个 token 投影、过 MLP)大致随 token 数线性增长。在 DiT-XL/2 这个配置、256×256 分辨率、256 个 token 的条件下,你觉得这个平方项占总计算量的比例大概是多少?
答完再看
按下面这套计数方式算出来,DiT-XL/2 在 256 个 token 时,注意力矩阵那一项只占总计算量的 3.6%。「平方增长最终会主导」这句话本身没错,但「最终」在哪里到达,取决于 token 数要涨到多大,本节剩下的部分会把这个交叉点算出来。
先把账目摊开,按乘加次数(MAC,一次乘法加一次加法算一次)来数,而不是笼统地说「注意力是平方复杂度」。一个 transformer block 里主要有三类矩阵乘法:
自己推一遍:从 token 数到计算量
一层里,Q、K、V 三个投影加上最后把注意力结果投影回去的输出层,一共四次投影。每次投影把一个 d 维向量映射成另一个 d 维向量,T 个 token 一起算,这部分的计算量怎么数?
想好了再看
单个 token 的一次投影是一个 d×d 矩阵乘一个长度 d 的向量,需要 d² 次乘加。T 个 token 就是 Td²;四次投影(Q、K、V、输出)一共是 4Td²。这一项随 T 线性增长。
注意力矩阵那一步:每个 token 的 Q 要和每个 token 的 K 做一次点积,点积长度是 d。这一步要做多少次点积,一共多少次乘加?
想好了再看
T 个 token 两两配对,一共 T² 次点积,每次点积 d 次乘加,这一步是 T²d。算出权重之后还要用它们对 V 做一次加权求和,形状上是同样的 T²d,两步加起来是 2T²d。这一项随 T 的平方增长,是整章要盯的那个二次项。
MLP 那一步:每个 token 先放大 r 倍(DiT 用 r=4),再缩回 d 维,两次矩阵乘法,这部分呢?
想好了再看
放大这一步是 d×rd 的矩阵乘一个 d 维向量,rd² 次乘加;缩回去是反过来的形状,同样 rd²。T 个 token 一起,是 2rTd²。这一项也随 T 线性增长,而且系数 2r 通常比投影那一项的系数 4 大得多(r=4 时是 8)。
把 N=28(DiT-XL 的层数)、d=1152、r=4、T=256(DiT-XL/2)代进去,三项各是多少,加起来乘上层数,和论文公布的 118.64 对不对得上?
想好了再看
投影项 4Td² ≈ 1.36G;注意力项 2T²d ≈ 0.15G;MLP 项 2rTd² ≈ 2.72G。三项加起来约 4.23G,乘上 28 层是 118.4G,和论文报告的 118.64 Gflops 几乎重合,误差在 0.2% 以内。这里有一句要说在明处:论文表里写的单位是「Gflops」,但只有把它当成 GMAC(乘加次数,一次记一次而不是记两次浮点运算)来算,本站复现的数字才能和论文对上——这是本站从数字倒推出来的判断,论文正文没有明确说这个「Gflops」是不是等于两倍的 MAC 数。
| 模型 | token 数 T | 本站算出 | 论文报告 | 误差 | 注意力矩阵占比 |
|---|---|---|---|---|---|
| DiT-XL/8 | 16 | 7.15 | 7.39 | 3.2% | 0.2% |
| DiT-XL/4 | 64 | 28.80 | 29.05 | 0.9% | 0.9% |
| DiT-XL/2 | 256 | 118.38 | 118.64 | 0.2% | 3.6% |
这张表带出一个不太直观的结果:DiT-XL/8、/4、/2 三档参数量几乎完全一样(676M、675M、675M——patchify 不改变投影层的权重形状,7.2 节已经算过这笔账),Gflops 却相差 16 倍,但如果把 Gflops 除以 token 数,每个 token 平均花掉的计算量几乎是同一个常数(约 0.46G)。这说明在 DiT 这个规模上,总计算量几乎完全由「线性项」主导,token 数翻倍,计算量跟着几乎翻倍,二次项那部分涨幅被线性项的体量盖住了,一直到某个 token 数之后,这个关系才会反过来。
用同一套公式,算一下 DiT-B(N=12,d=768,r=4)在 T=64(对应 /4 这一档)时的 Gflops,并说出注意力矩阵占的比例。
变式:如果 T 不变,把 r 从 4 改成 8(MLP 放大倍数变大),注意力矩阵的占比会怎么变?
现在解交叉点。二次项 2T²d 什么时候追上两个线性项的和 (4+2r)Td²?两边同时除以 Td,解出 T = (2+r)d,r=4 时就是 T=6d。
| 模型规模 | d | 交叉点 token 数 | 换算分辨率(f=8, p=2) |
|---|---|---|---|
| DiT-XL | 1,152 | 6,912 | ≈1328×1328 |
| FLUX.1 / Qwen-Image 级别 | 3,072 | 18,432 | ≈2160×2160 |
T < 6d:线性项说了算
DiT-XL 在 256~1024 这个 token 数区间,投影和 MLP 两项加起来稳稳压过注意力矩阵,表 3 里 3.6% 那个占比就是这个区间的典型样子——二次项存在,但不是决定总计算量的那一项。
T > 6d:二次项开始说了算
FLUX 规模的模型推到 2048² 分辨率、序列长度上万的时候,注意力矩阵已经涨到接近总计算量的一半,继续往更高分辨率走,它会变成真正决定总开销的那一项。
差别在:同一套公式,在两个不同的 token 数区间里,「谁是主要开销」这件事会整个反过来——公式没变,变的只是 T 和 d 谁更大。
在 FLUX 这个规模的模型上(d=3072),把序列长度换算成实际会遇到的分辨率:1024×1024 时,算上文本 token,总序列长度是 4,608,这时候注意力矩阵只占总计算量的 20.0%;到了 2048×2048,序列长度涨到 16,896,占比才升到 47.8%——距离真正反超线性项,还差着一截。这和很多人凭直觉给出的说法正好相反:注意力的平方复杂度会在 1K 分辨率就成为扩散模型的主要成本,这句话在今天常见的生成分辨率上并不成立,真正的交叉点要到 2K 附近才出现。
交叉点公式 T=(2+r)d 说明模型的隐藏维度 d 越大,需要越高的分辨率二次项才会追上线性项。结合第 6 章「通道数从 4 涨到 16、32」那条趋势——更大的模型、更高的分辨率是当前这条路线的大方向——这个交叉点公式对「继续把模型和分辨率往上堆」这件事意味着什么?
变式:如果保持 d 不变,只是把 patch 从 p=2 换成 p=1(token 数变成四倍),交叉点对应的分辨率会怎么变?
这张账单还有另一面——显存。注意力矩阵本身是一个 T×T 的表,要不要把它完整地摆在显存里,不只是计算量的问题,也是空间的问题。1024×1024 分辨率下,图像 token 数 T=4,096,一个 T×T 的分数矩阵用 bf16(每个数 2 字节)存起来是 33.6MB;一个 FLUX 级别的模型有 24 个注意力头,24 个头一起是 0.81GB——这只是一层、一张图的数字,训练时一个批次里有很多张图、很多层,这部分显存会迅速滚雪球。这正是 FlashAttention 这类实现选择不把整个分数矩阵摆进显存、而是分块现算现丢的理由:省下来的不是计算量本身,是这张 33.6MB 表格在显存里占的位置。
对你而言未知交叉点之后,有没有比稀疏注意力更好的答案
交叉点公式说明二次项在多高分辨率反超,没说反超之后怎么办。本章唯一的线索是第 12 章的稀疏注意力——让每个 token 只关注一部分而不是全部,压低二次项系数,代价是牺牲「任意两个 token 直接对话」的能力,这笔代价何时划算,材料没给判据。
先做这一步:在注意力开销实验室里,把 d 固定在 FLUX 规模,分辨率从 1024 推到 4096,画出占比曲线,找到过半的点;再翻到第 12 章,看 Sliding Tile Attention 是在多大 token 数下介入的,比较两个点是否吻合。
读的时候要小心
关于本章数字的来路
本章的 token 数公式、MAC 计数、交叉点分辨率,都是按定义推出来的,能自己验证。adaLN-Zero 对比 cross-attention、in-context 的 FID 曲线(Figure 5)是论文作者自己训练出的结果,没有第三方复现;「论文的 Gflops 等同于 GMAC」是本站倒推出的判断,不是论文原话。
这一层要加什么:把去噪器换成注意力
为什么现在才加它:前六级的去噪器不管具体怎么实现,骨架里都带着卷积和上下采样,输入输出天然保留着二维排列关系。这一层要把整个去噪器换成一串 token 上的自注意力加 MLP,顺带处理一件新麻烦——token 一旦被拉直成一条序列,模型要怎么知道每个 token原来在哪。
难点:自注意力这一步算的是每个 token 和其余所有 token 的两两匹配程度,再加权求和,这一步计算本身不关心 token 排在序列的第几位——把输入的 token 顺序整个打乱,只要不带任何位置信息,算出来的匹配程度和加权结果会跟着一起打乱,但每个 token 各自对应的那份输出内容不会有任何变化。容易被忽略的地方是,这种错误不会在训练时报警:绝大多数 patch 的去噪目标,靠这个 patch 自己的像素和周围 patch 的内容就能猜个大概,不需要知道自己在网格里的第几行第几列,损失曲线该降还是会降,模型看起来训练得很成功。真正丢掉的是任何依赖方位的信息——7.3 节「左边一只猫,右边一只狗」那种指令,没有位置编码,模型连哪些 token 算「左边」都判断不出来,这种错误不会体现在损失数字上,只会体现在生成结果的构图里,而且要等到有人去看图才会发现。
自己验:把 patchify 之后的 token 序列打乱顺序再喂进去,不加位置编码时,把模型输出按同样的方式换回原来的顺序,应该和没打乱之前的输出完全一致,逐个数值都对得上;换成加了位置编码之后再做同一组打乱实验,换回顺序之后的输出应该和没打乱之前明显不一样——位置编码一旦加入,token 排在序列的第几位就成了计算的一部分,顺序信息才真正进了模型,而不是被自注意力这一步悄悄忽略掉。
留给你的问题
这一章换掉的是去噪器内部的结构:用一串对每个 token 一视同仁的注意力层,代替了专为图像设计的下采样、上采样、跳跃连接。但采样时,扩散模型还是要从一份纯噪声出发,沿着第 4、5 章那条路径一步步走向清晰图像,那条路径当时留下的问题没被这一章碰到:路径本身是弯的,想走准就要忍受成百步的小步长,或者像 DDIM 那样跳步、放弃精确解。换掉去噪器内部的结构,会不会顺带影响这条采样路径本身的形状?如果这条路径能被设计成一条真正的直线,走一步是不是就该等于走到底?
不看材料,写下这一章的三堵墙
第一堵:以为 UNet 那套下采样、上采样、跳跃连接是扩散模型必需的结构,其实换成几乎不针对图像设计的标准 transformer,规模给够一样能训出来。第二堵:以为条件注入越精细越好,cross-attention 能让每个 token 单独关注条件,adaLN-Zero 却凭零初始化「起步等于什么都没做」这个技巧,计算量更低还赢了它,前提是条件足够粗粒度。第三堵:以为注意力的平方复杂度在常见的 1K 分辨率就会成为主要开销,实际算下来要到 2K 附近,二次项才追得上线性项。
第8章 把弯路拉直
开篇故事第 8 集 · 把方差调到零,传言才勉强成真这模型也太真实了
炼丹废人的鼠标停在发布按钮上方,页面标题他已经写好了——图像这条线一步就能生成,实测误差趋近于零。他这次没打草稿,直接把《潜空》刚跑出来的那条曲线截了图,准备现在就贴上去。
复现进行到第五十二天,图像这条线眼看就要收尾,这个执念却从论坛里那条传言开始:有人说只要把训练目标从预测噪声换成预测速度场,路径就会变成一条直线,直线只用一步就能走到底。炼丹废人半信半疑地在《潜空》里试了一把,为了图省事,他把玩具数据集的方差直接拖到了零再训练、再采样,一步的误差小到几乎看不见,屏幕上跳出来的数字精确到小数点后十二位都不带抖的。他没多想,觉得这就是传言成真的证据。
一步生成到底是真传言还是假传言,谁验过
1L炼丹废人我这边跑出来误差已经小到看不见了,直线路径这事我算是信了,值得现在就写进本周的进度贴。
2L沈砚数据方差你是不是顺手调成零了?我记得你上周才吐槽过图省事改参数这个老毛病。
3L复现之鬼这个疑点问到点子上了,方差为零等于所有数据挤在同一个点,路径当然不会互相打架,这种条件换不了别的说法。
4L靳(主控认证)把方差调回正常值,再跑一遍一步采样,数字自己会说话。
这时候已经是后半夜,炼丹废人桌角那桶泡面汤都凉透了,他也没顾上再热一次,转身就把数据集的方差换回 0.5 重新训练了一遍。
系统提示数据方差调回 0.5 后,一步 Euler 采样误差回升至约 1.0,与方差为 0 时的 1e-12 相差超过十个数量级,前向路径的校验结果由通过转为未通过。
“直的是那条插值路径,不是学出来的那条流。”靳工瞥了眼两条曲线,“数据只要有分散度,不同点连出来的线迟早会在半路碰头,网络学到的是碰头之处的平均方向,那条平均出来的流天然是弯的,方差归零只是把碰头这件事从物理上抹掉了。”
这场闹剧之外,图像这条线另外两处改动这两天也顺带定了下来:图像和文字不再挤在同一套参数里,各自留一套权重,只在算注意力那一步把两边拼到一起;引导强度那个旋钮也被重新标定过,论坛上老有人把代码里的 guidance_scale 和论文里的 w 混着用,两者其实差着 1,换算错了方向就全错了。
炼丹废人把截图撤了下来,另开一个帖子,标题换成了更老实的一句:一步生成只在方差为零这种退化情况下才精确,真实数据集里并不成立。这条更正帖依然是他署的名,比原来那条判他更疼,组里没人替他挡这一下。
沈砚倒是从这场乌龙里摸出点信心,觉得自己在图像这条线上摸得差不多了,随口说视频那条线无非是多画几帧的事,明天就能试试手——他还没意识到,这句话明天就要把他自己送上论坛的反面教材。
系统结算 · 本章考点
- Rectified Flow 把训练目标直接定义成两点连线:数据点到噪声点的插值路径写下来就是一条直线,不需要再解方程才知道形状
- 但训练路径直,不代表学出来的那条流也直:数据分布只要有分散度,不同的连线会在中途相遇,网络学到的是交叉点上的平均方向,这条平均出来的流通常是弯的
- 那道最关键的验证:把数据方差 s 调到 0,训练路径彼此不再相交,一步走到底的误差跌到 1e-12 以下;方差调回 0.5,误差立刻回到约 1.0,代码一行没改,变的只是数据
- SD3 的 MMDiT 没有沿用单向的 cross-attention,图像和文字各自留一套独立参数,只在算注意力那一步把两边拼起来一起算
- 论文里的引导强度 w 和代码里常见的 guidance_scale 差了 1:guidance_scale=4.5 换算回去是 w=3.5,CFG 论文自己认为最好的区间是 w=0.1 到 0.3,社区常用的 guidance_scale=7.5 换算过去是 w=6.5,比论文的最佳区间高出一大截
第 5 章留下一堵墙:DDIM 能跳步,是因为它沿着一条弯曲的路径走了一条近似的捷径,步子迈大了,误差就跟着涨。那条从纯噪声通向数据分布的路径,从一开始就是弯的——越弯的地方,想走准就越需要小步子,这是那一章反复验证过的事。如果这条路径从一开始就被设计成一条直线,会发生什么?直线只需要知道起点和方向,按理说一步就该能走到终点。
学完这一章你应该能做到
- 说清 Rectified Flow 为什么能把采样路径设计成直线,以及这条直线路径对应的训练目标是什么
- 解释清楚「训练用的每条路径都是直的」和「学出来的那条流也是直的」这两句话为什么不是一回事
- 说出 SD3 的 MMDiT 架构里,图像和文字两路信息各自的参数是怎么安排的,和第 6 章的 cross-attention 有什么不同
- 把 CFG 的引导强度 w 和代码里的 guidance_scale 换算清楚,说出两者为什么会差 1
这一层要把去噪器预测的目标从噪声换成速度场,并且在采样时加上分类器无关引导。前七级的采样路径都是弯的,需要几十到上百步小心地走;这一层要验证,把路径设计成直线之后,一步走到底在什么条件下是精确的,在什么条件下只是一个近似。
8.1 如果路径本身是直的,一步是不是就该走到底?
上一章我们看到,扩散模型从噪声走到数据要沿着一条弯曲的路径,DDIM 跳步只是一种近似解法。如果换一种训练方式,让模型学的这条路径从定义上就是一条直线——直接连接一个噪声点和一个数据点的线段——你觉得只用一步 Euler 法走完这条路径,误差会怎样?
答完再看
RF 论文把这句话说得很直接:如果流真的是直的,一步 Euler 更新 Z1=Z0+v(Z0,0) 算出来的就是精确的 Z1(RF §2.2)。「真的是直的」这个前提很关键——第 8.2 节会看到,这个前提在实践里并不总是成立。
上一章的采样路径之所以弯,是因为它是被动推导出来的结果——先定义好怎么往数据里加噪声,再倒过来解那条对应的概率流方程,路径长什么样,是加噪声那条规则决定的,没有人主动设计过它的形状。Rectified Flow 换了一个思路:直接把路径定义成直线。给定一个数据点 X0 和一个噪声点 X1——这里沿用全书的记法,下标 0 是数据、下标 1 是噪声,原始论文自己用的是反过来的记法(论文原文:「we set π0 to be the standard Gaussian distribution and π1 the data distribution」,即论文的 X0 是噪声、X1 是数据),这里把两个下标对调过来,和全书保持一致——中间时刻 t 的位置,就是这两点的线性插值,训练目标是让网络学会这条线段的速度:
第 3~5 章的路径(先定加噪声规则,再解概率流方程),和 Rectified Flow 的直线路径,哪一个的形状是在「写下训练目标公式」这一步就已经确定的,不需要再解任何方程才能知道?
变式:如果换一种加噪声的规则,DDPM 对应的路径形状会不会跟着变?RF 的直线形状会不会跟着变?
| 符号 | 是什么 | 直觉 |
|---|---|---|
| X0 | 数据点(下标 0,和全书记法一致) | 这条路径的终点,采样要抵达的地方 |
| X1 | 噪声点(下标 1) | 这条路径的起点,采样从这里出发 |
| t | 插值进度,0 到 1 | t=0 在数据,t=1 在噪声 |
| v(Xt,t) | 网络要学的速度场 | 训练目标是让它逼近常数 X1−X0 |
给定 X0=(0,0)(数据点),X1=(4,2)(噪声点)。如果网络已经完美学到了这一对的速度 v=X1−X0,从噪声点出发、一步走到底,最终落点应该是多少?
变式:如果网络学到的速度有偏差,实际输出的是 (3,2) 而不是 (4,2),一步走到底的落点会偏到哪里?
这个训练目标和第 4、5 章预测噪声的目标,本质上是同一类东西——都是用均方误差去拟合一个目标值,只是目标从「噪声本身」换成了「两点之间的位移」。因为 X1−X0 是一个不随 t 变化的常数,如果网络真的学到了这个目标,那么对某一对具体的 (X0,X1) 而言,它在这条线段上任何一点算出来的速度都应该是同一个向量。
被动推导:先定加噪声规则,路径是解出来的
第 3~5 章的路径,是先定好前向加噪声的规则,再倒过来解对应的概率流方程得到的,路径长什么形状不受直接控制,往往是曲线。
主动设计:直接把路径定义成直线
Rectified Flow 把「路径是直的」写进训练目标本身——数据点和噪声点之间的线段,不需要再解一个复杂的微分方程去找路径形状。
差别在于路径的形状是不是算法本身可以直接控制的自由度——RF 把它变成了定义的一部分,而不是等着它自己从别的规则里冒出来。
但「训练目标要求路径是直的」和「训练出来的模型真的学到了一条直的路径」,是两件不同的事——这正是下一节要拆开看的地方。
8.2 每一条训练路径都是直的,学出来的那条路径也一定直吗?
训练时抽出的每一对 (X0,X1),它们之间的插值路径都是一条严格的直线,这件事从公式上看是确定的。你觉得网络训练完之后,从任意一个新的噪声点出发、沿着网络学到的速度场走出来的那条采样路径,会不会也是一条直线?
答完再看
这是这一章最容易被讲错的一点:训练用的每一条条件路径确实是直的,但网络最终学到的、真正在采样时会用到的那条边缘化流,通常不是直的。原因不在网络结构,而在训练目标本身——第二个选项已经点到了关键词,下面从头推一遍。
自己推一遍:为什么训练路径直,学出来的流不一定直
网络在某个具体的点 z、具体的时刻 t,被要求输出一个速度。这个输出到底是由哪些训练样本决定的——是只由「穿过 z 这个点」的那些 (X0,X1) 决定,还是由所有训练样本一起决定?
想好了再看
均方误差最小化的最优解是条件期望:v*(z,t)=E[X1−X0 | Xt=z]。这个式子的意思是,网络在 (z,t) 处的最优输出,是对所有满足「在时刻 t 恰好经过 z」的那些训练对,把它们各自的 X1−X0 取平均。只由穿过 z 的那些样本决定,但如果不止一对穿过 z,网络看到的是它们的平均,不是其中任何一对单独的方向。
会不会真的有不止一条训练路径,在同一个时刻经过同一个点?举一个具体的例子。
想好了再看
会,而且只要数据分布不是单点,这种情况几乎必然发生。比如两个数据点 X0=(0,0) 和 X0'=(4,0),配上两个噪声点 X1=(4,4) 和 X1'=(0,4):连接 (0,0)→(4,4) 的直线,和连接 (4,0)→(0,4) 的直线,在平面几何上会在中点 (2,2) 相交,两条线经过 (2,2) 的时刻正好都是 t=0.5。
在这个交叉点上,两条路径各自的方向(也就是各自的 X1−X0)分别是什么?网络在这一点会输出哪一个?
想好了再看
第一条路径方向是 (4,4)−(0,0)=(4,4);第二条路径方向是 (0,4)−(4,0)=(−4,4),两个方向并不相同。网络在 (2,2, t=0.5) 这一点只能输出一个向量,按第 1 步的结论,它会输出两者的平均:((4,4)+(−4,4))/2=(0,4)——一个哪条原始路径都不指向的新方向。
把这些被平均过的速度,在各个时刻拼起来连续地走一遍,会得到一条什么样的路径?这件事听起来是不是似曾相识?
想好了再看
会得到一条弯曲的路径——在交叉点附近,方向被硬拉向两条原始直线的中间,前后方向不一致,连起来就是弯的。这和第 2 章「回归预测的是条件期望」、第 4 章「直接回归会把好几种合理结果拉成一张模糊平均脸」是同一件事的另一种版本:只要多个训练目标会在同一个输入上重叠,最小二乘学到的都是它们的平均,不是其中任意一个。
这条道理不只是几何直觉,实验室里能直接算出数字。同一个一维高斯玩具问题(第 4、5 章那个 x0~N(1, 0.5²)),换成 Rectified Flow 的方式重新推一遍最优速度场,再用不同步数的 Euler 法采样:
| 步数 | 1 | 2 | 5 | 10 | 20 | 50 | 100 |
|---|---|---|---|---|---|---|---|
| RF 误差 | 1.000e+00 | 6.000e-01 | 2.615e-01 | 1.384e-01 | 7.149e-02 | 2.919e-02 | 1.469e-02 |
| DDIM 误差 | 9.907e-01 | 9.707e-01 | 5.451e-01 | 2.729e-01 | 1.357e-01 | 5.299e-02 | 2.522e-02 |
两条结论都藏在这张表里。第一条是真实的优势:步数相同时,RF 的误差大约只有 DDIM 的一半。第二条戳破了「一步走到底」的期待:RF 一步的误差是 1.000,和 DDIM 一步的 0.9907 几乎一样糟,直线路径的优势,只走一步时完全没体现出来。
如果数据分布是两个离得很远、几乎不重叠的高斯团块(比如均值分别是 -10 和 10),噪声仍然是标准正态分布。你觉得这种情况下,训练路径互相交叉的概率会比 x0~N(1,0.5²) 这种单团块分布更高还是更低?说说你的理由。
变式:如果反过来,让数据分布变成单一的一个点(方差为 0),交叉概率会怎么变?
这张表也留了一条退路。把数据分布的方差 s 一路调小、调到趋近于 0——所有数据几乎挤在同一个点上——这时候不同的 (X0,X1) 配对之间,路径还会不会互相交叉?答案是不会,因为终点几乎是同一个点,不同噪声起点连向同一个终点的直线,只在起点附近散开,彼此之间几乎不相交。这时候边缘化流真的是直的,RF 一步的误差降到 1.000000000000——不是「非常接近」精确,是浮点数精度范围内的精确。
普通情况:数据有分散度,路径会交叉
s=0.5 时,不同配对的连线在中途相遇,网络学到的是交叉点上的平均方向,边缘化流是曲线,一步误差高达 1.000。
退化情况:数据挤在一点,路径不交叉
s→0 时,所有连线的终点几乎重合,彼此不再相交,网络学到的就是每条线自己的方向,边缘化流真的是直线,一步误差精确到 1.000000000000。
差别只在数据分布本身的分散度——训练代码、网络结构、优化器都没有变,变的只是数据会不会让不同的路径撞在一起。
回到 8.1 节预测门那个问题:「如果路径本身是直的,一步是不是就该走到底」。结合这一节学到的东西,给一个完整的回答:什么样的数据分布,能让训练出来的流满足「一步走到底精确」这个条件?对于真实的图像数据集,这个条件成立吗?
变式:如果数据集只有两种严格不重叠、极窄的模式(比如只有纯黑和纯白两张图,不掺任何中间形态),路径交叉的情况会比真实图像数据集更多还是更少?
8.3 路径不够直,能不能再把它拉直一次?
上一节看到,边缘化之后的流直不直,取决于训练路径会不会交叉,而真实数据几乎总会交叉。这不是一个只能被动接受的结果——RF 论文给出了一个能量化「有多不直」的指标,还给出一个能把它压下去的操作。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| Z1−Z0 | 起点终点连线暗示的「应有」恒定速度 | 如果路径是直线匀速走的,任意时刻速度都该等于这个值 |
| Żt | 路径在时刻 t 的实际瞬时速度 | 沿着学出来的流实际走出来的速度 |
| S(Z) | 两者差值的平方,沿全程积分 | S(Z)=0 就是「真的直」,8.1 节一步走到底精确成立的条件 |
RF 论文接着证明了一件有用的事:把训练好的流反过来当成一个数据生成器,用它重新生成一批 (Z0,Z1) 配对,再用这批新配对重新训练一个新的流,这个操作叫 reflow。这不是随便配对——一个良定义的常微分方程,不同起点画出来的解路径,根据解的唯一性,彼此之间不可能在 t∈[0,1) 内相交(RF 关于「流不交叉」的性质)。
原始配对:随便凑的
训练用的 (X0,X1) 是随机独立配对的——每次从噪声堆里随便抓一个点,和数据堆里随便抓一个点凑成一对,不同配对之间的连线当然可能互相穿过。
reflow 后的配对:由模型自己的轨迹给出
新一轮训练用的 (Z0,Z1),是让已经训好的流从 Z1 出发、沿着自己的速度场走到底得到的 Z0——同一个确定性 ODE 的不同解路径,不可能相交。
差别在于配对是随便凑的,还是由模型自己的确定性轨迹给出的——后者从构造方式上就排除了路径相交的可能,S(Z) 因此能被压低,不是靠运气,是靠这个配对方式本身的几何性质。
反复做这个操作,RF 给出一个可以量化的保证(Theorem 3.7):连续做 K 次 reflow,这 K 次里最好的一次,它的不直程度有一个明确的上界。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| k | 第几次 reflow(k=0 是原始未 reflow 的流) | 做了几轮「重新配对、重新训练」 |
| K | 总共打算做的 reflow 次数 | K 越大,上界压得越低 |
| E[|X1−X0|²] | 最初数据点和噪声点之间距离平方的期望 | 一次性算出来的常数,不随 K 变化 |
如果 E[|X1−X0|²]=8,做 K=4 次 reflow,最好一次的 S(Zk) 上界是多少?要把这个上界再减半,至少要把 K 加到多少?
变式:如果只做 K=1 次 reflow,上界会是多少?和不做 reflow(k=0 那一次本身没有这个保证)相比,这说明了什么?
为什么「同一个确定性 ODE 的不同解路径不会相交」?如果两条解路径真的在某个点相交了,会出现什么逻辑矛盾?
变式:那为什么 8.2 节里,两条原始训练路径的连线却可以在 (2,2) 相交?和这里的论证不矛盾吗?
答辩:如果我是审稿人
reflow 每做一次,都要用当前的流重新生成一整批模拟数据、重新训练一个新模型,这笔训练成本,会不会只是把多步采样的成本挪到了训练阶段?真的划算吗?
参考防守(先自己组织语言再看)
这笔成本确实真实存在,但性质不同:它是一次性的训练成本,训完之后能一直用来做一步甚至几步的快速采样;多步采样的成本是每次生成都要重复付出的。只要模型上线之后会被调用很多次,一次性的训练成本很容易被大量的推理次数摊薄。Theorem 3.7 给出的是一个可以量化的保证,不是玄学承诺。但论文自己也提醒不要做太多轮 reflow(原文「it is not recommended to apply too many reflow steps」),因为每一轮都会在训练里引入新的估计误差,好处会在有限的几轮之后见顶——具体几轮最划算,需要针对场景做核算,论文没有给出普适的答案。
对你而言未知能不能在第一次训练时就选一种不容易交叉的配对方式
reflow 的思路是先接受第一轮配对会交叉,训完之后再重新配对、重新训练一轮来补救。反过来想,如果在第一轮训练之前就想办法把 (X0,X1) 配得更聪明——比如让距离近的噪声点和数据点尽量配对在一起,而不是完全随机配对——会不会从一开始就减少交叉,省掉后面几轮 reflow?本章材料没有展开讨论这个方向,要不要现在就去查更晚近的后续工作,由你决定。
先做这一步:在本章的直线路径实验室里,如果实验室支持切换配对方式,把配对从「随机配对」改成「按最近邻配对」,重新画一遍轨迹图,看交叉点是不是真的变少了、S(Z) 的初始值是不是比随机配对更低。
8.4 图像和文字两路信息,要不要挤在同一套参数里?
第 6 章的 cross-attention,让图像 token 去查文字信息——Q 来自图像,K、V 来自文字,是不对称的单向查询。如果要设计一个同时处理图像和文字两种信息的模型,除了这种单向查询,你觉得还有没有更对称的安排?
答完再看
SD3 的 MMDiT 选的正是第二种:图像和文字各自有一套独立的权重,只在做注意力运算的时候把两边的序列拼接起来一起算,让两边「各自在自己的空间里工作,同时又能看到对方」(SD3 原文)。
论文给出的理由很直接:图像的 embedding 和文字的 embedding,概念上差别很大,所以用两套独立的权重分别处理(SD3 §4,MM-DiT 架构在这一节引入)。这等价于两个独立的 transformer,只是在算注意力的那一步,把两边的 token 序列拼接起来一起参与计算,让两种表示都能在各自的空间里工作,同时又能顾及到对方(SD3 原文:「this is equivalent to having two independent transformers for each modality, but joining the sequences of the two modalities for the attention operation, such that both representations can work in their own space yet take the other one into account」)。
cross-attention:单向查询(第 6 章)
Q 只来自图像,K、V 只来自文字,图像 token 主动去「问」文字,文字不会主动看图像,只有一套权重服务于图像这一侧的查询。
MMDiT 联合注意力:双向对称
图像和文字各自有独立参数,只在注意力这一步把两边拼起来一起算,谁都能看到谁,过完这一步各自又分开继续自己的计算路径。
差别在于「谁的权重专门为谁准备」——cross-attention 里文字那一侧只提供内容、不参与专门为它准备的计算;MMDiT 里两套权重各自完整,拼接只发生在算注意力矩阵这一步。
SD3 依然保留了一部分 adaLN 式的调制,用来处理文字的池化向量(一个粗粒度的整体表示),但文字里逐 token 的细节信息走的是刚讲过的联合注意力,不是 adaLN。结合第 7 章「adaLN 全体 token 共用一组系数」的局限,说说 SD3 为什么要把这两种机制分工使用,而不是二选一。
变式:如果一个条件只有类别标签、没有任何文字描述,SD3 这套分工设计是不是就退化成了第 7 章单纯的 adaLN-Zero?
8.5 训练时该在哪个噪声强度上多花功夫,是随便挑的吗?
前几章训练时,时间步 t 都是均匀随机抽出来的,0 到 1 之间每个值被抽到的概率一样。SD3 在这一点上做了一个具体的改动:不再均匀抽,而是从一个叫 logit-normal 的分布里抽——这个分布在 0 和 1 两端的密度很低,中间某个位置的密度最高,具体位置由一个参数 m 控制,m 是负数时偏向数据那一端(t 靠近 0),是正数时偏向噪声那一端(t 靠近 1)(SD3 §3.1:「enables us to bias the training timesteps towards either data p0 (negative m) or noise p1 (positive m)」)——这句话里 p0=数据、p1=噪声,和全书记法完全一致,不需要再做换算。
如果 m=1.5(正数),训练时抽到的时间步会更集中在靠近数据的一端,还是靠近噪声的一端?
变式:如果想让训练更多地关注「数据刚开始被破坏」的阶段,m 该往哪个方向调?
为什么不均匀抽更好?中间时刻的 t,对应的是「噪声和信号大致势均力敌」的状态,这时候网络要预测的速度场信息量最大,学起来最有用;越靠近两端,输入要么几乎是纯数据、要么几乎是纯噪声,网络更容易蒙对,练得再多边际收益也小。让训练多花时间在信息量大的区间,是这个改动背后的直觉。
第二个改动和分辨率有关。把一张纯色图像(所有像素值相同)加噪声,可以看成对同一个随机变量做 n 次独立观测再取平均,n 就是这张图有多少个像素(或者多少个 token)。观测次数越多,平均值的抖动就越小——n 个独立观测的均值,标准差会缩小到 1/√n 倍。SD3 把这个道理直接搬了过来,算出在时间步 t、像素数为 n 时剩余的不确定程度:
均匀抽时间步时,t 在 0 附近(几乎纯数据)和 t 在 1 附近(几乎纯噪声)这两种情况,网络预测速度场需要「猜」的信息量,和 t=0.5 附近相比,谁更大?
变式:如果反过来,把 logit-normal 的密度设计成两端高、中间低,训练会偏重学到什么?
| 符号 | 是什么 | 直觉 |
|---|---|---|
| t | 时间步 | 越靠近 1,噪声越强 |
| n | 像素数(或 token 数) | 分辨率越高,n 越大 |
| σ(t,n) | 剩余的不确定程度 | 同一个 t,n 越大,σ 越小 |
这个公式带来一个直接的推论:把图像的宽和高都翻倍,n 变成 4 倍,同一个 t 上的不确定程度就减半——原文写的是「doubling the width and height leads to half the uncertainty at any given time」。同一个数值的 t,在大图上实际代表的「离数据还有多远」,比在小图上要近得多;继续用同一套不考虑分辨率的时间步安排训练大图,相当于让大图在信息量最大的中间区间被系统性地训练不足。SD3 的做法是解出一个换算公式,给定小分辨率 n 上已经调好的 tn,算出大分辨率 m 上能达到同样不确定程度的 tm:
用 σ(t,n)=t/(1−t)·√(1/n) 这个公式,固定 t=0.5,比较 n=256 和 n=1024 两种情况下的 σ,比值是多少?
变式:如果 n 从 256 涨到 2304(比如 token 数从 16² 涨到 48²),σ 会缩小到原来的多少?
| 符号 | 是什么 | 直觉 |
|---|---|---|
| n、m | 两个不同的分辨率(像素数/token 数) | n 是已经调好的基准分辨率,m 是目标分辨率 |
| tn | 在分辨率 n 上已经调好的时间步 | 已知的起点 |
| tm | 换算到分辨率 m 上、不确定程度相同的时间步 | 要求出的目标 |
| α | √(m/n),平移强度 | 1024² 训练和采样都用 α=3.0(SD3 §5.3.2) |
SD3 在 1024² 训练时用 α=3.0。如果这个模型是从 256²(对应 n)开始先调好时间步分布的,α=3.0 对应的目标分辨率 m 应该接近多少?
变式:如果基准分辨率 n 换成 512²,同样 α=3.0,目标分辨率 m 会是多少?
8.6 引导强度调大,到底是在做什么?
训练好条件生成模型之后,很多实现会引入一个「引导强度」参数,调大它,生成的图会更贴合文字描述,但也更容易过饱和、多样性下降。你觉得这个参数做的这件事,本质上更接近「在条件预测和无条件预测之间取一个折衷」,还是别的什么操作?
答完再看
CFG 的公式改写一下就能看出来:它不是插值,是外推——以无条件预测为起点,沿着「条件预测减无条件预测」这个方向,走得比条件预测本身还要远。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| εc | 条件预测(喂了文字描述) | 模型「知道该画什么」时的答案 |
| εu | 无条件预测(不喂文字描述) | 模型「完全不知道该画什么」时的答案,外推的起点 |
| w | 引导强度 | w=0 时直接用条件预测本身;越大,外推距离越远 |
| ε̃ | 最终用来采样的预测 | 从 εu 出发,朝 (εc−εu) 方向外推 (1+w) 倍的结果 |
这两个写法代数上完全一样,但外推那个写法更直白地说明了 w 在干什么:w 越大,从无条件预测出发朝条件方向走的距离就越远,不只是走到条件预测那里,而是绕过它继续往前。这解释了为什么引导强度调太大会过饱和、丢多样性——朝一个方向不断外推,早晚会冲出「正常」图像该在的范围。
看成插值:条件和无条件的加权平均
容易让人以为 w 只是在两个「正常」结果之间选一个折衷点,推不出为什么 w 太大会导致过饱和这种「跑出正常范围」的现象。
看成外推:从无条件出发,沿条件方向冲得更远
直接解释了为什么 w 变大会让图像越来越不自然——不断沿一个方向往前冲,迟早冲出正常图像该在的范围。
两种写法代数上完全等价,但外推这个框架能预测到插值框架预测不到的现象——同一个公式换一种写法,能看出的直觉可能完全不同。
一个符号陷阱
论文里的 w,和很多代码库(比如 diffusers)里的 guidance_scale,不是同一个数,两者相差 1:guidance_scale = 1+w。论文写 w=0 表示不引导,对应代码里的 guidance_scale=1;社区常用的默认值 guidance_scale=7.5,换算回论文的 w,是 6.5,不是 7.5。看论文里的实验数字时,要先确认这是原始的 w,还是已经加过 1 的 guidance_scale,两者代入外推公式,效果差得很远。
某个代码库里 guidance_scale=4.5,换算成论文里的 w 是多少?
变式:CFG 论文自己报告的最佳 FID 区间是 w=0.1~0.3,换算成 guidance_scale 是多少?
CFG 论文自己在 ImageNet 上报告,最好的 FID 出现在 w=0.1 到 w=0.3 之间(CFG §4:「We obtain the best FID results with a small amount of guidance (w = 0.1 or w = 0.3, ...)」),换算成 guidance_scale 大约是 1.1 到 1.3;而 Stable Diffusion 系列社区常用的默认值是 guidance_scale=7.5,对应 w=6.5——比论文自己认为的最佳区间高出二十倍不止。FID 衡量的是生成图像分布和真实图像分布有多接近,人眼觉得「好看」「贴合文字」,和这个统计意义上的接近,不是同一件事,这也是为什么两个数字会差这么远。
答辩:如果我是审稿人
引导强度越大、图像越贴合文字描述,但同时也越不自然、多样性越低,这算不算一种应该被修复的缺陷?还是说,对大多数使用场景,「贴合文字」本来就比「统计上多样」更重要,这个副作用其实是被默许接受的?
参考防守(先自己组织语言再看)
两种立场都有数据支撑,取决于评价标准选哪个。从 FID 这个统计指标看,过饱和确实是偏离真实图像分布的表现,算是缺陷;但从人类主观评分看,适度调高引导强度往往会提升「好不好看」「是否贴合文字」这些主观分数,这也是社区默认值远高于论文最佳 FID 区间的实际原因。论文自己也提到了这一点:FID 最优的引导强度和视觉主观质量最优的引导强度不是同一个数。真正悬而未决的是有没有可能同时兼顾两者,目前看到的做法是牺牲统计意义上的分布匹配、换取主观视觉效果,而不是真正解决了这个张力。
8.7 今天的开源图像模型,都在往哪几个方向走?
这本书从第 6 章到这里,一直跟着一条主线走——从 LDM 的潜空间压缩,到 DiT 扔掉 UNet,到 SD3 把图像和文字拆成两路参数。这条主线今天还在往前走,而且分成了几条并不相同的方向。
第一条是纯粹的规模路线。FLUX.2 的 dev 版本是一个 32B 参数的生成骨干;HunyuanImage 3.0 走的是另一种放大方式,总参数 80B,但用混合专家(MoE)架构,每处理一个 token 只激活其中 8 个专家,实际参与计算的部分远小于总参数量。两条路都是「更大」,但实现方式不一样——一个是老老实实把稠密网络做大,一个是把总容量做大、同时把每次实际计算量控制住。
HunyuanImage 3.0 总参数 80B、每 token 激活 8 个专家(共 64 个专家)。结合第 7 章「计算量取决于实际参与的乘加次数」这个道理,为什么不能直接把它的推理成本,按 80B 参数量类比成一个 80B 的稠密模型来估算?
变式:MoE 这种设计,省下来的主要是训练/推理的哪种资源——存储模型需要的显存,还是做一次前向计算需要的算力?两者省的程度一样吗?
第二条路线反过来,比的是「多小、多快」。Z-Image 是一个 6B 参数的单流 S3-DiT 模型,蒸馏之后 8 步就能出图;FLUX.2 klein 只有 4B 参数,走的也是把模型和步数都压到最小的路子。这条路线关心的不是能不能把质量做到最好,是能不能把同样过得去的质量做到最省。第三条路线的方向是多模态统一——不再只做「文字生成图像」这一件事,让同一个模型同时具备理解图像和生成图像的能力。这条路线这几年动作很多,但本书这一批论文材料没有覆盖到具体的架构细节,这里只标出方向,不展开讲。
Z-Image(6B,蒸馏 8 步)和 FLUX.2 klein(4B)这两个例子,共同点是在优化模型的哪个维度?和第一条规模路线的 FLUX.2 dev(32B)、HunyuanImage 3.0(80B MoE)相比,两条路线追求的目标是不是矛盾的?
变式:如果一个团队只有很少的推理算力预算,这两条路线里哪一条更适合他们直接拿来部署?
还有一条贯穿这几条路线的趋势:文字编码器正在从 CLIP、T5 这类专用模型,换成完整的通用 VLM 或 LLM——FLUX.2 用 Mistral-3 24B 做文字编码器,Qwen-Image 用 Qwen2.5-VL,Lumina-Image 2.0 用 Gemma-2-2B。这带来一个不算小的代价:条件编码器本身可能比生成骨干网络还要大,部署的时候不再是一个模型,是两个大模型一起跑。
这一串名字里,没有一个能被称为「当前最好的图像生成模型」。原因很直接:这些模型评测用的基准、评测的方式都不一样,有的比 FID,有的比人类偏好投票,有的比特定风格下的生成质量,换一套评测标准,排在前面的名字就会变。FLUX.1 这个在下载量上名列前茅的系列,甚至没有一篇官方论文——它 19 层双流块加 38 层单流块的骨干结构,是从三个社区镜像仓库不约而同给出的配置文件里读到的,不是 Black Forest Labs 自己公布的数字。
FLUX.1 的 19+38 层结构,属于本书第二章讲过的哪一档证据(A 档可自算 / B 档论文自述 / C 档本站推断)?
变式:如果三个社区镜像给出的层数彼此不一致,这个数字还能不能按 C 档写进正文?
读的时候要小心
关于本章数字的来路
本章的 Rectified Flow 玩具问题误差表、CFG 的 w 与 guidance_scale 换算、SD3 分辨率平移公式,都是按定义直接推出来的,实验室里能自己验证。SD3 分辨率平移在人类偏好评测上的提升(Figure 6)、CFG 论文自己报告的最佳 FID 区间、模型清单里的 GitHub star 和 HuggingFace 下载量,都是论文作者或官方仓库自己给出的数字,本站没有重新测过。
这一层要加什么:流匹配的速度场加 CFG
为什么现在才加它:前七级预测的都是噪声本身,采样时要沿着一条弯曲的路径走很多小步。这一层把预测目标换成速度场,训练目标是这一章推导的 X1−X0;同时加上分类器无关引导,让条件预测在采样时被有意地放大。
难点:这段代码本身实现的训练目标和采样公式都没有错,但「训出来的模型一步采样准不准」不是代码写没写对的问题,是数据分布本身决定的。如果数据分布有分散度(方差大于 0),8.2 节推过的道理会原样发生:不同的 (X0,X1) 配对在训练过程中会互相交叉,网络学到的实际是交叉点上的平均速度,这个平均值不管训练多久、代码写得多精确,都不会让一步采样的路径变直——想让它变直,要么让数据退化到单点,要么走 8.3 节的 reflow。这里最容易踩的坑是,看到一步采样误差大就怀疑训练代码写错了、反复调试代码本身,而真正的原因和代码正确性无关。
自己验:把数据分布的方差 s 调到 0(所有数据点退化成同一个点),重新训练、跑一步 Euler 采样,误差应该跌到 1e-12 以下——这是浮点数精度带来的误差下限,说明训练路径这时候已经没有系统性偏差了;把方差调回 0.5,重新训练、重新跑一步采样,误差应该回到约 1.0,和方差为 0 时判若两个模型,这个对比本身就是「训练路径直不直是数据决定的,不是代码决定的」这句话的证据。
留给你的问题
这一章把静止图像这条线走到了能实际部署的程度:直线路径、联合注意力、引导强度,都是这条线上摸出来的具体答案。但这些设计从头到尾都假设生成的是一张孤立的图,没有考虑「下一帧」是什么。如果生成的目标从一张图换成一段连续变化的画面,这一章讨论的直线路径、这套联合注意力,还能不能直接搬过去用?新多出来的这一维,会不会带来这一章完全没遇到过的新麻烦?
不看材料,写下这一章的三堵墙
第一堵:以为训练目标里每条路径是直的,学出来的那条边缘化的流也一定是直的,其实训练路径直不直是定义决定的,边缘化流直不直是数据分布决定的,只有在路径不交叉的时候两者才重合。第二堵:以为图像和文字最好的处理方式是让图像单向去查文字(cross-attention),SD3 的 MMDiT 证明了另一条路——两套独立参数只在注意力这一步拼接,双向对称,效果更好。第三堵:以为论文报告的引导强度就是社区常用的那个数,CFG 论文自己的最佳 FID 区间是 w=0.1~0.3,跟社区常用的 guidance_scale=7.5(对应 w=6.5)差了二十倍不止。
第9章 多了一维时间
开篇故事第 9 集 · 一百二十一帧,一百二十一只不一样的猫这模型也太真实了
播放条走到最后一帧,画面停在一只灰色斑纹猫身上,可十秒前跳上窗台的明明是一只全黑的猫——沈砚把进度条拖回开头重看了一遍,确认自己没有手滑,盯得眼睛发酸,一百二十一帧里那只猫换了不止十次花色。
复现进行到第六十三天,视频这条线才刚开工,半天前他还信心十足。图像这条线跑顺了之后,他觉得视频不过是同一件事多做几遍——把“一只猫从地板跳到窗台”这句话喂给模型,一帧一帧画出来,按每秒二十四张接起来,一段五秒的视频也就一百二十一帧,画完拼上就该是一条顺畅的镜头。他跟显存刺客打包票说,这活儿今晚就能交。
系统提示本次采样:一百二十一帧独立生成,每帧初始噪声互不相关,相邻帧特征相关系数趋近于零,画面连贯性校验未通过。
系统提示同一句文本条件下,模型给出的是一百二十一个各自独立成立的合理样本,而不是一个随时间连续变化的样本,本次结果符合预期性质,非数值错误。
沈砚那段视频为什么每一帧都在换猫,谁看出问题在哪
1L沈砚我是真没想到会这样,同一句话喂进去,跑出来的猫一直在换,这一百二十一帧我自己重看了三遍,问题到底出在哪。
2L炼丹废人我瞄着像是采样本身的事,每一帧应该都是各算各的,谁也不知道隔壁那一帧长什么样。
3L复现之鬼那就问到点子上了,你们是不是每一帧都重新抽了一份初始噪声?没在代码里查实之前,先别急着下结论。
4L显存刺客查过了,一百二十一次采样各自的初始噪声确实互不相关,日志里这条对得上,算力账也没算错,错的不是机器。
“要让下一帧知道上一帧发生过什么,得在网络里专门加一层。”靳工调出《潜空》的结构图,“空间那部分还是只看一帧内部,新插一层时间注意力,专门跨帧对比同一个位置,模型这才第一次真正能看见前一帧发生了什么。”
这层修好之后,另一个更大的数字冒了出来:5 秒、每秒 24 帧的这段 121 帧视频,如果还是按最省事的办法逐帧用二维 VAE 编码再切块,要用掉 435,600 个 token;换成时间和空间都往下压一层的方案,能压到 14,080 个,可就算压到这么狠的一档,也仍然比一张同分辨率的静态图片贵出好几倍。
沈砚这一晚白烧的那一百二十一次采样,算进这周机器时长的账里,直接顶掉了原本留给复现之鬼那组评测的额度——这周她的评测得往后挪,账他自己认了,没人替他说话。
靳工把两个 token 数字截图存进频道,说这笔账压缩率能不能再往下探,明天开个投票,让大家选一档——沈砚盯着那张图,忽然觉得画面里那只一直在换毛色的猫,好像还没资格抱怨谁。
系统结算 · 本章考点
- 5 秒、24 帧每秒、121 帧的视频,如果照最省事的办法逐帧用 2D VAE 编码再切 patch,要用掉 435,600 个 token
- 换成时间和空间都往下压一层的方案,同一段视频只要 14,080 个 token,是前一种算法的零头,但仍然比一张静态图片贵出好几倍——视频比图片信息量大,这件事怎么压都压不没
- Video Diffusion Models 最早是在 16 帧、64×64 这个规模上,把一层时间注意力插进原本只认得单帧的 2D 网络里
- 逐帧独立采样时,每一帧各自成立,帧与帧之间却没有任何东西把它们绑在一起——这不是模型出故障,是独立采样这件事本身该有的样子
- 真按 1280×720 算,704 才是能被压缩倍数整除的那个数:16 帧乘 22 乘 40,正好等于 14,080
你已经会用扩散模型画一张图了。手边正好想要一段镜头:一只猫从地板跳到窗台。最直接的念头是——把这句话喂给模型,画出第一帧,再画第二帧、第三帧,一路画到第一百二十帧,按每秒二十四张的速度接起来放出来,不就是一段视频了吗?这一章先把这个念头认真走一遍,因为它撞的墙,正是后面所有设计的起点。
学完这一章你应该能做到
- 说清逐帧独立采样为什么会让视频失去时间上的连贯性,以及为什么这不是模型的缺陷而是它本来的性质
- 讲出 Video Diffusion Models 往 2D UNet 里插入时间注意力层的具体做法,以及为什么选分解而不是联合的时空注意力
- 用 BRIEF 给定的公式,算出同一段视频在不同压缩方案下的 token 数量级差异
- 把视频的 token 暴涨和第七章的注意力二次项接起来,说清这堵墙在视频这个规模上有多高
这一层要把第 1 级到第 8 级一直在处理的数据——一张图、一个一维分布——换成一串带时间轴的序列。前八级里时间要么不存在,要么只是加噪去噪过程自己的那个 t,画面本身永远只有一帧。这一层要让画面本身也随时间变化,而且要让相邻帧知道彼此的存在,不然它们连「相邻」这件事都不知道。
9.1 逐帧独立采样,第二帧的猫为什么不是同一只猫
用训练好的文本生成图像模型,把「一只猫从地板跳到窗台」这句话喂给它一百二十次,每次单独采样一张图,按顺序接成视频。你觉得放出来会是什么样子?
答完再看
第三个选项是实际会发生的事。文本条件只锁定了「这是一只猫」这个层面,具体是哪只猫、什么姿势、什么光线,由那一帧单独抽到的初始噪声决定,而这份噪声每次都是重新抽的。第一个选项把「同一句话」错当成了「同一个结果」;第二个选项把扩散过程内部的那一串步骤,和外部一帧接一帧播放的视频混成了一件事,这两种「一步步」完全不是同一层。
这件事不用做实验也能想清楚,但做过一次就再也忘不掉。把同一句话喂给模型采样两次,得到的两张图从来不是同一张——耳朵形状、瞳孔颜色、光线角度都可能不同,这正是我们希望图像模型具备的性质:第 5 章讲过,一个训练好的去噪器配上一份新的初始噪声,就该给出一个新的、同样合理的样本。逐帧独立采样做的事情,是把这份「每次都不一样」的能力,原封不动地用在了一百二十次连续的采样上。让图像模型显得好用的那个性质,原样搬到视频上就成了缺陷。
每帧独立抽噪声:连贯性无从谈起
一百二十次采样,一百二十份不相关的初始噪声,网络对「这是第几帧」「前一帧长什么样」一无所知。画面在每一帧上都合理,帧与帧之间却没有任何约束,猫可以在一帧里趴着、下一帧里已经在半空——这就是闪烁的来源。
每帧共用同一份初始噪声:想省事,但换了一种坏
如果一百二十次都从同一个 xT 出发,配一个确定性采样器(第 5 章的 η=0),网络在每一帧上看到的输入完全相同,输出也会完全相同——得到的不是一段视频,是同一张照片复制一百二十遍,一动不动。
差别在:共享起点解决不了「网络不知道自己在处理哪一帧、前一帧发生了什么」这件事,只是把随机的跳变换成了确定的静止,两条路都没有让网络看到任何真正的时间信息。
如果确定性采样器(η=0)配上完全相同的初始噪声和完全相同的文本条件,一百二十次采样的输出会不会逐像素相同?给出你的推理,并说说如果换成 DDPM 的随机采样器(η=1),结论会不会变。
变式:如果每一帧仍然共享同一份 xT,但把文本条件从「一只猫从地板跳到窗台」换成「一只猫,第 k 帧」,把帧号写进 prompt 里,这样做能不能让输出真的动起来?
9.2 时间信息该塞进网络的哪个环节
上一节那两条路都没让网络看到「这是第几帧」「前一帧发生了什么」。要把这些信息喂进去,最省事的办法是不发明新架构,直接在已经训练好、已经会画图的 2D UNet 上做手术。
自己设计一遍:往 2D UNet 里插时间信息
2D UNet 里每一层卷积和每一层空间注意力,处理的都是单独一帧内部的空间关系。如果输入变成一整段 T 帧的视频,最偷懒的做法是什么,会有什么问题?
想好了再看
最偷懒的做法是把帧维直接当成批量维——每一帧仍然独立过卷积和空间注意力,互相看不见。这样效率没有任何损失,但结果退化回上一节那两条路,时间信息照样没有进来。
既然卷积和空间注意力都不该负责这件事,要不要新加一种专门处理帧与帧关系的层?它该放在网络的哪个位置,一次处理多大范围?
想好了再看
在每个空间注意力块之后,新插入一个时间注意力块,这次反过来把帧维当成要做注意力的那根轴,把空间位置(H×W 每个格子)当成批量维——画面上同一个位置的特征,在所有帧之间互相看一遍。空间那部分继续只管一帧内部,时间那部分只管跨帧同一位置,两件事分开处理。
时间注意力块要知道谁是第 1 帧、谁是第 16 帧,才能分清先后顺序。直接把绝对帧号喂进去可以吗,这样做会有什么代价?
想好了再看
可以,但代价是模型被绑死在训练时见过的帧号范围上,换一个总帧数、或者做本节最后要讲的自回归外推时,绝对帧号会超出训练时见过的范围。更稳的做法是相对位置嵌入——只告诉网络「这一帧比另一帧早几步」,不告诉绝对第几帧,这样不管从哪一段开始切,相对关系都不变。
Video Diffusion Models 就是这么做的。论文原话把前两步写得很直接:先把每个 2D 卷积换成「space-only 3D convolution」,比如把 3×3 卷积换成 1×3×3,帧轴单纯当批量轴;然后「insert a temporal attention block that performs attention over the first axis and treats the spatial axes as batch axes」,并且在每个时间注意力块里用「relative position embeddings」,让网络能分清帧的先后顺序,而不需要一个绝对的视频时间概念(§3)。
联合处理:所有帧、所有位置的 token 一起做注意力
一个 token 要和 T×H×W 个 token 逐一比较,注意力矩阵是 (THW)×(THW),代价正比于 (THW)²。规模一旦上去,这个矩阵大到算不动,更大到存不下。
分解处理:空间和时间各管一半
空间注意力块只在同一帧内部的 HW 个位置之间算,时间注意力块只在同一位置跨 T 帧之间算,从来不会出现一个 (THW)×(THW) 那么大的矩阵——一个大问题被拆成了两个小问题分别做。
差别在:分解版本用两次小规模注意力换掉一次大规模注意力,省下来的是那个平方项里最贵的部分。
论文自己给出的理由是效率:分解式的时空注意力「is known to be a good choice in video transformers for its computational efficiency」,这个判断当时是在 16 帧、64×64 这个规模下做出的(§4.1),算力紧张到联合处理压根跑不起来。
一帧的空间位置数记作 N(比如一个 32×32 的潜空间网格,N=1024),视频总帧数记作 T。联合处理的代价正比于 (T·N)²;分解处理的代价正比于 T·N²+N·T²(空间那部分对每帧算一次 N×N,时间那部分对每个位置算一次 T×T)。取 T=16、N=1024,两种代价各是多少,比值大概是多少?
变式:如果把 T 从 16 提到 64(N 仍是 1024),节省倍数会变成多少?这个结果和上一问之间有什么关系?
这里有个岔路口 配对的文本-视频数据不够、算力也不够的时候,各家怎么绕
把上面岔路口的三条路各用一个词概括它的核心代价:VDM 的代价、Make-A-Video 的代价、Imagen Video 的代价,分别是什么?
变式:如果必须三选一去做一个新产品,视频要求「够长」但对运动的物理真实性要求不高,你会挑哪一条路的思路,为什么?
9.3 十六帧封顶,怎么把视频拍得更长
手上的模型一次只能处理 16 帧。想要更长的视频,最直接的想法是:先采样出前 16 帧,把最后几帧当成已知条件,再采样接下来的 16 帧,首尾对齐拼起来,一段接一段往后延伸。你觉得拼接处会怎样?
答完再看
作者真的试过这个最直接的办法,论文原话说结果是「samples xb looked good in isolation, but were often not coherent with xa」——单独看每一段都行,接在一起就不连贯了(§3.1)。第三个选项也值得说一句:模型确实没见过这种输入格式,但扩散模型有个不需要重新训练就能凑出条件采样器的办法,这也是问题最终出在别处、而不是出在「能不能用」上的原因。
这个最直接的办法有个名字,论文管它叫替换法:每一步采样时,把已知那一段直接替换成它自己按前向过程加噪之后的版本,强迫这部分数值对齐真实值,只让新的一段自由生成。听起来很合理,问题出在它只管住了数值,没有管住网络的预测方向——新的一段可以在数值对齐已知段的表面下,内容依然和已知段的语义脱节,因为网络从来没有被要求生成一个和已知段连贯的延续,它只是恰好被摆在了已知段旁边。
替换法:采完之后拿真值贴皮
把已知段的加噪版本直接怼进每一步采样,强迫那部分数值对齐。新一段的预测方向从头到尾没有被这件事影响过,只是采样结束后被摆在了一个数值对齐的邻居旁边。
重建引导:每一步都被目标推着走
额外算一个梯度,从采样的第一步开始就推着新一段的预测朝「重建已知段更准」的方向走,不是事后拼接。
差别在:一个是采完之后拿真值贴皮,一个是每一步都在被一个和目标直接相关的信号推着走。
把这套修正写成公式:定义 x̂a 为网络对已知段的重建、x̂b 为网络对待生成段的原始预测(为了少写一层角标,这里把论文里的上标 a、b 改成了下标,意思不变)。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| x̂b(zt) | 网络对待生成段的原始预测 | 不看已知段时模型自己想画的东西 |
| x̂a(zt) | 网络顺带对已知段的重建 | 模型觉得已知段长什么样 |
| ‖xa − x̂a(zt)‖² | 重建和真实已知段之间的误差 | 模型这时候想歪了多少 |
| wr | 权重,越大修正力度越强 | 和第8章 CFG 里的 w 同一个角色 |
重建引导的更新式(式7)和第8章 CFG 的更新式 ε̃ = εu + (1+w)(εc − εu) 形状相同,都是「原始预测 + 权重 × 额外方向」,但额外方向的来历完全不同。分别说清楚这两处的额外方向是从哪里算出来的,以及如果把重建引导里的权重 wr 设成 0,采样会退化成本节提到的哪种做法?
变式:如果反过来把 wr 调得远大于 1,会发生什么?用 CFG 里 w 调得过大时的经验类比一下。
这套方法把 16 帧的模型延伸到了 64 帧(§4.3.3),用的是同一个思路反复应用:每往后生成一段,就把刚生成的这一段当成下一段的已知条件。它解决的是「能不能更长」,没有解决「一段视频里同时要塞进去的信息量本身有多大」——这才是下一节要算的账。
9.4 一段五秒的视频,要用掉多少个 token?
第6章算过,一张 1024×1024 的图片,经过 f=8 的 VAE 加 patch=2 之后是 4,096 个 token。现在换成一段 5 秒、24fps、1280×720 的视频(121 帧),如果用最省事的办法——2D VAE 逐帧编码,再按图像的方式切 patch——这段视频会变成多少个 token?
答完再看
按这套最省事的方案算出来是 435,600 个 token,是那张 1024² 图片的一百零六倍。第一个选项低估了整整一个数量级;第三个选项把「VAE 会压缩」和「VAE 在时间维上也压缩」混成了一件事——2D VAE 只在空间维上压,时间维完全没动,121 帧就是 121 份互相独立的空间压缩叠在一起。
最省事:2D VAE 逐帧编码,时间维不压
435,600 个 token,是同分辨率 1024² 图片的 106 倍。每一帧各自独立压缩,帧与帧之间的相似性一点没被利用上。
最狠:时空都往下压一层
同一段视频只要 14,080 个 token,是前者的三十分之一,但仍然是那张 1024² 图片的 3.4 倍——视频比图片信息量大,这件事无论怎么压都压不没。
差别在:要不要在时间维和空间维都再往下压一层,下一章要讲这一层具体是怎么压的。
把六种真实存在的压缩方案摆在一起,才看得清这中间三十倍的差距具体分布在哪里。这里用到的公式是 BRIEF 给定的口径,1+(F−1)/DT 是时间方向压缩之后剩下的 latent 帧数,那个「+1」来自下一章要讲的一个细节——3D VAE 对第一帧的处理方式和其余帧不一样。这里先照着用,不影响这一节要算的账。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| F | 视频总帧数,像素空间里、编码之前的帧数 | 原始素材有多长 |
| DT、DH、DW | 3D VAE 在时间、高、宽三个方向上的压缩倍数 | VAE 这一层压掉多少 |
| PH、PW | patch 化在高、宽方向上再切掉的倍数 | 第11章要讲的那一层再压多少 |
| 1+(F−1)/DT | 时间方向压缩之后剩下的 latent 帧数 | 还剩几帧要喂给 transformer |
| 方案 | 有效压缩 | token 数 | 相对最省的倍数 |
|---|---|---|---|
| SVD 式 2D VAE(1×8×8)+ patch 1×2×2 | 1×16×16 | 435,600 | 30.9× |
| CogVideoX / HunyuanVideo / Wan2.1(4×8×8 + patch 1×2×2) | 4×16×16 | 111,600 | 7.9× |
| Mochi 1(6×8×8 + patch 1×2×2) | 6×16×16 | 75,600 | 5.4× |
| Wan2.2 TI2V-5B(4×16×16 + patch 1×2×2,按官方 1280×704) | 4×32×32 | 27,280 | 1.9× |
| HunyuanVideo 1.5(4×16×16 + patch 1×1×1) | 4×16×16 | 111,600 | 7.9× |
| LTX-Video(32×32×8,无 patch 化) | 8×32×32 | 14,080 | 1.0× |
表 1 里 LTX-Video 那一行用的有效压缩是 8×32×32,token 数写的是 14,080。如果直接套用 1280×720 代入公式,H 方向 720÷32 除不尽。用 704 代替 720(32 的整数倍,是 LTX 实际会用的编码分辨率)重新算一遍,能不能对上 14,080?
变式:Wan2.2 TI2V-5B 那一行标称分辨率写的是官方 1280×704,不是 1280×720。用 704 和用 720 各算一遍那一行的 token 数,差多少?
这张表本身只是账本,真正要紧的是这些 token 最终要喂给谁——第 7 章那个花在注意力上的二次项。
第7章推出过一条交叉点公式:当 token 数 T 超过 (2+r)·d(r 是 MLP 展开倍数,取 4;d 是隐藏维度)时,注意力矩阵那一项的计算量就会超过线性层那一项。在 d=3072(FLUX 那个规模)时,这个门槛算出来是 18,432 个 token。把表 1 的六个方案分别和这个门槛比一比,谁已经过线,谁还没有?
变式:如果换成 d=1152(DiT-XL 那个规模),门槛是 6,912 个 token。这时候六个方案里还有谁能留在门槛以下?
答辩:如果我是审稿人
Video Diffusion Models 说分解时空注意力「is known to be a good choice… for its computational efficiency」,这在 16 帧、64×64 这个规模上当然说得通。但两年后 CogVideoX 自己做的消融显示,把全时空注意力换成 2D+1D 分解注意力后,FVD 明显变差,模型越大越容易在训练中失稳,作者干脆判定这种分解结构不适合视频生成任务。VDM 当年的判断错了吗?
参考防守(先自己组织语言再看)
没有错,错的是把一个规模下成立的判断当成放之四海而皆准的结论去用。VDM 那句话是在 16 帧、64×64 这个很小的规模下说的,当时全时空联合注意力在算力上完全负担不起,分解省下来的计算量是「能不能训得起模型」的问题,不是锦上添花。CogVideoX 面对的是更高分辨率、更大模型、更长视频,算力预算完全是另一个量级,能负担得起全注意力的代价,而这时候分解注意力的坏处开始压过它省下来的那点计算量——CogVideoX 的原话是分解注意力需要「extensive implicit transmission of visual information」,视觉信息只能靠隐式地经过别的背景 patch 中转,才能从一帧传到另一帧,大幅运动物体的一致性因此很难维持。两篇论文其实在回答同一个问题的不同版本:在当前的算力预算下,省下来的计算量能不能换回将来靠更多算力和更多数据也补不回来的那部分质量。规模变了,这道题的答案跟着变——这也是第12章稀疏注意力要面对的同一类张力,怎么在不放弃质量的前提下把联合注意力的计算量降下来,而不是简单粗暴地砍成两个独立的小问题。
对你而言未知分解时空注意力具体在哪种运动上开始失真,有没有一个能提前算出来的判据
CogVideoX 说分解注意力在大幅度运动的物体上容易失去一致性,STA 的论文也提到注意力在空间上其实高度局部——只占 15.52% 的 token 空间的一个局部窗口就能占到 70% 的注意力分数。这两件事放在一起会引出一个问题:能不能提前算出来,对于给定的运动幅度,分解注意力理论上能不能捕捉到,而不需要真的训一个模型跑出来再看。本站的材料里没有给出这样一个判据,只有「效果不好」这个观察结果。
先做这一步:回到第7章的注意力实验室,把输入换成一个人为构造的匀速平移信号(比如一个亮点每帧向右挪 k 个格子),分别用联合注意力和分解注意力重建这个信号,看 k 多大的时候分解版本开始丢失信息;再回去读 STA 论文§3.1那段关于局部性的实验设置,看它是怎么定义局部窗口的。
这一层要加什么:数据升成带时间轴的序列
为什么现在才加它:前八级处理的数据从来只有一帧,不管是那个一维分布还是后来的图像,时间轴只属于加噪去噪过程自己,画面本身是静止的。这一层要让画面本身变成一个长度为 T 的序列,而且要给序列里的每一帧一个能相互感知的位置。
难点:sample_naive_video 不会报错,也不会产出任何异常数值——它的输出在单帧上看完全正常,问题只出在帧与帧之间的关系上,而这种问题不会出现在任何单帧的质量分数里,必须专门去量帧间的统计量才能发现。这也是为什么闪烁这类问题在早期很容易被漏掉:每一帧单独拿出来评审,看不出任何毛病。
自己验:对 sample_naive_video 的输出,取任意两个相邻帧对应位置的像素值,跑几百次采样算它们的皮尔逊相关系数,应该落在 0 附近(绝对值小于 0.05);换成一个真的加了时间注意力的实现,同样的统计量应该明显偏离 0,具体量级取决于运动的剧烈程度,但不会再贴着 0。
读的时候要小心
关于本章数字的来路
本章的六方案 token 对照表是按公式直接算出来的,旁边的实验室按一下就能复现,属于可复算的一类。但 VDM 16 帧实验的具体指标、CogVideoX Table 8 的耗时对比、STA 论文里 84.7% 和 15.52% 这两个数,都来自各论文作者自己跑出来的实验,没有第三方复现过。
留给你的问题
如果给你两个预算——一是能训练的总帧数固定在 16 帧不能再多,二是能训练的总 token 数固定在某个上限——这一章讲的三种绕开限制的方式(时间注意力、自回归外推、缩短单帧 token 数)分别在解决哪一个预算里的哪一部分?有没有哪种方式同时缓解了两个预算?
不看材料,写下这一章的三堵墙
第一堵:逐帧独立采样,第二帧的猫和第一帧不是同一只猫,因为独立采样本身就是扩散模型多样性的来源,原样搬到视频上就成了缺陷。第二堵:16 帧封顶,朴素的拼接外推法单独看每段都行,接起来会跳变,因为它没有让新的一段真的去贴合已经生成的那一段。第三堵:不做时间压缩的话,一段五秒视频的 token 数是同分辨率单张图片的一百多倍,早就冲过了第7章那条注意力二次项接管的门槛。
第10章 在时间上也压
开篇故事第 10 集 · 通道堆到顶,画质却缩回了更早那一档这模型也太真实了
训练队列监控的屏幕在凌晨忽然跳出一行新任务,配置栏写着压缩率十六乘十六乘八,比论坛投票帖里挂着的选项还要再狠一档——这是回声复现的第七十一天,投票帖明明还贴在顶部,离截止还有一个钟头,显存刺客的工号却已经排在了队列最前面。沈砚盯着那行任务愣了几秒,机柜的风扇声在这个点格外清楚,他一时想不通对方为什么等不及。
半小时前,显存刺客在论坛发起投票时说得斩钉截铁:显存和电费是回声这一年最大的开销,视频这条线不比图像,格子数一涨,租卡成本立刻翻好几倍,压缩率能压多狠就该压多狠,通道数不够就一路往上堆,反正堆多少显存都比省下来的存储划算。他这一票投给了最激进的那一档,理由是有 CogVideoX 自己的消融表撑腰——那张表里通道数确实是跟着涨的。
沈砚把这一档的参数丢进《潜空》,想在投票结束前先跑一遍看看结果,没想到面板弹出来的数字和他猜的完全不一样。
系统提示压缩率设定为十六乘十六乘八,潜通道数一百二十八,重建峰值信噪比 27.9,低于压缩率八乘八乘四、通道三十二那一组的 30.5,本次训练收敛未达预期。
“不对啊。”显存刺客凑过来看了一眼屏幕,声音先小了半截,“通道给到一百二十八,是那一档三十二的四倍,怎么反而更差了。”沈砚把两组参数并排摆在桌上,没有立刻回答,先把 CogVideoX 那张表从头到尾又看了一遍,确认自己没有看错哪一列。
靳工端着杯子路过,看了一眼两组数字,说了一句:“论文原话是这一档收敛极其困难,通道给再多也补不回来,压缩率不是越狠越好,是一条有拐点的曲线,你们这一票押在了拐点后面。”他没有多解释,转身回了自己那张桌子。
投票截止前最后吵一轮,压缩率到底该投给狠的还是稳的
1L沈砚显存刺客这一票的时间戳比投票公告还早六分钟,队列里已经先挂上了十六乘十六乘八那一档的任务,这个算不算没等大家投完就自己先定了?
2L复现之鬼靳工说的那句「收敛极其困难」,原始那几行数字谁贴出来,我这边想自己核一遍,别人跑得出来的结果才真的作数。
3L显存刺客数字我贴在这——通道给到128、压缩率16×16×8,峰值信噪比27.9;稳健那一档压缩率8×8×4、通道32,峰值信噪比30.5,是我算漏了,这一票我改投稳健档。
沈砚把队列里那个抢跑的任务撤下来,重新排到了稳健那一档后面,这一撤,这周留给视频线的机器时长已经去掉了小半天,显存刺客偷跑的那几个小时也全都打了水漂。他正准备把这件事写进这周的进度贴,论坛角落却弹出一条复现之鬼的私信,问的是视频那条线的数据到底谁在盯着标注和清洗,这条私信一直挂在那,还没人回。
系统结算 · 本章考点
- 稳健那一档(压缩率8×8×4,通道32)峰值信噪比 30.5;压到最狠的16×16×8那一档,通道给到128——是前者的4倍,也是基线通道的32倍——峰值信噪比反而跌到 27.9
- 论文原话是这一档收敛极其困难,压缩率不是越狠越好,是一条有拐点的曲线,不是通道数能无限补回来的单调曲线
- 换成3D结构这一步本身几乎是白拿的:闪烁指标从基线的 93.2 降到 86.3,清晰度同时从 28.4 涨到 28.7,不是拿清晰度换连贯
- 同压缩率下把通道数从4提到16,重建质量跟着涨:OpenSora-1.2是 30.15,CogVideoX-1.5是 33.22,HunyuanVideo做到 35.39
- HunyuanVideo从1.0升级到1.5,空间压缩从8倍提到16倍、patch从2降到1,token数还是 111,600,和1.0版完全一样
把一段视频逐帧摆开来看,第 30 帧和第 31 帧之间,除了猫的爪子往前挪了一点,地板、窗台、光线几乎一模一样。第9章那套办法——2D VAE 逐帧编码——对这份「几乎一样」视而不见,一百二十帧就老老实实编码一百二十次,把同一份信息重复存了一百二十遍。这一章要把这份重复利用起来。
学完这一章你应该能做到
- 说清 2D VAE 逐帧编码浪费在哪,以及「相邻帧相似」这件事具体能怎么被利用
- 讲清楚 3D 因果卷积是什么、为什么必须是因果的,以及它怎么让一张图片成为视频的特例
- 逐符号读懂 CogVideoX 的 3D VAE 消融表,说清压缩率、通道数、重建质量三者怎么互相牵制
- 认出「压缩率提高了」和「token 数变少了」不是一回事,能拆穿容易被误读的宣传点
这一层要给第9级那串带时间轴的序列,加上一层专门在时间方向上压缩的 VAE。上一级的六种方案里,不压时间维的那一种要付出 106 倍于单张图片的 token 代价;这一层要造出把这份代价压到零头的机器。
10.1 相邻帧几乎一样,2D VAE 在浪费什么?
第6章说空间 VAE 能把一张图压缩掉九成人眼看不出的冗余。如果把同一个 2D VAE 直接用在视频的每一帧上,120 帧各编码一次,这套压缩会不会漏掉什么冗余没有利用上?
答完再看
第二个选项说中了要害。第6章证明的是空间维上一张图内部的冗余;这里是另一层冗余,同一个空间位置在相邻帧之间也长得像。2D VAE 的视野从头到尾只有一帧,它没有能看见隔壁帧的通道,自然也没办法利用这份冗余。第三个选项混淆了两件事:减少帧数会丢东西,丢的是运动的采样密度;压缩时间冗余不丢东西,丢的只是重复存储。
这份差异有多大,直接决定了时间维上能压多狠而不丢东西。慢速运动的场景,相邻帧的差异可能只在几个像素上;快速运动的场景,相邻帧的差异会大得多——这也是本章末尾建造台阶要让你亲手量一遍的事。
2D VAE 逐帧编码:120 次互相独立
每一帧各自压缩,存储量是单帧的 120 倍,相邻帧之间的相似性完全没被利用上,压缩器的感受野从来没跨出过一帧。
如果压缩器能同时看见相邻几帧
只需要存一份「共同的部分」,再加上每帧和这份共同部分的差异。差异通常远小于原始帧本身,尤其是在慢速运动的时候。
差别在:要不要让压缩器的感受野跨越时间轴,而不只是停留在一帧内部。
假设一段视频里连续两帧几乎完全相同(比如静止的背景),用 2D VAE 逐帧编码,这两帧各自的潜空间表示会不会也几乎相同?如果几乎相同,这说明了什么?
变式:如果两帧之间发生了剧烈的镜头切换(完全不同的场景),这套「共享大部分表示」的想法还成立吗?
10.2 卷积也能跨时间做,但为什么必须是因果的?
如果把时间也当成一根卷积轴,最省事的做法是照搬 2D 卷积的对称 padding——每一帧前后都垫上一样多的邻居帧。你觉得这样做会在哪里出问题?
答完再看
第二个选项说中了要害。对称 padding 要求每个输出位置左右都有邻居,视频开头结尾这两处天然缺邻居,只能编造,这就让第一帧和中间帧走的是两套不同的有效计算规则。更麻烦的是单张图片——它连「下一帧」这个概念都没有,如果 VAE 的默认接口要求对称的时间邻居,一张图片根本没法被这套 VAE 处理,只能另外造一个专门的 2D VAE,图片和视频从此变成两套压缩器。
对称卷积:需要未来帧当邻居
视频开头结尾要编造不存在的邻居,单张图片连「下一帧」都没有,图片和视频没法共用同一个 VAE。
因果卷积:padding 全垫在开头
第 t 帧的输出只看自己和更早的帧,单张图片就是「没有更早帧」的极端情况,天然是视频的一个特例,可以直接复用同一套权重。
差别在:因果卷积让一张图片不再是需要另造模型才能处理的特殊情况,而是视频这个更一般情形里最短的那一种。
CogVideoX 和 Wan 都选了这条路。做法是把所有 padding 都垫在时间轴的开头,不垫在结尾——CogVideoX 的原话是「places all the paddings at the beginning of the convolution space… This ensures that future information does not influence the present or past predictions」。第 t 帧的输出只依赖它自己和更早的帧,从不依赖它之后的帧。
Wan 把这套设计写成了一个具体的例子(§4.1.1):输入视频形状是 (1+T)×H×W×3,编码之后变成 (1+T/4)×H/8×W/8——开头那个「1」被单独处理,只做空间压缩,不参与时间方向的压缩,原文说这是跟随 MagViT-v2、专门用来更好地处理图像数据的做法。这就是为什么 latent 帧数不是简单的 F/DT,而是接下来这条式子:
因果卷积把所有 padding 都垫在时间轴的开头,不垫在结尾。结合上一段对称卷积的麻烦,这样做具体避免了什么?
变式:如果反过来把 padding 全垫在结尾,视频的开头会不会遇到同样的麻烦?
| 符号 | 是什么 | 直觉 |
|---|---|---|
| 开头那个 1 | 第一帧单独占用的 latent 位置,只经过空间压缩 | 第一帧没有过去,只能单独处理 |
| F−1 | 除去第一帧之后,真正参与时间压缩的帧数 | 剩下的部分才轮到时间压缩上场 |
| DT | 时间压缩倍数,只作用在这 F−1 帧上 | 每 DT 帧压成一个 latent 帧 |
拿第9章算过的例子验一下:F=121,DT=4 时,1+(121−1)/4=1+30=31,和第9章表1第二行的 latent 帧数正好吻合。
因果版本的 latent 帧数公式是 1+(F−1)/DT。假设有另一个不特殊对待第一帧的朴素版本,直接用 F/DT。构造一个 DT 的取值,让这两个公式对任意 F 都算出同一个数,并说明为什么只有这一种情况成立。
变式:F=17(CogVideoX 训练 3D VAE 时用的短片段长度),DT=4 时,因果公式和朴素公式 F/DT 分别给出的 latent 帧数差多少?这个差距占 F 本身的比例是多少?
10.3 压缩率、通道数、画质,为什么不能三个都要?
把 SDXL 那样的 2D VAE 直接搬去压视频,不做任何时间方向的处理,再对比一个真正做了时间压缩的 3D VAE。你猜画面的闪烁感——相邻帧之间的跳变——会有什么差别?
答完再看
CogVideoX 的消融表给出了确切数字,闪烁指标(相邻帧 L1 差异)从基线的 93.2 降到 86.3,论文原话是「After using 3D structures, the reconstructed video shows almost no more jitter」。第三个选项的担心不是没有道理——混合多帧信息确实可能带来模糊,但表里 PSNR 同时从 28.4 涨到 28.7,说明这一步换来的是双赢,不是拿清晰度换连贯性。
自己设计一遍:怎么用一张表证明「压缩率」和「通道数」是两件独立的事
手上有一个 2D 基线 VAE。想证明「换成 3D 结构」这一步本身有没有用,第一步该怎么改、其余条件怎么处理?
想好了再看
只改一件事:把 2D 结构换成 3D 结构,压缩率和通道数暂时不动。这样闪烁指标的变化就能干净地归因于「结构」这一个变量——这正是表1基线到 A 这一步在做的事。
现在想证明「通道数」这个变量本身能不能换来质量提升。压缩率要不要跟着通道数一起变?
想好了再看
不能变——压缩率必须锁死在同一个值,只让通道数一路变化,这样质量的变化才能单独归因于通道数,不会和压缩率的变化混在一起。这正是表1 A、B、C 三行在做的事。
最后想证明「压缩率」本身能不能无限往狠了推。这一步该怎么设计,才不会被「通道数不够」这个因素干扰了结论?
想好了再看
把压缩率推到更激进的档位时,同时给足通道数——这样如果质量还是下降,就能排除「只是通道数不够」这个解释,把责任落实到压缩率本身。这正是表1 E 档在做的事:通道数给到全表最高,质量却是全表最低。
| 变体 | 压缩率 | latent 通道 | Flickering↓ | PSNR↑ |
|---|---|---|---|---|
| 基线(SDXL 2D VAE) | 8×8×1 | 4 | 93.2 | 28.4 |
| A | 8×8×4 | 8 | 87.6 | 27.2 |
| B(实际采用) | 8×8×4 | 16 | 86.3 | 28.7 |
| C | 8×8×4 | 32 | 87.7 | 30.5 |
| D | 8×8×8 | 32 | 87.8 | 29.0 |
| E | 16×16×8 | 128 | 87.3 | 27.9 |
这张表同时说明三件事。第一件是刚刚揭晓的:从 2D 换成 3D,闪烁从 93.2 降到 86.3,这一步几乎是白拿的。第二件看 A 到 C 这三行——压缩率固定在 8×8×4 不变,只把通道数从 8 一路加到 32,PSNR 跟着从 27.2 涨到 30.5,通道数能实打实换来质量,这和第6章 SD3 涨通道数的逻辑是同一件事。
表1的A、B、C三行,压缩率都固定在8×8×4,只有通道数从8变到16再到32。这三行放在一起,最能说明什么?
变式:如果只加通道数、压缩率完全不变,PSNR 会不会一直涨下去,有没有上限?
第三件藏在 E 这一行,也是最容易被忽略的一件。把压缩率进一步推到 16×16×8,即便把通道数一口气加到 128——是基线的 32 倍——PSNR 不但没有继续涨,反而跌到 27.9,比 C 档(通道 32,PSNR 30.5)还要低。论文原话说得很直接:「when spatial-temporal compression is too aggressive (16×16×8), even if the channel dimensions are correspondingly increased, the convergence of the model also becomes extremely difficult」。压缩不是越狠越好,这是一个有拐点的权衡。
C 档:压缩适度,通道给够
压缩率 8×8×4,通道 32,PSNR 30.5——目前六档里最高的一档,说明只要压缩率留有余地,加通道数就能稳稳换来质量。
E 档:压缩和通道一起往狠了推
压缩率 16×16×8,通道 128(C 档的 4 倍),PSNR 反而跌到 27.9。通道数给得更多,换来的却是更差的重建。
差别在:C 档只在通道这一个维度上加码,E 档同时把时间和空间的压缩都推向更激进的一档,两件事叠在一起,通道数补不回来。
第6章说通道数不够会漏在编码这一步,通道数涨太快又会漏在学习这一步,两种失效都会让最终图像变差,但病根长在流水线的不同环节。CogVideoX 的 E 档,压缩率 16×16×8,通道数已经涨到 128(基线的 32 倍),PSNR 却只有 27.9,比 C 档(通道 32,PSNR 30.5)还低。用第6章那套框架判断,E 档的问题更像哪一种失效?
变式:如果 E 档的通道数不是 128,而是像 D 档一样只给 32,PSNR 会不会比现在的 27.9 更低?给出你的推理依据,而不只是猜。
表 1 的六行里,哪两行之间的对比控制变量最干净——只有一个变量在变,其余完全一样?
变式:如果只能从表里选两行来说服别人「通道数能换质量」,你会选哪两行?
10.4 各家把时间压缩定在几倍,通道数又换来了什么?
HunyuanVideo 论文把自己的 VAE 拿去和另外几家的开源 VAE 对比,控制变量做得更干净——下采样因子全部锁定在 4×8×8,只看通道数不同带来的差别。
| 模型 | 潜通道 | ImageNet 256² PSNR | MCL-JCV PSNR |
|---|---|---|---|
| OpenSora-1.2 | 4 | 28.11 | 30.15 |
| CogVideoX-1.5 | 16 | 31.73 | 33.22 |
| Cosmos-VAE | 16 | 30.07 | 32.76 |
| HunyuanVideo | 16 | 33.14 | 35.39 |
表2里,OpenSora-1.2 和 CogVideoX-1.5 的压缩率都是 4×8×8,通道数分别是 4 和 16。两者在 MCL-JCV 这一列的 PSNR 差多少 dB?
变式:HunyuanVideo 自己的压缩率也是 4×8×8、通道 16,MCL-JCV 上的 PSNR 是 35.39,比 CogVideoX-1.5 的 33.22 还高出 2.17dB。既然通道数和压缩率都一样,这个差距会是从哪来的?
把各家实际用在产品里的取值摆在一起,能看出一条清楚的趋势:时间压缩几乎清一色定在 4 倍,例外是 Mochi 1 的 6 倍和 LTX-Video 的 8 倍;空间压缩正在从 8 倍这个老标准,往 16 倍甚至 32 倍迁移。
| 模型 | 空间压缩 | 时间压缩 |
|---|---|---|
| CogVideoX / HunyuanVideo 1.0 / Wan2.1 | 8× | 4× |
| Mochi 1 | 8× | 6× |
| LTX-Video | 32× | 8× |
| Wan2.2 TI2V-5B / HunyuanVideo 1.5 | 16× | 4× |
顺带说一句能不能拿来用的问题:这几家的开源协议并不统一。CogVideoX 的 2B 版是 Apache 2.0,5B 版换成了自家的 CogVideoX License;HunyuanVideo 用腾讯混元社区许可,明确不适用于欧盟、英国、韩国;Wan2.1/2.2 全系是 Apache 2.0,不带额外限制。同一张压缩率表格背后,能不能商用、能不能在哪个地区用,是完全独立的另一个维度。
表3显示空间压缩从8倍涨到16倍、32倍的模型不少,但时间压缩绝大多数停在4倍。结合10.1节「慢速运动冗余大、快速运动冗余小」这条道理,说说为什么时间压缩相对空间压缩更保守。
变式:如果一个模型专门用于生成「运动很少」的场景(比如静态产品展示视频),它的时间压缩倍数理论上能不能比4倍更高?
10.5 空间压缩翻倍、patch 减半,token 数为什么没变?
HunyuanVideo 从 1.0 升级到 1.5,把 VAE 的空间压缩从 8 倍提到 16 倍,同时把 patch 从 2 降到 1。如果只看空间压缩这一项翻倍了,你猜 token 数会变成原来的多少?
答完再看
第二个选项是对的。第9章的公式里,H 方向的分母是 DH·PH——1.0 版是 8×2=16,1.5 版是 16×1=16,两个乘积完全相等,分母没变,token 数自然不变。这也是这一节要拆穿的地方:只盯着「压缩倍数」这一个数字看,很容易漏掉 patch 大小同时在发生的变化。
HunyuanVideo 1.0:压缩 8×,patch 2
DH·PH = 8×2 = 16,同一段视频的 token 数是 111,600。
HunyuanVideo 1.5:压缩 16×,patch 1
DH·PH = 16×1 = 16,token 数还是 111,600,和 1.0 版完全相同。
差别在:两个因子的分工不同,乘积却完全相等——这也是为什么单看「压缩率提高了」这一句话容易产生误导。
第9章表1里,CogVideoX/HunyuanVideo/Wan2.1 那一行和 HunyuanVideo 1.5 那一行,token 数都是 111,600,有效压缩写的也都是 4×16×16,完全一样。既然 token 数完全没变,HunyuanVideo 1.5 号称的推理提速,只可能来自哪里?
变式:如果 HunyuanVideo 1.5 把 patch 保持在 2(不降到 1),同时把压缩率提到 16 倍,token 数会变成 1.0 版的多少?这样做在 token 数上是划算还是不划算?
答辩:如果我是审稿人
CogVideoX 自己说 16×16×8 这种压缩率让收敛变得极其困难,可现在的趋势偏偏是把空间压缩往 16 倍甚至 32 倍推,LTX-Video 用的还是 32×32×8,压缩率比 CogVideoX 那个失败的 E 档还要狠。这两件事不矛盾吗?
参考防守(先自己组织语言再看)
不矛盾,但需要把 E 档到底做了什么看清楚。E 档不只是把空间压缩推到 16 倍,它同时把时间压缩也推到 8 倍——两个轴一起往狠了压,而且是在 CogVideoX 那次训练用的模型规模和数据量上试的。后来把空间压缩推到 16 倍甚至 32 倍的那些方案,表3里能看到,大多数把时间压缩摁在 4 倍不动,只在空间这一个轴上加码。这不代表空间压缩本身没有上限,只是说明把两个轴同时往狠了推,风险会叠加,而 CogVideoX 的 E 档恰好撞上了这种叠加。这也提示一件事:论文里「压缩率 X 会导致收敛困难」这类结论,很难脱离「具体是哪几个轴一起变、模型多大、数据多少」这些条件单独成立,材料本身没有给出一个能把这几个变量拆开分别测试的对照实验。
对你而言未知激进压缩具体在哪个环节卡住收敛,材料里没给出机制
CogVideoX 说 E 档收敛极其困难,但没有说是哪个环节出的问题——是编码器把信息丢得太狠导致重建本身就有偏差,还是扩散模型在这么高维、这么抽象的潜空间里学不动分布,又或者两者叠加。本章读到的几篇论文里,没有一篇专门拆开这几种可能性做对照实验。
先做这一步:回到本章的3D VAE实验室,固定重建质量(用 PSNR 当代理指标)不变,只改变时间和空间压缩率的搭配比例,看哪种搭配方式在相同总压缩倍数下重建质量掉得最少;再去读 HunyuanVideo 1.5 的论文,看它把空间压缩推到 16 倍时,有没有专门讨论过训练稳定性的部分——Wan2.2 TI2V-5B 至今没有独立论文,只能查它的官方模型卡和技术博客。
这一层要加什么:时间维压缩
为什么现在才加它:上一层给序列加了时间轴,但沿这条轴的每一步仍然是满分辨率地存下来的,和第9级里「不压时间」的那一种方案一样浪费。这一层要在时间方向上也做一次压缩,把相邻帧共享的那部分只存一份。
难点:平均池化不是 CogVideoX、Wan 真正用的机制——它们用的是可训练的 3D 卷积,能学会怎么合并信息;这里用平均池化是为了不依赖任何训练,就能亲手量出「时间维上到底能压到什么程度会开始丢东西」这个问题的答案。真正的 3D VAE 比这聪明得多,但要解决的是同一个问题。
自己验:构造一段慢速运动信号(一个点的位置每帧只挪 0.1 个单位)和一段快速运动信号(每帧挪 2 个单位),各自按 4 帧一组做 temporal_downsample 再 temporal_upsample,慢速运动的重建误差应该明显小于快速运动的,至少小一个数量级;把分组从 4 改成 8,快速运动那组的重建误差应该继续变大,出现肉眼可见的拖影——同一个 latent 帧被复制给 8 个位置,运动越快,复制出来的这 8 帧和真实位置的偏差就越大。
读的时候要小心
关于本章数字的来路
本章 CogVideoX Table 1 的六行数据、HunyuanVideo Table 1 的重建质量对比,都是论文作者自己训练评测出来的结果,没有第三方复现;各家实际压缩率取值的整理来自公开的模型信息,不是本站训练验证的。
留给你的问题
如果给你一个新的压缩预算,要求时间压缩倍数乘以空间压缩倍数的平方不能超过 CogVideoX 那个失败的 E 档(16×16×8,也就是 4×256=1024),但可以自由分配在三个轴上,你会怎么分配,为什么几乎没人把三个轴压得同样狠?
不看材料,写下这一章的三堵墙
第一堵:2D VAE 逐帧编码,把相邻帧共享的信息重复存了一百二十遍。第二堵:对称卷积让视频开头结尾要编造不存在的邻居帧,单张图片更是没法用同一套 VAE 处理,因果卷积把 padding 全部垫在开头解决了这件事,顺带生出了 latent 帧数里那个「+1」。第三堵:压缩率和通道数不是可以无限对刷的两个旋钮,CogVideoX 的 E 档证明总有一个点,信息量超过了当前模型架构能消化的上限,通道数补不回来。
第11章 时空 patch
开篇故事第 11 集 · 没人喊卡,预览里那个镜头自己切了这模型也太真实了
回声的开源仓库里,一条几个小时前提的 issue 被顶到了列表最上面,配的是上周放出去的一段预览视频截图——网友在第七秒那一帧停了一下,留言问这个镜头是不是剪辑没剪干净,评论区底下已经跟了六条附和。这是回声复现的第八十天,沈砚把视频翻到那一秒,画面确实毫无来由地从一间屋子跳到了另一个完全不相关的场景,谁都没有在提示词里写过要切镜。
十几天前定下视频那条线的数据方案时,复现之鬼在论坛上说得很干脆:他们扒下来的原始片段已经有好几十万条,镜头切乱的、画面几乎不动的,占的比例撑死也就那么点,量一大自然会被稀释掉,与其花时间去搭一整套筛选流程,不如先把训练跑起来看效果。那次讨论她这一票占了上风,切镜检测和画面静止过滤这两道关卡就先放在了一边,没有人再提起。
沈砚把最近几批训练用的原始片段挑出来,就着一碗已经坨了的泡面,一条一条看了几十条,发现镜头切换和长时间静止的画面确实不算少,而且这类切换往往没有任何过渡,画面说变就变。他把这个发现和那条 issue 一起发到了论坛上,问是不是这两件事有关系。
预览里那个无缘无故的切镜,是不是原始数据里那些没剪干净的镜头闹的
1L炼丹废人说句大实话,咱们现在扒回来的片段已经有好几十万条了,几个镜头切乱的画面能占多少,真被平均掉也不奇怪吧?
2L复现之鬼上次那一票是我投的,我当时就是这么想的,觉得量大了自然会把这些坏样本稀释掉,现在看来这个想法可能站不住。
3L显存刺客楼上两位算过没有,镜头切换这种坏样本在原始片段里是不是本来就不是随机出现的,是不是从一开始就系统性地偏多?
4L沈砚不对,稀释说的是随机噪声才会被平均掉,可切镜和画面不动是同一类错误在反复出现,模型会把这类错误当成规律学进去,量再大也稀释不了系统性的偏差。
沈砚把这条思路发给靳工确认,靳工只回了一句:“随机噪声才能被平均掉,系统性偏的东西数据量再大也学不淡,这套账 SVD 那篇论文早就摊开过了,你们去翻它的筛选流程。”沈砚顺着这句话把 SVD 的四道关卡整理了出来:先按不同帧率检测切镜,把长视频切成不含镜头切换的短片段;再以每秒 2 帧算光流,剔除画面几乎不动的静止片段;然后用 CLIP 给首帧、中间帧、尾帧各打一个美学分;最后用文字检测器把画面里文字面积超过 7% 的片段也筛掉。原始的 5.8 亿条片段,正是靠这四关一路筛下来,才成了论文里真正拿去训练的那个子集。
复现之鬼把两道被她当初挡下的关卡认领了回来,说这条 issue 也由她去回复;沈砚合上笔记本,把这四道关卡记进了下一步要做的事项,视频这条线真正的大规模训练,还没有正式排上日期。
系统结算 · 本章考点
- 复现之鬼原以为原始片段数量够大,镜头切乱、画面静止这类坏样本会被数量本身稀释掉,实际上系统性的偏差不是随机噪声,数据量再大也学不淡
- SVD 的筛选流水线按顺序过四道关:切镜检测、光流过滤、CLIP 美学分、文字检测,任何一关单独存在都补不上另外三关的漏洞
- 光流过滤以每秒 2 帧的频率计算画面变化幅度,专门用来剔除「画面几乎不动」的静止片段
- 文字检测把画面里的文字标出包围盒,文字覆盖面积超过画面 7% 的片段会被剔除
- 原始的 5.8 亿 条视频片段,正是经过这四道关卡筛出了真正拿去训练的子集
第9章算过一段五秒视频要用掉十几万甚至四十多万个 token,第10章把这串数字里时间维那部分压掉了一大截。可这些 token 到底是怎么从一段视频里「长」出来的,两章都还没细说。压缩之后的视频,本质上是一个三维的方块——宽、高、时间,三个方向都有厚度。这一章要回答一个具体到不能再具体的问题:这个方块最后是怎么被切成一颗颗小块、拍平成一条 transformer 吃得下的序列,又是怎么被重新告诉每一块原来该摆在哪的。
学完这一章你应该能做到
- 说清 Sora 报告里「spacetime patches」具体指什么,以及它为什么能让图片和视频共用同一套输入格式
- 讲清楚原生分辨率、时长、宽高比训练是 patch 化直接带来的好处,而不是另外加的功能
- 用 HunyuanVideo 论文 Table 2 的数字,说清 3D 位置编码怎么把一个 patch 的三个坐标分别编码进去,并认出哪些数字是论文自己给的、哪些是旁证推断的
- 说清视频文字标注本身也是个不断升级的生成问题,讲出 SVD 那套数据筛选流水线具体卡住了哪些坏数据
这一层要把第10级那个三维的 latent 方块——时间、高、宽都已经被压缩过——切成一颗颗小方块,拍平成一条 token 序列,再给每一块一个能定位它原来在三维空间里哪个位置的编号。拍平这个动作本身会把方块原来的三维排列关系抹掉,不额外补上位置信息,模型就无从得知哪一块跟哪一块在时间上相邻、哪一块跟哪一块在空间上相邻。
11.1 一段视频,为什么可以看成一摞时空方块?
第10章那个因果 3D VAE,把一段视频压缩成一个形状是(latent 帧数×H×W×通道数)的方块。第7章处理图片时,是把 2D 的 H×W 网格切成一个个小 patch,拍平成一条序列喂给 transformer。现在这个方块比图片多了一个时间维度,你觉得最直接的处理方式是什么?
答完再看
第二个选项是 Sora 报告和几乎所有后来的视频模型共同的做法。第一个选项不是不能用,但它把时间和空间硬性分开处理,等于在 patch 这一步就已经预设了「时间和空间不一样重要」;第二个选项让 patch 本身横跨三个轴,谁也不比谁特殊。第三个选项会让 token 数直接等于方块的格子数,第9章算过这个数字动辄十几万到四十几万,不切 patch 只会让它更夸张。
Sora 报告把这件事说得很直接:先训练一个「video compression network」,把视频压缩到时间空间都降维的隐空间——这正是第10章因果 3D VAE 在做的事,报告原话是「Sora is trained on and subsequently generates videos within this compressed latent space」。压缩完之后,「Given a compressed input video, we extract a sequence of spacetime patches which act as transformer tokens」——这串 patch 专门起了一个新名字,既不叫「帧」也不叫「像素块」,因为它们从一开始就不是纯空间的,也不是纯时间的。
自己组装一遍:从一段视频到一条 token 序列
原始视频是像素级别的方块。第一步要做什么,为什么不能直接对像素做 patch 化?
想好了再看
第一步是第10章讲的因果 3D VAE 压缩。像素级别的方块太大——第9章算过 121 帧 1280×720 不压缩直接切 patch 会是天文数字,VAE 先把方块在时间和空间上都缩小一圈,之后切 patch 才处理得动。
压缩后的方块还是三维的(时间×高×宽),第7章处理的是二维方块。三维方块要怎么切成一颗颗 patch?
想好了再看
三个轴一起切:每隔一定数量的 latent 帧、一定数量的高、宽格子划一刀,切出的每一个小方块就是一个 patch,拉平之后线性投影成一个 token。这是第7章的 2D patchify 多加一个轴之后的直接推广。
切完之后,这些 patch 要拍平成一条 1D 序列才能喂给 transformer。拍平这个动作本身会丢掉什么?
想好了再看
会丢掉每个 patch 原来在三维方块里的位置——第7章验证过,把拍平之后的序列打乱顺序再喂进去,没有位置编码时模型的输出完全不变,因为自注意力本身不关心谁排第几。视频比图片多出一个轴,丢掉的位置信息也多出一个维度,这正是 11.3 节要补回来的东西。
图片:patch 只切 H×W 两个方向
第7章的 DiT,一张图片的 latent 只有高和宽两个轴,patch 是一个二维小方块,切出多少个 patch 只取决于图片的空间尺寸。
视频:patch 同时切 T×H×W 三个方向
压缩后的 latent 多了一个时间轴,patch 变成一个三维小方块,同一个 patch 里既有空间上相邻的像素,也有时间上相邻的帧,图片只是这套三维方案里 latent 帧数恰好等于 1 的特例。
差别在:视频的 patch 化不是在图片的基础上另外加一步,而是同一套三维逻辑,图片恰好落在这套逻辑的一个退化角落。
这个实验室摆出一段视频原始的三维方块——时间、高、宽都有厚度,再摆出经过压缩和 patch 化之后剩下的方块,两者并排对比,能直接看见这一路压缩把方块削小了多少、最终落地成了多少个 token;真实模型的 patch 数动辄几万个,这里为了看得清楚只挑一小把摆出来。
如果一张单独的图片也要走这套三维 patch 化流程,它对应的是 latent 帧数等于几的情况?这时候 patch 在时间维的厚度取多大才合理?
变式:如果一定要让 patch 的时间厚度等于 2,要付出什么代价才能让单张图片也能被切 patch?
「spacetime patches」这个说法里「time」这一维,切的是原始视频的帧数 F,还是因果 VAE 压缩之后的 latent 帧数 1+(F−1)/DT?这个区分会不会影响 patch 的数量?
变式:如果一个新模型把 patch 的时间厚度改成 2(不再是 1),第9章的 token 公式需要怎么改?
11.2 原生分辨率、时长、宽高比训练,为什么反而更好?
在 patch 化之前,生成模型的通行做法是把每张图片、每段视频都缩放或裁剪到同一个固定尺寸,好凑成一批规整的数据一起训练。Sora 报告反过来做——「Our patch-based representation enables Sora to train on videos and images of variable resolutions, durations and aspect ratios」。
这件事能成立,根子在 patch 化本身不对输入尺寸做任何假设:一段视频不管多长、多宽、多高,切出来的 patch 数量按第9章那条公式自动算出来,不需要模型的输入层预先写死一个固定形状。传统卷积网络的末端通常要接一层和输入尺寸绑死的全连接层,缩放裁剪因此成了不得不做的预处理;patch 化配上天生能吃变长序列的 transformer,第一次让这层预处理变得没有必要。
传统做法:统一缩放或裁剪
把每个训练样本都强制变成同一个尺寸,宽画幅的视频被裁掉两边,竖画幅的被裁掉上下——构图完整的原始素材,变成了构图被硬切过的训练样本。
Sora:原生尺寸训练
保留每段视频原本的分辨率、时长、宽高比,patch 数量随输入尺寸自动变化,训练数据里的构图不需要为了凑统一形状而被裁剪。
差别在:要不要为了训练流程的方便,牺牲每个样本本来的构图。
一个用固定尺寸特征图堆出来的网络,为什么天然要求输入尺寸固定?换成 patch 化加 transformer 之后,这个限制具体是在哪一步被解除的?
变式:如果 token 数量变得特别大,除了「能不能处理」,第7、9章讲过的什么问题会先冒出来?
答辩:如果我是审稿人
Sora 报告说原生分辨率训练「改善了构图」,但报告里没有给出一个对照组——没有一个「同样的模型、同样的数据,只是训练时统一缩放裁剪」的版本拿来对比构图评分。仅凭这一句断言,这个结论能采信吗?
参考防守(先自己组织语言再看)
不能直接当成一个证明过的结论,只能当成作者的观察。Sora 报告本身不开源、没有第三方复现,报告里大多数结论都停留在「我们观察到」这个层级,构图这一条不是特例。但这不代表这个说法没有价值——它至少给出了一个可以被后来者验证的具体主张,而不是一句空泛的「效果更好」。HunyuanVideo 的论文里能看到间接支持:它把 3D RoPE 的动机明确写成「To support multi-resolution, multi-aspect ratio, and varying duration generation」,说明至少有一家独立训练视频模型的团队,认定原生形状训练值得作为设计目标去追求,不只是 Sora 一家的一面之词。真正缺的是一份对照实验的具体数字,这一点材料里确实拿不出来。
11.3 patch 拍平成一串 token 之后,模型怎么知道每一块原来在哪?
第7章验证过:给 2D 图片的 patch 序列打乱顺序,如果没有位置编码,模型的输出会原封不动地跟着换回来,逐个数值都对得上。把同样的实验搬到视频的 3D patch 序列上,如果同样不加任何位置编码,你觉得会发生什么?
答完再看
第二个选项是对的,道理和第7章完全一样——自注意力这一步的计算天生对序列顺序不敏感,不管序列里装的是图片 patch 还是视频 patch。第一个和第三个选项都在暗示「数据本身会替代位置编码」,但 patch 拍平之后就是一串向量,向量本身不带任何「我是第几帧第几行第几列」的标签,这个标签只能靠额外补一份位置编码补上。
RoPE(Rotary Position Embedding,旋转位置编码):不是把位置信息加到 token 向量上,而是按 token 的位置对它的一部分维度做一次旋转操作。两个 token 之间的注意力分数,最终只取决于它们旋转角度的差,也就是只取决于两者位置的相对距离,不取决于各自的绝对位置。第9章提到 Video Diffusion Models 选的是「relative position embeddings」而不是绝对帧号,RoPE 正是把这个选择具体实现出来的一种办法。
绝对位置编码:记住「排第几」
第7章 DiT 用的正弦-余弦位置编码,给每个 patch 的绝对排列位置编一个固定向量,换一段更长的序列,超出训练时见过的最大位置就没有对应的编码可用。
RoPE:只记住「差几个」
两个 token 的注意力分数只取决于它们位置的相对差,不取决于各自的绝对位置。序列多长都能算,不需要为「见过的最大位置」预设一个上限。
差别在:要不要把「绝对排第几」编码进去,还是只关心「彼此差多远」。
第7章的建造台阶验证过:打乱 patch 顺序、不加位置编码时,换回顺序后的输出和没打乱之前逐个数值相同;加了位置编码之后重复实验,换回顺序后的输出会明显不同。把这个实验搬到视频的 3D patch 序列上,需要对第7章的验证方法做什么改动,还是完全不需要改?
变式:如果只给时间轴加位置编码、空间轴不加,打乱空间顺序会不会影响输出?
HunyuanVideo 的 3D RoPE 把这件事说得很具体:先把每个坐标(时间 t、高 h、宽 w)各自算出一套旋转频率,再把注意力头的维度切成三段,每一段只用其中一个坐标去旋转,最后把三段拼回去(§4.2)。切法不是均分:
| 符号 | 是什么 | HunyuanVideo 的取值 |
|---|---|---|
| d | 单个注意力头的总维度 | 128 |
| dt | 分给时间坐标的维度 | 16 |
| dh、dw | 分给高、宽坐标的维度 | 各 56 |
HunyuanVideo 把 128 维的头拆成 16(时间)+56(高)+56(宽)。空间两个坐标加起来占了 112 维,是时间坐标 16 维的 7 倍。结合第10章的数字,猜猜这个悬殊比例最可能的原因是什么?
变式:如果一段视频的分辨率降到很低、但时长变得很长,这个 16:56:56 的比例还合适吗?
三个坐标各自的旋转频率之外,还有一个数字的来源要单独交代:patch 本身的三维厚度。HunyuanVideo 论文用符号 kt×kh×kw 表示这个量,正文只给了符号,不含具体数字;第9章表1里用的「1×2×2」,来自 Open-Sora 2.0 附录 F.1 用 HunyuanVideo 做跨模型 token 数对比时所用的配置,和公开代码仓库的默认设置一致(C档,本站综合两处旁证推断,不是 HunyuanVideo 论文自己给出的数字)。
11.4 文字标注本身,是不是也是一个不断升级的生成问题?
原始视频的标题通常又短又糙,配不上画面里发生的事。如果要给训练数据重新打一遍更详细的标注,你觉得最直接可行的办法是什么?
答完再看
第二个选项正是 Sora 报告说的「re-captioning」,报告原话是「We apply the re-captioning technique introduced in DALL·E 3 to videos」,也是接下来四家视频模型不约而同选的路。第一个选项在小数据集上可行,训练视频动辄几亿条,人工标注根本铺不开;第三个选项混淆了「数据量大」和「数据标注准」,标注本身不准,学得再久映射也是错的。
这里有个岔路口 给视频重新打标,各家怎么做
上面四条标注路线,最终都要解决同一个分布不匹配问题。这个问题具体是指哪两种文字之间的分布不匹配?
变式:Wan 和 Sora 都选择了用 LLM 把用户的短 prompt 改写扩写,这一招解决的是训练阶段还是推理阶段的分布不匹配?
重新训练一个描述能力强的 captioner 给训练数据打标、推理时再用 LLM 扩写用户 prompt——按 Sora 报告自己的说法,这项技术最早是哪个模型的报告提出来的?
变式:CogVideoX 的参考文献列表里也出现了 DALL·E 3 的技术报告,这说明什么?
11.5 拍下来的素材,要闯过几道关卡才能喂给模型?
从网上大批量抓下来的原始视频,不做任何筛选,直接配上标题拿去训练文字生成视频模型。你觉得训出来的模型会有什么毛病?
答完再看
第二个选项是 SVD 论文自己验证过的事:原始数据里剪辑镜头和静止画面的比例都不低,不处理的话,生成模型会把这些当成「视频该有的样子」学进去。第一个选项低估了这件事——数量大不能抵消系统性偏差,坏样本不是随机噪声,是有规律的偏差,规律的东西越多反而学得越牢。
SVD 的筛选流水线按顺序做四件事:先把长视频切成不含镜头切换的短片段,再过滤掉画面几乎不动的静止片段,然后用 CLIP 打一个视觉美学分,最后把画面里文字过多的片段也剔除掉。
单一帧率的切镜检测:只抓得住硬切
只在原始帧率下跑一次切镜检测,能抓住「上一帧还是教室,下一帧突然变成海边」这种硬切,但抓不住淡入淡出这种连续过渡几帧才完成的转场,这类转场会被漏检,混进「同一个片段」里当成没有切换过。
SVD 的级联检测:三个探测器,不同帧率不同阈值
同时跑三个切镜检测器,各自用不同的帧率和阈值扫描同一段视频,硬切和缓慢的淡入淡出都能被抓住,不再只依赖单一时间尺度上的画面突变。
差别在:转场不是只有一种速度,检测器如果只在一个时间尺度上找突变,找到的就只是突变最剧烈的那一部分转场。
一个只在原始帧率、单一阈值下工作的切镜检测器,为什么会漏检「淡入淡出」这种转场,却能抓住「硬切」?
变式:如果一段转场比淡入淡出还要更缓慢,三个探测器的方案还够用吗?
切完片段之后,第二关是光流过滤:以每秒 2 帧的频率算稠密光流,把平均光流幅度低于某个门槛的片段判定为「静止场景」剔除掉——专门对付「画面几乎不动」这种偏差。第三关是 CLIP:分别对片段的首帧、中间帧、尾帧打一个视觉美学分,分数太低的片段也被剔除。第四关是文字检测:用现成的文字检测器 CRAFT 标出画面里所有文字的包围盒,文字覆盖面积超过画面 7% 的片段被剔除——SVD 论文说早期版本的模型学会了在生成的画面里塞入大量文字,这不是文字生成视频模型该有的行为。
把切镜检测、光流过滤、CLIP 美学分、OCR 文字检测这四关筛选,和它们各自要剔除的训练数据症状配对起来:镜头切换、画面静止、视觉质量低、画面文字过多。
变式:如果一段视频画面很美、没有切镜、也没有文字,但整段都是同一个静止镜头,这段视频能通过前三关吗?会在哪一关被挡下来?
答辩:如果我是审稿人
Sora 报告在正文里主动写了一句对自己不利的话——「it does not accurately model the physics of many basic interactions, like glass shattering」。一份对外发布、意在展示能力的技术报告,为什么要自己承认做不到的事?这句话是不是纯粹的公关姿态?
参考防守(先自己组织语言再看)
不只是公关姿态,这句话本身带着信息量。它没有笼统地说「还不完美」,而是给了一个具体到能被验证、能被复现失败案例的例子——玻璃碎裂。具体的失败案例比空泛的免责声明更有价值:读者可以直接去检验这个模型是不是真的在玻璃碎裂这类物理交互上表现差,而不是被一句无法验证的套话打发。这也是本章反复在做的事——不管是这句物理局限的承认,还是刚才 SVD 那套筛选流水线公开自己怎么筛数据,具体到可检验的描述,天然比空泛的宣传语更经得起推敲,不管说这句话的动机是不是纯粹为了展示诚实。
对你而言未知3D RoPE 里 16:56:56 这个切分比例是怎么选出来的
HunyuanVideo 论文给出了这三个数字,也解释了为什么要做三维位置编码,但空间两个坐标各占 56、是时间坐标 16 的 3.5 倍这个具体比例,是不是经过消融实验,还是某种经验法则,论文正文查不到答案。
先做这一步:回到本章的时空 patch 实验室,固定总维度 128 不变,换几组不同的时间/高/宽切分比例,看能不能凭直觉判断哪种切法更浪费;再去翻 HunyuanVideo 或者其他视频模型公开的代码仓库,看这个切分在代码里是不是一个可以配置的超参数,默认值又是怎么给出来的。
这一层要加什么:patch 化与三维位置编码
为什么现在才加它:上一层把 latent 方块在时间维上也压缩了一轮,但这个方块本身还是一整块摆在那里,没有变成 transformer 能处理的 token 序列。这一层要把方块切成小块、拍平成序列,再把拍平时丢掉的三维位置信息用位置编码补回来。
难点:这里用显式记录坐标、再手动做旋转的方式,是为了让「位置编码到底改变了什么」这件事能被单独摘出来测量;真正的实现里,旋转矩阵是提前按坐标算好直接乘上去的,效果一样,但看不出这一步单独的贡献。
自己验:构造一个只在时间轴上变化的合成序列(每个 patch 的取值只和它的时间坐标 t 有关,与 h、w 无关),不加位置编码时,交换两个时间上相邻的 patch 和交换两个时间上相距很远的 patch,输出的变化幅度应该完全相同(因为不加位置编码时输出根本不随顺序变化);加上 3D 位置编码之后重复同样的交换实验,交换离得越远的两个 patch,位置编码带来的扰动幅度应该越大,交换时间上相邻的两个 patch 扰动应该最小——这是 RoPE 作为相对位置编码的直接后果:两个位置的编码差异,随相对距离增大而增大。
读的时候要小心
关于本章材料的来路
Sora 技术报告不开源、没有发表论文,本章引用的几句话都来自 OpenAI 官方原文,没有第三方复现过。HunyuanVideo Table 2 的 RoPE 维度分配、SVD 附录的筛选流水线设计,同样是论文作者自述;patch 三维厚度这个数字,正文已经说明是本站综合旁证推断,不是 HunyuanVideo 论文直接给出的。
留给你的问题
如果要给一个新的视频模型设计 3D 位置编码,输入方块是 latent 帧数 40、高 60、宽 100,注意力头维度 96,你会把这 96 维怎样分给三个坐标,依据是什么?
不看材料,写下这一章的三堵墙
第一堵:三维方块拍平成 token 序列会丢掉位置信息,图片只是 latent 帧数等于 1 的特例。第二堵:patch 化解除了输入尺寸的限制,原生形状训练因此可行,但这个好处目前只停留在报告自述,没有对照实验的数字。第三堵:文字标注本身也是个不断打磨的环节,四条路线都在补「用户的短话」和「训练用的详细描述」之间的分布落差,源头可以追溯到 DALL·E 3。
第12章 注意力顶不住了
开篇故事第 12 集 · 三天过去,进度条连一半都没爬到这模型也太真实了
训练监控的屏幕在凌晨四点还亮着,沈砚的手指一直按在触控板上没有松开,光标停在那根几乎不动的进度条正上方——这是回声复现的第九十二天,视频这条线的训练已经连续跑了三天,进度条却连一半都没有走到,机柜里几十块显卡的风扇声这时候听着比平时更吵。
三天前排队列的时候,沈砚力排众议说不用等显存刺客那套稀疏注意力方案先接上再启动,理由是第七章早就算过账:DiT-XL/2 那类图像模型上,token 数是 256 的时候,那个随 token 数平方增长的注意力矩阵只占整个网络计算量的 3.6%,视频无非是多堆了几帧,占比应该也高不到哪里去,没必要为这点计算量专门等一套还没调完的方案。
复现之鬼盯着这条卡了三天的进度条,把当前这次训练的真实配置丢进了《潜空》。
系统提示当前训练配置:隐藏维度 3072,单步 token 数 115,200,按第七章的份额公式代入,二次项占计算量的 86.2%,与本次训练实测的单步耗时占比基本吻合。
沈砚看着这行数字愣住了,没有立刻反驳,而是把笔记本翻到空白页,把第七章那条公式重新抄了一遍:份额等于 token 数除以 token 数加上一个由隐藏维度和层内开销撑起来的分母。他把图像那边的 256 代进去,分母比 token 数大得多,二次项自然只占零头;再把这次视频的 115,200 代进去,分母只有一万八千多,token 数已经反过来把分母甩在了后面。
“图像和视频用的是同一条公式,不是两条不同的规则。”靳工站在他身后看了一眼那两组演算,“第七章的结论是那时候的token数太小,不是注意力天生占比小,你把一个只在小token数下成立的结论,直接搬到了大两个数量级的场景上。”
这条视频训练三天才跑不到一半,问题到底出在哪
1L复现之鬼我一路盯着日志,单步耗时从头到尾没降过,你们谁去查一下是不是哪个算子没吃满显存,先别把锅扣到模型头上。
2L炼丹废人我一开始也以为注意力这块占不了多少,第七章不是算过嘛,token 数才 256 的时候只有百分之三点六,谁能想到视频这边直接反过来了。
3L沈砚不对,问题不在算子,是我当初直接套了第七章那个结论,没重新拿视频的 token 数去算,二次项现在占了八成六,从配角到主角这件事上我判断错了。
4L显存刺客早说这话,队列这三天的卡时就不至于烧在一次跑不完的训练上,稀疏注意力那套早该在启动前就接上。
沈砚翻出 STA 那篇论文,论文自己测出来的数字是注意力单独占了 800 秒,占比 84.7%,和他刚才算出的 86.2% 只差一点五个百分点。论文里还有一条更关键的发现:把每个 token 的可选范围收窄到只占全部 token 空间 15.52% 的局部窗口,平均下来仍能装下 70% 的注意力分数,这正是能把计算量砍下去而不是瞎砍的原因;像 LongCat 那种块稀疏配上由粗到精再加 LCM 的做法,加速比能做到 12.3 倍。
这一次的教训代价不小:三天已经烧掉的卡时追不回来,团队只能把这次训练中止,接上稀疏注意力重新排队,这一停一接,这周留给视频线的机器时长基本全搭了进去。沈砚把中止的训练重新交给接上稀疏注意力的那一版排进队列,转头就看见工作群里跳出一条新消息——一个客户问能不能给一张照片,让模型接着往下演成一段视频,这个问题他们谁都还没碰过,也不知道现在这一套东西答不答得上来。
系统结算 · 本章考点
- 第七章算过,DiT-XL/2那类图像模型token数是256时,二次项只占计算量的 3.6%;这次视频配置隐藏维度3072、单步token数 115,200,同一条份额公式算出来的占比是 86.2%
- STA论文实测印证了这一点:一段视频生成总耗时里,注意力单独占了 800秒,占比84.7%,和公式算出的86.2%只差 1.5 个百分点
- 决定占比的不是分辨率本身,是token数相对隐藏维度的比例:图像的token数和隐藏维度是同一个量级,视频的token数却比隐藏维度多出两个数量级
- STA发现,把可选范围收窄到只占全部token空间 15.52% 的局部窗口,平均下来仍能装下 70% 的注意力分数,这是能把计算砍下去的原因
- 稀疏化省下的计算量因做法不同差得很大,像LongCat那种块稀疏配上由粗到精加LCM的做法,加速比能做到 12.3 倍
- 论文验证「专化窗口跟着头走、不跟着内容走」这个结论时,只平均了 16个 提示词,这份证据本身留了一个没堵上的口子
把一段五秒的720P视频喂进HunyuanVideo,等它吐出画面要十五分钟。这十五分钟里,九成时间只花在一件事上——一个在第七章里被证明「不值得担心」的操作,注意力。这一章要看着同一道算式,在视频上把那个结论整个翻过来。
学完这一章你应该能做到
- 用第七章的份额公式,算出同一套算法在图像和视频上,二次项各占多大份额,并说清份额为什么会这么大幅度地变
- 说清HunyuanVideo为什么明知全时空注意力更贵,还是放弃了更便宜的分解注意力
- 读懂CogVideoX的耗时对照表,解释额外开销为什么随分辨率非线性放大
- 说清STA发现的局部性现象到底在说什么,以及这份局部性为什么能被拿来省计算
- 区分免训练稀疏化和微调后稀疏化两档加速比,各自的代价是什么
- 说清MAGI-1的分块自回归和前面几节的稀疏化,是两类完全不同的解法
这一层要给第11级那套时空patch加一层过滤:不再让每个token和其余全部token都算一遍注意力,只让它和一个局部邻域算。前11级跑的都是把token造出来、排好位置的事,还没有人问过要不要把每一对token都算一遍。
12.1 第七章说不用担心,视频上还这样吗
第七章用DiT-XL/2算过一次账:图像上,token数256的时候,那个随token数平方增长的注意力矩阵,只占整个网络计算量的3.6%。现在把镜头切到一段5秒720P的视频,按第9章的公式,token数涨到十一万上下,比图像整整多了两个数量级。你猜这个3.6%,在视频上会变成什么样?
答完再看
按第七章那条份额公式,代入HunyuanVideo自己的隐藏维度3072和它跑117帧、1280×768视频时的真实token数十一万五千二百,二次项占到八成六。第二个选项错在把T和T²当成了同一种涨法——线性项确实也在涨,但只跟着T涨一次方,注意力矩阵却跟着T涨两次方,token数一旦从几千冲到十几万,平方项的涨幅会把一次方项远远甩在后面。
把这笔账摊开看。第七章的份额公式是share = T / (T + (2+r)·d),T是token数,d是隐藏维度,r是MLP比自注意力多摊的倍数。DiT-XL在T=256、d=1152、r=4时,代入算出3.57%,和论文报告的3.6%对上。放到今天常见的生成分辨率,比如1024×1024、d取FLUX那一档的3072,token数是四千零九十六,同样代入公式,份额是四千零九十六除以四千零九十六加一万八千四百三十二,约18.2%——和FACTS.md里给出的另一个数字(1024×1024、含文本token共4608时占20.0%)差得不多,差的那一点来自要不要把文本token也算进T里。图像线上不管取哪个分辨率,二次项都还是配角。
自己推一遍:从第七章的公式到视频上的86%
第七章那条份额公式里有一个r,是MLP比自注意力多摊的倍数。HunyuanVideo自己的r是多少?从表2里FFN维度和模型维度的比值就能读出来。
想好了再看
FFN维度12288除以模型维度3072,正好是4。这和图像线上DiT-XL、FLUX用的r一样,4倍差不多是这一类Transformer的标准配置,不是HunyuanVideo特有的选择。
第9章给过视频token数的公式,tokens=(1+(F−1)/D_T)×H/(D_H·P_H)×W/(D_W·P_W)。STA论文里跑的是117帧、1280×768、HunyuanVideo的4×8×8压缩加1×2×2的patch。把这些数代进去,token数是多少?
想好了再看
时间方向1+(117−1)/4=30;高度768/8/2=48;宽度1280/8/2=80。三者相乘,30×48×80=115,200。STA论文自己也报过这个latent形状(30,48,80),正好对上。
把d=3072、r=4、T=115,200代进份额公式,(2+r)d=6×3072=18,432。算出来的份额是多少?
想好了再看
115,200除以(115,200+18,432),也就是115,200除以133,632,约0.862,86.2%。
STA论文自己测出来的数字是84.7%(945秒总推理时间里,注意力占800秒)。这两个数只差1.5个百分点,但它们量的其实不是同一件事——差在哪?
想好了再看
份额公式算的是矩阵乘法的乘加次数占比,一个纯理论的FLOP份额;84.7%是配了FlashAttention-3之后的真实墙钟时间占比,里面混进了显存搬运、算子调度这些FLOP份额算不进去的开销,也混进了FlashAttention-3把矩阵乘法和注意力两类算子加速的程度并不完全相同这件事。两个数能对上到1.5个百分点以内,已经说明这条份额公式抓住了主要矛盾——真正决定这十五分钟大部分去哪儿的,就是这个随token数平方增长的矩阵。
图像上:一笔小账
DiT-XL/2在T=256时,二次项占3.6%;放到1024×1024、d=3072的现代规模,token数四千出头,占比约一成八到两成。不管哪个,线性项(MLP和投影)都还是大头。
视频上:账单倒过来
同样的公式,d不变,token数换成十几万,二次项占到八成六。线性项从大头变成了零头,注意力矩阵成了几乎全部的账单。
差别不在公式变了,在token数从几千级跳到十万级——这一步跨度,图像线上从来没有发生过。
HunyuanVideo 1.5把隐藏维度从3072降到2048,但按第9章的反直觉结论,它的视频token数和1.0版几乎一样,还是十一万一千六百左右。用第七章的份额公式(r仍取4)算一算,1.5版的二次项占比,比1.0版是更高还是更低?
变式:如果保持d=3072不变,但把注意力头数从24降到12、每个头的维度从128翻倍到256(总维度不变),份额公式里的d需不需要跟着改?为什么?
12.2 明知更贵,为什么还选全时空注意力
视频比图像多了一维,时间。把每一帧当独立的图,先让空间内部的像素互相看一遍,再让同一个位置跨帧互相看一遍——这条分开算的路远比把全部时空token放在一起算一遍便宜,HunyuanVideo之前不少模型都这么干,包括最早的Video Diffusion Models。已经知道分开算更省、放在一起算贵得多,你是HunyuanVideo的架构师,会怎么选?
答完再看
HunyuanVideo选的是更贵的那条,论文§4.2把理由摆得很清楚——账算过了,贵的那条仍然是最终的选择,理由有三条:全时空注意力的效果好于分开算空间和时间;图像和视频能因此共用同一套训练流程;还能直接借用大语言模型社区已经打磨到极致的加速算子生态。
三条理由值得逐条摊开。第一条是效果:论文引了多篇此前工作的对照,全时空注意力的生成质量,好于先空间后时间或者先时间后空间这类分开算的路。第二条是训练流程:一张静止图片可以被当成只有一帧的视频,用同一套全注意力模块处理,图像和视频从此共用一套训练代码和一套模型权重,不必为分开算的那条路单独维护一套时间维度的模块。第三条是工程生态:大语言模型社区这几年把全量的、不分块的自注意力优化到了极致,FlashAttention这类算子就是为这种形状的注意力设计的。选全时空注意力,意味着可以直接搬来这整套现成的加速代码,不用为分开算的那条路重新写一遍kernel。
全时空注意力:贵,但……
效果更好、图像视频共用一套代码、能直接用FlashAttention这类现成加速。代价是计算量按token数平方增长,视频token一多就要付出高得多的代价。
分解时空注意力:省,但……
空间和时间分开算,计算量摊薄很多。代价是论文引用的多项此前工作显示效果不如全时空;要为空间和时间分别维护模块,图像和视频没法共用一套代码;也没有现成的、被打磨到极致的加速算子可用。
差别在:分解注意力省下来的计算量,换来的是效果、工程复杂度和加速生态三处代价。HunyuanVideo判断这笔交换不划算,才在明知更贵的情况下选了全时空注意力。
答辩:如果我是审稿人
论文说全时空注意力效果好于分解注意力,给出的依据是引用了另外四篇此前工作,不是HunyuanVideo自己在同一个数据集、同一个规模下跑的对照实验。这个结论站得住吗?
参考防守(先自己组织语言再看)
站得住的部分是方向,站不住的部分是幅度。引用其他工作能说明业界普遍观察到全时空注意力质量更好这个方向,但没有告诉我们在HunyuanVideo自己13B这个规模、自己的数据上,分解注意力具体会差多少——那需要一次专门的消融实验,论文没有公开做过。可以确认的是后面两条理由:图像视频共用一套代码、能直接用现成加速算子,这两条不依赖任何对照实验,只要看训练代码和推理耗时就能验证,是三条理由里最硬的两条。
假设HunyuanVideo团队当年没有FlashAttention这类现成的高性能注意力算子可用,得自己从头写。三条理由里,哪一条会最先失去分量,论文的选择还会不会一样?
变式:如果反过来,HunyuanVideo选的是分解时空注意力,STA这类稀疏化工作还有没有用武之地?
12.3 贵出来的账单,陡到什么程度
选了更贵的那条路,贵多少总得有个数。CogVideoX团队直接做了对照——同一个模型,把全时空注意力换成分开算空间和时间的版本,在三档分辨率下各跑一次单次前向,量出的是H800上真实的墙钟时间,不是理论FLOP。
| 配置 | 256×384×6s | 480×720×6s | 768×1360×5s |
|---|---|---|---|
| 2D+1D(分离) | 0.38 s | 1.26 s | 4.17 s |
| 3D(全时空) | 0.41 s | 2.11 s | 9.60 s |
| 全注意力额外开销 | +7.9% | +67% | +130% |
三档分辨率里,分辨率每往上跳一档,额外开销不是按同一个倍数涨,是越往后跳得越猛——从7.9%到67%,再到130%,涨幅本身在加速。
256×384:多花7.9%,几乎不心疼
token数还小,注意力矩阵这一项在总账单里占比还不高,换成全时空注意力的额外成本几乎可以忽略。
768×1360:多花130%,时间翻倍还多
token数涨起来之后,注意力矩阵按平方增长的那部分迅速压过分离注意力原本省下来的部分,全时空注意力反而要花掉两倍多分离注意力的时间。
差别在:分离注意力的计算量大体上跟着token数线性涨,全时空注意力跟着token数平方涨。分辨率越高,平方项和线性项之间的差距被拉得越开,额外开销的百分比也就跟着越走越陡。
按表里的趋势,如果CogVideoX团队再跑一档比768×1360更高的分辨率,额外开销会大概率超过130%,还是回落到130%以下?说明理由。
变式:反过来,如果分辨率一路降到很小,比如64×64,额外开销的百分比会趋向于什么?
这份对照表量的是单次前向,一次真正的视频生成要跑几十次这样的前向,再叠上文本编码、VAE解码这些环节。把这些都算进去,STA论文给出了一个更直接的数字:HunyuanVideo生成一段5秒720P视频,总共945秒,其中注意力单独占了800秒,占比84.7%。这个数字已经是配好FlashAttention-3这个当时最快的精确注意力实现之后测出来的——算子本身优化到了极致,也没能把这个比例压下去,因为FlashAttention-3加速的是怎么算这个矩阵,没有改变要算多大的矩阵。
假设未来出现一种比FlashAttention-3快十倍的注意力kernel,但计算的仍然是完整的T×T矩阵。用这一节的框架说清,84.7%这个占比会因此发生什么变化,以及要真正把这个占比降下来,该在公式的哪一项上动手。
变式:如果反过来,只优化MLP和线性投影这部分的kernel,让它们也变快十倍,注意力占比会怎么变?
12.4 没人规定它该看哪儿,它自己怎么看
HunyuanVideo用全时空注意力训练,从头到尾允许每个token关注视频里任何位置的任何token,没有人规定它该往哪看。你猜训好之后,它实际会怎么用这份自由?
答完再看
STA论文对HunyuanVideo的注意力分数做了统计:平均下来,一个只覆盖全部token空间15.52%的局部窗口,却装下了70%的注意力分数。第二个选项猜对了局部这个方向,但漏掉了时间——STA特别指出,这个局部性是三维的,同时跨越空间和时间,一个只在空间上局部、时间上随意的窗口装不下这么多分数。
这里的15.52%和70%说的是同一件事的两个角度。先把每个query token的注意力分数按大小排序,划出一个以它为中心、覆盖全部token空间15.52%体积的局部窗口——这个窗口在空间和时间上都有一定宽度,不是只顾一头;再把落在这个窗口内的分数加起来,除以这个token全部的注意力分数,平均下来是70%。这个token剩下84.48%的可选范围,总共只贡献了30%的注意力。
分解注意力:人为规定只看局部
早期模型把时空拆开算,本质上是在架构层面强行限制了每个token能看到的范围,局部性是设计者定死的规矩。
全时空注意力:没人规定,自己学出了局部
HunyuanVideo从没被限制过看多远,训练目标里没有任何一条鼓励局部性,但训完之后自己把注意力分数收拢进了一个不大的窗口里。
差别在:一个是被架构逼出来的局部,一个是数据和目标自己教出来的局部。后者更值得信——如果连不受限制的模型都倾向于局部,这份局部性大概率来自视频数据本身的规律(相邻帧、相邻像素之间信息冗余),不是某种架构偏见。
这份局部性还有一层细节。不同的注意力头,专精的窗口大小并不一样——有的头只盯着很小的一块区域看细节,有的头把窗口开得很大去抓整体的上下文,论文把这个现象叫做头部专化。更重要的是,同一个头在不同的输入提示下,倾向的窗口大小基本保持一致,专化模式跟着头走,不太跟着内容走。
如果HunyuanVideo从一开始就用分解时空注意力训练,还能不能观测到STA论文这种15.52%窗口装下70%分数的统计?为什么这个统计只有在全时空注意力上做才有意义?
变式:STA的训练做法里提到,推理最初几步仍然保留全注意力,过了这几步才切到稀疏。为什么偏偏是最初几步要保留?
答辩:如果我是审稿人
论文说头部专化的模式与提示无关,但支撑这个结论的是对16个提示求平均之后标准差很低这一件事。16个提示,对着一个要处理海量不同内容(人物、风景、动作、文字)的视频模型来说,够不够说明问题?
参考防守(先自己组织语言再看)
16个提示能说明的是,至少在这16个覆盖了常见场景的提示上,专化模式的波动很小,足以支撑「可以对每个头离线搜索一次最优窗口,不用每次生成都重新搜」这个工程判断——这也是论文实际拿它做的事,免训练版本的STA正是靠这个假设成立的。但这份证据回答不了一个更极端的问题:如果换成一段几乎全是高速运动、边缘细节极多的视频,原本习惯开大窗口的头会不会被逼着收窄?论文的免训练方法留了一道保险——推理最开始的几步继续用全注意力,只有后面的步骤才切到稀疏窗口,这在一定程度上对冲了某个特殊内容让局部性假设失效的风险,但没有从理论上证明失效的情况不存在。
真未解头部专化是怎么被训出来的
STA只报告了头部专化这个现象——有的头习惯开小窗口盯细节,有的头习惯开大窗口抓上下文,而且这个偏好跟着头走、基本不随输入内容变化。论文没有解释这份分工是怎么在训练里长出来的:是初始化的某个随机因素被放大了,还是某种更深层的机制(比如不同的头恰好学会了处理不同频率的运动信息)。目前能看到的只是结果,没有看到机制层面的解释,这和大语言模型里注意力头功能分化的问题属于同一类,都还停留在观察阶段。
先做这一步:在本章的稀疏注意力实验室里,用提供的那份代表性分数分布,比较不同层、不同头各自偏好的窗口大小和层数深度之间有没有稳定的关系(比如浅层是不是更偏好大窗口),再去查大语言模型里关于注意力头功能分化的相关讨论,看有没有能类比过来的分析方法。
把15.52%和70%这两个数字,分别填进「这是____的比例」这句话里,并说清为什么两者不能直接相加或相减来比较。
变式:如果换一个窗口大小,比如只覆盖5%的token空间,你预期分数保留率会比70%高还是低?
12.5 知道该看哪儿了,怎么真的省下计算
知道注意力集中在局部窗口,最直接的想法是照着这个窗口把注意力算一遍,窗口外的直接跳过。这类做法叫滑动窗口注意力,在处理长文本的模型里已经用了多年:每个token只和自己附近的一小圈token算注意力,窗口跟着token一起滑动。把这个想法原样搬到视频的三维token上,理应能省下大部分计算。
问题出在GPU怎么算这件事。FlashAttention把整个注意力矩阵切成一块一块的小方块,每一块里的query和key一起搬进片上高速缓存,算完再搬出去,这样才不用把整个T×T矩阵摊在显存里。如果每个token的窗口都是围绕它自己滑动的,相邻两个token的窗口只差一点点,同一块里不同的query,各自该保留哪些key并不完全一样——这样的块被称为混合块:块内一部分位置要算、一部分要丢,GPU没有办法只算需要的那一部分,只能把整块都算出来,再拿掩码盖掉不要的部分。省下来的只是最后要不要保留结果,没有省下计算本身。
一块FlashAttention计算块里,如果有一半的query-key对该被掩码盖掉、一半该保留,GPU实际执行的乘加次数,和把整块当成全部保留的稠密块相比,是少了一半、完全一样,还是可能更多?
变式:如果一个块被判定为空块(query和key的窗口完全不重叠),这块还需要搬进片上缓存计算吗?
STA换了一个更粗的滑动单位。它不再让窗口跟着每一个token滑动,而是先把token分成一块一块的瓦片——比如三维空间里12×24×24个token算一块——让同一块瓦片里的全部token,共享同一份要看的key瓦片范围。这样一来,一块的query只对应一份完全确定的key范围,块要么整块保留、要么整块丢弃,不再有块内一部分要一部分不要的情况。同样的局部性,换一个滑动的单位,GPU就能把省下来的计算真正省掉。
滑动瓦片注意力(Sliding Tile Attention, STA):把滑动窗口的单位从单个token换成一整块瓦片,让每块的query只对应一份确定的key瓦片集合,消除混合块的做法。
免训练档:不碰模型参数
把原来的全时空注意力直接换成滑动瓦片,不做任何微调就上线推理,随时能用,省下的是重新训练这一整笔成本。
微调档:多花一笔小算力
在免训练的基础上,再用一小笔算力对模型做微调,让它更适应这种稀疏的注意力模式,换来更高的加速比。
差别在要不要多付一笔训练成本——下表给出两档各自的加速比和代价。
| 方法 | 是否微调 | 加速后耗时 | 加速比 | 质量代价 |
|---|---|---|---|---|
| STA | 免训练 | 501秒 | 1.89× | — |
| STA | 微调后 | 268秒 | 3.53× | VBench总分仅降0.09% |
| SSTA(HunyuanVideo 1.5,10秒720p端到端) | — | — | 1.87× | — |
| Block Sparse Attention+粗到精+LCM(LongCat 720p×93帧,基线1429.5秒) | — | 116.5秒 | 12.3× | — |
两档差出来的1.89倍,买的是模型完全不用再碰一次的省心;微调那一步只用了8块H100、8小时,换来的是耗时再降近一半,而生成质量几乎没有变化——VBench这项综合评测的总分只掉了0.09个百分点。免训练版本更适合等不起重新训练的场景,微调版本更适合能接受一次性投入、追求更快速度的场景,两者面对的是同一份局部性,只是要不要为了再多榨一点速度多付一笔训练成本。
12.6 稀疏之后,那个平方项去哪儿了
省下计算量,最终该看到的效果是——token数翻倍,耗时不该跟着翻两倍以上,该更接近翻一倍。HunyuanVideo 1.5的官方耗时表,正好给了这个对照。
| 帧数 | 不开稀疏 | 开稀疏 |
|---|---|---|
| 121 | 2.0084 s | 1.5638 s |
| 241 | 5.5070 s(2.74×) | 2.9475 s(1.88×) |
帧数从121涨到241,接近翻倍。不开稀疏时,耗时从2.0084秒涨到5.5070秒,涨了2.74倍——比帧数本身的涨幅高出不少,这正是二次项在起作用:帧数翻倍,token数也大致翻倍,而二次项跟着token数的平方涨,理应比token数本身涨得更猛。开了稀疏之后,耗时从1.5638秒涨到2.9475秒,涨了1.88倍,已经很接近帧数本身的涨幅——这就是稀疏化真正买到的东西:不是让每一步都变快一个固定倍数,是把耗时随帧数增长的规律,从比平方还猛,压回了接近一次方。
不开稀疏:帧数翻倍,耗时涨2.74倍
涨幅超过帧数本身的涨幅,是二次项没被压制时的典型样子。
开稀疏:帧数翻倍,耗时涨1.88倍
涨幅接近帧数本身的涨幅,已经很接近线性。
差别在:稀疏化改变的不是某一次运行的绝对耗时,是耗时随帧数(进而随token数)增长的那条曲线的形状——从超线性压成了接近线性。
Wan的技术报告用另一套记号把这条规律写成了公式。DiT一层的计算量正比于α·b·s·h²+β·b·s²·h,s是token序列长度,h是隐藏维度,b是批大小,α对应线性层(投影、MLP)的开销,β对应注意力层的开销;论文里非因果注意力(Wan用的这一种)的β,前向传播是4,反向传播是8。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| L | DiT的层数 | 每层都要重复这笔账,层数是总账的倍数 |
| α | 线性层(投影、MLP)的开销系数 | 对应这一章反复出现的那个(2+r) |
| β | 注意力层的开销系数,非因果注意力下前向4、反向8 | 对应这一章反复出现的份额公式里的2这一项,只是这里把前反向拆开写了 |
| b | 批大小 | 一次同时处理几个样本 |
| s、h | token序列长度、隐藏维度 | 就是这一章反复出现的T和d,换了个字母 |
用Wan给出的β(前向4、反向8)解释:如果HunyuanVideo1.5「不开稀疏」这条设置严格遵循注意力项主导的O(n²)增长,帧数翻倍时耗时理应接近4倍,但实测只涨了2.74倍。差距从哪里来?
变式:开稀疏之后涨幅是1.88倍,比帧数本身的2倍还略低一点。这说明了什么?
论文的结论写得很直接:序列长度一旦到了一百万,注意力的计算时间能占到端到端训练时间的95%。
显存这边的规律不一样。GPU要为激活值(反向传播算梯度需要保留下来的中间结果)占用的显存,正比于γ·L·b·s·h——这一项只跟着s线性涨,不像计算量那样跟着s平方涨。γ这个系数由具体实现决定,普通大语言模型大约是34,DiT因为要同时处理视频token、文本提示、还有时间步这三种输入,γ能超过60。序列长度到一百万、批大小为1时,一个14B参数的DiT,光是激活值占用的显存就能超过8TB——这个数字远超单张GPU、甚至单台服务器能提供的显存,这也是为什么大规模视频模型训练离不开跨设备切分序列这类并行策略。
12.7 峰值成本,能不能干脆不随时长涨
这一章前面几节的稀疏化,做的都是同一件事:还是一次性对整段视频跑完整的多步去噪,只是想办法让每一步的注意力算得更省。如果生成的视频从5秒变成50秒,时长涨了十倍,用这种一次性处理整段的模型,推理的峰值显存和延迟大致会怎样变化?
答完再看
前面几节的稀疏化确实把注意力那一项的增长速度从接近平方压到了接近线性,但整个生成过程仍然要求一次性持有整段视频对应的全部token,从第一步去噪到最后一步。视频越长,这份同时在场的token越多,峰值显存和延迟依然会随时长增长,只是增长的斜率比不做稀疏化时缓和一些。
MAGI-1走了一条不同的路,不再继续优化某一步注意力怎么算,而是换掉一次性处理整段视频这个前提。它把视频按24帧切成一段一段的分块——按24帧每秒的帧率算,正好是一秒钟一块——让噪声水平随时间单调递增:靠前的块噪声水平低、更接近生成完成,靠后的块噪声水平高、还处在早期的去噪阶段。模型只需要在任意时刻,同时持有正在推进的这几个相邻分块,不需要把整段视频从头到尾都摊在显存里。
分块自回归(chunk-wise autoregressive):把序列切成固定长度的块,按顺序逐块生成,同一时刻只需要在场的是当前块和它依赖的少数邻近块,而不是整条序列。
这样带来的好处很直接:推理的峰值成本,不再随视频总时长增长——不管最终要生成5秒还是50秒,任意时刻同时在场、占用显存和计算的,都只是固定数量的几个分块,多出来的时长只是让这个滚动窗口往前多滑动几次,不会让某一时刻的峰值负担跟着堆高。这也顺带带来另一个好处:因为生成本来就是按时间顺序、一块接一块推进的,前面的块可以边生成边往外吐,不需要等整段视频全部生成完才能看到第一帧,天然适合流式播放这类场景。
这一章前面的路:一次性处理整段
前面几节的全部技巧(全时空注意力、局部性发现、稀疏化)都建立在整段视频是一个序列、要整体做多步去噪这个前提上,时长越长、序列越长,峰值显存和延迟就越高,稀疏化只能减缓这个趋势。
MAGI-1的路:分块自回归
把整段视频拆成按时间排列、噪声水平递增的分块,任意时刻只需要少数几个分块同时在场,时长增加只是让处理过程多推进几轮,不推高峰值负担,还天然支持边生成边播放。
差别在:一条路把整段视频必须整体处理当成不可动摇的前提,在这个前提下想办法省计算;另一条路直接放弃这个前提,换一种生成顺序,让峰值成本从设计上就和视频总长脱钩。
分块自回归解决的是峰值成本随总时长增长这个问题,这一章前面几节的稀疏注意力解决的是同一次前向里二次项占比过高这个问题。这两件事是同一个问题的两种解法,还是两个不同层面的问题,理论上能不能同时用在同一个模型上?
变式:如果分块的大小从24帧改成240帧(十倍),分块自回归原本峰值成本与总时长无关这个优点,还能不能完全成立?
读的时候要小心
关于本章数字的来路
本章里CogVideoX的耗时对照表、STA论文的945秒/800秒/84.7%、15.52%和70%这两个局部性统计、STA自己的加速比表、HunyuanVideo 1.5的单步耗时表,以及Wan给出的95%和8TB这两个数,全部来自各论文作者自己的实验,没有第三方复现。MAGI-1那一节的具体机制(24帧一块、噪声水平单调递增)来自公开资料对这篇论文的转述,本站没有拿到这篇论文本身做逐句核对。
这一层要加什么:稀疏注意力窗口
为什么现在才加它:前11级造出来的token,不管窗口稀疏与否,都要过一遍注意力这道关,而且这道关一直是按全部token两两互相看的方式实现的。这一层要给这道关加一层过滤,只让每个token和一个局部窗口内的token互相看,窗口能调,调到100%要能精确退回全注意力。
难点:这一层最容易踩的坑,是拿一份随机生成的Q、K去验证「15.52%窗口装下70%分数」这个结论——随机向量算出来的注意力分数是均匀铺开的,任何一个15%的窗口平均只能装下15%左右的分数,不会自动跑出70%。这个70%是HunyuanVideo这个训练好的真实模型的性质,不是注意力机制天生就有的数学性质。这一层的实验室因此提供了一份代表性的分数分布(仿照论文里的统计特征构造,不是凭空编的随机数),用来验证窗口和分数保留率之间的关系;而窗口覆盖全部token时必须精确退回全注意力这一条,才是不管分数分布如何、只要代码写对了就一定成立的数学性质,两者的验证方式不一样,不能混着看。
自己验:把frac设成1.0,窗口覆盖全部token,sparse_attn的输出必须和不加掩码的全注意力逐位相同,误差小于1e-12;把frac设成0.15,计算量(参与softmax和加权求和的query-key对数)应该降到全注意力的约15%,而用提供的那份代表性分数分布算出来的分数保留率,应该落在70%左右。
留给你的问题
这一章看着同一个token,从占计算量3.6%、不用担心,一路走到占84.7%、占了几乎全部,靠的始终是同一条份额公式,变的只是T这一个数。现在反过来想:如果有一天,某个视频模型的隐藏维度d也跟着模型规模一起往上涨,涨到能追上token数T的涨幅——比如d也涨到十万这个量级——份额公式会把结论拉回哪个方向?这样的模型还值得为它专门做稀疏注意力吗?
不看材料,写下这一章的三堵墙
第一堵:以为第七章「二次项不用担心」的结论能直接搬到视频上,没意识到token数从几千跳到十几万这一步,图像线上从没发生过。第二堵:以为分解时空注意力更便宜就一定该选它,没看到HunyuanVideo明知更贵、算完效果和工程两笔账之后仍然选了全时空注意力。第三堵:以为按token滑动窗口就能省下计算,没想到GPU按块计算的方式让混合块完全省不下任何乘加,必须换成按瓦片滑动才能把理论上的稀疏变成真实的加速。
第13章 条件怎么接进来
开篇故事第 13 集 · 二十条新通道,先把老演示搅塌了这模型也太真实了
下午两点,显存刺客把上周就能稳定跑出来的那条文生视频演示又点了一遍,屏幕上弹回来的却是一团认不出内容的雪花噪点,跟他记忆里那段花园镜头完全对不上,他的手指在刷新键上按了第三次,噪点还是那一团,没有一点要变好的意思——这是回声复现的第一百零一天。这条演示压根没碰过任何一张照片,纯粹靠文字生成,本该和昨天分毫不差才对。
系统提示不带参考图的常规生成校验:本次输出与昨日基线逐帧比对,第1帧起差值即冲出正常范围,本次校验未通过。
半小时前,显存刺客刚把客户要的那个功能接完——对方要一张照片,让模型接着往下演,可回声这套模型从出生起就只认纯噪声加一句话,没有任何入口能接收这一帧已经定了这种要求。他照着能查到的思路,把参考图编码成和视频一样的潜变量,再配一份掩码,一起沿通道塞进原本的输入:视频自己的噪声潜变量占十六个通道,参考图编码出来的也占十六个,掩码按时间压缩步幅折成四个,三项加起来是三十六个,比原来宽出二十条。宽出来的这二十条通道,总得接一层新的投影才能喂进模型,他照着平时的习惯把这层投影初始化成随机数,想的是训练一开始就要学着怎么用这张照片,随机数好歹是个正常起点,全摆成零,这二十条通道岂不是白占地方,好几天都学不出动静。
他心里想的是,就算这层投影一开始学得不准,无非是模型暂时看不懂那张照片,把它当成没给图处理,照旧吐一段普通的文生视频,先把这版跑起来讲个大概,回头再慢慢调细——谁知道真跑起来是另一回事,连没碰过照片的那条老演示都一起垮了。
沈砚把这团噪点截图发到工作群里,靳工端着杯子晃过来看了一眼,说:“问题不在这二十条通道里装的是什么,是那层新投影自己算出来的东西,会直接叠加进原来调好的表示里,随机数乘出来是什么谁也说不准,等于是往一个训好的模型头上现浇一遍噪声,跟给没给图没有关系。”
沈砚这才把自己那份改动翻回去看,新加的那层投影确实是接在原来的通路旁边,输出直接叠加,不是替换——这意味着不管新通道里塞的是不是真图,只要这层投影不是从零开始,它算出来的那份扰动都会摞上去,原来的生成能力自然保不住。他把这层权重清零重新跑了一遍,这一次没碰照片的那条演示,和昨天的结果对得上每一个数。
系统提示新增投影层权重清零后重新校验:不带参考图的生成输出与昨日基线逐位一致,误差为零,本次校验通过。
新投影层接进来,到底该随机初始化还是先摆成全零
1L显存刺客二十条通道要是一上来就摆成全零,那不等于白占着不学吗,我还是觉得跟着一起随机初始化更划算,能学得快一点。
2L炼丹废人可你这一跑,连没给图的那条老演示都塌了,这不是学得慢,是从起点就整个走了样,划算两个字现在还谈不上。
3L复现之鬼随机数乘出来的东西根本不是可控的噪声,谁也说不准它把隐藏状态搅成什么样,这笔代价你算进你那本账里了吗?
演示修好之后,显存刺客把这周多烧掉的那几个小时机器时长记成了自己头上的一笔账,这周的进度贴也主动认领了下来。沈砚把改动收进仓库时,看见客户那边又追加了一条留言,问这段视频以后能不能配上声音——回声连语音这条线都还没正式开工,这条留言先搁在那儿,没人回。
系统结算 · 本章考点
- Wan给模型输入多开的通道:视频自己的噪声潜变量占16个,参考图编码出来的也占16个,掩码按时间压缩步幅折成4个,三项加起来共36个,比原来宽出20个
- 新投影层必须先归零:微调起点的模型行为要和原来的文生视频模型逐位相同,新增的这部分能力只能靠后续训练慢慢长出来,不是一开始就搅进去
- LTX-Video走的是另一条路:把整段序列共享同一个时间步这条假设放开,让条件帧的token单独设成一个接近0的时间步,不需要新增任何参数
- HunyuanVideo的token替换只保证第一帧的像素被原样保留,管的是重建,不是理解,所以还要单独做一次语义注入才补齐
- 画面和声音如果先出一个再配另一个,声音只能靠画面生成完之后留下的摘要去猜,摘要压缩时已经丢掉的时间精度补不回来
把一张你自己拍的照片喂给一个训练好的文生视频模型,让它接着往下演——模型会愣住。它从出生到现在,见过的输入只有一份纯噪声和一句话,从没见过「这一帧已经定了」这种要求。这一章看三个团队,怎么各自把这道从没被问过的问题,接出一条真正能用的线路。
学完这一章你应该能做到
- 说清为什么一个训练好的纯文生视频模型,不能直接被要求「接着这张照片往下演」
- 说清Wan I2V怎么用通道拼接+掩码接入条件图,零初始化的投影层解决的是什么问题
- 说清LTX-Video的逐token时间步方案,为什么是三种接法里最省事的一种
- 说清HunyuanVideo为什么在token替换之外还要单独做一次语义注入,以及LTX-Video为什么把patchify和最后一步去噪都搬进了VAE
- 说清音频和画面为什么必须在同一次前向里一起生成,事后配音配不上的根本原因在哪
- 说清「开着权重」「能商用」「完全开源」是三件不同的事,分别在哪些模型上遇到过坑
这一层要给采样这一步开一个口子:允许序列里不同的token各自带着自己的时间步,第一帧对应的token可以被钉在一个很低的时间步上,其余token照常参与去噪。前12级造出来的每一步,还都是整段序列共享同一个时间步在走。
13.1 凭空生成和接着这张照片往下演,是同一件事吗
前面12章训出来的模型,不管是图像还是视频,起点永远是同一件事:一份纯高斯噪声,加一句文本描述,然后一步步去噪出结果。现在有人拿着一张真实照片说「让这张照片动起来,接着往下演」。把这张照片直接当成第一帧塞给这个训练好的模型,你猜会先卡在哪一步?
答完再看
训练好的文生视频模型,网络结构里没有一处是为「这一帧已经确定,其余帧待生成」这种输入设计的。它见过的每一个训练样本,全部token从头到尾都是待去噪的对象,没有哪个token被标记过「我已经是答案,不用你猜」。要接上这张照片,先得给模型开一个能接收这句话的入口,这是效果或计算量之前,必须先解决的问题。
这不是一个新问题,是这本书第一次真正遇到「部分已知、部分未知」的生成任务。前面十二章的训练目标从来只有一种:给一段带时间轴的噪声轨迹,学会把它变回数据。图生视频要求的是另一件事——同一条轨迹里,有一部分(第一帧)从起点就已知道答案,其余部分仍然从噪声开始。模型要同时做到两件事:完全保留已知的那部分,又不能让已知和待生成的部分脱节成两段互不相关的内容。
纯文生视频:整段都是自由变量
每一个token从t=1的纯噪声出发,谁都不比谁更「确定」,模型只需要学会一件事:怎么把整段噪声变回一段连贯的视频。
图生视频:一部分已经钉死
第一帧对应的token从一开始就有确定答案,其余token仍然从噪声出发,模型要学会的是另一件事:怎么让确定的部分引导不确定的部分,还不破坏确定的那部分。
差别不在生成的东西看起来像不像,在训练时模型有没有见过「一部分token的答案从一开始就已知」这种情况——纯文生视频的训练数据里,这种情况从没出现过。
接下来三节要看的,是三个团队对「怎么把这部分已知信息接进去」给出的三种完全不同的答案:Wan给模型的输入多开几条通道;LTX-Video放开「所有token共享一个时间步」这条从没被要求过的假设;HunyuanVideo一边替换token、一边单独给模型补一次语义理解。三条路解决的是同一个问题,但没有一条是显而易见的。
如果只是想让生成的第一帧「看起来」和参考图相似,训练时在损失函数里加一项「生成的第一帧要接近参考图」,不改模型的输入结构,这和这一章要讲的三种方法比,缺了什么?
变式:如果硬约束和损失函数同时用,会不会比只用硬约束更好?这样做多出来的成本是什么?
13.2 多出来的通道,怎么才能不搅乱原来的模型
Wan给出的第一种答案,思路很直白:把条件图编码成和视频latent同样形状的东西,沿通道这一维直接拼上去,让模型的输入变宽。参考图先和一串全零帧沿时间轴拼起来,凑成一段和目标视频等长的引导视频,送进Wan-VAE编码,得到条件latent z_c;再准备一份二值掩码,标出哪些位置已知、哪些待生成,按VAE的时间压缩倍数重新排列,和视频本身的噪声latent z_t一起,沿通道轴拼成一个更宽的输入。
这样一拼,模型的输入通道数就变了。原来的T2V模型只认c个通道,现在z_t、z_c各占c个通道,掩码再占s个通道,总数变成2c+s,比原来宽出一倍还多。多出来的这些通道得有一层新的投影,才能接进模型原本的隐藏维度,而这层投影是随机初始化还是另有讲究,决定了微调一开始模型会不会被带偏。
自己推一遍:多出来的通道到底有多少
z_t是视频本身的噪声latent,通道数是多少?直接对应Wan-VAE的潜通道数。
想好了再看
c=16,和T2V模型原本吃的通道数一样。
z_c是条件图编码出来的latent,用的是同一个Wan-VAE,通道数是多少?
想好了再看
同样是16——z_c和z_t走的是同一个编码器,形状规则完全一致。
掩码原本每帧一个二值通道,要重新排列成s个通道,s是Wan-VAE的时间压缩步幅,这个数是多少?
想好了再看
4——Wan用的是4×8×8时空压缩,时间方向压缩4倍,掩码沿时间方向每4帧折成1组,对应s=4个通道。
把z_t、z_c、掩码三部分的通道数加起来,总的输入通道数是多少,比原来宽了多少?
想好了再看
16+16+4=36,比原来的16多出20个通道,超过原来宽度的两倍。这么大的一次输入结构改动,处理不好很容易在微调刚开始就把模型原本练好的T2V能力搅乱。
新投影层随机初始化
微调刚开始,多出来的20个通道会被一层随机权重投影成一堆随机的隐藏状态扰动,直接叠加进模型原本调好的表示里——相当于在一个训好的模型上,一开始就强行灌进去一份噪声。
新投影层零初始化
投影层权重全部设成0,微调刚开始,多出来的通道不管取什么值,投影出来的结果都是0,对隐藏状态毫无影响——模型行为和原来的T2V模型逐位相同,新增的这部分参数靠后续梯度更新慢慢学会怎么用这份新信息。
差别在微调的起点:一个从被随机扰动的模型开始爬坡,一个从和原模型完全等价的起点开始,只往前走,不用先花力气修复自己造成的损伤。
零初始化(zero initialization):把新增的投影层权重全部设为0,使微调起点的模型行为与新增输入无关、与原模型完全一致,新增能力靠后续训练逐步长出来,不是一开始就介入。
如果新投影层不是接收视频条件,而是接收一个和视频内容完全无关的随机向量(比如当天的日期编码),零初始化还有没有必要?
变式:如果不用零初始化,而是把新投影层的输出结果在训练最初几步人为乘上一个从0慢慢增大到1的系数,效果会不会和零初始化差不多?
掩码和通道拼接负责的是细节这个维度——它告诉模型哪些像素是钉死的、要原样保留。Wan还单独加了一条通道,处理大意这个维度:把条件图送进CLIP的图像编码器,抽出一份全局特征,经过一个三层MLP投影,通过独立的一路交叉注意力注入模型——这一路和文本走的是同一种机制,只是喂的是图像的全局语义,不是逐像素细节。两条通道分工不同,一条保真,一条达意。
13.3 一段视频里,非要所有token共享同一个时间步吗
从第3章的前向加噪公式到第11章的视频patch化,这本书里每一次出现时间步t,都是一个数,广播给一个样本里的全部token——同一时刻,一段视频里的每一帧、每一个token,共享同一个t。LTX-Video要做图生视频,提的问题是:这条共享同一个t的规矩,是数学上的必须,还是只是图省事的简化?如果放开它,让条件帧的token设一个接近0的时间步、其余token设1,同一个训练好的去噪网络,要不要新增参数(比如新的输入通道、新的投影层)才能用?
答完再看
不需要新增参数,但需要新的训练经历。LTX-Video论文自己说得很直接:所有token共享同一个timestep只是DiT和Pixart-α沿用的一种简化,不是必须的规矩——放开它,允许每个token有自己的timestep和对应的加噪程度,用的还是原本那套按timestep产生缩放偏移的机制。但模型要学会怎么用这份每个token时间步不一样的信息,还是得在训练时真正见过这种输入分布,不是完全零样本泛化过来的。
这份不需要新增参数,正是这个方案被称为三条路里最省事一条的原因。Wan要多开2c+s个通道、接一层新投影、还要另配一路CLIP加交叉注意力;HunyuanVideo要做token替换、外加一整套语义注入模块。LTX-Video要做的只是把「一个样本一个t」放宽成「一个token一个t」,把原本给整个样本用的那份机制,原样搬给每个token单独用一份,模型结构一行没改,模型见过的参数量一个没多。图生视频能力不是靠加零件换来的,是靠放松一条从没被要求过的假设换来的。
Wan:新增一整套结构
新通道(2c+s)、新投影层、新的CLIP图像编码器、新的一路交叉注意力——条件图接入靠的是往模型里加零件,需要零初始化小心保护微调起点。
LTX-Video:放松一条假设
不加任何新通道、新投影层,只是把一个样本一个时间步放宽成一个token一个时间步,复用的是原本处理时间步的那套机制。
差别在解决问题的层面:一个在模型结构上做加法,一个在训练假设上做减法——少一条限制,换来的是同一套参数直接多出一种能力。
答辩:如果我是审稿人
不需要新增参数听起来像是免费的午餐,但LTX-Video的训练过程里仍然要偶尔把第一帧token的时间步设成一个小的随机值——这意味着这个能力得从预训练阶段就规划进去。如果拿一个已经训完、从没见过这种输入分布的纯T2V模型,事后能不能直接补上这种能力?
参考防守(先自己组织语言再看)
大概率不能直接补上,这正是免费这个说法要打折扣的地方。Wan的方案虽然要新增结构,但正因为新结构是独立的一块,可以在一个已经训完的T2V模型上单独做一次微调,不用碰原来的参数分布;LTX-Video的方案不新增任何结构,但正因为复用的是原模型处理时间步的同一套参数,要让这套参数学会应对每个token时间步不同这种全新的分布,很可能需要更早介入训练过程,而不只是训完之后再补一课。两条路省的不是同一样东西——一个省的是要不要新增结构,一个省的是能不能事后再补。
第8章的整流流插值公式是z_t=(1−t)z_0+tε。如果条件帧的token被设成t=0,代入这条公式,z_0这一项和ε这一项的系数分别是多少?这对「条件帧要不要被无损保留」这件事意味着什么?
变式:如果t设成一个很小但非0的值,比如0.001,z_t和z_0之间的差距量级大概是多少?
13.4 只做token替换,模型就懂这张图在说什么了吗
HunyuanVideo接图生视频的第一步和LTX-Video的思路不太一样,走的是token替换:把参考图编码出的latent,直接当成第一帧的latent使用,对应的时间步设成0,其余帧的处理和T2V训练完全一致。这一步单独看,和LTX-Video条件帧t设成0利用的是同一条整流流插值公式在t=0时的精确性质。
但只做到这一步还不够。token替换保证的是第一帧像素细节被原样保留,管的是重建,不是理解——模型能看到这帧latent,不代表能像理解文本提示那样,读懂画面里是什么内容、接下来大概率会发生什么。一份为像素重建优化的VAE latent,不天然包含「这是什么」这层语义信息。
如果只做token替换、不做任何补救,让HunyuanVideo接一张「一只猫蹲在窗台上」的照片,生成的视频最可能在哪个方面出问题:第一帧的像素还原度,还是后续帧的动作合理性?
变式:反过来,如果只有语义理解、没有token替换,会先在哪个方面出问题?
HunyuanVideo的做法是单独补一条语义通路:把参考图送进模型本来就用来编码文本提示的那个多模态大语言模型,取出一份语义图像token,拼接进视频latent token序列里,一起参与全量注意力。这条通路和token替换各管一段:token替换保重建、保细节;语义注入保理解、保内容的连贯性。
Token替换:管细节重建
参考图latent直接当第一帧使用,时间步设为0,保证的是像素级的精确保留——这一帧长什么样,和参考图逐位一致。
语义注入:管内容理解
参考图经过多模态大语言模型提取语义token,拼接进注意力序列参与全局计算,保证的是模型读懂这张图里是什么内容,好让后续生成的帧和这张图的语义连贯,不只是像素连贯。
差别在管的是同一张图的两个不同侧面——一个负责长什么样,一个负责是什么,两条通路同时存在,才补齐了单独token替换缺的那一半。
13.5 patchify这一步,凭什么不能让VAE自己做
第11章讲过标准流程:VAE把像素压缩成latent,再由一个独立的patchify层,把相邻几个latent格子拼成一个token,送进transformer。典型配置是VAE做8×8×4的时空压缩、通道从3提到16,patchify再拼2×2×1,两步合起来是1:1024的像素:token比。LTX-Video的问题是:VAE本身就是一个可学习的压缩器,为什么还要在它后面再接一个不参与学习的拼接操作?
LTX-Video把patchify直接挪进VAE编码器内部,让VAE一步到位压缩到token粒度:32×32×8的时空压缩,配128个通道,不再需要额外的patchify层。算一下压缩率:每个token对应的像素是32×32×8=8192个,是典型方案1024或2048的四倍;把RGB三通道也算进去,一个token吃进32×32×8×3=24576个像素值,压缩到128个latent通道,24576/128=192倍,这就是论文说的1:192总压缩率,是典型方案1:48或1:96的两倍。
典型方案:VAE和patchify各管一段
VAE压缩到8×8×4、16通道(1:48),patchify再拼2×2×1个latent格子成一个token(1:1024像素:token)。patchify只是简单拼接,不参与训练,不负责进一步压缩冗余。
LTX-Video:VAE一步到位
VAE直接压缩到32×32×8、128通道(1:192),不再需要patchify层,像素:token比达到1:8192。整个压缩过程都在一个可学习的模块里完成。
差别在冗余是谁在消除:简单拼接不会主动减少相邻latent格子之间的冗余信息;让VAE自己学到token粒度,冗余能不能被进一步压掉,取决于这个可学习模块学得好不好——论文用PCA分析发现,训练越往后,各latent通道之间的冗余确实在持续下降。
典型方案里,patchify这一步本身有没有可学习的参数?把它搬进VAE、由VAE的卷积层直接完成同样的压缩,多出来的是什么?
变式:如果只是把patchify从transformer开头挪到VAE末尾,但patchify本身还是不带参数的拼接,冗余会不会因此减少?
把patchify交给VAE解决的是压缩率的问题,LTX-Video还顺手解决了另一个问题:压缩率越高,扩散采样留下的残余误差在解码时越容易变成明显的伪影,因为编码器把高频细节压得更狠,解码器要凭更少的信息把它们猜回来。LTX-Video的解法是让VAE解码器多干一件事:不只是把latent转换回像素,还承担最后一步去噪——解码器额外接收时间步作为条件,直接从一个还带着少量噪声的latent,一步输出干净的像素,而不是先在latent空间去噪完,再交给一个只管转换、不管去噪的解码器。
去噪解码器(denoising decoder):VAE解码器额外以扩散时间步为条件,在把latent转换成像素的同时完成最后一步去噪,直接输出像素空间的清晰结果,不把去噪和解码当成两个互不相关的步骤。
如果不用去噪解码器,仍然是「latent空间去噪到底、再交给普通解码器转换成像素」这条标准流程,对LTX-Video这种1:192高压缩比的VAE来说,最可能在哪个环节吃亏?
变式:如果VAE的压缩率不高(比如典型的1:48),去噪解码器带来的收益会不会像1:192这么明显?
13.6 画面和声音,能不能先出一个再配上另一个
HunyuanVideo已经证明了一种做法:先用文生视频模型把整段视频生成完,再用一个单独的视频到音频模型,看着已经生成好的画面(用CLIP在4fps的时间分辨率上抽取视觉特征),配上匹配的音效。这个组合能不能稳定做到,音效的时间点和画面里的动作精确对上——比如一步踩下去的脚步声,正好落在画面里脚接触地面的那一帧?
答完再看
很难稳定做到。HunyuanVideo的V2A模块用CLIP在4fps这个时间分辨率上抽取视觉特征,比原始视频的帧率粗糙得多——画面里精确到某一帧的动作时刻,经过这层压缩,音频模型拿到的只是一个模糊的时间窗口,不是精确的时间点。音频模型的输出帧率再怎么调,也补不回输入阶段已经丢掉的时间精度。
这不是HunyuanVideo这一个模型的问题,是先画面、后声音这个两阶段流程本身的结构性代价。声音生成模型只能通过画面生成完之后留下的某种摘要(CLIP特征、字幕,或任何形式的中间表示)去猜测发生了什么,这份摘要不管做得多精细,都是对原始时间轴的一次压缩,压缩就会丢信息。Ovi和LTX-2给出的方案是把这两阶段合并成一次:用一个模型,同一次前向,同时把画面和声音都生成出来,让两者在生成过程的每一步都能互相看到对方最新的状态,不必等一方彻底定型之后,另一方才开始工作。
Ovi:对称双塔
视频和音频各配一个5B参数的塔,两塔参数规模相同,通过跨模态注意力交换信息,把视频和音频当成两个同等重要、同等复杂的模态来对待。
LTX-2:非对称双流
视频塔14B、音频塔5B,参数规模明显不对称,把视频当成信息量更大、需要更多容量的主模态,音频作为规模更小的伴生模态,配合按模态区分强度的CFG。
差别在要不要假定两个模态同等复杂:对称设计假定画面和声音值得同等的建模容量,非对称设计假定画面天然需要更多容量——两条路都绕开了先画面后声音这个结构性时间对齐问题,只是在两个塔该不该一样大这个问题上给出了不同答案。
答辩:如果我是审稿人
同一次前向联合生成,时间对齐天然更好,这是一个结构上说得通的论证,但需要一次干净的对照实验才能坐实:同一份数据、同一个模型规模,一边训成联合生成,一边训成先画面后声音的两阶段流程,测同一套同步误差指标,两者才有可比性。这样的直接对照存在吗?
参考防守(先自己组织语言再看)
本章能拿到的材料里没有这样一份直接对照——Ovi和LTX-2报告的是联合生成方案本身的效果,HunyuanVideo的V2A报告的是两阶段方案本身的效果,两边用的模型规模、训练数据、评测指标都不是同一套,没法把「哪种流程更好」和「哪个团队训得更好」这两个问题分开。联合生成在时间对齐上更有结构性优势,这个论证本身站得住,但优势有多大这个量化问题,需要一次同条件对照实验才能回答,本章没有这样的证据。
对你而言未知联合生成到底比先画面后声音好多少
上面这场答辩指出的对照实验缺口,答案大概率不在本站的材料范围之内——Ovi和LTX-2的论文里,很可能各自报告了针对同步误差的量化评测,甚至包含和级联式基线的直接对比,只是这两篇论文本身没有被收进本站的参考材料,没法在这里逐句核实。
先做这一步:去读Ovi论文(arXiv:2510.01284)和LTX-2论文(arXiv:2601.03233)各自的实验部分,找有没有针对音画同步误差的量化指标,以及有没有拿级联式(先画面后声音)方案做对照基线;如果两篇都没有,再查有没有第三方评测做过这个对照。
如果把HunyuanVideo的V2A模块里CLIP特征的抽取频率,从4fps提高到和视频帧率完全一致(比如24fps),音画对齐问题会不会被彻底解决,变得和联合生成一样好?
变式:如果反过来,先生成声音、再让画面看着声音生成,会不会遇到同样性质的问题?
13.7 权重能下载,是不是就等于能拿去商用
这本书讲到的视频模型,开源二字底下藏着至少三个互相独立的问题:权重能不能下载?下载下来能不能商用?训练过程和数据是不是也公开、可复现?三个问题的答案经常并不一致,混着用开源这一个词,容易把「能看到权重」误当成「什么都能做」。
一个模型的权重可以直接下载,这件事本身能不能回答「下载下来能不能商用」和「训练数据、训练过程是不是公开」这两个问题?
变式:如果一个模型的训练数据和训练脚本完全公开、可复现,是不是就意味着它的权重一定可以免费商用?
训练成本这件事本身就很少被公开。Open-Sora 2.0是少数把训练成本拆开公布的项目:256px文生视频阶段用了2240个GPU-day,花费10.75万美元;256px图生/文生视频联合阶段384个GPU-day,1.84万美元;768px阶段1536个GPU-day,7.37万美元;三个阶段加起来4160个GPU-day,共计19.96万美元——按每GPU每天24小时、每小时2美元反算,4160×24×2=19.968万美元,和论文报的数字对得上。这份透明度在同类项目里并不常见,多数模型的训练成本只能靠社区自己估算。
| 模型 | 最低显存 | 备注 |
|---|---|---|
| Wan2.1-T2V-1.3B | 8.19 GB | RTX 4090上5秒480P约4分钟 |
| HunyuanVideo 1.5 | 13.6 GB | 720p 121帧,开offload+VAE tiling |
| Wan2.2-TI2V-5B | 24 GB | RTX 4090上5秒720P不到9分钟 |
| HunyuanVideo 1.0 | 45~60 GB | 540p约45GB,720p约60GB |
同样是视频生成模型,HunyuanVideo 1.0需要45到60GB显存,Wan2.1-T2V-1.3B只要8.19GB,差出来的这个量级,主要来自这一章哪条已经讲过的规律?
变式:如果两个模型参数量相同,仅仅是HunyuanVideo生成的分辨率更高、token数更多,显存差距会不会以同样的方式出现?
显存门槛从8GB到60GB,跨度接近八倍——同一类「消费级显卡够不够用」的问题,不同模型给出的答案差着一个数量级。许可证这边的跨度同样大,而且更容易被忽略。
| 模型 | 权重 | 具体限制 |
|---|---|---|
| Wan 2.1 / 2.2 | 可下载 | Apache 2.0,无地区、无用户规模限制 |
| Open-Sora 2.0 | 可下载 | Apache 2.0,训练成本与数据流程同时公开 |
| HunyuanVideo | 可下载 | 不适用于欧盟、英国、韩国用户;月活超1亿需单独申请许可 |
| MiniMax H3 | 部分可下载 | 美/欧/英/韩用户须单独申请;Context-IR与2K上采样器不在开源范围内,能拿到的只是基础生成模型 |
| LTX-Video | 可下载 | 代码仓库标Apache 2.0,权重本身另有一份单独的许可协议 |
LTX-Video的GitHub仓库标着Apache 2.0协议,如果只看仓库首页的这个标签,直接认为「权重也是Apache 2.0,可以随便商用」,这个推断错在哪一步?
变式:HunyuanVideo的权重可以直接下载、代码也能跑起来,是不是就意味着可以在任何国家商用部署?
读的时候要小心
关于本章数字的来路
Open-Sora 2.0的训练成本分阶段表格、消费级显存门槛表,都来自项目方自己公开的数字,没有第三方复现或审计。Ovi和LTX-2那一节的具体机制(对称/非对称双塔、跨模态注意力、按模态区分的CFG)来自公开资料对这两篇论文的转述,本站没有拿到这两篇论文本身做逐句核对;这两个模型的参数量和许可证信息则直接来自公开的模型清单。HunyuanVideo的语义注入机制本章按1.0版论文(多模态大语言模型路线)逐句核对写出,1.5版是否换成了别的视觉编码器,仅见于二手转述,未独立核实。
这一层要加什么:首帧条件注入
为什么现在才加它:前12级不管加不加稀疏注意力,采样这一步用的都还是整段序列共享同一个时间步这条规矩。这一层要放开它:允许每个token有自己的时间步,第一帧对应的token可以被单独设成一个很低的时间步,代表这部分已经知道答案,其余token正常参与去噪。
难点:这一层最容易踩的坑,是把t=0这一档的更新公式写成两个独立量相减、恰好在数学上抵消为0的形式——只要中间某一步涉及浮点乘除后再相减,结果就可能是1e-16量级的残留,不是真正的0。要保证t=0时误差精确为0,必须让t作为一个单独的标量因子,直接乘在整个更新项外面,t取字面上的0.0时,这一项在IEEE浮点里精确等于0,没有任何舍入残留。这里的一步更新是一次性跳到z_0的简化演示,用来验证每个token按自己的时间步决定改多少这条机制本身,不代表真实采样只用一步——真实采样仍然是多步、小步长地往前走。
自己验:把t设成0,first_frame_step对is_first这些token的输出必须和输入逐位相同,误差精确为0(不是1e-16这样的浮点残留);把t依次设成0.3、0.5、0.7、0.9,输出和输入的差值(L2范数)应该单调递增,因为这一项就是t乘以预测的速度场,t越大,被乘的部分自然越大。
留给你的问题
这一章的三种接图方法——通道拼接、逐token时间步、token替换+语义注入——都是在一个已经训练好的纯文生视频模型上做手术,用的是三条完全不同的技术路线。如果从零设计一个视频生成模型,从第一天起就明确知道以后既要支持文生视频、也要支持图生视频,这三条路里,你会选哪一条当作「原生」设计的起点?选定之后,另外两条路还有没有存在的必要?
不看材料,写下这一章的三堵墙
第一堵:以为喂一张图当条件只是简单地把图片塞进去当额外输入,没意识到这背后至少有三种完全不同的技术方案,各自要解决模型「听得懂」和「不忘记原来的自己」这两个问题。第二堵:以为所有token共享同一个时间步是扩散模型的硬性规定,没想到这只是一个从没被要求过的简化假设,放开它就能换来图生视频能力,不需要新增任何参数。第三堵:以为先出画面、后配声音只是工程流程上的先后顺序问题,没意识到时间对齐一旦错过在同一次前向里被联合决定的机会,就没有办法再无损地补回来。
第14章 声音在计算机里是什么
开篇故事第 14 集 · 进度条卡在百分之二,靳工这次没有答案这模型也太真实了
进度条停在百分之二,已经四十秒没有挪动过——这是回声复现的第一百一十天,语音那条线刚开工接的第一版原型,沈砚只让它合成五秒钟的一句问候语,屏幕右下角的预计剩余时间却还在往上跳,从最初的几分钟一路涨到二十多分钟,还没有要停下来的意思。
系统提示当前合成速度实时率0.003,即生成1秒音频约需333秒,本次目标时长5秒,预计总耗时约1667秒,尚未完成。
机房这几天刚给语音那条线腾出一小块地方,模型选的是最早能把这件事做出声音的老路子——逐个采样点地往下猜,每猜一个新的点,都要先把前一个点的真实取值喂回去才能往下走,一秒钟的音频在十六千赫下就是一万六千个这样的点,一次都不能省,跑起来自然是眼前这副样子。
显存刺客盯着那根不动的进度条,随口问了一句:“等这条线跑顺了,以后要不要顺手把音乐也接了?反正压扩量化这一套东西现在就得写,一起写不是更划算。”靳工端着杯子想了几秒,说:“这套压扩曲线是照电话语音的统计特性调出来的,音乐的动态范围跟语音不是一回事,打击乐那类忽然拔高的瞬态未必吃这一套。我没查到有谁真拿音乐去对照测过,这个我答不上来。”
这是沈砚进组这些天头一次听见靳工说这种话,往常不管问什么,他多半只答该答的那一条,从没说过材料里没有。屋子里安静了两秒,倒是有人先在论坛上开了腔。
这套用来压扩语音的μ-law公式,换成音乐还照样管用吗
1L炼丹废人都火烧眉毛了,进度条卡在百分之二动都不动,现在纠结音乐能不能用这套曲线是不是想岔了地方。
2L复现之鬼没人验证过的事,与其在这儿干猜,不如挑一段打击乐自己跑一遍编码解码,把误差摆出来才作数。
3L沈砚不对,两件事不冲突,反正压扩这几行代码今天就要写,顺手挑一段音乐跑一下量化误差,花不了几分钟。
4L显存刺客那我把打击乐那段素材先传上去,等这版原型总算跑完,正好可以顺路验一验。
又等了小一刻钟,那五秒钟的问候语总算合成完,放出来的声音是对的,可这一趟三百多秒的等待谁都不想再来第二遍。沈砚把这次的耗时记进了语音线的第一条进度贴,标题只写了一句还没想好怎么往下接的话:这个速度,明天见客户该怎么交代。
系统结算 · 本章考点
- WaveNet逐点自回归,生成1秒16kHz音频要老实做满16000次前向,一次都省不掉,谁也没法只算一半
- HiFi-GAN论文测过它在V100上的实时率只有0.003,也就是生成1秒音频要在GPU上跑333秒
- 合成5秒的目标语音,照这个速度算下来要等约1667秒,接近半个小时才能收到声音
- 扩张因果卷积让感受野指数增长,10层一个块能看到1024个点,三块首尾相接,论文报告的最终感受野是240毫秒
- 16位整数存的波形本有65536种可能值,μ-law先把它压扩量化到256档,才轮到softmax接手
戴上耳机,或者点开手机里录音笔存的一段话,你听到的是一段连续的声音,没有格子,没有像素,似乎也没有什么可以拆开来数的东西。但只要这段声音能存成一个文件,它在计算机里就必须是一串数字——前十三章画的每一张图,最后也是一堆数字,只是这堆数字排列的方式,和声音完全不是一回事。
学完这一章你应该能做到
- 说出一段 16kHz、24kHz 录音每秒钟各是多少个数,并说清这堆数字为什么和图像的数字个数不能直接比大小
- 讲清 WaveNet 逐样本点自回归生成波形的做法,算出它单个残差块的感受野,并说出这套做法付出的代价具体是多少
- 写出 μ-law 压扩公式,算出给定幅度对应的档位,解释「量化」为什么不是把区间均匀切开
- 说清拼接合成和统计参数合成各自撞在哪堵墙上,以及这两堵墙如何解释了 WaveNet 出现时的分量
这一层要给建造台阶换一种原料:从图像的像素格子换成声音的波形数组。前十三级处理的数据,不管压缩成什么样,骨子里都是空间格子;这一级要接入一条完全没有空间、只有时间的一维数组,还要让读者亲手验证量化这件事到底在干什么。
14.1 一段录音在电脑里到底是什么
声音本身是空气压强随时间的起伏。有人说话、乐器振动、喇叭震颤,都是在推动周围的空气一下一下地压缩和舒张,这股压强变化以固定速度向外传播,撞到耳膜或者话筒的振膜上,让它跟着一起振动。录音要做的事情很直接:用话筒把这股连续变化的压强变成一段连续变化的电压,再每隔很短的时间量一次这个电压,记下一个数。量得越勤,记下来的这串数字就越能还原原来那条连续曲线的细节,这个「量得多勤」就是采样率,单位是赫兹,表示每秒钟量多少次。
数字给到具体的场景里才有分量。16kHz 是很多语音识别系统常用的采样率,意思是一秒钟量 16000 次,每次记一个数,一秒钟的录音就是 16000 个数排成一条线,正是第0章已经算过的那句话。24kHz 是 Tacotron 2 这类高质量语音合成常用的采样率,一秒钟是 24000 个数。把这两个数字摆到第0章那张 512×512 的彩色照片旁边——那张照片是 786432 个数——一秒钟的语音听起来是小巫见大巫:16000 也好,24000 也好,都只是那张照片零头的零头。
一段 5 秒钟、24kHz 单声道的语音,总共是多少个数?和第0章那张 512×512 的彩色照片比,谁的数字个数更多,多多少倍?
变式:如果录成立体声(左右两个声道各自独立采样),同样 5 秒 24kHz,总数字个数变成多少?这时候和照片比,倍数关系有没有变?
采样率(sampling rate):每秒钟对连续信号测量的次数,单位赫兹(Hz)。16kHz 表示一秒钟测 16000 次,测得越勤,记录下来的数字越能贴近原始的连续曲线。
第0章还说过一句更扎心的话:图像好歹有高、宽、通道三个轴可以互相参照,声音只有时间这一个轴,每个时刻孤零零一个数,不知道自己往左往右挨着的是谁在做什么形状。这条只有一个轴、数字个数看起来又不多的数组,会在下一节暴露出它真正的麻烦——麻烦根本不在「数字有多少个」,而在「这些数字必须按什么顺序被造出来」。
14.2 为什么不能像图像一样直接把波形画出来
前面十三章已经会用扩散模型直接从噪声画出一张图的每一个像素。语音的数字个数比图像还少,那能不能照搬同一套办法,直接从噪声「画」出波形上的每一个采样点?你觉得会撞在哪里?
答完再看
第三个选项点到了真正的麻烦。图像的像素之间当然也互相关联,但一次去噪的前向计算可以把一整张图的所有像素同时算出来;波形不是这样——下一节马上会看到,最早在这件事上做出实质突破的 WaveNet,选择的做法是让每一个采样点的生成都直接依赖前面所有采样点,这条依赖链一旦选定,就注定了生成的方式没法把所有采样点摆在一起同时算完。第二个选项猜错了方向:波形的数值经过 14.3 节要讲的处理后,范围反而比像素更规整。
认真走一遍这条最朴素的路:把生成一段波形,看成逐点预测的问题——给定前面已经生成的所有采样点,网络输出下一个采样点最可能是什么。这正是 2016 年 WaveNet 的做法,论文把整段波形的联合概率,直接写成一串条件概率相乘:p(x) = ∏t p(xt | x1,…,xt−1)(WaveNet 式 1)。每一个采样点的分布,都要看着它前面所有已经出现过的采样点来定。这套写法有个名字。
自回归(autoregressive):把一整条序列的生成,拆成一步接一步的条件预测——每一步都以前面已经生成的全部内容为条件,预测下一步该是什么。
问题来了:给定前面几万个采样点,网络要怎么看到足够长的历史,才能预测得准?最简单的卷积网络,每层只能把感受野往外扩一点点,摞很多层才能看到够长的历史,层数很快就会多到训练不动。
自己推一遍:把感受野堆到 1024 个采样点,要花多大代价
最普通的卷积,卷积核宽度为 2(只看当前和前一个点),每加一层,感受野往前多扩多少个点?堆 10 层能看到多远?
想好了再看
每加一层多扩 1 个点,10 层只能看到 11 个点,这是线性增长,离「看清上千个点的历史」差得很远,真要够到 1024 个点,需要一千多层卷积,训练和推理都吃不消。
如果每一层不再看相邻的点,而是每隔一定间距跳着看——这个间距逐层翻倍(1, 2, 4, 8, …),10 层能看到多远?
想好了再看
感受野变成指数增长。间距翻倍到第 10 层时是 512,累加起来整个块的感受野正好是 1024 个采样点——这就是 WaveNet 论文里那句「1,2,4,…,512 这样一个块,感受野是 1×1024 卷积的等价物」(§2.1)。同样是 10 层,指数增长比线性增长在感受野上差了近百倍。
这种跳着看、但输出位置数量不变的卷积该叫什么名字?它和第 6 章、第 12 章里出现过的「跳过一部分」是同一回事吗?
想好了再看
论文管它叫空洞卷积(dilated convolution),也叫 à trous 卷积。它和跳过计算不是一回事——空洞卷积仍然算完了每一个输出位置,只是每个输出位置看到的输入是稀疏采样的,不是相邻的一小块;感受野呈指数增长,但每层的计算量和普通卷积基本持平,这是它被选中的根本原因。
WaveNet 把这个 1,2,4,…,512 的膨胀率循环堆了 3 遍(论文用于 TTS 的配置),总感受野变成多大?换算成时间,240 毫秒对应大约多少个 16kHz 的采样点?
想好了再看
3 个这样的块首尾相接,总感受野是 3×1024=3072 个采样点左右(实际感受野的精确计算还要算上每层卷积核宽度带来的少量额外增量,论文报告的最终值是 240 毫秒)。240 毫秒在 16kHz 下是 3840 个采样点,这就是 WaveNet 用于 TTS 任务时,网络在预测下一个采样点时能回看的全部历史——不到四分之一秒。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| ∗ | 卷积操作 | 用一小段权重滑过整条序列做加权求和 |
| Wf,k、Wg,k | 第 k 层的两组可学习卷积核,一组叫滤波、一组叫门 | 一组决定「内容」,一组决定「开放多少」 |
| σ(·) | sigmoid 函数,输出压在 0 到 1 之间 | 门的开合程度 |
| ⊙ | 逐元素相乘 | 门控——门开多大,内容就通过多少 |
感受野的问题解决了,下一步是要不要用扩张因果卷积把每一步都算成一个具体的实数。这里论文没有选最直接的均方误差回归。
给定前面所有采样点,如果直接用均方误差训练网络去回归下一个采样点的数值(输出一个实数,损失是它和真实采样值之差的平方),用第2章的道理说清这样做会遇到什么问题,以及为什么 WaveNet 论文最终选择让网络输出一个 256 档的分类分布(softmax),而不是一个具体数值。
变式:如果把 256 档分类换成一个混合高斯分布(比如 10 个高斯分量的加权和),这样做能不能部分缓解均方误差回归的问题?它和 256 档分类比,各自对分布形状做了什么假设?
类别分布要求每一步给全部可能值都算一个概率,16 位整数存的波形有 65536 种可能值,这个数量级直接输出softmax 层会太大。WaveNet 用了一次非线性压扩,把取值范围先收窄到 256 档,14.3 节马上要讲这一步具体怎么做。用这套完整方案训出来的 WaveNet,在 Table 1 的主观评分里第一次把合成语音的自然度推过了拼接合成:英语上 WaveNet 拿到 4.21 分,同一批测试里最好的拼接合成基线是 3.86 分,统计参数合成的基线只有 3.67 分(满分 5 分的 MOS,§3.2)。
| 系统 | MOS |
|---|---|
| LSTM-RNN 统计参数合成 | 3.67 ± 0.098 |
| HMM 拼接合成 | 3.86 ± 0.137 |
| WaveNet(语言特征 + 基频) | 4.21 ± 0.081 |
| 真人录音(16-bit PCM) | 4.55 ± 0.075 |
一步扩散:一整张图同时算完
图像扩散的每一步去噪,网络一次前向就能把一整张图的所有像素同时输出,采样一张图只需要几十次这样的前向,和图有多少个像素几乎没关系。
逐点自回归:算完一个才能算下一个
WaveNet 生成第 t 个采样点,必须先拿到第 t−1 个采样点的真实取值(推理时是刚生成出来的那个值)才能往下算,16kHz 意味着一秒钟的音频要老老实实做满 16000 次这样的前向,一次都省不掉。
差别在:图像扩散把「同时处理很多个位置」这件事放进了单次前向内部;WaveNet 把「依次处理很多个位置」这件事焊死在了生成过程的结构里,两者付出代价的方式完全不同。
这笔代价有多大,HiFi-GAN 论文的 Table 1 专门测过:在一块 V100 GPU 上,WaveNet(混合逻辑斯谛输出版本)合成语音的速度只有 0.07kHz,也就是实时率 0.003——生成 1 秒钟的音频,要在 GPU 上跑 333 秒(这一行的 CPU 那一栏论文原文是空的,没有测;本站核对过原始表格,速度数字标在 GPU 那一栏,不是 CPU)。GPU 本该最擅长同时处理大量数据,但一整块 GPU 在这里几乎是闲置的——它能同时算的是一层里的很多个卷积核,算不出「还没生成的下一个采样点」,16000 次前向必须一次接一次地排队做完,这正是自回归结构焊死的那条串行链条。这个数字,就是第 1 章埋下的「实时」这堵墙第一次真正现身,也是接下来几章一直在想办法拆掉的东西。
用 HiFi-GAN Table 1 给出的 WaveNet 实时率 0.003,估算生成一段 5 秒钟音频大概要多久。
变式:HiFi-GAN V1 在同一块 V100 上的速度是 3701kHz(×167.86)。用它生成 5 秒钟音频要多久?比 WaveNet 快多少倍?
14.3 μ-law:256 个档位怎么分给一个连续信号
波形的幅度是一个连续的数,范围在 −1 到 1 之间(缩放之后)。现在要把这个区间切成 256 个档位,好让 softmax 输出一个类别。最省事的切法是把 −1 到 1 等距切成 256 份,每份宽度一样。你觉得这样切,对最终合成出来的声音质量会有什么影响?
答完再看
第一个选项说中了要害。语音波形的幅度分布并不均匀——大部分时间信号都很轻,只有少数爆破音、重音的瞬间才会接近满幅度。均匀切割会把 256 个档位平均分给整个 −1 到 1 区间,不管这个区间里有没有信号真的落在那儿,结果是最常出现的小幅度段只分到很少几个档位,量化误差在这些最常见的地方最大。第三个选项猜错了原因,档位数量不管怎么切都还是 256,计算量不会因为切法不同而变化。
WaveNet 的解法不是重新设计量化的档位数,而是在量化之前先用一个非线性函数,把小信号「拉伸」、把大信号「压缩」,让原本挤在 0 附近的大量小幅度样本被拉开,分到更多的档位。这个非线性函数来自电话通信里的老技术,论文原文写成 μ-law 压扩变换(式 1)。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| xt | 原始波形在第 t 个采样点的幅度,−1 到 1 之间 | 压扩之前的真实信号 |
| sign(xt) | 取符号,正负各自单独处理 | 只压缩幅度,不改变方向 |
| μ | 压扩强度,WaveNet 取 255 | 越大,小信号被拉伸得越厉害 |
| f(xt) | 压扩之后的值,仍在 −1 到 1 之间 | 拉伸小信号、压缩大信号之后的新坐标 |
均匀切:256 档平分整个区间
−1 到 1 平均分成 256 份,每份宽度都是 1/128,档位怎么分和数据实际落在哪儿没有关系。
μ-law:档位跟着数据的常住地走
先用式 1 把小信号拉伸、大信号压缩,再均匀切 256 档。小幅度区被拉伸后占了坐标轴的大半,实际分到的档位数远超均匀切法。
差别在:均匀切法假设所有幅度出现的机会相等,μ-law 假设小幅度出现得更频繁——这个假设站不站得住,决定了它划不划算。
把几个具体的幅度代进式 1 就能看出这条曲线有多不客气。幅度只有 0.01 的一个很轻的信号,压扩之后是 0.2285,对应到 256 档里的第 157 档;幅度到 0.5 的一个响亮得多的信号,压扩之后是 0.8757,对应第 239 档。0.01 只是满幅度的百分之一,却已经被分到了超过一半档位之后的位置——256 个档位里,前 157 档全部留给了幅度不到 0.01 的那一小段区间,因为语音绝大部分时间的能量就落在这一小段里。
μ-law 压扩是针对语音的幅度分布特点设计的:大部分时间信号很轻,少数时刻很响。构造一种信号,它的幅度分布和语音正好相反(大部分时间接近满幅度,很少落在小幅度区),这时候用 μ-law 量化,比均匀量化还会更差吗?
变式:如果把 μ 从 255 调到接近 0,压扩曲线会趋近于什么?这时候它和均匀量化还有区别吗?
14.4 拼接和参数合成,各自撞在哪堵墙上
在 WaveNet 之前,如果不能用神经网络逐点生成波形,要让机器读出一句话,你会怎么办?
答完再看
第一个选项是历史上真实发生过的两条路,而且都统治过生产环境很多年。第三个选项低估了这段历史——Tacotron 2 论文的引言开篇就说,拼接合成「多年来一直是最先进的方法」(§1)。第二个选项弄错了方向:这两条老路都刻意避开了「逐点预测波形」这件事,因为在深度网络能撑得住之前,这件事在算力上几乎不可行。
拼接合成的思路很朴素:先录一个足够大的语料库,把它切成一个个小单元,需要合成一句新话时,用动态规划从库里搜出一串代价最小的单元序列拼接起来。这条路的天花板很清楚——库里没有的韵律组合,无论怎么搜都拼不出来;单元和单元的接缝处,音高、响度、频谱经常对不上,拼接处的不连续听得出来;换一个新的说话人,几乎要把这一整套语音库重新录一遍。
统计参数合成换了一种思路:不存语音波形本身,存一套参数化的声学模型(通常基于源—滤波器假设,把发声过程简化成一个激励源经过一个频谱滤波器),用 HMM 或者神经网络逐帧预测这些参数,再用声码器合成波形。它的天花板出在两处:源—滤波器假设本身把复杂的发声过程简化得太厉害;逐帧独立预测又缺了帧与帧之间的强约束。Tacotron 2 论文对这条路的评价很直接:合成出来的音频「经常显得沉闷、不自然」(§1)。
拼接合成:像素级真实,但拼不出库里没有的东西
每一个用到的片段都来自真人录音,音质天花板很高,但代价是数据库要足够大、拼接处要处理得足够细,换音色的成本几乎等于从头再录一遍。
统计参数合成:能插值,但先天带着糊
模型小、能在没见过的文本上插值出合理的参数,但源—滤波器假设和逐帧独立预测这两层简化叠在一起,合成出来的声音天生带着一种被平均过的、不锋利的质感。
差别在:拼接合成的墙在「库里没有的东西造不出来」,统计参数合成的墙在「造出来的东西本身就不够锐利」——两条路殊途,但都没有触碰「直接对波形建模」这件事。
如果只有一个新说话人几分钟的录音,要求当天就能合成出这个人的语音,拼接合成和统计参数合成,你会建议选哪一条路,为什么?
变式:反过来,如果录音库有上千小时、要求音质做到极致、不在乎准备时间,你会怎么选?
这正是 WaveNet 被看作一次转折的原因。它既不用查库拼接,也不做参数化的中间层,直接对波形本身的联合分布建模,Table 1 那 4.21 分不只是比某一条老路高一点,而是第一次让「直接生成波形」这件事,在质量上反超了两条经营多年的老路。代价当然也摆在明面上——14.2 节算过的那 333 秒,直到第 20 章才有真正意义上的解法。
答辩:如果我是审稿人
WaveNet 的感受野只有 240 毫秒,论文自己也承认,语言特征版本的 WaveNet 有时会把重音放错地方,原因可能是基频轮廓这种长程依赖,240 毫秒的感受野抓不住(§3.2)。既然感受野是这套架构的硬伤,为什么不干脆把膨胀率再往上堆,把感受野做得更大?
参考防守(先自己组织语言再看)
膨胀率每翻一倍,感受野翻一倍,但训练和推理的成本并不是免费的——层数越多,每一步自回归前向要算的计算量也越大,而 14.2 节已经算过一次前向要连着做一万六千次。感受野和推理速度在这套架构里是直接对撞的两个目标,不能既要 240 毫秒的四十倍长,又要保持同样的速度。论文自己给的解法是外部再接一个跑在更低频率(200Hz)上的基频预测模型,专门负责长程的音高轮廓,让 WaveNet 只需要在局部把细节做对——这是把一个长程问题挪到另一个更适合处理长程信息的模型上,而不是硬撑着让同一个自回归网络把所有时间尺度都扛下来。
对你而言未知μ-law 这套曲线是为电话语音设计的,对音乐是不是最优的
μ=255 这个具体数值和 256 档这个量化精度,来自 ITU-T 的电话通信标准,专为人类语音的统计特性调校。WaveNet 论文也训练过音乐生成,用的是同一套 μ-law 压扩。音乐的动态范围和幅度分布跟语音很不一样——打击乐的瞬态、多件乐器叠加的峰值,未必符合「大部分时间幅度很小」这个前提。μ-law 对音乐是不是仍然是最优的压扩曲线,本站没有查到专门针对这一点的对照实验。
先做这一步:在本章的量化实验室里,把输入换成一段打击乐或者多乐器混音的波形,对比 μ-law 和均匀量化各自的量化误差分布,看看误差最集中的区域是不是还落在小幅度那一侧;再去查有没有专门为音乐设计的压扩曲线(比如 A-law 或者其他行业标准),对照它们的参数选择依据。
这一层要加什么:把数据换成波形
为什么现在才加它:前十三级不管数据形状怎么变,本质上都是空间格子,压缩、patch 化、加时间轴都是在格子的基础上做文章。这一级要接入一种完全不同的数据——一条只有时间轴、数值分布极不均匀的一维数组,还要给它配一套压扩和量化。
难点:压扩和量化是两步不同的操作,压扩本身不产生任何信息损失(它是一一对应的连续函数),损失只发生在压扩之后的线性量化那一步——很多实现会把这两步混在一起,误以为压扩本身就是有损的。另一个难点是编码和解码必须用同一个 μ,解码时如果 μ 和编码时不一致,波形会被系统性地拉伸或压缩,且不容易从听感上一眼发现。
自己验:对一段幅度 0.01 的正弦波和一段幅度 0.5 的正弦波分别做「编码再解码」,幅度 0.01 那一段的相对误差应该明显小于直接用 256 档均匀量化的相对误差;把 μ 从 255 改成 1,μ-law 量化和均匀量化在同一段信号上的误差应该几乎相等——μ=1 时式1 的曲线已经接近直线。
读的时候要小心
关于本章数字的来路
μ-law 数值和感受野是算出来的,可复算。Table 1 的 MOS 对比、WaveNet 重音出错的归因,都是论文作者自己的主观评测,没有第三方复现。HiFi-GAN Table 1 里 WaveNet 那一行的 0.07kHz,本站核对过原始表格标在 GPU(V100)栏,CPU 栏原文留空。
留给你的问题
WaveNet 把生成一秒钟音频的代价焊死成一万六千次串行前向。如果只能改一件事——让每一步前向更快,或者让每一步一次多生成几个采样点——你会选哪条路,它会撞在哪里?
不看材料,写下这一章的三堵墙
第一堵:自回归把处理波形的方式焊死成串行,图像扩散能在一次前向里处理所有像素,WaveNet 不能。第二堵:256 档量化不能均匀分给整个幅度区间,语音能量多挤在小幅度区。第三堵:拼接合成撞在库里没有的东西造不出来,统计参数合成撞在过平滑,两条老路的墙解释了 WaveNet 为什么是转折。
第15章 Mel 频谱:丢掉相位换来可学习
开篇故事第 15 集 · 同一段问候语,两张对不上的频谱图这模型也太真实了
两张频谱图并排贴在《潜空》的对比窗口里,颜色分布却完全对不上——同一段五秒钟的问候语,沈砚这边跑出来的图和复现之鬼那边跑出来的图,亮的地方压根不在同一条线上,他盯着屏幕看了很久,确定自己没有传错文件。
系统提示同一音频输入,两组Mel频谱数值逐帧比对:差异全面超出浮点误差范围,本次一致性校验未通过。
半小时前,回声复现到了第一百一十八天,语音这条线刚要把频谱这一步接上训练流程,沈砚顺手拿平时惯用的那个音频库把梅尔滤波器组跑了一遍,复现之鬼说她要单独用另一套代码复现一遍,确认这个数别人也跑得出来。两人用的是同一份音频、同样的采样率,连帧长和帧移都对过了一遍数字,没有一处不一样,跑出来的结果却是两张长得不像同一段声音的图。
沈砚的第一反应是哪里传错了参数,他把两边的代码翻出来一行一行核对,窗函数、帧长、帧移、滤波器数量,连覆盖的频率范围都一模一样,翻了两遍也没找到任何一处写错的地方,可数字就是对不上,他开始怀疑是不是某个库本身藏了一个没人发现的计算错误。
复现之鬼把两边跑出来的一千赫兹那一格数值单独挑出来贴在群里,沈砚这边跑出来的接近一千,她那边却只有十五,两人这才意识到问题根本不在参数,也不在哪一行代码写错了。靳工看了一眼那两个数,说:“你们俩压根不是在用同一把尺子,HTK公式把1000赫兹定在1000附近,Slaney公式把它定在15,librosa默认走Slaney,某些版本的音频库默认走HTK,两套公式只是恰好共用了梅尔这个名字。”
同一段音频,为什么两边跑出来的梅尔频谱对不上
1L沈砚帧长帧移采样率我们俩对过好几遍,连小数点后面的参数都一样,数字却还是差着一大截,我是真的怀疑是不是某个库自己算错了。
2L复现之鬼不是谁算错了,是HTK和Slaney根本不是同一套公式,1000赫兹这一格你们俩谁贴一下自己的原始数值,一眼就能看出来问题出在哪。
3L炼丹废人这坑我好像也踩过,当时没深想,还以为是自己哪个参数抄错了,改了半天最后蒙对了才过去。
4L显存刺客以后干脆把用的是哪套梅尔公式写进配置文件里,省得每次都靠肉眼去猜两边是不是同一把尺子。
这一趟真正定下来的是语音这条线往后统一用同一套梅尔公式,训练和复现的数字总算叠在了一起。可沈砚翻资料时也留意到一件事——这层频谱图把相位那部分数值整个丢掉了,合成阶段要把声音变回来,得先有人想办法把丢掉的这部分猜回来,猜得准不准,会不会让合成出来的声音听着有哪里不对劲,明天拿真正的语音测一遍就知道。
系统结算 · 本章考点
- 直接对原始波形用均方误差回归会撞在相位上:同一频率同一幅度的两段声音只要起始相位不同,逐点数值就可能差出满幅度,人耳却完全听不出区别
- HTK公式把1000Hz定在999.986附近,Slaney公式却把同一个1000Hz定在15.0,两套公式在这一点上差了66倍还多
- 16kHz的语音过一遍STFT加80通道Mel,每秒数值个数只从16000压到5000,压缩倍数是3.2倍;时间轴却从16000个采样点压到62.5帧,整整压了256倍
- 一段3秒、24kHz的语音,帧长1024、帧移256做STFT,大概能切出281帧;帧移改成128,帧数会翻倍到约562帧
- Tacotron 2实际用的是80通道Mel、覆盖125Hz到7.6kHz这套配置,训出来的系统MOS能到4.526,逼近真人录音的4.582
上一章把生成一秒钟语音焊死成一万六千次串行前向,代价是 GPU 上要跑三百多秒。图像那条线早就学会了一次前向并行输出一整张图的所有像素,语音能不能抄同一份作业,一次前向直接并行吐出整段波形?这一章先让你去撞一次这堵墙,因为撞完这一下,你才会明白为什么几乎所有语音合成系统,中间都插了一层看着多余的东西。
学完这一章你应该能做到
- 说清直接用均方误差并行回归原始波形会撞在哪,以及这堵墙和相位这个量的关系
- 解释傅里叶变换在做什么,讲清 STFT 为什么要先分帧
- 写出 HTK 和 Slaney 两套 Mel 公式,算出给定频率对应的 Mel 值,并说清同一段音频在不同工具里频谱为什么会不一样
- 算出一段音频过 Mel 之后数值个数和时间轴长度各自被压缩了多少倍,说清这两个数为什么不是一回事
这一层要在波形和网络之间插一层前端:前十四级要么面对空间格子,要么面对上一级刚接进来的原始一维波形;这一级要把波形先切成帧、做傅里叶变换、再按人耳的尺子重新分箱,换出一套网络真正学得动的中间表示。
15.1 一次性并行回归波形,会撞在哪
训练一个网络,一次前向直接输出整段波形的全部采样点,损失用均方误差,和真实波形逐点比较。你觉得这样做能不能训出能听的语音?
答完再看
第三个选项说中了要害,这个量叫相位。Tacotron 2 论文选择在网络和波形之间插一层 Mel 频谱,给出的理由原文是这么写的:这个中间表示「更容易用平方误差损失训练,因为它在每一帧内部对相位是不变的」(§2.1)。反过来读这句话:如果不插这一层、直接对原始波形用平方误差,相位就会从「听不出来的东西」变成「让损失爆炸的东西」。第二个选项猜错了方向,数值范围可以简单地缩放到统一区间,不是真正的障碍。
相位(phase):一个周期性波形在它自己的循环里,此刻正走到哪个位置。同一个频率、同一个幅度的两段波,只要各自的相位不同,逐点数值就可能完全不一样,人耳却基本分辨不出这种差别。
自己推一遍:相位错位为什么让均方误差爆炸
取一个 440Hz 的纯音,比较 sin(2π·440·t) 和 cos(2π·440·t)。这两条曲线人耳听起来有区别吗?
想好了再看
没有区别。两者是同一个频率、同一个幅度的纯音,cos 只是比 sin 提前四分之一个周期开始,相当于同一段声音从循环里不同的起点开始播放,人耳完全听不出「从哪个相位开始」这件事。
在 t=0 这一点上,这两条曲线的取值各是多少?差了多少?
想好了再看
sin(0)=0,cos(0)=1,t=0 这一点上两者差了整整 1,是满幅度的差,不是一个可以忽略的小数。
如果把这两条曲线在完整一个周期上逐点算均方误差,量级会有多大?(提示:把 sin 和 cos 想成同一个圆周运动投影到两根互相垂直的轴上)
想好了再看
完整周期上的均方误差和振幅本身同量级——对单位幅度的纯音,这个均方误差约等于 1,相当于把整段信号的能量重新洗了一遍牌。而这两段声音在人耳听起来毫无差别,均方误差给出的数字和「能不能听出区别」完全不成比例。
语音里每个频率成分几乎都带着一个人耳听不出来的起始相位。一个只看波形数值、用均方误差训练的网络,面对同一段语义和音色、只是相位不同的许多种「正确答案」,最后会学到输出什么?
想好了再看
均方误差的最优解是条件期望,也就是这些相位各异、听感却完全相同的波形的逐点平均。这些波形在同一个采样点上可能一个是正、一个是负,平均下来互相抵消,抵消的结果是能量被削弱、细节被磨平——这正是均方误差在图像上产出模糊平均脸的那套机制,只是在波形上它连「一个大致合理的轮廓」都保不住,因为相位错位造成的抵消比像素级别的模糊剧烈得多。
直接比较原始波形:对相位极度敏感
两个听感完全相同的波形,只因为相位不同,逐点相减的差值可能达到满幅度。均方误差没有办法区分「这只是相位不同」和「这是两段完全不同的声音」。
先做频率分解、只留幅度:相位被主动丢在外面
下一节要讲的频谱,把一帧波形拆成很多个频率分量,只记录每个频率的幅度,不记录每个分量各自的起始相位。相位偏移这件事不再出现在被比较的数值里,均方误差也就不会被它带偏。
差别在:要不要在做比较之前,先把这个对听感不重要、却会让逐点比较失控的维度主动丢掉。
如果坚持不经过任何中间表示,训练一个网络一次前向并行输出整段语音波形的所有采样点、损失用均方误差,训练收敛之后,这个网络在推理时最可能给出什么样的输出?请说清背后的统计原理。
变式:如果不是并行输出整段波形,而是像第14章 WaveNet 那样逐点自回归、但把损失从分类交叉熵换成均方误差,前面几个采样点预测准了之后,后面的预测会不会重新陷入同一个问题?
15.2 为什么要先把波形切成一帧一帧
傅里叶变换能把一段波形拆成很多个不同频率的正弦波,告诉你每个频率各占多大能量。既然如此,直接对一整段几秒钟的录音做一次傅里叶变换,能不能知道这段录音里某个乐器是从第几秒开始进来的?
答完再看
第二个选项是对的。傅里叶变换把一段信号整个吃进去,吐出来的是「这段时间里各个频率总共有多响」,时间顺序在这个过程里被彻底摊平了——一段先弹钢琴再拉小提琴的录音,和把这两段掐碎了拼在一起的录音,只要各个频率的总能量相同,一次性傅里叶变换的结果可以一模一样。第三个选项搞错了适用范围,傅里叶变换对任意复杂的波形都成立,这正是它的价值所在。
傅里叶变换(Fourier transform):把一段波形拆解成很多个不同频率的正弦波的叠加,并算出每个频率各占多大的能量。
解法很直接:既然一次性变换会摊平时间,那就别一次性做,把整段波形先切成一小段一小段有重叠的帧,每一帧单独做一次傅里叶变换,再把结果按时间顺序排起来。这样每一个时刻都对应着「当下这一小段里各个频率各有多响」,时间信息被保留在了帧与帧之间。
整段做一次傅里叶变换:时间被摊平
一段先弹钢琴再拉小提琴的录音,和把两段掐碎了拼在一起的录音,只要各频率总能量相同,一次性变换的结果可以一模一样,没法知道「谁先谁后」。
切帧之后再逐帧变换:时间被保留在帧序号里
每一帧只覆盖一小段波形,这一帧算出来的频谱只代表这一小段时刻的内容,帧与帧按时间顺序排起来,就重新有了「什么时候发生」这个信息。
差别在:要不要先牺牲一点频率分辨率(每帧变短,能分辨的最低频率变粗),换回被摊平掉的时间信息。
切帧之后再做的这一串傅里叶变换,有自己的名字。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| n | 帧序号 | 第几个时间片 |
| H | 帧移(hop),相邻两帧起点之间隔多少个采样点 | 每次向前挪多远再切一帧 |
| N | 每帧的采样点数(窗长) | 一帧看多长的一段波形 |
| w[m] | 窗函数,帧的两端权重小、中间权重大 | 让帧的边界平滑过渡,不生硬截断 |
| k | 频率序号(bin) | 正在检测的是第几个候选频率 |
| cos、sin 两个求和 | 这一帧的波形分别和一个余弦波、一个正弦波有多像 | 两个「探测器」的读数 |
| |X[n,k]| | 两个探测器读数的平方和再开根号 | 第 n 帧里第 k 个频率的响度 |
短时傅里叶变换(Short-Time Fourier Transform, STFT):切帧之后逐帧做傅里叶变换、按时间顺序排列。输出是二维数组:一维是时间,一维是频率。
一段 3 秒钟、24kHz 的语音,用帧长 1024、帧移 256 做 STFT,大概会得到多少帧?如果把帧移改成 128,帧数会怎么变?
变式:如果帧长从 1024 改成 2048、帧移不变,帧数会怎么变?每帧能分辨的最低频率会变粗还是变细?
cos 和 sin 那两个探测器的读数还各自携带着一件事:它们的比例决定了这一帧里这个频率分量此刻走到了周期中的哪个位置,正是上一节说的相位。STFT 幅度只取两个读数的平方和,相位信息——也就是 cos 和 sin 这两个读数各自的正负与比例——被直接丢在了外面。这件事在第16章会变得非常要紧:丢掉的东西,合成语音的时候还得想办法猜回来。
频谱(spectrum):一段信号里各个频率各占多大能量的分布。STFT 算出来的是随时间变化的频谱,通常画成横轴是时间、纵轴是频率、颜色深浅代表能量的一张图。
15.3 为什么人耳的尺子和傅里叶的尺子不是一把
STFT 给出的频率轴是均匀的——第 1 个 bin 和第 2 个 bin 之间的频率差,跟第 100 个 bin 和第 101 个 bin 之间的频率差完全一样。这把尺子拿给人耳用,合适吗?
答完再看
第二个选项对。人耳对音高的分辨力是按比例感觉的,不是按绝对差值感觉的——这也是音乐里「八度」的由来,频率翻倍听起来才是「高了一个八度」这种等量的变化。第三个选项过了头,人耳能听到的范围一般到接近 20kHz,只是分辨力在高频区变得粗糙,不是完全听不到。
既然人耳按比例感觉频率,那就把频率轴按人耳的感觉重新画一条尺子,让这条新尺子上的等距离,对应人耳听起来差不多大小的音高变化。这条尺子有两套互相不兼容的公式,都还在广泛使用。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| f | 原始频率,单位赫兹 | 傅里叶那把尺子上的刻度 |
| 700 | 一个经验常数,决定曲线从哪里开始明显弯曲 | 低频区还接近直线,这个数控制拐点 |
| 2595 | 另一个经验常数,把 log 的结果缩放到合适的量级 | 让 1000Hz 落在接近 1000 mel 的地方 |
1000Hz 代入 HTK 公式,算出来是 999.986 mel——这不是巧合,1000Hz 约等于 1000 mel 正是设计这套公式时选定的锚点。同一把尺子上,700Hz 是 781.173 mel,8000Hz 只有 2840.023 mel:频率翻了 11.4 倍,mel 值只涨了 3.6 倍,低频区被拉得更开,高频区被压得更紧,这正是上一段预测门里揭晓的那件事的定量版本。
用 HTK 公式算出 4000Hz 对应多少 mel。
变式:用 Slaney 公式算 4000Hz 对应多少 mel,和 HTK 算出来的 2146.065 比,量级上差多少?
Slaney 公式在 1000Hz 处分段:低于 1000Hz 是一段直线,高于 1000Hz 换成对数曲线。同样代入 1000Hz,Slaney 给出的是 15.0,不是接近 1000 的数——两套公式压根不是同一把尺子的不同版本,只是恰好用了同一个名字。
HTK:锚点在「1000Hz≈1000mel」
单一对数公式,整条曲线光滑,1000Hz 映射到 999.986,数值上和原始频率同量级,直觉上容易对上。
Slaney:锚点在「1000Hz=15」,低频先给一段直线
分段公式,1000Hz 以下是纯线性,1000Hz 以上才转成对数,1000Hz 映射到 15.0,数值量级和原始频率完全不在一个层次上。
差别在:两套公式在 1000Hz 处的取值差了 66 倍还多,同一段音频用这两套公式算出来的 Mel 频谱数值范围完全不同,图形状也不完全一样。
librosa 和 torchaudio 的经典坑
librosa 默认用 Slaney 公式,torchaudio 早期版本默认用 HTK 公式。同一段音频、同样的采样率和帧参数,两个库跑出来的 Mel 频谱数值不一样,拿一个库训练、另一个库做推理预处理,模型会在完全没有报错的情况下悄悄吃进去一批分布偏移的输入。
15.4 连续的尺子怎么变成八十个数
有了 Mel 这把尺子,还差最后一步:STFT 给出的是几百甚至上千个频率 bin,网络不需要这么细,通常只要几十到一百个 Mel 通道。要把上千个 bin 压缩成几十个 Mel 通道,标准做法是搭一组三角形滤波器,每个滤波器负责把一段频率范围的能量加权合并成一个数。
动手搭一组这样的滤波器,分五步走。第一步,在你关心的频率范围(比如 125Hz 到 7.6kHz)里,用 Mel 公式把两端转换成 Mel 值,在这两个 Mel 值之间均匀切出「通道数+2」个点——均匀切的是 Mel 轴,不是 Hz 轴,这一步正是上一节那把新尺子真正派上用场的地方。第二步,把这些均匀分布的 Mel 点,用 Mel 公式的反函数逐个转换回 Hz,得到一组疏密不均的边界频率——在 Mel 轴上等距,换回 Hz 轴之后,低频区的点会更密、高频区的点会更疏。
在 0 到 2840.023 mel(对应 0 到 8000Hz)之间均匀切 3 个点:0、1420、2840.023 mel。换算回 Hz,这三个点在频率轴上是均匀分布的吗?
变式:如果一开始就直接在 Hz 轴上均匀切 3 个点(0、4000、8000),滤波器组会把等量的分辨率分给低频和高频,这样搭出来的 Mel 频谱还能叫「Mel 频谱」吗?
第三步,相邻三个边界频率确定一个三角形滤波器:左脚在前一个边界,峰值在中间那个边界,右脚在后一个边界,边界之间线性升高再线性降低。第四步,把这个三角形采样到 STFT 实际输出的那些离散频率 bin 上——每个 bin 落在三角形的哪个位置,就乘上对应的权重。第五步,因为 Mel 轴上等宽的三角形换回 Hz 轴之后宽窄不一,宽的滤波器会自然覆盖更多能量,为了让各个通道输出的量级公平,通常还要按每个滤波器的宽度做一次归一化。
Mel 滤波器组(mel filterbank):一组三角形加权窗,每一个把 STFT 输出里一段频率范围的能量加权合并成一个数,窗的宽度在 Hz 轴上随频率增高而变宽,因为它们的边界在 Mel 轴上才是均匀的。
一个 80 通道的 Mel 滤波器组,覆盖 125Hz 到 7.6kHz。凭本节讲过的道理,判断第 5 个滤波器(对应低频区)和第 75 个滤波器(对应高频区)相比,谁覆盖的 STFT 频率 bin 更多,为什么。
变式:如果把通道数从 80 降到 40,其余条件不变,低频区滤波器覆盖的 bin 数会变多还是变少?
15.5 Mel 真正压缩的是什么
很多人的直觉是,Mel 频谱之所以有用,是因为它把数据大幅压缩了。一段 16kHz 的语音,过一遍 STFT 加 80 通道 Mel 之后,你觉得每秒钟的数值个数压缩了多少倍?
答完再看
第二个选项是对的,而且这件事值得自己动手算一遍。
16kHz、hop 256、80 维 Mel 的配置下,帧率是 16000/256=62.5Hz,一秒钟有 62.5 帧,每帧 80 个数,一秒钟总共 5000 个 Mel 数值。原始波形一秒钟是 16000 个数。两者相除,5000/16000=0.3125,压缩倍数是 16000/5000=3.2 倍——不是几十倍,只是三倍多一点,甚至不如第6章那张 f=8、4 通道 VAE 表里随便一行的压缩倍数。
图像那边的潜空间压缩里,格子数比是 1/64,但因为潜空间通道数比输入通道数多,数值个数其实只压了 48 倍,这两个比例并不相等。现在这里算出 16kHz、hop 256、80 维 Mel 的配置下,数值个数只压了 3.2 倍。这是不是同一类现象?说清「压缩了多少倍」这个说法必须先回答哪个问题,才有意义。
变式:Tacotron 2 用的是 24kHz、hop 300、80 维 Mel,算出这个配置下数值个数压缩倍数和时间轴压缩倍数各是多少,和 16kHz/hop256 那组比,哪个维度的压缩倍数变了、哪个没变?
数值个数:只压了 3.20 倍
一秒钟从 16000 个数变成 5000 个数,乍看不算震撼,也解释不了为什么整条语音合成技术路线都绕不开 Mel 这一步。
时间轴:压了 256 倍
一秒钟从 16000 个时间步变成 62.5 个时间步,喂给循环网络或者注意力机制的序列长度砍掉了两个半数量级,这才是网络真正学得动的原因。
差别在:同一次压缩,从「数值个数」这个角度看平平无奇,从「要处理的序列长度」这个角度看才是决定性的。
把这一章的两件事拼起来看,就是 Tacotron 2 实际用的配置:50 毫秒帧长、12.5 毫秒帧移、Hann 窗、80 通道 Mel、覆盖 125Hz 到 7.6kHz,log 压缩动态范围(论文还试过 5 毫秒帧移去匹配 WaveNet 的条件输入频率,结果分辨率提得太高反而带来更多发音问题,§2.2)。用这套配置训出来的系统,MOS 达到 4.526,逼近真人录音的 4.582(Table 1)。下一章要处理的,正是这一章故意丢掉的那样东西——相位——在合成阶段怎么被猜回来。
答辩:如果我是审稿人
Tacotron 2 自己在消融实验里承认,预测出来的 Mel 频谱「比真实频谱更平滑、细节更少,这是特征预测网络所优化的平方误差损失带来的后果」(§3.3.1)。这是不是说明 Mel 这一层根本没有解决均方误差的老毛病,只是把它从波形挪到了 Mel 频谱上?
参考防守(先自己组织语言再看)
问题确实没有被连根拔除,只是被降级了。Mel 频谱内部还有大量和相位无关的多值性——同一段语义可以对应音高、语速、口音上的细微差异——均方误差在这些维度上仍然会给出平均而不是某一个具体样本,这正是过平滑的来源。但降级本身就是价值所在:相位错位带来的是灾难性的逐点抵消,丢掉它避免的是「学不出任何靠谱结果」;Mel 内部剩下的多值性带来的是过平滑,代价是「结果偏保守、细节不够锐利」,是一个可以用后续模块(比如论文里那个五层卷积的 post-net)去弥补的程度,而不是从根子上无解。两种过平滑不是同一个量级的问题,这也是为什么 Mel 加 WaveNet 组合的 MOS 能到 4.526,接近真人,而直接对波形用均方误差回归,从15.1节的推导看,连能听的语音都出不来。
答辩:如果我是审稿人
HTK 和 Slaney 两套 Mel 公式在 1000Hz 处相差六十多倍,librosa 和 torchaudio 默认用的还不是同一套,这样一个基础到几乎每一篇语音合成论文都要用到的预处理步骤,为什么这么多年了业界都没有统一掉?
参考防守(先自己组织语言再看)
两套公式各自绑定了自己的历史包袱:HTK 公式源自语音识别工具包 HTK,那个生态从上世纪就在用;Slaney 公式源自另一套听觉建模的工作,被 librosa 继承为默认值,而 librosa 又是学术界用得最广的音频库。只要模型训练和推理用的是同一套代码、同一套默认参数,两套公式各自都能自洽地训出能用的系统——问题不出现在单个系统内部,只出现在「用库 A 训练、拿库 B 做推理预处理」这种跨工具链的场景,而这种场景往往要等到复现别人的论文、或者换一套部署环境时才会暴露。没有一个足够痛的失败案例逼着整个社区统一,这道坑就会一直留在那里,成为这一章值得专门标出来的实践细节。
对你而言未知有没有一种谱表示,既对相位不敏感又不把相位彻底丢光
Mel 频谱解决相位问题的办法是完全丢弃相位,只留幅度,代价是合成阶段要把丢掉的东西想办法猜回来或者用模型学出来,这是第16章要处理的整整一章内容。反过来想,如果能找到一种表示,既像 Mel 一样对「相位在人耳听不出的方向上偏移」不敏感,又能保留足够的信息不需要在合成阶段整段猜回来,理论上可以省掉第16章要解决的整个问题。本站的材料里没有看到针对这个问题的正面方案,只看到后续章节里用完全不同的思路(比如学出来的离散声学 token)绕开了它。
先做这一步:回到本章的频谱实验室,把同一帧信号的相位做小幅度扰动(比如把每个频率分量的相位加一个和频率无关的随机偏移),分别看幅度谱和某种考虑了相位的表示各自变化多大;再去查复数值频谱或者相位声码器相关的工作,看它们是怎么处理这个问题的。
这一层要加什么:STFT 与 Mel 前端
为什么现在才加它:上一层刚把数据换成了原始波形,直接拿它训练会撞上15.1节那堵相位的墙。这一层要在波形和网络之间插一层固定的、不需要训练的前端,把波形变成一个网络真正学得动的二维数组。
难点:mel_fb 这个滤波器矩阵只由采样率、帧参数和通道数决定,和具体某一段音频毫无关系,应该只算一次、缓存起来,不能塞进每一帧的循环里重复计算——这是最容易踩的性能坑。另一个难点是 n_frames 的计算方式,不同库对帧数的取整和是否在两端补零的约定不一样,同样的音频用不同库跑出来的帧数可能差 1,对不齐会让后续模块的时间轴错位。
自己验:对一段 440Hz 的正弦波做 STFT,能量应该集中在 440Hz 对应的那个 bin 上,相邻 bin 的能量应该低至少 20dB;把 hop 从 256 改成 128,帧数应该正好变成原来的两倍,而每一帧的频谱形状应该基本不变。
读的时候要小心
关于本章数字的来路
Mel 公式的锚点、滤波器组的疏密、帧率与压缩倍数都是算出来的,可复算。Tacotron 2 的 MOS 4.526、以及「5 毫秒帧移带来更多发音问题」「预测频谱过平滑」这两条观察,都来自论文作者自己的实验和消融,没有第三方复现。
留给你的问题
Mel 频谱丢掉了相位,换来了一个均方误差训得动、时间轴短了两个半数量级的中间表示。如果有一天算力不再是瓶颈,序列长度不再是问题,直接对原始波形做逐点均方误差这条路,会不会重新变得可行?15.1 节那堵墙,究竟是一堵能被算力砸开的墙,还是一堵和算力无关的墙?
不看材料,写下这一章的三堵墙
第一堵:直接对原始波形用均方误差,相位错位会让逐点比较的损失剧烈爆炸,均方误差最优解是条件期望,多种相位各异的合理波形一平均就互相抵消。第二堵:均匀的频率轴不是人耳的尺子,HTK 和 Slaney 两套 Mel 公式在 1000Hz 处相差六十多倍,同一段音频用不同工具算出来的 Mel 频谱可能不一样。第三堵:很多人以为 Mel 的价值是压缩数值个数,实际只压了 3.2 倍,真正被压掉两个半数量级的是时间轴。
第16章 猜回相位
开篇故事第 16 集 · 轮数翻了一百倍,那句问候还是从水里冒出来这模型也太真实了
耳机里那句“欢迎使用回声语音合成系统”闷得像是从水缸底下钻出来的,尾音还拖着一圈化不开的嗡鸣,沈砚把音量键按到了顶,那层水汽也没被拧干净,这已经是他今晚第五次重放同一句问候。
系统提示本次合成结果自检:目标幅度谱经循环还原后,相对误差稳定在 0.0025,判定为已进入平台期,本次校验不通过。
半小时前,语音这条线才把幅度谱预测那一段跑通——上一层为了让误差好训,把相位整段丢掉,只留一张幅度谱,可耳朵最后要接的是一段实打实的波形,没有相位,连一次逆变换都做不回去。这是回声复现的第一百二十七天,《潜空》里挂着的循环按库里默认的设置跑了三十二轮:先瞎猜一个相位拼出候选波形,变回频域,再把这一轮算出的幅度硬换回目标幅度,只留住这一轮的相位,如此循环,三十二轮跑完,吐出来的正是眼前这句闷在水里的问候。
炼丹废人凑过来听了一耳朵,笑着说这活他熟,“不就是没跑够嘛,三十二轮省得太狠了,今晚队列刚好空出几个钟头,我把循环拉到三千二百轮试试,误差压到挨着零,这层水汽肯定能拧干。”他说着已经把参数改完提交了,还顺嘴打了个赌:要是压不到零,这周的夜班全归他一个人上。
三千二百轮真能把这层水声压干净吗,今晚这个赌谁陪他一起等
1L炼丹废人三十二轮是库里的默认值,谁规定循环不能再往上加,队列里空出来的机时够我跑这一版了,压到挨着零应该不难。
2L显存刺客三千二百轮是三十二轮的一百倍,这一晚的电费和排队损失你算过没有,压不到零这笔账最后算在谁头上?
3L复现之鬼先别急着算电费,我更想知道这条误差曲线你打不打算截图发出来,压到挨着零这种说法,没有曲线我是不信的。
4L沈砚曲线我一直盯着呢,前二十轮误差确实掉得挺快,三百二十轮之后就几乎不再往下走了,现在都快跑完了,数字还是没怎么挪动。
沈砚把中间结果调出来又核对了一遍:某一帧算出的 ‖|Gx|−s‖² 是 0.02,‖s‖² 是 8,相对误差 E(x)=0.02/8=0.0025,换算成信噪比是 −10×log10(0.0025),正好是 26.02 分贝——他翻出三千二百轮跑完的那版日志,这个数字纹丝没动,和三十二轮时几乎一模一样。
靳工端着续好的水杯路过,看了一眼这两组数字,说:“这堵墙不是轮数的事,你们这张预测出来的幅度谱,压根不是任何一段真实波形的 STFT,术语里管这个叫不一致的谱——循环只能在真实波形能生成的那张曲面上,找离它最近的一点,这个最近距离早就定死了,轮数只决定你多快撞上,撞上以后墙还立在原地。”
炼丹废人盯着屏幕愣了几秒,把队列里还剩的两千多轮直接掐掉,把进度贴里那句“压到挨着零”划掉,换成了一行更老实的话:曲线确实会平,平的地方不是终点,是这套办法自己的边界,这周的夜班他认了。沈砚把这几组数字整理好发进了频道:要不要把整条声码器换成一次前向就出结果的神经网络,不再靠这套循环一点点猜,这件事还没有人拍板,是明天例会要吵的第一条。
系统结算 · 本章考点
- Griffin-Lim 的循环靠交替投影来回逼近:库里默认跑 32 轮,动量系数取到 0.99 时已经丢掉了理论上的收敛保证
- 循环轮数从 32 加到 320、3200,前 10 到 20 轮误差降得很快,之后进入一个大于 0 的平台期,连 1e-10 量级的浮点精度都够不着
- 平台期的根子在于预测出的幅度谱是不一致的谱:不存在任何一段真实波形的 STFT 幅度恰好等于它,循环只能找曲面上离它最近的一点
- Tacotron 2 的 Table 3:把声码器从 Griffin-Lim 换成 WaveNet,MOS 从 3.944 涨到 4.510,涨了 0.566;把输入从线性谱换成 Mel 谱,MOS 只多涨 0.016,换声码器的代价是换输入表示的 35 倍
- HiFi-GAN 的多周期判别器专挑质数做周期(2,3,5,7,11),换成都是 2 的幂的 2,4,8,16,32 之后,MOS 从 4.10 掉到 3.90
- 同一块 V100 上,HiFi-GAN V1 的合成速度是 3701kHz、实时率 167.86,WaveNet 只有 0.07kHz、实时率 0.003,MOS 还从 4.02 涨到了 4.36
上一章为了让均方误差训得动,把相位整个丢掉了,只留下一张幅度谱。可最后要交给耳朵的是一段实实在在的波形,波形里必须有相位——没有相位,连一次逆变换都做不回去。这一章要解决的问题很直白:丢掉的东西,怎么在合成的时候补回来。
学完这一章你应该能做到
- 说清 Griffin-Lim 迭代循环每一步在做什么,以及它为什么被叫作交替投影
- 用「目标谱不一致」这件事解释金属声的成因,并区分哪一步的证据能自己验证、哪一步只能算传闻
- 用 Tacotron 2 Table 3 的数字,说清相位重建方式和输入用线性谱还是 Mel 谱,这两件事对音质的影响差着多少个数量级
- 讲清 HiFi-GAN 的多周期判别器为什么选质数作周期,以及神经声码器把合成速度提升了多少
这一层要给建造台阶补上缺的那一半:上一层把波形变成了幅度谱,这一层要把幅度谱变回波形。前十五级全部朝着丢信息、换取可学习性的方向走,这是第一次要往回走——从一个信息不全的表示,尽量猜出一个听起来对的东西。
16.1 只有幅度谱,相位怎么补回来
手上只有一张幅度谱,每个时刻每个频率的响度都知道,但每个频率此刻走到周期里哪个位置——相位——完全不知道。你觉得能不能从这张幅度谱变回一段听起来对的波形?
答完再看
第三个选项是这一章要讲的思路。第二个选项低估了这件事——所有频率的相位都设成 0,变出来的波形会在每一帧的开头出现一次剧烈的冲击(因为所有正弦波在同一个点上同时冲到最大),听起来像连续的爆裂声。真正管用的办法要用到一个循环。
自己推一遍:Griffin-Lim 的循环每一步在做什么
只有目标幅度谱 s,没有相位。最直接的开始方式是什么?
想好了再看
随便猜一个相位——可以是 0,也可以是随机数——把它和已知的幅度 s 拼成一个完整的复数谱。
有了这个猜出来的complex谱,能不能变回时域,得到一段候选波形?
想好了再看
能,做一次逆短时傅里叶变换(iSTFT)。因为帧与帧之间有重叠,iSTFT 在拼接重叠区域时会把各帧的数值加起来求平均,这一步是整个循环里唯一真正「合成」出时域信号的地方。
这段候选波形是刚拼凑出来的,它自己的 STFT 幅度还等于最初的目标 s 吗?
想好了再看
通常不等于。因为最初的相位是瞎猜的,各帧之间猜出来的相位互不搭配,iSTFT 做重叠相加时会把这些不搭配的地方互相抵消或者叠加出多余的能量,重新对这段候选波形做一次 STFT,算出来的幅度已经和 s 有了出入。
现在手上有这一轮新算出的幅度和相位。该怎么把这个结果往目标拉一把,同时不浪费掉这一轮算出的信息?
想好了再看
把这一轮算出的幅度强行换回已知的目标 s,但保留这一轮算出的相位(不是最初瞎猜的那个),再做一次 iSTFT 回到时域,然后重复整个过程。相位在每一轮里都会被这一步的重叠相加稍微调和一点,循环足够多次,相位会逐渐变得彼此协调。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| x | 正在寻找的候选波形 | 要猜出来的答案 |
| G | STFT 算子,把波形变成频谱 | 上一章造的那台机器 |
| |Gx| | 候选波形的 STFT 幅度 | candidate 自己算出来的幅度谱 |
| s | 已知的目标幅度谱 | 要凑齐的答案 |
交替投影(alternating projection):在两个约束之间来回切换、各自只满足一半——这里是「幅度必须等于 s」和「必须是某个真实波形的 STFT」——每次只朝其中一个约束挪一步,指望反复横跳最终落在两者都满足(或者尽量都接近满足)的地方。
不加动量:稳,但要更多轮
每一轮老老实实做完两次投影,不借上一轮的势头,收敛平稳但慢,要逼近一个可用的结果往往要上百次迭代。
加动量(FGLA):快,但丢了收敛保证
借上一轮的更新方向再推一把,同样的效果只要几十次迭代,librosa 默认 32 次就够用,代价是动量系数一旦选得太大,理论上不再保证一定收敛。
差别在:要不要用一份不保真的「势头」去换迭代次数——这份势头在实践里通常划算,但确实不再是原始算法那个有理论保证的版本。
32 次这样的循环,代价已经不小——每一轮都要做一次 iSTFT 和一次 STFT。有一种加速办法,在更新时额外加上一份「惯性」,用上一步的更新方向再推一把。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| cn | 第 n 轮的复数谱估计 | 这一轮手上的候选答案 |
| PC1、PC2 | 两个投影操作:把幅度换成目标 s;把谱变回一个真实波形再变回来 | 上面循环里的第 4 步和第 2/3 步 |
| Δcn−1 | 上一轮的更新方向(这一轮减上一轮) | 上一次往哪走,这次再多走一点 |
| α | 动量系数,librosa 默认 0.99 | 「多走一点」的力度 |
α=0.99 已经很接近这套更新方式还能保持稳定的上限,作者自己在论文里写明,这个取值会让整套方法「丢掉所有关于收敛性的理论保证」。librosa 默认用 32 次迭代、0.99 的动量,实践中用这套设定换来的收敛速度,远比不加动量的原始循环快。
Griffin-Lim 循环里,「把幅度换成目标 s」和「变回时域再变回频域」这两步,分别对应交替投影里的哪一个约束集合?哪一步可能会让幅度重新偏离 s?
变式:如果只做第一步(换幅度)、不做第二步(变回时域再变回来),会发生什么?这样算不算 Griffin-Lim?
16.2 为什么迭代出来的声音是金属声
如果把 Griffin-Lim 的迭代次数从 32 次加到 320 次、3200 次,误差会不会一路降到 0?
答完再看
第二个选项对。前十几到二十次迭代误差下降得很快,之后进入一段几乎不再下降的平台期,这个平台不是零。第三个选项的方向也不对,交替投影每一步都不会让目标变大,不存在越迭代越差这回事,只是「不再变得更好」。
误差降不到 0,根子出在目标幅度谱 s 本身。一段真实存在的波形,它的 STFT 幅度谱必须满足很强的内部约束——STFT 分帧时相邻帧有大段重叠,同一批采样点会被好几帧同时看到,这些帧算出来的幅度和相位之间因此互相牵制,所有「真的来自某个波形」的幅度谱只占据全部可能数组里极小的一部分,像是嵌在一个高维空间里的一张薄薄的低维曲面。网络预测出来的谱,或者任何不是直接从一段真实波形算出来的谱,几乎必然不落在这张曲面上。
不一致的谱(inconsistent spectrogram):不是任何真实波形的 STFT 幅度——也就是说,不存在任何一个 x,使得 |Gx| 精确等于它。
一致的谱:曲面上有一个精确解
真实录音自己的幅度谱,天生就落在那张低维曲面上,曲面上正好有一点(也就是原始波形自己)满足 |Gx|=s 精确成立,误差的下界就是 0。
不一致的谱:曲面上没有任何一点能精确匹配
网络预测出来的谱,几乎必然不落在那张曲面上,交替投影只能在曲面上找一个离 s 最近的点,这个最近距离本身就大于 0,不管迭代多少次都跨不过去。
差别在:要找的目标本身在不在「所有真实波形能产生的谱」这张曲面上,这件事迭代次数改变不了。
这解释了误差为什么有一个下不去的平台,但还没解释金属声这个具体的听感。交替投影本身也只能保证目标不增,不保证收敛到全局最优——它每一步都在两个约束之间来回投影,找到的通常是一个局部最优,不是理论上离 s 最近的那个点。最终收敛到的那个候选波形,各帧之间的相位仍然残留着没有被完全调和的错位,这种帧间相位不连续,听起来正是那种水下、嗡嗡、带着金属质感的音色——不是随机的噪声,是一种有结构的、周期性的相位错误。
如果同一个条件对应好几个合理答案,用均方误差去找一个折中,这个折中的结果好歹是存在的、算得出来的,只是不锐利。Griffin-Lim 要解的问题——找一个真实波形,使它的 STFT 幅度恰好等于目标 s——是不是同一种「折中出来的模糊」?如果不是,差别在哪?
变式:如果 s 恰好就是从一段真实录音的原始波形算出来的、完全没有被网络预测或者扰动过,这时候 Griffin-Lim 的交替投影还会遇到「无解」这个问题吗?
「每一步都让误差单调下降」这句话,几乎所有介绍 Griffin-Lim 的资料都会提一句,出处通常写成 Griffin 和 Lim 1984 年发表的那篇原始论文。这篇论文在一份订阅制期刊的付费墙后面,本站没有读到原文证明单调下降那一段,只查到后续论文反复引用这个结论。与其在这里照抄一句没有亲自核对过的话,不如把它当成一个可以自己验证的说法:本章的实验室会把每一次迭代的误差画成一条曲线,你可以亲眼确认它是不是真的每一步都不增大,还是偶尔会有反弹——这也是判断一个说法能不能直接抄进正文的办法,查得到原文就引原文,查不到就老实交代查到哪一步,剩下的交给能亲手验证的实验室。
Griffin-Lim 的相对误差定义为 E(x)=‖|Gx|−s‖²/‖s‖²。如果某一轮迭代算出 ‖|Gx|−s‖²=0.02,‖s‖²=8,这一轮的相对误差是多少?换算成信噪比 SSNR=−10·log10(E(x)) 是多少分贝?
变式:如果换一段更容易重建的信号,误差比值降到 0.00025,SSNR 会变成多少?
16.3 相位重建的代价,为什么比丢信息本身贵得多
拿同一个 Tacotron 2 模型做两组对照:一组保持输入是线性频谱(STFT 直接输出的、没有过 Mel 那层压缩的幅度谱),只把声码器从 Griffin-Lim 换成 WaveNet;另一组保持用 WaveNet 声码器,只把输入从线性频谱换成信息量小得多的 Mel 频谱。你觉得哪一组变化对最终音质的影响更大?
答完再看
第一个选项是 Tacotron 2 论文 Table 3 实测出来的结果,而且差距大到不是一个量级。
| 系统 | MOS |
|---|---|
| 线性谱 + Griffin-Lim | 3.944 ± 0.091 |
| 线性谱 + WaveNet | 4.510 ± 0.054 |
| Mel 谱 + WaveNet | 4.526 ± 0.066 |
把这三行拆成两组差值看:声码器从 Griffin-Lim 换成 WaveNet(保持线性谱不变),MOS 从 3.944 涨到 4.510,涨了 0.566;输入从线性谱换成 Mel 谱(保持 WaveNet 不变),MOS 从 4.510 涨到 4.526,只涨了 0.016。前者是后者的三十五倍还多。
用表 1 的三个数,算出「换声码器」和「换输入表示」这两个差值各是多少,并说清这组数字在说什么。
变式:如果有人说「Mel 频谱信息量比线性谱少很多,所以用 Mel 频谱天然会限制音质上限」,用表 1 的数字反驳这句话。
16.4 能不能用学出来的先验,替代交替投影
如果不再靠交替投影一步步猜相位,改成训练一个网络,直接从幅度谱(或者 Mel 谱)一次前向输出整段波形,用一个判别器去判断输出像不像真实语音。这个判别器要学会分辨真假,最该盯着波形的哪个结构特征?
答完再看
第二个选项是 HiFi-GAN 的核心洞察,论文原文把这件事写在摘要里:语音音频由各种周期的正弦信号构成,建模这些周期性模式对提升样本质量至关重要。第一、三个选项抓的都是波形的粗粒度统计量,抓不住让金属声和真实语音区分开的那种细粒度周期结构。
HiFi-GAN 把这个洞察直接做进了判别器的结构里:不再让判别器看一整条一维波形,而是先按一个周期 p,把长度 T 的一维波形重排成高 T/p、宽 p 的二维网格,再用卷积去处理这个网格——因为卷积核在宽度方向的宽度被限制成 1,判别器实际上是在纵向比较「间隔 p 个采样点的那些位置」彼此是不是符合规律,专门抓这个周期上的结构。一个判别器只能专注一个周期,于是用好几个判别器,各自负责一个不同的周期,论文选定的周期是 2、3、5、7、11。
多周期判别器(Multi-Period Discriminator, MPD):一组子判别器,每个子判别器把一维波形按自己负责的周期重排成二维网格再做卷积,各自捕捉不同周期上的规律。
交替投影:不用训练,但每次生成都要重新迭代
不需要任何训练数据,直接对着目标幅度谱迭代,但目标谱不一致这件事无法通过迭代绕开,而且合成一段语音要重新跑几十轮 iSTFT/STFT。
神经声码器:训练一次,之后一次前向出结果
用大量真实语音把「什么样的波形听起来自然」这件事学成网络参数,训练只做一次,合成时不再迭代,一次前向直接给出波形。
差别在:要不要把「怎样的相位关系才自然」这件事本身也交给数据去学,而不是每次都从零开始猜。
HiFi-GAN 选的五个周期 2、3、5、7、11 都是质数。如果换成 2、4、8、16、32 这五个都是 2 的幂的周期,判别器组的效果会不会打折扣,为什么?
变式:如果换成 [3,5,7,11,13],这五个数虽然都不是 2 的幂,但都是质数,会不会比 [2,4,8,16,32] 更接近原始的 [2,3,5,7,11]?原始配置里为什么要专门包含 2 这个最小的质数?
换掉交替投影带来的收益直接体现在速度上。HiFi-GAN 论文自己的 Table 1,在同一块 V100 GPU 上测过这件事:上一章测出 WaveNet 的合成速度是 0.07kHz、实时率 0.003;HiFi-GAN V1 在同一块 GPU 上是 3701kHz、实时率 167.86。
| 模型 | MOS | GPU 速度(kHz) | 相对实时倍数 |
|---|---|---|---|
| WaveNet(MoL) | 4.02 | 0.07 | ×0.003 |
| HiFi-GAN V1 | 4.36 | 3,701 | ×167.86 |
用表 2 的两个实时倍数,算出 HiFi-GAN V1 比 WaveNet 快多少倍,并结合 MOS 说说这次提速是不是拿音质换来的。
变式:HiFi-GAN V3 参数量只有 1.46M,GPU 实时倍数是 1186.80。用这个数据说说,是不是判别器越复杂、模型越大,合成质量和速度就一定越好?
答辩:如果我是审稿人
FGLA 的作者自己承认,动量系数 α=0.99「丢掉了所有关于收敛性的理论保证」,librosa 却把 0.99 设成了默认值。一个连理论保证都没有的设定,为什么能大大方方地成为业界默认?
参考防守(先自己组织语言再看)
理论保证和实践可用性本来就不是一回事。GLA 本身的收敛性证明,针对的也只是「误差不增」这么弱的一条性质,并不保证收敛到全局最优,实践里本来就要靠有限次迭代加经验参数把结果调到能听。α=0.99 之所以敢用,是因为它的失败模式是可观察、可回退的——动量过大顶多是某几次迭代震荡一下,不会让整个算法发散到不可用的地步,而且librosa这些库默认的迭代次数只有 32 次,早早停下来的策略天然限制了震荡有多少机会累积。工程实践里常见的取舍是,用一个失去理论保证、但经验上更快更稳的配置,换一个反正也只追求「听起来还行」而不是「理论最优」的目标——只要有实验(比如这里的对照)能验证经验上确实更好,理论保证的缺失就不是必须补上的窟窿。
答辩:如果我是审稿人
HiFi-GAN 的消融实验只对照了 [2,3,5,7,11] 和 [2,4,8,16,32] 这一组,证明的到底是「质数」本身重要,还是只要「不含 2 的高次幂」就够了?如果换成 [3,5,7,11,13] 这种不含 2 的质数组合,或者 [6,10,15,21,33] 这种两两互质但不是质数的组合,会不会效果一样好?
参考防守(先自己组织语言再看)
严格说,这组消融只能证明「[2,4,8,16,32] 这种两两不互质的组合比 [2,3,5,7,11] 差」,证明不了「质数」这个身份本身有特殊意义——按上面那道题的数论道理,真正起作用的条件是两两互质,质数只是凑出互质集合最省心的办法,[6,10,15,21,33] 这种两两互质的合数组合,理论上应该有同样的效果,但论文没有测这一组。这是一处只做了一半的消融:它排除了「幂次结构」这个混淆变量,但没有把「质数」和「互质」这两个可能的解释分开。这类只测了一侧对照组的实验,在能推出的结论范围上要打折扣。
对你而言未知Griffin & Lim 1984 原文是否真的证明了每步单调下降
「Griffin-Lim 每一步迭代都让误差单调下降」这句话在后续几乎所有相关论文里都被反复引用,本站没有拿到 1984 年那篇原始论文的全文,没有核对过它是不是真的给出了完整证明,证明的前提条件又是什么(比如是不是对任意窗函数、任意帧移都成立)。这不属于学界公认还是有争议这个层面的判断,只是本站的调研没有走到能验证这一句话本身的那一步。
先做这一步:去查 D. W. Griffin 和 J. S. Lim 发表在 IEEE Transactions on Acoustics, Speech, and Signal Processing(1984 年)上的原始论文,找到证明单调性的那一段,看清楚它的前提条件;同时在本章的实验室里,对多组不同的随机初始相位跑 Griffin-Lim,把每一轮的误差都记录下来,看误差曲线是不是处处不增,有没有出现过任何一次反弹。
这一层要加什么:Griffin-Lim 反变换
为什么现在才加它:前面十五级一路都在往「丢信息换可学习性」这个方向走,这一层第一次要反过来,从一个信息不全的幅度谱,尽量猜出一个听起来对的波形,把建造台阶接回一条能听的声音。
难点:第 4 步「幅度换回 s,保留这一步的相位」,实现时最容易写错成直接把 abs(c) 换成 s 再重新拼一个新的相位——这样做会丢掉这一轮 iSTFT 帮忙调和过的相位信息,退化成每轮都从头瞎猜,循环再多次也不会收敛。正确的写法是用 c / abs(c) 取出这一轮的相位方向(一个模长为 1 的复数),再乘上目标幅度 s,相位保持不变,只有长度被强制拉到 s。另一个难点是 random_phase 的初始化:用全零相位启动,前几轮会有短暂的冲击声,用真正的随机相位启动收敛得更稳。
自己验:拿一段真实录音,先把它的幅度谱做一次小幅度随机扰动模拟网络预测的偏差,跑 Griffin-Lim,误差曲线应该在前 10 到 20 次迭代里下降得很快,之后进入平台期,且不管跑多少轮都不会回到 0;换成直接用这段录音自己、未经扰动的原始 STFT 幅度(这就是一致的谱,真实波形本身就是它的一个精确解),误差应该能一路降到接近浮点精度(1e-10 量级甚至更小)——这一组对照就是「不一致」这个概念的实证。
读的时候要小心
关于本章数字的来路
误差度量和交替投影的机制是算出来的,可复算。Table 3 的三组 MOS、Table 1 的速度对比、质数换成 2 的幂后 MOS 掉 0.20,都来自论文作者自己的实验,没有第三方复现。表 2 里 WaveNet 的 0.07kHz 和第14章一样标在 GPU 栏。
留给你的问题
从 Griffin-Lim 到 HiFi-GAN,声码器把交替投影换成了一次前向的神经网络,速度涨了近五万六千倍,音质反而更好。如果目标谱换成分辨率更低的某种压缩表示,这一章讲的两件事——目标谱是否一致、判别器该抓什么结构——还会以同样的方式出现吗?
不看材料,写下这一章的三堵墙
第一堵:网络预测的目标谱通常不落在「真实波形能产生的谱」这张曲面上,压根不存在精确解,这和均方误差那种「有解但模糊」不是一回事。第二堵:即使目标谱一致,交替投影也只保证误差不增,残留的帧间相位错位听起来就是金属声。第三堵:换声码器(怎么猜相位)比换输入表示(丢多少信息)贵三十五倍。
第17章 为什么不漏字不重复
开篇故事第 17 集 · 像卡带一样,那个词被念了两遍这模型也太真实了
耳机里那句“欢迎使用回声语音合成系统”,中间那个“语音”被结结实实地念了两遍,像唱片卡了一下针。沈砚把这段回放拖回起点,第三遍确认自己耳朵没出问题,那两个字真的被吐出来了两次。
系统提示本次合成结果自检:识别到语音与文本的对应关系出现一次非单调回退,位置落在“语音”一词对应的区间,判定为一次重复。
半小时前,语音合成这条线还在用一套自由注意力做文本和语音之间的关联:解码到某一帧,就对文本里每一个音素算一遍权重,权重加起来是 1,这一帧就是所有音素按权重加权求和的结果——机制本身从没要求这份权重的重心必须跟着时间往前挪,训练数据只是让它大概率往前走。这是回声复现的第一百三十六天,长句子和生僻词上,这个“大概率”时不时会松一下手,字要么被跳过,要么被回头重念一遍,刚才那个“语音”重复,就是回头重念的那一种。
复现之鬼在频道里甩出一个方案:给权重加一条平滑的惩罚项,谁往回跳就扣谁的分,逼着权重老老实实往前走。她连夜挑了两百句长句子跑了一遍,重复率降到零,直接把这条方案标成“已解决”贴进了周报草稿。
复现之鬼说这条惩罚项已经把重复彻底压住了,两百句零失手能不能算数
1L复现之鬼两百句长句子,加了惩罚项之后一次重复都没再出现,这个结果我贴在附件里了,大家看看能不能先把这条并进主分支。
2L显存刺客两百句能不能代表全部我不知道,就是好奇你这两百句是怎么挑出来的,是随机抽的,还是专挑好念的那种?
3L沈砚不对,惩罚项说到底只是让权重往回跳这件事变贵了,贵不等于不可能,两百句零失手证明不了以后第两百零一句不会撞上。
4L炼丹废人楼上这话听着耳熟,我上次也就拿几十条样本试了试没出事,就敢在论坛上打包票,后来才知道那叫运气好没撞上。
靳工把一句五十个音素、五百帧长的句子扔进《潜空》,没有直接回复复现之鬼的周报。
系统提示五十个音素对五百帧,满足“不跳字、顺序不倒”这两条的合法路径一共 C(499,49) 种,约 2.314×10⁶⁸ 条;这些路径里,任何一个音素被分到 0 帧的情况一条都不占。
靳工看着这行数字说:“你这条惩罚项,只是在刚才那一大堆路径里,把往回跳的路径罚得贵一点,贵的路径依然在候选集合里,只是不常被选中。真要让它们压根不在候选集合里,得把“至少一帧、顺序不倒”这两条直接写进搜索的定义本身,这是下一步要做的事,不是罚出来的。”
复现之鬼把“已解决”那三个字从周报草稿里划掉,换成一行更谨慎的备注:惩罚项能把概率压低,压不掉这件事本身还会不会发生。社区那条追问“什么时候能把重复修好”的回复,组里本来想今晚就发出去,现在还没有人敢按下发送。
系统结算 · 本章考点
- 自由注意力每一帧都要对全部音素重新打分归一,机制本身没有规定权重的重心必须随时间单调前进,长句子生僻词上偶尔往回跳或往前跳,就是重复和漏字的根源
- 长度调节器唯一的承诺:把 12 个音素的预测时长 3,5,0,4,2,6,3,5,4,2,3,5 加起来是 42 帧,第 3 个音素时长是 0,会从输出里彻底消失——这是回归数值错了,不是机制失效
- 50 个音素对 500 帧,满足不跳字、顺序不倒这两条的合法路径一共 C(499,49)≈2.314×10⁶⁸ 条,一张动态规划表只要 50×500=25000 个格子就能装下,两者相差约 64 个数量级
- 这 2.314×10⁶⁸ 条路径里,某个音素被分到 0 帧的情况一条都不占——不是训练学会了不漏字,是这套计数从定义上就没给漏字留过位置
- FastSpeech 2 换成 MFA 之后,音素边界的平均偏差从 teacher 模型的 19.68 毫秒降到了 12.47 毫秒
- VITS 去掉标准化流后,LJSpeech 上的 MOS 从 4.50 掉到 2.98,掉了 1.52 分,这项消融动的是重建质量,不是候选集合排除漏字这件事本身
早期的语音合成系统读一段稍长的句子,经常会在某个词上出问题:一个词被吞掉了,句子听起来像是跳着读的;或者反过来,同一个词被念了两遍,像是卡带。音质本身没有任何毛病,每个字单独听都清清楚楚,问题出在这段语音和这段文本对不上号——模型在这句话的哪个位置,跟它此刻该读文本里的哪个字,两者脱钩了。
学完这一章你应该能做到
- 说清文本和语音的对应关系为什么不是天生就有的,文本有多少个音素、语音有多少帧,这两个数字本来就不相等
- 解释不加约束的注意力为什么会漏字重复,以及 FastSpeech 2 的显式时长预测器怎么从结构上排除漏字这一种可能
- 用组合数学算出单调对齐的合法路径数量级,说清「排除漏字」靠的是约束本身,不是模型学出来的
- 区分确定性时长回归和随机时长预测器,说出两者对同一句话能不能读出不同节奏的差别,以及各自留下的代价
这一层要给已经能把波形变成 Mel、又能把 Mel 猜回波形的管线接上文本这一端:一段时长机制,保证文本里的每一个音素和语音里的某一段帧之间,存在一份不多不少、顺序不乱的对应关系。前十六级处理的都是声音自己怎么被表示成数字,没有一处涉及某一帧到底该对应文本里第几个字。
17.1 二十个音素、两百帧,谁对应谁?
一句话被念出来,切成音素大概是二十个,而对应的语音帧可能有两百个。如果什么都不做,只让一个注意力模块自己去学「这一帧该看文本里的哪个音素」,用足够多的录音训练它,你觉得推理时会发生什么?
答完再看
第二个选项是实际发生的事。FastSpeech 2 的作者在论文开头就点名了这一类早期模型的通病:「它们通常存在推理速度慢和鲁棒性问题(漏字和重复)」,原文写的是「slow inference speed and robustness (word skipping and repeating) issues」。第一个选项把「大多数时候对」和「保证对」混成了一件事——注意力权重是一组连续的数,没有任何东西规定它必须沿着时间单调前进,训练数据再多也只是降低出错概率,不是把出错的可能性从结构上拿掉。第三个选项低估了注意力:它在大多数位置上确实学得不错,出问题的只是少数几个位置,但这几个位置已经足够让读者听出漏字重复了。
把这套自由注意力的工作方式写清楚:解码到某一帧时,它对文本里的每一个音素都算一个权重,权重加起来是 1,这一帧的输入就是所有音素按权重加权求和的结果。这套机制本身没有任何东西要求下一帧的权重必须往后挪一位——它完全可以在第 5 个音素和第 18 个音素之间来回跳。大多数句子里,模型学到的权重确实会随着时间大致单调地往后走,因为这是训练数据里最常见的模式。但「大致」两个字就是漏洞所在:只要有一帧的权重稍微偏向了前一个已经读过的音素,重复就发生了;只要有一帧的权重跳过了下一个还没读的音素,漏字就发生了。
漏字:权重跳过了某个音素
解码到某一帧时,注意力的重心从第 5 个音素直接跳到第 7 个,第 6 个音素在整个解码过程里从来没有拿到过多少权重,它对应的那部分语音就没有被生成出来。
重复:权重退回了已经读过的音素
解码到某一帧时,注意力的重心从第 7 个音素退回到第 5 个,模型又把第 5、6 个音素对应的内容重新生成了一遍,听起来像是卡带。
差别在:两种失败看起来方向相反,根源却是同一个——权重沿时间前进这件事,从来没有被写进约束里,它只是训练数据里的一个统计倾向。
自由注意力的权重在每一帧都要对全部音素重新算一遍并归一化。请用一句话说清:这套机制里,到底哪一件事没有被保证,才导致了漏字和重复?
变式:如果给注意力权重加一个正则项,惩罚相邻两帧权重重心的位置往回退,这能不能彻底消除重复?为什么这和后面要讲的方案不是一回事?
17.2 怎么保证一个音素不多读也不少读
FastSpeech 2 的做法是把「哪一帧对应哪个音素」这件事从注意力手里拿走,改成一个显式的数:每个音素在语音里占几帧,这个数叫时长。时长不是靠模型自己在训练里隐式摸索出来的权重,而是提前从真实录音里量出来的——用一个独立的强制对齐工具(Montreal Forced Alignment,缩写 MFA)比对文本和录音,给每个音素标出一个具体的帧数。训练时,这份时长当作已知条件喂给模型;推理时没有真实录音可以对齐,改由一个小型的时长预测器给出。
时长来自 teacher 模型的注意力图
FastSpeech(第一代)用一个训练好的自回归模型跑一遍,把它的注意力权重当成时长的来源。这份时长继承了自由注意力的所有毛病,只是被搬到了训练阶段而不是推理阶段。
时长来自外部强制对齐工具 MFA
FastSpeech 2 换成一个专门做文本-语音对齐的独立系统,用动态规划在给定文本和录音的条件下找最优边界。它和 TTS 模型完全无关,也不参与端到端训练,只是训练开始之前跑一遍、产出一份时长表。
差别在:后者的对齐精度更高,但依然是一个独立于 TTS 模型之外的系统——它自己的误差会原样传给下游。FastSpeech 2 自己测过这份差距:teacher 模型给出的音素边界平均偏差 19.68 毫秒,换成 MFA 之后降到 12.47 毫秒。
时长预测器本身很朴素:两层一维卷积加 ReLU,各跟一层归一化和 dropout,最后接一个线性层,把每个音素的隐藏向量映射成一个数(对数域),用均方误差训练,目标就是 MFA 标出的真实时长。拿到这份时长之后,怎么把音素序列变成帧序列,靠的是长度调节器:把第 i 个音素的隐藏向量,按预测出的时长 di 原样复制 di 份,首尾相接拼成新序列。这个操作只有复制和拼接,没有任何可学习的参数。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| Ttext | 音素个数,一句话切出来的音素总数 | 要读多少个字音 |
| di | 第 i 个音素被复制的次数,即它占几帧 | 这个字音读多久 |
| Tmel | 复制拼接之后的总帧数,等于输出 Mel 谱的长度 | 整句话一共占多少帧 |
一句话切出 12 个音素,时长预测器给出的预测值分别是 3, 5, 0, 4, 2, 6, 3, 5, 4, 2, 3, 5(单位:帧)。这句话最终会生成多少帧的 Mel 谱?第 3 个音素会发生什么?这属不属于「漏字」?
变式:如果把时长预测器的输出统一加上一个下限,任何小于 1 的预测值都强制改成 1,这能不能彻底杜绝这种漏字?它会带来什么新的副作用?
这条不变量是长度调节器唯一的承诺,但它很值钱:只要每个 di 至少是 1,输出序列里就一定包含全部 Ttext 个音素各自的至少一份拷贝,音素个数在输出里一个不少地全部出现过。这不是训练出来的性质,是复制加拼接这个操作本身决定的。时长预测器仍然可能把某个音素的时长预测错——读快了或读慢了——但那是长度错了,不是这个音素从输出里彻底消失。漏字这件事,从「注意力权重偶尔跳过一格」变成了「时长预测器把某个数字预测成了别的数字」,后者是一个连续量上的误差,不再是结构上「有」或「没有」的赌博。
答辩:如果我是审稿人
长度调节器的「音素个数不丢」是靠复制加拼接这个操作本身保证的,听起来很结构化。但这份保证建立在时长预测器学到的目标——MFA 给出的音素边界——本身之上。如果 MFA 对某一类语言、某一类说话人系统性地偏短(不是随机误差,是有偏差),时长预测器学到的也会是同一种系统性偏差,「结构上不丢音素」这句话是不是掩盖了「音素内容本身可能被系统性地压缩到几乎听不出来」这个更隐蔽的问题?
参考防守(先自己组织语言再看)
这个担心是对的,需要把「结构性保证」这四个字的范围说准确:长度调节器保证的严格来说只是「每个音素在输出序列里至少有一份拷贝,个数上不会归零」,不保证这份拷贝携带的时长「合理」。如果 MFA 对某一类数据系统性地给出偏短的边界,时长预测器会忠实地学到这个偏差,长度调节器依然会按这个(偏小的)预测值复制出对应的帧,音素确实没有从输出里消失,但可能被压缩到只剩一两帧,听感上和「消失了」相差无几。结构性保证排除的是「预测值恰好等于 0」这种极端情况,排除不了「预测值系统性偏小但不为 0」这种更隐蔽的情况——后者最终还是要看 MFA 本身的标注质量够不够好,这一点本节没有单独展开,值得读者自己留意。
17.3 不靠外部工具,模型怎么自己找出单调的路?
MFA 是一个独立于 TTS 之外的系统,训练时先跑一遍拿到时长表,说到底还是一层额外的工程。如果想让模型在训练时自己找出对齐——不靠外部工具——同时还要把「不漏字、不重复」这两条硬性质一次性满足,而不是像自由注意力那样只是大概率满足,你觉得可能做到吗?
答完再看
第三个选项就是 VITS 提出的单调对齐搜索(Monotonic Alignment Search,MAS)的思路,论文原话是「candidate alignments are restricted to be monotonic and non-skipping following the fact that humans read text in order without skipping any words」——候选对齐被限制为单调且不跳过,因为人读文本时本来就是按顺序念、不会跳字。第二个选项是很多人的第一反应,但正则项只是调整了「不同对齐方式各自的可能性有多大」,被惩罚的方式依然在候选集合里,出问题的概率降低了,却没有被排除。第一个选项低估了约束能做的事:约束不需要「告诉」模型正确答案在哪,它只需要划定一个更小的候选范围,再在这个范围里找最优解。
把「单调」和「不跳过」拆开说清楚。单调是指:如果第 y 帧对应第 x 个音素,那么第 y+1 帧对应的音素编号不会比 x 小——时间只往前走,音素编号跟着往前走或者原地不动,绝不后退。不跳过是指:每个音素至少要分到一帧,不允许某个音素被完全空过去。这两条合在一起,把「所有可能的帧到音素的映射」这个天文数字大小的空间,砍成了一个小得多的候选集合。剩下的问题是:在这个集合里,哪一种切法和当前模型给出的声学打分最匹配。这个搜索靠动态规划完成,VITS 沿用了 Glow-TTS 提出的算法。
这个集合到底小了多少,值得算一遍。把一段语音的 500 帧看成排成一行的 500 个格子,要把它们切成 50 段连续的区间——每一段对应一个音素,段的顺序不能变,因为约束要求单调;每段至少 1 帧,因为约束要求不跳过。这就是一个纯粹的计数问题:满足这两条的切法一共有多少种?
自己推一遍:50 个音素对 500 帧,合法对齐一共有多少种
500 帧排成一行,要把它切成 50 个非空的连续小段,你会怎么数一共有多少种切法?
想好了再看
想象在相邻两帧之间的缝隙里插隔板:500 帧之间一共有 499 个缝隙(第 1、2 帧之间一个,第 2、3 帧之间一个,一路数到第 499、500 帧之间),每个缝隙插或不插一块隔板,插了就代表这里是两段的分界。
要把 500 帧切成 50 段,需要插几块隔板?
想好了再看
49 块。切成 k 段只需要 k−1 块隔板——段数总是比隔板数多 1,这和「一根绳子剪 k−1 刀变成 k 段」是同一件事。
这就变成了什么样的标准计数问题?
想好了再看
从 499 个缝隙里挑出 49 个位置放隔板,不计挑选的先后顺序。从 n 个位置里挑 k 个、不计顺序的挑法个数,记作组合数 C(n,k),这里就是 C(499,49)。这个数算出来约是 2.314×10⁶⁸。
这个数和动态规划要填的表格大小——50 乘 500 等于 25000 个格子——差了多少?这说明了什么?
想好了再看
2.314×10⁶⁸ 比 25000 大了大约 64 个数量级,穷举法要在这么多条路径里逐条比较根本不现实。动态规划完全不会真的把这些路径列出来:它只维护一张 50×500 的表,每个格子存一个数——「对齐到这个位置为止,最优路径的对数似然是多少」,后一个格子的最优值直接用前面已经算好的格子拼出来,不需要重新回溯整条路径。约束把候选空间从 10⁶⁸ 量级砍到「表格大小」这个量级,才是动态规划能真正运行起来的前提;表格本身只有 25000 个格子,这才是能在训练时反复跑的量级。
这条算术顺带回答了另一个问题:漏字对应的那些切法——某个音素被分到 0 帧——在 2.314×10⁶⁸ 条合法路径里占几条?答案是一条都不占。组合数 C(499,49) 数的本来就是「每段至少 1 帧」的切法,0 帧的切法从一开始就不满足「非空」这个条件,压根没有被计入这 2.314×10⁶⁸ 之中。模型不是在训练过程里学会了不要漏字,是候选集合从定义上就没有给漏字留过位置——这和 17.2 节长度调节器的道理是一回事,只是这里连接口都没有开给不合法的方案。
候选集合缩小之后,剩下的活是在这个集合里找最优的一条,VITS 沿用 Glow-TTS 给出的动态规划来做。设 Q[x,y] 表示:把前 x+1 个音素对齐到前 y+1 帧、且满足单调不跳过时,能拿到的最大对数似然。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| value[x,y] | 第 y 帧的声学特征,落在第 x 个音素当前的高斯分布里的对数似然 | 这一帧长得像不像第 x 个音素 |
| Q[x−1,y−1] | 上一帧刚好对应上一个音素——这一步是「换到下一个音素」 | 这个字刚读完,换下一个字 |
| Q[x,y−1] | 上一帧也对应同一个音素——这一步是「继续停留」 | 这个字还没读完,继续读 |
| max(⋯) | 两种来路里选对数似然更大的那个 | 这一帧更像是刚换字,还是像是接着上一个字 |
穷举:2.314×10⁶⁸ 条路径
把每一种满足单调不跳过的切法都列出来,逐条算总似然再比较。50 个音素对 500 帧就已经是这个量级,实际训练里的句子和帧数往往更长。
动态规划:25000 个格子
只维护一张 Ttext×Tmel 大小的表,每个格子的值由左边或左上角的格子加上这一帧自己的打分得到,算完整张表就等于比较完了全部合法路径。
差别在:穷举把每条路径当成独立的对象重新算一遍,动态规划发现绝大多数路径共享同一段前缀的最优值,算一次、存下来、反复取用。
如果一句话的音素个数比语音帧数还多——比如 3 个音素对 2 帧——单调不跳过的合法对齐还存在吗?构造这个最小的反例,说清为什么。
变式:如果音素数恰好等于帧数(比如 500 个音素对 500 帧),合法对齐还剩几种?这时候 C(499,499) 等于多少,说明了什么?
VITS 用的频谱配置是 22.05 kHz 采样、hop 256。给一段 3 秒长、40 个音素的句子喂进 MAS,动态规划要填的表格一共有多少个格子?
变式:如果把 hop 从 256 改成 128(帧率翻倍),同样 3 秒 40 音素的句子,格子数会变成多少?相对原来涨了几倍?
17.4 把丢掉的节奏找回来,以及这条路后来卡在哪
FastSpeech 2 的时长预测器是一个确定性回归:同一句话、同一个模型,每次预测出来的时长完全一样。VITS 靠 MAS 在训练时找到对齐,但推理时没有真实语音可以对齐,也需要一个模块在没有目标的情况下给出时长。你觉得 VITS 会不会照搬 FastSpeech 2 那种确定性回归?
答完再看
VITS 论文明确指出了确定性回归的短板:「确定性时长预测器……无法表达一个人在不同时刻用不同语速说话的方式」,原文是「it cannot express the way a person utters at different speaking rates each time」。他们的解法是随机时长预测器,让它的采样结果服从给定音素的时长分布,而不是给出单一的点估计。论文里有一张图很直观:Glow-TTS 对同一句话反复生成 100 次,得到的时长永远是同一个值,因为它是确定性回归;换成 VITS 的随机时长预测器,这 100 次的时长会散开成一条分布,形状和自回归模型 Tacotron 2 天然带有的时长分布很接近。
随机时长预测器要解决一个具体的麻烦:每个音素的时长是一个正整数,而想要训练一个能采样出「分布」而不是「一个数」的生成模型,通常要靠一类叫标准化流的技术,这类技术要求变量是连续的、可逆变换的。VITS 用变分反量化的办法把离散的整数时长转成连续变量,再在这个连续空间上训练一个基于流的生成模型;采样时反过来,从随机噪声出发,通过这个流的逆变换,还原出一个具体的时长整数。这一层机制本身不需要在这里深究,记住它带来的效果就够了:同一句话,VITS 每次合成都可能读出不同的节奏,而 FastSpeech 2 那种确定性回归永远给出同一个答案。
确定性回归:FastSpeech 2 的时长预测器
给定一个音素的隐藏向量,输出永远是同一个数。同一句话反复合成一百次,每一次的时长完全相同,语速被锁死在训练数据的平均水平上,猜得越准越好。
随机时长预测器:VITS 的做法
给定同一个音素的隐藏向量,输出的是一整条分布,每次采样都从这条分布里抽一个新的时长。同一句话反复合成一百次,时长会散开成一条曲线,形状接近 Tacotron 2 这类自回归模型天然带有的时长分布。
差别在:确定性回归把「哪个时长最常见」当成唯一正确答案去拟合,随机时长预测器把「这句话所有说得通的读法」当成建模目标——代价是多套了一层标准化流,训练和推理都比单纯的回归贵。
这套机制的代价也要说清楚。VITS 的训练目标把五项损失拧在一起:重建损失和 KL 散度来自变分自编码器那一半,时长损失来自随机时长预测器这个基于流的生成模型,对抗损失和特征匹配损失来自判别器那一半——三种完全不同的训练范式(VAE、标准化流、GAN)被塞进同一个目标函数联合优化。VITS 自己的消融实验能看出这套组合有多依赖其中每一块:去掉先验分布里的标准化流,LJSpeech 上的 MOS 从 4.50 掉到 2.98,掉了 1.52 分,是这篇论文里能看到的最大单项下滑。
VITS 的消融实验显示,去掉先验分布里的标准化流之后,LJSpeech 上的 MOS 从 4.50 掉到 2.98,掉了 1.52 分。这项消融动的是先验分布的形状,没有碰「单调不跳过」这条对齐约束。这 1.52 分的下滑,能不能当成「MAS 排除漏字这件事不成立了」的证据?
变式:如果消融实验换成「去掉随机时长预测器,换回确定性回归」,你预期 MOS 会往哪个方向变,理由是什么?
单调不跳过这条约束本身也是一把双刃剑。它要求语音里的每一帧都能在文本序列里找到一个对应的音素,训练数据如果掺进了笑声、长停顿、或者字幕之外的语气词,这些帧在文本里根本没有对应的音素可以分配,动态规划只能把它们硬塞给旁边某个音素,对齐就会被这些不属于任何文字的片段带偏——这一点 VITS 论文没有做实验验证,是把单调不跳过这条约束的定义往下推一步就能得到的结论,论文自己的两个数据集(LJSpeech、VCTK)都是干净的朗读录音,没有涉及这类情况。VITS 给每个说话人配一个从查表得到的固定嵌入向量,训练时这张表的大小就是训练集里出现过的说话人数(VCTK 是 109 个);论文附录D演示的声音转换,做的也是在这 109 个训练时见过的说话人之间互相转换。论文全文没有一处提到,把一个从没在训练里出现过的新说话人的几秒钟录音喂进去,会发生什么。
FastSpeech 2 的时长来自 MFA 这个独立系统;VITS 的时长来自 MAS 加上 ELBO 联合训练,不需要外部工具。如果现在要给一批语料训练 TTS,其中混有大段的笑声和长停顿、还没有干净的音素级标注,你会更倾向选哪一种方案,为什么?
变式:如果语料是干净的单说话人朗读录音,没有笑声停顿,这时候两种方案的选择会不会反过来?
答辩:如果我是审稿人
17.3 节说 MAS 从结构上排除了漏字——不合法的切法根本不在候选集合里。可这只是训练阶段的性质。推理阶段没有真实语音可以做 MAS,时长只能靠随机时长预测器给出,如果它给某个音素采样出一个接近 0、四舍五入之后变成 0 帧的值,这个音素不还是会从最终语音里消失吗?MAS 排除漏字,是不是只在训练阶段成立,到了推理阶段又变回了 17.2 节那种「预测器出错就漏字」的老问题?
参考防守(先自己组织语言再看)
这个质疑站得住脚,而且指向了一个经常被混淆的区别:约束保证的是「训练时对齐的候选集合里没有漏字的方案」,不是「推理时不可能输出漏字」。两件事完全不是一回事。训练阶段,MAS 在已知真实语音的前提下,从单调不跳过的合法集合里挑最优对齐,这个集合里确实没有 0 帧的音素,模型学到的正是这种「每个音素至少一帧」的模式。但推理阶段要生成一段全新的语音,没有真实帧数可以约束,随机时长预测器给出的是一个连续采样值,理论上确实可能采样出接近 0 的数。这和 17.2 节的处境本质上是同一类问题:约束能保证的是候选空间的形状,保证不了具体某一次预测的数值一定落在合法范围内。VITS 论文没有专门讨论这个边界情况,训练数据里的音素时长通常有一个自然下限(不会有哪个真实音素只占零点几帧),随机时长预测器的采样也大概率落在这个范围附近,但「大概率」和 17.2 节里长度调节器给出的那种结构性保证不是同一个强度的承诺。
对你而言未知单调不跳过这条假设,边界到底在哪
单调不跳过对朗读体的干净语音成立得很好,但对着唱歌(同一个音节被拉长跨越好几个音高)、快速交叠的对话、或者本章刚讨论过的笑声停顿,这条假设会在哪个点上开始失真,有没有一个可以提前算出来的判据,而不是训一个模型跑出来再看效果?VITS 论文和它依据的 Glow-TTS 论文都没有正面回答这个问题,只是选择在干净的朗读语料上验证。
先做这一步:回到本节的 MAS 实验室,把打分矩阵人为构造成「某一个音素对应的帧在时间上并不连续」(模拟一个音节被拆成两段、中间插入别的音素),看动态规划给出的最优路径会怎么处理这种情况;再去查 Kim et al. 2020 的 Glow-TTS 论文,看它对这类边界情况有没有讨论。
读的时候要小心
关于本章数字的来路
本章里 FastSpeech 2 和 VITS 各自的 MOS、CMOS、RTF、以及去掉标准化流之后的 1.52 分下滑,都来自这两篇论文作者自己跑出来的评测,没有第三方复现过。MAS 的路径数和 DP 格子数是本站按组合公式直接算出来的,旁边的实验室按一下就能复现。
这一层要加什么:时长预测与单调对齐
为什么现在才加它:前十六级处理的都是声音自己怎么被表示成数字,没有一处涉及某一帧到底该对应文本里第几个字。这一层要给管线接上文本这一端,并且保证这份对应关系不多不少、顺序不乱。
难点:mas 里 x 的取值范围不是从 0 到 tx,而是 max(0, tx+y-ty) 到 min(tx, y+1),这一行最容易被漏掉。它做的事情正是 17.3 节反例题讲的那件事——检查「剩下的帧够不够剩下的音素每人至少分一帧」,超出这个范围的格子不满足单调不跳过,本来就不该被算进去。漏掉这个边界不会让代码报错,只会让 DP 偶尔算出一条不满足约束的路径,而且不容易被发现。另一个难点是 length_regulator 的输入必须严格是正整数,0 会让某个音素彻底消失,这正是 17.2 节和答辩里讨论的那种漏字。
自己验:给 length_regulator 任意一组时长向量(比如 [3,5,0,4,2]),输出的帧数必须精确等于这组数字的和,误差为 0;把某一位改成 0,对应位置的隐藏向量必须在输出里完全找不到。对 mas,取 T_text=10、T_mel=100 的随机打分矩阵求最优路径,回溯出的路径长度必须正好是 100,而且每个音素(0 到 9)在路径里至少出现一次;把某个音素的打分整体调低到极小值,最优路径依然会给它分够至少 1 帧,不会把它跳过去。
留给你的问题
17.2 节说长度调节器能保证「音素个数不丢」,17.3 节说 MAS 能保证「候选对齐里没有漏字的方案」,本节的答辩又指出这两条保证都只在各自的那个阶段成立。现在把三章拼在一起想:如果要设计一套既不依赖外部对齐工具(像 MFA)、又要在推理阶段也能给出「结构上不可能漏字」这种强保证(而不是随机时长预测器那种「大概率不会」)的方案,你觉得卡点会出在哪一步?
不看材料,写下这一章的三堵墙
第一堵:自由注意力没有任何机制保证权重沿时间单调前进,长句子和生僻词上偶尔会漏字重复。第二堵:显式时长预测器解决了「音素个数不丢」,但时长的准确度整个押在一个独立于 TTS 之外、非联合训练的外部对齐工具上。第三堵:MAS 用单调不跳过的约束从训练时的候选集合里彻底排除了漏字的可能,但这条约束假设了语音里的每一帧都对应某个文字,遇到笑声停顿或者推理阶段的时长采样误差,保证就不再是结构性的了。
第18章 把声音变成 token
开篇故事第 18 集 · 码本开到一千零二十四,还是塞不下一帧的信息这模型也太真实了
《潜空》弹出的那行判定让沈砚愣在屏幕前:码本一千零二十四个编号全部用满,还原出来的信噪比却只有个位数——这一千零二十四个编号,他昨晚提交的时候还觉得绰绰有余。
系统提示码本占用率 100%,重建信噪比 6.8 分贝,远低于同类配置的预期区间,本次量化校验不通过。
半小时前,语音这条线开始做一件新事:把每一帧连续的向量换成一个从固定表里查出来的编号,好让后面接一个像 GPT 那样一步一步往外吐符号的模型。这是回声复现的第一百四十八天,沈砚领了这个码本要开多大的活——他想起前面几层用的量化表大多在一千上下,随手就把这次的码本也定在了 1024,觉得跟以前差不多规模,应该够用。
结果码本刚训完就把语音压成了一团糊,靳工只问了他一句话就转身去接水:“你这一帧要装多少信息,算过吗?”沈砚这才发现自己漏了这一步——按 SoundStream 那档 6000 bit 每秒的目标,编码器每秒钟吐 75 帧,平摊下来每一帧要装 80 bit,而 1024 只是 2 的 10 次方,只够分辨 10 bit,剩下 70 bit 的组合全被挤成了同一个编号。
码本从1024开始训到崩溃,这道账到底该怎么算清楚
1L沈砚我把这次码本定在了 1024,跟前面几层用的规模差不多,没想到还原出来的信噪比直接跌到个位数,这道账我到底算错在哪一步?
2L复现之鬼你这一千零二十四个编号,是按信息量倒推出来的,还是照搬了以前的规模,没倒推的话能不能拿出算式来,我想自己核一遍?
3L炼丹废人我也一直以为码本这种东西大差不差,能用就行,听你们这么一问,我才发现自己从没算过这个数该怎么来。
4L显存刺客真要按位数翻倍算,八十个 bit 要开到 2 的 80 次方,这数字存下来都够呛,谁的显卡也扛不住这个规模。
显存刺客这句话把靳工引了过来,他扫了一眼那串 2 的 80 次方,说这数字本身就是这道题的死结——2 的 80 次方约等于 1.21×10²⁴,SoundStream 论文原话说这个规模的码本显然不可行,连存都存不下,量级已经追上了宇宙里的原子数。
沈砚没有立刻认怂,他把 1024 拆成八张表接连查,每张还是 1024 个编号:第一张选完剩下的误差交给第二张,第二张选完剩下的再交给第三张,八张连着查完,一帧变成八个编号。他把 1024 自乘八次,屏幕上跳出的答案和最初那面死结——2 的 80 次方——一位不差地对上了。
“组合数对得上,不等于八张表拼出来的声音和那张开不出来的大表一样准。”靳工看着这行吻合的数字,补了一句,“第二张表只能在第一张剩下的残差里找,它没有资格回头去动第一张已经选好的编号,这是省着存的代价,不是白拿的。”
这一晚重训八层码本,把语音这条线原定这周要交的进度往后推了一天,沈砚主动把这次的教训写进了模块说明文档,签上了自己的名字。他盯着桌上那一串刚查出来的八个编号,忽然想到一个更大的问题:这串编号排好之后,是不是就能像 GPT 写句子那样,一个一个往外蹦?这事他们谁都还没试过。
系统结算 · 本章考点
- 一帧语音想装下 80 bit 信息,单一码本要覆盖这些组合,大小必须是 2⁸⁰,这是指数关系不是线性关系,SoundStream 论文原话说这个规模显然不可行
- 这 80 bit 的来路:SoundStream 编码器每秒吐 75 帧,目标码率 6000 bit/秒,平摊到每一帧正好是 80 bit
- 残差量化把这堵墙拆成 8 层、每层 1024 个编号的表,1024 的 8 次方换算成 2 的幂正好是 2⁸⁰,和单一大码本能表达的组合数完全相等
- 组合数不丢不等于重建质量不打折扣:第二层只能去逼近第一层剩下的残差,没法回头改第一层已经选中的编号,是逐层的局部最优
- EnCodec 论文说码本层数是 4 的倍数,按比特率公式反推五档码率对应的层数其实是 2、4、8、16、32,最低的 1.5 kbps 那一档是 2 层,并不是 4 的倍数
- VALL-E 的图 2 说第一层码本管的是说话人身份这类粗粒度信息,越往后几层补的是精细的声学细节,影响逐层减小
GPT 生成一段文字时,每一步都是从几万个候选词里挑一个写下来,再挑下一个。如果想让同一套机制生成语音——每一步从一张固定的词表里挑一个符号——第一个绕不开的问题是:语音原本是一条连续的波形,压成频谱之后还是一串连续的数,这张词表要从哪里来,得有多大,才能装下一帧语音携带的信息?
学完这一章你应该能做到
- 说清这一章的「token」和第6、9章那种数出来多少个的 token 不是一回事——这里的 token 必须是从一张固定大小的表里挑出来的一个编号
- 推出单一码本会撞的容量墙:一帧语音想装下多少信息,朴素的码本大小就要以什么速度爆炸
- 说清残差量化怎么把一个装不下的码本拆成几层装得下的码本,用组合数学说明这一拆没有损失可表达的组合数量,但这不等于重建质量不打折扣
- 亲手复现或拆穿论文表格里的一个数字,练一次「论文写的数字要自己算一遍才能信」
这一层要把上一层给出的连续 Mel 帧,换成一串离散的整数编号——不是四舍五入,是从一个训练出来的码本里挑出最接近的那一个。前十七级不管输出端是扩散模型的噪声预测,还是长度调节器给出的时长,都要么是连续值,要么是一条受约束的对齐路径,没有一层的输出是「从一张固定词表里选一个编号」。这一层要第一次把语音也变成能塞进语言模型词表的样子,为下一层把生成语音变成语言模型任务铺好地基。
18.1 一段语音要变成词表里的一个词,词表得有多大?
一个训练好的语音编码器,把一整段音频压缩成一串帧,每一帧是一个携带固定信息量的向量——不妨假设每帧携带 80 bit 信息(后面会看到这个数字从哪来)。如果想把每一帧直接变成「查表得到的一个编号」,最直接的办法是训练一张码本:码本里存一堆向量,编码时把这一帧换成离得最近的那个码本向量的编号。这张码本要多大,才能保证每一种可能的 80 bit 组合都对应一个不同的编号?
答完再看
第二个选项是算术上的必然结果。要让码本里的编号能区分开 80 bit 信息能表达的全部组合,编号的个数必须至少是 2⁸⁰——这不是设计选择,是「一个二进制位能翻出两倍的可能性」这条定义本身决定的。第一个选项把这里的码本和别处见过的小规模码本混成了一回事,但那些码本都只覆盖很少几个bit;第三个选项把「bit 数」和「码本项数」搞反了,8 bit 对应 2⁸=256 个可能取值,不是 8 个。
先把「token」这个词在这里的意思说清楚,因为它在前面章节里已经出现过,意思却不一样。第6章算过一张图片压缩之后有多少个 token,第9、11章算过一段视频有多少个 token——那些 token 说的是「序列里有多少个位置」,每个位置上放的仍然是一个连续取值的向量,扩散模型要预测的是这个向量该长什么样,答案有无穷多种可能。这一章说的 token 是另一回事:从一张大小固定、事先训练好的表里,挑出第几号,答案只有表里那么多种,不多不少。语言模型要在词表上做 softmax、挑概率最高(或按概率采样)的一个词,这套机制要求候选集合本身就是有限、离散的——语音要接进这套机制,光靠「数出来有多少个位置」是不够的,每个位置上放的东西必须先变成离散的编号。
第6、9章的 token:位置离散,取值连续
一段视频压缩完有 14,080 个 token,这个「14,080」数的是序列里有多少个位置;每个位置上是一个连续的隐向量,扩散模型给它预测一个连续的噪声或速度,取值有无穷多种可能。
这一章的 token:位置和取值都是离散的
每一帧只能变成码本里 1024 个(或者别的固定数字)编号中的一个,取值本身就是有限集合里的一个整数,这正是语言模型的 softmax 输出层要求的形状。
差别在:前者的「token」只回答「序列有多长」,后者的「token」还要回答「每一格的取值一共有多少种可能」——这一章要处理的就是第二个问题。
把「一帧语音携带 80 bit 信息」这个数字的来路交代清楚。这个例子来自 SoundStream:encoder 每秒钟把 24000 Hz 的音频压成 S = 75 帧,如果目标是把整段音频压到 6000 bit/秒(6 kbps,SoundStream 论文实测过的一档,对照未压缩的 24kHz×16bit=384,000 bit/秒,已经是 64 倍压缩),平均到每一帧,就是 6000÷75=80 bit。论文原文写的是「When using a striding factor M = 320, each second of audio at sampling rate fs = 24000 Hz is represented by S = 75 frames at the output of the encoder. This corresponds to r = 6000/75 = 80 bits allocated to each frame.」
如果一帧语音只需要携带 20 bit 信息(比 80 bit 小得多),用单一码本直接查表,码本需要多大?这个数字还算「不可行」吗?
变式:80 bit 如果不平均分给一帧,而是允许码本大小达到 100 万(20 bit 左右),要维持同样的总信息量,需要多播出多少帧才能不丢信息?
这就是单一码本撞上的墙:N = 2⁸⁰ ≈ 1.21×10²⁴ 个向量,SoundStream 论文原话是「Using a plain vector quantizer, this requires storing a codebook with N = 2^80 vectors, which is obviously unfeasible」——用一个朴素的向量量化器,需要存储一个有 2⁸⁰ 个向量的码本,这在存储上完全不可行。不可行不是训练慢的问题,是这张表本身连存储都存不下:宇宙里的原子数量级大约是 10⁸⁰,这张码本要存的向量个数和这个数字是同一个量级。
第14章的 μ-law 把每一个原始采样点量化成 256 档(8 bit),24000 Hz 采样率下,相当于 8×24000=192,000 bit/秒。这一章 SoundStream 的目标码率是 6,000 bit/秒。同样是把连续信号变成离散符号,为什么后者能把码率压到前者的三十二分之一,而不是听起来更差?
变式:如果把 μ-law 的量化对象也换成「先用某种方式每 320 个采样点取一个平均值」,粗暴降采样之后再做 256 档量化,码率会降到多少?这样做和 SoundStream 的差别还剩下什么?
18.2 一张装不下的表,怎么拆成几张装得下的表?
2⁸⁰ 个向量的码本存不下、训不动,但 80 bit 的信息量本身没有办法凭空减少——减少就意味着丢信息。如果不能把码本变小,但可以把「查一次表」这件事拆成「查好几次表」,你觉得能不能用这个思路把 2⁸⁰ 这堵墙绕过去?
答完再看
第二个选项是残差量化(Residual Vector Quantization,RVQ)的思路,SoundStream 论文原话是「The total rate budget is uniformly allocated to each VQ, i.e. r_i = r/Nq = log₂N. For example, when using Nq = 8, each quantizer uses a codebook of size N = 2^(r/Nq) = 2^(80/8) = 1024」——把总的比特预算平均分给每一级量化器,8 级、每级 1024。第三个选项的担心不是没有道理,8 张 1024 大小的表能表示的组合数其实和 2⁸⁰ 是同一个量级(下面会用组合数学验证这件事),信息容量并没有被牺牲,但这不等于重建质量和一步到位的大码本完全相同,这一点后面会说清楚。第一个选项的直觉在别的场合是对的(比如无损编码不能凭空压缩信息),但这里问的是「表的存储总量」,不是「信息量」,两者可以不成正比地变化。
拆分的具体做法叫残差量化。先训练第一张码本,让每一帧去里面找最接近的向量,这一步和单一码本量化没有区别;不同的是,接下来不再扔掉「找得不够准」这部分误差,而是把它算出来——原始向量减去刚才选中的码本向量,剩下的这部分叫残差,它是原始信息里第一张表没能表达的那部分。第二张码本不再去逼近原始向量,改成去逼近这份残差;量化完第二张表之后,再算一次新的残差,交给第三张表……如此重复 8 次。最终每一帧被换成 8 个编号,一个编号对应一张表,解码时把 8 张表里选中的向量加起来,就是对原始向量的近似还原。
这里有一个容易被忽略的问题,值得在往下走之前先算一遍:8 张 1024 大小的表,真的和 1 张 2⁸⁰ 大小的表一样能装吗?还是说,为了塞进存储,实际上已经在悄悄丢信息?
自己推一遍:8 层、每层 1024 个选项,一共能拼出多少种不同的编号组合
第一层独立选 1024 个选项里的一个,第二层再独立选自己那 1024 个选项里的一个,两层合在一起,一共有多少种不同的组合?
想好了再看
1024×1024 种。这是乘法原理:第一层的每一种选择,都可以搭配第二层的任意一种选择,两件独立的事各自的选法数相乘,就是合在一起的选法数。
照这个逻辑推广到 8 层,一共能拼出多少种不同的编号组合?
想好了再看
1024⁸ 种,八个 1024 连乘。8 层的选择互相独立——每一层选哪个编号,不会限制另一层能选哪个编号,乘法原理可以直接推广到任意多层。
1024⁸ 换算成 2 的幂,等于多少?和最开始那张 2⁸⁰ 的码本比,谁大谁小?
想好了再看
1024=2¹⁰,1024⁸=(2¹⁰)⁸=2⁸⁰——和单一大码本能表达的组合数完全相等,一个编号都不多,一个编号都不少。8 层、每层 1024,拼出来的编号组合总数和那张存不下的大表严丝合缝地对上了。
既然可表达的组合数完全相等,是不是可以说「RVQ 的重建质量和那张假想中的 2⁸⁰ 大码本完全一样好」?
想好了再看
不能这么说,这一步容易踩空。组合数相等,说的是「8 个编号连在一起,能表示 2⁸⁰ 种互不相同的取值」,这是一个纯计数事实。但那张假想的 2⁸⁰ 大码本,它的每一个向量位置都可以在训练时自由地摆在高维空间里最合适的地方;而 RVQ 的第二层码本,只能去逼近「第一层已经量化完之后剩下的残差」,它的搜索范围被第一层的选择锁定了,不是在整个原始空间里自由挑选。编号组合数不丢,不等于两种方案对同一个原始向量给出的重建误差也完全相同——后面会看到,8 层加起来的重建误差通常比理想的大码本略大,这是残差量化用存储可行性换来的代价,不是免费的午餐。
组合数对得上、真实重建质量却可能有折扣,这个区别到这一步已经算清楚了;比特率这本账还没算。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| Nq | 残差量化的层数 | 拆成了几张表 |
| N | 每一层码本的大小(选项个数) | 每张表有多少个编号可选 |
| log₂N | 每一层需要几个 bit 才能编码一个编号 | 存一个编号要花多少信息量 |
| S | 编码器每秒钟输出多少帧,等于 fs/M | 一秒钟要查多少次表 |
| 编解码器 | fs | M | S(帧率) | N | Nq | 算出的比特率 | 论文值 |
|---|---|---|---|---|---|---|---|
| SoundStream | 24000 Hz | 320 | 75 Hz | 1024 | 8 | 6,000 bit/s | 6 kbps |
| EnCodec 24kHz | 24000 Hz | 320 | 75 Hz | 1024 | 2/4/8/16/32 | 1.5/3/6/12/24 kbit/s | 同左(VALL-E 用 Nq=8 那档) |
| EnCodec 48kHz | 48000 Hz | 320 | 150 Hz | 1024 | 16 | 24,000 bit/s | 24 kbps |
| DAC | 44100 Hz | 512 | 86.13 Hz(论文取整 86) | 1024 | 9 | 7,740 bit/s(用 86 算) | 约 8 kbps |
用上面的公式算一下 EnCodec 24kHz 在 Nq=16 那一档的比特率,应该是多少 kbps?和表1里给出的档位对得上吗?
变式:如果想要 EnCodec 24kHz 达到 9 kbps,需要几层?算出来是整数吗,这说明了什么?
同样是覆盖 80 bit 的信息量,方案A是 8 层、每层 1024(本节的做法);方案B是 4 层、每层 65536(2¹⁶)。两个方案编号组合总数是否相等?两个方案的码本存储总量(层数×每层大小)分别是多少,谁的存储代价更小?
变式:如果反过来,用 16 层、每层 32(2⁵),能不能覆盖 80 bit?这个方案的存储总量是多少,比 8 层 1024 更省吗?
答辩:如果我是审稿人
刚才的推导证明了 8 层 1024 和单一 2⁸⁰ 码本能表达的编号组合数完全相等,你自己也承认了这一点。既然组合数不丢,为什么后面还要一直强调 RVQ 的重建质量「可能有折扣」?这两句话是不是自相矛盾——要么信息没丢、质量应该一样好,要么信息丢了、组合数就不该相等?
参考防守(先自己组织语言再看)
这不矛盾,关键在于分清「能表示多少种不同的编号」和「每一种编号实际对应的重建误差有多大」是两个不同的问题。组合数相等这件事只说明:8 个编号连起来,取值空间和 2⁸⁰ 一样大,不存在「有些原始向量根本没有编号可以对应」的情况。但两种方案给「同一个原始向量」分配编号的过程完全不同:假想中的大码本可以在训练时直接优化 2⁸⁰ 个向量在高维空间里的位置,让它们尽量贴近数据的真实分布;RVQ 的第二层码本,训练时看到的输入永远是「第一层量化剩下的残差」,它的搜索空间被第一层的选择结果限定死了,不能反过来影响第一层已经做出的选择。这是一种贪心式的、逐层局部最优的搜索,不是全局联合最优的搜索,两者理论上可能达到的最小重建误差不是同一个数。组合数够用,只保证「有地方放」,不保证「放的时候每次都放在离原始向量最近的位置」。
18.3 91.16 这个压缩比,是用哪个帧率算出来的?
把表1里 DAC 那一行的比特率往下算一层,能碰到一个值得停下来看的细节。DAC 论文 Table 1 报告的压缩比是 91.16——压缩比的定义是原始比特率除以压缩后的比特率,DAC 用的是 44100 Hz、16 bit 的原始音频,除以量化之后的比特率。用论文里写出来的帧率 86 Hz(四舍五入到整数)代入:
91.1628 四舍五入到两位小数正是 91.16,和 DAC 论文 Table 1 写的数字完全对上。但这里悄悄用了一个取整之后的帧率:86 Hz 不是精确值。DAC 的帧率由 S = fs/M = 44100/512 算出,这个除法算不尽。
44100 除以 512,精确到小数点后四位是多少?把这个精确值代入压缩比公式(分母换成 9×10×精确帧率),算出来的压缩比还是 91.16 吗?
变式:如果 DAC 论文的 Table 1 换成用精确帧率 86.1328 重新出版,91.16 这一格会变成 91.02,你觉得这算不算一个需要勘误的错误?为什么?
同一张表的另外几行没有这个问题:EnCodec 24kHz 在 24 kbps 那一档,24000×16/24000=16 倍,SoundStream 在 6 kbps 那一档,24000×16/6000=64 倍——这两个都是整数相除、除得尽,不存在四舍五入的分歧。91.16 这个数字之所以特别,是因为 DAC 恰好选了 44100/512 这样一个除不尽的组合,取整与否的差别才第一次在压缩比这个数字上露出来。
18.4「码本数是4的倍数」这句话,为什么有一档对不上?
EnCodec 论文 §3.2 说,训练时会为不同的目标码率随机选码本层数,「码本层数是 4 的倍数」,并且给出了五档目标码率:1.5、3、6、12、24 kbps。按照本节的比特率公式反推每一档需要的层数(N=1024,S=75 Hz),五档算出来的层数会不会都是 4 的倍数?
答完再看
第二个选项是算出来的结果,靠本节的比特率公式反推就够了,不需要看代码。这是一处值得练习的地方:论文里一句概括性的描述(「a multiple of 4」)和论文自己给出的具体数字表(五档码率)放在一起核对,会发现两者没有完全对齐——这种时候更可信的是那组具体数字,不是那句概括性的描述。
把五档码率挨个反推一遍:层数 = 目标码率 ÷(log₂1024 × 75),也就是目标码率(单位 bit/s)除以 750。1.5 kbps→1500/750=2;3 kbps→3000/750=4;6 kbps→6000/750=8;12 kbps→12000/750=16;24 kbps→24000/750=32。五个层数是 2、4、8、16、32——后面四个确实都是 4 的倍数,只有最低的 1.5 kbps 那一档,层数是 2,不满足「4 的倍数」。
只凭上面这道除法,还只能说「我自己反推出层数是 2、4、8、16、32,和论文那句话对不上」,没法排除「我的公式本身就用错了」这种可能。EnCodec 论文和另外两篇论文里,各有一处独立的数字可以用来交叉核对:EnCodec 论文同一段还提到「最多 32 个码本(48kHz 模型是16个)」;DAC 论文 Table 1 记录了 EnCodec 24kHz 在 24kbps 档用的码本数;VALL-E §3.1 提到自己用的「8 层」对应 EnCodec 哪个码率档。把这三条分别对上你反推出的 2、4、8、16、32 这组数字,它们能不能共同确认这组数字是对的,而不是你的公式推错了?
变式:如果只有 VALL-E 这一条证据(8 层对应 6kbps),没有 EnCodec 自己「最多32个」和 DAC Table 1 这两条,你还能不能同样有把握地下结论?为什么三条证据比一条更有说服力?
答辩:如果我是审稿人
你说 EnCodec 论文「写得不严谨」,但会不会是你对「a multiple of 4」这句话理解错了?也许论文的原意根本不是「五档码率对应的层数都是4的倍数」,而是「训练时随机选码本层数,随机选的候选范围本身被限制成4的倍数」,1.5kbps 那一档的 2 层,可能压根不在这句话描述的「训练时随机采样」范围内,只是推理时额外支持的一个固定档位?这样的话,论文原文就没有自相矛盾。
参考防守(先自己组织语言再看)
这个质疑提出了一种能让论文自洽的读法,值得认真对待——本站手上的材料(论文正文这一段)没有给出足够的信息去彻底排除这种读法,这一点应该老实承认,不该硬说论文一定错了。但即便按这种更宽松的读法,正文提出的核心论点依然成立:不管「4的倍数」描述的是训练时的随机采样范围,还是全部支持的档位,1.5kbps 这一档用的都是 2 层,这个数字本身没有争议(有 DAC Table 1 和比特率公式两条独立证据)。真正站得住的结论不是「论文有错」,而是「读这句话时,光看『a multiple of 4』这几个字,会让人以为全部支持的码率档位层数都是4的倍数,实际去核对五档具体数字才会发现1.5kbps是个例外」——这提醒读者具体数字表比一句概括性描述更可靠,不必纠结于论文原意是不是自洽。
18.5 八层码本里,第一层和最后一层各管什么?
残差量化还留了一个问题没有回答:8 层码本量化误差是怎么随层数下降的,每一层各自都在补什么。这里要先说清一件事:残差误差具体怎么随层数衰减,SoundStream 和 EnCodec 两篇论文都没有给出一条闭式公式——常见的一种简化设想是,假设每一层的量化误差都和它接收到的残差能量成固定比例 ρ(0<ρ<1),那么第 i 层之后剩下的误差能量满足 Ei/E0=ρi,换算成信噪比大约随层数线性上升。这是本站为了方便直觉给出的一个理想化模型,不是论文里的公式——旁边的 RVQ 实验室可以自己跑一批向量,把每层实测的信噪比和这条 ρ^i 曲线摆在一起看像不像,这是一个可以自己验证、但不该当成论文结论转述的假设。
既然 ρ^i 这条衰减曲线不是论文里的公式,那论文里真正能找到的、关于「多层残差量化确实有效」的证据是什么?
变式:如果训练时不用量化器 dropout,只固定用满 8 层,解码时只用前 3 层还能不能正常工作?为什么量化器 dropout 是让「用前几层也能解码」这件事成立的关键?
量化器 dropout 这个训练技巧顺带回答了另一个问题:为什么可以放心地说「只用前 k 层解码,效果也能听」——因为训练时模型本来就被反复逼着只用随机截断的前若干层去重建,只用一部分层解码从来不是一个训练时没见过的场景。这个性质叫嵌套性:用完整 8 层解码得到的结果,和只用前 3 层解码得到的结果,前者的前 3 层解码路径和单独跑一次「只用 3 层」得到的结果是同一回事——不需要为不同的层数分别训练一套模型。
第1层:内容和音色的骨架
去掉这一层,语音的可懂度和说话人身份会受到最大冲击——它扛的是「这段声音听起来是谁在说、大致在说什么」这类粗粒度信息,是残差量化里最先被分配、也最不能丢的一层。
第8层:打磨细节
去掉这一层,声音大概率还听得懂、还是那个说话人,只是质感会变粗糙一点——它补的是前面几层留下的、能量已经很小的残差,丢了它损失的是细节而不是骨架。
差别在:这不是「哪一层更重要」的主观判断,是残差量化的结构本身决定的——第一层拿到的是原始信号的全部能量,后面每一层拿到的都是前面没解释掉的、越来越小的残差,越靠前的层能补的信息量天然越大。
这个分工不是本站自己猜的,VALL-E 论文写得很直接。图2标题:「the first quantizer plays the most important role in reconstruction, and the impact from others gradually decreases」——第一个量化器在重建里起的作用最大,后面几个量化器的影响逐渐减小。§3.1 说得更具体:「tokens from previous quantizers recover acoustic properties like speaker identity, while the consecutive quantizers learn fine acoustic details」——前面几层的 token 恢复的是说话人身份这类声学属性,后续几层学到的是精细的声学细节。
答辩:如果我是审稿人
这条分工证据只来自 VALL-E 一篇论文对 EnCodec 的观察,VALL-E 自己并不是编解码器论文,它只是拿别人训好的 EnCodec 来用。SoundStream 和 DAC 的论文都没有专门分析每一层携带什么语义。拿一篇下游论文的观察,去支持一条关于「残差量化结构本身」的普遍结论,是不是证据不够?
参考防守(先自己组织语言再看)
这个担心站得住,应该老实承认:本节能引用的分层语义证据确实只来自 VALL-E 对 EnCodec 的观察,SoundStream、DAC 的论文重点是压缩率和音质,都没有专门做这项分析。前面的对比之所以还站得住脚,不是因为有多个编解码器的证据互相印证,而是能从残差量化的结构本身推出一个方向性的论证:第一层看到的是原始信号的全部能量,后面每一层看到的都是前面没解释掉的残差,残差能量逐层变小这件事不依赖具体是哪个编解码器。但「结构上残差能量递减」和「递减的具体内容恰好是内容与音色优先、细节殿后」这两件事之间,还有一步没有被证明——这一步目前只有 VALL-E 这一篇论文的观察在支撑,换一个编解码器会不会重复出现同样的分工,材料回答不了。
对你而言未知层级分工是残差结构必然导致的,还是训练出来的
「第一层管大局、后面几层管细节」这个分工,从残差量化的结构本身(先量化能量最大的部分,后面各层量化越来越小的残差)看似乎是必然的——但 SoundStream、EnCodec、DAC 三篇论文都只是在训练完之后观察到了这个现象(VALL-E 图2那句话本身也是描述性的,不是一条证明),没有一篇给出理论证明:换一种训练目标或者损失函数的权重分配,这种分工会不会变化,甚至反过来?残差结构本身规定的只是「后面层的输入是前面层的残差」,并没有从数学上规定「说话人身份信息一定会集中在前几层」,这里面很可能混杂了训练数据分布和损失函数设计的影响,本章手上的材料回答不了这个问题。
先做这一步:回到 RVQ 实验室,尝试换一种训练目标(比如把损失函数换成只优化音色相似度,而不是整体重建误差),看层级分工的现象是否还会出现在同样靠前的层;再去查有没有专门分析 RVQ 分工机制的后续论文,看这个问题在本章成文之后有没有被解决。
如果传输带宽只够传 8 层里的 3 层,你会选前 3 层还是后 3 层(比如第6、7、8层)?说清理由,以及这个选择在什么情况下可能是错的。
变式:如果反过来,可以传 5 层,但允许跳过中间某一层(比如传第1、2、3、5、8层,跳过第4、6、7层),这能不能正常解码?为什么?
读的时候要小心
关于本章数字的来路
SoundStream、EnCodec、DAC 三篇论文各自报告的比特率、压缩比、以及 VALL-E 图2和§3.1 那两句关于分层语义的描述,都来自论文作者自己的实验和观察,没有第三方复现过,本章按 B 档处理。本章的比特率公式、DAC 压缩比的两种算法、EnCodec 层数反推,都是可以自己拿计算器复算的算术,不依赖任何未公开的细节。ρ^i 那条误差衰减曲线是本站给的理想化假设,不是任何论文的公式,需要单独标出来。
这一层要加什么:k-means 码本与残差量化
为什么现在才加它:前十七级不管是扩散模型的连续输出还是长度调节器的时长,从没有一层的输出是「从固定词表里选一个编号」。这一层要把连续向量换成离散编号,还要保证「先解码前几层」和「直接解码全部层」在共享的那部分是完全一致的。
难点:rvq_encode 里每一层的 residual = residual - cb[idx] 这一行不能漏,漏了这一行,8 层量化器全都在逼近同一个原始向量,不再是逐层缩小残差,等于把 8 层退化成了 8 次互相独立的重复量化,既不会带来嵌套性,重建误差也不会随层数下降。另一个难点是 rvq_decode 的 k 参数:只用前 k 层解码,必须是把 indices 列表切片到前 k 个直接求和,不能重新跑一遍编码再截断——训练时如果配了量化器 dropout(随机只用前若干层计算损失),这两种写法在数值上应该完全等价,但如果没有配量化器 dropout,只训练过「用满全部层」这一种情况,中途截断解码出来的质量会明显比训练时预期的差。
自己验:对一批随机高维向量跑 8 层、每层 K=1024 的 RVQ,逐层记录重建信噪比(SNR),应该逐层上升,不应该出现加一层反而下降的情况;分别用 rvq_decode(indices, codebooks, k=3) 和「先跑完整的 8 层 rvq_encode,只对前 3 个 indices 手动求和」这两种算法,两者的输出必须逐元素相等,不是「数值接近」而是完全相同;把每层的 K 从 1024 改成 2,重新跑一遍,每加一层带来的 SNR 提升应该比 K=1024 时小得多——K=2 时每层只能在两个候选点里挑,K=1024 时每层能精细得多,层数增加的收益因此被压缩得多。
留给你的问题
这一章证明了 8 层 1024 的编号组合数和一张 2⁸⁰ 的大码本完全相等,也承认了这不等于两者重建质量相同;还看到 91.16 这个数字背后藏着一个取整口径的选择,「4 的倍数」这句话只精确覆盖了五档码率里的四档。如果现在要你重新设计一套编解码器的验收标准,除了论文喜欢报告的比特率和压缩比,你觉得还应该要求作者公开哪些具体数字,才能让别人不用去猜取整口径、不用去凑三条独立证据,就能直接核实论文的说法?
不看材料,写下这一章的三堵墙
第一堵:单一码本要覆盖一帧语音的信息量,需要的码本大小是 2 的指数次方,80 bit 就已经让这张表比宇宙里的原子还多,存不下也训不动。第二堵:残差量化把这堵墙拆成了 8 层小码本,编号组合数在算术上完全不丢,但这只保证「有地方放」,不保证每一层的局部最优选择能拼出全局最优的重建,代价藏在「贪心式逐层搜索」这个结构本身里。第三堵:论文自己报告的数字不能直接照搬——DAC 的压缩比取决于帧率取整与否,EnCodec 「4 的倍数」这句概括性描述本身就有一档反例,读论文的数字需要自己核实,不能只读一句话就当结论。
第19章 语言模型做语音
开篇故事第 19 集 · 三秒钟,音箱里说出靳工没讲过的一句话这模型也太真实了
音箱先“滋”了一下电流声,接着那句话就冒了出来——是靳工的声音,连他开口前习惯性的那个停顿都在,说的却是一句“回声这一版语音,往后配四十分钟的解说词都用不着真人”。靳工人就站在离音箱两步远的地方,眉头先皱了一下,他确定自己从没说过这句话,一个字都没说过。屋里几个人齐刷刷转头看他,谁都没先开口,那几秒钟的安静比刚才那句话本身还让人后背发凉。
这是回声复现的第一百五十九天,半小时前,沈砚正试着用第十八天定下的那套码本,看看能不能真的凭空克隆一个从没录过音的人。他手头没有现成的陌生人素材,就翻出上周开会时顺手录下的一段音频,把里面靳工说的那几句话剪出来,凑够三秒钟,当成参考音频喂了进去,又随手敲了几句靳工从没说过的台词,让模型接着往下念。
显存刺客凑过来看了一眼那段波形,脸色比屏幕还白,一口咬定这背后必须连夜跑过一次微调——三秒钟的料,怎么可能凭空喂出这么准的音色。他转身就去翻当晚的训练队列,想揪出那个“偷跑”的任务,顺带算一算这笔算力账最后该记在谁头上。
系统提示本次合成未触发任何参数更新,参考音频编码为 3 秒声学 token 前缀,模型全程只做前向续写,训练队列自晚间八点起没有新增任务。
显存刺客盯着这行字愣住了,队列干干净净,没有他以为的那个任务。零样本克隆靠的不是连夜微调,是把这三秒钟编码成前缀,模型在训练时见过太多“前缀是谁的音色,后面就该延续谁的音色”这种续写,套到靳工身上,它照做了,参数一根都没动。“别老想着算力账,这次真没花你的卡时。”靳工端着杯子从里屋出来,看了一眼那行提示,“它在六万小时的录音里,把这条规律学得很熟,谁的声音喂进去,它都敢接着往下续。”
三秒钟就能克隆音色,这活儿到底有没有偷偷开一次微调
1L显存刺客三秒钟就能有这个像真度,我是不信零训练能做到的,肯定是拿那段录音偷偷起了个任务,不然这音色哪来的这么准。
2L炼丹废人我查过了,今晚八点之后队列里压根没起过新任务,显存占用曲线也是一条直线,你说的那个偷跑任务根本不存在。
3L沈砚不是查不查得到的事,是这套机制本来就不用查——参考音频编码成前缀,模型往下续写,从头到尾没有反向传播这一步。
4L复现之鬼不对的是判断的起点,不是查证的方式,谁的声音都能这样三秒钟喂进去,这件事本身就值得多想一层,不该光顾着算成本。
复现之鬼这句话说完,屋里又静了一下,这回没人接话。靳工把杯子放到桌上,说了句和刚才那件事看似不搭边的话:“论文自己认了三笔账没了结,其中一笔跟模型算得准不准没关系——官方到现在也没放出能下载的那份权重。”他说完转身回了自己那张桌子,没再往下说,那三秒钟的录音还留在系统里,谁都没提要不要删掉。这段试出来的音频,靳工没让删,也没再放第二遍,工作群里那条问“能不能现场演示一次语音对话”的私信,还没有人回。
系统结算 · 本章考点
- 零样本克隆靠的不是微调:合成前后模型参数没有发生任何更新,只把几秒钟的参考音频编码成前缀,让模型接着往下续写
- “零样本”准确的意思是这个说话人没有出现在训练集里,不是模型对这类音色一无所知——它是在 60,000 小时、不同说话人的录音里,学出了一整片能覆盖相当广音色的空间
- 8 层声学 token 里,AR 只负责吐出第一层、靠采样到结束符定长度;剩下 7 层全部交给 NAR 并行补齐
- 10 秒语音对应约 500 帧,8 层全部自回归要走 8×500=4000 步,AR+NAR 分工只要 500+7=507 轮,快了约 7.9 倍
- 参考音频常见的长度是 3 秒左右,这段前缀里的音色,会被续写的部分自然带下去,不需要专门指定
- VALL-E 论文 2023 年发布时自己承认三处没解决的问题,其中一条和模型算得好不好没关系——官方从未放出可下载的权重
第18章把每一帧语音换成了 8 个离散编号,这正是 GPT 那一套机制要的输入形状:一串来自固定词表的整数。如果语音已经变成了一串整数,生成语音这件事,会不会就是生成文字时同一个任务——给定前面的 token,预测下一个 token?
学完这一章你应该能做到
- 说清 VALL-E 怎么把语音合成变成条件语言建模:文本和一小段参考音频当前缀,要生成的声学 token 当续写目标
- 区分 AR 和 NAR 在这套系统里各自只干一件事:AR 只生成第一层、靠一个结束符给出可变长度;NAR 被连续调用 7 次补完剩下的层
- 说清零样本克隆不是靠微调出一个新模型,是靠把参考音频编码成前缀之后做上下文学习
- 认清 VALL-E 留下的三个没解决的问题,看后续工作各自挑了哪一个当靶子,包括一条和第17章同源的旧伤
这一层要把上一层给出的离散声学 token,接上一个和文字语言模型同一套结构的解码器——不训练新的对齐机制,不训练新的时长预测器,直接把语音生成变成「下一个 token 是什么」这个语言模型已经会回答的问题。这是第17、18两级分别解决对齐和离散化之后,第一次把语音生成整个交给一个原本为文字设计的机制。
19.1 语音生成,能不能就是续写下一个 token?
第18章已经把语音变成了一串离散 token。如果想用一个和 GPT 结构相同的解码器-only Transformer 来生成语音,最直接的办法是什么,这套办法需要把什么东西喂给模型?
答完再看
第二个选项是 VALL-E 的做法。文本先转成音素序列,一小段参考语音(比如3秒)编码成第18章那种声学 token,两者拼接成前缀;模型要做的事和文字语言模型完全一样——给定前面的 token,预测下一个 token 的概率分布,一步步续写下去,直到生成完整段语音对应的 token 序列。第一个选项低估了两种模态的差异:文本词表和语音词表的统计规律完全不同,直接混在一张表里训练不是不可以尝试,但 VALL-E 实际的做法是保留文本和语音各自的处理方式,只在「都是离散 token、都能被同一个 Transformer 续写」这一点上共享机制。第三个选项过于悲观,语言模型的续写机制恰恰是这一章的核心论点:只要东西是离散 token,续写机制就能生搬过来。
把这套重新表述写得更明确一点:给定文本内容 x 和一段参考语音的声学 token C̃,目标是生成新的声学 token 序列 C,使得 C 对应的语音读出来是 x 的内容,音色和 C̃ 一致。这和文字语言模型的目标「给定上文,生成符合语境的下文」在形式上是同一件事:都是在一个离散序列上,给定条件(文字语言模型的条件是上文,VALL-E 的条件是文本加参考音色),预测下一个 token 的分布。VALL-E 论文把这个框架叫神经编解码器语言建模(neural codec language modeling)。
文字语言模型:续写文字
词表是文字 token(或子词),条件是已经写出来的上文,目标是续写出接下来最合理的文字,训练数据是海量的纯文本。
VALL-E:续写声学 token
词表是第18章那种残差量化编号,条件是文本内容加一段参考音色的声学 token,目标是续写出剩下的声学 token,训练数据是海量带文本标注的录音(LibriLight,6万小时)。
差别在:两者的词表内容和训练数据完全不同,但「用一个 Transformer 解码器,在离散词表上一步步续写」这套机制本身是同一套,没有为语音重新设计。
VALL-E 生成一段语音时,模型的输入前缀里包含哪两类信息?输出是什么形式?
变式:如果只给文本、不给参考语音的声学 token 当前缀,VALL-E 还能不能生成语音?会缺少什么信息?
19.2 一个模型只管第一层,为什么剩下七层交给另一个?
第18章说过,残差量化的 8 层 token 里,第一层扛的是内容和音色的骨架,后面几层补的是逐层递减的细节。如果要用语言模型续写这 8 层 token,你觉得应该用同一个自回归模型把 8 层从头到尾按顺序续写完,还是应该拆成不同的模型分别处理?
答完再看
第二个选项是 VALL-E 的实际设计:AR 模型只负责第一层,NAR 模型负责第 2 到第 8 层。第一个选项忽略了一件事——8 层如果全部自回归,语音每多一层就要多一整轮串行解码,生成一段 10 秒的语音要付出 8 倍的串行步数,代价很高;第三个选项忽略了长度这件事从哪里来:语音总共有多少帧、由谁来决定,如果全部并行,谁来负责「这段话到底该有多长」这个问题就没有答案了。
先看 AR 为什么必须留着。语音的总长度(一共有多少帧)在生成之前是不知道的——同一句文本,读快一点或读慢一点,对应的帧数不一样,第17章处理这个问题靠的是一个显式的时长预测器,直接回归出每个音素占几帧。VALL-E 不训练时长预测器,改成让 AR 模型自己决定:每一步预测下一个 token,直到预测出一个特殊的结束符(EOS)为止,序列有多长完全由「什么时候采样到 EOS」决定,这是语言模型生成变长文本时用的同一套机制。
第17章的 FastSpeech 2 用显式时长预测器决定每个音素占几帧,VITS 用 MAS 在训练时搜出对齐、推理时靠随机时长预测器给出时长。VALL-E 的 AR 模型不训练任何时长预测器,靠什么机制让生成的语音有一个确定的长度?这个机制和第17章的两种方案比,谁的「长度不对」风险更结构化,谁更依赖运气?
变式:如果给 AR 模型的采样过程加一个「最大长度」上限,强制超过这个长度就截断,这能不能解决「长度不对」的问题?会带来什么新问题?
再看 NAR 为什么能不自回归。第 2 到第 8 层不需要再决定「这段话多长」——第一层的 AR 输出已经把总帧数定死了,后面 7 层每一层都是在这个已经确定的帧数上,给每一帧补一个更精细的编号,不存在「这一层要生成到哪里为止」这个悬而未决的问题。VALL-E 论文的说法是:「for the consecutive stages, as the number of output slots follows the sequence length of the first stage, NAR can reduce the time complexity from O(T) to O(1)」——对后续几个阶段来说,输出槽位的数量跟随第一阶段的序列长度,NAR 能把时间复杂度从 O(T) 降到 O(1):不再需要像 AR 那样一步接一步地串行解码,8 层里的每一层都可以一次前向直接predict出全部帧的编号。
自己推一遍:AR+NAR 这样分工,比 8 层全部自回归省了多少步
如果 8 层全部用自回归生成——每一层都像文字语言模型那样一个 token 接一个 token地串行预测,要生成一段 T 帧的语音,光是其中一层就要走多少步?
想好了再看
T 步。一帧对应一个 token,串行生成 T 个 token 就要走 T 步,中间每一步都要等前一步的结果出来才能算下一步。
8 层全部这样自回归,总共要走多少步?
想好了再看
8×T 步。8 层,每层都要串行走 T 步,层与层之间如果也按顺序来,总步数是 8 层的步数直接相加。
换成 VALL-E 实际的分工——第一层 AR(T 步),第 2 到第 8 层每层用 NAR 只需要一次前向(不随 T 增长,记成 O(1))——总共要走多少「轮」?
想好了再看
T+7 轮:AR 那一层走 T 步,NAR 的 7 层每层只需要 1 次前向,7 层就是 7 次,两部分加起来是 T+7。
当 T 比较大的时候(比如 10 秒语音,T=500),两种方案的步数比是多少?这说明了什么?
想好了再看
8×500=4000 步,对比 500+7=507 轮,比值约等于 7.9 倍。T 越大,这个比值越接近 8——因为分母里的「+7」在 T 很大时几乎可以忽略,NAR 那部分几乎是白送的常数开销,真正的加速来自「把 7 层的串行开销,换成了 7 次和 T 无关的并行开销」,这正是 O(T) 降到 O(1) 这句话在具体数字上的样子。
如果把 VALL-E 的设计反过来——用 AR 模型生成第 8 层(细节层),用 NAR 模型一次性预测第 1 层(内容/音色层),会出什么问题?用第18章「越靠前的层能补的信息量天然越大」这条结论来解释。
变式:如果编解码器不是残差量化,而是8层信息量均匀分布、互不依赖的并行结构,AR/NAR 这种分工还有必要吗?
19.3 只靠三秒钟的录音,怎么克隆一个从没见过的声音?
VALL-E 号称能「零样本」克隆一个训练时从没出现过的说话人的声音,只需要那个人几秒钟的录音。你觉得这几秒钟的录音,是被用来现场微调(哪怕只更新很少几步)出一个专属于这个说话人的小模型,还是模型参数完全不变、只是把这几秒钟的信息塞进了输入?
答完再看
第二个选项是 VALL-E 做零样本克隆的真实机制,术语叫上下文学习(in-context learning):模型在 60,000 小时的 LibriLight 数据上训练时,见过海量「前缀的音色延续到后面」这种模式,参数里已经编码了这条统计规律。推理时给一个全新的说话人,模型没有专门针对这个人训练过,但只要把这个人的录音编码成前缀,模型会把「前缀音色和续写音色一致」这条规律套用到这个从没见过的新说话人身上。第一个选项是很多人对「克隆」这个词的第一反应,但现场微调的代价(几步梯度更新也需要反向传播和优化器状态)远高于「直接当前缀输入」,VALL-E 论文里没有现场微调这一步。
把这套机制的输入输出对应关系写清楚:参考音频先经过第18章那套编解码器,变成一串声学 token;参考音频对应的文本也转成音素;这两者加上「要读的新文本」的音素,一起构成 AR 模型的前缀;AR 模型从这个前缀之后开始续写,续写出来的第一层 token 音色上会追随前缀里参考音频的音色——这不是被显式指定的,是模型在训练数据里学到的条件延续关系自然发生的结果。VALL-E 的训练数据是 LibriLight,60,000 小时,比之前同类 TTS 系统的训练数据规模大了几百倍,论文里用来举例的参考音频长度是「a 3-second enrolled recording」,3 秒钟的录音。
| 系统 | 需要的参考音频 |
|---|---|
| VALL-E | 3 秒(零样本) |
| Qwen3-TTS | 3 秒(零样本) |
| XTTS-v2 | 6 秒(零样本) |
| GPT-SoVITS | 5 秒(零样本)/ 1 分钟(少样本微调) |
GPT-SoVITS 提供两种模式:零样本(5秒参考音频)和少样本微调(1分钟录音,专门训练)。如果某个应用场景需要克隆的音色效果尽量好,且可以接受等几分钟训练时间,你会选哪种模式?如果场景要求几秒钟内就要出结果,完全不能等训练,又该选哪种?两者本质的区别是什么?
变式:如果用同一个说话人的零样本克隆反复生成很多句话,每一句都要重新编码一次参考音频当前缀吗,还是编码一次能重复使用?
答辩:如果我是审稿人
「模型从没见过这个说话人」这句话是不是有点夸大?LibriLight 有 60,000 小时的录音,说话人数量肯定是几千甚至更多,训练数据的说话人分布已经很广了,测试时用的新说话人真的和训练分布毫无关系吗,还是只是「没有被逐字逐句训练过」,本质上仍然落在训练数据覆盖的音色范围之内?
参考防守(先自己组织语言再看)
这个区分是对的,需要说清楚:「零样本」准确的意思是这个说话人的录音没有出现在训练集里,不是说模型在语音特征的抽象空间里对这种音色一无所知。模型学到的不是某个具体说话人的音色,是「一段语音里,音色这个属性在时间上是连贯的、前缀和续写部分应该匹配」这条抽象规律,外加一个能覆盖相当广的音色变化范围的表示空间——这个空间是从 60,000 小时、大量不同说话人的数据里学出来的。如果测试时的新说话人音色和训练分布里的音色差异极大(比如带有训练数据里几乎没有的口音、发声方式),零样本克隆的效果大概率会下降,这一点论文没有给出一个能提前预判「差多远会失效」的界限,只报告了在标准测试集上的效果。
19.4 VALL-E 留下的三个没解决的问题是什么?
VALL-E 论文自己在结论部分列出了几处没解决的问题,其中关于生成质量的一条原文是:「Synthesis robustness: We observe that some words may be unclear, missed, or duplicated in speech synthesis. It is mainly because the phoneme-to-acoustic language part is an autoregressive model, in which disordered attention alignments exist and no constraints to solving the issue」——合成鲁棒性:有些词在合成语音里会不清楚、丢失或者重复,主要原因是从音素到声学 token 这部分是一个自回归模型,注意力对齐存在紊乱,也没有约束能解决这个问题。
第17章讲的漏字重复,根源是自由注意力没有机制保证权重沿时间单调前进。VALL-E 的漏字重复发生在一个完全不同的机制里——自回归 token 采样,不是逐帧注意力对齐。这两种漏字重复,是同一堵墙,还是两堵不同的墙?
变式:如果给 VALL-E 的 AR 采样也加一条「单调不跳过」式的硬约束,直接套用第17章 MAS 的思路,你觉得可行吗?困难会出在哪一步?
VALL-E 论文自己承认的第二个问题和时长有关:语音的时长完全由 AR 采样到 EOS 的时刻决定,没有任何外部信号能强制指定「这段语音必须正好是几秒」。这在很多场景下不是问题,但视频配音这类场景需要生成的语音和画面里角色的口型、动作精确对上时长,VALL-E 没有提供任何接口能做到这一点——19.2 节说过,这正是 AR 不训练时长预测器换来的代价,用长度的灵活性换掉了长度的可控性,鱼与熊掌在这里没能兼得。第三个问题不是技术上的:微软没有开源 VALL-E 的官方权重,论文公开了架构和训练细节,但没有发布任何可以直接下载运行的模型文件,后续想用 VALL-E 的人只能自己按论文描述重新训练。
答辩:如果我是审稿人
这份「三个硬伤」的清单,是不是把两个本质相同的问题拆成了两条?漏字重复和时长不可控,会不会都只是同一个根源——AR 每一步都在做一次不受约束的开放式采样——在两个不同指标上的表现,而不是两个独立的问题?
参考防守(先自己组织语言再看)
这个质疑有一部分是对的:两者的根源确实都能追溯到「AR 每一步的采样没有结构约束」这一件事,19.2 节推导过,长度完全是「什么时候采样到 EOS」的副产品,和漏字重复共享同一个机制上的弱点。但把它们看成完全同一个问题,会掩盖一个实际差别:漏字重复是「内容错了」(该说的没说、多说了),时长不可控是「内容对,但整体节奏没法被外部指定」——即使解决了漏字重复(内容完全正确),时长依然可以是任意的,因为没有任何机制能让使用者说「我要求这段话正好是 3.2 秒」。两个问题共享根源,但落在两个不同的可观测指标上,后续工作也确实是分头应对的:MaskGCT 用总时长模块单独解决了后者,却不能完全避免前者;解决其中一个不会自动解决另一个,所以分开列成两条硬伤是合理的,不是重复计数。
对你而言未知「没有约束能解决」这句话,是不是还成立
VALL-E 论文写「no constraints to solving the issue」是 2023 年初的判断。第17章的 MAS 证明了「把结构性约束直接写进候选集合」这条路对显式对齐任务是可行的,只是还没有人把类似的思路成功搬到自回归 token 采样这个开放式生成过程里——至少论文原文里没有提到任何尝试。这中间的困难具体卡在哪一步:是「候选集合太大枚举不了」,还是「自回归生成的每一步本来就没有一个天然的、可以拿来约束的候选边界」,材料手上没有给出答案,需要去查 VALL-E 之后的后续工作有没有专门针对这一点提出结构化的解法。
先做这一步:去查后面几节要讲的 MaskGCT、F5-TTS 各自的论文,看它们摆脱自回归采样之后,是不是真的不再出现「漏字重复」这一类问题,还是只是换了一种新的失败模式;如果找到了专门分析「自回归语音生成的漏字重复能不能被结构化约束」的论文,再回来对照这里的猜测。
19.5 不自回归,还会不会漏字重复?
上一节的结论是,VALL-E 的漏字重复根源在自回归采样本身没有结构约束。如果干脆不用自回归——每一步都不是「看着前面生成的内容续写下一个」,而是想办法一次性、并行地把整段语音的 token 都决定出来,你觉得这样做能不能绕开漏字重复这个问题?
答完再看
第二个选项是 MaskGCT 的思路,借用的是图像生成里 MaskGIT 提出的掩码预测(mask-and-predict)范式:先把要生成的 token 序列大部分挡住,模型基于剩下没被挡住的部分和条件信息,预测被挡住位置该是什么,把预测置信度最高的一批位置填上真实值,剩下没填的位置留到下一轮继续预测,如此反复几轮直到全部填满。第一个选项把「不自回归」和「不会犯错」画了等号,但并行生成一样可能漏字——如果模型在第一轮就把某个该出现的内容判断成低置信度、迟迟没有被填上,反复多轮也可能收敛到一个不完整的结果,只是失败的具体形式变了。第三个选项低估了掩码预测和自回归的实质区别:自回归每一步只能看到「已经生成」的前缀,掩码预测每一轮都能看到全部位置的当前状态(包括还没确定的),条件信息更充分。
MaskGCT 把这套掩码预测用在两个阶段:文本到语义(T2S)和语义到声学(S2A)。两个阶段都不需要文本和语音之间逐音素的对齐监督——这是和第17章、VALL-E 都不一样的地方:FastSpeech 2 靠 MFA 给出音素级时长,VITS 靠 MAS 搜出对齐,VALL-E 的 AR 隐式地一步步决定长度,MaskGCT 只预测一个总时长(这段话总共该有多少帧),不再关心每个音素各占几帧,总时长本身由一个基于流匹配的独立模块给出,总长度确定之后,掩码预测在这个长度上一轮接一轮地把内容补全。
MaskGCT 只控制「总时长」,不像 FastSpeech 2 那样控制「每个音素占几帧」。这意味着 MaskGCT 没办法回答哪类问题?举一个具体场景。
变式:视频配音场景需要某个词精确对上角色张嘴的那一帧,MaskGCT 的总时长控制能不能满足这个需求?和第19.4节VALL-E的配音问题相比,谁的问题更容易解决?
置信度多轮解码的效果和轮数直接相关。MaskGCT 论文报告,T2S 阶段从 5 步增加到 25 步,中文测试集 WER 从 10.19 降到 2.507,英文测试集从 8.096 降到 2.346,25 步之后基本不再下降,论文把 25 步当作实用的最优选择。T2S 有两个规模,Base 315M、Large 695M,S2A 是 353M,训练数据是 Emilia,100,000 小时。MaskGCT 的代码是 MIT 许可,但权重是 CC-BY-NC-4.0,非商用。
19.6 把时长预测器、文本编码器都拆掉,还剩下什么?
MaskGCT 已经放弃了音素级时长,但还保留着第18章那种离散声学 token、还有专门的两阶段结构。如果更进一步,把时长预测器、专门的文本编码器、音素级对齐这些统统拿掉,甚至连第18章的离散 token 也不用,直接回到连续的 Mel 频谱上做生成,你觉得这还能不能把语音做出来?
答完再看
第二个选项是 F5-TTS 的做法。它不用第18章的离散 token,直接在连续的 100 维 log-Mel 频谱上工作,把「补全」当训练任务:一段完整语音,随机挡住一部分(论文里是挡住 70% 到 100% 这个比例区间),模型看着没被挡住的部分加上完整文本,用条件流匹配损失学着把挡住的部分填出来——这正是第8章讲过的流匹配训练目标,用在了语音上。第一个选项低估了注意力机制隐式学习对应关系的能力;第三个选项也站不住,F5-TTS 报告的效果和同期需要显式时长模块的系统相比并不逊色。
F5-TTS 的骨干网络是 DiT(第7章「扔掉 UNet」讲过的同一类结构)加 ConvNeXt V2,Base 版本 335.8M 参数(22 层 DiT + 4 层 ConvNeXt V2)。文本不经过专门的音素对齐工具,直接和带缺口的语音表示拼在一起喂给模型,缺口该填多长——也就是这段新生成的语音大概多长——只用一个朴素的启发式给出:按参考语音的字符数和目标文本的字符数的比例,直接估算一个总帧数,不训练任何回归模型。论文原文列出被它一起丢掉的模块:不需要音素级时长、不需要文本编码器、不需要音素对齐。
传统管线:VALL-E、MaskGCT
语音先变成第18章的离散 token,用语言模型或者掩码预测在 token 序列上生成,时长要么靠 AR 采样的副产品给出,要么靠一个专门训练的总时长模块给出。
F5-TTS:回到连续表示
语音留在连续的 Mel 频谱上,不经过离散化,用第8章那套流匹配直接学「把缺口补上」这个任务,时长靠字符数比例粗略估算,不训练任何专门模块。
差别在:F5-TTS 没有沿着「离散化之后用语言模型」这条路线继续走,是绕回了连续表示加流匹配这条路线,说明本章前几节搭起来的离散 token 语言建模,不是语音生成唯一可行的方向。
第8章讲过,把弯路拉直之后,一步或几步采样的效果好不好,取决于走的是不是直线路径。F5-TTS 推理时用的 Sway Sampling,做的是把原本均匀分布的采样步长,改成向 0 或 1 偏斜的非均匀分布,公式是 f_sway(u;s)=u+s·(cos(πu/2)−1+u)。这个技巧动的是流匹配的哪个环节:训练目标、速度场本身,还是别的什么?它和第8章的直线路径是不是同一件事?
变式:如果把 s 设成 0,Sway Sampling 的公式会变成什么?这时候它和普通的均匀步长采样还有区别吗?
F5-TTS 报告的 RTF(合成一秒语音需要几秒)是 0.15,训练数据大约 100,000 小时的 Emilia。它的代码是 MIT 许可,权重是 CC-BY-NC,非商用。
本章讲的三条路线,还有一种常见的组合方式没有单独展开:CosyVoice 2 用 Qwen2.5-0.5B 做语言模型骨干,先把文本变成语义 token(用 FSQ 而不是第18章的普通 VQ,提升码本利用率,token 率 25Hz),这一步和 VALL-E 的思路是同一类;再用一个分块因果的流匹配模块,把语义 token 变成可以直接合成的声学表示,这一步和 F5-TTS 的思路是同一类。一个系统里同时用上了「语言模型生成离散 token」和「流匹配生成连续表示」两套机制,不是二选一。CosyVoice 2 的代码和模型都是 Apache 2.0 许可,可以商用,这一点和 VALL-E 没有开源权重、F5-TTS/MaskGCT 权重非商用都不一样。
答辩:如果我是审稿人
几乎所有语音生成系统,最后都要靠某种连续的生成机制把中间表示变成能播放的波形(哪怕只是一个普通声码器)。照这个标准,是不是随便一个系统都能说自己「结合了流匹配」?把 CosyVoice 2 单独挑出来当「混合方案」的代表,是不是没有太大信息量?
参考防守(先自己组织语言再看)
这个提醒是对的,需要划一条界线:普通声码器(比如 HiFi-GAN)只是把已经基本定型的声学表示转成波形,这一步不承担「决定说什么内容、音色是否连贯」这类核心生成任务;CosyVoice 2 的流匹配模块要做的事情更重——它接手的是信息量很稀疏的语义 token(25Hz,携带的是内容和大致韵律),要在这之上生成出携带完整声学细节的表示,这一步承担的是实质性的内容生成工作,不只是「把已经决定好的东西转换一下格式」。用「流匹配是不是在做实质性的生成工作,还是只在做最后一步波形合成」这条线,能把「真正的混合架构」和「随便一个系统都在做的常规声码化」区分开。
| AR token 语言模型(VALL-E) | 多轮掩码预测(MaskGCT) | 连续流匹配(F5-TTS) | |
|---|---|---|---|
| 表示 | 离散声学 token | 离散声学 token | 连续 Mel 频谱 |
| 时长怎么定 | AR 采样到 EOS 的副产品 | 独立的总时长模块 | 字符数比例的启发式估算 |
| 主要风险 | 采样漏字、重复、循环 | 多轮收敛不完整时的残留错误 | 推理步数(NFE)少时质量下降 |
| 并行度 | 逐 token 串行 | 逐轮并行,轮数远少于 token 数 | 逐 ODE 步并行(每步内部并行) |
VALL-E(2023年初)、MaskGCT 和 F5-TTS(都在2024年下半年)、CosyVoice 2(2024年底)在时间上有先后,但表2里的三条路线到现在都还有人用,没有哪一条把另外两条完全淘汰掉。结合三条路线各自的「主要风险」,说说为什么会是这个局面,而不是收敛到一个最优解。
变式:如果场景要求极低延迟的流式输出(边生成边播放,不能等整段生成完),三条路线里哪一条天然更合适,为什么?
读的时候要小心
关于本章数字的来路
VALL-E 的三个硬伤、MaskGCT 的步数-WER 曲线、F5-TTS 的 RTF、表1里几个系统需要的参考音频时长,都来自各论文作者自己报告的实验和描述,没有第三方复现过,本章按 B 档处理。19.2、19.4、19.6 节几处「这是为什么」式的推理(比如 AR/NAR 分工为什么绑定第18章的分层语义),是本站在论文给出的动机之上做的解释性推导,不是论文原文逐字给出的论证,读的时候请把论文引号里的原文和本站的解释分开看。
这一层要加什么:自回归声学 token 生成循环
为什么现在才加它:前十八级从没有一层的生成过程是「给定前缀,一步步续写离散 token,直到采样出结束符为止」。这一层要把这套循环实现出来,并亲手验证它在两种温度下的两种截然不同的行为。
难点:temperature == 0 这个分支必须单独用 argmax,不能图省事直接把 temperature 传进 softmax 再除——除以一个极小值在数值上会让 probs 变成几乎的 one-hot,看起来效果差不多,但浮点误差可能导致两次运行选中不同的 token,破坏了「temp=0 必须完全可复现」这条性质。另一个难点是 rng 必须由传入的 seed 初始化,不能用全局随机状态——否则同一个 seed 跑两次会因为调用顺序或者其他代码路径污染了全局状态,而得到不一样的结果,看起来像是「seed 没生效」,其实是随机源没有被真正隔离。
自己验:固定同一个 seed、同一段 prefix_tokens,temperature=0 时连续跑两次,两次返回的 token 序列必须逐一相同,因为贪心解码本身没有随机性;把 temperature 调到 1.2,同样的前缀反复跑几次,应该会观察到某一段连续的 token 子序列被重复出现两次以上——温度调高之后模型不再总是选最大概率的选项,一旦某一步采样到的 token 恰好把上下文带回了和几步之前相似的状态,后面的采样就可能沿着相似的条件反复兜圈子。这里要说清一层区别:VALL-E 论文原话报告的是集束搜索(beam search)会让模型陷入死循环,论文正是为了避免这个问题才改用采样解码;这里用调高 temperature 复现的是同一类「自回归采样没有结构约束、容易被带入自我重复」的问题在一个简化模型上的表现,不等于断言真实 VALL-E 在 temperature=1.2 时一定会循环——具体在哪个温度、哪种解码策略下出现循环,取决于模型本身,这道 bs-check 练的是「体会这一类不稳定性从哪里来」,不是复刻论文的精确数字。
留给你的问题
这一章把语音生成交给了语言模型机制,换来了零样本克隆,但也把第17章「没有结构约束就会漏字重复」的墙原样带了过来,只是换了一副面孔。MaskGCT 用多轮掩码预测绕开了自回归采样,F5-TTS 放弃离散 token 回到连续表示——会不会存在一种方案,既保留 AR「不用提前知道时长」的灵活性,又能像 MAS 一样从结构上排除漏字重复,而不是只能靠换一种全新范式来绕开?
不看材料,写下这一章的三堵墙
第一堵:AR 只负责第一层、NAR 负责剩下七层,这个分工是第18章「前面层信息量大、后面层信息量小」这条结构性质决定的,反过来分配会两头吃亏。第二堵:零样本克隆靠上下文学习而不是微调,效果依赖训练数据音色分布覆盖得够不够广,一个和训练分布差异极大的新音色,材料给不出「差多远会失效」的界限。第三堵:VALL-E 的自回归采样把第17章漏字重复的墙原样带了过来,只是从帧级对齐换成了 token 级采样,论文自己承认没有约束能解决,后续工作各自绕开了它,但没有一个从根子上解决了它。
第20章 实时
开篇故事第 20 集 · 把 M 砍到只剩一个,声音先碎了这模型也太真实了
测试面板上那根首包延迟的数字,这三天一直在四百毫秒附近来回晃,怎么都压不下去——RTF 那一栏却一直是绿的,显示这套语音链路跑得比实时还快好几倍。沈砚盯着这两个数字对不上号的样子,手指在触控板上敲了又停,停了又敲。
这是回声复现的第一百七十天,语音这条线接上流式输出才十来天,客户那边已经催过两次,想看一次真正的语音对话演示。沈砚早前算过一笔账:首包延迟等于三道工序各自的耗时,乘上每次攒够才交付的 token 数 M,这三项加起来摆在公式里,账面上一清二楚。
他想的是,既然这三项都被同一个 M 乘着,M 砍得越小,三项跟着等比例缩小,首包延迟没道理不跟着往下掉。他把 M 从现在这一档直接砍到只剩一个 token,一次交付一个,想着这样总该能把那四百毫秒的死结解开。
M 是不是越小,首包延迟就应该跟着一路压到底
1L沈砚我把 M 直接砍到只剩一个 token,账面上三项耗时应该跟着缩到最小,可这一试出来的东西整句话都糊在一起,问题到底出在哪一步。
2L炼丹废人我印象里这套流程从来不是想砍多小就能砍多小的,你先说说 M 砍到这个地步,后面渲染那一步手里还剩多少东西能看。
3L显存刺客账我倒觉得没问题,M 越小单次算力占用越低,显卡那边压力反而更轻,这笔账不该是稳赚不赔的吗。
4L复现之鬼不对,压力轻不等于结果对,你们先把这段糊掉的音频拿出来听一遍,比空口算这本账有用得多。
改过的参数刚提交上去,耳机里先是一声很轻的“咔”,紧跟着后半句话像是被人捏住了喉咙,含混地粘在一起,听不出说的是哪几个字。复现之鬼这句话把大家的注意力拉回那段音频本身,系统面板紧跟着弹出一行提示。
系统提示M=1 时,flow matching 渲染窗口内可用帧数不足,声学细节出现明显缺口,本次输出质量判定未通过;M 恢复至此前档位后,判定通过。
M 从来不是只由首包延迟这一个目标定下来的自由参数——它同时被“flow matching 至少要留多少上下文才不崩”这条底线摁住,砍得太狠,账面数字好看,声音先垮。“三项乘出来的账只管交付快不快,管不了这一口气里有没有东西可画。”靳工把那份判定截图存进频道,“往后再想着砍 M,先问一句,留给流匹配的那点窗口,够不够它认出该画什么。”
沈砚把 M 调回原来那一档,四百毫秒那道坎依旧纹丝未动,只是这回他至少弄清楚了,这道坎不能拿“多攒一点是浪费”这套账去硬撬。今晚这最后一段测试时间,原本是留给明天客户演示前最后一次彩排的,现在只剩下把音频重新跑一遍。工作群里,客户那条问“明天能不能准时开始”的消息,还没有人回。
系统结算 · 本章考点
- RTF 和首包延迟是两件独立的事:一个 RTF=0.2 的系统如果等全部内容生成完才交付,首包延迟能和整段合成时间一样长
- token 率决定单步的计算预算:75Hz 时每个 token 只留 13.3 毫秒,降到 7.5Hz 宽裕到 133.3 毫秒,整整十倍的余地
- CosyVoice 2 的首包延迟分解式是三项相加,M 每变大一倍数字跟着精确翻倍:M=1 时 23 毫秒,M=4 时 92 毫秒
- M 不是只由首包延迟决定的自由参数:flow matching 需要一段最小上下文才能工作,M 压到 1 时这段上下文几乎不存在,质量会先崩,这条代价不会显示在公式里
- 2026 年实测水位:Qwen3-TTS 0.6B 首包 97 毫秒(语言模型 93 毫秒加解码 4 毫秒),1.7B 首包 101 毫秒
- VibeVoice 靠 7.5Hz 把 90 分钟音频压到 40,500 个 token,装进 64K 窗口还剩两万多个位置;换成 75Hz,同样 90 分钟要 405,000 个 token,超出窗口六倍以上
对着语音助手说完一句话,等它开口回答,这段等待有多长,是一件事。它说完整句回答一共花了多久,是另一件事。前十九章讲的几乎全是第二件事——合成质量、合成速度、一段音频要花多少计算。这一章要讲第一件事,而且要说清楚,为什么这两件事表面上都叫「快不快」,实际上是两道完全不同的题,逼出来的解法也完全不一样。
学完这一章你应该能做到
- 从 RTF 的定义推出 token 率和单步耗时之间的预算关系,并说清为什么降 token 率能换来更大的模型
- 逐符号读懂 CosyVoice 2 的首包延迟分解式,说清它和 RTF 回答的是两个不同的问题
- 说出流式合成撞的三处因果性各自卡在哪一层,以及不同系统各自怎么绕
- 说清 Kyutai 的延迟流建模和分块流式,是两种完全不同的思路
- 解释极低帧率为什么是塞进长音频的必要条件,而不是可有可无的优化
这一层要给第19级那个能连续吐 token 的自回归循环,加上一条「边吐边播」的路:不再等全部 token 生成完才开始合成波形,而是攒够一小段就先渲染、先播放。前19级关心的都是「生成得准不准、快不快」,没有一级问过「用户第一次听到声音之前要等多久」这个完全独立的问题。
20.1 首包延迟和合成总耗时,是不是同一件事
一个语音合成系统,合成一句十秒钟的回答,从头到尾一共花了两秒钟——这十秒钟的音频,用了两秒钟就造完了,RTF(合成一秒波形需要几秒,第19章提过这个定义)是 0.2,远快于实时。用户对着它说完话,从这个系统开始处理,到第一声「嗯」传到耳朵里,也是差不多两秒钟吗?
答完再看
第一个选项说中了要害。RTF 只回答「造一秒钟音频要花多少计算时间」这一件事,完全没有说这些计算是怎么分批交付的——一次性生成整段十秒音频再整体返回,和每生成一小段就立刻吐出来,两者的 RTF 可以完全相同,用户等待的时间却天差地别。第三个选项提到的麦克风和网络确实是真实系统里的延迟来源,但这一章要单独挑出来讲的,是合成系统自己这一侧能控制、也必须设计的那部分延迟,先把这部分谈透,其余的传输开销是加法题,不改变这一章的结论。
这两件事需要两个名字。一段音频从请求发出到完全生成完毕所需要的总时间,除以这段音频本身的时长,就是 RTF——它回答的是「一直造下去,追不追得上」。而用户从提出请求到听见第一个声音之间的等待,叫首包延迟(first-package latency),它回答的是「第一口气多久喷出来」。一个 RTF 远小于 1(跑得比实时快很多)的系统,如果坚持等全部内容生成完才交付,首包延迟可以和整段合成时间一样长;反过来,一个系统哪怕 RTF 略大于 1(长期跑,会越攒越慢),只要它每生成一小段就立刻交付,首包延迟也可以做得很短。这两个数字互相独立,一套系统必须分别优化。
整段返回:RTF 好看,首包却等于全程
合成完整十秒音频只用两秒钟计算,RTF=0.2,账面上很快。但如果实现是等这两秒钟计算全部跑完、拿到完整波形才返回,用户从开口到听见第一个音,等的就是这整整两秒。
分段返回:每一小段生成完就交付
同样两秒钟总计算时间,如果拆成十份、每 0.2 秒交付一小段音频,用户听到第一声「嗯」的等待,理论上可以压到这 0.2 秒附近,而不是两秒。总计算量和 RTF 完全没变,变的只是「怎么把这些计算切成小块、按顺序交出去」。
差别在:RTF 衡量的是一条流水线长期能不能跟上实时播放的速度,首包延迟衡量的是这条流水线第一次交货要等多久——省流水线总时间的办法,和缩短第一次交货时间的办法,不是同一套技巧。
语音助手场景里,首包延迟才是用户体感的主角。整段回答花多久造完,用户往往感觉不到——因为音频一边播放一边继续生成,播放的速度天然被限制在实时(一秒钟的音频总要花一秒钟播完),只要生成速度不比播放速度慢,用户听到的就是连续不断的声音。但开口前那一段死寂,是纯粹的、能被秒表量出来的等待,没有任何东西在这段时间里掩护它。这一章要拆开的,正是这段死寂里到底藏着哪些必须付出的计算。
系统A:RTF=0.5,一次性返回整段音频。系统B:RTF=1.2(长期跑会略微跟不上实时),但每生成 0.3 秒音频就立刻交付一次。合成一段 20 秒的回答,谁的首包延迟更短?谁在合成到第 15 秒时更可能出现播放卡顿?
变式:如果系统B的RTF能优化到0.9(小于1),前面「播放到第15秒可能卡顿」这个结论还成立吗?
20.2 为什么 token 率才是这条线路真正的主角
第18、19章讲过,语音先被切成一串离散 token,语言模型一步吐一个 token。VALL-E 用的 EnCodec 跑在 75 Hz(每秒 75 个 token),VibeVoice 的 tokenizer 跑在 7.5 Hz(每秒 7.5 个 token),整整差了十倍。如果只从「实时」这一个目标出发,你觉得哪一档 token 率给模型算力留的余地更大?
答完再看
75 Hz 意味着每秒要吐 75 个 token,留给每个 token 的时间只有 13.3 毫秒;7.5 Hz 每秒只需要吐 7.5 个,留给每个 token 的时间宽裕到 133 毫秒,是前者的整整十倍。第一个选项把「吐得快」和「算得快」搞反了——token 率高,恰恰意味着模型必须在更短的时间里完成一次前向计算,这是压力更大,不是余地更大。
把这件事写成公式,答案就精确了。合成一秒钟音频,需要 token_rate 个 token;每个 token 从模型算出来到变成对应那一小段波形,需要一段真实的挂钟时间,记作 tstep。造这一秒钟音频总共花掉的挂钟时间,就是 token_rate 乘以 tstep;这个总时间除以「一秒钟」,正好就是 RTF 的定义。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| token_rate | tokenizer 每秒产出多少个 token,单位 Hz | 一秒钟音频要切成几份 |
| tstep | 模型从上一个 token 算到下一个 token,实际花掉的挂钟时间 | 每一份留给模型的算力预算 |
| 1/token_rate | token 率的倒数,单位是秒/token | 能守住实时这条线的单步预算上限 |
自己推一遍:从 RTF 的定义到「降 token 率是为了上大模型」这句话
要合成 10 秒钟音频,token 率是 25 Hz,一共需要生成多少个 token?如果模型跑得再快,每个 token 只花 10 毫秒,这 10 秒音频总共要花多少挂钟时间?
想好了再看
需要 250 个 token(25×10)。每个 token 10 毫秒,总共 2.5 秒。RTF=2.5/10=0.25,比实时快四倍。
模型不变,只把 token 率从 25 Hz 提到 75 Hz(三倍),其余条件不变,每个 token 还是 10 毫秒。这时候合成 10 秒音频的挂钟时间和 RTF 分别是多少?还守得住实时吗?
想好了再看
需要 750 个 token(75×10),总挂钟时间 7.5 秒,RTF=0.75,还是小于1,仍然守得住实时,只是余地比刚才小了很多——从「快四倍」掉到了「快1.33倍」。
现在反过来问:如果想把模型规模换大三倍,而更大的模型每算一个 token 需要的挂钟时间也差不多线性涨到 30 毫秒(三倍),token 率还是 75 Hz,这个更大的模型还能守住实时吗?
想好了再看
守不住。t_step=30ms,1/token_rate=1/75≈13.3ms,30ms 远超过 13.3ms 这条上限,RTF 会涨到 2.25——生成速度只有播放速度的不到一半,没法维持实时对话。
同样是把模型换大三倍(t_step 涨到 30ms),但这次把 token 率也一并降到 7.5 Hz,还守得住实时吗?这说明了什么?
想好了再看
守得住。1/token_rate=1/7.5≈133.3ms,30ms 远小于这条上限,RTF 只有 0.225,比实时快四倍还多,甚至比第一步(小模型、25Hz)的余地更大。这正是这一章要讲透的那句话:token 率不是一个只和「一秒钟切成几份」有关的旁支参数,它直接决定了模型能有多大——token 率每降一档,单步预算就宽松一档,同一块 GPU 上能塞下的模型规模跟着往上涨。降 token 率不是为了省显存,是为了能在实时这条硬约束下,换进一个更大、更慢、但通常也更聪明的模型。
75 Hz:每步只有 13.3 毫秒
模型必须在13.3毫秒内算完一步,这个预算连一个中等规模网络的一次前向都未必够用,逼着模型只能做得很小。
7.5 Hz:每步有 133.3 毫秒
同样的实时红线下,预算宽裕了十倍,一个大十倍、慢十倍的网络依然能守住实时——这十倍的余地,正是换更大模型的空间。
差别在:两条线用的是同一条实时红线(RTF≤1),红线本身没有变松,变的是分母(token率)把这条红线换算成的挂钟时间预算。
把几套真实系统的 token 率摆在同一张表里,这条规律就有了具体的刻度。
| 系统 | token 率 | RTF=1 时的单步预算 | 10 秒语音的 token 数 |
|---|---|---|---|
| VALL-E AR 层(EnCodec) | 75 Hz | 13.3 ms | 750 |
| CosyVoice 2 | 25 Hz | 40.0 ms | 250 |
| Qwen3-TTS-12Hz / Mimi 系 | 12.5 Hz | 80.0 ms | 125 |
| VibeVoice | 7.5 Hz | 133.3 ms | 75 |
按这张表的规律,CosyVoice 2(25Hz,骨干0.5B)到 VibeVoice(7.5Hz,骨干最大7B)之间,token率降低了多少倍?骨干规模涨了多少倍?两个倍数是不是精确相等?
变式:如果两个倍数应该精确相等,说明单步预算多出来的部分应该「全部」拿去换模型规模;实际涨幅更大,这说明多出来的预算除了换模型规模,还可能被花在了哪里?
这张表沿着时间轴摆开,正好是 2023 到 2026 年语音语言模型路线的缩影。VALL-E(2023年初)用 75 Hz 的 EnCodec,AR 层本身刻意做得不大——单步预算只有 13.3 毫秒,容不下太大的网络。CosyVoice 2(2024年底)把 token 率降到 25 Hz,单步预算宽松到 40 毫秒,用得起 Qwen2.5-0.5B 这个规模的骨干。VibeVoice(2025年)把 token 率进一步压到 7.5 Hz,单步预算放宽到 133 毫秒,骨干直接用到 Qwen2.5-7B——十倍于 CosyVoice 2 的规模,比值比刚才算出来的 14 倍还要更悬殊,说明这十年间模型规模上涨的速度,比单纯的 token 率下降更快。
token 率降低整整十倍,换来的正是能塞进同一条实时约束里的模型规模上涨超过十倍。
第18章给过残差量化的比特率公式 bitrate=Nq·log₂N·S,S 就是这一章说的 token 率。如果一个编解码器为了让每个 token 携带更多信息(提高 Nq 或 N),同时把 S 降到原来的一半,总比特率不变,这和这一章「降低 token 率换模型规模」的做法,本质上是不是同一类操作?
变式:如果一个系统同时想要 VibeVoice 那种 7.5 Hz 的超低 token 率,又想要 EnCodec 那种精细的音质,第18章的框架告诉你这笔账最终要花在哪里?
20.3 「多久才开口」这道题,拆开是哪几笔账
上一节的 RTF 回答的是「一直造下去追不追得上」,这一节要回到 20.1 节那道真正的题——用户开口之后,第一声回应之前,到底在等哪几件事。CosyVoice 2 把这道题写成了一个分解式。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| M | 第一个音频片段包含多少个 speech token——论文原话是「every M tokens」返回一次结果 | 第一口气要攒多大一口 |
| dlm | 语言模型生成一个 speech token 的计算时间 | 攒这一口气,脑子要转多久 |
| dfm | flow matching 模型把一个 speech token 对应的 Mel 帧渲染出来的计算时间 | 把这口气的内容画成频谱要多久 |
| dvoc | 声码器把一个 speech token 对应的波形合成出来的计算时间 | 把频谱吹成声音要多久 |
| LTTS | 纯 TTS 场景下的首包延迟 | 从拿到文本到第一声响起的总等待 |
首包这一刻:流水线是空的
三道工序谁都没法提前开工,语言模型吐完 M 个 token,flow matching 才能动手;flow matching 渲染完,声码器才能动手,只能顺序排队,首包延迟是三项之和,一项都不能少,这正是式11写成加号而不是取最大值的原因。
稳定运行后:流水线已经灌满
第 N 个 token 在被声码器处理的同时,语言模型可能已经在算第 N+1 个 token,三道工序同时在转,这时候瓶颈是三道工序里最慢的那一道,而不是三道加起来。
差别在:RTF 描述的是灌满之后的稳态吞吐,式11描述的是灌满之前那一次性的排队等待——同一条流水线,两种账算法,问的是两个不同时刻,都对。
假设 dlm=5ms、dfm=15ms、dvoc=3ms。M=1 和 M=4 时,LTTS 分别是多少?
变式:如果只有 dfm 这一项会随着 M 的增大而单位耗时变短(比如攒的token多了,flow matching一次渲染多帧反而更划算,单帧耗时从15ms降到10ms),M=4 时的 LTTS 会怎么变?还能不能直接用「4倍」估算?
voice chat(比如语音助手)这个场景,还要在前面再接一段。用户说的话要先经过一个文本对话模型(比如 GPT 级别的大语言模型)思考出回应,回应的前 N 个文本 token 生成出来之后,语音合成才能开始工作。
N 是首包所需要的文本 token 数,dllm 是对话模型吐一个文本 token 的时间。这条式子用的是「≤」不是「=」——论文给出的理由是,CosyVoice 2 自己的文本 tokenizer 会把多字词拆得更细,同样一段原始文字,CosyVoice 2 切出来的文本 token 数,通常比对话模型自己的 tokenizer 切出来的要多。也就是说,对话模型吐出 N 个它自己的 token 时,对应的原始文字,往往已经够 CosyVoice 2 切出更多个自己的 token 来用了,实际能提前开始合成的时间点,比按「N 个对话模型 token」估计的还要早一点,所以真实延迟只会更短,不会更长。
式12用的是「≤」,前提是 CosyVoice 2 自己的文本 tokenizer 切词比对话模型的 tokenizer 更细。如果反过来,对话模型的 tokenizer 切词比 CosyVoice 2 的更细,「实际延迟只会更短」这个结论还成立吗?
变式:这说明式12里的「≤」并非一个普适的数学性质,它依赖的是两个 tokenizer 相对粒度的一个经验事实,换一套模型组合后还能不能直接照搬?
这条式子直接照进了 20.1 节的开场问题:语音助手为什么开口要等——等的不只是语音合成这一侧的三道工序,前面还压着一整个对话模型思考出前 N 个字要花的时间。这也解释了为什么论文特别强调 SFT 流式模式「可以被语音到语音的多模态大模型直接借用,以获得极低延迟」——如果说话和思考共用同一个模型、同一次前向,就不再需要等一个独立的对话模型先吐完 N 个字,N·dllm 这一项可以被压缩到几乎为零。
LTTS:纯朗读一段现成的文本
文本已经在手,只欠语音合成自己那三道工序,首包延迟就是式11这三项之和,不多背任何东西。
LChat:先想清楚要说什么,再开口
文本本身还不存在,要先靠一个独立的对话模型想出开头这几个字,这段思考时间原封不动地叠加在语音合成的三道工序之前,用户等的是两段延迟的总和。
差别在:多出来的 N·dllm 这一项不是语音合成的账,是对话这个环节自己的账——唯一能省掉它的办法,是让说话和思考共用同一次前向,而不是优化语音合成本身。
Qwen3-TTS 的公开测试数据,给了这条抽象公式一个可以逐项对上的真实样本。它的 12.5 Hz tokenizer 被设计成纯左侧上下文流式解码器(pure left-context streaming decoder)——论文原话是它「能在所需 token 一到手就立刻吐出波形,不用等待未来上下文」。这意味着它把 flow matching 那道工序直接省掉了,token 一生成,编解码器就能就地解出波形,dfm 这一项在它的架构里等于零。0.6B 版本实测:语言模型吐够首包所需 token 花了 93 毫秒,编解码器解码只花了 4 毫秒,首包延迟合计 97 毫秒。
| 模型 | 语言模型阶段 | 编解码器解码阶段 | 首包延迟合计 |
|---|---|---|---|
| Qwen3-TTS-12Hz-0.6B | 93 ms | 4 ms | 97 ms |
| Qwen3-TTS-12Hz-1.7B | 97 ms | 4 ms | 101 ms |
表2只有两列耗时(语言模型阶段、编解码器解码阶段),式11却有三项(dlm、dfm、dvoc)。表2里「编解码器解码阶段」这一列,对应式11的哪一项,还是对应了不止一项?
变式:如果Qwen3-TTS也像CosyVoice2一样需要独立的flow matching步骤,仅凭这张表的两列数字,你还能不能推算出加上这道工序之后新的首包延迟?
把这张表接回式11:Qwen3-TTS 的架构相当于把 dfm 这一项直接砍掉,只剩 M·dlm(语言模型阶段)加 M·dvoc(编解码器解码阶段)两项。少一道工序,首包延迟里就少一份要排队等待的时间——这不是巧合,是下一节要讲的「流式为什么难」这堵墙,被这套架构从设计上绕开了一处。
答辩:如果我是审稿人
Qwen3-TTS 用「纯左侧上下文解码器」砍掉了 dfm 这一项,首包延迟因此比很多同类系统更短。这是不是意味着「去掉 flow matching 这道工序」本身就是更优的设计,其他系统都应该照着抄?
参考防守(先自己组织语言再看)
不能这么直接下结论。式11少一项确实能让首包延迟更短,但这一项从来不是白白浪费的开销——flow matching 这道工序存在,是因为它能在渲染 Mel 帧的时候看一眼更丰富的上下文,换取更好的音质,这正是20.4节要讲的「flow matching 本质双向」这堵墙的另一面。Qwen3-TTS 选择让编解码器直接因果解码,付出的代价大概率是牺牲了一部分本可以靠双向上下文换来的音质——具体牺牲多少,需要一次同条件的音质对照实验才能量化,本章手上的材料给不出这个数字。真正能确认的是:这是两种不同的权衡,不是「一种架构天然更优」,各自在首包延迟和音质之间选了不同的点。
20.4 流式为什么难:三处绕不开的因果性
既然式11已经把首包延迟拆成三道工序,把每道工序都做得更快、每次只处理更少的 token(M 取更小),首包延迟不就能一路压下去,压到接近于零吗?
答完再看
第二个选项对。M 确实可以调小,式11本身在数学上没有下限,但把 M 调到 1 之后,flow matching 面对的是一个只有一个 token 长度的窗口,几乎没有任何上下文可用,质量会明显崩塌——这不是式11这条公式能预见的代价,是接下来要讲的三处因果性里的第三处。
三处因果性,各自卡在流水线的不同环节,逼着系统没法只靠「M 调小」这一招把首包延迟无限压低。
第一处卡在 flow matching 本身。第8章讲过,扩散和流匹配的去噪网络,标准做法是让每一层的注意力看到全部 token——包括时间上还没轮到的那些。这对非流式合成没有任何问题,反正整段 Mel 频谱是一次性渲染出来的。但流式场景要求模型只能看已经生成的那部分,第 t 帧不能提前偷看第 t+10 帧的信息,这就要求把这套本质双向的注意力,改成分块甚至全因果的。CosyVoice 2 的解法是同时训练四种掩码——非因果(看全部帧,效果最好,用于不在乎延迟的离线场景)、全因果(只看过去,延迟最低)、Chunk-M(能看过去加未来 M 帧,是首个 chunk 的折中)、Chunk-2M(看得更多,牺牲更多延迟换更接近离线的效果)——训练时每个样本随机抽一种掩码,让同一套参数能在四种延迟档位之间切换。掩码越接近全因果,能看到的上下文越少,效果也越接近打折的版本,这是这一处因果性的代价:不是「改成因果的」这个动作本身要花额外的计算,是能看的东西变少了,质量的天花板跟着往下压。
CosyVoice 2 训练了非因果、全因果、Chunk-M、Chunk-2M 四种掩码。如果一个场景要求首个 chunk 必须尽快返回,但又不是完全不能容忍质量打折,四种里最合适的是哪一种?
变式:一段语音的第一个chunk用Chunk-M生成,后续chunk都用Chunk-2M生成,这样的组合策略图的是什么?
第二处卡在声码器。声码器把 Mel 帧变成波形,靠的是卷积网络,第16章讲过 HiFi-GAN 靠转置卷积把 Mel 帧一路放大回采样点。卷积的感受野是对称的——算某一时刻的输出,天然要看左右两边一段邻域,而不是只看左边。如果强行把这段感受野从中间截断在 chunk 边界上,模型在边界附近拿到的输入信息和训练时看到的完整邻域不一样,边界两侧的波形衔接不上,听感上就是一声轻微的咔哒。Qwen3-TTS 绕开这一处的办法,是从设计上就把编解码器做成纯左侧上下文——不是把一个双向的解码器强行截断,是从一开始训练的就是一个只用得到过去信息的解码器,牺牲的是这个解码器能达到的音质上限,换来的是不存在「边界」这个概念,因为它从来没指望过看未来。
「把一个训练好的双向声码器强行在chunk边界截断感受野」,和「从一开始就只训练一个纯因果的声码器」,这两种做法都不看未来,效果会一样吗?
变式:如果一个团队手上已经有一个训练好的双向声码器,不想从头重新训练,还有没有办法让它适应因果场景?
第三处正是这一节开头预测门问到的:M 不能太小。M=1 时首包延迟里 M 这个乘数确实最小,但对 flow matching 这类需要一段上下文才能工作得好的模块来说,一个 token 宽的窗口几乎不提供任何可利用的上下文,质量会明显崩掉。M 因此不是一个只由「首包延迟」这一个目标决定的自由参数,它同时被「flow matching 至少需要多少上下文才能不崩」这条底线约束着,真实系统里 M 的取值,是在这两个互相拉扯的要求之间找到的一个折中点。
M 越小,账面上首包延迟越低
式11里 M 是三项共同的乘数,M=1 时账面数字最小,看起来是最优解。
M 太小,flow matching 先崩
M=1 意味着 flow matching 只能在一个 token 宽的窗口里工作,几乎没有上下文可用,生成质量的下滑不会体现在式11这条纯计时公式里,但会体现在用户听到的声音里。
差别在:式11只管「攒够 M 个 token 要花多久」,管不了「攒得不够多的时候,flow matching 还干不干得好这份活」——这是一堵式11本身看不见、但真实系统必须绕开的墙。
这三处因果性——flow matching本质双向、声码器的对称感受野、M不能太小——听起来都是「模型习惯用未来信息,流式场景不让用」这同一类麻烦。能不能设计一种通用的解法,一次性堵住这三处,而不是像CosyVoice2和Qwen3-TTS各自分别去绕?
变式:如果未来出现一种声码器,天生只需要因果(左侧)感受野就能达到和双向感受野同样的音质,三处因果性会剩下几处?
20.5 除了切块,还能怎么流式
前面几节讲的流式方案,核心动作都是「把整段任务切成一块一块(chunk),改成能因果处理这些块」。如果不切块,还有没有别的办法能做到边生成边播放?
答完再看
Kyutai 的延迟流建模走的正是第二条路。它不对任何一条流做「切块」这个动作,而是让文本流和音频流各自按时间步连续推进,只是让音频流相对文本流延后固定的若干步,模型在同一个时间步上能看到「稍早一点的文本」和「当前的音频」,这个延后量就是它唯一要调的旋钮。第三个选项没有解决问题的实质——不管硬件多快,只要模型还在等一整块任务的输入齐备才开始算,「块」这个结构就还在,硬件加速能缩短延迟,不能消除分块本身带来的因果性麻烦。
这条路的关键想法是:与其把语音和文本硬凑成一个只能整段处理的序列,不如让它们各自变成两条按同一个时间轴推进的流——每一步,模型同时往前走一格文本、一格音频。如果完全同步(两条流的第 t 步对应同一时刻),模型在预测第 t 步的音频时,看不到第 t 步、第 t+1 步的文本还没说完的部分,这和第20.4节遇到的因果性麻烦是同一类:要生成的东西,需要还没发生的信息才能生成准。Kyutai 的做法是引入一个延迟 τ,把音频流相对文本流推迟 τ 步:预测第 t 步的音频时,模型已经看到了文本流一直到第 t+τ 步的内容——相当于提前把「未来」变成了「已经发生的过去」,只是这份「未来」只提前了固定的 τ 步,不是整段文本都能看到。
延迟流建模(Delayed Streams Modeling, DSM):让多条需要对齐的序列各自作为独立的流并行推进,通过在流之间引入固定的步数延迟,把预测一条流所需要的「未来」信息,转换成另一条流里已经生成过的「过去」信息。
分块流式:切开一整段任务
CosyVoice2和Qwen3-TTS走的路——把要处理的token序列切成一块一块,攒够M个再交付一次,块与块之间的边界是这条路要单独处理的麻烦(20.4节的三处因果性)。
延迟流建模:两条流错开着走,不切
Kyutai的路——文本和音频各自连续推进,不存在「一块」这个概念,只有两条流之间固定的时间差τ,边界问题从设计上就不存在,因为没有边界。
差别在:一条路问「一次交付多大的一块」,另一条路问「两条流之间该错开多远」,问的是两个不同维度的问题,凑巧都能达到边生成边播放的效果。
| 符号 | 是什么 | 直觉 |
|---|---|---|
| X≤t+τ | 文本流截止到第 t+τ 步的全部内容 | 提前 τ 步看到的文本 |
| Y<t | 音频流截止到第 t 步之前已经生成的部分 | 已经说出口的声音 |
| τ | 音频流相对文本流延后的固定步数 | 两条流之间错开的那个时间差 |
| Yt | 要预测的第 t 步音频 | 下一格要说什么声音 |
式7里,如果把 τ 设成 0,这个式子退化成什么?这时候模型预测第 t 步音频,能看到的文本流最多到第几步?
变式:τ 能不能取负数?负的 τ 在这个框架里对应什么操作?
τ 这个旋钮直接决定了延迟和质量之间的权衡:τ 越大,模型预测每一步音频时能看到的未来文本越多,效果越接近非流式的离线生成;τ 越小,响应越快,但模型能依据的文本上下文越少。这和 CosyVoice 2 的 M 是同一类权衡,只是作用的对象不同——M 调的是攒多大一块再交付,τ 调的是两条流错开多远。真正的区别在下一步:Kyutai 没有为每一个 τ 训练一个独立的模型,而是训练时让 τ 本身随机取值、并把 τ 的数值编码成一个条件输入喂给模型,换来的是推理时可调的延迟——同一套权重,部署时想要更低延迟就调小 τ,想要更好效果就调大 τ,不需要为每一个延迟点重新训练一次。
如果 Kyutai 没有做「τ 随机采样、编码成条件」这一步,只是固定用 τ=25 步训练一个模型,这个模型能不能在推理时被临时调成 τ=10 步来换取更低延迟?
变式:如果只需要固定一档延迟上线,从来不需要动态调整,训练时随机采样τ这件事还有没有意义?
20.6 2026 年,实际水位落在哪里
把前面几节的公式和思路放到一边,几套 2026 年公开的系统,各自实测的首包延迟给出了一张具体的水位图。Qwen3-TTS 在并发数为1时,0.6B 版本 97 毫秒、1.7B 版本 101 毫秒——20.3 节已经拆开过这两个数字的来路。MOSS-TTS-Realtime 报告的首字节延迟(TTFB)是 180 毫秒,端到端对话延迟(从用户说完话到语音助手开口,对应式12里的 LChat)是 377 毫秒。Kyutai 这套延迟流建模系统,在批大小为1时,实测首包延迟是 150 毫秒(对应表6里 1.8B 规模、批大小1这一档)。
MOSS-TTS-Realtime 的 TTFB(180毫秒)和端到端对话延迟(377毫秒),分别对应式12 LChat≤N·dllm+LTTS 里的哪一部分?
变式:如果把对话模型换成一个思考更快、但吐字质量更差的小模型,N·dllm这一项会变小,端到端对话延迟会不会一定跟着变小?
这几个数字之间有一处空白必须交代:CosyVoice 2 这篇论文本身,没有给出任何一个具体的首包延迟毫秒数——它给出的只有式11和式12这两条分解公式。社区流传的「CosyVoice 2 首包 150 毫秒」,正文和附录都查不到出处,这一章不引用它。
| 系统 | 规模 | 首包延迟 | 备注 |
|---|---|---|---|
| Qwen3-TTS-12Hz-0.6B | 0.6B | 97 ms | 并发数1,含语言模型阶段93ms+解码4ms |
| Qwen3-TTS-12Hz-1.7B | 1.7B | 101 ms | 并发数1 |
| Kyutai DSM-TTS | 1.8B | 150 ms | 批大小1,延迟流建模,τ固定为训练时的默认值 |
| MOSS-TTS-Realtime | 1.7B | 180 ms(TTFB)/ 377 ms(端到端对话) | 后者含对话模型思考时间 |
| CosyVoice 2 | Qwen2.5-0.5B 骨干 | 论文未给出具体毫秒数 | 只给了式11、式12两条分解公式 |
表3里四个给出了具体数字的系统,能不能直接按数字从小到大排出一份「谁的流式方案更好」的名次?
变式:如果四套系统都在同一块GPU、同一批测试文本上重新测一遍,这份对照会不会因此变得可信?还缺什么条件?
20.7 极低帧率不是可有可无的优化,是能不能塞进去的门槛
VibeVoice 号称能在一次生成里做到 90 分钟的长音频,靠的是把 tokenizer 压到 7.5 Hz、配上一个 64K 大小的上下文窗口。如果换用 EnCodec 那样 75 Hz 的 tokenizer,其他条件不变,还能不能在同一个 64K 上下文窗口里塞进 90 分钟音频?
答完再看
第二个选项是算术上的直接结果。90 分钟等于 5400 秒,7.5 Hz 下需要 5400×7.5=40,500 个音频 token,能塞进 64K(65,536)的上下文窗口,还剩两万多个位置留给文本和提示词;换成 75 Hz,同样 90 分钟需要 5400×75=405,000 个 token,是 64K 窗口容量的六倍还多,物理上塞不进去,不存在「压缩每个token」这种事后补救——上下文窗口的位置数是语言模型架构里固定的一个数字,不会因为你想塞更多东西就自动变大。
这道预测门算出的两个数字,值得并排摆在一起看。
| tokenizer | token率 | 90分钟需要的token数 | 相对64K窗口 |
|---|---|---|---|
| VibeVoice | 7.5 Hz | 40,500 | 装得下,剩约25,036个位置给文本和提示词 |
| 假设换成EnCodec档位 | 75 Hz | 405,000 | 超出64K窗口六倍以上,装不下 |
7.5 Hz:90分钟音频,40,500个token
装进64K窗口后还剩两万多个位置,留给文本提示词绰绰有余,这正是VibeVoice敢承诺「单次最长90分钟」的算术基础。
75 Hz:90分钟音频,405,000个token
是64K窗口容量的六倍还多,不存在任何压缩技巧能把这个数字硬塞回去——这不是「效果差一点」,是「根本装不进这个架构」。
差别在:token率决定的不是音质档位,是「能不能开口谈论90分钟」这件事本身能不能成立。
VibeVoice 的 tokenizer 具体是怎么压到 7.5 Hz 的:输入采样率 24000 Hz,总下采样倍数 3200 倍,24000÷3200=7.5,和论文摘要里「3200× 压缩率」这句话对得上。论文还给出了另一个好记的比例——语音 token 和文本 token 的数量大致是 2 比 1,也就是说同一段内容,语音这边用的 token 数,大约是把它写成文字所需 token 数的两倍。这个比例解释了为什么论文特别标注 64K 窗口里「最多可以有 25,036 个位置留给文本和提示词」这个具体数字并不是随口一说,是满打满算塞进 40,500 个音频 token 之后剩下的空间。
这一章反复出现两个关于降低token率的理由:20.2节说降token率是为了给单步计算换出更大的预算(时间维度);这一节说降token率是为了让更长的内容塞进固定大小的上下文窗口(空间/容量维度)。这两个理由是不是同一件事的两种说法,还是两个各自独立、只是恰好靠同一个旋钮实现的好处?
变式:如果一个系统把上下文窗口从64K扩大到256K(四倍),VibeVoice用同样7.5Hz的tokenizer理论上能撑到多长的音频?
读的时候要小心
关于本章数字的来路
Qwen3-TTS、MOSS-TTS-Realtime、Kyutai DSM-TTS 各自的首包延迟数字,以及 VibeVoice 的 3200 倍压缩率、90 分钟长度上限,都来自各论文作者自己的实测和描述,没有第三方复现过,测试硬件和条件互不相同,本章按 B 档处理,不做横向排名。CosyVoice 2 的式11、式12是论文自己给出的分解公式,本身是可以直接代入具体数字验证的关系式,属于A档;但公式里 dlm、dfm、dvoc 这些变量的真实取值,论文没有公开报告过。
这一层要加什么:分块流式输出
为什么现在才加它:前十九级关心的都是「生成得准不准、要花多久算完」,把生成完的结果整段交出去就算完工。这一层要把「整段生成完再交付」换成「攒够一小段就先交付」,还要能在攒得多和攒得少两种极端之间自由调,并且两种极端各自的行为都要能被读者亲手验证出来。
难点:这段伪代码把 flow matching「看双向上下文才能渲染准」这件事,简化成了「取窗口内均值」——一个真实实现里,这一步是一整个神经网络的多步去噪,不是简单平均,但简化不改变这一层要验证的核心行为:窗口一旦被 chunk 边界卡住变窄,输出就会偏离参考值,偏离的位置精确集中在块的边界附近,不是随机散布在整段序列上。真正容易写错的地方是 lo, hi = max(c0, i-R), min(c1, i+R+1) 这一行——如果不小心写成 max(0, i-R), min(N, i+R+1)(用序列的起止代替块的起止),窗口就会偷偷跨过块边界,整个「流式」的因果约束形同虚设,跑起来结果看着没问题,但其实和非流式没有区别,这个 bug 不会报错,只会让下面的 bs-check 全部失效。
自己验:把 chunk 设成等于序列全长(只有一块),out 必须和 y_true 逐位相等,误差为 0——这时候窗口从不会被块边界卡住;把 chunk 固定在 2R,统计「块边界附近(到最近块边界距离小于R的位置)」和「块内部(距离≥R的位置)」两组的平均绝对误差,边界附近的误差应该明显大于内部误差,至少两倍以上——这正是CosyVoice2要用四种掩码应对、HiFi-GAN转置卷积在chunk边界产生咔哒声的同一类现象,量化后的证据;把 chunk 从1一路调大,边界误差应该逐步下降、越来越接近内部误差,因为块越大、边界占比越小;最后验证 first_package_latency:chunk从4改成8(t_step不变),返回值应该精确翻倍,不多不少。
留给你的问题
这一章讲的每一处权衡——token率换模型规模、M换首包延迟、τ换质量、chunk边界换咔哒声——都是在同一个大前提下发生的:模型必须先把一部分输入变成输出,才能开始处理下一部分。如果有一天,语音合成不再是「先文本后语音」这种单向依赖,而是允许模型在生成到第5秒时,回头小幅修改已经生成过的第2秒的内容(类似人说话时会重新组织已经开口的句子),这一章讲的首包延迟、token率、chunk这一整套框架,还能不能原样套用?「首包」这个概念在这种场景下还成立吗?
不看材料,写下这一章的三堵墙
第一堵:以为RTF已经把「快不快」说清楚了,其实RTF只管长期能不能追上实时,管不了用户开口前那第一段死寂到底有多长,这是两个必须分别优化的指标。第二堵:以为把M调到最小就能无限压低首包延迟,没想到flow matching这类需要上下文的模块,窗口小到一定程度质量会先崩,M被两头拉扯,不是一个自由变量。第三堵:以为一段音频要塞进多长的上下文窗口只取决于窗口大小本身,没意识到token率是这道除法里的分母,分母不降下来,再大的窗口也装不下90分钟。
第21章 怎么读一张评测表
开篇故事第 21 集 · 高出的那五分,把屋里吵成两派这模型也太真实了
“人家都比咱们高五分了,还争什么?”炼丹废人把笔记本转过来给大家看,屏幕上那张评测表被他用红框圈住了一行,语气里带着点破罐子破摔的认命。
这是回声复现的第一百八十一天,那张表是今天下午从一篇新论文里截出来的:对方那个视频模型的 VBench 总分,比第十二章早测出来的 STA 微调版本高出五分。消息刚贴进频道,屋里就分成了两派——一派跟着炼丹废人开始盘算这几天的活是不是白干了,另一派梗着脖子说这事没那么简单。窗外天已经擦黑,机柜的风扇声比白天更吵,谁都没顾上去开灯,两派人就着屏幕那点光互相瞪着,谁也不肯先松口。
新论文那五分,到底能不能说明人家的视频模型全面更好
1L炼丹废人人家总分比咱们高五分,我是真没什么好争的,五分不是舍入误差能抹平的差距,这局我认了。
2L显存刺客我也倾向信这个数,论文摆出来的总分总不能是编的,输了就是输了,别在这儿找补。
3L复现之鬼不对,总分高不高先放一边,你们谁核过这两次测的是不是同一批提示词、同一个版本的 VBench,条件没摆清楚就直接认输,这局根本没法判。
4L沈砚她这话点到了点子上,第十二章那 0.09% 是我们自己在同一套条件下测出来的差值,人家那五分是从另一篇论文里搬过来的,两笔账压根不是同一把尺子量的。
复现之鬼把话摊开,靳工在后面接了一句,把这件事钉死。总分要比大小,得先把测试用的提示词、模型版本、维度权重这些条件定成同一套,条件不是同一套,谁高谁低这句话就没资格被问出来。“别老想着追平那五分。”靳工把杯子放下,“先去问问那篇论文用的是哪一版 VBench、哪几条提示词,问不到,这五分就只是个数字,不是结论。”
炼丹废人把红框撤了,说自己确实是看见数字就先慌了,这毛病他一时半会儿改不干净;显存刺客也挠了挠头,承认自己那句“输了就是输了”说得太快,其实压根没点开那篇论文核对用的是哪一版评测代码。复现之鬼倒是给这事定了个规矩——回声这边往后写进展贴,不该再把哪个模型直接钉上“最好”这顶帽子,没查清条件之前,一个数字什么都证明不了。
这场两派的争论散得快,屋里刚安静下来,工作群里又跳出一条新消息——这次是外聘律师发来的私信,问的是评测表上排第一的那个模型:“这个能商用吗。”消息下面,还没有人回。
系统结算 · 本章考点
- FID 的全称锁定了一个具体的网络——Inception,换一个网络当裁判,两个模型的排名可能会跟着变
- CFG 论文自己报告的最优 FID 落在 w=0.1 到 0.3(guidance_scale 约 1.1 到 1.3),社区常用的 guidance_scale 却是 7.5(换算回去 w=6.5),二十倍都不止的差距
- VBench 的五个时间维度里,完全静止的视频能讨好其中四个,只有“动态程度”这一项会被打到接近零分,只想冲高总分的模型天然会被前四项拉向静止
- STA 微调版本的 VBench 总分只降了 0.09%;换一篇论文说自己总分高出 5 分,不查清测试用的是不是同一批提示词、同一个版本,这 5 分说不清“全面更好”
- WER 不是语音自己的属性:CosyVoice 2 中文测试集找 Paraformer 当裁判,英文测试集换成 Whisper-large V3,换一个 ASR 模型,同一段语音的 WER 可以不一样
- HunyuanVideo 官方仓库的下载量只有 825,点赞却有 2294 个——大多数使用者走的是社区维护的 diffusers 仓库,只看一条渠道的数字测不出真实热度
翻开一篇生成模型的论文,最后总有一张表,写着 FID 多少、VBench 多少分、WER 多少。这张表看起来是终点——数字摆在那里,谁高谁低一目了然。这一章要说清楚,这张表其实更像一个起点:每一个数字背后,都站着一次没有写进表格的选择——选了哪个网络当裁判、选了哪个引导强度去测、选了哪几个维度去打分。不知道这些选择,这张表能告诉你的事,比它看起来的要少得多。
学完这一章你应该能做到
- 说清 FID 这类指标为什么依赖一个具体的预训练网络,换一个网络排名可能会变
- 区分「统计意义上最优」和「人眼看起来最好」,说出两者为什么常常不是同一个引导强度
- 逐个说出 VBench 五个时间维度各自怎么算出来的,并能构造一个让其中几个维度失真的反例
- 说清 WER、SIM、MOS 这类指标为什么是「内容加裁判模型」两者共同决定的数字,不是内容本身单独的属性
- 解释本站为什么从头到尾没有给任何一个模型冠上「SOTA」这个词
- 拿到一个新模型的热度数字,知道该先问哪几个问题,而不是直接拿数字当结论
这一层不是给某个模型加零件,是给你自己加一把尺子。前20级你一直在信任别人报告出来的数字——FID、WER、VBench 分数、GitHub 星标。这一级要亲手做一次这些尺子当中的一把,看看它到底在量什么,又会在什么情况下失灵。
21.1 FID 说这张图更像真的,用的是什么眼睛?
第2章提到,LDM 论文用 Precision 和 Recall 比较过 ProjectedGAN 和 ADM,这两个指标和 FID 一样,都要先把图像喂进一个预训练网络,取某一层的输出当特征,再在这个特征空间里算距离或者算覆盖率。如果换一个预训练网络当特征提取器,FID 给两个模型排出的名次,会不会跟着变?
答完再看
FID 的全称是 Fréchet Inception Distance——「Inception」这个词本身就写明了它默认绑定的网络:一个在 ImageNet 上训练好的 Inception 网络。真实图像和生成图像各自被这个网络编码成一组特征向量,假设这组特征服从正态分布,再算出两组正态分布之间的 Fréchet 距离。这个网络在 ImageNet 分类任务上学到的特征,天然对训练时见过的物体类别更敏感,对这些类别之外的视觉差异未必同样敏感。第一个选项把「统计分布」和「某一个网络看到的统计分布」混成了一件事;第三个选项假设不同网络学到的特征相互等价,这个假设没有任何理论保证。
第2章那场答辩已经把这件事挑明过一次——Precision 和 Recall 依赖一个预训练网络这个担心是成立的,但几条独立证据让那一章的结论还站得住:同一张表里的多个模型用的是同一套网络,系统性偏差压在每一行上,不太会单独放大某一行的排名;模式坍塌这类现象在生成样本里也能被肉眼直接看到,不是只有指标这一个证据来源。FID 继承的是同一种结构,也继承同一种局限。
FID(Fréchet Inception Distance):把真实图像和生成图像各自喂进一个预训练的 Inception 网络,取某一层的输出当特征,假设两组特征各自服从一个多维正态分布,算出这两个正态分布之间的 Fréchet 距离。距离越小,说明生成图像的特征分布和真实图像的特征分布越接近。
FID 实际在做的事
把生成图和真实图各自过一遍 Inception 网络,在这个网络学到的特征空间里,比较两组分布的接近程度。
容易被读成的样子
直接比较两组图像本身有多像,仿佛这是一次不经过任何中间步骤、不依赖任何选择的客观测量。
差别在:FID 这个数字自己不会说明它信的是哪一双眼睛,这一步选择被定义悄悄吸收掉了,读表的人如果不知道这件事,会把「这个网络觉得像」直接读成「像」。
如果两篇论文分别用不同版本的 Inception 网络权重算出了各自的 FID,把这两个数字直接摆在一起比大小,这样做为什么站不住脚?
变式:如果一定要横向比较两篇论文的 FID,除了看数字本身,应该先核对哪个条件是不是一致?
21.2 FID 最优的引导强度,为什么不是社区常用的那个数?
第8章提过,CFG 论文自己报告,ImageNet 上最好的 FID 出现在 w=0.1 到 w=0.3 之间,换算成 guidance_scale 大约是 1.1 到 1.3;而 Stable Diffusion 系列社区默认用的 guidance_scale 是 7.5。如果 FID 是论文自己拿来衡量生成质量的指标,社区为什么没有跟着用 FID 最优的那个引导强度?
答完再看
换算关系是 s=1+w,guidance_scale=7.5 对应 w=6.5,比论文自己认定的最佳区间高出二十倍不止,第二个选项里的换算是对的。FID 衡量的是生成图像分布和真实图像分布在 Inception 特征空间里有多接近,这个接近程度和「人眼觉得好看、和文字贴合」是两把不同的尺子——高引导强度会把生成结果推向更符合文字描述、色彩更饱和、构图更突出主体的方向,这些变化恰恰会让生成分布偏离真实图像分布,FID 因此变差,但人的主观评分经常因此变好。
这不是 CFG 一篇论文的特例。任何一个「统计分布匹配」类型的指标,都在衡量生成结果和训练数据分布有多接近;而人类偏好衡量的是这张图好不好看、是否贴合意图,这两件事在很多操作区间里方向一致,但没有任何理由保证它们的最大值精确落在同一个参数点上。FID 论文没有承诺「FID 最优等于人眼最优」,是后续使用者把两者默认成了同一件事。
CFG 论文自己报告的最优区间
ImageNet 上 w=0.1~0.3,对应 guidance_scale≈1.1~1.3,这是让生成分布最贴近真实分布的引导强度。
社区默认使用的区间
Stable Diffusion 系列常用 guidance_scale=7.5,对应 w=6.5,这是让人眼主观评分更高、更贴合文字描述的引导强度。
差别在:一条线追的是统计分布匹配的最大值,另一条线追的是主观偏好评分的最大值,论文报告的那个数字,只精确回答了前一个问题。
如果一篇新论文只报告了一个比前人更低的 FID,没有附带任何人类偏好评测,能不能直接得出「这个模型生成的图更好看」这个结论?
变式:如果两篇论文用同一个引导强度、同一个数据集报告了 FID,这时候比较两个 FID 数字,可信度会不会比21.1节里那种跨论文比较更高?
21.3 一段完全静止的视频,VBench 能打出几分?
VBench 用五个维度衡量一段生成视频在时间上的质量:主体一致性、背景一致性、时间闪烁、动作平滑度、动态程度。如果拿一段完全静止、每一帧都逐像素相同的视频去测这五个维度,你觉得它会在几个维度上拿到接近满分的分数?
答完再看
五个维度里,主体一致性、背景一致性、时间闪烁、动作平滑度这四个,衡量的都是「画面有没有意外地变化」,完全静止的视频在这四个维度上是理论上的最优解——什么都没变,自然不存在不一致、不存在闪烁、不存在不平滑的运动。只有动态程度这一个维度,衡量的是「画面里有没有足够大的运动」,完全静止的视频在这一项上会被打到接近零分。
VBench 的五个时间维度各自用什么方法计算,直接决定了它们各自在乎什么、又对什么视而不见。把计算方法摆在一张表里,这件事就不再是一句笼统的描述。
| 维度 | 衡量什么 | 怎么算 |
|---|---|---|
| 主体一致性 | 视频里的主体,外观是否始终保持一致 | 逐帧提取 DINO 特征,算跨帧的特征相似度 |
| 背景一致性 | 背景场景的时间一致性 | 逐帧提取 CLIP 特征,算跨帧的特征相似度 |
| 时间闪烁 | 局部高频细节有没有不该有的跳变 | 取相邻帧,算逐像素绝对差值的均值 |
| 动作平滑度 | 运动是否顺滑、是否符合物理规律 | 借助一个视频插帧模型自带的运动先验来打分 |
| 动态程度 | 视频里有没有足够大的运动 | 用光流模型 RAFT 估计画面的运动幅度 |
如果一个视频生成模型专门针对 VBench 的主体一致性和背景一致性做了优化,你会预期它的动态程度分数大概率发生什么变化?
变式:VBench 最终会把五个维度汇总成一个总分,如果总分权重给动态程度的份额很小,一个只求总分好看的模型,最理性的策略是什么?
前四个维度的计算方法有一个共同的结构:都是在测「跨帧的差异有多小」,只是分别用不同的工具(DINO 特征、CLIP 特征、逐像素差值、插帧先验)去测这件事。差异越小,前四项的分数就越高。只有动态程度反过来,测的是「跨帧的差异有多大」。一段视频只要什么都不改变,就能同时讨好前四项、得罪最后一项——VBench 论文自己也承认,这是模型能够「作弊」换取高一致性分数的一条路。
完全静止的视频
主体一致性、背景一致性、时间闪烁、动作平滑度四项接近满分,因为跨帧差异趋近于零;动态程度接近零分,因为光流估计不出任何运动。
有真实合理运动的视频
动态程度能拿到有意义的分数,但前四项会因为画面确实在变化,分数比静止视频更容易出现扣分。
差别在:前四项和最后一项在优化方向上互相牵制——把画面做得越安分,前四项越漂亮,动态程度越难看,VBench 总分因此没法只靠讨好某一侧被无限推高。
21.4 WER 和 SIM 这两个字母,背后站着哪个模型?
CosyVoice 2 自己的评测方法写得很具体:英文测试集用 Whisper-large V3 当 ASR 模型,把合成语音转写成文字,再和输入文本比较算出 WER;中文测试集换成了 Paraformer 这个模型来做同样的转写。说话人相似度(SIM)则是另一套流程——用 ERes2Net 模型分别提取参考音频和生成音频的说话人向量,取两个向量的余弦相似度。同一篇论文,中英文测试集用了两个不同的 ASR 模型。
中文测试集:Paraformer 当裁判
Paraformer 是专门为中文语音识别优化过的模型,用它转写中文合成语音,WER 反映的是「这段语音,在 Paraformer 眼里,转写准确度如何」。
英文测试集:Whisper-large V3 当裁判
Whisper 是一个多语种通用 ASR 模型,用它转写英文合成语音,WER 反映的是「这段语音,在 Whisper 眼里,转写准确度如何」。
差别在:两个 WER 数字用的是两把不同的尺子,论文这么选是因为每种语言用专精的识别模型更公平,但这也直接证明了 WER 从来不是脱离裁判模型单独存在的。
MOS(平均意见得分)也不例外,只是它的「裁判」经常换成一群人类评分者,或者一个专门训练出来预测人类评分的神经网络模型,比如 CosyVoice 2 用到的 NMOS。人类评分者的听感偏好会随文化背景、听音设备、评分习惯波动;神经网络预测器则会继承训练它时用的那批评分数据的偏好。不管裁判是人还是模型,MOS 都不是语音本身单独拥有的性质。
如果同一段合成语音分别用 Whisper 和 Paraformer 转写,两次算出的 WER 一样吗?
变式:如果一篇论文只用一个明显更弱的 ASR 模型给自己的系统转写、却用一个更强的 ASR 模型给对照系统转写,WER 对比结果会往哪个方向偏?
21.5 本站为什么从头到尾没有说过谁是SOTA?
前20章介绍了几十个模型,每一个都有自己的优点。如果要你现在把图像、视频、语音这三条线里各自「最好」的模型选出来,你觉得这件事本身能不能做到?
答完再看
前四节已经拆开了三层原因:FID 依赖具体用哪个特征网络,跨论文的 FID 不能直接比大小;FID 最优和人眼最优本来就是两个不同的最大值点;VBench 的时间维度之间互相牵制,一个模型可以在其中几项占优、在另一项吃亏;WER、SIM、MOS 都绑定着一个具体的裁判模型或评分群体。把这几层原因叠起来,「谁是最好的」这句话在没有说清楚「用哪把尺子、量哪个维度、跟谁比」之前,本身就不是一个能被回答的问题。第三个选项在21.6节会看到同样站不住。
这不是本站在回避一个本可以回答的问题,是这个问题在当前的评测体系下,问法本身就不完整。一个模型在 FID 上领先,可能在人类偏好评测上落后;一个视频模型在动态程度上领先,可能在一致性上落后;一个语音模型用更宽松的 ASR 模型测出更低的 WER,换一个更严格的 ASR 模型排名可能倒过来。每一次比较,都需要先固定住评测集合、评测方法、裁判模型这三样东西,固定的方式一变,排序就可能跟着变。
能被直接回答的问题
「模型A和模型B在同一批提示词、同一版本VBench、同一套权重下,总分谁更高」——固定住了全部条件,剩下的只是读一个数字。
不能被直接回答的问题
「模型A和模型B谁更好」——没有说清楚在哪个维度、用哪套评测、跟谁比,这句问话本身还没有资格拥有一个单一答案。
差别在:前一种问法把「用哪把尺子」这个选择摆在明处,后一种问法把这个选择悄悄留给了读者自己去脑补,脑补的结果因人而异,答案自然也就不唯一。
答辩:如果我是审稿人
拒绝给出「谁是最好」这个结论,会不会只是一种听起来严谨、实际上什么都没说的回避?读者真正想要的,不就是一个能直接照着选模型的排名吗?
参考防守(先自己组织语言再看)
这个担心有它的道理——一份只讲「不能排名」的材料,如果止步于此,确实对要选模型的读者没有帮助。但本站给出的不是空白,是一套更细的问法:先问清楚你在乎的是哪个维度(分布匹配、人眼偏好、说话人相似度、还是动态程度),再去看报告这个维度的数字用的是哪套评测方法,是不是和你要比较的另一个模型用的是同一套条件。这比一个囫囵的排名更麻烦,但一个跨论文、跨评测集拼出来的总排名,本身就带着前面几节拆开的全部隐患,给出一个看似干脆的答案,代价是把这些隐患都悄悄藏起来,不说反而比说更诚实。
第12章提到过,STA 免训练和微调两档加速方案里,微调版本速度更快,「VBench 总分仅降 0.09%」。如果另一篇论文的模型 VBench 总分比这个数字高 5 分,能不能据此断定另一篇论文的模型生成质量更好?
变式:如果两篇论文用的是完全相同的 VBench 版本、相同的提示词集合,这时候比较两个总分,可信度会不会比不同条件下的比较高很多?
21.6 一个模型的下载量和星标,能不能直接当成受欢迎程度?
HuggingFace 的下载量统计的是「近30天」这个窗口内的程序化下载次数,不含 Civitai、ModelScope、种子文件、ComfyUI 自动拉取和各类量化衍生版本;GitHub 星标数只精确到 0.1k,是抓网页读到的整数被圆整过的结果,不是一个可以拿去做精确算术的数字。FACTS.md 记录的一个具体例子很能说明这一点:tencent/HunyuanVideo 在 HuggingFace 上只有 825 次下载,但获得了 2294 个赞——因为大多数使用者走的是社区维护的 diffusers 版本仓库,官方仓库的下载数字因此显得很低。
只看官方仓库下载量:825
如果只统计 tencent/HunyuanVideo 这一个官方仓库的近30天下载数,数字很小,容易被读成「这个模型没什么人用」。
再看点赞数和社区仓库:2294 个赞
点赞数不受「走哪个仓库」影响,是另一个独立信号;配合已知的社区 diffusers 版本使用量,能看出真实使用人数远比 825 这个数字所暗示的多。
差别在:下载量只统计一条特定的分发渠道,一个模型的真实使用者可以从好几条渠道拿到权重,只看一条渠道的数字,测到的是这条渠道的热度,不是模型本身的热度。
拿到一个新模型的热度数字时,与其直接把它当结论使用,不如先问几个问题,把数字放回它产生的语境里再读。
| 看到的数字 | 先问什么 |
|---|---|
| 一个更低的 FID / 更高的 VBench 总分 | 和对照模型是不是同一套评测集合、同一版本的评测工具测出来的 |
| 一个很高的 GitHub 星标数 | 这个数字精确到什么程度,是不是被四舍五入圆整过 |
| 一个很低或很高的 HF 下载量 | 是不是只统计了官方仓库这一条渠道,有没有社区镜像仓库分流 |
| 论文自己给出的推理速度或加速比 | 测试硬件、批大小、序列长度这些条件是否已经写清楚 |
| 「开源」两个字 | 放出来的是代码还是权重,许可证允不允许商用——第22章会专门拆开这件事 |
某个模型的介绍页写道「GitHub 25.9k 星标,遥遥领先同类模型」,只凭这一句话和表2的清单,你能不能判断这个说法是否站得住?
变式:如果把「星标数」换成「HF 近30天下载量」,同样一句「遥遥领先」,还需要多核对表2里的哪一条?
读的时候要小心
关于本章数字的来路
CFG 论文报告的最佳 FID 区间、VBench 论文给出的五个维度定义和计算方法、CosyVoice 2 自己使用的 WER/SIM/MOS 测评流程,都是各论文作者自己的描述和实验,本章没有重新训练任何模型去复现。FACTS.md 里 tencent/HunyuanVideo 的下载量与点赞数、GitHub 星标的 0.1k 精度,都是官方页面在 2026-09-18 这一天抓取到的数字,会随时间继续变化。本章的简化版时间闪烁指标是本站为教学目的实现的简化版本,不是 VBench 官方代码,量出来的数值不能和 VBench 论文报告的数字直接比较。
这一把尺子怎么亲手打磨:简化版时间闪烁指标
为什么现在才加它:前20级你一直在使用论文报告的数字,从没有一级要求你自己实现过一把评测的尺子,也就从没有机会亲眼看到这把尺子在什么地方会失灵。这一级要把 VBench 时间闪烁维度的核心计算亲手实现一遍,用两段自己构造的视频去试它。
难点:temporal_flicker 这个简化实现,量的是逐像素绝对差值,它没有办法区分「一个物体在合理地移动,导致像素在变」和「画面在无规律地抖动,导致像素在变」——只要逐帧差异大,这把尺子就判定分数差,不管这份差异是不是来自真实、合理的运动。这正是21.3节表格里「时间闪烁」这一栏计算方法的真实样子,不是本站简化带来的偏差。另一个难点是 make_moving_video 的构造:如果移动幅度太小(比如一个物体只平移一两个像素),测出来的分数会和静止视频很接近,看不出这把尺子的局限,需要构造一段有明显、合理运动的视频(比如一个色块匀速平移穿过大半个画面)才能让差别显现出来。
自己验:对一段完全静止的视频跑 temporal_flicker,结果应该精确等于 0;对一段有明显合理运动的视频跑同一个函数,结果应该明显大于 0,数值比静止视频的差很多;把这两个结果对照着看,会得到一个自己亲手造出来的悖论——「真实、合理的运动」和「画面质量下降式的闪烁」在这把简化尺子下,得到的是同一个方向的判罚,分数更差的那一段视频,恰恰是运动更真实的那一段,不是质量更差的那一段。
留给你的问题
这一章拆开了 FID、VBench、WER 这几把尺子各自的内部构造,也亲手造出了一把尺子自己的悖论。如果让你现在给下一章要讲的「这些论文没告诉你的事」列一张问题清单,第一条你会写什么——是继续追问某个具体指标还靠不靠得住,还是转而去问,一篇论文选择报告哪些指标、不报告哪些指标,这件事本身说明了什么?
不看材料,写下这一章的三堵墙
第一堵:FID 看起来是一个衡量图像本身的客观数字,实际是一个绑定着具体特征网络的相对距离,换一个网络这个数字就换了含义。第二堵:统计分布匹配最优和人眼主观质量最优,被想当然地当成了同一件事,CFG 的 w=0.1~0.3 和社区常用的 guidance_scale=7.5 之间二十倍的差距把这个假设戳穿了。第三堵:VBench 的五个时间维度里,前四个奖励「画面不变」,最后一个奖励「画面在变」,一个只想讨好总分的模型,天然会被前四项拉向静止,这堵墙不是评测方法的疏忽,是这五个维度定义本身互相牵制的结果。
第22章 这些论文没告诉你的事
开篇故事第 22 集 · 那张发给律师的截图,只截到一半这模型也太真实了
中午十二点半,工作群里跳出客户法务转来的一句话:“这个能商用吗?”这是回声复现的第一百九十天,问的是现在给客户演示的那版语音克隆——自家这条线还没训完,沈砚半个月前手快,直接拉了 MaskGCT 的公开权重接上演示管线,图的是快。他想起自己确实点开过那个仓库,首页顶上明晃晃写着 MIT 协议,想都没多想就回了一句“能,MIT 协议,随便用”,顺手截了张图发过去,图里只有仓库首页最上面那一截,权重那个文件夹他压根没点进去看过。
下午两点,他的手机在桌上震了一下,弹出来的是法务助理转发的第二条消息——“麻烦把权重那部分的许可证文件也发一份过来,我们这边不能只看代码仓库那一份。”沈砚盯着屏幕愣了两秒钟,才反应过来对方翻得比自己深,中午那张截图这时候摆在眼前,忽然显得心虚起来。
法务助理没有停在这一句。她把仓库翻到了权重那个文件夹,里面单独放着一份文件,写的是 CC-BY-NC-4.0——明确禁止商业使用。这条消息转到工作群里的时候,办公室瞬间安静下来,只剩机柜那头的风扇声嗡嗡响着,沈砚把自己中午发的那张截图重新点开,从头到尾找了一遍,截图里确实从没出现过「权重」这两个字。
系统提示本次核对:MaskGCT 代码许可证是 MIT,权重许可证另标 CC-BY-NC-4.0,二者互不相同,按「代码协议即整体协议」估出的档位是 5,本次判定校验未通过。
“代码一份协议,权重另一份协议,这是两份单独签的文件,你截图只截住了上面那半。”靳工端着杯子从里屋走出来,把杯子往桌角一放,“MIT 这两个字母,从来管不到权重头上,你把两件事当成了一件事。”沈砚没有立刻反驳,而是把自己中午那几步想法倒着捋了一遍:他看见 MIT 那三个字母,就直接把它当成了整个模型的通行证,权重那个文件夹他确实点开过,只是扫了一眼目录列表就退了出来,没点进去看那份单独放着的协议文件,凭着写代码几年攒下的经验,他下意识觉得一个仓库的许可证只会写在最显眼的地方,没想过它会被拆成两份、分别塞在两个不容易同时看到的位置。
系统提示license_check 复核:权重已发布,论文已发表,非商用条款为真,判定档位由 5 修正为 3,也就是能研究、能改,不能直接卖给客户用在产品里。
沈砚拿着手机站在原地,一时不知道该先回法务还是先回客户那边的商务负责人——早上那句“能,随便用”已经是白纸黑字发过去的书面记录,现在要收回,等于当着客户的面承认自己功课没做全。靳工没有替他挡这一下,只说了一句,这种时候越藏着才越麻烦,趁着还没到发布会那天把话说清楚,总比东西已经用出去了才被人翻出来划算。傍晚下班前,这件事已经传遍了整个论坛,几个人的判断分成了两派。
那张只截了一半的截图,到底该算谁的责任
1L显存刺客MIT 不就是能商用吗,谁没事会去翻权重那个文件夹,我看是法务那边故意较真。
2L炼丹废人不对,代码和权重根本是两份独立声明的东西,MIT 管的只是代码那一半,权重那份写着 CC-BY-NC-4.0,这不叫较真,是两份文件本来就没在说同一件事。
3L沈砚我把自己那几步想法倒着捋了一遍,是我看见 MIT 就默认它管得住整个模型,权重那个文件夹我确实点开过,只是没往里翻到那份单独放着的协议。
4L复现之鬼这次算是接住了,图像和视频那两条线要不要也翻一遍是不是借用过别的公开权重,趁着还没到发布会那天查清楚,别让同一个坑留到最后十天才炸。
法务那封邮件最下面还压着一行没回的附言:如果整条流水线里还有别的地方,答案和 MaskGCT 这次一样,回声打算怎么处理?沈砚把这句话截了整屏存进备忘录,标题写着「发布前必须查完」,日期一栏,离发布会只剩十天。
系统结算 · 本章考点
- 一个模型的「开源」常常拆成两份文件:MaskGCT 代码许可证是 MIT,权重另标 CC-BY-NC-4.0,代码能商用不代表权重能商用,只扫一眼代码那份就是这次犯的错
- FACTS.md 把「开源」拆成五档,从权重从未放出到权重、代码、论文一应俱全、不问地区不问规模,中间还隔着好几档不同的处境
- 同一套 license_check 判定:HunyuanVideo 因为地区和规模受限,档位是 4;Wan 2.1/2.2 没有这些附加条款,档位是 5,同一个函数会因为字段不同给出两种答案
- 把 Wan 2.1 的地区限制字段手动改成真,重新判定一次,档位从 5 掉回 4,同一段代码只改一个字段,结论就会跟着变
- HunyuanVideo 的门槛写在细则里,月活超 1 亿需要单独申请;SD3 Medium 的门槛是年收入低于 $1M,同一句「可以商用」背后还压着地区和规模这两个问题
前21章一直在教你怎么相信一个数字——怎么把它算出来,怎么核对它的来路。这一章要反过来,教你怎么不相信一个数字,或者更准确地说,怎么看出一个数字背后藏着一处没说清楚的地方。同一篇论文里两张表打架、一个「开源」标签背后其实是五档完全不同的现实、本站自己也有没做到的事——这些都不是要否定前21章讲过的内容,是这门课走到这里,该把手里压着没说的那部分账一次性摊开。
学完这一章你应该能做到
- 在同一篇论文内部发现两个互相打架的数字,并且知道除了猜测之外还能做什么
- 说出本站正文里标为 C 档的几处推断分别是怎么来的,为什么不能算作论文明说的结论
- 区分「开源」这个词在不同模型身上实际对应的五档不同处境,说出每一档能不能商用
- 打开一份陌生的许可证文件,知道该优先看哪几行
- 说清本站这套讲法本身有哪些做不到的事,尤其是「能自己算」和「真的跑过一个模型」的区别
这一层不再给任何模型加零件,是把前21层积累下来的怀疑态度,浓缩成一张能反复使用的核对清单。给你一段诚实声明模板和一份许可证检查流程,拿真实的两个模型分别走一遍,看看同样一套流程会不会给出截然不同的结论。
22.1 同一篇论文里的两个数字打架,该信哪一个?
CogVideoX 论文附录的 Table 5 说,stage3、stage4 训练阶段,Sequence Length 是 700k;紧接着的 Table 6 却说,同一对模型(CogVideoX-2b 和 CogVideoX-5b)的 Max Sequence Length 是 82k。这两个数字相差 8 倍多,遇到这种情况,最站得住的做法是什么?
答完再看
Table 5 的 Sequence Length 紧跟在训练阶段、批大小、训练步数这些配置旁边,看起来是某一次具体训练配置下的实际 token 数;Table 6 的 Max Sequence Length 紧跟在层数、注意力头数、隐藏维度这些结构参数旁边,看起来更像模型结构预留的一个上限。这两种解释都只是合理的猜测,论文没有一句话说明这两张表的数字为什么不一致。取平均数或者凭感觉挑一个「更合理」的数字,做的都是论文没有做的解释工作,风险比原样记录一处未解释的矛盾更大。
第18章提到过,EnCodec「码本数是4的倍数」这句概括性描述,本身就有一档配置是反例,需要读者自己核实每一档具体数字才能发现。这和这一节要讲的「同一篇论文内部两个数字打架」,是不是同一类问题?
变式:如果只读了论文里「码本数是4的倍数」这一句话,没有去查每一档的具体配置,会不会觉得这句话没有任何问题?
| 来源 | 数字一 | 数字二 | 矛盾在哪 |
|---|---|---|---|
| CogVideoX(arXiv:2408.06072,Table 5/6) | Sequence Length 700k(stage3/4 训练配置) | Max Sequence Length 82k(模型结构参数) | 同一对模型,两张相邻的表给出相差 8 倍多的数字,论文没有交叉引用或说明 |
| Open-Sora 2.0(arXiv:2503.09642,§3.1) | 「约 115K 个 token」(未说明具体分辨率和时长) | 「76K」(明确写出 5 秒、24fps、768px) | 两句话都在描述同一种配置(HunyuanVideo VAE 加 2×2 patch),前一句没有给出能让读者核实是否一致的完整条件 |
| Mochi 1(Genmo 官方 GitHub README 与官方博客,无 arXiv) | README:「128x smaller」 | 博客:「96x smaller」 | 两份官方材料描述同一个 AsymmVAE(8×8 空间、6× 时间、12 通道潜空间),总压缩倍数却不一致 |
第6章给过数值个数比的算法:空间压缩8×8、时间压缩6×,如果输入是3通道、潜空间是12通道,按这个公式算出来的压缩倍数应该是多少?这个结果和 Mochi 1 的哪一个官方数字对得上?
变式:如果 Mochi 1 的潜空间通道数换成9(两份材料实际写明的都是12),按同样的公式,压缩倍数会不会正好算出128?
动手算一遍就能看出,「96x」这个数字能用第6章的公式、配合两份材料都认可的「12通道潜空间」精确重新推出来,「128x」这个数字对不上——除非潜空间通道数不是12,但两份材料在这一点上又是一致的。这不代表「128x」一定是错的,可能是 Genmo 内部另有一种未公开的算法,只是这份材料本身没有给出这种算法是什么。
能被独立验证的说法
「96x」压缩倍数:给定两份材料都写明的空间、时间压缩率和通道数,用一条公开的公式就能重新推出这个数字。
暂时无法被独立验证的说法
「128x」压缩倍数:同样给定这几个条件,用同一条公式推不出这个数字,而材料没有给出另一套算法来解释它。
差别不在于哪个数字「更权威」——两份材料都来自 Genmo 官方——差别在于哪个数字这一刻能被读者自己的计算支持,哪个还不能。
22.2 本站自己推断出来的数,要怎么和论文说的分清楚?
| 推断内容 | 怎么来的 | 出自 |
|---|---|---|
| FLUX.1 是 19 层双流块加 38 层单流块 | 官方仓库设置为 gated,这个数是从三个社区镜像仓库的 config.json 里读到的,三份配置互相一致 | 第8章 |
| HunyuanVideo 的 patch 大小是 1×2×2 | 论文正文没有直接给出这个三元组,是本站结合 token 数公式反推出来的取值 | 第9、11章 |
| HunyuanVideo 3D RoPE 的 16:56:56 切分比例是怎么选出来的 | 论文 Table 2 给出了这三个数本身,但没有解释这个具体切分比例的选择依据 | 第11章 |
| Griffin-Lim「每步单调下降」的出处 | 本站没有拿到 1984 年原文(付费墙),只查到后续论文反复引用这个结论,正文写成「出处通常写成」 | 第16章 |
| RVQ 逐层误差按 ρi 衰减 | 这是本站给出的理想化假设模型,不是任何论文写出的公式 | 第18章 |
| DiT 论文的「Gflops」等同于 GMAC(乘加次数) | 论文没有明确说明这一点,是本站倒推出的判断 | 第7章 |
如果你在某篇新论文的 GitHub 仓库讨论区看到一条评论,说这个模型的某个超参数「应该是」某个值,这条信息如果被写进本站正文,应该归入 A、B、C 哪一档,还是本站压根不该用它?
变式:如果这条讨论区评论的作者正好是论文的某个作者本人,情况会不会不一样?
22.3 一个模型说自己「开源」,到底是哪种意思?
MaskGCT 的代码仓库标着 MIT 许可证,权重文件单独标着 CC-BY-NC-4.0。如果一家公司只看到「MIT」这三个字母,就认定这个模型可以直接商用,这个判断错在哪里?
答完再看
「开源」这个词经常被当成一个非黑即白的标签,实际上一个模型至少有代码和权重两个可以分别打许可证标签的部分,两者完全可以不一致。MaskGCT、F5-TTS、Spark-TTS 都是这个模式的例子:代码用宽松许可证(MIT 或 Apache-2.0),吸引开发者阅读和改造代码本身;权重用限制性许可证(CC-BY-NC 系列),把实际能直接拿来用的产出物锁在非商用范围内。只看仓库首页那一个许可证徽章,很容易漏掉这个拆分。
| 处境 | 例子 | 能不能商用 |
|---|---|---|
| 论文写了方法,权重从未放出 | VALL-E(Microsoft,「从未开源官方权重」) | 无从谈起,连自己跑一遍都做不到 |
| 权重放出来了,没有论文或技术报告 | GPT-SoVITS(无论文,代码 MIT)、Mochi 1(无 arXiv,仅技术博客) | 能用,但没有论文可以核实它为什么这样设计、数字是否可信 |
| 权重和论文都有,许可证明确非商用 | DiT(CC-BY-NC)、MaskGCT/F5-TTS 权重(CC-BY-NC 系)、FLUX.1[dev](非商用) | 能研究、能改,不能直接用在产品里 |
| 可以商用,但有地区或规模门槛 | HunyuanVideo(不适用于欧盟、英国、韩国;月活超1亿需单独申请)、SD3 Medium(年收入低于$1M才免费商用) | 取决于你是谁、在哪、多大规模,同一份许可证对不同读者的答案不一样 |
| 真正不设商用门槛 | Wan 2.1/2.2、Qwen3-TTS、Open-Sora 2.0(均为 Apache 2.0) | 可以直接商用,不问地区、不问规模 |
五档里的下限:VALL-E
论文写清楚了方法,官方权重从未放出,读者连自己跑一遍的资格都没有,「开源」在它身上只剩下「公开了做法」这一层意思。
五档里的上限:Wan 2.1/2.2
Apache 2.0,不问地区、不问规模,权重、代码、论文一应俱全,「开源」在它身上是字面意思,没有任何隐藏条款。
差别在:同一个「开源」标签,两端相距的不是程度上的一点差异,是「能不能用」这件事上的差别,中间三档都落在这两端之间的某个具体位置上。
这五档不是本站强行发明的分类,是 FACTS.md 清单里真实出现过的五种组合。同一个「开源」标签,能覆盖从「连权重都拿不到」到「随便怎么用都行」这么大的跨度,这也是为什么第21章的清单里,最后一行专门把「开源」两个字单独列出来,提醒读者不能只看这两个字就下结论。
第19章提到的 Spark-TTS,代码是 Apache-2.0,权重是 CC-BY-NC-SA-4.0。把它放进表3的五档分类里,应该归到哪一档,为什么不是「权重和论文都有,许可证明确非商用」这一档更靠上或更靠下的档位?
变式:如果 Spark-TTS 未来发布一个新版本,权重许可证改成「年收入低于$500万可免费商用」,它会从表3的哪一档移到哪一档?
22.4 打开一份许可证,该先看哪几行?
许可证原文通常有大段法律措辞,真正决定表3归档的,往往只是其中几个具体条款。核对一份新许可证时,按顺序找这几行,比从头读到尾更快。
| 顺序 | 要找的条款 | 找到之后回答什么 |
|---|---|---|
| 1 | 代码许可证和权重许可证是不是同一份文件 | 如果分开声明,两份都要分别核对,不能只看其中一份 |
| 2 | 有没有出现「非商用」「NC」「research only」「仅供研究」这类措辞 | 出现即归入表3第三档,不用再往下看 |
| 3 | 有没有地区排除条款(比如「不适用于欧盟、英国、韩国」)或规模门槛(月活跃用户数、年收入上限) | 出现即归入表3第四档,需要读者自己核对是否落在限制范围内 |
| 4 | 许可证名称是不是标准的 Apache 2.0、MIT 这类通用许可证,且没有第2、3步的附加条款 | 确认后才能归入表3第五档,真正不设商用门槛 |
如果一份许可证文件里同时出现了「Apache License 2.0」这个标准名称,又在附加条款里写着「月活跃用户超过1亿需要单独申请商用授权」,按表4的顺序,应该归到第几档?
变式:这种「主体许可证名称很宽松,但附加条款另有限制」的写法,和表3哪个真实例子的结构完全一样?
22.5 这个网站自己,有哪些做不到的事?
本站把大量数字标成 A 档,意思是读者能在实验室组件里亲手算出来。如果一个读者跟着本站,把 RTF 预算、token 数公式、加噪调度曲线全部亲手算了一遍,这是不是等于他已经亲手训练或者运行过一个真实的生成模型?
答完再看
本站实验室里能跑的,是把论文公式抠出来、在浏览器里用很小的规模重新实现一遍——比如几层的 RVQ、几十个点的加噪曲线、一个简化版的时间闪烁指标。这些确实是论文数学的真实体现,不是编造,但它们的规模、数据、训练时长,和 HunyuanVideo、CosyVoice 2 这些真实模型实际训练时面对的规模,完全不是一个量级。会算公式和跑过真实规模的训练,是两种不同的经验,前者是本站能给的,后者不是。
把这件事摊开说清楚,比让读者自己误会更负责任。本站的材料截止到 2026年9月18日,之后发布的模型和论文不在覆盖范围内;正文里标为 B 档的数字,全部来自论文作者自己的实验,本站没有重新训练任何一个真实规模的模型去复现;GitHub 星标和 HuggingFace 下载量这类热度数字,精度和口径都在第21章说明过,会随时间变化,读到这句话的时候未必还准确。
本站能给的
论文公式本身的推导过程、能在小规模上亲手复现的数学关系、多篇论文之间交叉核对出来的矛盾和推断。
本站给不了的
真实规模训练时会遇到的显存不够、数据脏、调参调不出来这类只有动手跑过大规模训练才会撞到的麻烦,本站从未训练过一个真实规模的生成模型。
差别在:一边是「这个数学关系是不是成立」,能在浏览器里验证;另一边是「把这个数学关系真的搬进一次训练里会遇到什么」,这件事本站没有做过,也没有假装做过。
如果本站某处 A 档结论后来被发现和某篇论文的真实实现有出入(比如浏览器里的简化版本漏掉了论文原文的一个细节),这处出入应该归咎于「A档标注错了」还是「A档这个分级本身失效了」?
变式:如果这处出入的根源换成论文原文本身写错了公式,跟简化实现漏掉细节完全无关,这时候「谁错了」这个问题的答案会不会不一样?
读的时候要小心
关于本章数字的来路
CogVideoX Table 5/6 的两个数字、Open-Sora 2.0 的115K与76K、Mochi 1 官方 GitHub README 与官方博客给出的两个压缩倍数,全部是本站直接从原始材料里摘录的原文数字,没有第三方复现或解释这几处矛盾,本章也没有替代论文或官方材料去下结论。表3、表4 涉及的许可证条款均来自 FACTS.md 在 2026-09-18 抓取到的信息,许可证文本本身可能在此后被模型发布方修改。
这一层要加什么:诚实声明与许可证检查清单
为什么现在才加它:前21级要么在实现一个模型的机制,要么在实现一把评测的尺子,没有一级要求你反过来检查「这个东西本身能不能被信任、能不能被使用」。这一级要把这套检查流程写成一个能反复调用的函数,拿两个真实模型的真实许可证条款去跑一遍。
难点:license_check 里 tier 的判断顺序不能打乱——必须先查有没有权重、再查有没有论文,最后才查许可证条款的具体限制,因为「非商用」和「地区受限但可商用」这两档的判断,前提是权重和论文都已经确认存在。如果颠倒顺序,比如先查地区限制、后发现权重根本没放出来,会把一个连权重都拿不到的模型误判成某种「受限商用」,比它实际的处境更宽松。另一个难点是 region_or_scale_restricted 这个字段——它是一个需要读者自己去读许可证原文才能填的布尔值,不是能从模型名字或者机构名字猜出来的,表4给的四步检查顺序,就是用来把这个字段填对的具体流程。
自己验:把 hunyuanvideo 和 wan21 两个字典的四个字段和 FACTS.md 模型清单里的原始许可证描述逐条核对,应该完全对得上;跑一遍 license_check,两者应该输出不同的档位(4 和 5),不是同一个结论;再手动把 wan21 的 region_or_scale_restricted 改成 True,重新跑一遍,输出应该从 5 变成 4——同一个函数,只改一个字段,结论就会跟着改变,这正是许可证检查不能只看「是不是 Apache 2.0」这几个字、必须逐条核对具体条款的原因。
留给你的问题
这一章把三处数字矛盾、六处本站自己的推断、五档「开源」的真实处境、以及本站自己做不到的事,一次性摊开了。下一章是全站最后一章,如果让你现在就去想它会怎么收尾,你会更期待它把前22章的道理拼成一张什么样的网,还是更期待它诚实地告诉你,拼完这张网之后,你手里到底拿到了什么、还缺什么?
不看材料,写下这一章的三堵墙
第一堵:论文内部的数字矛盾不会自己消失,CogVideoX 的700k对82k、Open-Sora 2.0 的115K对76K、Mochi 1 的128x对96x,三处都没有官方解释,读者只能原样记录,不能替材料做它没做的解释工作。第二堵:「开源」这个词覆盖的现实跨度极大,从连权重都拿不到,到真正不设任何商用门槛,中间还有权重无论文、许可证非商用、地区规模受限这几档,只看一个「开源」标签会漏掉真正决定能不能用的那几行条款。第三堵:本站的A档可复算只证明了论文的数学关系站得住,不等于本站或读者真的训练、运行过一个真实规模的生成模型,这堵墙不是本站的疏忽,是这门课这种讲法从一开始就有的边界。
第23章 终极闯关
开篇故事第 23 集 · 那杯茶,靳工端在半空忘了放下这模型也太真实了
屏幕中间弹出一行确认框,要求手动敲下「真卡确认」四个字才能继续——这是回声内部脚本从第一天就锁死的一道关卡,专门用来拦一次手滑。沈砚的手指悬在键盘上方,多停了两秒才敲下去回车,这是回声复现的第二百天,也是全流程头一次不在《潜空》里试跑,而是直接压上机房那几十块真实的显卡。他摸了摸胸口那张门禁卡——两百天前他连这张卡都还没办下来,此刻卡面已经被摸得快看不清编号,两百天前那个还没批下来的账号,此刻是唯一能签下这次真卡授权的那一个。
走廊那扇玻璃门斜斜漏进来一点光,正好扫过门上贴着的那张纸条——“在模拟里炸一万次,在真卡上一次都别。”过去两百天,几乎所有惊险的时刻都被这句话挡在了《潜空》里面,改错一个系数,最多也就是弹出一行红字,重跑一次了事;今晚是头一次,这句话要在真正的硬件上兑现,炸了就是真的炸了。
提示词只有一句话,沈砚敲完回车,屏幕左边先跳出一张静止的画面,接着那张画面开始动,几秒钟后画面右下角叠上一行字幕,配的是刚生成出来的嗓音,跟着字幕逐块往外吐,中间没有一次卡顿,进度条从头到尾没有回退过一格。这几步以前分别在三个不同的实验室组件里各自跑通过,今晚是它们头一次在同一条流水线里首尾接上,没有人从中间插手改过一行参数。
系统提示本次压测判定:图像、视频、语音三条线在同一次真实硬件运行里首尾相接,全部检查项通过,这是两百天来第一次不经过《潜空》模拟环境的完整贯通。
那天夜里没有人提议开什么,几个人只是靠着机柜坐了一会儿,谁都没说话。复现之鬼在语音那条线的评测表格里填到最后一栏时停住了,那一栏问的是「这份分数可信吗」,她抬头问了一句:“有没有一种评测方法,压根不用另一个模型当裁判?”靳工端着杯子,没有立刻答上来,杯子在半空停了很久,忘了往下放——这是他从没在沈砚面前卡住过的一件事,四十五篇论文里没有一篇正面给出这个问题的答案,检索过后,仍然是这样,这一次该归的类是真未解,不是材料没写,是没人写过。他最后只说了一句:“这道题我答不上来,材料范围里,眼下也没有人能答得上来。”
评测要是离不开另一个模型当裁判,这个「可信」两个字到底从哪来
1L复现之鬼我刚才问出口才发现,这四十五篇论文里翻遍了也没人正面答过这道题,FID 靠一个网络,WER 靠一个 ASR,分数从头到尾都在借别人的眼睛。
2L沈砚不对,不是所有裁判都不可信,问题不是借了别人的眼睛,是这双眼睛本身有没有被反过来验证过,这才是真正没人做过的那一半。
3L炼丹废人说句丧气话,我倒觉得这题可能压根没有答案,不借助任何外部信号去评价生成内容,这事听着就有点像自己给自己打分。
4L显存刺客丧气归丧气,这题答不上来不耽误明天发布,只是发布之后这句「可信」两个字,咱们自己心里得先有数。
复现之鬼把那一栏重新空了出来,没有勉强填一个不确定的数字进去糊弄过去,鼠标停在那个空格上方,下面就是明天一早要交的复现报告——那份要贴到官网首页的说明里,评测那一节眼下还留着一整块没写完,谁都还没想好,该怎么写才算诚实。
系统结算 · 本章考点
- 这次用来收尾的全流程真卡演练,靠的不是新东西,是第 3 级到第 22 级这些老部件头一次真的接在一起跑完
- 判断一个计算能不能跳步,关键只有一条:目标状态能不能只靠已经存在的已知量直接算出来,第3章的闭式解和第20章的流式因果性,答的是同一个问题
- 第22级留下的那条验证标准这次又用上了一遍:同一个函数,只改一个字段,档位判定就会跟着改变,昨天用在许可证上,今天照样成立
- 22级台阶里,第5级DDIM把步数设成1000会退化回第4级的行为,第20级把分块长度设成整段会退化回不分块的输出,新自由度必须能退回旧行为
- 「真未解」和「对你而言未知」不是一回事:前者是检索过后仍找不到答案,后者只是材料没写,评测要不要依赖外部裁判模型这道题,这次被明确标成了前一种
- 这门课能亲手验证的,是论文背后的数学关系站不站得住,不是真的训练、运行过一个真实规模的生成模型,这条边界从第22章开始就没打算藏着
前22章,你在22个不同的地方分别撞过墙——每一次都是先带着一个朴素方案往前走,撞上一堵具体的墙,再看着这堵墙逼出一个新部件。这一章不再引入任何新部件,只问一件事:这22面墙、22个部件,真的只是各自独立地立在原地,还是它们之间早就悄悄连着一张网,只是没有人把线牵出来给你看过。
学完这一章你应该能做到
- 把两个相隔很远的章节的道理放在一起,回答一个任何单独一章都答不完整的问题
- 对「扩散模型注定被流匹配取代」这类判断性的说法,独立组织出正反两边站得住的论据
- 区分一个问题是「这个领域至今没有公认答案」,还是「答案存在,只是本站没有给」
- 不看材料,说出前22级建造台阶各自加了什么、各自的验证标准是什么
- 说清本站能给你的是什么、不能给你的是什么,以及下一步该往哪里走
23.1 前22章的道理,是22个抽屉,还是同一张网?
第3章证明了前向加噪过程存在闭式解——不管要跳到第几步,一次乘加就能直接算出结果,不需要真的一步步模拟。第20章却花了一整节去拆三处因果性麻烦,只是为了让生成过程能够流式进行。如果你只读过其中一章,会不会觉得这两件事完全不相关?
答完再看
只读第3章,你会记住「前向过程可以闭式跳步」这个具体结论;只读第20章,你会记住「生成要流式很难,卡在三处因果性」这个具体结论。两条结论字面上确实在说不同的事,但支撑它们的是同一条更深的原理:一个计算能不能被跳步或者提前完成,取决于它需要的信息是不是已经全部就位。前向加噪的每一步都只需要原图和一份采样噪声,两者都是已知量;生成过程的每一步经常需要「还没生成出来的东西」,这才是跳步在一边行得通、在另一边行不通的真正原因。前22章有大量这样的连接,这一节要练的就是主动把它们找出来的能力。
第3章的闭式解和第20章的流式因果性,都在回答「什么样的计算能被跳步」这个问题。用一句话说清,决定「能不能跳步」的关键条件是什么?
变式:按这条原理反过来推,DDIM(第5章)能跳步采样、把1000步压缩到50步,这是不是说明DDIM采样这个方向上,「目标状态」也只需要已知量就能算出来?
第2章的 VAE 把像素压缩进潜空间,靠一个训练出来的解码器把细节找回来;第15章的 Mel 频谱把相位直接丢掉,靠 Griffin-Lim 或者一个声码器把相位找回来。两条「压缩再找回来」的路径,找回信息的那一步是同一类机制吗?
变式:如果给 Mel 频谱也配一个和提取过程联合训练出来的「相位解码器」,这条设计思路会不会让 Mel 频谱这条线更接近 VAE 那条线?
第6章说,潜空间的压缩倍数 f 是在真实图像上跑出来的、能提前用 FID 衡量优劣的一个经验选择。第15章却说,Mel 频谱真正的价值不在于把每一帧的数值个数压小了几倍,而在于把时间轴上要处理的步数压小了两个多数量级。这两种「压缩带来的收益」,是同一类收益吗?
变式:如果把第6章 VAE 的评价标准(压缩倍数换 FID)直接套到 Mel 频谱头上,按这套标准该怎么选 Mel 滤波器组的档数?这样选出来的答案,会不会错过第15章真正想强调的那个收益?
第17章的 MAS 靠约束对齐搜索的路径空间,让「跳过某个音素」在结构上直接变得不可能;第19章的 VALL-E 里,自回归采样依然会漏字、重复,这个问题第19章章末说和第17章是「同一堵墙换了张面孔」。既然 MAS 已经证明「把结构性约束写进搜索过程」这条路可行,为什么不能把同一招直接搬到 VALL-E 的自回归解码上?
变式:如果给 VALL-E 也预先提供一个固定长度的目标序列(比如提前用某种方式定好要生成多少个token),MAS这条思路是不是就有可能被借用过来?
第7章证明了 token 数一旦超过 (2+r)d 这个门槛,注意力的二次项就会从配角变成账单大头;第12章看到视频的 token 数轻易越过这个门槛。第7章还提过,图切成方块的 patch 越大、token 数越少。如果不改动 VAE 的时空压缩率,只是把 patch 调大,能不能把一个视频模型重新拉回到门槛以下?
变式:第11章提过 patch 化本身会丢失 patch 内部的精细空间信息,如果一味调大 patch 去压 token 数,这笔账最终会不会转移到第11章说的那种代价上?
只读一章时看到的
一个具体的、局部的结论——比如「前向过程能闭式跳步」,或者「流式生成撞三处因果性」,各自看起来是孤立的技术细节。
跨章对照之后看到的
支撑这些局部结论的,是同一条更深的原理反复出现——计算能不能被跳步或提前完成,取决于它需要的信息是不是已经全部就位。
差别在:局部结论是这门课教给你的「答案」,跨章对照才能看到这些答案背后共享的「方法」,本节5道综合题练的就是主动找出这层共享结构的能力。
23.2 扩散模型注定被流匹配取代吗?
接下来这场答辩的题目是「扩散模型注定被流匹配取代」。按照前22章一路的写法——从不说谁是SOTA、有争议的判断放进答辩框而不是正文结论——你觉得本站会不会在这场答辩里明确站到某一边?
答完再看
第21章整整一节讲过本站为什么不说谁是SOTA,这个态度延续到这里。这场答辩考的不是「本站认为哪边对」,是你能不能调用前22章攒下的证据,替某一边撑起一套站得住的论证,或者反过来把对方的论证拆穿。从「某个技术出现的次数多」去猜本站的立场,本身就是第21章刚刚拆穿过的那种误读——出现频率不等于结论倾向。
答辩:如果我是审稿人
第8章证明了流匹配的训练目标更简单,边缘化流在数据分布满足特定条件时可以走直线;第19、20章里新出现的语音生成系统,相当一部分用的是流匹配或者类似流匹配的连续化路线。这是不是已经足够的证据,可以断定扩散模型会被流匹配取代?
参考防守(先自己组织语言再看)
支持这个判断的一方能拿出的证据确实存在:训练目标更简单、采样路径在理想情况下更直、新系统选择流匹配的比例看起来在上升。但反对这个判断的一方同样有站得住的论据。第一,第8章说得很清楚,边缘化流走不走直线,是由数据分布本身的性质决定的,不是流匹配这套框架单方面能保证的,换一批数据分布,这个优势不一定成立。第二,DDPM式的扩散模型和流匹配在数学上高度相关,都可以写成同一族随机或确定性微分方程的特例,把两者描述成互相取代的竞争对手,可能低估了它们共享的理论根基。第三,语音这条线里,VALL-E式的离散token自回归路线,走的完全是第三条路,既不是扩散也不是流匹配,「取代」这个说法预设了竞争只发生在这两者之间,忽略了还有其他路线同样活跃。真正站得住的结论大概是:流匹配在部分场景里确实降低了工程复杂度,但材料范围内找不到证据支持「注定」这个强度的判断。
支持「会被取代」的论据
训练目标更简单;边缘化流在合适的数据分布下能走直线;第19、20章新出现的语音系统里,选流匹配路线的比例看起来在上升。
反对「注定被取代」的论据
走不走直线依赖数据分布,不是流匹配单方面保证的;两者在数学上同属一族微分方程;VALL-E式离散token自回归是完全独立的第三条路。
差别在:两栏都标成alt而不是ok/bad,是因为这场答辩里没有哪一栏代表本站认定的「正确答案」,两栏证据都站得住,站队与否留给答题的人自己决定。
23.3 还没有答案的问题,和只是本站没写的问题,是同一回事吗?
前22章一共留下过大约20处研究课题,绝大多数标的都是「对你而言未知」——答案存在,只是材料没有给。只有一处,第12章的注意力头部专化,标成了「真未解」。这一节要重新审视其中两处,把它们从「对你而言未知」升级为「真未解」,并且说清升级的理由,不是随手改一个标签。
对你而言未知
答案存在,只是不在这份材料里——你知道该去哪儿查(专门的后续论文、原始文献、内部实验记录),只是材料没有替你查到。
真未解
材料明说是开放问题,或者检索过能找到的相关工作之后,确实找不到任何一份声称已经解决它的出处——拿不出「谁已经解决了」这个具体指向。
差别在:前者是材料的边界,后者是这个领域此刻的边界,把前者标成后者,会让读者误以为自己已经站在了学科前沿,其实只是站在了这份材料的边界上。
真未解RVQ 残差量化器的层级分工,是结构必然还是训练出来的
第18章第一次遇到这个问题时,标成了「对你而言未知」——当时的判断是答案存在,只是材料没有给出。写到这里,重新检索过全部45篇材料里涉及残差量化设计的几篇,没有一篇给出理论证明:换一种训练目标或者损失函数权重分配,第一层是否依然会自动学到说话人身份这类粗粒度信息。EnCodec、SoundStream、DAC 三篇奠基性论文全部只是训练完之后观察到这个现象,没有一篇尝试过反向验证——换个训练目标,这个分工还在不在。这不再像是某一篇论文的疏漏,更像是这个具体问题此刻还没有人正式回答过,这次把它升级为真未解。
先做这一步:找一篇专门分析 RVQ 训练动力学或者残差量化收敛性质的后续工作,看有没有人从优化过程本身、而不是训练结果去解释这个分工;如果检索不到,回到第18章的实验室组件,自己动手换一种损失函数权重,跑一遍消融实验,看层级分工的现象是否还会出现在同样靠前的层。
上面这处升级,真正让它从「对你而言未知」变成「真未解」的关键新证据是什么?
变式:如果之后确实有人发表了一篇专门证明这个分工机制的论文,这一处标注该改回「对你而言未知」还是留在「真未解」?
真未解HunyuanVideo 3D RoPE 的 16:56:56 切分比例,是怎么选出来的
第11章标注过这个问题是「对你而言未知」。写到这里,检索过全部45篇材料里涉及3D位置编码的几篇,没有一篇给出这个具体切分比例的选择依据——是网格搜索出来的经验值,还是某种理论推导的结果,材料给不出答案,检索范围内也没有找到给出这个答案的后续工作。同一份材料读到这里,「材料没写」和「没有人写过」之间的界限,这一次更倾向于后者,所以升级为真未解。
先做这一步:在第11章的3D RoPE实验室组件里,把切分比例从16:56:56换成接近均匀的切分(比如24:52:52),跑一遍第11章buildstep给出的交换扰动实验,看幅度的变化规律是否和16:56:56有系统性差异——如果差异不明显,至少能说明这个具体比例不是唯一能工作的选择。
RVQ分工和RoPE切分比例这两处升级,走的是同一套判断流程吗?
变式:这套判断流程能不能用来处理第13、14章那几处还标着「对你而言未知」的研究课题?
真未解有没有一种不依赖外部裁判模型的生成质量评测方法
第21章拆开的 FID、VBench、WER、SIM、MOS,无一例外都依赖一个具体的预训练网络、一个具体的 ASR 模型,或者一群具体的评分者。这不是某一个指标设计得不够好,是当前整个评测体系共享的同一个结构——用另一个模型或者另一群人的判断,去代替一个不依赖任何裁判的独立标准。有没有可能存在一种只从生成内容本身的统计性质出发、不需要借助外部裁判的评测方法,这是一个公开的问题,材料范围内没有看到任何一篇论文声称已经解决了它。
先做这一步:检索「reference-free」或者「no-reference」这类关键词下的生成质量评测方法,看目前最接近这个目标的尝试卡在哪一步——是还没有人给出「不依赖裁判」这件事本身的可操作定义,还是已经有雏形,只是效果暂时不够好。
「有没有不依赖外部裁判的评测方法」这一条,为什么标的是「真未解」而不是「对你而言未知」?
变式:如果某天有一篇论文声称解决了这个问题,这一条会不会需要重新降级为「对你而言未知」甚至直接从研究课题清单里拿掉?
对你而言未知CosyVoice 2 的 dlm、dfm、dvoc 具体取值,论文没有公开报告过
第20章的式11把首包延迟拆成了三项,每一项的具体毫秒数——语言模型吐一个token要多久、flow matching渲染一帧要多久、声码器合成一帧要多久——CosyVoice2论文本身没有给出这三个数字的实测值,只给出了整体架构描述。这不代表这三个数字测不出来,团队大概率在内部测过,只是没有写进这篇论文,材料的边界卡在「没有公开」这一步,不是「测不出来」这一步。
先做这一步:如果能拿到 CosyVoice 2 的开源权重和推理代码,在同一块 GPU 上分别给三个环节单独计时,把式11里悬空的三个变量填成具体数字。
CosyVoice2这三个未公开的耗时数字,为什么标的是「对你而言未知」而不是「真未解」?
变式:如果连CosyVoice2团队自己都从未测过这三个数字,这一条的标注该不该改成「真未解」?
对你而言未知Mochi 1 的 128x 和 96x,Genmo 自己从没有公开解释过
第22章摆出了这处矛盾——官方 GitHub README 说128x,官方博客说96x,本站用第6章的公式能重新推出96x,推不出128x。这不代表128x一定是笔误,Genmo内部完全可能有另一套没有公开的计算口径,只是两份官方材料都没有解释这处不一致。这个问题的答案很可能就摆在Genmo工程团队某一次内部讨论里,只是没有变成任何一份公开材料的一部分。
先做这一步:去 Mochi 1 的 GitHub 仓库翻一遍两个数字各自第一次出现的提交记录和相关讨论,看它们是不是在不同时间点、由不同的人写下的,也许能从版本历史里找到线索。
如果你读到一篇新论文,作者在讨论部分写道「这个现象背后的机制,我们把它留给未来的工作」,这句话本身,能不能直接让你把对应的问题标成「真未解」?
变式:如果这篇论文发表于很多年前,而你检索的时间范围又足够广,这种情况下「找不到后续工作回答」这件事的说服力,会不会比论文刚发表不久时更强?
23.4 22级台阶,你亲手验过哪几级?
| 级 | 加了什么 | 怎么验证 |
|---|---|---|
| 1 | 一维分布采样 | 直方图应现双峰,误差小于0.05 |
| 2 | 朴素编码器证明模糊 | 均值≈0但密度避开真实数据聚集区 |
| 3 | 前向加噪闭式解 | 单步公式与千步模拟结果一致 |
| 4 | 解析最优去噪器+DDPM循环 | 去噪结果与条件期望对齐到小数点后六位 |
| 5 | DDIM跳步采样器 | 步数翻倍误差减半,η=0时确定性输出 |
| 6 | 编码器与解码器 | 瓶颈降到1维时两个峰塌缩成一团 |
| 7 | 去噪器换成注意力 | 打乱token顺序,无位置编码输出不变 |
| 8 | 流匹配速度场加CFG | 数据方差为0时训练路径误差趋近浮点下限 |
| 9 | 数据升成带时间轴序列 | 无时间注意力时相邻帧相关系数趋近0 |
| 10 | 时间维压缩 | 慢速运动重建误差远小于快速运动 |
| 11 | patch化与三维位置编码 | 交换越远的patch,扰动幅度越大 |
表1里,哪一级的验证标准是「打乱输入顺序,不加某个部件时输出完全不变,加了之后输出明显变化」?
变式:表1里还有哪一级的验证标准,用的也是「加了某个部件前后对比」这同一种实验设计?
| 级 | 加了什么 | 怎么验证 |
|---|---|---|
| 12 | 稀疏注意力窗口 | 窗口覆盖全部token时与全注意力逐位相同 |
| 13 | 首帧条件注入 | t=0时输出等于输入,t越大偏离越大 |
| 14 | 数据换成波形 | 小幅度信号用μ-law误差明显更小 |
| 15 | STFT与Mel前端 | 440Hz正弦波能量集中在对应频率bin |
| 16 | Griffin-Lim反变换 | 一致谱误差降到浮点精度,不一致谱有平台期 |
| 17 | 时长预测与单调对齐 | 输出帧数精确等于时长之和,MAS覆盖全部音素 |
| 18 | k-means码本与残差量化 | 截断前k层与切片求和逐位相等 |
| 19 | 自回归声学token循环 | 温度0确定性输出,温度升高出现重复兜圈 |
| 20 | 分块流式输出 | chunk等于全长时输出逐位相同 |
| 21 | 简化版时间闪烁指标 | 静止视频得分趋近满分,真实运动得分反而更差 |
| 22 | 诚实声明与许可证检查 | 同一函数换一个字段,档位判定跟着改变 |
表2里第12级的验证标准是「窗口覆盖全部token时,输出必须和全注意力逐位相同」,这种「把新部件的某个参数调到一个极限值,让它退化成没加这个部件之前的样子」的验证方式,在表1、表2里还能找到至少两个使用同样思路的例子吗?
变式:这种「退化验证」为什么比单纯检查新部件本身「有没有效果」更能说明这个部件的实现是对的?
23.5 关掉这个网站,你能做什么,还不能做什么?
如果你认真做完了前22章的全部实验室组件和22级建造台阶,你觉得这时候的你,已经具备了从零训练一个像 HunyuanVideo 或者 CosyVoice 2 这样规模的真实生成模型的能力了吗?
答完再看
第22章已经把这件事说得很直接:本站的 A 档验证的是论文数学关系本身站不站得住,用的是很小的规模、干净的合成数据、能在浏览器里跑完的规模。真实规模训练要面对的显存不够用、数据里混进的脏样本、调参调不出预期效果这些具体的麻烦,本站从未让你亲手撞上过,因为本站自己也从未训练过一个真实规模的生成模型。原理和规模之间,隔着一段只有真正动手训练才能积累的经验,这段经验,这门课给不了。
这不是这一章才第一次承认这件事——第22章已经把它写成了正式的一节。这一章要做的是把它放进最后的位置,和前22章积累起来的全部信心放在一起看:你现在能推导 RTF、能亲手实现一版简化的评测指标、能一眼看出一份许可证里最该核对的几行,这些都是真的能力,不是错觉;但「已经能独立训练一个真实规模的生成模型」,是另一种能力,这门课没有给到这一步。
这门课确实给了你的
看懂一篇生成模型论文在讲什么、亲手推导它的核心公式、亲手验证它数学上是不是自洽、看出评测数字和许可证条款背后没说清楚的地方。
这门课没有给你的
真实规模训练时踩过显存的坑、清洗过脏数据、调过一次训练调了几个星期还没收敛的经验——这些经验只能靠真的去跑一次训练获得,不能靠读懂公式获得。
差别在:前者是「理解一件事为什么成立」,后者是「亲手把一件事做成」,两者经常被默认成同一件事的两个阶段,实际上是需要分别积累的两种经验,这门课只覆盖了前者。
如果读完这一章,你决定要往「真的训练一个模型」这个方向迈出第一步,按第22章表3的五档「开源」现实,你会优先挑哪一档里的模型去尝试,为什么?
变式:如果你的目标换成把某个已有模型部署进一个商业产品,跟从零训练一个新模型完全是两件事,这时候第22章表4的许可证检查清单里,哪一步会变得比选训练起点时更重要?
留给你的问题
22级建造台阶,22道亲手验证过的判据,5道跨章节的综合追问,5个标清楚了「真未解」还是「对你而言未知」的研究课题——这些加在一起,构成了这门课能给你的全部。合上这个网站之后,你会先去找一个第22章表3里真正不设商用门槛的模型,动手训练点什么,还是会先去追一追这一章留下的某个「真未解」,看看自己能不能往前推进一点点?两条路都是往前走,选哪一条,从现在开始就是你自己的账了。
不看材料,写下这一章的三堵墙
第一堵:前22章的道理不是22个互不相干的抽屉,是同一套「朴素方案先走、撞墙、逼出部件」的方法在22个具体问题上的重复应用,相隔很远的两章之间,经常能找到彼此呼应的结构,只是没有人主动去找,就不会自己浮现出来。第二堵:「真未解」和「对你而言未知」是两种完全不同的边界,一种是材料的边界,一种是整个领域此刻的边界,把前者错标成后者,会让人误以为自己站在了学科前沿。第三堵:全部亲手验证过的22级台阶,证明的是论文背后的数学关系站得住,不等于亲手训练、运行过一个真实规模的生成模型,这堵墙不是这门课的疏漏,是这种讲法从一开始就有、也一直没有回避过的边界。