Reiner Pope:大语言模型训练与服务背后的数学
原文:Reiner Pope – The math behind how LLMs are trained and served
访谈正文
与Reiner Pope做了一期非常不同的形式——一场黑板讲座,他一步步讲解前沿LLM是如何被训练和服务的。
令人震惊的是,仅凭几个方程、公开的API价格和一些粉笔,你就能推断出实验室在做什么。
这有点技术性,但我鼓励你坚持看下去——真的非常值得。
世界上很少有人能像Reiner那样理解AI的全栈,从芯片设计到模型架构。能向他学习真是一大乐事。
我写了一些闪卡和练习题来帮助自己记住Reiner教的内容。希望对你也有帮助!
Reiner是MatX的CEO,这是一家新的芯片初创公司(完整披露——我是天使投资人)。他此前在Google工作,从事软件效率、编译器和TPU架构方面的工作。
强烈推荐他合著的扩展手册以供进一步学习。
在YouTube上观看这一期,这样你就能看到黑板了。
文字记录
批大小如何影响词元成本和速度
Dwarkesh Patel:
今天,我采访的是Reiner Pope,他是MatX的CEO,这是一家新的芯片初创公司。此前,他在Google从事TPU架构以及许多其他工作。这与我通常的采访形式非常不同。这将是一场黑板讲座。我们马上就会起身。事实上,我们建造这整个新演播室时就是专门考虑到了这种形式,所以能与你一起为它揭幕真是荣幸。
我们将讨论模型架构、ML基础设施以及许多其他内容。我认为这是一个重要话题,因为一旦你理解了训练和推理在集群中是如何运作的,很多事情——关于AI为什么是现在这样、AI架构为什么是现在这样、API价格为什么是现在这样,以及从根本上说AI进展为什么是现在这样——就开始变得合理了。你需要理解细节才能达到那一步,而你需要一块黑板来理解细节。Reiner,非常感谢你做这件事。
Reiner Pope:
非常高兴能在这里。
Dwarkesh Patel:
完整披露,我是MatX的天使投资人,但这与本期播客无关。Reiner,作为开场,我要问这个问题。我们有Claude、Codex和Cursor这样的公司提供类似Fast Mode的功能,花6倍的价格,它们会以2.5倍的速度向你流式传输词元。从机制上讲,我很好奇这里发生了什么。为什么你可以付更多钱来获得更快的延迟?
第二,你能继续加码吗?你能付100倍的钱,然后以某种方式获得快得多的速度吗?第三,你能反过来吗?能不能有类似Claude Code的“Slow Mode”,如果你愿意等上好几分钟,你就能得到更便宜的价格?也许这有助于引出你将在整个讲座中进行的分析。
Reiner Pope:
好的。稍微直接跳到结论,最大的效应是批大小。我们现在要做的就是精确量化它是什么样子,以及它对延迟和成本的影响。还有另一个效应,你可以称之为投机解码或多词元预测。我们也许可以稍后再回到那个,但我们要先讲的是批大小。
我想介绍的是分析的两条原则。首先,我们要看一个屋顶线分析,关于我们如何在芯片集群上运行Transformer模型。我们会拿一个Blackwell NVL72集群,也就是一机架72块GPU。屋顶线分析意味着我们看内存带宽和计算性能。另一方面,我们只看模型的两个简单因素:操作权重的时间,以及操作上下文即KV缓存的时间。
让我们开始吧。我们要尝试估计运行某个形状的推理所需的时间。我们在这里并不完美。我们无法精确预测时间,所以我们将改为近似。我们会说时间必须大于或等于某个量。我们要考虑两个不同的方面:做内存读取所需的时间,以及做计算所需的时间。结果会证明,即使是一个简单的模型,这也给了我们非常强的预测能力。
逐一来看,做计算所需的时间是多少?在计算中我实际上需要做两件事。我需要乘以所有活跃的参数,然后我需要对注意力做一些工作。乘以所有活跃参数,我有一个正在运行的特定批大小,我的模型中有一定数量的活跃参数。然后我只需将其除以计算吞吐量,也就是芯片的FLOPs。这是一个硬件方面的问题。
这涵盖了所有权重矩阵乘法的全部计算时间。这里有一个小提醒。我们忽略了做任何注意力计算的时间,但一般来说,与这个相比它会相当小。所以我们忽略它。
Dwarkesh Patel:
我会时不时打断一下,问一些非常天真的问题,或者澄清一些基本要点。对观众来说,你不是一次只服务一个用户。批指的是你同时服务许多不同的用户,那就是一整批。
Reiner Pope:
我至少可以稍微解释一下批的动机。我们会确切看到为什么批是如此有利的优化。结果会证明,如果你不把许多用户批在一起,你得到的成本和经济性可能比把许多用户批在一起差一千倍。我们将能够相当明确地看到这一点。
然后是活跃参数的数量。如果我看看,例如一个DeepSeek模型,DeepSeek V3模型大约有370亿活跃参数,以及7000亿总参数。我们只关注单个AI词元所活跃的那些。
我们在建模计算性能。我会一直写等号,但在所有这些情况下,你可以把这个时间看作至少这么多,也许还有一些我们忽略的项。
在内存方面,我们需要对内存做什么?我们需要读取所有权重,所以读取总参数数量(而不仅仅是活跃参数)需要一些时间。有权重读取时间,此外还有KV缓存读取时间。这实际上取决于批大小。对于批中的每个元素,我们必须读取整个上下文长度的词元,并且每个词元有一个大小,即一个词元的字节数。这是一个模型参数。
Dwarkesh Patel:
也许先退一步,让我们快速解释一下KV缓存是什么。
Reiner Pope:
当我做一次前向传播……让我画一下自回归推理是如何工作的。这是在解码期间。如果我有一堆文本词元……我画的是一个张量,因为最终词元在某个嵌入维度上被表示为张量。在这个方向上,我有序列长度。
运行解码的工作是,我必须让每个词元经过一堆不同层中的一大堆矩阵乘法。一般来说,我将不得不对所有这堆词元做这项工作。但解码的一步只是在这里产生这一个额外的词元。
我在那里要做的是运行一次完整的前向传播,乘以整个模型中的所有权重矩阵。但然后我有这个注意力机制,这个词元在看着所有过去的词元,它具体在看什么?它在看模型对词元产生的某种内部表示,我们称之为KV缓存。这个单个词元关注所有历史词元的过程就是注意力。它主要由内存读取主导,而不是矩阵乘法。
所以我们这里展示了正在获取的内存量,然后这当然只是除以内存带宽,也就是每秒的内存字节数。事实上,这里的这些方程已经足够让我们现在画出一些拟合线了。我们想研究的是对批大小的敏感性,然后还有对上下文长度的敏感性,后者我们会单独画。我们说过,你能获得的大效应是在批大小上延迟与成本之间的某种权衡。
让我们把它们画出来。我觉得我们真正想画的其实只有两张图。我们首先在这里画批大小对时间。当我们看它的形状时,我们有一个和的最大值,然后还有另一项。让我们逐项来看它们如何缩放:计算的时间和内存的时间,以及它们如何呈现。
让我们先看这个计算时间。它纯粹是批大小的线性函数,没有偏移,所以它是这样一条曲线。这是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用上一定秒数,比如一毫秒或20毫秒。我必须为那段时间支付租用费用。所以是每GPU每小时2美元之类的。
那就是这次推理的成本,但我在那次推理期间处理了多少词元?那就是批大小。我们真正想画的是成本对批大小,也就是t除以B对批大小。这是每个词元的成本。我们必须想象把这三条曲线中的每一条都除以B,也就是乘以这个倒数。我们最终得到的是……计算曲线原本是线性的。我们除以B,这就使它在这里变成一个常数。这是t计算。KV获取原本是线性的,现在它也变成一个常数。然后权重获取原本是常数,现在我们除以了B,所以它变成这条双曲线。
同样,我们要计算和的最大值。这两项的和把双曲线向上移。KV获取与权重获取的和给了我们一条更高的双曲线,像这样。然后我们要与这里的计算取最大值。我们最终得到的就是我们关心的整体形状。
同样,我们看到一些极限行为。成本在批大小为1时起初非常高。它几乎趋于无穷,因为我们有那么多权重获取没有被大批大小摊销。但随着我们增大批大小,权重获取被摊销到那么多不同的批元素上,它们的成本变得非常小,最终计算时间开始主导成本。所以成本有一个极限下界,就是这里的这条线。
Dwarkesh Patel:
所以Claude Code Slow或Codex Slow之类的就只会落在这条线上。它帮不上什么忙,因为你没法把KV值摊销到更大的批上。
Reiner Pope:
它们对每个批都是独特的。计算对每个批也是独特的。那么,在把其他一切都摊销掉之后,你每个批最少能做的工作是什么?
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。
这实际上给了你一个与实践惊人地准确的大致范围。一般来说,人们会稍微比这大一点。他们并不真的想正好处在平衡点上,因为现实世界的效率并不像roofline分析说的那么好。但拿这个数,也许翻倍或三倍。
Dwarkesh Patel:
好,所以是每批两到三千个词元。但如果你把KV缓存也算进去,那含义就是最优批大小……
Reiner Pope:
应该变得更大。我们解出的是计算时间等于内存时间时的等价关系。如果我加入一些消耗更多内存带宽的东西,那么我能用于权重加载的就少了。我需要把内存带宽增大更多,因此批大小也要更大。
Dwarkesh Patel:
这看起来小得不可思议。这还不到一个序列,对吧?
Reiner Pope:
记住,我说的是我要再生成一个词元的那些词元的数量。实际上是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吉字节除以每秒20太字节。这算出来大约是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:
我们可以对此做一点分析。你可以从用户数量的角度来想,但更有成效的思考方式是从每秒词元数的角度来想。这个批次大小对系统的每秒词元数意味着什么?
每秒词元数将等于批次大小。我们运行一批词元,并且我们每个时间间隔做一次,这个间隔等于15毫秒或20毫秒这个数字。这最终是批次大小乘以大约60,所以是64 x B。这最终大约是2,000 x 64,也就是每秒128,000个词元。这是更容易消化的单位。
并发用户很难推理,但一个系统的全球流量是多少?当你看到一些公告时,有时API提供商会吹嘘他们有多少流量。我记得去年Gemini一些公告中的数字是全球每秒数亿个词元。这是那个的千分之一。
Dwarkesh Patel:
Gemini很大。Gemini的千分之一也很多。要在规模上真正有竞争力,你至少得能服务Gemini的千分之一。这很有意思。
稀疏性越高,你需要的计算就越少。根据这个分析,随着批次大小变大,计算确实最终会成为瓶颈。那么问题就是,你能把稀疏性推到多远?随着稀疏比例增加,随着你相对于总参数拥有更少的活跃参数,模型性能会下降多少?它下降的速度是否快于你通过提高稀疏因子所节省的计算?
Reiner Pope:
你指的是模型的质量,而不是模型的速度。不幸的是,我们无法分析性地回答这个问题。那是一个关于模型质量的实证问题。我最多只能调出一篇论文,从实证上回答它。
Dwarkesh Patel:
我们现在要调出那篇论文吗?
Reiner Pope:
这篇论文是“Unified Scaling Laws for Routed Language Models”。到这个阶段它已经算是有点老的论文了,但他们研究的一件事是,如果我持续提高稀疏性,对模型质量的影响是什么?这个答案对专家混合的实际选择非常敏感。专家混合已经存在很长时间了,也许早在2017年就有了,但技术已经变化了很多。DeepSeek的专家混合在其工作方式上是一个巨大的变化。还有更早的论文,比如“GShard”和“Switch Transformer”。实际的实证结果将取决于所有这些。

