大语言模型训练与推理背后的数学原理Reiner Pope – The math behind how LLMs are trained and served
文章通过少量方程和黑板推演,揭示了大型语言模型(LLMs)训练和部署的核心机制。作者Reiner Pope指出,仅需几个关键公式就能推断出AI实验室在模型架构、数据流和计算优化方面的实际做法。这些数学洞察暴露了从梯度下降到注意力机制等核心技术细节。该分析为理解LLM内部运作提供了独特视角,即使没有访问源代码也能获得深入认知。
Dwarkesh Patel
我和 Reiner Pope 采用了截然不同的形式——黑板讲座,他逐步讲解了前沿大语言模型的训练与部署方式。
令人震惊的是,仅凭几行公式、公开的 API 定价和一些粉笔痕迹,就能推断出实验室在做什么。
内容略显技术化,但我鼓励你坚持看完——绝对值得。
真正理解从芯片设计到模型架构全栈 AI 的人屈指可数,Reiner 就是其中之一。能从他那里学习真是莫大的荣幸。
推荐你在 YouTube 上观看这个视频,以便看到黑板上的推导过程。
Reiner 是 MatX 的 CEO,这是一家新成立的芯片初创公司(补充说明:我是其天使投资人)。他曾在 Google 工作,负责软件效率优化、编译器以及 TPU 架构。
点击此处下载该段落的 Markdown 格式文稿,可与 LLM 互动——我正在制作一些闪卡帮助大家记住本集内容,几小时后回来就能看到!
(00:00:00) – 批次大小如何影响 token 成本和推理速度
(00:32:09) – MoE 模型如何在 GPU 机架间分布
(00:47:12) – 流水线并行如何将模型层分布在多个机架上
(01:03:37) – 为什么 Ilya 说“正如我们现在所知的,流水线并行并不明智。”
(01:18:59) – 由于强化学习,模型可能比 Chinchilla 最优解过度训练 100 倍
(01:33:02) – 从 API 定价推断长上下文内存成本
(02:04:02) – 神经网络与密码学之间的趋同演化
Dwarkesh Patel
今天,我采访了 Reiner Pope,他是 MatX 的 CEO,这是一家新的芯片初创公司。此前,他在 Google 从事 TPU 架构等工作。这与我的常规访谈形式非常不同。这将是一场黑板讲座。我们马上开始。事实上,我们专门为此格式打造了这个全新工作室,很高兴能与你共同开启它。
我们将要讨论模型架构、机器学习基础设施以及其他许多内容。我认为这是一个重要话题的原因是:一旦你理解了集群中训练和推理的工作原理,很多问题——比如为什么AI是这样的现状、为什么AI架构如此设计、为什么API定价如此、以及从根本上说为什么AI的进步速度是这样的——都会变得清晰起来。你需要理解这些细节才能明白,而黑板正是帮助你理解细节的工具。Reiner,非常感谢你参与这次分享。
Reiner Pope
非常高兴能来到这里。
Dwarkesh Patel
坦诚地说,我本人是MatX的天使投资人,但这与本期播客无关。Reiner,让我们开始吧。我想先问你一个问题:像Claude、Codex和Cursor这样的公司都提供类似“快速模式”的服务,你可以支付6倍的价格,就能以2.5倍的速度流式传输token。从机制上看,我很想知道这里发生了什么?为什么多付钱就能获得更快的延迟?
二,这种模式还能继续升级吗?比如支付100倍的价格,是否可能获得快得多的速度?三,反过来呢?有没有可能推出像Claude Code那样的“慢速模式”?如果你愿意等上几分钟,也许能获得更便宜的价格?这可能有助于我们接下来在讲座中进行的分析。
Reiner Pope
好的。稍微提前透露一下结论:主要影响因素是批量大小(batch size)。我们现在就要精确量化它的表现及其对延迟和成本的影响。还有一个因素,可以称为推测解码或多令牌预测。我们稍后再深入探讨,但首先我们要讲的是批量大小。
我想介绍两个分析原则。第一,我们将对在一组芯片上运行transformer模型的方式进行屋顶线分析(roofline analysis)。我们会使用一个Blackwell NVL72集群,也就是包含72块GPU的一整排机柜。屋顶线分析意味着我们要关注内存带宽和计算性能。另一方面,我们只考虑模型的两个简单因素:处理权重所需的时间,以及处理上下文(即KV缓存)所需的时间。
让我们开始吧。我们要尝试估算执行特定形状推理所需的时间。这里我们并不追求精确预测,而是进行近似估计。我们会说这个时间至少等于某个量。我们将从两个方面来考虑:内存访问所需的时间和计算所需的时间。事实证明,即使使用简单的模型,这种方法也能提供非常强的预测能力。
逐个来看,计算所需的时间是多少?实际上,我在计算中有两件事要做。我需要将所有活跃参数相乘,然后还要处理注意力机制的部分。当我用所有活跃参数相乘时,我有一个当前的批量大小,以及模型中的活跃参数数量。然后我只是把这个除以芯片的计算吞吐量,也就是FLOPs,这是硬件层面的考量。
这涵盖了所有权重矩阵乘法所需的全部计算时间。不过这里有个小前提:我们忽略了注意力计算所需的时间,但通常这部分开销相比上述计算量要小得多,因此可以忽略不计。
Dwarkesh Patel
我会偶尔打断一下,提出一些非常基础的问题或澄清某些基本概念。对听众来说,你们不是一次只为一个用户服务。所谓“批处理”(batch),是指同时服务于多个不同用户——这就是一个完整的批次。
Reiner Pope
至少我可以稍微解释一下为什么要使用批处理。我们会看到,批处理为何是一种极其高效的优化手段。事实上,如果不将多个用户的请求合并在一起处理,其成本和经济效益可能会比批量处理高出上千倍。这一点我们将非常清晰地呈现出来。
接下来是活跃参数的数量。比如以 DeepSeek 模型为例,DeepSeek V3 模型约有 370 亿个活跃参数,而总参数量为 700 亿。我们关注的是在生成单个 AI token 时实际参与运算的那部分参数。
我们正在建模计算性能。我还会继续写等号,但在所有这些情况下,你可以认为这个时间是至少等于这个数值的,可能还有一些我们忽略掉的额外项。
从内存角度看,我们需要做什么?我们需要加载所有的权重参数,因此需要花费时间来获取全部参数,而不仅仅是活跃参数。这包括权重加载时间和 KV 缓存加载时间。后者实际上与批大小有关:对于批次中的每一个元素,我们都必须加载整个上下文长度所对应的 token 数据,每个 token 占用一定的字节数,这是模型的一个参数。
Dwarkesh Patel
或许我们先简单回顾一下,什么是 KV 缓存?
Reiner Pope
当我执行前向传播……让我画一下自回归推理的工作流程。这是在解码阶段进行的。假设我已经有一串文本 token……我画成一个张量,因为最终这些 token 是以某种嵌入维度表示的张量形式存在的。沿着这个方向是序列长度。
执行一次解码任务的工作内容是:我需要将每个 token 依次通过模型中多个层的大量矩阵乘法运算。一般来说,我必须对所有这些 token 都完成这样的操作。但每次解码步骤的目标只是生成上面那个新的 token。
为此,我会对整个模型中的所有权重矩阵进行一次完整的前向传播计算。然而,在此过程中还存在一个注意力机制——当前这个 token 会查看之前所有的历史 token,具体来看,它是在参考模型为历史 token 生成的内部表征,我们称这种表征为 KV 缓存。这个过程——即单个 token 关注所有过往 token 的行为——就是注意力机制。它主要由内存读取操作主导,而非矩阵乘法。
所以我们在这里展示了所读取的内存总量,然后当然还要除以内存带宽(单位:每秒传输的字节数)。事实上,有了这些公式,我们现在就可以画出拟合曲线了。我们想要分析的两个关键因素分别是:对批大小的敏感度,以及稍后会单独绘制的对上下文长度的敏感度。正如我们所说,批大小带来的最大影响体现在延迟与成本之间的权衡上。
让我们把它们画出来。我认为实际上只需要画两个图。首先,我们在这里画批量大小与时间的关系图。观察这个图形的形状,它有一个最大值,然后还有另一项。让我们逐个分析这些项以及它们的扩展性:计算时间和内存时间,以及它们是如何体现的。
我们先来看计算时间。它纯粹是批量大小的线性函数,没有偏移量,因此它是一条类似这样的曲线。这就是 t_compute。在内存方面,这里有一部分是一个常数,位于某个基础偏移量上,也就是权重获取。最后,我们还有这一项,即 KV 获取,它对批量大小大致呈线性关系,所以看起来就像这样。将这两者相加后取最大值……至少我们先画出它们的和。这两个内存时间结合起来,最终呈现出一条弯曲的斜率,就像这样。而整体的最大值——我会画得更粗一点——就是这两条曲线的最大值。
这说明了什么?这是一个延迟图。如果我增加批量大小,最初对批量大小并没有很强的依赖性,因此这里的延迟存在一个下限。这已经部分回答了问题。对于给定的硬件配置——我们可以讨论如何调整硬件配置——延迟都有一个下限。原因很简单,我需要将所有参数从内存读取到芯片中,这需要一定的时间。如果我已经用满了内存带宽,就无法做得更好了。
Dwarkesh Patel
你画的计算时间和 KV 增长的趋势,以及 KV 对内存时间的影响,似乎有些问题。
Reiner Pope
如果这个值在上面或下面会怎样?
Dwarkesh Patel
是的,情况一定是这样吗?如果总是如此,那么随着批量大小的增加,计算始终主导 KV,这意味着如果你使用足够大的批量大小,也许内存就永远不会成为问题。
Reiner Pope
这非常依赖于上下文长度,所以我认为我们应该回来再探讨一下。当你改变上下文长度时,KV 获取时间会不断上升,从而导致从计算受限转变为内存受限。
Dwarkesh Patel
计算时间的斜率恰好等于这个斜率有什么特别重要的意义吗?
Reiner Pope
每当出现平衡点时,就意味着你刚好达到了最优状态。对于斜率匹配的特定上下文长度,说明你同时受到内存和计算的均等限制,这是一个非常理想的状态。
Dwarkesh Patel
这是一个非常简单的代数问题,但假设最优的上下文长度是 10 万,而你增加到 20 万。你的 MFU 会下降到 50% 吗?稍微偏离最优上下文长度范围(也就是“黄金区间”)会对 MFU 产生巨大的影响吗?
Reiner Pope
没错。正如这里所建模的那样。这里有一个关键点,我把内存获取建模为与上下文长度成线性关系。这取决于模型架构。对于所有具有密集注意力的模型架构来说,这是成立的。稀疏注意力实际上要高效得多。
Dwarkesh Patel
明白了。实践中大家是不是都在用稀疏注意力?
Reiner Pope
我对稀疏注意力感到非常兴奋。很难知道实验室实际使用的是哪种。DeepSeek 发表了一种稀疏注意力机制。我顺便提一句,一些 DeepSeek 发表的论文中提出的稀疏注意力方法,实际上会在这个项中引入平方根。
到目前为止,我们一直在关注延迟问题。但要从这些数据中直接读出成本却很难。如果我要思考“成本”的含义……为了运行这次推理,我需要用 GPU 运行若干秒,比如 1 毫秒或 20 毫秒。我必须为这段时间支付租金。GPU 的租赁费用大约是每小时 2 美元左右。
这就是这次推理的成本,但在这个过程中我处理了多少个 token?这就是批大小(batch size)。实际上,我们真正想画的是成本与批大小之间的关系,也就是每个 token 的成本(t/B)随批大小的变化曲线。我们必须想象将这三条曲线中的每一条都除以 B,或者说乘以它的倒数。最终得到的结果是:计算曲线原本是线性的,除以 B 后就变成了一条水平直线,即 t_compute;KV 缓存获取也是线性的,同样变成了常数;而权重获取原本就是常数,现在除以 B 后则变成了一条双曲线。
再次强调,我们要计算的是这些项之和的最大值。将 KV 获取和权重获取相加会使双曲线向上移动,形成一条更高的双曲线。然后我们再取它与计算时间这条直线的最大值,最终得到的形状就是我们关心的整体趋势。
我们可以看到一些极限行为。当批大小为 1 时,成本一开始非常高,几乎趋于无穷大,因为权重获取没有被大批量分摊。但随着批大小增加,权重获取被分摊到越来越多的批次元素上,其成本迅速下降,最终只剩下计算时间主导成本。因此,成本存在一个下限,也就是这条直线所表示的值。
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 个,因此这里的稀疏性是 8。
这实际上给出了一个非常接近实际的粗略估计。通常人们会取比这个稍大的值。他们并不真正希望精确处于平衡点,因为现实世界的效率不如屋顶线分析所预测的那样理想。但你可以把这个数值翻倍或三倍。
Dwarkesh Patel
好的,所以最优批量大小大约是两到三千个 token。但如果考虑 KV 缓存,这意味着最优批量大小……
Reiner Pope
应该更大。我们求解的是计算时间等于内存时间的等价条件。如果我加入更多消耗内存带宽的内容,那么可用于权重加载的带宽就减少了。我需要增加更多的内存带宽,因此也需要更大的批量大小。
Dwarkesh Patel
这看起来小得惊人。这甚至不到一个序列吧?
Reiner Pope
请记住,我说的是一旦生成一个新 token,需要处理的 token 数量。实际上有 2000 个独特的序列。
Dwarkesh Patel
明白了。我们只是针对这些序列进行一次前向传播。你把批量大小理解为序列的数量。
Reiner Pope
没错。
Dwarkesh Patel
如果你有一个前沿模型并进行推理,肯定会有超过 2000 个并发用户。由于需要填满整个批次,是否会产生额外的延迟?或者如果有合理数量的用户,是否不太可能在 100 毫秒内填满接下来的 2000 个位置?
Reiner Pope
思考方式是:模型何时发车?假设我已经选定了要运行的批量大小。顺便说一下,这个交点在这里也是相同的。我选择这个批量大小,并且知道它需要 20 毫秒,这是一个常见的结果。
这是 GPU 上正在运行的进程时间线。每20毫秒就会启动一个新的批次,无论当前情况如何。你可以把它想象成火车的时刻表——每20毫秒就有一班火车出发。所有准备就绪的“乘客”(请求)都会登上这趟列车。如果列车已满,他们就要等下一班;如果没满,列车依然会按时发车。
从排队延迟的角度来看,最坏的情况是:一个请求刚好在火车离开之后到达。它必须等待下一班车,最多等20毫秒,然后还要等这趟车完成处理。因此,最坏情况下的延迟是40毫秒。
Dwarkesh Patel
这个20毫秒是怎么来的?
Reiner Pope
这是一个经验法则,但它的来源还没有完全解释清楚。到目前为止,我们主要关注内存带宽和计算时间。当我们考虑内存时,另一个重要因素是我们希望充分利用现有的内存容量。通常,我们会用全部容量来存储权重或KV缓存。在做前向传播时,我们希望在这段时间内把整个内存容量都加载进芯片。这个时间等于容量除以带宽,在很多不同代的HBM上,这个值大约是20毫秒。
Dwarkesh Patel
单位分析是对的,就是字节除以字节每秒。
Reiner Pope
比如,在Rubin这一代上,大概是288GB除以每秒20TB。算下来大约是15毫秒。
Dwarkesh Patel
让我确认一下我理解得对不对。我明白单位分析的部分。它的意思是说,我们可以在这么长的时间内清空并替换HBM内容。所以,我们不希望出现HBM容量不够大,导致无法写入所有需要的数据,或者无法读取所有内容的情况。或者说,我们也不希望来回读写的速度太慢……
Reiner Pope
其实有两种情况。为什么我们不选择一个大于15毫秒的延迟呢?如果我这样想,这意味着我有足够的时间把HBM读取两次。顺便说一下,大多数HBM访问都是读操作,而不是写操作。几乎全是读,因为权重矩阵只读,KV缓存也基本是读。在30毫秒内,我可以把HBM完整读取两次,但这有什么意义呢?我不想重复读取权重矩阵,也不想重复读取KV缓存。
Dwarkesh Patel
非常有道理。有几个问题。如果最优批大小确实是2000左右,那完全取决于稀疏性,与模型大小或其他因素无关。
Reiner Pope
稀疏性会影响模型大小,除此之外,它只取决于稀疏性,而不随规模变化。
Dwarkesh Patel
这是个非常有趣的结果。一个问题在于,这种通过批量推理获得的规模经济效应有多大程度上推动了中心化趋势?不过看起来影响可能没那么大。2000个用户同时在线,算多吗?感觉不算太多。
Reiner Pope
我们可以稍微分析一下。你可以从用户数量的角度来考虑,但更有效的方式是从每秒token数的角度来看。这个批大小意味着系统每秒能处理多少token?
每秒处理的 token 数量将等于批次大小。我们每次时间间隔(15 毫秒或 20 毫秒)运行一批 token,因此每秒大约执行 60 次。最终结果就是批次大小乘以约 60,即 64 × B。例如,若批次为 64,则每秒处理约 2,000 × 64 = 128,000 个 token。这样的单位更易于理解。
很难直接衡量并发用户数,但系统的全局流量是多少呢?有时 API 提供商会吹嘘他们的系统能承载多少流量。我记得去年 Gemini 的一些公告中提到,其全球流量达到每秒数十亿个 token。而我们讨论的只是其中的千分之一。
Dwarkesh Patel
Gemini 规模巨大。它的千分之一也依然非常可观。要在实际竞争中具备 scalability,至少需要达到 Gemini 千分之一的水平。这很有意思。
稀疏性越高,所需的计算量就越少。根据这项分析,随着批次增大,计算资源似乎成了瓶颈。那么问题来了:你能把稀疏性推到多高?随着稀疏比上升——也就是相对于总参数量,活跃参数变少——模型性能会如何下降?下降的速度是否快于你通过提高稀疏因子所节省的计算量?
Reiner Pope
你的意思是模型的质量,而不是速度。很遗憾,我们无法从理论上回答这个问题。这是一个关于模型质量的实证性问题。我能做的最多就是引用一篇论文,用实验数据来回答。
Dwarkesh Patel
我们现在就打开那篇论文看看吗?
Reiner Pope
这篇论文是《Unified Scaling Laws for Routed Language Models》。到这时已经有些年头了,但他们研究的一个问题是:如果我持续增加稀疏性,对模型质量有什么影响?这个答案其实高度依赖于混合专家(mixture of experts)的具体实现方式。混合专家技术由来已久,可能最早可以追溯到 2017 年,但相关方法一直在演进。DeepSeek 的 MoE 架构是一次重大革新。之前也有像“GShard”和“Switch Transformer”这样的早期工作。最终的实证结果都会受到这些因素的影响。
在这张图展示的较老的技术中可以看到,如果保持活跃参数数量不变,然后增加稀疏性(他们称之为 expert count),模型质量反而会持续提升。想象一条从 13 亿参数的稠密模型水平延伸过去,你会发现,在这个例子中,拥有 64 个专家、激活 3.7 亿参数的专家模型,其表现与 13 亿参数的稠密模型相当。
Dwarkesh Patel
所以某种意义上说,要达到 10 倍活跃参数的效果,并不需要把总参数量扩大一百倍。
Reiner Pope
实际上还要更甚。效率提升有限,而参数量的增长却极为显著。
Dwarkesh Patel
所以在这种情况下,其实是 4 倍?
Reiner Pope
64 倍的总参数换来 4 倍的效率提升。
Dwarkesh Patel
所以虽然增加稀疏性的确能带来节省计算时间的优势,表面上看似乎值得做这个权衡。但如果每把稀疏性翻倍,性能只下降 2 倍,而参数量却要增加 8 倍……
Reiner Pope
从内存角度看,这到底是好是坏?记住,这样做会使这部分内存读取量翻倍,不过可以通过批量处理来分摊。所以只需继续增大批量大小即可。就我们目前所做的分析而言,这纯粹是优势。一直这么做,直到用完可用用户为止。
这里存在一个等价关系:如果有很多用户,就可以转向更稀疏的模型。从这个角度看,这是一个合理的权衡。另一个出现的权衡是,它也会消耗内存容量。我们刚才只讨论了内存带宽,但它同样会占用内存容量。
Dwarkesh Patel
我明白了。让我确认一下我的理解是否正确。你的意思是,我们希望减少计算时间,因此增加稀疏性。为了实现这一点,我们需要更大的批量大小,这意味着为了获得更高的稀疏性,我们需要更多的内存容量。
Reiner Pope
也许现在是讨论混合专家层通常如何在 GPU 机架上进行布局的好时机。
Dwarkesh Patel
好的,有道理。我们讲到哪儿了?
Reiner Pope
关于稀疏混合专家模型,或许可以谈谈它在 GPU 上的布局方式。
我们先聚焦于混合专家层,并画出它的结构。通常会有一个路由层,决定如何将 token 分配到不同的专家。token 输入进来后,先经过路由层,然后进入多个不同的专家模块。我会画几个,让它们对齐排列。
路由层会决定把 token 路由到哪些专家,通常只会选择一小部分,比如每 32 个中选 1 个。可能选择这个,也可能选择那个,还有可能选另一个。每个专家本身就是一个普通的 MLP,包含一个上投影层,中间有一个非线性激活函数,然后是下投影层。
最后,我们要执行逆操作:之前是把数据广播出去,现在要把它们汇聚回来并求和。像这样把它们汇集起来。最后再加上残差连接——token 也会直接从这里通过,并与 MoE 层的输出相加。这就是一个标准的混合专家层。
我想重点讲的是这个结构如何映射到 GPU 机架上,以及这对通信意味着什么,因为我认为这会开始揭示我们能实现多高的稀疏度。标准做法是使用专家并行(expert parallelism),即不同的专家分布在不同的 GPU 上。以 DeepSeek 模型为例,它有 256 个专家。假设要在 Blackwell 机架上运行,该机架有 72 块 GPU。
这就出现了可整除性问题。这不是 2 的幂次方。为简化问题,我们只使用其中的 64 块 GPU,忽略其余的 8 块,问题不大。于是每块 GPU 上有 4 个专家。很简单。为了图示方便,我们假设每块 GPU 只有 2 个专家。这样,每对专家就独占一块 GPU。
接下来我们可以看看通信开销。这些 token 集中存储在这里,被路由到所有专家,会产生一定的通信成本;输出时也要进行同样的通信操作。希望这些通信不会成为瓶颈。
那么这里的流量模式是怎样的呢?任何一块 GPU 都可能与其他任意 GPU 通信,具体取决于模型的决策。这是一种全对全(all-to-all)的通信模式。
Dwarkesh Patel
你说的是任何 GPU,那路由器是否就相当于多个 GPU?
Reiner Pope
我画的时候把它当作一个路由器。实际上,你会拥有该路由器的多个副本,而且路由器的数量会和 GPU 一样多,事实上确实如此。
Dwarkesh Patel
随着输入流量的增长。
Reiner Pope
是的。这些是 64 个 GPU,这些也是 64 个 GPU。它们实际上是同一个 GPU,我们之所以画成不同的,是因为它们承担不同的功能。因此,此时任何一个 GPU 都可以向其他任意 GPU 发送数据。
这种全对全(all-to-all)通信模式正是 Blackwell 机架所配置的通信方式,也完全符合 MoE 实际所需的通信模式。不过,如果你觉得一个机架速度太慢,想使用两个机架,那么就会遇到一个问题:比如在这里画一条机架边界线,这样一来,两个机架之间的所有 GPU 就不再具备全对全的通信能力了。机架间的通信最终会成为显著的瓶颈。
这里的关键在于,一个机架限制了你能实现的专家层(expert layer)规模。这一直是推动更大互连域发展的原因之一。
Dwarkesh Patel
在继续之前,或许值得你解释一下“机架”到底是什么?以及机架内与机架间带宽的差异,还有通信是全对全还是非全对全的问题。
Reiner Pope
这一点在不同厂商之间开始变得非常不同,比如 Nvidia、Google,以及我们。通常来说,机架是一个物理结构,高约几米,宽一米到两米不等,具体取决于配置,里面容纳若干数量的 GPU 或 XPUs,通常是大约 64 个。
限制其尺寸的主要因素是供电、重量和散热能力。由于这些物理限制,大多数情况下它就是这个大小。当我部署数据中心时,数据中心可能有数千个这样的机架。我有一个这么高的机架,里面装满了 GPU,然后旁边再放一个机架。
Dwarkesh Patel
听起来太简单了。
Reiner Pope
没错,我就是把它们往里一放就行了。在 Nvidia 的方案中,通信拓扑结构……他们实际上把 GPU 放在机架外部,然后在机架内部放置交换机。结果就是,这里有一组交换机,也就是 NV 交换机。接着拉了很多线缆,每个 GPU 都连接到中间这些交换机上。这些交换机连接所有 GPU,因此所有 GPU 之间只需两步就能通信:先到交换机,再到目标 GPU。
而当我需要离开这个机架时,就必须走另一条路径。GPU 还具备一种更慢的连接方式,通常带宽要低八倍左右。我在 GPU 框图里画的绿色部分就是 NVLink,更一般地称为扩展网络(scale-up network)。通常还会配备一个扩展出网(scale-out network),用于连接数据中心交换机。所有 GPU 都会通过某种方式连接到某个数据中心交换机上,这就是扩展出网,其带宽通常比扩展入网低约八倍。
挑战在于,如果你想将专家混合层分布在两个机架上,那么一半的 GPU 会希望与另一半的 GPU 通信。平均来看,当我观察这些 GPU 上的 token 想去哪里时,有一半的 token 想在本机架内传输——这很好,它们可以使用高速的横向扩展网络;但另一半 token 需要离开当前机架去往另一个机架,这就没那么高效了。它们必须使用一个慢得多的网络,而这会成为 all-to-all 模式下的瓶颈。
另一种选择是:为什么不在中间放一个大交换机,把所有设备都连到一个更大的交换机上,从而把两个机架合并成一个整体?这个方向有很多思路,但总体而言,之所以采用这种交换机的层级结构而不是单一的大交换机,是为了管理线缆拥堵问题——你只需要铺设大量电缆即可。
Dwarkesh Patel
抱歉,你刚才问的问题其实是在问:为什么不直接做成更大规模的横向扩展?
Reiner Pope
没错。为什么不直接让一百万个芯片或一千个芯片进行横向扩展?
Dwarkesh Patel
是什么变化让英伟达从 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
非常 naive 的问题。但如果看看真实的数据中心,感觉机架内部其实有很多空间。我不太确定……电缆本身也挺粗的……
Reiner Pope
机架外部确实有空间。但机架内部……随着优化程度提高,这些机架变得非常紧凑。从托盘到机架的连接器密度很高,而且机架背板本身的密度也非常高。还有其他物理限制,比如电缆的弯曲半径——你不能把它弯得太厉害以至于折断之类的。
Dwarkesh Patel
所以,真正限制因素就是放置电缆的物理空间?真没想到。挺有意思的。看起来有点反直觉,毕竟机架那么大,我们却没法往里面塞更多的电缆。
Reiner Pope
机架设计并非我的专长,但当我与业内人士交流时,他们提到的限制因素往往是多方面的。你们主要优化哪些物理层面的指标?空间、机架重量。实际上它非常重,因此需要足够的金属结构来防止下塌或倾倒。但增加金属又会使其更重。此外还有功耗和散热问题,这些因素相互制约。现代机架正在将这些物理极限推向极致。
Dwarkesh Patel
GPT-4 又是何时发布的?是 2022 年还是 2023 年?
Reiner Pope
2023 年。
Dwarkesh Patel
好的。据说它有超过一万亿个参数。似乎直到最近六个月,模型才陆续发布,其参数量远超三年前发布的模型——而按照预期,这段时间本应出现显著的规模扩展。
我们是否一直在等待具备足够内存的机架,以容纳五万亿参数的模型及其 KV 缓存,同时支持大量用户请求的多条序列?或者在做强化学习(RL)时,也需要考虑实际能承载用于批量问题求解的 KV 缓存?
如果你看看 Hopper 架构,当时有八个 Hopper GPU,截至 2022 年总容量为 640GB。而 Blackwell 终于实现了……?
Reiner Pope
非常近期才部署。可能是在去年。
Dwarkesh Patel
去年。你终于实现了约 10–20TB 级别的扩展,这足以支撑一个五万亿参数模型及其 KV 缓存。
Reiner Pope
在大规模扩展域上的部署是一个巨大的突破。我在这里画出了 Nvidia Blackwell 的部署示意图。Google 的实际部署早已拥有非常大的扩展域。
Dwarkesh Patel
这也解释了 Gemini 为何显得领先。看起来 Gemini 在预训练方面比其他实验室更早取得成功。
Reiner Pope
我当时不在现场,不确定其中有多少归功于成功部署更高的稀疏比,但也可能是多种建模技术的综合成果,特别是专家混合(mixture of experts)的实现方式。我们看到 DeepSeek 的专家混合激活了更多细粒度专家,这是一个重大创新。当然,模型架构和训练数据方面肯定还有其他许多创新。
要区分所有这些因素确实困难,但从实际效果来看,活跃参数受限于计算成本,而总参数量则受限于扩展规模的大小。
Dwarkesh Patel
当你在单个扩展域内运行时,这是否特别针对前向传播或反向传播,还是专门针对 prefilling 或 decoding?无论你是进行预训练、RL 生成,还是为用户推理服务,是否都最好始终保持在同一个扩展域内?
Reiner Pope
非常有意思的问题。要回答这个问题,我们需要先讨论通信模式。我们之前提到过专家混合的通信模式,即 all-to-all。这种 all-to-all 模式强烈偏好全连接拓扑,正如我们刚才展示的,并且更倾向于在同一机架内部完成。
除了专家并行之外,还有其他类型的并行方式,我们刚才已经展示了其中一种。文献中提到的还有张量并行。随着专家规模不断缩小,这种方式的重要性大大降低,因此我们可以忽略它。但另外两种可用的并行方式是数据并行和流水线并行,它们更适合用于多机架部署。
让我们特别关注流水线并行。这是 MoE 的一层结构。我上面还会有另外一百层。比如我现在就可以决定移动到另一个机架,也就是更换机架。那么这是否会成为通信瓶颈呢?我们实际上可以计算出何时会出现这种情况。在代数推导之前,我们先直观地分析一下并画出路径图。我们将有另一个 MoE 层,再往上还有另一个 MoE 层,依此类推。
假设我在这里更换了机架,然后在若干层之后又在另一个位置也更换了机架。我们用来判断在更换机架的位置是否存在通信瓶颈的方法是:比较横向扩展(scale-out)所需的带宽与纵向扩展(scale-up)所需的带宽。我们来写一下这个关系式。关键线索是:这里的发送操作要多得多。这里需要发送很多数据,而这里只发送一个数据,而且可能还要重复多次,这就是造成差异的原因。
Dwarkesh Patel
我可以试着猜猜看吗?出于好奇,想看看我是否真的理解了——看起来你是把批次大小发送到机架里。
Reiner Pope
在这里?是的。
Dwarkesh Patel
但机架内部的通信量是批次大小乘以 GPU 数量。
Reiner Pope
是激活的 GPU 数量。我根本不需要向这个 GPU 发送数据。从图中可以看出,这里的通信量爆炸性地增加了 1-3 倍。关键在于我甚至不需要向这个 GPU 发送数据,这样就节省了很多资源。
接下来我们要讨论的是:纵向扩展是否是比横向扩展更大的瓶颈。我们会直接跳到纵向扩展时间与横向扩展时间的比值。这就是我们讨论的量。
第一个考虑因素是:纵向扩展通常比横向扩展快 8 倍。在基础情况下,如果带宽相同,我们会有 1/8 的比例,这来自带宽差异。但我们还需要考虑发送数据量的增长。如果一个 token 进入这里,它会被路由到,比如在 DeepSeek 的情况下可能是 32 个或 16 个专家。它会路由到一定数量的专家。这就是激活的专家数量。同样的情况会出现在多个不同的层上,所以我可能会运行两层。每阶段也会有多个层。
Dwarkesh Patel
你不应该把所有东西都乘以 2 来考虑 all-to-all 通信吗?
Reiner Pope
对于 up 和 down 方向。是的,有一个 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
与此相反的观点……在这次访谈之前,我和 Jane Street 的 GPU 性能工程师 Axel 聊过。他解释说,要实现流水线并行,必须使用微批次(micro-batches)而不是完整批次。如果使用微批次,就无法定义地将权重加载成本分摊到所有用户或所有序列上。这种方式的积极面是内存占用更少;消极面则是无法将权重加载成本分摊到那么多用户身上。也许有必要解释一下为什么必须使用微批次。
Reiner Pope
我们来画一下流水线的气泡图吧?什么是流水线并行中的微批次呢?
我先从推理(inference)开始讲。这个问题稍微简单一点。我来画一下时间轴,然后标出我们所在的机架(rack)。假设我有四个机架。推理过程会按时间顺序依次遍历这四个机架。这是第零次推理(inference zero),它以某个批次大小(batch size)运行,并按顺序经过所有流水线阶段。
现在我们设想,“我们要在这里运行第一次推理(inference number one)”,这显然非常浪费——每个机架大约四分之三的时间都在空转。实际上我们不会等到这个位置才运行下一次推理,而是会在上一次推理结束后立即启动下一次,也就是紧接着第零次推理之后。如此循环下去。如果我们不提前安排好这些任务,中间就会出现一个空档期,我们称之为“流水线气泡”(pipeline bubble)。在只有前向传播(forward pass)的推理场景中,这种安排一目了然:谁会做这么低效的事?但在训练(training)场景下可能就没那么明显。不过在推理中,这种优化方式却是很自然的。
Dwarkesh Patel
哦,有意思。其实微批处理(micro-batch)和批处理(batch)在推理中根本没区别,因为你可以随便叫它什么。只有在训练中才有意义,因为存在一个最优的批处理大小。
Reiner Pope
是的。
Dwarkesh Patel
在做完整的反向传播(backward step)之前,你需要先把该批次中的所有序列都累积起来。如果在训练中使用流水线并行(pipelining),为了避免那个气泡,你需要——
Reiner Pope
要不要把这个训练的流程图画出来?我们来画吧。这是推理的流程图,我把它标记为“前向传播”(forward),以免搞混了。现在我们也用同样的方式来画训练的流程图。训练也有前向传播阶段,但到某个时刻必须切换到反向传播阶段。
我们在前向传播阶段完成若干个批次后,会一次性将所有计算单元切换到反向传播阶段。推理部分和前面一样,但此时会强制暂停,所有人同时进入反向传播,编号也类似这样排列。
Dwarkesh Patel
或许有必要说明一下,为什么要有这个强制暂停:是因为你希望整个批次一次性完成反向传播步骤。而且这个批次大小本身也有一个最优值。
Reiner Pope
从机器学习收敛速度的角度看,越小越好。因为梯度下降(gradient descent)能获取最新的信息,所以批处理越小越好。
Dwarkesh Patel
但从整体训练时间的角度看呢?
Reiner Pope
从系统性能角度看,批处理越小反而更差。最佳方案是这两者的权衡结果。
所以你选择一个批处理大小,然后在这个大小的基础上,执行一定量的前向和反向计算。你可能会问,为什么非得有这个强制暂停?在流水线并行中,由于存在这个空闲时段(即气泡),文献里提出了很多方法来重新组织流程以消除它。比如一些更复杂的方案,像“零气泡”(zero bubble)或“一前一后”(one-forward-one-backward),会把前向和后向传播交错编织在一起。
Dwarkesh Patel
你可以在那个气泡里挖比特币。
Reiner Pope
没错。更有用的是,你可以在气泡期间执行权重梯度更新(weight gradient step),当然也可以顺便挖比特币。
在推理阶段,流水线对任何你关心的指标(比如批量大小或延迟)都没有影响。它不会提升性能,也不会让情况变得更糟。如果你比较一下这种推理的延迟——分别是在启用流水线的情况下和所有计算都在同一机架内运行的情况……如果所有计算都在同一机架内,我们只需把所有模块堆叠起来,仍然排成一行,延迟是一样的。
流水线对延迟既没有更好,也没有更差。但它确实意味着每台机架所需的内存容量减少了。因为现在你不需要整个模型都加载进来,只需要四分之一大小的模型就够了,而且还能进行扩展。
Dwarkesh Patel
这完全说得通。所以在推理时使用流水线是显而易见的选择,但在训练过程中却面临一个更复杂的权衡问题。
Reiner Pope
实际上,即使在推理阶段,流水线技术也并未被广泛使用。虽然它能降低内存容量的需求,但目前大多数系统其实已经拥有远超需求的巨大内存余量。我记得你说过一台Blackwell机架就有几十TB的内存,而一个拥有上万亿参数的模型只需要1TB左右的内存空间,所以根本装得下。既然原本就绰绰有余,那采用流水线带来的收益其实并不算大。
不过这也说明从理论上讲,也许当时的设计确实存在过度配置内存的问题。如果重新设计硬件架构,完全可以考虑减少每颗GPU上的HBM内存数量。比如你可以这样想:“我不需要这么多内存,因为我可以把权重分布到八个机架上,而不是集中在一个机架里。”这样一来,就可以设计出每颗GPU配备更少HBM的硬件了。
Dwarkesh Patel
宏观层面的问题是:现在大家都在讨论“内存墙”现象。内存成本越来越高,供应严重不足。Dylan提到,由于内存短缺,智能手机出货量今年将下降30%。这太令人震惊了!他还说,超大规模数据中心(hyperscalers)今年的资本支出(CapEx)中有高达50%都花在了内存上。
Reiner Pope
这个说法可信。
Dwarkesh Patel
什么是超大规模数据中心的资本支出?那是数百亿甚至上千亿美元级别的投入,其中一半都用于购买内存?这真是一个巨大的限制因素。正因如此,今年我们才会买不到新的笔记本电脑和手机。
但与此同时,我们又似乎拥有过多的内存?人们还在不断往这些系统中塞入更多内存。如果真的不需要那么多,为什么Jensen还要拼命往机架上堆满内存呢?
Reiner Pope
在我们之前画过的公式中,涉及到了内存时间、内存带宽和计算带宽。现在我们换个角度,直接来看内存容量。
我们先不考虑并行策略,单纯分析对内存容量的需求。总参数数量决定了你需要多少内存来存储模型的权重。此外,还需要为KV缓存预留空间——其大小等于批量大小乘以上下文长度再乘以每个token占用的字节数。
在这个背景下,我之所以主张使用流水线技术,是因为它提供了一种解决上述问题的有效方法。假设我们要在一组GPU上运行任务,其中一个维度E代表专家并行度(expert parallelism),即把某个专家层切分到多个GPU上执行,比如总共64个GPU。另一个维度P代表流水线并行度,也就是将模型切分成多少个阶段,比如选择4个机架。
这是整个系统的总内存需求,但现在我要计算每个 GPU 的内存需求。我会用小写的 cmem 表示。显然,我们只需将这些数值除以 E 和 P 即可。非常简单。具体来说,就是 Ntotal 加上 batch 乘以 context 长度再乘以每个 token 占用的字节数,然后除以 E 乘以 P。
为什么这样划分是正确的?我们知道参数在机架内的所有 GPU 之间是完美划分的,各层也在不同机架之间完美划分,因此这种方式可行。接下来我们会以某种方式安排——我暂且略过具体细节——将上下文在机架内 GPU 之间以及跨机架按层进行同样的完美分片。
Dwarkesh Patel
抱歉,4 是机架的数量吗?
Reiner Pope
是的,举个例子来说就是这样。
这里我们需要回头重新分析一下这个 batch size B。你之前提到过微批次(micro-batching)与全局批次的区别,让我们回到这张流水线示意图。
图中有一个批次正在前向传播,然后如我所画的那样消失了,但这其实并不准确。考虑到解码过程的工作原理:我已经生成了一串 token,进行一次前向传播来生成一个新 token,并将其写入 KV 缓存;接着再进行下一次前向传播以生成下一个 token。实际上,我会把 batch zero 放在一个循环中反复执行。完成一次后,就可以开始下一轮循环,也就是图中的这个位置。我们把它补全一下:有两个、三个、两个和三个,还有两个和三个。
我们把这一批拆分成多个微批次。这里的 batch 将是全局批次大小。B 等于微批次数量乘以每个微批次的 batch size。那么需要多少个微批次呢?从图中可以看出是 4 个:零、一、二、三。每个微批次的大小仍然是那个大约 2000 左右的数值。哦,不对,刚才说错了,其实是 300 倍的稀疏度。
Dwarkesh Patel
这就是每 20 毫秒发射一次训练任务的大小。
Reiner Pope
对的,这就是 20 毫秒级别的“列车”。全局批次大小等于微批次数量乘以本地批次大小,而本地批次大小由硬件参数决定。
微批次数应尽可能少,以便能够无缝衔接而不产生空闲时间。如果数量更少,在循环结束时就会出现空闲期。你可以直观地看到它正好等于流水线阶段数。这可以通过图示证明:确实是 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
我们讨论了跳点的延迟。还有 tmem 延迟。而更大的扩展域显著改善了内存时间延迟。让我回顾一下 tmem。权重的 tmem 等于总参数量除以内存带宽。这里说的内存带宽是指什么?是指单个 GPU 的带宽吗?实际上是指我在扩展域中可以并行使用的 GPU 数量。因为我不能在不同流水线阶段之间并行使用,但它们又不是同时运行,所以我可以并行使用扩展域中的所有 GPU 来加载权重。这实际上非常有效。基本上,我这里得到一个项,这个内存带宽项本身就等于扩展规模……
Dwarkesh Patel
乘以每个 GPU 的内存带宽。
Reiner Pope
对,乘以 GPU 的带宽。这一项增长不多,每代可能只增加 1.5 到 2 倍,但这个从 Hopper 开始增长了 8 倍。
Dwarkesh Patel
所以更大的扩展之所以重要,不是因为整个扩展域的内存容量,而是真的在于内存带宽。
Reiner Pope
没错。流水线完全解决了容量问题,但扩展规模有助于解决带宽问题。
Dwarkesh Patel
而带宽问题能让你支持更长的上下文长度,这在模型越来越具代理能力时变得越来越重要。
Reiner Pope
它让你可以首先以较低的延迟直接运行模型。如果我采用一个非常稀疏的模型,并且它部署在一台小小的 H100 机器上,延迟会非常高。
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
至少会是两个,因为这是下限……
Reiner Pope
大概在 2 到 6 之间。我们就说在 2 到 6 这个范围内吧,先这么定下来。然后再加上推理成本。推理成本是活跃参数数量乘以每次推理的数据量,结果是 2。
Dwarkesh Patel
抱歉,我想我之前表达得有点混乱。简单说明一下:每个参数的前向加反向传播总共是 6;仅前向传播是 2。这就是为什么在强化学习(RL)中,你肯定要生成所有轨迹,但可能不会训练所有轨迹——它的开销就在 2 到 6 之间。
Reiner Pope
是的,谢谢。然后推理成本就是 2。我们实际上是要让这三个项大致相等。这就是目前人们所处的大致水平。实验室有更多关于如何分配资源的信息,比如多做 RL 还是多做预训练更有效。我没有这些信息,但我认为一个合理的估计是三者各占三分之一。
Dwarkesh Patel
我不太明白这个直觉从何而来。另一个简单的模型可能是:RL 加预训练占 50%,推理也占 50%。
Reiner Pope
这也是个合理的答案。因为这只是一个启发式估算,我没法真正去争论哪一个更准确。它们之间的差异其实不大,33% 和 25% 也就差一个不大的比例。
我们就选其中一个吧。三者相等看起来最简单,所以我们就直接假设它们相等。这很直接。我们可以立刻看出激活参数的数量完全消失了,那我们就把它提出来。我们现在就说:预训练数据量——我决定按你的方式处理,这样更清晰——加上……哦,这边我也没考虑效率问题。预训练数据量加上 α 的若干倍 RL 数据量,最终会等于 β 倍的推理数据量。
我们来粗略估算一下 α。这个 α 大概在 2 到 6 之间,由这一项与那一项相比得出。然后我们还有一个效率损失项,我估计大概是 30%。所以这个 α 大概就是 1/10。而这个 β 其实也是一样的,它是三分之一乘以 30%,结果也是 1/10。
Dwarkesh Patel
如果两者都是十分之一,那就意味着 RL 从来不需要反向传播?
Reiner Pope
对的。嗯,我们可以把它改成 2/10,稍微大一点。再写一遍:这个是 2/10,那个是 1/10。
推理 token 的数量只取决于每秒几百亿个 token,以及我的模型运行两个月后才发布下一个版本。这应该决定了 RL 和预训练所用的 token 数量。
我想我们还没把预训练和 RL 的成本等价关系算出来,那就在这里补上。为了让成本相当,预训练数据量应该等于 RL 数据的 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 和预训练的时间相等,那么为了达到相同的实际运行时间(wall time),就需要更少的 token。
Dwarkesh Patel
这很有意思,我从来没从“均衡数据”的角度考虑过这个问题。
Reiner Pope
我认为一开始按成本来均衡是对的,但具体怎么建模成本,其实会趋近于数据的均衡。
Dwarkesh Patel
所以要让 GPT 被最优地训练,每一个使用 GPT-5 的用户所流式传输的总 token 数,都应该等于投入预训练的总 token 数。而投入预训练的总 token 数,就是人类所有知识的总和。每个模型在输入上生成的输出,也应该体现人类知识的总和。
Reiner Pope
没错。人们通常会偏向哪一边呢?如果你认为人类的预测能力并不完美,而且你还冒着风险训练出一个非前沿模型然后直接丢弃它,这就改变了成本权衡——因为推理过程中存在一定的失败概率,所以你应当相应地降低推理 token 的权重。
Dwarkesh Patel
对。我们能倒推出给定规模模型相比 Chinchilla 最优配置多用了多少算力吗?
Reiner Pope
我觉得得先做一些现实世界的假设才能算出来。
推理 token 总数我们肯定能估算出来吧?比如每秒几百兆?现在大概是每秒五亿个 token,我也不太确定。假设一个模型部署两个月后就过时了。
我在脑子里算不出来,你能把它输进电脑里算一下吗?
Dwarkesh Patel
2.6 x 10^15。
Reiner Pope
好的,2.6 x 10^15。但这个数字可能偏大了,因为这通常是一整个模型家族多个模型的总和。我们把它缩小5倍或10倍吧,比如估计每个特定模型每秒处理五千万个 token。模型运行两个月,总共就是大约二十万亿个 token。接下来我们要和前沿模型中活跃的参数量做对比。说实话我不清楚最新的传闻,你知道吗?
Dwarkesh Patel
有人告诉我是一百五十万亿。
Reiner Pope
是活跃参数量吗?
Dwarkesh Patel
抱歉,我说的是 token 数量。
Reiner Pope
是用一百五十万亿个 token 训练的。有意思。
Dwarkesh Patel
差不多嘛。
Reiner Pope
其实还挺接近的。这就是预训练阶段的数据量。
Dwarkesh Patel
这个数据来源不太好查证,不过没关系。
Reiner Pope
我认为通常活跃参数的数量可能在百亿级别,差不多是这个数。也许稍微多一点。所以乘以20就能得到Chinchilla的token数量。因此Chinchilla和DChinchilla大概在两万亿左右。我们看到我们现在的规模大概是它的100倍。
Dwarkesh Patel
DChinchilla到底是什么意思?
Reiner Pope
我猜这是Chinchilla缩放定律推荐的预训练token数量。
Dwarkesh Patel
哦,明白了。那它多训练了多少倍?懂了。
Reiner Pope
这里是两百万亿或一百万亿参数,而Chinchilla最优是两万亿,这就是它超出的部分。也就是说它多训练了100倍。
Dwarkesh Patel
100倍。所以如果你考虑这里的数字,只要大致在合理范围内——仅仅通过思考如何让所有计算资源相等……如果OpenAI也意识到这一点,并且他们每秒能处理一定数量的token,这就告诉你GPT-5预训练用了多少数据。即使误差达到50%甚至更多,能够从基本原理推导出这些数字也是令人惊叹的。
Reiner Pope
这就是为什么你应该到处都做近似估算的原因,因为这里存在很大的误差范围。但设定A等于B然后算出来其实挺有力量的。
Dwarkesh Patel
太酷了。好吧,本着尝试推导各种事情的精神,我们可以公开查一下这些模型的API价格,也许我们能从中学到一些东西。
首先,对于更长的上下文,Gemini 3.1在超过20万token时的费用比低于20万token时高出50%。从宏观角度看我能理解为什么会这样,但为什么偏偏是50%呢?
Reiner Pope
为什么偏偏是50%?宏观上,即使在最初阶段,上下文长度增加也会带来某种成本上升。我们可以回顾一下之前提到的内容:内存时间与计算时间的关系。
我们把之前的那些方程贴出来了:内存获取的时间(权重和KV缓存),然后是计算时间(仅仅是权重的矩阵乘法)。这次我会画出成本曲线,不过这次是以上下文长度为变量,而不是批量大小。这就是以上下文长度为函数的成本曲线。我们会画出计算部分。实际上,计算成本作为上下文长度的函数是恒定的。这里没有对上下文长度的依赖。现实中确实有一些依赖关系,但非常微弱,所以我们忽略不计。这就是计算所需的时间。
然后我们还会画出内存获取随上下文长度变化的依赖关系。它从权重大数值开始,然后随着上下文长度逐渐增长。可能从这里开始,然后随着上下文长度逐步增长。于是你取最大值,就能看到这里有一个拐点。
这就是Gemini可能要支付的成本。接着你想,怎么在这个基础上设计定价结构?你希望无论上下文长度是多少,都能保持盈利。所以我们采用了两层定价结构。某种程度上看起来像这样。
我觉得这说明了一点:既然这个跳跃点出现在20万,很可能意味着它与这个交叉点大致对齐。当然不一定完全对齐。我们甚至可以完成这个计算来看看结果如何。
如果我们能对活跃参数的数量做一些假设,就可以求出每个 token 占用的字节数。因此,在求解每个 token 的字节数时,我们假设内存时间和计算时间相等的位置大约在 20 万个 token 处。于是我们就让这两个时间相等。
我们还会假设批次大小足够大,以至于权重所占用的内存时间可以忽略不计。所以我们干脆不考虑这部分,只关注 KV 缓存实际占用的内存时间。最终可以得到:批大小 × 上下文长度 × 每 token 字节数 / 内存带宽 = 激活参数量 / FLOPs。然后我们来求解每 token 的字节数。这里漏掉了批大小,但它会在后续步骤中抵消掉。另外我也忽略了上下文长度。
我们可以代入具体数值。这个值是前文提到数值的倒数,约为 1/300,在不同硬件平台上相对稳定。我们推测激活参数量可能是一千亿左右,上下文长度为 20 万。但这里似乎有问题——上下文长度应该在分母而不是分子上。
Dwarkesh Patel
1667。差不多两千字节。
Reiner Pope
实际上这很合理。你说的是大约两千字节。让我们验证一下这个数字是否合理。当每个 token 占用很少字节时,人们通常通过两种机制实现注意力计算:一种是各层之间大量复用的密集注意力;Character AI 有一篇博客讨论过这种交替长短上下文的方法。在 Character AI 这类模型(也在 Gemma 模型中出现)中,全局上下文——也就是我们正在讨论的内容——在所有层之间是共享的。
要达到千字节级别,例如可以设置 d_head 为 128(这是典型值)。那么总字节数通常是注意力层数 × 2 × d_head × KV 头数。这就是每层唯一的上下文数量。
你是将上下文在多层间共享,还是只用一次?在类似 Character AI 的模型中,这个数值是 1。我们之前说 d_head 是 128。KV 头的数量通常在 1 到 8 之间变化。抱歉,我刚才说错了,这里指的是 KV 头数。
Dwarkesh Patel
head 和 KV head 的区别是什么?
Reiner Pope
KV head 是存储前序 token 内容的头,保存在内存中。Q head 是检索头,只在临时使用,由当前参与注意力的 token 使用。在这个自回归上下文中,我有与所有上下文关联的 KV head,以及与此新 token 关联的 Q head。
Dwarkesh Patel
但这个 head,128 是指什么?
Reiner Pope
哦,抱歉。这里的 d_head 是向量维度。KV 头的数量通常在 1 到 8 之间。例如有 8 个 KV head 且 d_head 为 128,就能得到这个数值。或者也可以减少 KV head 数量但增加层数。
这是通过密集注意力实现的一种方式。稀疏注意力也能达到类似效果:虽然可以增加这些数值,但会引入一个 1/稀疏度因子。我认为这个数值是合理的,尽管可能稍微偏小一些。
Dwarkesh Patel
有趣的是,他们竟然会通过 API 定价泄露这么多信息。
Reiner Pope
你的定价会被激励着接近成本价,因为否则别人就能把你抢走。
Dwarkesh Patel
也许我们可以从输入价格和输出价格的差异中学到一些东西,这能告诉我们这些模型中解码(decode)和前缀填充(prefill)之间的区别。我记得上次查的时候,大概是贵50%左右?
Reiner Pope
我不太记得了。我之前看到的是贵3到5倍。
Dwarkesh Patel
哦,那这样更合理。假设就是贵5倍。这是计算处理下一个 token 所需的算力——在解码阶段。假设你现在在做前缀填充(prefill),你不是只处理最新的那个 token,而是把前面所有的 token 都并行处理一遍。我想说的是,这会不会是前缀长度乘以这个系数?
Reiner Pope
或者说是整个 pass 的长度。如果我们把解码看作一个长度为1的 pass,那么前缀填充就是一个长度为多 token 的 pass。
Dwarkesh Patel
好的。所以也许叫“前缀”更合适?对了,内存方面:你不会为那些已经在前缀填充阶段生成的 token 保留 KV cache。
Reiner Pope
让我画一下前缀填充是怎么出现的,如果可以澄清的话。我们确实会做一些类似解码的操作。但之后可能还会回来再做一次前缀填充。如果你把它想象成一个聊天会话:用户说一句话,AI 回复,然后用户又说一句新的,这时我们就需要对这个新输入做前缀填充。或许这才是更一般的情况,而不是刚才那种。
Dwarkesh Patel
事实上,这就像你读一个文件一样。
Reiner Pope
读文件,或者 AI 正在响应用户输入、工具调用之类非 AI 生成内容的情况。
Dwarkesh Patel
好,假设我们现在在这里。之前你已经算过所有这些了。也就是说,所有之前 token 的 KV 值都已经有了。那这部分有什么内存开销吗?嗯,主要是内存带宽的开销。如果你用的是 flash attention,那——
Reiner Pope
它基本上是临时的,甚至不会写入主存。忽略这个吧。
Dwarkesh Patel
对啊。所以实际上就只是前面那些 token 的 KV 缓存而已。难道不是这样吗?
Reiner Pope
实际上,内存时间根本不需要调整。
Dwarkesh Patel
好的。太好了。所以只要把这个乘上5倍就能解释了。那为什么会这样呢?这到底说明了什么?这个变量帮助我们限制了什么?唯一可能的变化是:计算量变成了原来的5倍。
Reiner Pope
这是完成一次 pass 所需的时间,但实际上处理的 token 数量要多得多。我们真正关心的是每个 token 的成本,或者说每个 token 所需的时间。
Dwarkesh Patel
我不太确定我理解得对不对。这是针对前缀(prefix)中的下一个 token 的处理吗?
Reiner Pope
不,其实这是针对整个 batch 的处理。按照这个成本来看,我们已经处理了这么多 token,也就是前缀填充的长度。或者更准确地说,是整个 pass 的长度。不是指这个特定的前缀,而是这个总成本。
Dwarkesh Patel
好的。我们就按这个 pass 来算。所以输入(即前缀填充)的成本是原来的5倍。
Reiner Pope
实际上,输出也更贵。
Dwarkesh Patel
输出也是原来的5倍。
Reiner Pope
我们最终希望达到的目标是:前缀填充受计算能力限制,而解码受内存带宽限制。
Dwarkesh Patel
那我们为什么不直接画个图,横轴是 len-pass,纵轴是 t?
Reiner Pope
我们要看的是每 token 的成本,所以应该是 t 除以 pass 的长度。没错。
Dwarkesh Patel
我有点被搞糊涂了。len-pass……感觉当你在做前缀填充的时候,这个值应该更高才对。
Reiner Pope
前缀填充的 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 低五倍,这确实表明他们在很大程度上受限于内存带宽——因为 t 相当于成本(即租用计算资源的费用),所以这里应该是 1,而这里是 5。
Reiner Pope
没错。
Dwarkesh Patel
所以实际上,系统严重受限于内存带宽。真实的图表大概就是这个样子。
Reiner Pope
它还是会交叉,不过没错。
Dwarkesh Patel
完全正确。让我换个方式画一下。这是 decode 阶段内存时间与计算时间的差距。有意思。
另一个有趣的问题是:为什么缓存命中这么便宜?我记得缓存命中大概便宜十倍……根据这些模型的价格来看,写入缓存本身更贵。但一旦命中缓存,就能省十倍的钱。大概是因为把数据保留在 HBM 里的成本,比直接丢弃后再重新加载要划算得多。
Reiner Pope
对的。生成 token 的 KV cache 有两种方式:一种是从头开始计算,基于底层 token ID(非常小);另一种是之前已经生成并存放在某处内存中。
成本比例反映的就是这两种机制之间的差异。缓存未命中意味着你删除了所有内存中的内容,必须直接从 token 重新计算。更进一步说,还可以考虑把它存放在哪一级内存中——比如 HBM。当然还有比 HBM 更慢但更便宜的内存,比如主机上的 DDR 或者闪存。关键在于判断在哪一级内存中存放最合适,而这取决于你要保存多长时间。
我们需要比较不同内存层级(memory tier)的存储成本,以及“rematerialization”(重物化)的成本。“Remat”指的是删除后从头重建整个 KV cache 的开销,也就是我们所说的“rematerialize”。简单来说,这相当于上下文长度乘以单位 token 的成本。实际上,我们会按每个 token 来计算成本,因此不需要全局携带“上下文长度”这个变量。
要重新生成 KV 缓存中的一个 token,我只需要对整个模型进行一次前向传播。这就是计算时间。我必须以 GPU 的速度重新运行计算,然后乘以每秒钟的 GPU 成本。
Dwarkesh Patel
抱歉,冒昧问个外行问题。为什么没有二次项?
Reiner Pope
其实是有二次项的,它体现在计算量中。为了简化近似,我选择忽略它。我快速给你展示一下。如果你看每个 token 的成本,或者每个 token 的 FLOPs,这些 FLOPs 来自于权重矩阵乘法——
Dwarkesh Patel
是平坦的。
Reiner Pope
...与上下文长度有关。然后是 KV 缓存带来的乘法次数,它会随着你关注的内容线性增长。这条线的斜率非常小,以至于当你这样画出来时,它非常接近一条直线。只有在几百万个 token 左右,你才会注意到二次或线性项的影响。所以它并不是特别重要。
Dwarkesh Patel
那么,如果这是真的,为什么没有一家公司能提供超过一百万 token 的上下文长度?
Reiner Pope
长上下文的成本有两个:一个是内存带宽成本,我们已经花了很多时间分析这个;另一个是计算成本。计算成本几乎总是受基本原理限制,其斜率远小于内存带宽成本。真正限制你使用超大上下文的主要因素是内存带宽和内存容量,这正是这种效应。
Dwarkesh Patel
Dario 在播客中提到过,其他人也说过,“我们不需要持续学习来实现 AGI,in-context learning 就够了。”如果你相信这一点,就必须认为我们需要达到一亿 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 缓存,一旦 HBM 被填满,我就无法使用这块 GPU。
如何定价?也许我会说成本与使用 HBM 的比例成正比。还有 GPU 美元成本。让我们再添加一个内存层级,假设存储在 DDR 中。对于 flash 和 DDR,情况类似。
我把它们放错了列。我本意是分成两列。我想区分的重点是:有一个检索成本,还有一个持有成本。这是每秒的成本,而这个是瞬时成本。重新物化(rematerialization)有检索成本,但持有成本为零,因为我们已经删除了它。这就是我之前放错位置的那个。这实际上是持有成本,所以我将重写它。
如果我们只是将其存储在 HBM 中,它具有这种成本结构。如果存储在 DDR 中,实际上需要一些时间。这里的情况相同:DDR 容量除以每字节成本乘以每秒成本。但现在检索成本比 HBM 高,因为我们需要将其复制到 HBM 中。所以这是 DDR 带宽除以每字节成本。同时,它也会消耗一定的 DDR 容量。
Dwarkesh Patel
每个扩展层级都有 DDR 和 flash 吗?
Reiner Pope
这确实是一个部署问题,你可以选择。Nvidia 确实以这种方式部署,它同时包含两者。
Dwarkesh Patel
为什么检索 HBM 的成本不是字节数除以内存带宽?
Reiner Pope
这取决于你对“检索”的定义。在这里,我将检索定义为将其移动到 HBM 中,以便开始实际进行推理。
Dwarkesh Patel
因为如果它已经在 HBM 中,你可以在从 HBM 获取到 SRAM 的同时进行计算?有意思。
Reiner Pope
是的,例如。这是三件事,我想我排序错了。一般来说,如果你在平衡两种成本,并且内存层次结构中有不同的层级,你应该预期随着这个成本上升,另一个成本会下降。你可以大致看出零值的位置。我应该把它排第一,这个排第二,这个排第三。
如果你打算短暂地保留它,那么所有这些都会乘以持有时间。这个和这个也是。
Dwarkesh Patel
有趣的是,它们的写入价格不同。你是否在 API 中指定了五分钟和一小时的区别?这表明五分钟对应 HBM,一小时对应 DDR。
Reiner Pope
我认为这是一个相当好的假设。看看数字,也可能实际上是下一层级,即 DDR 与 flash。
Dwarkesh Patel
有意思。我会查一下价格差异。基础输入 token 的价格是每百万 token 5 美元。
Reiner Pope
Base,意思是 remat。这是 5 美元。
Dwarkesh Patel
那是“读取”的费用。而写入的话,大概是用 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
我想,两者的交汇点或许在于后门。在大型语言模型中设置后门时,你实际上是在隐藏……这算不算一种输入?它不是前向传播中的输入,而是反向传播中的输入。你试图隐藏的就是反向传播中的一个输入。
Reiner Pope
这是一个对抗性上下文?实际上,这正是密码学中“雪崩效应”恰好出现的地方。在图像分类模型中,对抗性攻击的目标是找到图像的一个极小扰动,从而完全改变其分类结果,即彻底改变模型的输出。这种情况在密码学中是理想特性,但在神经网络中却是我们不希望看到的。
Dwarkesh Patel
好的,我之前问过你,神经网络是否真的被用于密码学?我们发现直接在黑板上讨论可能更清晰。那么,它们是否真的被用于密码学呢?
Reiner Pope
使用神经网络进行密码学……一般来说,设计一种新的密码是一项非常危险的工作。几乎全部都被破解了。99%的密码都被破解了,所以这可能不是一个好的起点。但反过来,至少在一个非常明确的案例中,方向是正确的,并且取得了显著成果。
密码学中存在一种构造,后来被引入神经网络,称为费斯妥(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 则成为输出 y。
实际上,如果你考虑两层之前的结构,这正是你之前提到的内容。它实现了两层之前的残差连接。这里的 y 来自上一层,并作为那里的残差连接。由于这种构造,整个结构都是可逆的。
我为什么要关心这个?可逆性有什么意义呢?它最有趣的应用场景是训练。假设我们考虑训练的前向传播过程……比如我有四层网络,按顺序 0、1、2、3 运行它们,就必须把所有激活值写入 HBM(高带宽内存)。此时 HBM 的占用空间会随着层数的增加呈线性增长。
实际上,这往往是训练过程中最大的内存开销。这是正常的训练流程:前向传播正向进行,反向传播则逆向读取。前向传播时写入的数据,在反向传播时必须重新读出。
RevNets 这篇论文的核心思想在于:由于模型是可逆的,我们根本不需要存储这些激活值。我们可以完全重新计算(rematerialize),即在前向传播完成后,反向传播时同步撤销每一步操作,从而按需恢复所需的激活值。这种做法能显著节省内存,是一个非常巧妙的思路。
Dwarkesh Patel
有意思。从某种意义上说,你是用更多的计算量来换取内存的节省。
Reiner Pope
没错。
Dwarkesh Patel
有意思。这和你在 KV cache 上的做法正好相反。使用 KV cache 时,你是用更多内存来节省计算量。
Reiner Pope
是的。在当前硬件条件下,用更多内存来节省计算通常是划算的。
Dwarkesh Patel
太棒了,这次讨论非常精彩。Reiner,非常感谢你参与。我觉得这真正验证了工作室和黑板背后的愿景。
Reiner Pope
是的。
Dwarkesh Patel
好的,再次感谢你的分享。
Reiner Pope
谢谢。
需要完整排版与评论请前往来源站点阅读。