AI 时代最硬核的一课:从逻辑门到 GPU、TPU 与人脑(Reiner Pope 拆解 AI 芯片)
一句话:Reiner Pope 从最底层的逻辑门出发,逐步搭建乘法累加电路、脉动阵列,并一路讲到 GPU、TPU、FPGA 及人脑的架构差异,揭示 AI 芯片设计的核心是在计算密度与数据移动成本之间做面积和功耗的权衡。
文章首先用手工推导展示 4 位乘 4 位加 8 位的乘加单元:用 16 个 AND gate 生成部分积,再用多个 full adder(3 到 2 compressor)反复压缩,最终消耗 p×q 个 full adder。作者指出矩阵乘法每步都是一次乘加,且累加需要更高精度以避免舍入误差累积。这种简洁的 p×q 门数关系以及比特宽度的二次方缩放,在他看来“是低精度算术对神经网络如此有效的单一原因”。Nvidia 在 B300 及之后已将 FP4 性能设为 FP8 的 3 倍,开始反映二次方缩放超越倍速的加速效果。
接着分析数据移动开销:在一个 8 条目 register file 和乘加器之间,每个输入需要一个 n 输入 p 位宽的 mux,其 AND 与 OR gate 数量巨大,导致“几乎所有成本都花在读写 register file 上”。这直接推动了 Volta 代之后 Tensor Cores(脉动阵列)的引入。脉动阵列把矩阵乘法外层循环固化,权重矩阵局部存放在阵列内,通过 daisy chain 缓慢灌入,使进出 register file 的数据量仅随向量维度线性增长,计算量随矩阵规模平方增长,最大化计算相对于通信的比例。但阵列大小与 register file 尺寸之间存在面积分配的持续权衡。
在时钟与架构层面,芯片通过全局时钟每纳秒同步一次,流水线寄存器插入可提高时钟频率却牺牲面积,“如果你把时钟速度推得太高,你会得到更少的并行性”。FPGA 利用可编程 LUT(本质是 16 选 1 的 mux,约 32 个 gate)和可配置 mux 网络实现现场灵活编程,但其实现一个四路 AND 需要 32 个 gate,而在 ASIC 上只需 3 个,这解释了 FPGA 比 ASIC 贵约一个数量级的经验法则。CPU 的非确定性延迟主要来自 cache 的硬件自动管理,而 TPU 采用 scratchpad,由软件显式选择片内或片外访存指令,实现确定性延迟。
宏观对比上,CPU 核比 GPU 核大得多,因为 CPU 芯片面积大量用于分支预测器等复杂控制逻辑,GPU 去除这些并使 register files 更紧凑后获得收益。从高层看,GPU“其实就是一堆小型 TPU”:大量 SM 平铺,每个 SM 内含类似 TPU 的 matrix unit 和 vector unit。这种小粒度设计使 SM 内部数据移动更高效,但跨 SM 通信代价更高,而大脉动阵列能更好地摊薄控制开销。大脑的时钟速度远低于芯片,但单纯放慢时钟并不能带来 1000 倍能效优势,因为动态功耗主要由比特翻转次数决定。
曾在谷歌从事软件效率、编译器以及 TPU 架构方面的工作,后创办 AI 芯片公司 MatX 并担任 CEO。他具备从底层逻辑门到高层系统架构的深入理解,本文中他以黑板推演形式拆解了芯片计算与通信的核心设计取舍。

Reiner Pope
从基本逻辑门出发,一直讲到为什么 GPU、TPU、FPGA 和人脑会呈现出各自的形态。
Dwarkesh Patel
2026 年 5 月 22 日
Dwarkesh 又带来一场新的黑板课,嘉宾是 Reiner Pope:芯片到底是怎么工作的?这次从基本逻辑门开始,一层层往上讲到为什么 GPU、TPU、FPGA 和人脑各自会呈现出现在这样的形态。
Reiner 是 MatX 的 CEO,这是一家新的芯片创业公司(完整披露:我是它的天使投资人)。他之前在 Google 工作,做过软件效率、编译器和 TPU 架构。
这一期建议在 YouTube 上观看,因为你可以看到黑板。