在这里展示的其中一种较老的技术中,你可以看到,如果我把活跃参数的数量保持在某个固定规模,然后增加稀疏度——他们称之为专家数量——质量会持续提升。如果你想象从1.3B稠密模型画一条水平线横穿过去,你会发现,在这种情况下,64专家的370百万激活参数模型与稠密13亿模型一样好。
Dwarkesh Patel:
所以从某种意义上说,这实际上并不是惊人的回报,你需要把总参数增加一百倍,才能获得相当于10倍活跃参数的等效效果。
Reiner Pope:
实际上还不止。参数数量是巨大增加,而效率只是适度提升。
Dwarkesh Patel:
所以在这种情况下,实际上是4倍?
Reiner Pope:
64倍换4倍。
Dwarkesh Patel:
所以,虽然如果你提高稀疏性,你确实能获得节省计算时间的好处,乍看起来这似乎是值得做的权衡。但如果你把它降低2倍,然后每次把稀疏性翻倍时这个就上升8倍……
Reiner Pope:
这到底是好还是坏呢?即使从内存的角度看……记住,你是在把这部分内存读取翻倍,而这部分是被批次摊销的。所以只要继续运行更大的批次大小就行。从我们这里所做的分析的角度看,这是纯粹的收益。基本上一直这么做,直到你用尽可用用户为止。
这里有一种等价关系:如果我有很多用户,我就可以转向稀疏得多的模型。从这个角度看,这是个合理的权衡。这里出现的另一个权衡是,它也会消耗内存容量。我们这里只推理了内存带宽,但它也会消耗内存容量。
Dwarkesh Patel:
我明白了。让我确认一下我理解了。你是说我们想花更少的时间计算,因此我们做更多稀疏化。为了让这行得通,我们需要更大的批次大小。这意味着我们需要更多内存容量来支持更多稀疏性。
Reiner Pope:
也许这里是个好时机,来谈谈专家混合层通常是如何布置在一机架GPU上的。
MoE模型如何布置在GPU机架上
Dwarkesh Patel:
好。说得通。我们刚才讲到哪了?
Reiner Pope:
稀疏专家混合。也许讲讲我们如何把它布置在GPU上。
我们先放大看专家混合层,画出它是什么样子。通常,我们会有一个某种路由器层,它负责决定把词元路由到哪里。词元从这里进来,经过一个路由器层,然后我们有一堆不同的专家。我再多画几个,好排成一排。
路由器会决定它要路由到哪些专家,而这只会是其中很小一部分,也许是32个里选1个。也许它会决定路由到这个,也许这个,也许这个。每个专家本身就是一个普通的MLP。它有一个上投影,然后中间有一个非线性,接着是一个下投影。
然后最后,我们做逆操作。之前我们在这里把东西广播出去,现在我们要把它们收回来并求和。像这样把它们收回来。然后最后,我们有残差连接。词元也会从这里传过去,并被加到MoE层的结果上。这就是一个普通的MoE层。
我想讲清楚的是,这如何映射到一个GPU机架上,以及这对通信意味着什么,因为我认为这会开始显示出我们能稀疏到什么程度的一些限制。这里的标准做法,也是最好的解决方案,是使用专家并行。这意味着不同的专家放在不同的GPU上。如果我们拿像DeepSeek模型这样的东西来说,它们有256个专家。假设我们想在一个Blackwell机架上运行它。那里有72个GPU。
我们有一个整除性问题。这不是2的幂。我们就简化一下,说我们只用其中64个。忽略另外8个就行。这不是什么大问题。所以我们每个GPU有4个专家。非常简单。为了画图方便,实际上我们就说每个GPU有2个专家。我们最终就把这些GPU边界放上去。每一对专家都在自己的GPU上。
然后我们可以看看通信成本。我们有一些词元集中存储在这里。它们被路由到所有这些专家,这里要付出一些通信成本。输出端也要付出同样的通信成本。我们希望这不会成为通信受限的瓶颈。
那么这里的流量模式是什么?这里的流量模式是,任何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放在机架的外侧,然后把交换机放在机架的内侧。这最终形成的是,这里面有一组交换机。这些是NV交换机。然后他们铺设一堆线缆。每一个GPU都有线缆连接到中间的交换机。交换机连接到所有GPU。所有GPU都可以在仅仅两跳内与所有其他GPU通信:到交换机,再到另一个GPU。
现在,当我想离开机架时,我最终要走一条不同的路径。GPU还有一种慢得多的连接,通常大约慢8倍。我在这里GPU盒子里画的绿色是NVLink。更一般地说,它被称为纵向扩展网络。你通常还会有一个横向扩展网络,它允许你连接到某个数据中心交换机。所有GPU都会有一些连接到某个地方的数据中心交换机。这就是横向扩展,它的带宽通常大约慢8倍。
如果你想把一个专家混合层铺设在两个机架上,挑战在于这里一半的GPU会想要和这里的GPU通信。平均而言,当我看看这些GPU上的词元想去哪里时,一半的词元想去机架内部。这很好。它们可以使用快速的纵向扩展网络。但一半的词元会想要离开机架去另一个机架,这就没那么好了。它们需要使用慢得多的网络,所以这就成了全对全模式上的瓶颈。
另一种选择是,为什么我不在这里放一个大交换机,把所有东西都连接到一个更大的交换机上,实际上把两个机架合并在一起?这个方向有很多想法,但一般来说,你之所以有这种交换机层级结构而不是一个大交换机,是为了管理线缆拥塞。你只是需要铺设大量线缆。
Dwarkesh Patel:
抱歉,你刚才问的问题基本上就是,为什么它不是一个更大的纵向扩展?
Reiner Pope:
正是。为什么不在纵向扩展中直接放一百万个芯片,或者一千个芯片?
Dwarkesh Patel:
是什么变化让Nvidia能够从Hopper的8个,到Blackwell的72个,而现在Rubin将会是……是500多个吗?
Reiner Pope:
对,500多个。
Dwarkesh Patel:
是什么让这成为可能?
Reiner Pope:
从Hopper到Blackwell,主要只是决定从以托盘为形态因素切换到以机架为形态因素。这是一个产品决策。那里没有实质性的技术障碍。
从64个切换到500个左右,那里有一点Jensen数学,但至少有一个真正的4倍增长,这来自于一个复杂和困难得多的机架设计。那实际上是一种新的物理设计,用来铺设更多线缆。
Dwarkesh Patel:
线缆的复杂性只是弄清楚哪根线缆跳到哪里的成本,或者哪个信号从什么到什么?
Reiner Pope:
让我们放大看这个,看看线密度。我再画一次这个图,这样我们有一个更干净、更大的版本可以用。
假设我在中间有一些交换机。最初,我先从每侧只有两个GPU或每侧两托盘GPU开始。假设也许每个托盘想有两根线缆伸出来。我物理上铺设像这样的垂直线缆,延伸到交换机。现在如果我想把机架中的GPU数量翻倍,我需要铺设字面意义上两倍密度的线缆。我也需要铺设这些。
Dwarkesh Patel:
一个极其天真的问题。但如果你看一个物理数据中心,似乎机架内有很多空间。我不知道。线缆真的很大,而且……
Reiner Pope:
机架外面有空间。机架内部……随着它们变得越来越优化,这些机架非常紧凑。从托盘进入机架和机架背板的连接器密度,以及背板本身都有非常高的密度。还有其他物理约束,包括线缆的弯曲半径。你不想把它们折断,等等。
Dwarkesh Patel:
好吧,所以字面上就是放线缆的物理空间在限制它。我完全不知道。有意思。这似乎令人惊讶。机架这么大,我们却不能只是往里面塞更多线缆。
Reiner Pope:
机架设计不是我的专长,但当我与人们谈论他们面临什么约束时,这是多种因素的组合。你在优化哪些大的物理因素?空间、机架的重量。它实际上非常重,所以你需要足够的金属来防止它下垂和倒塌。但然后你加更多金属,它就更重。然后是供电和冷却。所有这些都在相互竞争。现代机架正在把所有这些推向非常极端的物理极限。
Dwarkesh Patel:
GPT-4是什么时候发布的来着?是2022年还是2023年?
Reiner Pope:
2023年。
Dwarkesh Patel:
好。而且当时有传言说它的参数量超过一万亿。看起来直到现在,也就是过去六个月里,才开始有模型发布出来,其参数量显著超过三年前发布的那个模型,而按理说这期间本应发生这种规模上的扩展。
原因是不是我们一直在等待内存足够大的机架,以便容纳一个五万亿参数的模型,再加上为足够多用户、足够多序列所需的KV缓存?或者如果你在做强化学习,也有类似的考量,即实际要为你试图解决的那一批问题保留KV缓存。
如果你看Hopper,你有八个Hopper,我记得截至2022年那是640 GB。而Blackwell终于——它是在什么时候部署的……?
Reiner Pope:
非常近。也许是去年。
Dwarkesh Patel:
去年。你终于有了10到20 TB量级的scale-up域,这足以容纳一个5T模型加上KV缓存。
Reiner Pope:
在更大的scale-up域中部署是一个巨大的解锁。我在这里画的是Nvidia Blackwell的部署。Google的部署实际上长期以来一直拥有非常大的scale-up域。
Dwarkesh Patel:
这也解释了为什么Gemini看起来领先。看起来Gemini成功进行预训练的时间比其他一些实验室都要长。
Reiner Pope:
我当时不在那里,所以我不确定其中有多少来自成功部署了更高的稀疏率,这有可能。也可能是一大堆实际的建模方面的东西,特别是你如何做专家混合。我们看到DeepSeek的专家混合激活了更多专家,但专家粒度更细。那是一个重大创新。我相信在模型架构以及训练数据方面还有许多其他创新。
很难把它们一一区分开,但从你能做什么的极限来看,正如我们所见,活跃参数量受计算成本限制,而总参数量受scale-up规模限制。
流水线并行如何将模型层分布到多个机架上
Dwarkesh Patel:
当你在单个scale-up域内运行时,这是一个专门针对前向或后向的考量,还是专门针对预填充与解码的考量?还是说无论你手头是什么类型的工作负载——无论你是在做预训练运行、强化学习生成,还是面向用户的推理——都最好始终待在scale-up域内?
Reiner Pope:
非常有意思。要回答这个问题,我们需要谈谈通信模式。我们已经谈过专家混合的通信模式。那就是这种all-to-all。All-to-all非常强烈地偏好全连接,也就是我们刚刚在这里展示的,并且它偏好待在单个机架内。
除了我们刚刚展示的专家并行之外,还有其他种类的并行。文献中有张量并行。随着专家变得越来越小这一趋势,它的相关性已经大大降低,所以我们可以忽略它。但我们可用的另外两样东西是数据并行和流水线并行。它们可以更适合使用多个机架。
让我们专门聚焦于流水线并行。这是MoE的一层。在上面我还会有一百多层。此时我可以决定,比如说,切换到一个不同的机架,换机架。那么这会成为通信瓶颈吗?我们实际上可以解出它在什么时候会成为通信瓶颈。在我们用代数方法做这件事之前,让我们先把它可视化出来,勾勒一下路径。我们会有另一个MoE层,这里再来一个MoE层,以此类推。
假设我在这里换机架,然后在若干层之后,我在这里也换机架。我们用来判断在换机架之处是否存在通信瓶颈的方法,是比较scale-out带宽需求与scale-up带宽需求。让我们把它写下来。提示是:这里的发送要多得多。我们在这里发送许多东西,而在这里我们只发送一样东西,而且我们可能还要做很多次。这就是造成差异的原因。
Dwarkesh Patel:
我能试着猜一下吗?纯粹出于好奇,看看我是不是真的理解了,看起来你是把batch size发送进机架。
Reiner Pope:
在这里面?是的。
Dwarkesh Patel:
但机架内部的通信是batch size乘以GPU数量。
Reiner Pope:
是激活的GPU数量。我根本不往这个GPU发送。在这个图里,从这里到那里有1到3倍的膨胀。关键在于,我甚至根本不需要往这个GPU发送,所以这是一个很大的节省。
我们要讨论一下scale-up相对于scale-out在多大程度上是个瓶颈。我们将直接跳到花在scale-up上的时间与花在scale-out上的时间之比。这就是我们谈论的量。
第一个考量是,scale-up一般比scale-out快8倍。在基准情况下,如果带宽相同,我们会有这个1/8,它来自带宽。但随后我们在发送多少数据上还有一定程度的膨胀。如果一个词元从这里进来,那么这一个词元会被路由到——在DeepSeek的情况下也许是32个专家或16个专家。它会被路由到若干个专家。所以这就是激活专家的数量。同样的事情适用于多个不同的层,所以也许我要跑两层。还有每个stage的层数也要乘上去。
Dwarkesh Patel:
你不需要把整个东西乘以2吗,为了那个all-to-all?
Reiner Pope:
为了上行和下行。是的,有一个因子2。谢谢。
我们希望的是scale-up时间大于scale-out时间,因为scale-up时间是更重要、更宝贵的资源。我们希望这个数大于或等于1。这看起来真的不难。我们只需要克服一个因子8。所以我们需要这三样东西的乘积大于8。通常我们有相当多的激活专家数。它本身可能就是8。然后我们可以把每个stage的层数增加很多,直到满足这个条件。
这最终看起来的样子是,我可以有一整条机架流水线,一个机架做一层,然后我移到下一个机架做另一层,然后我移到下一个机架再做另一层。
Dwarkesh Patel:
我觉得有意思的是,实践中最好的并行策略最终在物理上类似于实际的架构。它不是那种银河大脑式的东西。它就像是,“哦,我们有专家,我们要把它们放到不同的GPU上,或者我们有不同的层,我们就干脆把它们放到不同的机架上。”我觉得这很有意思。
Reiner Pope:
切分方式与模型架构相匹配。
Dwarkesh Patel:
正是。它本来可能是某种更古怪的东西,用张量并行之类的。
Reiner Pope:
银河大脑式的思考方式是:一个模型在哪些不同的维度上被放大?它按层被放大,按模型维度被放大,按DFF维度被放大,按专家数量被放大。这些数字中的每一个你都可以选择沿着它来切分。如果那些数字足够大,沿着那里切分最终就会变得有利可图。我们选择了其中两个。另外两个,在模型通常的规模设定方式下,是不划算的。
Dwarkesh Patel:
所以Ilya有一个演讲,他说:“今天我们懂得不要做流水线并行。”而且Horace He给我的朋友们和我……我很讨厌这听起来像苏斯博士的引文。但他给我们讲了一堂关于这些不同种类并行的课。他说流水线并行的问题在于,除了气泡之外,它还制造了这些架构上的约束。比如Kimi有这些残差,其中注意力会关注往回几层,所以用这种方式实现就变得很难。
Reiner Pope:
我想我们甚至没有充分阐明我们从流水线中到底得到了什么好处。这些复杂性是真实存在的。流水线是个巨大的麻烦,但它确实给你一些好处。然后你可以决定那些好处是否值得这些代价。它在推理中有一些好处,在训练中也许好处更大。在推理中,我们省下的是什么?我们省下的是内存时间还是计算时间?其实都不是。我们只是把内存时间从一个芯片移到另一个芯片,或者从一个机架移到另一个机架。在运行时上并没有实际的好处。
然而,我们省下的是内存容量。如果我们认为机架中的内存是个瓶颈,那么我们能跑多快就有一个约束。流水线让我们能够大幅减少那个瓶颈。
Dwarkesh Patel:
与此相反的涵义是……在这次访谈之前,我和 Axel 聊过,他是 Jane Street 的 GPU 性能工程师。他解释说,要做流水线,你必须做微批次(micro-batches)而不是完整批次。如果你做微批次,那么从定义上讲,你就无法在所有用户或所有序列上摊销加载权重的成本。这件事的正面涵义是你不需要用那么多内存。负面涵义是我们无法在所有这些用户上摊销加载权重的成本。也许值得解释一下为什么你必须做微批次。
Reiner Pope:
我们要不要画一下流水线气泡?这个出现在流水线并行中的微批次到底是什么?
我先聚焦在推理上。这是个稍微简单一点的问题。我要画时间,然后是我们所在的机架。想法是,也许我有四个机架。我有一个推理,它会像这样在一段时间内依次走过这四个机架。这是第零号推理。它以某个批次大小运行,像这样依次走过所有流水线阶段。
现在,如果我们说,“好吧,我们要在这里运行第一号推理”,这显然是一个巨大的浪费。每个机架有四分之三的时间什么都不做。我们实际上不会在这里运行第一号推理,我们会在能运行的时候立刻运行它,也就是第零号推理一结束就马上运行。然后我们继续下去。如果我们没有把它填上,我们会把这称为流水线气泡。当我在这个推理语境中画出来,我们只做前向传播时,这很明显。你为什么要做这种蠢事?在训练语境中,这也许不那么明显。但在推理语境中,做这个改变是非常自然的。
Dwarkesh Patel:
哦,有意思。这有点显而易见,但微批次和批次之间的区别在推理中根本不重要,因为你想怎么叫它就怎么叫它。它只在训练中重要,因为那里存在一个最优批次大小。
Reiner Pope:
是的。
Dwarkesh Patel:
在你做一个完整的反向步骤之前,你想把那个批次中的所有序列都累积起来。如果你想在训练中做流水线,为了避免那个气泡,你需要——
Reiner Pope:
我们要不要把这个训练图也画出来?我们来画吧。这是推理图,我会把这个称为前向,这样就不会有错误的东西出现在那里。现在我们为训练做同样的事。我们有一个前向传播,但在某个阶段我们将不得不过渡到反向传播。
我们会在前向传播中做若干个批次,然后我们会一次性把所有人过渡到反向传播。推理部分在这里是一样的,但然后我们在这个点做一个硬停止,把所有人过渡到反向传播,编号方式类似这样。
Dwarkesh Patel:
也许值得澄清一下,那里有硬停止的原因是,你想为反向步骤一次性做一个完整批次。然后,那个批次应该有多大,存在一个最优大小。
Reiner Pope:
实际上,更小总是更好,可以这么说。从 ML 收敛速度 的角度来看,更小总是更好,因为你从 梯度下降 中获得的是最新的信息。
Dwarkesh Patel:
但从总训练时间的角度来看呢?
Reiner Pope:
从总训练时间的角度来看,从系统的角度来看,更小更糟。最优值是这两者之间的权衡。
所以你选一个批次大小,对于那个批次大小,你做若干次前向,然后做若干次反向。你问为什么那里甚至要有一个硬停止。在流水线并行中,因为你在这里有这段空闲时间,也就是气泡,文献中有非常多的技术来以不同方式安排这个布局并避免它。有更复杂的方案,叫做 零气泡 或 一前一后,它们以复杂的方式把前向和反向交织在一起。
Dwarkesh Patel:
你可以在那个气泡里挖比特币。
Reiner Pope:
对。更有用的是,你可以做权重梯度步骤,但你也可以挖比特币。
在推理中,流水线对你关心的任何事情(比如批次大小或延迟)的影响是中性的。它不会改善它,也不会让它变糟。如果你看这个推理的延迟,把它流水线化运行与把它全部放在一个机架上运行相比……如果全部放在一个机架上,我们只要把所有方框往下滑,仍然把它们排成一行,延迟会是一样的。
流水线对延迟既不好也不坏。它确实意味着你每个机架使用的内存容量更少。因为现在你不需要整个模型,你只需要模型的四分之一,然后你可以扩展。
Dwarkesh Patel:
非常有道理。所以在推理期间使用流水线是毫无疑问的,但在训练期间存在这个更难的权衡。
Reiner Pope:
实际上,即使在推理中,它也没有被大量使用。它降低了你的内存容量需求,但实际上存在巨大的过剩。我记得你说过,一个 Blackwell 机架有几十太字节。那比一个万亿参数模型大得多。一个万亿参数模型只需要一太字节,所以它已经能装下了。流水线没有巨大的好处,因为你在减少一个本来就已经相当小的数字。
但它确实说明,理论上,也许你在那里有太多内存了。你本可以造出内存更少的不同的硬件。如果你在设计你的硬件,你可以说,“我不需要那么多内存,因为我不需要权重装在一个机架里。我可以把权重装在八个机架里,那么我本可以造出每个 GPU 没有那么多 HBM 的硬件。”
为什么 Ilya 说,“正如我们现在所知,流水线并不明智。”
Dwarkesh Patel:
宏观问题:现在每个人都在谈论 内存墙。内存变得超级昂贵。内存不够。智能手机销量将下降 30%,因为内存不够。这很令人震惊,Dylan 说超大规模厂商今年把 50% 的资本支出花在内存上。
Reiner Pope:
这可信。
Dwarkesh Patel:
超大规模厂商的资本支出是多少?那是几千亿,也许一万亿,而他们把其中一半花在内存上?那是一个巨大的约束。这就是为什么我们今年不会得到新的笔记本电脑和手机。
但与此同时,我们有太多内存?人们愿意把太多内存放进这些系统里。如果你不需要它,为什么 Jensen 要把所有这些内存塞进这些机架里?
Reiner Pope:
在我们擦掉之前的那些方程里,我们在做内存时间、内存带宽和计算带宽。现在让我们开始看内存容量。
我们先从内存容量开始,甚至不考虑并行方案。对内存的需求是总参数数量。这是我们需要把权重装进我们正在使用的某个系统中的量。然后我们还需要装下 KV。KV 等于批次大小乘以上下文长度乘以每词元字节数。
在这个语境中我一直在论证的,以及我为流水线所做的辩护是,有一些技术可以让我们解决这个问题。让我们考虑在若干 GPU 上运行这个。我们将有一个维度,即 E,专家并行。当我们把一个专家层分片到许多 GPU 上时,我们做到什么程度?多少个 GPU?我们比如说这是 64。然后 P 将是流水线的维度。这是机架的数量,也许我们选 4 之类的。
这是整个系统的总内存需求,但现在我要计算每个 GPU 的内存需求。我会用小写 cmem。显然,我们只要把所有这些数字除以 E 和 P。非常简单。就是这个 Ntotal,加上批次乘以上下文长度乘以每词元字节数,全部除以 E 乘以 P。
为什么这样除是正确的?我们知道参数被完美地划分到机架中的所有 GPU 之间。层被完美地划分到不同机架之间。所以这在这里成立。不知怎么的我们会安排——具体怎么安排我就含糊带过了——把上下文同样完美地分片到机架中的 GPU 之间,然后基于层分片到机架之间。
Dwarkesh Patel:
抱歉,4 是机架的数量?
Reiner Pope:
是的,比如说。
这正是我们需要回去分析这个批次大小 B 的地方。你在评论说存在微批次与全局批次的区别。让我们回到这里的这个流水线图。
我们有一个批次在这里向前走,然后就像我画的那样,它有点消失了。那其实不太正确。如果你想想解码是如何工作的,我已经生成了一堆词元。我做一次前向传播,生成一个新词元,然后我把它写入我的 KV 缓存。然后我做另一次前向传播,生成下一个词元。实际上我会在一个循环中运行这个第零号批次。事实上,我向前走。一旦我完成,我就可以在这里开始循环的下一次迭代。我们就把它填上。我们有二、三、二和三,以及二和三。
我们把这一批拆开。这一批将是全局批次大小。B 等于微批次的数量乘以每个微批次的批次大小。我们需要多少个微批次?这个图里的微批次数量是 4:零、一、二、三。微批次大小仍然是这个 2000 左右的数字。抱歉,不对,这是 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,而不是两层的 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:
但我想这是每个词元 10 毫秒。
Reiner Pope:
没错。
Dwarkesh Patel:
2 乘以 4 左右,或者我不知道你说的是多少……每个词元 10 毫秒实际上很多。
Reiner Pope:
如果它从 20 变成 30,或者类似这样……
只是为了描绘它所经过的路径,这里你从你的 GPU 或 TPU 到网卡,然后到机架顶部交换机,然后跳到另一个机架,再反向做同样的事情。你必须把这些不同东西的延迟加起来。
Dwarkesh Patel:
抱歉,这和数据中心交换机是同一回事吗?
Reiner Pope:
它实际上可能会上到数据中心交换机再回来。这取决于部署配置。
Dwarkesh Patel:
明白了。而且因为它是解码且顺序的,它们会在各个阶段上叠加起来。你没法同时做它们。
Reiner Pope:
没错。
Dwarkesh Patel:
那么这就把我们带回了那个问题:扩展域的规模是否与过去几年 AI 模型规模之所以是那样有任何关系,无论是通过训练还是通过推理?
Reiner Pope:
我们谈到了跳的延迟。还有 tmem 延迟。内存时间延迟实际上被更大的扩展域大幅改善。我在这里回忆一下 tmem。权重的 tmem 等于总参数数量除以内存带宽。我们这里说的是哪个内存带宽?只是一个 GPU 吗?是我可以并行用来加载这些权重的 GPU 数量。我不能并行使用不同的流水线阶段,因为它们不是同时运行的,但我可以并行使用我扩展域中的所有 GPU 来加载权重。这实际上极其有效。基本上,我在这里最终得到一个项,这个内存带宽项本身等于扩展域大小……
Dwarkesh Patel:
乘以每个 GPU 的内存带宽。
Reiner Pope:
对。乘以 GPU 带宽。这一项不会增加很多。它每一代也许增加 1.5 或 2 倍,但这一项从 Hopper 起增加了 8 倍。
Dwarkesh Patel:
所以更大的扩展域之所以重要,原因不是整个扩展域的内存容量,而实际上是内存带宽。
Reiner Pope:
是的。流水线并行完全解决了容量问题,但扩大scale-up规模有助于解决带宽问题。
Dwarkesh Patel:
而带宽问题的解决有助于你实现更长的上下文长度,随着这些模型变得越来越具有智能体特性,这一点也越来越重要。
Reiner Pope:
首先,它让你能以更低的延迟运行模型。如果我只是做一个非常稀疏的模型,放在一个小小的H100机器上,延迟会非常高。
由于强化学习,模型可能被过度训练到超出Chinchilla最优100倍
Dwarkesh Patel:
一个非常跑题的问题。有Chinchilla缩放定律,它告诉你相对于你将要训练的数据量,模型应该有多大。但现在,显然你不仅仅是在优化训练计算所能得到的最优质模型。你想要的是用户在使用训练和推理计算混合的情况下能得到的最佳结果。
所以有一个问题:你应该将模型过度训练到什么程度,使得在训练和推理上摊销的计算量最小化以达到某种性能?但现在有了强化学习,还有另一个考虑因素,那就是你将进行一定量的预训练。那部分预训练将同时用于强化学习生成以及最终用户的推理。我在这里说的过度训练是指,虽然仅从训练计算的角度来看,训练一个更大的模型但训练时间更短会更高效,因为它能更快地学习,但也许你选择了一个更小的模型,花费了比原本更多的计算来训练它,但现在把它提供给用户的成本更低。
让我把问题说得更具体一些。基本上,模型被过度训练到超出Chinchilla最优多少?这个数字是否因为强化学习生成而发生了变化?
Reiner Pope:
这是一个我们不得不做一些猜测的地方,因为更新后的缩放定律和模型流量都没有被报告,所以我们只能猜测。一种看待它的方式……
让我先做一个一般性的启发式论断。如果我有某种成本,总成本是成本A和成本B之和,比如这可能是训练成本,这是推理成本,我想最小化这个总和……
对于许多曲线来说,最小值往往出现在成本相等的地方。这是一个启发式的论断,但有很多例子证明它是正确的。比如一个是1/x,另一个是x,它们往往在彼此相等的那一点取最小值。对于e*x和e-x*以及各种其他情况也是如此。基本上,我有一条下降的曲线,另一条上升的曲线,它们往往在这个相等的点取最小值。
从启发式角度,我推测对于你所描述的情况也是如此。实际上要证明这一点需要查看缩放定律并拟合这些奇怪的指数,但遵循幂律的事物往往具有这个性质。所以我就先做这个论断,然后继续。
我们要说我们希望使训练成本和推理成本相等。我们可以一般性地来做这件事。预训练的成本,就是活跃参数数量乘以预训练数据量。这里有一个因子6,也就是FLOPs的数量。有著名的6ND公式。然后在强化学习中,我们大致有同样的东西。我们有相同的活跃参数数量,但现在数据量是强化学习数据。有一个额外的效率乘数,或者说低效率……
Dwarkesh Patel:
也就是你并没有在你所有的rollout上进行训练这个事实。
Reiner Pope:
嗯,有这个因素,然后另一个也许更大的低效率是这涉及大量的解码。通常解码运行的MFU比训练低。
Dwarkesh Patel:
好的。所以如果你在强化学习中对每一次生成都做反向传播,那将是6ND。
Reiner Pope:
所以这可能是一个更小的数字,对吧?
Dwarkesh Patel:
至少是2,因为那是最低的……
Reiner Pope:
大概在2到6的范围内。我们就说在2到6的范围内,就这样吧。然后我们可以加上推理成本。推理成本是2,即活跃参数数量乘以推理数据量。
Dwarkesh Patel:
抱歉,我觉得我刚才说得太混乱了。为了观众,每个参数的前向加反向是6。仅前向是2。这就是为什么强化学习——你肯定会生成所有轨迹,但可能训练也可能不训练所有轨迹——是2到6。
Reiner Pope:
是的。谢谢。然后推理就是2。我们将求解这三项基本相等的情况。这就是人们大致会处于的范围。实验室有更多关于做更多强化学习相对于做更多预训练哪个更有产出的信息。我没有那个信息,但我认为一个好的大致估计是三者各占33%。
Dwarkesh Patel:
我不确定我理解这个直觉。另一个朴素的模型可能是强化学习加预训练占50%,推理占50%。
Reiner Pope:
那也是一个合理的答案。因为这是启发式的,我无法真正论证一个比另一个好。它们差别没那么大。33对25只是一个小因子差异。
我们就选其中一个吧。全部相等看起来足够简单,所以我们就求解它们相等的情况。这很直接。我们可以立即看到活跃参数数量完全消失了,所以我们把它提取出来。我们就说预训练数据量——我决定按你的方式来,这样更好一些——加上……哦,我这边也没有考虑低效率。预训练数据量加上α乘以强化学习数据量的某个倍数,最终等于β乘以推理数据量。
让我们大致估算一下α。这个α大概在2到6的范围内。除以6,从这一项与那一项相比。然后我们有一个低效率项,我会说大概在30%的范围内。所以这个alpha大概是1/10。而这个β实际上是一样的。它是三分之一。是三分之一乘以30%。所以它也等于1/10。
Dwarkesh Patel:
如果两者都是十分之一,那似乎意味着强化学习上从来没有反向传播?
Reiner Pope:
是的。好吧,我们可以把它改成2/10。让它大一点。再写一遍,这是2/10,这是1/10。
你的推理词元数量只是每秒数亿词元乘以我的模型部署两个月后我才发布下一个版本这个函数。这应该决定强化学习和预训练中的词元数量。
我想我们没有做预训练和强化学习之间的等价,所以我们在这里做一下。预训练数据量应该等于2/10的强化学习数据量,才能使它们在成本上等价。抱歉,是1/10。我搞反了。当效率低时我们付出更多成本,所以这应该是1/10。追溯回去……这个东西最终实际上是,如这里所写的……这大概是1.5,这是1。
Dwarkesh Patel:
价值数十亿美元的计算就这样流向了另一个方向。
Reiner Pope:
对吧?我认为如果你用电子表格实际建模一下,你可能会注意到钱什么时候在白白流失。所有这些最终都接近,如这里所建模的。这个30%可能有点太慷慨了。所以我们说这里大概是1.5,把这个保持为1。
我认为到了这一步,你几乎可以直接读出来了。推理词元数量应该大约与预训练词元数量相同,而预训练词元数量应该大约与强化学习词元数量相同,在我们无法推理的因子范围内。
Dwarkesh Patel:
抱歉犯了一个基础代数错误。看起来强化学习词元应该比预训练词元少?
Reiner Pope:
总的来说是对的。因为强化学习在机器时间方面效率更低,如果你试图使强化学习和预训练的时间相等,那么你应该有更少的词元才能有相同的墙钟时间。
Dwarkesh Patel:
这都挺有意思的。我从来没从数据量相等的角度想过这个问题。
Reiner Pope:
我认为从成本相等出发是对的,但取决于你如何建模成本,这接近于数据量相等。
Dwarkesh Patel:
所以对于GPT来说,要得到最优训练,每一个使用GPT-5的用户,他们流式传输的总词元量应该等于进入预训练的总量。而进入预训练的总词元量是所有人类知识的总和。每个模型在输出中生成的人类知识总量,应该等于它在输入中获得的总量。
Reiner Pope:
是的。人们会往哪个方向犯错?如果你认为人们的预测能力不是完美的,而且你还面临做出一个不是前沿模型的模型然后就直接扔掉的风险,那么这会改变成本权衡,因为有一些概率适用于推理。你应该按某个量来折减推理词元。
Dwarkesh Patel:
对。我们能否反推出对于给定规模的模型,计算量比Chinchilla最优多多少?
Reiner Pope:
我认为我们只需要在这里做一些现实世界的假设才能做到这一点。
推理词元,我们完全应该能够计数,对吧?假设每秒几亿。也许现在是每秒五亿词元,我不太确定。每秒五亿词元乘以。一个模型部署两个月后才过时?
我没法心算。你能在电脑上打出来吗?
Dwarkesh Patel:
2.6 x 1015。
Reiner Pope:
好。2.6 x 1015。这个数字可能太大了,因为这会是一个模型家族里的多个模型。我们把它缩小5倍或10倍之类的。所以我们估计大概是每秒五千万个词元,针对某个特定模型。这个模型上线两个月。这样算下来大约是两百万亿个词元。然后我们想把它和一个前沿模型上的活跃参数做比较。我其实不知道最新的传闻。你知道吗?
Dwarkesh Patel:
有人告诉我是一百五十万亿。
Reiner Pope:
活跃参数?
Dwarkesh Patel:
抱歉,我指的是词元。
Reiner Pope:
在一百五十万亿个词元上训练。有意思。
Dwarkesh Patel:
这很相似。
Reiner Pope:
这确实很相似。所以是预训练的数据。
Dwarkesh Patel:
这个出处不太可靠,但没关系。
Reiner Pope:
我认为活跃参数的数量常常可能在一千亿这个量级,差不多这样。也许再大一点。所以乘以20就得到Chinchilla词元数。那么Chinchilla,DChinchilla,大约会是两万亿。我们看到我们比那个大了大约一百倍。
Dwarkesh Patel:
*DChinchilla*到底是什么意思?
Reiner Pope:
我想,就是Chinchilla缩放定律会推荐的预训练词元数。
Dwarkesh Patel:
哦,我明白了。所以它被过度训练了多少?懂了。
Reiner Pope:
这两百万亿或一百万亿参数与Chinchilla最优的两万亿之间的比值,就是它被过度训练的程度。也就是过度训练了一百倍。
Dwarkesh Patel:
一百倍。所以如果你考虑这里这个,只要这大致在正确的量级上,仅仅通过思考你希望一切在算力方面如何相等……如果OpenAI也意识到了这一点,并且他们每秒服务一定数量的词元,那就能告诉你GPT-5的预训练用了多少数据。即使差了50%之类的,你能用第一性原理推出这类数字,这还是很疯狂的。
Reiner Pope:
这就是为什么你应该到处都做近似,因为这里面有很大的误差范围。但直接把A设为等于B然后算出来,还是有点赋权感的。
从API定价推断长上下文内存成本
Dwarkesh Patel:
这太酷了。好,那么本着试图推断事情的精神,我们可以公开查到这些模型的API价格,也许我们能从中了解到一些东西。
首先,在更长的上下文下,Gemini 3.1如果你超过200k词元,会比低于200k词元贵50%。从高层面上,我理解为什么会这样,但为什么偏偏是50%?
Reiner Pope:
为什么偏偏是50%?首先,高层面上,随着上下文长度增加,成本会有一定程度的增加。我们可以把那个再调出来。那就是内存时间与计算时间的对比。
我们放上了之前同样的这些方程,内存读取的时间,也就是权重和KV缓存,然后是计算的时间,也就是权重的矩阵乘法。我也会画出成本曲线,但这次我会把它画成上下文长度的函数,而不是批大小的函数。所以这是成本随上下文长度变化的曲线。我们来画计算。计算的成本实际上是随上下文长度恒定的。这里不依赖于上下文长度。现实中确实有一些依赖,但非常轻微,所以我们忽略它。所以这是计算的时间。
然后我们也会画出内存读取对上下文长度的依赖。它从权重的一个大数字开始,然后随着上下文长度逐渐增长。也许从这里开始,然后随着上下文长度逐渐增长。所以你取最大值,就会看到这里有一个拐点。
所以这就是Gemini可能付出的成本。然后你想,你会怎么在上面加一个定价结构?你会希望确保无论上下文长度是多少,你仍然有利可图。所以我们有了一个两档的定价结构。也许我们有一个看起来像这样的东西,到某个程度。
我觉得这说明了一些事情,鉴于这个跳变在200k,它可能意味着这在一定程度上与这个交叉点对齐。也许不是完全对齐。我们其实甚至可以完成那个计算,看看它落在哪里。
如果我们对活跃参数的数量做一些假设,我们可以解出每个词元的字节数。所以解每个词元的字节数,我们要假设内存时间和计算时间相等的点是在,比如说,200k词元。所以我们让这两个相等。
我们还要假设批大小足够大,以至于花在权重上的内存时间可以忽略不计。所以我们忘掉这个,专注于实际花在KV缓存上的内存时间。这最终就是说,把这个项抄过来,批大小乘以上下文长度乘以每个词元的字节数除以内存带宽,将等于激活参数的数量除以FLOPs。然后我们解每个词元的字节数。批大小在这里不见了。它出现在这里,然后到我们走到这里的时候它约掉了。然后我去掉了上下文长度。
所以我们可以代入数字。这是我们之前看到的数字的倒数。这是1/300,这在许多不同的硬件平台上都相当稳定。我们推测性地说过,激活参数的数量可能是一千亿。上下文长度我们说是200k。不过这里有点不对。上下文长度应该在分母上,而不是分子上。
Dwarkesh Patel:
1667。差不多两千字节。
Reiner Pope:
这其实是合理的。你说大约两千字节。我们做个合理性检查,看看这可能是什么。人们用每个词元少量字节来做注意力有两种机制。一种是密集注意力,在层之间大量复用。Character AI有一篇博客文章谈到了这个,交替使用长上下文和短上下文。在Character AI那种模型里,也出现在Gemma模型中,全局上下文——这其实就是我们这里在谈的——在所有层之间共享。
要把它降到千字节级别,你可以例如用dhead为128,这是典型的。那么字节数通常是注意力层数乘以2乘以dhead乘以KV头数。这是每层唯一上下文的数量。
你是把上下文在许多层之间共享,还是只使用一次?在类似Character AI的模型中,这个数字是1。我们说这是128。这是一个选择,通常范围从1……抱歉,我指的是KV头。
Dwarkesh Patel:
头和KV头之间的区别是……?
Reiner Pope:
KV头是存储在内存中的头,存储之前词元的内容。Q头是检索头。它们只是临时使用,由正在注意的词元使用。在这个自回归上下文中,我有与所有上下文关联的KV头,然后有与这里这个新词元关联的Q头。
Dwarkesh Patel:
但这个头,128。
Reiner Pope:
哦,抱歉。这个d-head是向量的维度。KV头的数量通常在1到8的范围内。通过例如有8个KV头和一个128的d-head来得到这个,是完全合理的。那正好给你这个数字。或者你可以有更少的KV头,但更多的层。
这是通过密集注意力达到那里的一种方式。还有一种通过稀疏注意力达到那里的方式,你把所有这些数字都增大,但然后你有一个1/稀疏度的项。我认为这个数字是合理的,也许有点小。
Dwarkesh Patel:
有趣的是,他们通过API定价泄露了这么多信息。
Reiner Pope:
我的意思是,你有动机把价格定得接近你的成本,因为否则别人可能会抢走你。
Dwarkesh Patel:
也许我们可以从输入价格和输出价格的差异中学到一些东西,以及这告诉我们这些模型中解码与预填充的什么。我上次查的时候是贵50%之类的?
Reiner Pope:
我不记得了。我过去看到的是贵3-5倍。
Dwarkesh Patel:
好,那更合理。那么假设贵5倍。这是解码中处理下一个词元的计算。假设你在做预填充,你不是只处理最近的词元,而是并行处理所有词元。我想说那会是这个乘以预填充长度?
Reiner Pope:
或者一般来说是这一趟的长度。如果我们能把解码看作长度为1的一趟,然后预填充是长度为多的一趟。
Dwarkesh Patel:
好。所以也许是前缀?好,内存。你不是为预填充词元存储KV缓存。
Reiner Pope:
如果我可以澄清一下,我们实际上来画一下预填充在这里是怎么出现的。我们像这样做一点解码。我们实际上可能会回来做更多预填充。如果你认为这是一个聊天会话,用户说了些什么,AI生成一个回复,然后用户说了别的东西,我们预填充这个。也许这是一般情况,而不是这个。
Dwarkesh Patel:
事实上,这就像你读一个文件之类的。
Reiner Pope:
读一个文件,或者AI在回应用户输入、工具调用,或任何不是AI生成的东西。
Dwarkesh Patel:
好,假设我们在这里。你之前已经把所有这些东西都算出来了。所以只是之前所有内容的 KV。但它的内存成本是多少?嗯,它的内存带宽成本是多少。如果你用的是flash attention,那它会——
Reiner Pope:
它基本上只是临时的。它甚至不会进主存。直接忽略它就行。
Dwarkesh Patel:
没错。所以那就只是之前所有内容。那不就只是这个吗?
Reiner Pope:
实际上对内存时间完全没有任何调整。
Dwarkesh Patel:
好。太好了。所以这是一个非常微不足道的改动就能容纳的。这一项让它贵了 5 倍。那么,为什么会这样?这实际上告诉了我们什么?它帮我们夹住了哪个变量?唯一可能发生变化的是,计算因此贵了 5 倍。
Reiner Pope:
这是一次 pass 的时间,但实际上 token 的数量要大得多。我们想要的其实是每个 token 的成本,或者每个 token 的时间。
Dwarkesh Patel:
我不确定我理解了。这是用于处理前缀中的下一个 token 吗?
Reiner Pope:
嗯,实际上是用于处理整个 batch。按这个成本,我们处理了这么多 token,也就是 prefill 的长度。或者我猜是这次 pass 的长度。不是这个前缀,而是这个成本。
Dwarkesh Patel:
好。我们就做这次 pass。所以这贵了 5 倍。输入贵了 5 倍。
Reiner Pope:
实际上输出更贵。
Dwarkesh Patel:
输出贵了 5 倍。
Reiner Pope:
我们想要努力得到的结论是:prefill 受计算限制,decode 受内存带宽限制。
Dwarkesh Patel:
我们为什么不这样做?我们为什么不把它画成 X 轴是 len-pass、Y 轴是 t 的图。
Reiner Pope:
我们想要的是每个 token 的成本,所以会是 t 除以 pass 的长度。那样就对了。
Dwarkesh Patel:
我猜我被这个搞糊涂了。Len-pass 是……看起来这个在你做 prefill 的时候应该更高。
Reiner Pope:
Prefill 的 pass 长度更大。是的。
Dwarkesh Patel:
但那为什么它更便宜?
Reiner Pope:
为什么成本更高?就是这个除以 pass 长度。这个会被除消掉,但然后所有这些都会被 pass 长度除,这会让内存成本变得更便宜。
Dwarkesh Patel:
好。让我想想这个。基本上我们会有四条不同的线。我们先做 prefill……实际上,我们先做 decode。
Reiner Pope:
pass 的长度,当它是 1 的时候,那就是 decode。当它更大的时候,那就是 prefill。
Dwarkesh Patel:
哦,好。我明白了。这说得通。回到这个。所以 tcompute,如果你基本上只是把这个除以 len-pass,那就只是这个量。这实际上不随 t 变化,所以它就只是像这样的某个平坦值。而这是 tcompute。而这是——
Reiner Pope:
那是 decode。
Dwarkesh Patel:
Decode。对。现在 tmem,我们有这整个东西除以 len-pass。嗯,上面是什么其实不太重要,它就只是看起来像这样的东西。假设这是 tmem。这又是 decode。
所以随着前缀长度,或者说 pass 长度上升,你的内存带宽时间下降,这意味着,就你之前受内存带宽瓶颈限制的程度而言,你可以避免受内存带宽瓶颈限制。
他们对 prefill 的收费比 decode 低 5 倍,这确实表明他们在相当程度上受内存带宽瓶颈限制,以至于至少对他们来说——因为 t 等价于成本,也就是租用计算的成本——这个会在 1,而这个会在 5。
Reiner Pope:
没错。
Dwarkesh Patel:
所以事实上,它极其受内存带宽瓶颈限制。真实的图看起来大概就是这样。
Reiner Pope:
它仍然会交叉,不过是的。
Dwarkesh Patel:
没错。让我这样来做。这是 decode 时内存时间和计算时间之间的差距。好,有意思。
另一个有意思的问题是,为什么缓存命中会便宜这么多。如果我记得没错,缓存命中大概是 10 倍……根据所有这些模型的定价,写入缓存更贵。但如果你确实命中了缓存,就是 10 倍。大概这是把东西保留在 HBM 里而不是直接把它清走的成本。但如果你确实把它保留在 HBM 里,那么再次加载就更便宜?
Reiner Pope:
对。为一个 token 生成 KV 缓存有两种方式。你可以直接从底层 token ID 计算,从头生成,而那些 ID 很小。或者你之前已经生成过它,并把它存在某个内存里。
这个成本比率实际上说的是这两种生成机制之间的比率。缓存未命中意味着你已经把它从所有内存中删掉了,你必须直接从 token 重新计算它。你甚至可以更进一步,想想你把它存在哪个内存层级。你可以把它存在 HBM 里。还有一些比 HBM 更慢、更便宜的内存,比如主机上的 DDR,或者 flash。你可以做的一件事是计算放在每个内存层级里在什么情况下合理,而这与你要存它多久有关。
我们想看看几个不同内存层级中的存储成本,以及重新物化的成本。Remat 指的是在你删除所有 KV 缓存之后从头重建它的成本,也就是我们重新物化它。基本上,这会花费上下文的长度。实际上,我们会看每个 token 的成本,所以我们不需要到处都带着这个上下文长度。
要重新物化一个 token 的 KV 缓存,我只需要在整个模型上跑一次前向 pass。这会是计算时间。我必须按我的 GPU 能做到的速度重新跑计算,然后乘以我的 GPU 每秒美元数。
Dwarkesh Patel:
抱歉,请原谅一个天真的问题。为什么这里没有二次项?
Reiner Pope:
有二次项。它出现在计算里。作为一个近似,我选择把它去掉。我快速给你看看那是什么样子。如果你看每个 token 的成本,或者每个 token 的 FLOPs 数,有来自做权重矩阵乘法的 FLOPs,它是……
Dwarkesh Patel:
那是平坦的。
Reiner Pope:
……上下文长度的函数。然后还有来自做 KV 缓存的乘法数量,它随着你 attend 的内容量线性上升。这个斜率非常低,所以当你像这样画出来时,它用一条平坦线就能很好地近似。你大概要到几百万 token 左右才会开始注意到二次项或线性项的影响。所以它并不是特别相关。
Dwarkesh Patel:
那么,如果这是真的,为什么没有一家公司拥有超过一百万 token 的上下文长度?
Reiner Pope:
长上下文有两种成本。一种是内存带宽成本,我们已经花了很多时间分析它。就是这个东西。另一种是计算成本。计算成本几乎总是被基本原理迫使成为比内存带宽成本小得多的斜率。真正限制你达到极大上下文的主要因素是内存带宽和内存容量,而这正是这个效应。
Dwarkesh Patel:
有这样一个想法,Dario 在播客上说过,其他人也说过,就是:“我们不需要持续学习来实现 AGI,上下文学习就够了。”如果你相信这一点,那你就必须认为我们得达到一亿 token 的上下文长度,才能拥有一个相当于和你一起工作一个月的员工。现在,也许有了稀疏注意力之类的,这就不再是真的了。但如果你这么认为,那么某些 ML 基础设施的东西就必须改变,才能支持一亿,比如内存带宽,才能支持一亿 token 的上下文长度。
Reiner Pope:
稀疏注意力当然给了你一个脱身办法,因为你得到了这个平方根。它给你带来很大的改进。但如果你看模型上下文长度的历史,从 GPT-3 这样的早期模型,也许到 GPT-4——我不太记得转变具体是什么时候发生的——它们从大约 8K 猛增到 100-200K。然后过去一两年,它们都一直徘徊在那附近。我认为这表明这就是一个相当平衡的成本点,而大幅超过它会成本高得令人望而却步。
Dwarkesh Patel:
不是因为计算成本,而是因为内存带宽……
Reiner Pope:
因为内存带宽成本,是的。我其实看不到解决这个问题的好路径。HBM 就在它现在的位置。它不会变得好太多。
Dwarkesh Patel:
那为什么稀疏注意力解决不了这个问题?
Reiner Pope:
稀疏注意力是一个很大的改进。也许这已经被定价进去了,大概吧。它不是无限的改进,因为如果你做得太稀疏,你会损失太多质量。
经验结果是,上下文长度并没有增加那么多。我认为这是因为这里的内存墙没有解决方案。做得太稀疏只是意味着你只关注非常小的一部分词元,质量会变差。
Dwarkesh Patel:
有道理。
Reiner Pope:
这些重新合成 KV 缓存的不同方式的成本是什么?从头计算是基于我的 GPU 时间。我必须做一定数量的乘法,花费一定量的 GPU 时间来产生它。
存储在 HBM 中。这实际上取决于我的每词元字节数。我需要每词元有一定数量的字节,然后我需要把它存储在 HBM 中。它会消耗我的一些 HBM 容量。一种思考方式是,如果我有太多这些东西放在我的 HBM 里,如果我用那些我没在用的 KV 缓存填满了我的 HBM,我就没法用那个 GPU 了。
我怎么给它定价?也许我说它的成本与我使用的 HBM 比例成正比。还有乘以 GPU 美元。让我们再做一层内存层级,说存储在 DDR 中。同样的事情也适用于闪存和 DDR。
我把这些放错列了。我本来想做两列。我想做的区分是,有检索的成本,然后有持有的成本。这是一个每秒成本,而这是一个瞬时成本。重新物化有检索成本,存储成本为零,因为我们已经删除了它。这是那个我放错位置的东西。这实际上只是持有的成本,所以我会重写它。
如果我们只是把它存储在 HBM 中,它有这种成本特征。如果我们存储在 DDR 中,实际上会花一些时间。我们在这里得到同样的东西:每词元字节数除以 DDR 容量乘以 DDR 每秒成本。但现在它的检索成本比 HBM 更高,因为我们需要把它复制到 HBM 中。所以这是每词元字节数除以 DDR 带宽。然后这也消耗一定量的 DDR。
Dwarkesh Patel:
每个 scale-up 都有 DDR 和闪存吗?
Reiner Pope:
这实际上是一个部署问题,所以你可以选择。Nvidia 确实以这种形式部署。它两者都有。
Dwarkesh Patel:
为什么检索 HBM 的成本不是字节数除以内存带宽?
Reiner Pope:
这取决于你把检索定义为什么。在这里,我把检索定义为,把它移到 HBM 中,这样你就可以开始对它做推理了。
Dwarkesh Patel:
因为如果它已经在 HBM 中,你可以在把它从 HBM 取到 SRAM 的同时进行计算?有意思。
Reiner Pope:
是的,比如说。这是三样东西,我想我把它们的顺序排错了。一般来说,如果你在平衡两个成本,而你在内存层级中有不同的层级,你应该预期当这个成本上升时,这个成本应该下降。你可以大致看到零在哪里。我应该把这个排第一,这个排第二,这个排第三。
如果你只打算持有非常短的时间,那么所有这些都乘以持有时间。这个是,这个也是。
Dwarkesh Patel:
有趣的是,它们有不同的写入价格。你在 API 中指定这个是五分钟还是一小时?这暗示五分钟是 HBM,一小时是 DDR。
Reiner Pope:
我认为这是一个相当好的假设。如果你看那些数字,也可能结果是低一个层级,是 DDR 对闪存。
Dwarkesh Patel:
有意思。我会去查一下价格差异。基础输入词元是每百万词元 5 美元。
Reiner Pope:
基础,意思是重新物化。这是 5 美元。
Dwarkesh Patel:
那是 5 美元来“检索”。然后写入,大概是 HBM,五分钟是 6.25。
Reiner Pope:
我们也许能通过时长来确定它是哪个内存层级。
Dwarkesh Patel:
五分钟对一小时。
Reiner Pope:
正是。我认为这最终可能会是你所在内存层级的排空时间。这意味着,既然我知道我要持有某个东西五分钟,我想选一个我能每五分钟读一次的内存。我可以大致每五分钟读一遍整个内存。那就是内存的排空时间。所以如果我拿存储容量除以存储带宽,我希望这个等于五分钟。
我们对 HBM 做了这个计算。对于 HBM,我们知道这个数字是 20 毫秒。所以 HBM 太小了。DDR 可能和这个差一到两个数量级,所以这大概是秒的量级,比如 1 到 10 秒。我没有记住这些数字,但一般来说,当你去到更慢的层级,闪存可能是一分钟的量级。然后旋转磁盘,那差别巨大,是一小时的量级。所以这实际上可能识别出闪存和旋转磁盘这两个层级。
Dwarkesh Patel:
抱歉,为什么是这个计算?这是存储容量除以带宽?
Reiner Pope:
你有一堆不同的内存层级,我们列了四个。你选择哪个内存层级是关于最小化成本。你使用了设备的多少比例?你用设备的一部分比例来持有它,然后你用设备的一部分比例来检索它。假设我用了设备的 10%。我想让这两个比例相等。那是一个信号,说明我找对了东西。
假设我这里有某个运行时间。我要持有这整段时间,所以这是持有时间。然后这里会有一定量的时间,这是检索时间。基本上为了平衡这两个成本,我想让检索时间等于持有时间乘以容量比例。因为这是检索时间,这是我能同时持有多少其他东西。
Dwarkesh Patel:
基本上,你想把东西存在那里那么久,使得它在里面的时间等于把你所有东西放进去和取出来的时间。
Reiner Pope:
是的,基本上。我认为那可能表明这两个层级是闪存和旋转磁盘。我有点震惊看到旋转磁盘居然还在被使用,因为它是如此古老的技术。
Dwarkesh Patel:
有意思。它慢到要花一小时才能把它的全部容量加载进去,这也很疯狂。
Reiner Pope:
它是一种非常没有吸引力的技术,但它在某些地方有用。
神经网络与密码学之间的趋同演化
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。它就在那里,我直接读出来就行。要恢复 y,如果这个东西叫做 z,我可以通过 z 减去 f(x) 来恢复 y,因为我已经恢复了 x。这意味着这个构造是可逆的。
这在密码中被大量使用,现在仍然在使用。它是构造密码的主要机制之一。通常你希望密码是可逆的,尤其是密码的各层,因为这样有更好的密码学性质。
这实际上已经被移植到神经网络中了。2017 年有一篇论文叫 RevNets,即可逆网络。它所做的就是让整个网络可逆。你可以把它应用到任何网络,比如 Transformer 网络。我做一次前向传播,但之后我也可以把整个传播反向运行。整个神经网络用这个构造就是可逆的。
这篇论文把它应用到某一层,比如 Transformer 层。我们有这个函数 f,也就是我们的 Transformer 层。通常我们只有一个输入,然后有一个残差连接出来,在这里被加上去。现在,这个变体是,我们有两个输入,x 和 y。x 经过这个函数,被加到 y 上,然后这成为新的 x,输出 x。然后这个 x 成为输出 y。
实际上,如果你往回看两层,它所做的就是你之前提到的东西。它做的是来自两层的残差连接。这个 y 来自前一层,是那里的残差连接。由于这个构造,整个东西是可逆的。
我为什么关心这个?可逆有什么用?它可能有趣的一个大用途是训练。如果我考虑训练的一次前向传播……假设我有四层,我按零、一、二、三的顺序运行它们。我必须把所有激活值写入 HBM。我在这里得到一个 HBM 占用,它大致与层数成线性关系。
这实际上可能是训练期间最大的内存占用。这是正常训练,然后我运行反向传播,按相反顺序读取它。前向传播向前走,反向传播向后走。我必须把它们再读出来。
这篇 RevNets 论文的想法是,因为它是可逆的,我根本不需要存储这些。我可以完全重新物化它。我运行前向传播,然后当我运行反向传播时,我同时同步地撤销我为了在这里获得所需激活值而做的所有前向传播步骤。这最终能节省内存,是个不错的想法。
Dwarkesh Patel:
有意思。在某种意义上,你是在花更多的计算来节省内存。
Reiner Pope:
没错。
Dwarkesh Patel:
有意思。这和你用 KV 缓存做的事情正好相反。用 KV 缓存,你是在花更多的内存来节省计算。
Reiner Pope:
是的。考虑到硬件的现状,花更多内存来节省计算通常是划算的。
Dwarkesh Patel:
有意思。这太有趣了。Reiner,非常感谢你来做这个。我觉得它真的证明了演播室和黑板背后的愿景。
Reiner Pope:
是的。
Dwarkesh Patel:
太好了,非常感谢你来做这个。
Reiner Pope:
谢谢。
本文由 AI 翻译。