Reiner Pope – LLM 训练与服务背后的数学
Reiner Pope – The math behind how LLMs are trained and served
嘉宾:Reiner Pope
全文文稿 · 按对话顺序保留完整内容,深入理解上下文。
§01批量大小如何影响 token 成本与速度
Dwarkesh Patel
今天我采访的是 Reiner Pope,他是新芯片初创公司 MatX 的 CEO。此前他在 Google 从事 TPU 架构以及许多其他工作。这次的形式和我平时的访谈很不一样,我们要做一场黑板讲解。我们马上就要开始了。事实上,我们专门为这种形式搭建了整间新演播室,所以很高兴能由你来为它揭幕。
我们要谈模型架构、机器学习基础设施,以及许多其他内容。我觉得这个话题很重要,因为一旦理解了训练和推理如何在集群中运行,很多事情就会开始变得明白:AI 为什么会是现在这个样子,AI 架构为何如此设计,API 定价为何如此,以及从根本上说 AI 进步为何如此。要理解这些,需要掌握细节;而理解细节需要一块黑板。Reiner,非常感谢你来做这次讲解。
Reiner Pope
很高兴来到这里。
Dwarkesh Patel
先完整披露一下:我是 MatX 的天使投资人,但这和这期播客无关。Reiner,先从这个问题开始。有几家公司,比如 Claude、Codex 和 Cursor,提供类似 Fast Mode 的模式:价格是 6 倍,token 输出速度是 2.5 倍。从机制上说,我很好奇这里发生了什么。为什么多付钱就能降低延迟?
第二,能不能继续提高?付 100 倍的价格,能不能让速度快得多?第三,反过来行不行?能否推出类似 Claude Code“慢速模式”的选项,如果你愿意等上几分钟,就能享受更低价格?也许这能帮助我们引出你这场讲解要分析的内容。
Reiner Pope
先稍微剧透一下结论:最主要的影响因素是批量大小。接下来我们会精确量化它的表现,以及它对延迟和成本的影响。还有另一个因素,可以叫作投机解码或多 token 预测。之后也许会再谈;但我们先讨论批量大小。
我想先介绍两条分析原则。第一,我们会用屋顶线分析来研究如何在芯片集群上运行 Transformer 模型。我们选用 Blackwell NVL72 集群,也就是一架装有 72 块 GPU 的机柜。屋顶线分析关注的是内存带宽和计算性能。另一边,我们只看模型的两个简单因素:处理权重所需的时间,以及处理上下文,也就是 KV 缓存,所需的时间。
我们开始吧。我们要尝试估算某种给定形态的推理需要多长时间。我们的模型并不完美,无法精确预测耗时,所以会做近似。我们会说,耗时必须大于或等于某个量。要考虑两个方面:执行内存读取所花的时间,以及执行计算所花的时间。结果会发现,即便使用这么简单的模型,也能得到很强的预测能力。
先逐项看计算要花多少时间。计算中实际有两件事要做:乘上所有活跃参数,然后对注意力机制做一些运算。要乘上所有活跃参数,我们有一个正在运行的批量大小,以及模型中的活跃参数数量。接着把它除以计算吞吐量,也就是芯片的 FLOPs。这属于硬件因素。
这涵盖了所有权重矩阵乘法的计算时间。这里有个小小的注意事项:我们忽略了注意力计算所需的时间,但通常和这部分相比很小,所以先不考虑。
Dwarkesh Patel
我会不时打断,问一些很基础的问题,或者澄清一些基本概念。对听众来说,你不是一次只为一个用户服务。这里的 batch 指同时服务许多不同的用户,把他们组成一个批次。
Reiner Pope
我可以先稍微解释一下为什么要做批处理。我们会清楚看到批处理为什么是一种非常有效的优化。结果会发现,如果不把许多用户放在同一批次里,成本和经济性可能比批量处理差一千倍。我们会非常直观地看到这一点。
然后是活跃参数的数量。比如看一个 DeepSeek 模型,DeepSeek V3 大约有 370 亿个活跃参数,总参数量则是 7000 亿。我们关注的只是每生成一个 AI token 时实际会激活的那些参数。
我们是在对计算性能建模。我会一直写等号,但这些情况下都可以把这个时间理解为至少这么长,实际还可能有一些被我们忽略的项。
再看内存方面,需要对内存做什么?我们要读取全部权重,所以读取的是总参数量对应的数据,而不只是活跃参数。这里有权重读取时间,此外还有 KV 缓存读取时间。后者实际上取决于批量大小。批次中的每个元素都要读取与其完整上下文长度相当的 token 数据;每个 token 占用一定字节数,这是一个模型参数。
Dwarkesh Patel
我们稍微退一步,快速解释一下 KV 缓存是什么。
Reiner Pope
执行一次前向传播时……我来画一下自回归推理是怎么工作的。这里说的是解码阶段。如果我有一串文本 token……我画的是一个张量,因为 token 最终会表示成某个嵌入维度上的张量。这个方向表示序列长度。
解码时的工作是:把每个 token 依次送过许多层中的大量矩阵乘法。一般来说,我需要对这里所有 token 都做这项工作。但解码的一步只会在这里额外生成这一个 token。
我接下来要对整个模型里的所有权重矩阵做乘法,完成一次完整的前向传播。但之后还有注意力机制:这个 token 会查看所有先前的 token。具体看什么?它查看的是模型为这些 token 生成的某种内部表示,我们称为 KV 缓存。这个 token 关注全部历史 token 的过程就是注意力。它主要受内存读取限制,而不是矩阵乘法。
所以这里画出了我们要读取的内存量,然后把它除以内存带宽,也就是每秒能传输的字节数。事实上,这些方程已经足以让我们画出一些拟合曲线。我们想看的变量是批量大小的影响;另外还有上下文长度的影响,我们会单独画图。我们刚才说过,批量大小会带来延迟与成本之间的权衡。
我们把曲线画出来。我认为主要看两张图就够了。先画批量大小与耗时的关系。看这个形状,它是一个和另一个项相加后取最大值的结果。我们逐项看看这些项及其变化方式:计算时间和内存时间,以及它们如何呈现。
先看计算时间。它与批量大小严格成线性关系,没有偏移量,所以是一条这样的曲线。这是计算时间 t。在内存一侧,有一部分是常数,构成一个基础偏移量,也就是权重读取。最后还有这一项 KV 读取,它大致随批量大小线性增长,所以曲线会是这样。把这部分和这部分相加,至少先画出它们的和:两项内存耗时合在一起会沿着这样一条弯曲的斜线变化。整体耗时则是这两条曲线的最大值,我会把它画粗一点。
这代表什么?这是延迟图。如果我增加批量大小,起初延迟不会有太明显的变化,因此这里存在一个延迟下限。这已经部分回答了最开始的问题。对于某种硬件配置——我们也可以讨论改变硬件配置的情况——延迟都有下限。原因很简单:必须把全部参数从内存读入芯片,这需要一定时间。即使把内存带宽全部用满,也不可能比这更快。
Dwarkesh Patel
看起来你画的计算时间斜率,以及 KV 增长的斜率——还有 KV 对内存时间的影响——
Reiner Pope
如果这条线在上面或下面呢?
Dwarkesh Patel
对,这种情况一定成立吗?如果始终如此,批量越大,计算就总是比 KV 占主导;这似乎意味着,只要批量足够大,内存就可能永远不会成为问题。
Reiner Pope
这对上下文长度非常敏感,所以我们稍后再回来讨论。上下文长度越长,KV 读取时间就会不断增加,这会让系统从计算受限转变为内存受限。
Dwarkesh Patel
斜率恰好与计算时间的斜率相同,这一点有什么特别重要的意义吗?
Reiner Pope
出现平衡点,说明你把事情做得恰到好处。当上下文长度恰好让两条斜率相同时,就表示系统既受内存限制,也受计算限制,而且两者程度相当。这是很理想的状态。
Dwarkesh Patel
这是个很简单的代数问题。假设最优上下文长度是 100K,而你把它提高到 200K。MFU 会不会降到 50%?稍微超出最优上下文长度范围,也就是这个“金发姑娘区间”,会不会对 MFU 产生巨大影响?
Reiner Pope
是的,按这里的模型确实如此。有一个关键点:我把内存读取建模成与上下文长度线性相关。这取决于模型架构。对于所有采用密集注意力的模型都是如此。稀疏注意力的扩展性会好得多。
Dwarkesh Patel
明白了。现在大家实际都在用稀疏注意力吗?
Reiner Pope
我对稀疏注意力很感兴趣。很难知道各家实验室实际在用什么。DeepSeek 已经发布过一种稀疏注意力机制。我想顺便推荐一下 DeepSeek 发布的几篇稀疏注意力论文,它们把这一项的增长关系改成了平方根。
到目前为止,我们看的是延迟。从延迟很难直接看出成本。如果想想成本是什么……运行这次推理时,我会占用 GPU 若干秒,比如 1 毫秒或 20 毫秒。这段时间的租用费用都要付。比如每块 GPU 每小时 2 美元之类。
这就是这次推理的成本。但这次推理处理了多少 token?这就是批量大小。我们真正想画的是成本与批量大小的关系,也就是 t 除以 B,再对比批量大小。这是每个 token 的成本。我们可以设想把这三条曲线都除以 B,也就是乘上它的倒数。结果是:计算曲线原本是线性的,除以 B 后就变成常数。这是计算时间 t。KV 读取原本也是线性的,现在同样变成常数。至于权重读取原本是常数,除以 B 后就变成双曲线。
我们还是把前两项的和与另一项取最大值。把这两个内存项相加,会让双曲线整体上移。KV 读取与权重读取相加后,会得到一条更高的双曲线。之后再与这里的计算项取最大值。最终得到的整体形状,就是我们关心的结果。
我们再次看到一些极限行为。批量大小为 1 时,成本一开始非常高,几乎趋向无穷大,因为大量权重读取费用都无法分摊到更大的批次上。但随着批量增大,权重读取可以由越来越多批次元素共同分摊,其成本就会变得很小,最后成本由计算时间决定。因此每 token 成本有一个下限,就是这条线。
Dwarkesh Patel
所以 Claude Code Slow、Codex Slow 之类的模式,最终也只能落在这条线上。它不会有太大帮助,因为你无法把 KV 值分摊到大得多的批次里。
Reiner Pope
KV 值是批次各自独有的,计算也一样。那么把其他所有成本都分摊掉之后,每个批次最低还需要做多少工作?
Dwarkesh Patel
在实际中,批量要多大才能摆脱内存带宽瓶颈?前沿模型的批次通常有多大?
Reiner Pope
这个可以直接算出来,而且对模型架构并不是特别敏感。我们来算一下。
我们讨论的是内存耗时等于计算耗时的时刻。这就是那个问题的含义。由于我们关注批量大小——更具体地说,是权重读取何时能在矩阵乘法中得到足够分摊——我会比较权重读取时间和权重乘法时间。为了简化分析,得到清晰答案,我们暂时忽略 KV 读取这一项。接下来让这两个时间相等。
写成方程就是:总参数数量 N 除以内存带宽,等于批量大小乘以活跃参数数量,再除以计算性能。看这边,分子里的量都是模型参数,分母里的量都是硬件参数。把式子重新整理一下,将硬件参数放到等号一侧会更方便。
这等价于:FLOPs 除以内存带宽,等于批量大小乘以活跃参数量,再除以总参数量。最后得到的这个硬件参数是一个无量纲常数。看 FLOPs 的单位……它的量纲是什么?这是每秒乘法次数,这是每秒字节数,所以还不是无量纲。但可以这样处理:看每秒有多少 FP4 乘法,再乘以每个 FP4 占半个字节。这样就能把它变成无量纲量。在大多数 GPU 上,这个数大约是 300。
Dwarkesh Patel
随着模型一代代更新、FLOPs 持续增加,这个比值有没有发生变化?
Reiner Pope
这是一个硬件参数。硬件变化有多大?从 A100 到 H100 再到 B100,FLOPs 大幅提升,内存带宽也大幅提升,而这个比值仍然相当稳定。
我们也可以把这个关系写出来。这是一个稀疏度参数。也可以稍微换种说法,直接求批量大小:批量大小大约要大于 300 乘以稀疏度。比如在 DeepSeek 中,我会激活 256 个专家里的 32 个,所以 DeepSeek 的这个值就是 8。
这个估算值和实践非常接近,精确度令人意外。通常人们会取比它稍大的值,因为实际效率不如屋顶线分析预测得那么理想。不过可以先按这个值估算,再乘以二到三。
Dwarkesh Patel
好,所以每批大约是两千到三千个 token。但如果把 KV 缓存也算进去,那最优批量大小就意味着……
Reiner Pope
应该更大。我们解的是计算时间等于内存时间时的平衡点。如果再加入一个占用内存带宽的项目,权重读取可用的带宽就会减少。我们需要更高的内存带宽,因此批量大小也要增大。
Dwarkesh Patel
这个数看起来小得惊人。甚至不到一个序列,对吧?
Reiner Pope
要记住,我说的是正在生成下一个 token 的序列数量。实际上是 2,000 个互不相同的序列。
Dwarkesh Patel
明白。我们说的是对这些序列各执行一次前向传播。你可以把批量理解为序列数量。
Reiner Pope
没错。
Dwarkesh Patel
如果有一个前沿模型在实际执行推理,同时用户肯定远超 2,000 人。必须等整个批次填满,会不会增加延迟?还是说只要用户量够多,等 100 毫秒凑齐接下来的 2,000 个请求就极不可能?
Reiner Pope
可以这样理解:作为模型,这趟列车何时发车?假设我选定了要运行的批量大小。顺便说一下,这里的交点就是那边的交点。我选定批量大小后,就知道它需要例如 20 毫秒;这个数值在实践中很常见。
这是一条 GPU 上任务运行的时间线。无论如何,它每隔 20 毫秒就会启动一个新批次。可以把它看成列车时刻表:每 20 毫秒发出一班车。准备好的乘客就上车。车满了,其他乘客等下一班;车没坐满也照样发车。
这对排队延迟意味着什么?最坏情况是请求刚好在列车发车后到达。它必须等下一班车,因此最多等 20 毫秒,然后还得等这班车运行完。最坏情况下延迟就是 40 毫秒。
Dwarkesh Patel
这 20 毫秒是怎么得出的?
Reiner Pope
这是经验法则,但它的来历还没完全解释。到目前为止,我们关注的是内存带宽和计算时间。看内存时,还有一个考量:我们想用满现有的内存容量。一般会把全部内存容量用于存放权重或 KV。执行一次前向传播时,我们希望将所有内存容量中的数据都读入芯片。这个时间等于容量除以带宽。对许多代 HBM 来说,结果通常是 20 毫秒。
Dwarkesh Patel
单位是对得上的:字节除以每秒字节数。
Reiner Pope
比如 Rubin 这一代,大约是 288 GB 除以每秒 20 TB,结果约为 15 毫秒。
Dwarkesh Patel
我确认一下自己理解得对不对。我明白单位分析。你的意思是,我们能在这段时间内把 HBM 清空再填满。所以我们不希望 HBM 容量太小,以至于无法把想要的数据都写进去或全部读出来。我们也不希望来回读写数据的带宽小到……
Reiner Pope
大致有两种情形。为什么不把延迟设得比 15 毫秒更长?想想这意味着什么:我们实际上有时间把 HBM 读两遍。顺便说一下,大多数 HBM 访问都是读取,而不是写入。几乎全是读取,因为权重矩阵是只读的,KV 缓存访问也几乎都是读取。大约 30 毫秒里,我可以把整个 HBM 读两遍,但这么做有什么意义?我不想把权重矩阵读两遍,也不想把 KV 读两遍。
Dwarkesh Patel
非常合理。再快速问几个问题。如果最优批量大小大约是 2,000,那它完全取决于稀疏度,与模型规模之类的因素无关吗?
Reiner Pope
稀疏度会体现在模型规模里;除此之外,它只取决于稀疏度,不取决于规模。
Dwarkesh Patel
这个结果很有意思。有个问题:推理批处理带来的规模经济会多大程度推动集中化?不过看起来这可能没那么重要。同时有 2,000 个用户算多吗?似乎不算多。
Reiner Pope
我们可以分析一下。可以用用户数量来思考,但更有用的视角是每秒 token 数。这个批量大小换算成系统每秒处理的 token 数是多少?
每秒 token 数等于批量大小。我们运行一批 token,每隔一个时间间隔就运行一次,这个间隔就是 15 或 20 毫秒左右。换算后就是批量大小乘以大约 60,也就是 64×B。这样大约是 2,000×64,即每秒 128,000 个 token。这个单位更容易理解。
很难用并发用户数来估算,但一个系统的全球流量是多少?有时看 API 提供商公布数据,会看到他们在炫耀自己的流量。我记得去年 Gemini 的某些公告提到,全球每秒 token 数达到数亿。这个数值是它的千分之一。
Dwarkesh Patel
Gemini 的规模很大。Gemini 的千分之一也不少。要在规模上具有竞争力,至少要能服务 Gemini 千分之一的流量。有意思。
稀疏度越高,所需计算量就越少。按这个分析,批量越大,瓶颈最终似乎会落在计算上。问题是,稀疏度能提高到什么程度?随着稀疏度比率增加,也就是相对于总参数,活跃参数变得更少,模型性能会下降多少?这种性能下降会不会比稀疏度提高所节省的计算量增长得更快?
Reiner Pope
你说的是模型质量,而不是模型速度。很遗憾,我们没法用分析方法回答。模型质量是个经验问题。我能做的就是找一篇论文,用实证结果来回答。
Dwarkesh Patel
那我们现在就看论文吗?
Reiner Pope
这篇论文叫《路由语言模型的统一扩展定律》。到现在已经算比较早期的论文了,但它考察的问题之一是:不断提高稀疏度会如何影响模型质量?结果非常依赖具体采用的专家混合方案。专家混合已经存在很久,可能早在 2017 年就有了,但技术已经有了很大变化。DeepSeek 的专家混合在运作方式上带来了重大改变。更早期的论文还有《GShard》和《Switch Transformer》。实际经验结果会取决于所有这些因素。
这里展示的一种较早技术可以看出:如果把活跃参数数量固定在某个规模,然后提高稀疏度,也就是他们所说的专家数量,模型质量会持续提升。想象从 13 亿参数的稠密模型画一条水平线。你会看到,在这个例子里,有 64 个专家、每次激活 3.7 亿参数的模型,质量和 13 亿参数的稠密模型相当。
Dwarkesh Patel
所以从某种意义上说,收益并不特别惊人:你得把总参数量提高一百倍,才能获得相当于活跃参数增加十倍的效果。
Reiner Pope
甚至还不止如此。参数量要大幅增加,效率才会小幅提高。
Dwarkesh Patel
所以在这个例子里,实际是 4 倍?
Reiner Pope
64 倍换来 4 倍。
Dwarkesh Patel
因此,虽然提高稀疏度确实能节省计算时间,乍看之下这似乎是值得的权衡。但如果稀疏度每翻一倍,这一项只下降两倍,而另一项却要增加八倍……
Reiner Pope
这究竟是好是坏?即使从内存角度看……要记住,这部分内存读取会翻倍,但可以通过批量处理来分摊。所以只要运行更大的批次就行。从我们刚才做的分析来看,这纯粹是好事。基本上可以一直这么做,直到可用用户数量不够为止。
这里存在一种等价关系:如果我有很多用户,就能使用稀疏得多的模型。从这个角度看,这种权衡是合理的。这里还涉及另一项权衡:它也会占用内存容量。我们刚才只讨论了内存带宽,但它还会占用内存容量。
Dwarkesh Patel
我确认一下自己理解得是否正确。你是说,我们想减少计算时间,因此提高稀疏度。要做到这一点,就需要更大的批量。这意味着我们需要更多内存容量,才能容纳更高的稀疏度。
Reiner Pope
也许现在正好可以聊聊专家混合层通常如何部署在 GPU 机柜上。
§02MoE 模型如何部署到 GPU 机柜上
Dwarkesh Patel
好,明白了。我们讲到哪里了?
Reiner Pope
稀疏专家混合模型。也许可以讲讲我们如何把它部署到 GPU 上。
我们先放大看专家混合层,画出它的样子。通常会有一个路由层,负责决定 token 要被路由到哪里。token 从这里进来,经过路由层,然后进入一组不同的专家。我再多画几个,把它们排成一列。
路由器会决定将 token 路由到哪些专家,只会选其中一小部分,比如 32 个里选 1 个。它可能会选择这个、那个,也可能还有这个。每个专家本身都是普通的 MLP:先做升维投影,再做降维投影,中间经过非线性变换。
最后执行反向操作:刚才把数据广播出去,现在要把它们收回来并求和。像这样汇集回来。最后还有残差连接。token 也会沿这条路径传过来,再加到 MoE 层的结果上。这就是普通的 MoE 层。
我想讲的是怎样把它映射到 GPU 机柜上,以及这对通信意味着什么,因为这能开始揭示稀疏度的极限。这里的标准做法,也是最佳方案,是使用专家并行。也就是说,不同专家放在不同 GPU 上。以 DeepSeek 模型为例,它有 256 个专家。假设我们想在一架 Blackwell 机柜上运行它,这里有 72 块 GPU。
这里有个除法问题:72 不是 2 的幂。我们就简化一下,假设只用其中 64 块,忽略另外 8 块。这没什么大不了的。这样每块 GPU 上有 4 个专家,非常简单。为了让示意图更清楚,我们实际上只画成每块 GPU 放两个专家。接着在这里画出 GPU 的边界,每一对专家都放在各自的 GPU 上。
接下来看看通信成本。这里有一些集中存放的 token,它们会被路由到所有这些专家,这里就要付出通信成本。输出阶段也有同样的通信成本。我们希望这不会成为通信瓶颈。
那这里的流量模式是什么?取决于模型作出的决定,任何一块 GPU 都可能和其他任意 GPU 通信。这就是全互联流量模式。
Dwarkesh Patel
你说“任何一块 GPU”时,是指路由器不止一块 GPU 吗?
Reiner Pope
我把它画成了一个路由器。实际情况是会有很多份路由器,事实上,每块 GPU 上都会有一个路由器。
Dwarkesh Patel
也就是输入流量。
Reiner Pope
对,这里是 64 块 GPU,那里也是 64 块 GPU。实际上是同一批 GPU,只是为了表示它们执行不同的功能,我们把它们分开画了。所以在这个阶段,任意一块 GPU 都可以向任意另一块 GPU 发送数据。
Blackwell 机柜的通信方式和布局,恰好非常适合 MoE 所需的全互联通信模式。不过,如果你觉得一个机柜太慢,想用两个机柜,就会遇到一个问题:比如机柜边界在这里,两个机柜里的所有 GPU 之间就不再是全互联了。跨机柜通信会成为一个很大的瓶颈。
这里的根本限制是:一个机柜限定了你能使用的专家层规模。这也是推动互连域不断扩大的因素之一。
Dwarkesh Patel
继续之前,你也许应该解释一下机柜到底是什么。机柜内与机柜间的带宽有什么差别?机柜内外的通信分别是全互联还是非全互联?
Reiner Pope
这方面 Nvidia、Google 以及其他公司(包括我们)之间的差异开始变得很大。一般来说,机柜是一种物理结构。它有几米高,一到两米宽,具体取决于配置,里面放着一定数量的 GPU 或 XPU,通常大约 64 块。
限制机柜尺寸的因素有供电、重量和散热能力。许多机柜最终都差不多这么大,就是因为这些物理限制。部署数据中心时,里面可能有成千上万个这样的机柜。我先放一个高高的机柜,里面装许多 GPU,等等。然后在旁边再放一个机柜。
Dwarkesh Patel
你说得好像这事很简单。
Reiner Pope
是啊,直接放进去就好。以 Nvidia 为例,它的通信拓扑是……GPU 实际放在机柜外侧,交换机放在机柜内部。结果就是这里会有一组交换机,也就是 NVSwitch。然后再接上许多线缆。每块 GPU 都有线缆连到机柜中间的交换机。交换机与所有 GPU 都有连接。任意两块 GPU 之间只需两跳就能通信:先到交换机,再到另一块 GPU。
现在如果要离开机柜,就得走另一条路径。GPU 还会有一种速度慢得多的连接,通常慢约 8 倍。我在 GPU 上画的绿色线代表 NVLink。更一般地说,它叫作扩展内网络。通常还会有一个扩展外网络,让你能连接到某台数据中心交换机。所有 GPU 都会通过某种连接连到数据中心里的交换机。这就是扩展外网络,它的带宽通常慢约 8 倍。
如果想把一个 MoE 层分布到两个机柜上,挑战在于这里一半的 GPU 会想和那边的 GPU 通信。看平均情况,这些 GPU 上的 token 有一半会被路由到本机柜内。这很好,它们可以使用快速的扩展内网络。但另一半 token 会离开本机柜,前往另一个机柜,那就没那么理想了。它们必须使用慢得多的网络,因此在全互联通信模式中,这会成为瓶颈。
另一种做法是:为什么不在这里放一个大型交换机,把所有东西都连到一个更大的交换机上,让两个机柜合在一起?这方面有很多设想,但通常之所以采用多层交换机,而不是一个大交换机,是为了应对布线拥堵。你必须铺设大量线缆。
Dwarkesh Patel
抱歉,你刚才问的其实就是:为什么不把扩展内网络做得更大?
Reiner Pope
没错。为什么不让一百万块芯片,或者一千块芯片都使用扩展内网络?
Dwarkesh Patel
Nvidia 从 Hopper 的 8 块,到 Blackwell 的 72 块,再到 Rubin,据说会到五百多块,这种扩展是怎么实现的?
Reiner Pope
对,五百多块。
Dwarkesh Patel
是什么让它成为可能?
Reiner Pope
从 Hopper 到 Blackwell,主要是把产品形态从托盘换成机柜。这是一个产品决策,并不存在什么重大的技术障碍。
从 64 块提升到大约 500 块,这里面有些 Jensen 式的算术,但至少确实提升了 4 倍;这需要更复杂、更难设计的机柜,也确实要采用新的物理设计来容纳更多线缆。
Dwarkesh Patel
线缆变复杂,难道只是要弄清楚哪条线接哪条、哪个信号从哪里连到哪里吗?
Reiner Pope
我们放大看看线缆密度。我再把这个图画一次,这样可以画得更干净、更大一些。
假设中间有一些交换机。起初,我只在左右两侧各放两块 GPU,或者两托 GPU。假设每托 GPU 需要引出两根线缆。我会像这样垂直铺设线缆,把它们接到交换机上。现在如果想让机柜里的 GPU 数量翻倍,就得把线缆密度也实实在在提高一倍。我还需要把这些线缆也铺上。
Dwarkesh Patel
问个特别基础的问题。我看真实数据中心时,感觉机柜里有很多空间。我不知道,线缆不是挺粗的吗……
Reiner Pope
机柜外面有空间。机柜里面……随着设计越来越紧凑,机柜内部就很挤。从托盘接入机柜,再连接到机柜背板,都受连接器密度限制,而背板本身的密度也已经很高。还有其他物理限制,比如线缆的弯曲半径。你不想把线缆折断,等等。
Dwarkesh Patel
好,所以限制因素真的就是没有物理空间再放线缆了。我完全没想到。听起来很意外。机柜明明这么大,我们却不能再往里面塞更多线缆。
Reiner Pope
机柜设计不是我的专长,但我和相关人员聊过他们面临的限制,原因是多方面的。你要优化哪些重要的物理条件?空间、机柜重量。机柜真的很重,所以需要足够多的金属支撑,不能让它下垂或倒塌;但增加金属又会让它更重。然后还要考虑供电和散热。这些条件彼此牵制。现代机柜正在把所有这些指标推到非常极端的物理边界。
Dwarkesh Patel
GPT-4 是哪年发布的来着?2022 年还是 2023 年?
Reiner Pope
2023 年。
Dwarkesh Patel
好。据传它有超过一万亿个参数。看起来直到最近六个月,才有模型的参数量明显超过三年前发布的模型;按理说,这期间应该已经经历了这样的扩展。
原因是不是我们一直在等有足够内存的机柜,才能容纳一个五万亿参数的模型,同时还要给足够多用户的许多序列留出 KV 缓存?如果做 RL,考虑因素也类似:要为正在求解的一批问题实际保存 KV 缓存。
看 Hopper 时有 8 块 Hopper,我记得 2022 年的总内存是 640 GB。到了 Blackwell 部署时,终于……
Reiner Pope
最近才部署。可能是去年。
Dwarkesh Patel
去年。现在你终于有了 10 到 20 TB 量级的扩展内存,足以容纳一个 5T 模型再加上 KV 缓存。
Reiner Pope
部署更大的扩展内域是一次重大突破。我这里画的是 Nvidia Blackwell 的部署方式。Google 很早以前就已经有非常大的扩展内域了。
Dwarkesh Patel
这也解释了为什么 Gemini 看起来领先。Gemini 成功进行预训练的时间似乎比其他一些实验室更早。
Reiner Pope
我当时不在 Google,所以不确定这在多大程度上是因为成功部署了更高的稀疏度,这有可能。也可能是各种实际建模因素,尤其是专家混合方案的设计方式。我们看到 DeepSeek 的专家混合激活了更多专家,但每个专家更细粒度。这是一项重大创新。我确信模型架构和训练数据方面也有许多其他创新。
很难把这些因素区分开。不过从可实现范围的限制来看,活跃参数数量受计算成本限制,而总参数数量受扩展内规模限制。
§03流水线并行如何将模型层分布到多个机柜
Dwarkesh Patel
如果在单个扩展内域中运行,这个因素具体影响前向还是反向传播?是影响预填充还是解码?还是说无论工作负载是什么——预训练、RL 生成,或面向用户的推理——最好都留在一个扩展内域里?
Reiner Pope
很有意思。要回答这个问题,我们得先谈通信模式。我们说过专家混合的通信模式是全互联。全互联非常依赖完整连接,也就是我们刚才展示的这种模式,而且适合在单个机柜内完成。
除了刚才展示的专家并行,还有其他并行方式。文献里有张量并行。随着专家越来越小,这种方式的重要性大幅下降,所以可以先忽略。但还有另外两种方式:数据并行和流水线并行。它们更适合跨多个机柜。
我们具体看一下流水线并行。这里是一个 MoE 层,上面还会有另外 100 层。我可以决定在这里切换到另一个机柜。这样会不会造成通信瓶颈?其实可以算出什么时候会成为通信瓶颈。在代数推导之前,先把它画出来,看看数据路径。再画一个 MoE 层,再一个 MoE 层,等等。
假设我在这里切换机柜,过若干层之后又在这里切换。要判断在切换机柜时是否出现通信瓶颈,我们会比较扩展外带宽的需求和扩展内带宽的需求。把它写下来。关键是这里要发送很多东西。此处有很多数据要发送,而这里仅发送一项,并且还可能要发送很多次。这正是差异所在。
Dwarkesh Patel
我能猜一下吗?只是想看看自己是否理解了。看起来你把批量大小的数据发进机柜。
Reiner Pope
这里吗?对。
Dwarkesh Patel
但机柜内通信量是批量大小乘以 GPU 数量。
Reiner Pope
是被激活的 GPU 数量。我完全不会向这块 GPU 发送数据。这里的数据量会扩大 1 到 3 倍。关键是,我甚至完全不必向这块 GPU 发送数据,这就节省了很多。
我们要讨论扩展内相比扩展外何时会成为瓶颈。我们直接计算扩展内耗时与扩展外耗时的比值。这就是我们要看的量。
首先要考虑的是,扩展内通常比扩展外快 8 倍。假如两边带宽原本相同,光是带宽差异就会产生 1/8 的比值。但接着还要考虑发送数据量扩大的倍数。一个 token 从这里进入后,会被路由到这里。以 DeepSeek 为例,可能会被路由到 32 个或 16 个专家,总之是某个数量的专家。这就是激活专家的数量。同样的过程会发生在多个层上,所以我可能会运行两层。此外,每个阶段还会有多个层。
Dwarkesh Patel
全互联通信不需要把整个数再乘以 2 吗?
Reiner Pope
升维和降维各算一次。对,还要乘以 2。谢谢提醒。
我们希望扩展内耗时大于扩展外耗时,因为扩展内是更重要、更宝贵的资源。这个比值最好大于或等于 1。这看起来并不难,毕竟只需要克服 8 倍的差距。所以这三个量的乘积必须大于 8。通常激活专家的数量相当多,单这一项就可能是 8。然后我们可以大幅增加每个阶段包含的层数,直到满足条件。
最后的布局可能像这样:机柜组成一条完整的流水线,一个机柜运行一层,然后移到下一个机柜运行另一层,再移到下一个机柜运行一层。
Dwarkesh Patel
我觉得很有意思:实践中最好的并行策略,恰好在物理上和实际架构相似。并不是什么特别复杂的妙招,而是“有专家,就把它们放在不同 GPU 上;有不同层,就把它们放在不同机柜上”。我觉得这很有意思。
Reiner Pope
划分方式与模型架构相对应。
Dwarkesh Patel
没错。原本也可能采用更古怪的方案,比如张量并行之类。
Reiner Pope
更全面的思考方式是:模型在哪些维度上扩展?它按层数扩展,按模型维度扩展,按 DFF 维度扩展,也按专家数量扩展。每一个维度都可以选择切分。如果这些维度上的数值够大,沿该维度切分最终就会划算。我们选了其中两个。按模型通常的规模设置,另外两个维度不值得切分。
Dwarkesh Patel
Ilya 有一次演讲,他说:“如今我们知道,不该使用流水线并行。”Horace He 曾给我和几位朋友……我不喜欢这听起来像苏斯博士的童谣。但他给我们讲过不同类型的并行方式。他说流水线并行的问题在于,除了会产生气泡之外,它还会带来架构上的限制。比如 Kimi,有一些残差连接会让注意力机制关注前几层,所以很难用这种方式实现。
Reiner Pope
我想我们甚至还没充分说明流水线能带来什么好处。这些复杂性确实存在。流水线很麻烦,但确实能带来一些收益。你可以自行判断这些收益是否值得付出代价。推理时有一些收益,训练时可能收益更大。推理时我们节省的是什么?内存时间还是计算时间?其实都不是。我们只是把内存耗时从一块芯片挪到另一块芯片,或者从一个机柜挪到另一个机柜。运行时间实际上没有任何收益。
不过我们节省的是内存容量。如果机柜内存成为瓶颈,就会限制我们能达到的速度。流水线能大幅降低这个瓶颈。
Dwarkesh Patel
这件事也有相反的一面……采访前我和 Axel 聊过,他是 Jane Street 的 GPU 性能工程师。他解释说,做流水线必须使用微批次,而不是完整批次。使用微批次,就意味着你无法把权重加载成本分摊到所有用户或所有序列上。好处是所需内存更少;坏处是这些用户无法共同分摊加载权重的成本。也许值得解释一下为什么必须使用微批次。
Reiner Pope
我们要不要画出流水线气泡?流水线并行里的微批处理到底是什么?
我们先看推理,这个问题稍微简单一些。我画一条时间轴,再标出我们位于哪个机柜。假设有四个机柜。一次推理会像这样依次经过这四个机柜。这里是推理 0,它以某个批量大小运行,然后依次经过所有流水线阶段。
如果我们说:“好,我们把推理 1 放在这里运行”,显然会造成巨大浪费。每个机柜有四分之三的时间都无事可做。实际上我们不会把推理 1 放在这里,而会在推理 0 一结束就尽快运行它,然后继续往下排。如果这些空白没有被填上,我们就称之为流水线气泡。我画的是只有前向传播的推理场景,这一点很明显:谁会做这种蠢事?在训练场景下,原因可能没那么明显;但在推理场景下,这样做很自然。
Dwarkesh Patel
哦,有意思。虽然这似乎显而易见,但在推理中,微批次和批次的区别根本无关紧要,因为你想怎么称呼都行。只有训练时才重要,因为训练存在一个最优批量大小。
Reiner Pope
是。
Dwarkesh Patel
执行完整的反向传播步骤之前,你需要先累积完批次中的所有序列。如果想在训练中使用流水线,同时又要避免气泡,就得……
Reiner Pope
我们要不要画一下对应的训练图?来画吧。这是推理图,我把它标为前向传播,以免混淆。现在用同样的方法画训练。我们先做前向传播,但之后某个阶段必须切换到反向传播。
我们先做若干个批次的前向传播,然后所有批次一起切换到反向传播。这里的推理部分相同,但到这个位置会突然停下,再把所有批次切到反向传播,编号方式也类似。
Dwarkesh Patel
也许需要说明为什么这里要突然停下:因为反向传播时需要一次处理完整批次,而且批量大小有一个最优值。
Reiner Pope
也可以这么说:从机器学习收敛速度的角度看,批次越小越好,因为梯度下降获得的信息更新鲜。
Dwarkesh Patel
但从总训练时间来看呢?
Reiner Pope
从总训练时间的系统角度看,批次越小越糟。最优值是两者之间的权衡。
所以你选定批量大小后,就针对这个批量先做一段前向传播,再做一段反向传播。你问为什么这里要突然停下。对于流水线并行,因为这里会产生闲置时间,也就是气泡,文献中有很多技术来改变这种布局并避免气泡。还有更复杂的方案,比如“零气泡”或“一前向一反向”,它们会以更复杂的方式交错安排前向和反向传播。
Dwarkesh Patel
你可以趁气泡的时候挖比特币。
Reiner Pope
说得更实用一点,你可以在这时计算权重梯度。当然,也可以挖比特币。
推理中,流水线对你关心的事项(例如批量大小或延迟)没有影响。既不会变好,也不会变差。看一次推理的延迟,无论它采用流水线,还是所有计算都放在一个机柜里……如果都在一个机柜里,只要把所有方框向下平移,然后仍然排成一行,延迟还是一样。
流水线对延迟既没有好处也没有坏处。它确实能减少每个机柜所需的内存容量,因为现在每个机柜只需要保存四分之一的模型,而不是整个模型,所以模型可以扩展得更大。
Dwarkesh Patel
很有道理。所以推理时使用流水线是显而易见的选择,但训练时要面对更复杂的权衡。
Reiner Pope
事实上,即使在推理时,流水线也并没有被大量采用。它会降低内存容量需求,但实际上内存容量富余很多。我想你刚才提到一架 Blackwell 机柜有几十 TB 内存,这远超一个万亿参数模型的需求。一个万亿参数模型只需要 1 TB,已经能放得下。因此流水线的好处不大,因为它降低的本来就是一个相对很小的数值。
不过从理论上说,内存容量可能确实太多了。你可以设计内存更少的硬件。如果你在设计硬件,就可以说:“我不需要这么多内存,因为权重不必都放进一个机柜。我可以把权重分散到八个机柜里,那么每块 GPU 就不需要配这么多 HBM。”
§04Ilya 为何说“如今我们知道,流水线并不明智”
Dwarkesh Patel
宏观来看,大家现在都在谈内存墙。内存越来越贵,供应也不够。因为内存不足,智能手机销量可能下降 30%。这很惊人,Dylan 说今年超大规模云厂商的资本支出有 50% 会花在内存上。
Reiner Pope
这听起来可信。
Dwarkesh Patel
超大规模云厂商的资本支出是多少?可能是数千亿美元,甚至一万亿美元,他们把一半花在内存上?这真是巨大的约束。这就是今年我们买不到新笔记本电脑和手机的原因。
但与此同时,我们的内存又太多了?人们还愿意在这些系统里配置过多的内存。如果内存并不需要那么多,Jensen 为什么要把这么多内存塞进这些机柜?
Reiner Pope
在我们刚才擦掉的方程里,我们讨论的是内存时间、内存带宽和计算带宽。现在开始看内存容量。
先不考虑任何并行方案,看看内存容量需求。内存需求中的一项是总参数量,也就是我们需要在所用系统中容纳的权重。除此之外,还要放下 KV。KV 的大小等于批量大小乘以上下文长度,再乘以每个 token 的字节数。
我在这个场景下主张的,也就是我支持流水线的理由,是有一些技术可以解决这个问题。假设我们用若干 GPU 来运行。这里有一个维度 E,表示专家并行的范围。一个专家层分片到多块 GPU 上时,具体会分到多大范围?要用多少块 GPU?比如这里取 64。然后 P 表示流水线范围,也就是机柜数量,比如可以选 4。
这是整个系统的总内存需求,但现在我要计算每块 GPU 的内存需求。我用小写 c_mem 表示。很简单,只要把这些数除以 E 和 P。总参数量加上批量大小乘以上下文长度再乘以每 token 字节数,最后除以 E×P。
为什么可以这样除?我们知道,机柜内的参数会完美地分布到所有 GPU 上。各层也会完美分布到不同机柜,所以这部分成立。我们也可以用某种方式安排——具体怎么做我先略过——让上下文在机柜内的 GPU 之间均匀分片,再按照层数分布到不同机柜。
Dwarkesh Patel
抱歉,4 是机柜数量吗?
Reiner Pope
对,比如说。
这里我们需要回头分析批量大小 B。你刚才提到了微批次与全局批次的区别。我们再看一下这个流水线示意图。
这里有一个批次正在进行前向传播。按我刚才的画法,它之后好像就消失了。这不太对。想想解码的过程:我已经生成了一批 token。先做一次前向传播,生成一个新 token,然后把它写入 KV 缓存。之后再执行一次前向传播,生成下一个 token。实际上,我会让批次 0 不断循环运行。向前执行完后,就可以在这里开始循环的下一轮。我们把它补进去:还有 2、3、2 和 3,以及 2 和 3。
把这个批次拆开。这里叫作全局批量大小。B 等于微批次数量乘以每个微批次的大小。我们需要多少个微批次?这个图里有 4 个:0、1、2、3。微批次大小还是大约 2,000。抱歉,不对,这里应该是 300 乘以稀疏度。
Dwarkesh Patel
这就是每 20 毫秒发车时,列车的容量。
Reiner Pope
对,这是一班每 20 毫秒发出的列车。全局批量大小等于微批次数乘以本地批次大小。本地批次大小由这个硬件参数决定。
微批次数量要尽可能少,但又必须足以循环运行时不留下空闲时间。如果数量更少,循环绕回时就会出现空闲时间。从图上可以直观看出,它等于流水线阶段数量。可以用图形来证明:这里是 4 个阶段,纵向也是 4 个。你可以看出它沿着这里运行,然后绕回时对应流水线阶段数。
Dwarkesh Patel
抱歉,这是个很基础的问题。实际就是这么做的吗?当今的前沿模型在推理时会使用流水线吗?
Reiner Pope
大规模训练肯定会这么做。推理也可以这么做。我接下来要解释为什么吸引力会小一些。它对权重有用,对 KV 却没那么有用。
最大的问题是……我们把图补全。这里的微批次大小最终等于流水线阶段数量。把这些代入这个式子后,会得到流水线阶段数乘上这里的小写 b。将其展开后,我会把这个加法拆成两项。
这里得到的是除以 E×P;这边也仍然除以 E×P,但 P 会约掉。结果发现,增加流水线阶段数后,权重所占内存会不断下降,但激活值所占内存保持不变。所以这种方法实际上行不通。
大部分内存……流水线做得足够多之后——其实不需要太多,很多时候两级就够——这一项就会变得很小。KV 缓存会成为主要部分。
Dwarkesh Patel
我知道我的想法不对,只是在想哪里出了问题。如果流水线跨越许多不同阶段,KV 值不会在不同层之间共享。为什么把多个层放进流水线不会有帮助?这样你就不用保存……
Reiner Pope
从这个角度说你是对的:你只需保存一层而不是两层的 KV。但与之竞争的是,为了让所有机柜一直有效工作,你必须让更多序列同时处于运行中。
Dwarkesh Patel
啊,这就说得通了。
Reiner Pope
这两者恰好抵消,结果每块 GPU 的内存没有节省。
Dwarkesh Patel
对,归根结底还是你无法把 KV 缓存的成本分摊出去。
Reiner Pope
首先,我们确定 KV 缓存无法通过增加批量大小来分摊。现在又看到它也无法沿流水线阶段分片。两方面都不理想。
Dwarkesh Patel
有意思。那推理阶段实际上会怎么做?
Reiner Pope
DeepSeek 的论文介绍了他们的做法:大量使用专家并行。实际上,应该把专家并行度提高到扩展内域的规模,然后只用很少的流水线并行。也许完全不用,也许用两级,刚好让权重存储不至于成为太大的问题。
真正有意义的并行方式只有这两种。以前还有张量并行,也就是把单个专家切开,但如今专家非常小,这种优化并不划算。
Dwarkesh Patel
这是否意味着前沿实验室在做推理时,只在一个扩展内域中运行?
Reiner Pope
是的。可以看它和模型大小的关系。你可能有个非常大的模型,超出单个机柜的内存容量,这时就应该用一些流水线。比如模型极其稀疏,这也可能是使用流水线的理由。
Dwarkesh Patel
这又回到了讲座开头的承诺:这些分析也能说明 AI 的进展。如果模型规模直到最近才开始加速……
我确认一下这个观点。你的意思不是可以跨更多机柜训练,而是以前这样做没有意义,因为我们没有能力轻松地对更大的模型执行推理。
Reiner Pope
实际上,流水线对上下文长度没有帮助,但对模型规模很有帮助。通过流水线,至少机柜不应该限制你容纳模型参数的能力。
你问的另一个问题是,为什么模型规模没有继续扩大,以及更大的扩展内域为什么会有帮助。我们讨论过其中一方面:这并不是因为内存容量不足。至少针对模型规模,我们有办法解决内存容量问题,但 KV 缓存容量仍然是问题。另一个限制是延迟。
Dwarkesh Patel
我正想问,跨机柜时每一跳会付出多少延迟成本?
Reiner Pope
这很大程度取决于硬件,我无法很有把握地给出答案。我觉得可能是几毫秒量级,不过也可能差一个数量级。
Dwarkesh Patel
流水线阶段数取 4,现实吗?
Reiner Pope
现实。
Dwarkesh Patel
所以延迟没那么高。
Reiner Pope
流水线阶段数不多时,延迟影响不会很大。
Dwarkesh Patel
但我想每个 token 要增加 10 毫秒。
Reiner Pope
没错。
Dwarkesh Patel
大概是 2 乘以 4 左右,或者我不确定你刚才说了几个阶段……每个 token 增加 10 毫秒,其实挺多的。
Reiner Pope
如果从 20 毫秒变成 30 毫秒左右……
具体数据路径是这样的:从 GPU 或 TPU 进入网卡,再到机架顶端交换机,跨到另一个机柜,然后沿反向路径走回来。这些不同环节的延迟都要加起来。
Dwarkesh Patel
抱歉,这和数据中心交换机是同一回事吗?
Reiner Pope
实际上可能会先到数据中心交换机再返回,具体取决于部署配置。
Dwarkesh Patel
明白了。因为这是解码,而且是顺序执行,所以延迟会在各阶段累积,无法同时进行。
Reiner Pope
是的。
Dwarkesh Patel
回到刚才的问题:扩展内规模是否和过去几年 AI 模型的规模有关?无论是训练还是推理。
Reiner Pope
我们谈过跨机柜跳转的延迟。此外还有内存时间 t_mem。更大的扩展内域能大幅改善内存时间。我把 t_mem 重新写在这里:权重的内存耗时等于总参数数量除以内存带宽。这里说的是哪种内存带宽?只算一块 GPU 吗?不是,而是我能并行用于加载这些权重的 GPU 数量。我不能让不同流水线阶段并行运行,因为它们不是同时执行;但我可以并行利用扩展内域里的所有 GPU 来加载权重。这非常有效。最后,内存带宽这一项本身基本等于扩展内规模……
Dwarkesh Patel
乘以每块 GPU 的内存带宽。
Reiner Pope
对,乘以 GPU 带宽。这个量每代不会增加太多,可能增加 1.5 到 2 倍;而从 Hopper 开始,另一个量增加了 8 倍。
Dwarkesh Patel
所以扩展内规模更大的意义,不在于整个扩展内域的内存容量,而在于内存带宽。
Reiner Pope
没错。流水线完全能解决容量问题,但扩展内规模有助于解决带宽问题。
Dwarkesh Patel
而带宽问题解决后,你就能处理更长的上下文长度;随着模型越来越像智能体,这一点也变得越来越重要。
Reiner Pope
首先,它让模型以更低延迟运行。比如一个极其稀疏的模型跑在一台小型 H100 服务器上,延迟就会很高。
§05为什么 Ilya 说“如今我们知道,流水线并不明智”
Dwarkesh Patel
有个比较离题的问题。Chinchilla 缩放定律告诉我们,模型大小应当和训练数据量相匹配。但现在显然,我们不只是想在给定训练计算量下得到质量最高的模型。我们想要的是结合训练和推理计算,给用户带来最佳结果。
所以问题是,应该把模型过度训练到什么程度,才能让训练与推理的总计算成本最低、同时达到某种性能?现在有了 RL,还要考虑另一件事:你会进行一定量的预训练,然后这些预训练结果既会用于 RL 生成,也会用于最终用户的推理。这里我说的“过度训练”是指:单看训练计算量,训练一个更大的模型并减少训练时间可能更高效,因为它学习得更快。但也许你可以改用更小的模型,多投入一些计算量来训练它,从而让它在服务用户时更便宜。
我把问题说得具体一点:模型比 Chinchilla 最优训练程度高出多少?RL 生成的引入有没有改变这一点?
Reiner Pope
这里我们得做一点推测,因为更新后的缩放定律和模型流量没有公开,所以只能估计。有一种分析方式……
我先提出一个一般性的启发式判断。如果有一个总成本,它是成本 A 和成本 B 之和,比如训练成本和推理成本,我想让总成本最小……
对于很多曲线,成本最低点通常出现在各项成本相等的位置。这算是一种经验判断,但有很多例子支持它。比如一项成本是 1/x,另一项是 x,它们往往在相等的位置达到最小值。e^x 和 e^-x 等许多其他情况也一样。总体上,一条曲线在下降,另一条在上升,最低点往往是两者相等的位置。
按经验判断,我推测你描述的情形也如此。要真正证明它,需要研究缩放定律并拟合这些特殊的指数,但遵循幂律的情况通常具有这个性质。所以我先提出这个判断,再继续往下分析。
我们假设要让训练成本和推理成本相等。原则上可以把整个问题列出来。预训练成本等于活跃参数数乘以预训练数据量,外面还要乘以 6,因为有 FLOPs。著名的 6ND 公式就是这么来的。RL 成本也大致类似:活跃参数数相同,但数据量变成 RL 数据量。此外还要乘以一个效率系数,或者说低效系数……
Dwarkesh Patel
也就是你没有在所有 rollout 上训练。
Reiner Pope
对,还有另一项、也许更大的低效因素:这里会涉及大量解码,而解码的 MFU 往往低于训练。
Dwarkesh Patel
好。所以如果 RL 中每次生成之后都做一次反向传播,那就是 6ND。
Reiner Pope
所以这个值可能会更小,对吧?
Dwarkesh Patel
至少是 2,因为这是下限……
Reiner Pope
介于 2 到 6 之间。我们就说大概是 2 到 6,然后先放在这里。接下来加上推理成本。推理成本是活跃参数数量乘以推理数据量,再乘以 2。
Dwarkesh Patel
抱歉,我刚才说得很乱。给听众解释一下:对每个参数做一次前向加反向传播,计算量是 6;只做前向传播是 2。所以 RL 里你肯定会生成所有轨迹,但不一定会在所有轨迹上训练,计算量就在 2 到 6 之间。
Reiner Pope
是。谢谢。推理的系数就是 2。我们要找的是这三项大致相等时的规模。这就是个数量级估计。实验室更清楚继续做更多 RL 还是更多预训练更有效。我没有这些信息,但我觉得一个不错的估计是三者各占 33%。
Dwarkesh Patel
我不太理解这个直觉。另一种简单模型可能是,RL 加预训练占 50%,推理占 50%。
Reiner Pope
那也是合理答案。因为这是启发式估计,我不能有力地论证哪一种更好。差距也不大,33% 和 25% 只是略有不同。
我们选其中一个吧。三者相等比较简单,所以就求它们相等。推导相当直接:我们马上就能看到,活跃参数数量会完全消掉,所以把它约掉。我们说预训练数据量——我决定按你的思路写,会更好看一些——加上……哦,我还没把这边的低效项写进去。预训练数据量加上 α 乘以 RL 数据量,最终等于 β 乘以推理数据量。
先粗略估算 α。它可能在 2 到 6 之间,也就是这项相对于那项的比例。再考虑低效系数,我估计大约是 30%。所以 α 大概是十分之一。这里的 β 其实也是一样的:三分之一乘以 30%,同样等于十分之一。
Dwarkesh Patel
如果这两项都是十分之一,是否意味着 RL 过程中从来没有反向传播?
Reiner Pope
是的。好吧,我们把这一项改成十分之二,大一点。再写一遍:这里是十分之二,这里是十分之一。
推理 token 数只是这样计算出来的:系统每秒处理数亿个 token,再乘以我的模型部署两个月、直到下个版本发布的时间。这应该能确定 RL 和预训练的 token 数量。
我想我们还没推导预训练和 RL 之间的等价关系,那就在这里算一下。要让两者成本相当,预训练数据量应该等于 RL 数据量的 2/10。抱歉,应该是 1/10。我弄反了。效率越低,成本越高,所以这里应该是 1/10。把这关系代回去……按这里的写法,结果大概是 1.5 比 1。
Dwarkesh Patel
价值几十亿美元的计算资源就这样被挪到另一边去了。
Reiner Pope
对吧?我觉得如果你用电子表格实际建模,也许会注意到钱正在打水漂。按这个模型算下来,它们的规模都很接近。这里的 30% 可能估得过于乐观了。就先把这一项设成大约 1.5,另一项仍然设成 1。
我觉得现在几乎可以直接读出结论:推理 token 数应该和预训练 token 数差不多,预训练 token 数又应该和 RL token 数差不多,误差范围取决于一些我们无法推断的因素。
Dwarkesh Patel
抱歉,我犯了个很基础的代数错误。按理说 RL token 应该比预训练 token 少吧?
Reiner Pope
一般来说没错。由于 RL 使用机器时间的效率较低,如果你想让 RL 和预训练耗时相当,那么要用更少的 token,才能获得相同的实际运行时间。
Dwarkesh Patel
这一切挺有意思。我以前没想过要从数据量相等的角度来看。
Reiner Pope
我认为从成本相等出发是正确的;不过具体成本模型不同,最后会非常接近数据量相等。
Dwarkesh Patel
也就是说,要让 GPT 得到最优训练效果,每一个 GPT-5 用户使用时输入和输出的总 token 数,应该等于预训练中消耗的 token 总数。预训练 token 总数就是人类所有知识的总和。每个模型都应该在输出端生成和输入端得到的总量相当的人类知识。
Reiner Pope
对。那么大家会往哪个方向出错?如果你认为人们的预测能力并不完美,而且还可能训练出一个不属于前沿水平的模型,最后只能把它丢掉,那么成本权衡就会变化,因为推理量要乘以模型成功的概率。你应该按某个比例折减推理 token 数。
Dwarkesh Patel
那我们能不能推算出,对于给定规模的模型,它比 Chinchilla 最优多用了多少计算量?
Reiner Pope
我觉得需要先作出一些符合现实的假设,才能算这个问题。
推理 token 数应该完全能数出来,对吧?假设每秒有几亿个 token。也许现在是每秒 5 亿,我也不太确定。每秒 5 亿个 token,乘以一个模型在过时之前部署两个月?
我没法心算。你能在电脑上算一下吗?
Dwarkesh Patel
2.6×10^15。
Reiner Pope
好,2.6×10^15。这个数可能太大了,因为这应该包含一个系列中的多个模型。我们把它缩小 5 倍或 10 倍之类。这样估算下来,每个特定模型每秒大约处理 5,000 万个 token。模型在发布后运行两个月,结果约为 2,000 亿个 token。然后要把这个数和前沿模型的活跃参数量比较。我不知道最新传闻是多少。你知道吗?
Dwarkesh Patel
有人告诉我,训练用了 150 万亿个。
Reiner Pope
活跃参数?
Dwarkesh Patel
抱歉,我说的是 token 数。
Reiner Pope
训练用了 150 万亿个 token。有意思。
Dwarkesh Patel
和我们估算的差不多。
Reiner Pope
确实相近。这就是预训练数据量。
Dwarkesh Patel
这个说法没有充分引用来源,不过没关系。
Reiner Pope
我觉得活跃参数可能约为 1,000 亿。也可能稍大一些。这样乘以 20,就能得到 Chinchilla 推荐的 token 数量。因此 Chinchilla 的 D 值,也就是 D_Chinchilla,大约是 2 万亿。可以看到,我们的估算约为它的一百倍。
Dwarkesh Patel
D_Chinchilla 到底是什么意思?
Reiner Pope
我想是 Chinchilla 缩放定律建议的预训练 token 数量。
Dwarkesh Patel
明白了,也就是过度训练了多少。
Reiner Pope
用 2,000 亿或 1,000 亿 token 除以 Chinchilla 最优的 2 万亿 token,就是过度训练的倍数。也就是过度训练了 100 倍。
Dwarkesh Patel
一百倍。所以如果这个估算大致靠谱,仅仅根据让计算量各部分相等的思路……假如 OpenAI 也发现了这一点,并且知道自己的模型每秒服务多少 token,就可以推算 GPT-5 的预训练数据量。哪怕误差达到 50%,也很惊人,居然能用第一性原理估算出这些数字。
Reiner Pope
这就是为什么到处都可以先做近似估算,因为这里存在很大的误差范围。不过把 A 设为等于 B 再推导,还是很有启发性。
§06从 API 定价推导长上下文的内存成本
Dwarkesh Patel
太酷了。好,为了继续推导,我们可以公开查到这些模型的 API 价格,也许能从中学到一些东西。
先看长上下文。输入超过 20 万 token 后,Gemini 3.1 的价格比低于 20 万 token 时高 50%。大方向上,我明白为什么会这样,但为什么恰好是 50%?
Reiner Pope
为什么恰好是 50%?从大方向上看,首先是上下文越长,成本就有一定增加。我们可以把前面的公式再拿出来,也就是内存耗时和计算耗时的关系。
我们重新写出前面的公式:内存读取时间包括权重和 KV 缓存,计算时间则只是权重矩阵乘法。我也会画出成本曲线,但这次以上下文长度为横轴,而不是批量大小。这就是成本随上下文长度变化的曲线。我们先画计算部分。计算成本实际上不随上下文长度变化。这里没有上下文长度这一项。现实中确实有一点相关性,但很弱,所以先忽略。这就是计算时间。
再画内存读取时间与上下文长度的关系。它一开始是一个较大的数值,对应权重读取,然后随着上下文长度逐渐增加。所以大概从这里开始,随上下文长度缓慢增长。取两者中的最大值,就会看到这里有一个拐点。
这可能就是 Gemini 实际承担的成本。接下来考虑如何据此设定价格。无论上下文长度是多少,你都希望保证盈利。因此可以设两档价格,比如在某个范围内是这样的价格。
我觉得,既然拐点出现在 20 万 token,可能说明它大致和这里的交叉点吻合,但未必完全重合。甚至可以把计算完整做出来,看看结果落在哪里。
如果对活跃参数数量作出假设,我们就能解出每个 token 占用多少字节。我们假定内存时间与计算时间相等的交叉点是 20 万 token,然后令这两个时间相等。
我们还假设批量足够大,使权重读取时间可以忽略。因此先忽略权重读取,只看 KV 缓存的实际内存读取时间。整理一下公式:批量大小乘以上下文长度,再乘以每 token 的字节数,再除以内存带宽,等于活跃参数数量除以 FLOPs。然后求每 token 字节数。批量大小原本不在这里,代入后会出现,但到这一步最终会约掉。上下文长度我也写错了。
我们可以代入数字。这是刚才那个数字的倒数,也就是 1/300,在许多硬件平台上都相当稳定。我们猜测活跃参数数量可能是 1,000 亿,上下文长度是 20 万。不过这里有点不对:上下文长度应该在分母,而不是分子。
Dwarkesh Patel
1,667。差不多 2 KB。
Reiner Pope
这个结果确实说得通。你说大约 2 KB。我们做个合理性检查,看看它可能是怎么来的。人们有两种方式可以用较少字节数为 token 执行注意力。其一是稠密注意力,并在多层间大量复用。Character AI 有篇博客谈到了这一点,采用长短上下文交替的方式。在 Character AI 类模型中,Gemma 模型也采用了这种做法:共享全局上下文,也就是我们这里讨论的上下文,并将它用于所有层。
要把数值做到 KB 级别,比如可以用常见的 128 维 d_head。字节数通常等于注意力层数量乘以 2,再乘以 d_head,最后乘以 KV 头数量。这表示每层中独有的上下文数据。
上下文会在许多层之间共享,还是每层只用一次?在 Character AI 类模型里,这个数是 1。我们刚才说的是 128。这个取值通常在 1 到……抱歉,我说的是 KV 头。
Dwarkesh Patel
头和 KV 头有什么区别?
Reiner Pope
KV 头是保存在内存中的头,用来存储先前 token 的内容。Q 头是检索头,只临时使用,由当前正在关注上下文的 token 使用。在自回归场景下,所有历史上下文都对应 KV 头,而这个新 token 对应 Q 头。
Dwarkesh Patel
不过这个头的维度是 128。
Reiner Pope
哦,抱歉。d_head 指向量的维度。KV 头数量通常在 1 到 8 之间。比如 8 个 KV 头、每个 d_head 是 128,就完全可以得到这个数。也可以减少 KV 头、增加层数。
这是一种通过稠密注意力达到这个数值的方法。还有一种通过稀疏注意力达到的方法:这些数都可以增加,但之后会有一个 1/稀疏度的项。我觉得这个数值是合理的,只是可能稍微偏小。
Dwarkesh Patel
他们居然会通过 API 定价泄露这么多信息,真有意思。
Reiner Pope
你确实有动机把价格定得接近成本,否则别人就能抢走你的客户。
Dwarkesh Patel
也许我们还可以从输入和输出价格的差异里看出一些东西,了解这些模型的解码和预填充有什么不同。我上次查的时候,价格大概贵 50% 之类?
Reiner Pope
我不记得了。我以前见过的情况是贵 3 到 5 倍。
Dwarkesh Patel
好,这就更合理了。假设贵 5 倍。这是解码时处理下一个 token 的计算量。如果做预填充,不只是处理最近一个 token,而是并行处理所有 token。我的理解是它应该是这个值乘以预填充长度?
Reiner Pope
或者乘以整个前向过程的长度。我们可以把解码看成只包含一个 token 的前向计算,而预填充是包含许多 token 的前向计算。
Dwarkesh Patel
好,也许是前缀?嗯,内存。你不会为预填充阶段的 token 保存 KV 缓存。
Reiner Pope
我来画一下预填充是什么样的,解释得更清楚些。我们先做一段解码。然后可能再进行更多预填充。如果这是一次聊天会话,用户说了一段话,AI 生成回复,之后用户又说了一段话,我们就对这段话进行预填充。和只考虑这一种情况相比,这才是更常见的情形。
Dwarkesh Patel
实际上就像你让模型读取一个文件之类。
Reiner Pope
读取文件,或者 AI 正在回应用户输入、工具调用,或任何不是 AI 自己生成的内容。
Dwarkesh Patel
好,假设来到这里。你之前已经算过所有这些,所以只剩先前所有内容的 KV。但这需要多少内存成本?更具体地说,内存带宽成本是多少?如果使用 FlashAttention,它会……
Reiner Pope
这基本上是临时的,不会进入主存。忽略它就好。
Dwarkesh Patel
没错。那么应该就是之前所有内容。难道不就是这样吗?
Reiner Pope
其实内存耗时完全不需要调整。
Dwarkesh Patel
好,很简单。所以只要做一点调整就能适配。这个项让价格贵了 5 倍。这是为什么?实际说明了什么?它能帮助我们确定哪个变量?唯一变化的可能是计算成本增加了 5 倍。
Reiner Pope
这是一次前向过程的时间,但实际处理的 token 数多了这么多。我们关心的应该是每个 token 的成本,或者每个 token 所需的时间。
Dwarkesh Patel
我不太确定自己理解了。这是在前缀中处理下一个 token 吗?
Reiner Pope
实际上是处理整个批次。花这么多成本后,我们处理了这么多个 token,也就是预填充长度。或者说一次前向过程的长度。不是这个前缀,而是这个成本。
Dwarkesh Patel
好,就看这次前向过程。所以贵了 5 倍,输入 token 的成本贵了 5 倍。
Reiner Pope
事实上,输出 token 更贵。
Dwarkesh Patel
输出贵 5 倍。
Reiner Pope
我们想要推导出的结论是,预填充受计算限制,而解码受内存带宽限制。
Dwarkesh Patel
我们换个画法怎么样?横轴用前向过程长度,纵轴用时间 t。
Reiner Pope
我们要看每个 token 的成本,所以用 t 除以前向过程的长度。这样就对了。
Dwarkesh Patel
我想我被弄糊涂了。前向过程长度……预填充时它应该更大。
Reiner Pope
对,预填充的前向过程更长。
Dwarkesh Patel
那为什么成本会更低?
Reiner Pope
你问为什么成本会更高?因为这里要除以前向过程长度。这样会把这项约掉,但之后所有这些项也要除以前向过程长度,这会降低内存成本。
Dwarkesh Patel
好,让我想想。我们大概会有四条线。先看预填充……不如先看解码。
Reiner Pope
前向过程长度为 1 时,就是解码。长度更大时,就是预填充。
Dwarkesh Patel
哦,明白了。回到刚才的话题。计算时间 t_compute,基本上就是用这个除以前向过程长度,因此只剩下这个数。它不会随 t 变化,所以是一条平线。这里是 t_compute,然后……
Reiner Pope
这是解码。
Dwarkesh Patel
解码。好。现在看内存时间 t_mem,整项都要除以前向过程长度。分子具体是什么并不重要,曲线大概会是这样。我们称它为 t_mem。这也是解码。
因此,随着前缀长度或前向过程长度增加,内存带宽耗时会下降。这意味着,之前如果你受内存带宽瓶颈限制,现在可以不再受限。
既然他们对预填充的收费比解码低 5 倍,这确实说明他们在很大程度上受内存带宽瓶颈限制。因为 t 等价于成本,也就是租用计算资源的成本,对他们来说,解码成本在 1,预填充成本在 5。
Reiner Pope
没错。
Dwarkesh Patel
所以实际上,它严重受内存带宽瓶颈限制。真实图线大概是这样。
Reiner Pope
两条线最后仍然会相交,不过对。
Dwarkesh Patel
没错,我换成这种画法。这里是解码阶段内存时间和计算时间之间的差距。有意思。
另一个有趣的问题是,为什么缓存命中的价格低这么多?如果我没记错,缓存命中可能便宜 10 倍……按照这些模型的定价,把内容写入缓存反而更贵。但如果命中缓存,就会便宜 10 倍。大概是因为把某些东西留在 HBM 里的成本,而不是将其清除。不过如果你确实把它留在 HBM 里,再次读取就更便宜?
Reiner Pope
没错。生成一个 token 的 KV 缓存有两种方式。一种是从头开始,用 token ID 重新计算出来;token ID 本身非常小。另一种是之前已经生成过,并把它存在某个内存里。
价格比说的就是这两种生成方式的成本比。缓存未命中意味着所有内存里的副本都已经删掉了,你必须直接根据 token 重新计算。还可以更进一步,考虑把缓存放在哪一层内存。可以放在 HBM,也可以放到更慢、更便宜的内存里,比如主机上的 DDR,或者闪存。你可以计算把数据放在哪一层最合适,而这和你打算保存它多久有关。
我们要比较几种内存层级的存储成本,以及重新物化的成本。重新物化指删除 KV 缓存之后,从头重建全部缓存的成本。基本上,它会随上下文长度增加。实际上我们会看每 token 的成本,所以不用每次都带上上下文长度。
要重新物化一个 token 的 KV 缓存,只需对整个模型执行一次前向传播。这就是计算时间。我必须按照 GPU 的速度重新执行计算,再乘以每秒 GPU 使用费用。
Dwarkesh Patel
抱歉问个基础问题,为什么这里没有二次项?
Reiner Pope
确实有二次项,它包含在计算量里。为了近似,我选择把它去掉。我快速解释一下它是什么样的。看每 token 成本,或者每 token 的 FLOPs:有一部分 FLOPs 来自权重矩阵乘法,和……有关。
Dwarkesh Patel
这部分是平的。
Reiner Pope
……上下文长度。另一部分乘法来自读取 KV 缓存,它会随注意力涉及的数据量线性增加。这条线的斜率非常小,所以画出来时,可以很好地近似成水平线。要到百万 token 量级,才能开始明显看到二次项,也就是这个线性项的影响。因此它不太重要。
Dwarkesh Patel
那么,如果确实如此,为什么没有公司提供超过 100 万 token 的上下文长度?
Reiner Pope
长上下文有两种成本。一种是内存带宽成本,我们已经花了很多时间分析,也就是这一项。另一种是计算成本。受基本原理约束,计算成本的斜率几乎总是远低于内存带宽成本。真正限制超长上下文的主要因素是内存带宽和内存容量,正是这个影响。
Dwarkesh Patel
Dario 在播客里提过一种观点,其他人也说过:“AGI 不需要持续学习,只要有上下文学习就够了。”如果相信这一点,就必须认为上下文长度要达到一亿 token,才能相当于有一个员工和你一起工作一个月。也许稀疏注意力之类的技术可以改变这一点。但如果确实如此,就必须有某种机器学习基础设施的变化,才能支持一亿 token 的上下文,比如提升内存带宽。
Reiner Pope
稀疏注意力肯定能帮上忙,因为它把增长关系变成平方根,效果会好很多。但看看模型上下文长度的历史,从较早的 GPT-3 到 GPT-4——我不记得具体什么时候发生变化——上下文长度从大约 8K 一下提高到 10 万至 20 万。之后一两年里,它们都停留在这个范围。我认为这说明这里大概是成本较均衡的点,远远超过它会变得难以承担。
Dwarkesh Patel
这不是因为计算成本,而是因为内存带宽……
Reiner Pope
对,是内存带宽成本。坦白说,我看不到很好的解决路径。HBM 的现状就是如此,性能不会大幅提升。
Dwarkesh Patel
稀疏注意力为什么不能解决这个问题?
Reiner Pope
稀疏注意力确实能大幅改善。也许这个效果已经体现在价格里了。不过改善不是无限的,因为稀疏到一定程度后,模型质量就会下降太多。
经验结果是上下文长度没有明显增长。我认为原因是我们还没有办法突破这里的内存墙。稀疏度过高,意味着只能关注极少数 token,模型质量就会变差。
Dwarkesh Patel
有道理。
Reiner Pope
那么,重新生成 KV 缓存的这些不同方法各自成本是多少?从头计算的成本取决于 GPU 时间。为了生成缓存,我必须占用 GPU 时间做一定数量的乘法运算。
存储在 HBM 中。成本基本取决于每 token 的字节数。我需要为每个 token 留出一定字节数,把它们存进 HBM,这会占用 HBM 容量。可以这样理解:如果 HBM 里塞满了暂时没用的 KV 缓存,就无法把 GPU 用于其他任务。
怎么给它定价?也许成本和占用的 HBM 比例成正比。还有 GPU 每秒费用。我们再加一层内存,比如存到 DDR。闪存和 DDR 的成本也类似。
我把这些放错列了。应该有两列,我要区分的是读取成本和持有成本。这边是每秒成本,那边是瞬时成本。重新物化有读取成本;缓存已经删除,所以没有存储成本。我放错位置的就是这一项。它其实是单纯持有缓存的成本,我重新写一下。
如果只存进 HBM,成本结构大致如此。如果存入 DDR,就需要额外时间。公式相同:每 token 字节数除以 DDR 容量,再乘以 DDR 每秒成本。但 DDR 还有比 HBM 更高的读取成本,因为要先把数据复制到 HBM。这一项是每 token 字节数除以 DDR 带宽,而且也会占用一定的 DDR 容量。
Dwarkesh Patel
每个扩展内域都同时有 DDR 和闪存吗?
Reiner Pope
这实际上是部署选择,你可以自行决定。Nvidia 的部署方式包含两者。
Dwarkesh Patel
为什么从 HBM 读取的成本不是字节数除以内存带宽?
Reiner Pope
这取决于你如何定义读取。这里我把读取定义为:把数据移入 HBM,这样你才能开始对它执行推理。
Dwarkesh Patel
因为如果它已经在 HBM 里,你就能一边计算,一边把数据从 HBM 搬到 SRAM?有意思。
Reiner Pope
对,比如说。这是三种情况,看来我把它们的顺序排错了。一般来说,如果你要平衡两种成本,而且内存层级有不同的层,那么一种成本上升时,另一种应该下降。你可以看看哪些位置的成本为零。我应该把它们按这个、那个、再那个的顺序排列。
如果你只打算保存很短时间,那么这些成本都要乘以保存时长。这个要乘,那个也要乘。
Dwarkesh Patel
有意思的是,他们对写入不同内存的价格也不同。你会在 API 里选择保存 5 分钟还是 1 小时吗?这暗示 5 分钟用 HBM,而 1 小时用 DDR。
Reiner Pope
我觉得这是很合理的假设。看具体数字,也可能是内存再往下一层,也就是 DDR 和闪存的区别。
Dwarkesh Patel
有意思。我来查一下价格差异。基础输入 token 的价格是每百万个 5 美元。
Reiner Pope
基础价格,也就是重新物化的成本。是 5 美元。
Dwarkesh Patel
那就是 5 美元的“读取”成本。然后写入成本——大概是写到 HBM,保存 5 分钟——是 6.25 美元。
Reiner Pope
也许我们可以通过保存时长推断它对应哪一层内存。
Dwarkesh Patel
5 分钟和 1 小时。
Reiner Pope
没错。我觉得这很可能是对应内存层级的数据排空时间。意思是,既然我知道要保存某些数据 5 分钟,我就应该选择一种能每 5 分钟完整读取一次的内存。粗略地说,整个内存每 5 分钟可以读一遍。这就是它的数据排空时间。因此,容量除以带宽应该等于 5 分钟。
我们对 HBM 做过计算。HBM 的这个数是 20 毫秒,所以容量太小。DDR 可能和这个数差一到两个数量级,所以大概是几秒,比如 1 到 10 秒。这些数我没记住,不过通常内存层级越慢,闪存就越可能需要 1 分钟左右。然后是速度差别很大的机械硬盘,可能需要约 1 小时。所以这也许能说明闪存和机械硬盘分别对应哪一层。
Dwarkesh Patel
抱歉,为什么要这样计算?是存储容量除以带宽吗?
Reiner Pope
这里有多个内存层级,我们列出了四种。选择哪一层是为了最小化成本。你占用了设备的多少比例?有一部分设备容量用于保存数据,另一部分设备容量用于读取数据。假设我用了设备的 10%,那么我希望这两部分占比相等。这就说明我选对了。
假设这里有一段运行时间。我会在这段时间里保存数据,这叫保存时间。然后还有一段读取时间。要让两种成本相等,读取时间应该等于保存时间乘以容量占比。因为这里表示读取时间,而它也表示我还能同时保存多少其他数据。
Dwarkesh Patel
也就是说,你要把数据保存足够久,让它在内存中的时间等于把所有数据读入和读出的时间。
Reiner Pope
大致如此。我觉得这很可能说明这两层分别是闪存和机械硬盘。我很惊讶居然还在用机械硬盘,因为它实在是太老的技术了。
Dwarkesh Patel
有意思。而且它慢到要花一小时才能装入相当于自身全部容量的数据,这也太离谱了。
Reiner Pope
它确实不太理想,但在一些场景下仍然有用。
§07神经网络与密码学的趋同演化
Dwarkesh Patel
我们坐下来谈,是因为有些问题不需要黑板。我看到你有一篇很有意思的博客,从较高层次上讨论了不同密码协议的架构和神经网络很像。它们有一种趋同演化,因为都要把所有输入的信息混合、打乱。密码协议这样做是为了确保哈希函数的每个新输入都会彻底扰乱结果。神经网络则需要考虑一条信息如何改变我们对另一条信息的理解。
我觉得这非常有意思。从较高层次来说,它们某种程度上是在做相反的事。密码协议要把有结构的信息变得像随机数一样,无法区分;神经网络要把看起来随机的东西——蛋白质序列、DNA、杂乱文本——转化为更高层次的结构。它们采用了相似的高层机制,实际上却在做相反的事。你怎么看?
Reiner Pope
我也会去寻找混合、打乱信息的其他例子。这几乎有一个物理层面的例子:做蛋糕时搅拌面糊。先朝一个方向搅,再朝另一个方向搅,这种办法确实不错。
回到数字世界,它们确实有一些区别,而你指出的这一点是很明显的区别。比如,如果随机初始化一个神经网络,它也许能成为一种不错的密码,因为随机初始化会以复杂的方式打乱信息。它甚至可能实现你想要的效果,谁知道呢?
神经网络之所以可解释,是因为梯度下降。你可以对神经网络求导,得到有意义的导数。我们会做许多工作来避免导数过于复杂,例如残差连接能让它保持受控和简单,LayerNorm 也有同样作用。
针对密码的一种重要攻击方式也是对密码求导。密码使用不同的数域,它们运算的域只有两个元素,也就是二进制;而神经网络理论上运算在实数域里。你需要对二进制数求导,但密码绝对可以求导。这叫差分密码分析。
基本来说,这种分析说明:给输入加入一个小差异,并不难,但很难让输出也只产生微小变化。设计良好的密码,其职责就是让输出产生很大变化。区别在于,此时的优化目标是让结果更复杂。它们没有像 LayerNorm 那样的残差连接。
Dwarkesh Patel
我想两者有交集的一处是后门。给 LLM 安插后门时,你试图隐藏……你会把它算作输入吗?它不是前向传播的输入,却是反向传播的输入。你试图把一个输入隐藏在反向传播中。
Reiner Pope
这是对抗场景吗?这里其实会出现和密码完全一样的雪崩效应。针对图像分类模型的对抗攻击,会寻找对图像做出的一个很小扰动,让分类结果彻底改变,也就是让输出完全变化。这在密码中是常见情况,但在神经网络里却是不希望出现的情况。
Dwarkesh Patel
好,我本来是想问神经网络是否真的用于密码学。我们觉得也许在黑板上讲更合适。它们真的用于密码学吗?
Reiner Pope
用神经网络做密码学……一般而言,创建一种新密码是非常危险的。几乎所有新密码都被破解了,99% 都被破解,所以大概不该从这里入手。但反过来,有至少一个明确案例证明这种做法很有成果。
一种在密码学中已有的构造,后来被引入神经网络,叫 Feistel 密码或 Feistel 网络。其思路是,你可以有一个函数 f,它不可逆,但你喜欢这个函数,因为它能做一些有意思的事情,比如作为一个 MLP,或者以有趣的方式混合信息。
你想基于这个函数构造一个可逆的东西。接下来要构造一个有两个输入而不是一个输入的函数。我们先计算 f(x)。但还得记住 x 是什么,所以把 x 放在这里,方便之后反向恢复;同时也不能丢掉 y,因此我们要保留 y,再把它们加起来,组成这个二元组。
要反转这个过程,假设我拿到输出,想恢复 x 和 y。x 很容易恢复,就在这里,直接读出来即可。如果这一项叫作 z,那我可以用 z 减去 f(x) 来恢复 y,因为 x 已经恢复出来了。因此这个构造是可逆的。
这种方法过去在密码里大量使用,现在也仍然在用。它是构造密码的主要方法之一。密码往往需要可逆,尤其是密码中的各层,因为这样能提供更好的密码学性质。
这种方法后来确实被移植到了神经网络。2017 年有一篇论文叫 RevNets,也就是可逆网络。它让整个网络都可以逆转。比如可以用在 Transformer 网络上。我先执行一次前向传播,然后也可以把整个过程反向运行。整个神经网络都可逆,正是用的这个构造。
这篇论文把它应用在某个层上,比如 Transformer 层。我们有一个函数 f,也就是 Transformer 层。普通结构只有一个输入,然后通过残差连接得到一个结果,再把结果加到这里。现在稍作变化:有两个输入 x 和 y。x 经过函数后与 y 相加,结果成为新的 x,也就是输出 x;接着这个 x 成为输出 y。
如果回看前两层,实际效果就是你刚才说的那种连接:残差连接跨越两层。y 来自前一层,并在那里作为残差连接。正是因为这样的结构,整个网络是可逆的。
为什么要关心可逆性?有什么用?一个重要用途是训练。以训练时的前向传播为例,假设有四层,按 0、1、2、3 的顺序运行。必须把所有激活值写入 HBM。这里的 HBM 占用量大致随层数线性增长。
这可能是训练期间最大的内存占用。这是普通训练过程,随后执行反向传播并反向读取。前向过程从前往后,反向过程从后往前。我必须把它们重新读出来。
RevNets 论文的思路是:既然网络可逆,我完全不需要保存这些激活值,而是可以重新物化。先执行前向传播;执行反向传播时,再同步地一步步撤销之前的前向传播,以恢复这里所需的激活值。这样可以节省内存,是个不错的思路。
Dwarkesh Patel
有意思。某种意义上,你用更多计算换取更少内存。
Reiner Pope
没错。
Dwarkesh Patel
有意思。这和 KV 缓存的做法正好相反:KV 缓存是用更多内存换取更少计算。
Reiner Pope
是。考虑到现有硬件的状况,用更多内存节省计算通常是划算的。
Dwarkesh Patel
有意思。这次对话非常有趣。Reiner,非常感谢你来讲解。我觉得它确实证明了演播室和黑板这个设想是正确的。
Reiner Pope
是啊。
Dwarkesh Patel
好,非常感谢你来参加。
Reiner Pope
谢谢。