时间戳
00:00:00 – 从逻辑门构建一个乘加单元
00:16:31 – Mux 与数据移动的成本
00:26:10 – 脉动阵列如何工作
00:39:11 – 时钟周期与流水线寄存器
00:51:51 – FPGA 与 ASIC
01:03:25 – 缓存与 scratchpad
01:07:27 – 为什么 CPU 核比 GPU 核大得多
01:12:00 – 大脑与芯片
01:15:33 – GPU 其实就是一堆小型 TPU
文字稿
00:00:00 – 从逻辑门构建一个乘加单元
Dwarkesh Patel
我又请来了 Reiner Pope,他是 MatX 的 CEO,这是一家新的 AI 芯片公司。上一次我们聊的是数据中心内部发生了什么。现在我想理解的是 AI 芯片内部到底发生了什么。芯片究竟是怎么工作的?顺便完整披露一下:我是 MatX 的天使投资人。所以希望你们设计了一颗好芯片。
Reiner Pope
希望如此。我会从芯片设计中最小的基本单元开始,然后一步步搭建到真正量产芯片的样子,以及它有哪些组成部分。在芯片最底层,我们使用的原语是逻辑门,也就是非常简单的东西,比如 AND、OR 和 NOT。这些逻辑门通过导线连接在一起,而这些导线必须作为金属走线被物理铺设在芯片上。
AI 芯片最主要想要计算的函数是矩阵乘法。在矩阵乘法内部,基本原语是一对数字之间的乘加运算。我们会手工演示这个计算是什么样子,然后从中推导对应电路应该长什么样。
如果我用一个四位数和另一个四位数做乘加,会最容易说明。最清楚的原语其实就是乘加。所以这里有这两个项的乘法,然后我们还要加进一个八位数。
Dwarkesh Patel
我可以问一个澄清问题吗?为什么这是计算机内部任何计算的自然原语?
Reiner Pope
有几个原因。它会稍微更高效一点,但它之所以对 AI 芯片来说很自然,是因为如果你看矩阵乘法中发生了什么……简单说,矩阵乘法是什么?它有一个关于 i 的 for 循环,一个关于 j 的 for 循环,还有一个关于 k 的 for 循环,也就是 output [i, k] += input [i, j] x other input [j, k]。矩阵乘法的每一步都会发生一次乘加。
另一个观察是,累加步骤中的精度几乎总是会高于乘法步骤中的精度。这一点是 AI 芯片特有的。你乘的是低精度数字,然后在累加的时候,误差会很快累积,所以那里需要更高精度。这就是为什么我们选择做四位乘法和八位加法。
Dwarkesh Patel
让我确认一下我理解得对不对。有两种方式理解这一点。一种是,这个值会比输入更大。另一种是,如果它是一个浮点数,也许……那部分对我来说不那么直观。但也许原理是一样的?
Reiner Pope
其实就是同一个原理。另一个单独的原则是,当你在对这个数求和的时候,你是在把一大堆数字相加,所以会累积很多舍入误差。而在这个例子中,链条里只有一次乘法,所以乘法过程中并不会累积很多舍入误差。
Dwarkesh Patel
为什么你会把一大堆数字相加?那里不是只有两个数字吗?
Reiner Pope
这个求和会重复 j 次。
Dwarkesh Patel
任何误差都会累积。我明白了。
Reiner Pope
那么我们手工会如何执行这个计算?作为人类,我们可能会把它分成两步,但其实也可以用长乘法一次完成。
先看乘法项。我们会把这个四位数分别乘以另一个四位数中的每一个比特位。我们把它写出来。首先,1001 乘以这个比特位。结果就是这个数本身。然后向左移一位,我们乘的是 0。得到全 0。再向左移一位,乘以这个 1,得到 1001。最后一个比特位则又得到全 0。
这样我们就得到了一堆需要相加的乘法项。在做这个求和的时候,我们也不妨把实际的累加项一起加进去。所以我们直接把它复制过来。这就是这个和。我们想计算的是一个五项求和。
到这个中间步骤为止,我们用了哪些逻辑门?我们需要生成所有 16 个部分积。如何生成其中一个部分积?比如这里的这个 1。我们是通过把这个数字和那边那个数字相乘来生成它的。我们可以用一个 AND gate 生成它。如果这两个比特都是 1,这个数字就是 1。如果其中任何一个是 0,那么 0 乘以任何东西都是 0。为了生成所有这些,我们最终消耗了 16 个 AND gate。一般情况下,如果我要做 p 位乘以 q 位的乘法,就会有 p 乘以 q 个 AND。
最后,我把它们求和。大部分工作都会发生在求和中。让我描述这里使用的另一个逻辑门。AND 几乎是芯片上存在的最简单逻辑门。它几乎是最小的。另一个极端上,通常会使用的最大逻辑门叫做 full adder。
从软件的角度看,你可能会以为 full adder 是把两个 32 位数加在一起。在这里,它只是把三个单比特数字加在一起,所以你可以把它想象成加 0、1 和 1。当我把它们加起来的时候,结果可能是 0、1、2 或 3,所以我只需要用两个比特就能用二进制表达它。它的输入是三个比特,输出是两个比特。数字 2 的二进制是 10。这也被称为 3→2 compressor,因为它接受三个输入比特并产生两个输出比特。
Dwarkesh Patel
我确认一下我理解对了:两个输入是一个 X 值和一个 Y 值,然后还有某个进来的进位……
Reiner Pope
三个输入都是同一个比特位置上的比特,比如这里同一列里的三个比特。两个输出,我这里竖着画,那里横着画,是为了匹配这种竖向和横向布局。这表示同一列里的东西处在同一个比特位置,而相邻列处在不同位置。这个是 carry out,也就是进位输出;而这个是 sum,也就是和。
Dwarkesh Patel
所以如果 full adder 的输入是,比如说 101,那么输出就是 10。如果是 111,就是 11。如果是 000,就是 00。如果是 010,仍然是 01。明白了。
Reiner Pope
对。它本质上就是在数有多少个 1,并用二进制表达出来。这个电路捕捉了我们作为人类在一列里做求和时自然会做的事情。
我会展示一次使用 full adder 做求和的迭代。这里我求和的方式对人类来说会有点不自然。我们会沿着列求和,然后记住进位,但我们这里不去“记住”进位,而是把它显式写出来。我们从最右列向左推进。在最右列,我们把 1 和 1 相加,得到这里的 0,以及一个进位 1。我们已经把这个 full adder 电路应用到了这两个比特上,并产生了一对比特作为输出。
现在我们可以对这一列做同样的事。这一列有四个数,所以我们拿前三个,把它们送进 full adder,得到 0 和 0 作为输出。它们的和是 00。这就是把 full adder 应用到这些比特上。我会把已经用掉的比特划掉,表示我已经处理过它们。
我们再继续一点。我拿这三个数,把它们相加,得到 1 和 0。我已经处理了这三个数。现在我拿这三个数相加,得到 1 和 0,我也已经处理了这些数。
你可以这样看:我有这样一整张需要相加的数字网格。我会不断把 full adder 应用到这里所有的比特上,不断从某一列移除三个数,并写出两个数作为输出。如此反复,直到最后只得到一个单独的输出数字。
这种方法叫做 Dadda multiplier。这是使用 full adder 构造面积高效乘法器的标准方法。我们试着量化一下这个电路的大小,这样就能对它有多大有一个感觉,并且之后可以做比较。
我用了多少个 full adder?我一开始有多少个数字?我有 16 个部分积,也就是这些项和那些项的所有乘积,再加上我要在这里相加的 8 个项。所以我一开始有 24 个比特。最终我输出了 8 个比特。每一步,我划掉三个数,然后写出两个数作为结果。
每一次使用 full adder 都会消除这里的一个比特。所以用了多少个 full adder?一定是 24 减 8,也就是这个电路里有 16 个 full adder。一般情况也是这样。这个电路里会有 p 乘以 q 个 full adder。
Dwarkesh Patel
让我确认一下这个逻辑。输入比特 24,是 p x q,再加 p + q。输出比特只是 p + q。所以 p x q 加 p + q,减去 p + q,等于 p x q。
Reiner Pope
没错。我认为这解释了,或者至少暗示了,为什么我们选择做乘加的第二个原因。第一个原因是它出现在矩阵乘法里。第二个原因是它给了我们这个非常漂亮、简单的 p x q,代数形式很简洁。
我们已经描述了整个过程。我在这里做的每一个原子步骤都会变成一个逻辑门,然后导线会被连接起来。当我有这三个输入,用来产生这两个输出时,如果我把它映射到一个物理设备上,就会有一根导线把所有这三个东西连接到一个逻辑门里,然后这个逻辑门产生这个输出。
这就是 AI 芯片内部在不同位宽下使用的主要原语。我们会从这里继续往上搭建,看看你会如何用它运行所有其他你可能想做的操作。
Dwarkesh Patel
也许现在问这个不是时候,但每当 Nvidia 报告说某个芯片可以做多少 FP4,或者一半数量的 FP8 时,它似乎在暗示这些电路是可替换的,并不是有专门的 FP4 电路和 FP8 电路。但你这里画出来的方式看起来像是,如果它必须映射到逻辑中,你就需要一个专门的 FP4 乘加单元,然后还要一个专门的 FP8 累加单元。它们能被“互换”吗?
Reiner Pope
按这里画的方式,它们并不是特别可互换。这其实是设计芯片时必须做出的主要选择之一:我要有多少 FP4,又要有多少 FP8?有时我会从客户需求的角度考虑这个问题。另一个角度是让 FP4 和 FP8 之间的功耗预算均衡。
Dwarkesh Patel
当它们报告那些数字时,如果它刚好能做 2 倍数量的 FP4 相比 FP8,这只是因为它们选择给所有浮点格式分配等价的裸片面积,结果就变成这样了吗?
Reiner Pope
为什么这个比例正好是 2 倍?其中一部分原因当然不会完全等价于裸片面积。还有一个数据移动方面的原因。也许之后我们会在看数据如何进出内存的时候回到这一点。从软件层面看,有一个非常好的性质:我可以把两个四位数打包到和一个八位数相同的存储空间里。当我把它存入内存时,芯片内部走线总线的尺寸这样安排会非常漂亮。
Dwarkesh Patel
转念一想,它不只是 2 倍。它占用的面积听起来会随着比特长度呈二次方增长。这就是为什么更低精度比你想象中还要更有利。
Reiner Pope
这是一个非常重要的原因。事实上,Nvidia 做过一个改变。从历史上看,一直到 B100 或 B200 为止,每当比特精度减半,FLOP 数量就翻倍。因为你刚才说的这种二次方缩放,那个比例其实略微不对。你应该得到比原先想象中更大的加速。Nvidia 的产品规格在 B300 及之后已经开始承认这一点,其中 FP4 比 FP8 快三倍。
Dwarkesh Patel
虽然它本该是 4 倍。
Reiner Pope
是的。我这里展示的是最简单的整数乘法情况。当你处理 FP4 和 FP8 这样的浮点时,会有另一个项,也就是指数,它会让计算变复杂。
我们现在已经能看出什么?我认为你刚才指出的重大观察是,比特宽度有这种二次方缩放,这非常有效,也是低精度算术对神经网络如此有效的单一原因。接下来我们要做的另一件事是比较乘法本身占用的面积,和它周围所有电路占用的面积。
00:16:31 – Mux 与数据移动的成本
Reiner Pope
我们稍微往回走一点,看看 Tensor Cores 之前的 GPUs 是怎么工作的,这实际上也和 CPUs 的工作方式一样。我们把这个乘加单元放在哪里?我会泛泛描述一个 CUDA core 或 CPU。你会有某个寄存器文件,里面存着一些条目,也许是八个条目,在这个例子中存四位数,但通常是 32 位数。
在 CUDA core 里面,我会有一个某种深度的 register file,然后会有我的乘加电路。它要做的是从这个 register file 里拿三个任意寄存器,执行乘加,然后写回 register file。它会写到这个寄存器,但它可以从这个、这个和另一个随机寄存器读取。所以它会接受像这样的三个输入。
这是许多处理器的核心数据路径。大多数处理器看起来都是这样。你有一组寄存器,然后有一组逻辑单元,或者叫 ALUs。我们想分析从 register file 到 ALU 再返回的数据移动成本。
最终会有某个电路说:“我不总是必须选择这个。我在任意时间点可能选择任何一个寄存器。”我们首先要问的是:我如何构建一个电路?我需要的这个电路是一个 mux。在这个例子中,它会有八个输入,分别来自 register file 的每一个条目,并且有一个输出,实际产生这个输出。
这个东西的成本是什么?我们手里能用来构建它的只有 AND 和 OR。我们怎么构建它?我们做最笨的事。我们形成一个掩码。当我们想读第三个条目时,我们会根据这是不是我们想读的条目,把每一个条目分别与 1 或 0 做 AND,然后再把它们全部 OR 在一起。
Dwarkesh Patel
我先确认一下基础。mux 做的事情就是选择一个输入?
Reiner Pope
就是选择,对软件来说是不可见的。你说“我要第 3 个输入”,这意味着这里有一个 mux。
所以这个 mux 的成本是什么?一个作用在 p 位上的 n 输入 mux。我有 n 行。这里就是八行,每一行宽 p 位。我必须对每一个比特做 AND,所以得到 n x p 个 AND gate。对于每个输入,我都要决定是否要把它屏蔽掉。然后我要把它们全部 OR 在一起。
会有 n – 1 乘以 p 个 OR gate。我有所有这些不同的东西,其中几乎全都是 0,但我需要把八个选项压缩成一个选项。每一步,我都需要把一行 OR 到现有的一行中。
Dwarkesh Patel
有趣的是,你不会从硬件层面去想。你只是想,“哦,我就选第 3 个元素”,而这么简单的一件事本身就是一个相当复杂的电路。
Reiner Pope
这是所有隐藏数据移动成本的第一步。我们只是来比较。我必须付出这个成本。这里有一个 mux,事实上我还有另外两个副本,因为乘加操作有三个输入。因此我这里有这个成本,也就是 3 x n x p 个 AND gate;相比之下,实际做我关心的事情的电路里是 p x q 个 gate。
如果我们代入真实数字,n 等于八,那么仅仅数据移动这里就是 24 x p 个 gate;相比之下,如果 q 是四,那么乘加器本身只是 4 x p 个 gate。
Dwarkesh Patel
那个 3 是从哪里来的?
Reiner Pope
这里有三个不同的输入。我在暗示的是,所有这些工作——它随着 register file 的大小缩放,而这还是一个非常小的 register file——仅仅把数据从 register file 移动到逻辑单元,就比逻辑单元本身昂贵很多很多倍。
Dwarkesh Patel
也许看一下 mux 长什么样会有帮助,可能是一个两位或四位 mux。
Reiner Pope
我们做一个二路 mux。我们有两个不同的数字,也就是这两个输入。这些是要在其中选择的输入,而选择器可以是“我要这个”,也可以是“我要另一个”。这是一种 one-hot 编码。
这是我们开始时的样子。我们专注看这个情况。这是我们实际拿到的输入,我们想产生这个作为结果。非常繁琐地,我们把这个比特和所有这些做 AND。也就是把这个比特和这一行做 AND。同样,我们把这个比特和这一行做 AND。这会产生全 0。这里有四个 AND。
最后,我们把这两个 OR 在一起,得到 1。把这两个 OR 在一起,得到 1。把这两个 OR 在一起,得到 0。把这两个 OR 在一起,得到 1。这就是四个 OR。这最终看起来有点像加法。我们做了完全同一组 AND。我们把所有这些东西都 AND 在一起,但随后不是用 full adder 电路把它折叠,而只是用 OR gate 做了一个非常简单的折叠。
Dwarkesh Patel
但那看起来不像是 n 乘以 p。
Reiner Pope
这里是 n=2 个输入。一般情况下,我们会有 n 行,每行有 p 个比特。这就给了我们 n 乘以 p 个 AND gate。在我描述的这个电路里,几乎所有成本,八分之七的成本,都花在读写 register file 上,只有极小一部分成本花在逻辑单元本身上。
这就是要解决的问题。本质上,这是 Nvidia GPU 的 Volta generation 之前的状态。这类东西就在 CUDA cores 里面。这个问题陈述推动了 Tensor Cores 的引入,更通用地说,它们叫做脉动阵列。
想想我们要怎么解决这个问题。我们把几乎所有电路面积都花在了一个我们其实并不关心、而且对软件程序员隐藏起来的东西上,而我们真正关心的东西占的面积不多。那就设法让后者变大,同时让前者保持同样大小。这就是目标。
00:26:10 – 脉动阵列如何工作
Reiner Pope
演化过程是,在这个阶段,我们已经把这么多东西烘焙进了硬件。这一行就是一个乘加,而这个单独的东西被烘焙进了硬件。脉动阵列的想法是,向上提升两层循环,把整个外层循环烘焙进硬件。想法是,如果我们有一个粒度大得多的固定功能逻辑块,那么也许我们在输入和输出上支付的税就会小得多。
Dwarkesh Patel
有意思。听起来你是在说,如果你在矩阵乘法循环里往上走一步,就可以让天平更多地偏向计算,而不是通信。
Reiner Pope
没错。这里我们会利用两个效应。一个是,每一次经过 register file,我们可以做更多事情。另一个是,在这个循环的一些部分里,我们可以利用某些东西保持固定这一事实。
视觉上,我们来看看这个矩阵乘法。循环的这一部分对应一个矩阵-向量乘法。我们拿一个矩阵乘以一个向量。怎么做?每一列都和向量相乘然后求和。我们会沿着列求和。
这个 0 和 3 会乘以 3 和 7,然后求和;然后 1 和 2 会乘以 3 和 7,然后求和。矩阵里的每一个条目都对应一个乘加。我们把这四个乘加画出来。
Dwarkesh Patel
我确认一下为什么有四个乘加:输出向量中每一个条目对应列上的一个点积,在这个例子里会是两次乘法,然后把这两次乘法相加。你在累加……
Reiner Pope
每个点积其实只有一次加法,但我们喜欢从 0 开始。
Dwarkesh Patel
但它包含 0 的初始化。
Reiner Pope
对。我们希望计算量按二次方增加。相比之前,我们有 x 乘以 y 倍的计算。但我们希望通信量只按 x 倍增加。目标是让这个优势项按 y 增长。
我们已经放下了这些乘法。我们想引入一个大小为二的向量,这已经符合我们关于列的目标。这没问题。然而,我们需要管理这个矩阵的通信,而它超过了我们的 x 预算。
想法是,在 AI 场景下,这个矩阵会在相当长的时间内保持固定。这里放着一些 register files。从这个 register file 出来的东西量……从某种意义上说,这就是我们希望按 x 增长的项。我们不想每个周期都从 register file 把整个矩阵带进来,因为从 register file 拉线的成本太高。
我们的关键技巧是,这个矩阵可以被局部存储在脉动阵列里。我们会把这些数字 0、1、2 和 3 存在一个叫做寄存器的门里,它会物理存储这些数字,然后我们会用这些数字一次又一次地服务大量不同的向量。
Dwarkesh Patel
这里的优化是,矩阵乘法的性质让你可以把这个平方级的东西直接存放在逻辑发生的位置,相比不断换入换出的输入,它多出一个维度。
Reiner Pope
没错。
Dwarkesh Patel
这是矩阵乘法本身的性质。你做很多乘法,才得到一个值。一个点积是许多乘法的结果。所以这个优化意味着你可以在得到某个输出值之前塞进很多乘法。
Reiner Pope
没错。为了把图补全,具体来说它是这样:我在这里把 3 和 2 交换了。就像这个 0 和 3 要乘以 3 和 7 一样,我们会沿着这里的列形成一个点积。我们会把 3 和 7 喂到这里。它进入这个乘法,也进入这个乘法。同样,3 进入这里,也进入这里。然后我们会沿着这里求和。从某一列的顶部开始,我们喂入 0,然后从底部出来时得到结果。
视觉上,矩阵的一列上执行一个点积,而这正好映射到脉动阵列中空间上做的事情。这是一个竖直求和的点积,这是第二个同样竖直求和的点积。
需要进出 register file 的数据是什么?我们有 x 量的数据作为输出出来,也有 x 量的数据从输入进来。至少就输入和输出向量而言,我们已经达成了只有 x 量数据进出 register file 的目标。
这留下一个问题:我刚才说权重矩阵被局部存储在脉动阵列里,那它最开始是怎么进去的?在某个时刻,你需要启动芯片并填充这些数据,那它们来自哪里?
技巧是,我们非常缓慢地做这件事。我们非常缓慢地把它滴灌进脉动阵列。最简单的策略是运行一个 daisy chain:这里喂入一个数字,下一个时钟周期它会下移到脉动阵列的下一个条目。我们可以在每一列并行做这件事,这个也会从这里来,这样我们就又得到一个大约 x 单位带宽的输入因子。
Dwarkesh Patel
你介意把那句话再重复一遍吗?
Reiner Pope
我们知道,我们只是很少把数字带入矩阵。我们只想提出任意一种构造,使得跨越脉动阵列边界的布线数量被限制在 x,而不是按 xy 增长。
一种特别简单的策略是,我们在一个时钟周期把一个数字带入脉动阵列的顶行。然后,接下来连续 y 个时钟周期,我们每次都带入顶行,并把所有其他行向下移动一格。这样就能把需要来自昂贵 register file 的布线保持在 x 这个因子上,而不是 xy。
Dwarkesh Patel
我明白了。通信有两个问题:通信时间和通信带宽。你是说,因为我们只会加载一次,所以我们要最小化带宽,因为带宽等于裸片面积。我们通过更小的通道慢慢加载它,因为我们会把这个值保留在那里一段时间。
Reiner Pope
完全正确。
Dwarkesh Patel
有趣的是,当我们上次谈到很多芯片之间的推理时,我们试图优化的高层大问题,是增加每单位内存带宽——也就是每单位通信——对应的计算量。这里也是,我们试图增加实际乘法或加法的数量,相对于把信息从寄存器传输到逻辑单元的数量。在两种情况下,你都是在尝试最大化计算相对于通信的比例。
Reiner Pope
这在整个技术栈上下都会出现。这里已经很接近底层、接近 gate 了。还有一个版本也许更接近 gate,就是你选择使用的数字格式的精度。我们也看到了同样的效应。无论是在 ALU 的精度里,还是在矩阵大小里,都有平方项与线性项的关系。
这个单元是下一个更大的单元。我们有乘法电路,在它之上有一个相当大的脉动阵列。我把它画成 2x2,但较旧的 TPUs 被描述为这个电路的 128x128 阵列。这最终成为实现矩阵乘法的已知最高效电路。
Dwarkesh Patel
我们已经谈过,最大化计算相对于通信的比例看起来似乎显而易见。有哪些不那么显而易见的权衡,会让你晚上睡不着?比如该做 X 还是做 Y,而答案并不明显。
Reiner Pope
芯片设计中的大多数决策都是尺寸决策。就我们目前画出来的内容而言……AI 芯片都有这个电路。它们有一个脉动阵列,附近某处有一个提供输入和输出的 register file。
即使在这个范围内,你需要回答的尺寸问题也包括:我的脉动阵列应该做多大?我的 register file 应该做多大?这两个问题是耦合在一起的。一种思考方式是,设定一个预算:你希望把芯片面积的百分之多少花在数据移动上。也许我说,我希望这部分是 10%,而脉动阵列是 90%。
然后我可以确定 register file 的大小。更大的 register file 更灵活。它们可以让我得到更多应用层面的性能,但它们会减少花在脉动阵列上的面积。
00:39:11 – 时钟周期与流水线寄存器
Dwarkesh Patel
芯片的时钟周期在哪里出现?它由什么决定?芯片的时钟周期是什么?
Reiner Pope
从基础上说,值得观察的是,芯片是极其并行的。一个芯片里有 1000 亿个晶体管。每当你有大规模并行时,一个关键任务就是在不同并行单元之间同步。
在软件里,你通常会有非常昂贵的同步方法,比如 mutex。一个线程完成它正在做的事情,抓取内存中某处存放的锁,然后通知另一个线程它已经完成了。在芯片上,我们采用非常不同的方式。大约每纳秒一次,芯片上的所有电路都会暂停片刻并同步。这就是时钟周期。整个芯片通常会一起进入下一个操作,步调一致。
这在电路中看起来是由寄存器调节的,而寄存器就是我们在别处画过的那些存储设备。你可以这样理解:我有某个存储器存着一个比特,可能是 0 或 1。然后我有一团逻辑,也许是这个脉动阵列或乘法器。有一堆输入喂进这团逻辑,最终会有某个输出寄存器被写入。
有一个全局时钟信号驱动所有这些寄存器。在某个瞬间,当时钟敲响时,那根导线上在那个瞬间恰好是什么值,就会被存储下来。
挑战在于,我希望时钟速度尽可能快。如果我以 2 GHz 运行,那么每秒完成的操作数就是 1 GHz 的两倍。但这意味着我对穿过这团逻辑的延迟非常敏感,因为里面发生的任何计算都必须在下一个时钟周期到来之前完成。任何芯片中的一个主要优化点,都是尽可能缩短这个延迟。
Dwarkesh Patel
有意思。这里的约束似乎是,如果你加入太多逻辑,可能会错过时钟周期。但如果加入不够,你又会把潜在计算量留在桌面上。有没有一种情况是,你以概率方式赌某个计算会完成?还是说它严格地要么在时钟周期内完成,要么没有完成?
Reiner Pope
在标准芯片设计中,你会留出余量,使得虽然存在概率,但它在很多个标准差之外。就实际目的而言,它是一个可靠部件,总是能赶上时钟。
也有一些奇怪的例外,比如时钟域交叉,也就是从一个时钟进入另一个时钟。那时你确实必须考虑这个概率。但在主路径上,你会留出余量,让它提前 25% 个时钟周期到达,使得错过的可能性非常低。
Dwarkesh Patel
时钟同步的地方,也就是寄存器所在的位置,是你作为芯片设计者决定的吗?还是说你想要某个逻辑序列,然后把你的 Verilog 转换成送给 TSMC 的东西的软件,会自动决定为了让它工作,你必须在这里、这里、这里放寄存器,确保没有单一步骤让整个芯片的时钟周期变得比必要的更长?
Reiner Pope
插入这些寄存器其实是芯片设计工作中非常大的一部分。它是手动方法和自动方法的组合。
为了展示你可以做的最笨版本,你可以把这段逻辑拆成两半。不是只有一团逻辑,而是有两团更小的逻辑,它们做同样的事情,但中间用一个寄存器分开。如果你正好从中间切开,就可以达到两倍的时钟频率。这很好,你获得了两倍性能,但代价是额外一个寄存器,也就是更多存储。
Dwarkesh Patel
退一步说,我们为什么需要同步整个芯片?如果你想象自己在玩 Factorio 之类的游戏,那里没有全局时钟周期。事情就是完成了就完成了。铁板在那里,你想拿就拿。
Reiner Pope
沿着这个类比,你需要注意的是,如果我有两条不同路径穿过某些逻辑。假设我这里要做计算 f,这里要做计算 g,然后它们会在计算 h 处会合。
制造过程中会有差异。在一些芯片上,f 会花稍微长一点时间;在另一些芯片上,g 会花稍微长一点时间。如果我有一个信号传播过去,而来自 f 和 g 的结果必须在 h 处会合,那么可能出错的是,f 先到达,然后它遇到的是上一次的 g 值,或者下一次的 g 值。
Dwarkesh Patel
啊。然后 h 需要知道什么时候开始,什么时候下一轮迭代已经……
Reiner Pope
完全正确。
Dwarkesh Patel
这解释了为什么在同一个制程节点、同一种 TSMC 技术下制造出来的不同芯片,可以有不同的时钟周期。两个用 3 nm 制程做出来的芯片,可能会有不同的时钟周期,取决于它们是否优化到能确保没有单个关键路径长到拖慢整颗芯片的时钟周期。
Reiner Pope
没错。我这里展示的这个优化叫做流水线寄存器插入。我们在流水线中间插入了一个寄存器。这是时钟速度和面积之间的纯粹权衡。那是简单情况。还有更难的情况。我画的是一条逻辑流水线,但在其他情况下,你可能会有某个计算实际上反馈到自身。它运行某个函数 f,然后写回自身。比如,这可能是一个加法器,每个时钟周期都加一个数。这个小电路本质上对不同周期呈现出来的所有数字求和。
挑战是,如果这个加法花的时间太长,我能做什么?如果我尝试在它中间直接放一个流水线寄存器,它会改变所执行的计算。它不再形成所有输入的运行总和,而是会有两个不同的运行总和。我最终会得到偶数项的运行总和和奇数项的运行总和。这个约束——也就是我的逻辑里有一个循环,而所有芯片在某处都有循环——是最难处理的事情,也会设定时钟周期。
Dwarkesh Patel
我不明白为什么那会是个问题。我甚至不确定在那里放一个寄存器是什么意思。它是不是某种原子操作?
Reiner Pope
嗯,加法并不是真正原子的。
Dwarkesh Patel
正如你刚才演示的那样。
Reiner Pope
做一次求和需要大量工作。你可以把那项工作前面的部分拿出来,在中间放一个寄存器,然后再处理后面的部分。
Dwarkesh Patel
好。TSMC 提供一个 PDK,里面规定了它们可以在芯片中给你的逻辑原语。由它们来决定,没有一个原语大到超过它们希望某个制程节点目标对应的时钟周期。但除此之外,你难道不能只是把所有来自 TSMC 的原语拿过来,然后在它们之间按需要不断加寄存器,直到达到你想要的时钟周期吗?
Reiner Pope
作为逻辑设计者,芯片架构师会设定时钟周期。比如,你从 TSMC 得到的原语,大致是 AND gates 或 full adders 这个量级。它很大程度上取决于电压以及你选择哪种库,但通常一个时钟周期里你可以顺序放大约 10、20 或 30 个这种东西。这些原语非常快,也许是 10 皮秒。
作为逻辑设计者,原则上,如果你只是有一个寄存器和一个 AND gate 构成循环,就可以得到极其快的时钟速度,超过 4、5 或 6 GHz。但如果你拿这个非常简单的电路来看你花了多少面积……这被称为一个 gate equivalent 的大小,也就是面积单位 1。这个东西也许是面积单位 8。
再一次,几乎所有成本都变成了同步或通信成本,相比真正的逻辑成本而言。这就是一个你走得太远的情况。你把时钟速度做得非常快,但代价是几乎所有面积都花在了流水线寄存器上。
Dwarkesh Patel
有意思。所以你在暗示一种动态:你可以有非常快的时钟速度,但你并没有完成那么多工作。你可以有低延迟,但吞吐量低。
Reiner Pope
事实上它会伤害吞吐量,因为芯片的吞吐量等于每个时钟周期完成多少工作——这基于面积效率——乘以每秒有多少个时钟。
Dwarkesh Patel
这其实和我们上次讨论 batch size 时非常相似。如果 batch size 很低,任何一个用户都可以很快收到下一个 token,但在比如一小时内处理的总 token 数会低于原本可以达到的水平。
Reiner Pope
完全一样。如果你把时钟速度推得太高,你会得到更少的并行性。
00:51:51 – FPGA 与 ASIC
Dwarkesh Patel
我记得我之前和 Jane Street 的一位 FPGA 工程师 Clark 聊过,他帮我准备过我们之前的那次访谈。他解释了为什么他们使用 FPGA。我想,对于高频交易来说,吞吐量不如延迟重要,所以以确定性的方式对时钟周期有非常具体的控制,才是最重要的。也许聊聊为什么不能直接用 ASIC 做到这一点会很有意思,或者为什么你可能会用 FPGA 来为高频交易获得确定性的时钟周期。
Reiner Pope
我们先考虑 FPGA 和 ASIC 的商业情况。FPGA 和 ASIC 在很大程度上使用同样的概念模型。你有一系列由小原语构成的 gate——AND、OR、XOR——通过导线连接起来,在固定时钟周期中运行。你能在 FPGA 里表达的任何东西,也都能在 ASIC 里表达。ASIC 会便宜大约一个数量级,而且能效更好。
权衡在于,第一块 FPGA 的成本是 1 万美元,而你做出来的第一颗 ASIC 成本是 3000 万美元,因为它需要一次完整的 tape-out。FPGA 的商业使用场景是:当你需要某种具有非常确定性延迟、快速运行和高并行性的东西,但你会频繁更改工作负载,也许每个月都改一次。你不想每次都支付 tape-out 成本。
FPGA 实际上是如何在一块固定硬件里模拟 ASIC 编程模型的?它的核心有我们刚刚谈过的两个组件。它有寄存器作为存储设备,也有查找表(LUTs)提供所有 gate。
然后还有第三个组件。我们有一群这样的寄存器和 LUTs,它们由一大组 mux 连接起来。在每一个这些东西前面,我们都有一个 mux,它可以从其他所有地方选择一个输入。我们有大量不同选项喂入所有这些东西。
这允许我们本质上在编程 FPGA 时,把所有这些组件拿来,然后叠加某种特定布线:经过这个 LUT,喂到另一个 LUT,送到这个寄存器,然后再喂到另一个 LUT,或者类似的东西。
我用橙色画出来的是你如何……FPGA 是 Field-Programmable Gate Array。橙色是在 field 中被编程的东西,而白色是为了最初制造这个设备,FPGA 里必须存在的所有导线。
Dwarkesh Patel
programmed in the field 是什么意思?
Reiner Pope
programmed in the field 意味着这个设备已经部署在数据中心里。它在真实世界中运行,然后你可以来对它编程。
Dwarkesh Patel
啊,不是 field 像电场那样的 field。是像现实世界外面那个 field。好。
如果我看这个 field programming 从第一个 lookup table 出来,进入第二个 lookup table,它是如何工作的?
Reiner Pope
让它发生的导线在哪里?我画这些时有点偷懒了。这里的每一个设备前面都有一个 mux,可以从所有附近可用的电路中选择。FPGA 的实际配置就等于 mux 控制。在这个 mux 里,我们有数据输入,也有负责选择的控制。
每一个 mux 旁边都有一个小存储设备,告诉它:“你要从这里取得输入。”对它编程就是配置每一个这样的 mux。
Dwarkesh Patel
这说得通。lookup table 内部发生了什么?
Reiner Pope
lookup table 也会有一点控制,告诉它该做什么。它的目的是可配置地充当 AND gate、OR gate、XOR 或任何不同的 gate。你可以考虑很多种方式来做到这一点。传统 FPGA 中的做法是……一个 lookup table 有四个输入比特和一个输出比特。从四个比特到一个比特有多少个不同函数?有 16 个不同函数。
你可以把它列成一张 16 个不同数字的表。你有一个表,比如 0111001,共 16 个条目。这张表存储在这个蓝色配置位里。它把这四个比特视为二进制数,查找表中相关的行,然后输出那个比特。这本质上就是 lookup table 的 truth-table 视角。
Dwarkesh Patel
好,所以如果你想一个 AND gate、OR gate、NOR gate、XOR gate,它们都接受作为输入……
Reiner Pope
那些是双输入函数。有时我们有三输入函数,比如三路 XOR,或者四路 XOR。
Dwarkesh Patel
在这种情况下,它是否只是取决于它有多大?
Reiner Pope
LUT 的典型大小是四个输入。这有点像一个甜点位。这里还有一个 compute vs. communication 权衡。如果输入太少,你就需要使用更多 LUT。
Dwarkesh Patel
基本上,lookup table 就是一个真值表。通过真值表,你可以编程出任何你想要的 gate。所以与其说 lookup table,你可以把它想成一个可编程 gate。
Reiner Pope
没错。你可以在这里做的一件事是,看出 FPGA 比 ASIC 贵一个数量级这个经验法则来自哪里。你数一下这个 lookup table 内部会有多少个 gate。
我们本质上可以把这个 lookup table 视为这些 mux 中的一个。它必须在 16 个不同值中选择,所以它是一个 n=16 选项、p=1 bit 的 mux。正如我们之前看到的,这个电路的成本是 n 乘以 p 个 gate。所以它的成本是 np,也就是 16 个 AND gate,还有 16 个 OR。
Dwarkesh Patel
这个电路指的是 mux?
Reiner Pope
正是 mux。
Dwarkesh Patel
进入 lookup table 的那个 mux?
Reiner Pope
lookup table 本身你可以想成一个大的 mux,它从所有 16 行中选择一行作为输出。这就是 lookup table。
Dwarkesh Patel
但你这里画出来的方式,是一个 mux 然后一个 lookup table。
Reiner Pope
一路向下都是 mux。这里内部有第二个 mux。这个 mux 就是这个 mux。
Dwarkesh Patel
另一个 mux 只是说……
Reiner Pope
它来自这堆 gate 中的哪里。
Dwarkesh Patel
对,第二个 mux 是说,“好,现在你有一个值,但那个值仍然是一个四位值。”
Reiner Pope
是的,我从这团东西里选了四个比特。然后我用这四个比特来选择 lookup table 中的哪个条目。
Dwarkesh Patel
假设第一个 mux 从附近八个寄存器中取输入。总共有 32 个比特进入。然后从中出来四个比特。这四个比特进入第二个 mux,也就是 lookup table 里面那个 mux。
Reiner Pope
在这个例子里,这些寄存器是单比特寄存器。如果附近有八个寄存器和 lookup table,那么附近进来的总共是八个比特。我从八个比特中选择四个不同值。实际上有四个不同的 mux,每个输入比特对应一个小 mux。它们各自从八个里面选择一个。
Dwarkesh Patel
那八个来自哪里?
Reiner Pope
附近的寄存器和其他 LUT。
Dwarkesh Patel
每个寄存器是一个比特。
Reiner Pope
是的。
Dwarkesh Patel
我猜 AMD 或者谁在制造这些 FPGA 时,仍然必须对哪些寄存器连接到哪些寄存器有自己的判断。你可以实际编程 gate,但它们会在线路连接中加入导线……也就是通信拓扑,对吧?
Reiner Pope
你在局部粒度上获得灵活性。有一个附近邻域可以从中选择,但对于更粗粒度、长距离连接,它们会形成自己的判断。
Dwarkesh Patel
它慢 10 倍的原因是什么?
Reiner Pope
如果你看构建这个 lookup table 的成本,它是 32 个 gate。它可以给我等价于——有什么有意思的东西我可以在这里做——一个四路 AND gate。四路 AND 意味着 AND、AND,然后再把 AND 的结果做 AND。这是一个我可以在 ASIC 里直接用三个 AND gate 实现的电路。用 LUT,我也可以实现它,但它需要 32 个 gate,而不是三个。
Dwarkesh Patel
所以开销真正来自这样一个事实:描述一个真值表有一种比列出所有可能输入组合更简洁的方式,也就是直接写出 gate。
Reiner Pope
是的,直接放下多晶硅和导线等等。
01:03:25 – 缓存与 scratchpad
Dwarkesh Patel
有意思。你对我说过一个重要点:他们更喜欢 FPGA 而不是 CPU 的原因,是他们能得到确定性的时钟周期。他们知道一个 packet 什么时候会进来、什么时候会出去。为什么 CPU 不能保证这一点?
Reiner Pope
你其实也可以设计一个具有确定性延迟的 CPU。事实上,许多 AI 芯片内部的处理器也有确定性延迟。Groq 就宣传过这一点。TPU 的核心里也有。
挑战在于同时获得确定性延迟和高速度。非确定性延迟来自 CPU 中特定的设计选择。实际上可以去掉这些设计选择,做出一个具有确定性延迟的 CPU,但这些 CPU 在市场上并不很有吸引力,所以现在人们不再制造它们。
从某种意义上说,确定性延迟是一个更简单的起点,而一些芯片设计者添加了一些东西,让它变成非确定性。举一个具体例子,也许 CPU 上最重要的非确定性来源就是 CPU cache 本身。
在 CPU 中,你有 CPU die 本身,然后旁边有 DDR memory。CPU 内部有一个 cache 系统,会记住最近访问过的 DDR 并存储它们。当我运行 CPU 指令时,每当有一条访问内存的指令,它首先检查数据是否存储在 cache 里。如果没有,它就从 DDR 获取。
这是一个巨大的优化。cache 比 DDR 快两个数量级。如果你从不使用 cache,基本上所有程序都会慢 100 倍。cache 的存在对于 CPU 以合理速度运行是绝对必要的。
但你是否 cache hit,取决于 CPU 的环境:还有哪些程序在运行,最近运行过什么,以及 cache 系统内部的随机数生成器在做什么。这就是 CPU 运行时间中一个重要的非确定性来源。
这就是 CPU 的内存系统。你可以做出的重大不同是,不让硬件说“我要读内存”然后由硬件决定它是否来自 cache,而是把这个决定烘焙进软件里。这是另一种设计哲学。
你会在 TPU 里看到这个。我要画同样的图,但把它叫做 scratchpad。主要区别是……这会是一个 TPU,你这里有 HBM,而不是 DDR,但它仍然是片外内存。软件不再说“先访问内存”并让硬件决定,而是有一种指令去 scratchpad,另一种完全不同的指令去 HBM。
这种风格通常被称为 scratchpad,而不是 cache。关键区别是,你有一种指令说“读或写 scratchpad”,另有一种完全不同的指令说“读或写 HBM”。
Dwarkesh Patel
所以 scratchpad 就是 cache。
Reiner Pope
对,这里这个东西就是 scratchpad。
01:07:27 – 为什么 CPU 核比 GPU 核大得多
Dwarkesh Patel
退很远来看:人们说计算机有“冯·诺依曼架构”,也就是信息的串行处理。也许只是因为我们一直在谈并行加速器,但 FPGA 是超级并行的。AI 加速器、TPU 也是超级并行的。即使 CPU,如果你考虑它们有多少核心,也是超级并行的。那么现代硬件在什么意义上仍然是冯·诺依曼架构?用这个描述现代硬件真的公平吗?
Reiner Pope
我认为用它描述 CPU 是公平的。CPU 上的并行量大约是 100 个核心,乘以可能 16 路 vector units,所以在 CPU 上大约是 1000 路并行。
Dwarkesh Patel
一个问题:CPU 使用了一块 die,如果 threads 更少,那么仅仅从晶体管电压开关的角度看,是不是字面上只有一个控制流——die 的一小部分——在那里电压开关?
你到底是如何占用 CPU 的 die 面积的……
Reiner Pope
如果核心这么少,你把所有 die 花在了哪里?
Dwarkesh Patel
对,那里发生了什么?
Reiner Pope
核心只是大得多,也复杂得多。我们应该把一个 CPU core——它占 die 的百分之一——和一个 LUT 做比较。一个 LUT 只有 16 个 gate。为什么 FPGA 里有比 CPU core 多得多的 LUT,这很清楚。
但为什么比如 CUDA cores 也比 CPU cores 多?CPU 和 GPU 的区别是什么?在 CPU 里,面积的一大用途是 cache。真正用于 ALU 的非常少。大部分是这些 register files,而不是 logic units。两者在 GPU 中都有对应物,所以这不是主要区别。
但有一个东西在 GPU 里没有对应物,那就是分支预测器。CPU 里有一大片区域,只是一堆预测器,用来预测下一个分支会什么时候发生,以及分支目标在哪里。去掉其中很多东西,再把这些 register files 做得更紧凑,就推动了 GPU 相对 CPU 的很多收益。
Dwarkesh Patel
分支预测器的目的是什么?同时执行两个分支,还是它做什么?
Reiner Pope
问题在于,当我有一系列指令时,如果我有一个分支,实际处理一条指令的步骤会花很长时间。可能要五纳秒。
发现我有一个分支、判断这个 Boolean 是否为真、把程序计数器更新到新目标、然后从指令内存读取,这整个过程可能需要五纳秒才能完成。因此,现实中它可能会在这里才完成。我想运行的时钟速度要比五纳秒允许的快得多。五纳秒是 200 MHz 的时钟速度。我想运行在 1 或 2 GHz。
所以我需要在分支被求值的时候运行其他指令。我只是想继续运行后面那些指令。但这可能是错的。如果这个分支最终被采用,那么我就需要知道,我不该求值这些指令,而应该跳转到目标位置,并运行那里的指令。分支预测器的目的,就是在你甚至到达那条指令之前,提前五个周期预测一个分支将会发生。
01:12:00 – 大脑与芯片
Dwarkesh Patel
如果我思考大脑如何工作,和你这里描述的东西相比,宏观层面的差异可能包括:虽然你可以在这些加速器中做结构化稀疏,并节省一些原本必须分配给 gate 的面积,但在大脑中是非结构化稀疏。任何神经元都可以连接到任何其他神经元,而且不是列对齐的那种方式。
然后还有一个事实,就是内存和计算共址。虽然我猜你也可以说,从某种意义上看,这些 die 上的内存和计算也是共址的。
Reiner Pope
这在某种意义上正是内存和计算的共址。
Dwarkesh Patel
所以也许那不是一个重大区别。另一个重大区别是,大脑的时钟周期比计算机慢得多。部分原因是为了节能,因为时钟周期越快,电压就需要越大,这样信号才能稳定下来,并识别出一个晶体管处于什么状态。
Reiner Pope
没错。
Dwarkesh Patel
我不知道你对大脑可能在做什么,以及这些芯片如何工作,有没有什么评论。
Reiner Pope
我们先看时钟速度这一点。芯片上的时钟速度相当高,因为这会带来更高吞吐量。当我们比较一块 GPU 跑某个工作负载时,它在跑 batch size 1000。而大脑不是在跑 batch size 1000,只有一个我。
你可以想象说,“拿一块 GPU,不要让它以 GHz 运行,让它以 MHz 运行”,那就开始看起来有点像你在大脑里谈的等价事物。但在硅的工作方式里,那并不会给你 1000 倍的能效优势。
它最终看起来是,你只是运行这个电路一次,让它稳定下来,然后它会长时间闲置。闲置时它不消耗太多能量,因为大多数能量是在比特从 0 翻转到 1 再翻转回来时消耗的。
我们聊聊这种电路的能耗。存储一个比特的思考方式是,你在芯片某处隐含的电容器里沉积了一些电荷。当这个比特变成 1 时,它被充电;然后当它下一次变成 0 时,它被放电。
给电容器充电然后把电荷倾倒到地的这个周期,就是能量消耗发生的地方。这叫做 dynamic power 或 switching power,它是芯片能耗的大部分。还有一些能耗来自绝缘体并不完美这一事实,但我们先忽略它。大多数能量消耗来自从 0 到 1 再回到 0 的翻转。
如果你让一颗芯片运行得慢得多,每 1000 个时钟周期才 clock 一次,那么你就会有少 1000 倍的转换。能耗也会大约少 1000 倍。但这在能量效率上并不是一个实质优势。
01:15:33 – GPU 其实就是一堆小型 TPU
Dwarkesh Patel
好,你从高层描述了 TPU 是如何工作的。那么从高层看,GPU 和 TPU 的工作方式有什么区别?
Reiner Pope
有一个高层组织原则不同,然后核心内部也不同。看高层结构,我们拿 GPU 和 TPU 来看看顶层 block 结构是什么样。
如果你把这两者都想成整颗芯片,那么 GPU 的组织方式主要是一堆几乎相同的单元,也就是 SMs。中间有一个 L2 memory,底部还有一堆这样的 SM。所以它是一个相当规则的核心网格。
如果我们比较 TPU,你最终会得到粒度粗得多的逻辑单元。你最终只有几个 matrix units,也就是大的脉动阵列。中间有一些 vector unit,然后底部是 matrix units。这些 matrix units 加上中间的 vector unit 构成整个 TPU 芯片。
你可以把这个东西缩小成一个非常小的单元,带有更小的 matrix unit 和更小的 vector unit,那差不多就是 SM 的样子。从非常高层的角度看,GPU 有很多个小型 TPU 平铺在整颗芯片上。
Dwarkesh Patel
哦,有意思。你是在说 streaming SM 里的 tensor core 类似于一个 MXU?
Reiner Pope
对,它们都非常相似。
Dwarkesh Patel
我明白了。如果结构更少,那么有一堆小型 TPU 就很有意义。而如果只是巨大的矩阵乘法,你可能会想避免每个 SM 各自有 registers 和 warp schedulers 的成本。为什么不直接做一个巨大的东西,把这些成本摊到整个东西上?
Reiner Pope
这体现在你能把东西做多大。我们已经看到这个主题,尤其是在脉动阵列上,更大的脉动阵列可以更好地摊薄 register file 的成本。
这种设计允许你拥有更大的脉动阵列,而 GPU 设计则限制你必须拥有各种小型单元。不过这里有一个权衡。因为这种粗粒度分离,你需要把大量数据从 vector unit 移动到 matrix units,只能通过这里两条边界线。
如果你看 GPU 中等价的东西,你到处都有 vector units,而且你可以通过很多不同线路移动数据。在 GPU 中,vector unit 和 matrix unit 之间可以移动的数据量实际上比 TPU 中高得多。你不是必须把所有数据只通过两条线移动,而是在 GPU 中通过 16 条线移动。
Dwarkesh Patel
对。但你可能也只需要跨越更小的面积。
Reiner Pope
这也会节能。所以如果你可以完全在一个 SM 内部运行,数据移动会小得多。但一旦你想跨 SM 运行,事情就会变得更复杂、更昂贵。
Dwarkesh Patel
所以你不必评论,但人们可能会期待 MatX 会尝试做一件事:获得类似 GPU 的更小脉动阵列结构,周围环绕着 SRAM,但同时把为了支持 CUDA 架构而需要在 SM 里放的那些东西——它们占用大量空间——丢掉。
Reiner Pope
我们公开谈过一个叫做可拆分脉动阵列的东西,从某种意义上说,你可以把它理解为既可以作为大脉动阵列,也可以作为小脉动阵列的大脉动阵列。
Dwarkesh Patel
酷。好,我觉得这是一个很好的收尾点。Reiner,非常感谢。
Reiner Pope
谢谢,Dwarkesh。