# #549. AI 芯片究竟如何工作?GPU/TPU 的底层设计
逐段整理
一凯:欢迎收听跨国串门计划。这是一档专注于让中文听众无障碍欣赏全球优质外语播客的节目。 通过先进的AI生文克隆技术,我们不仅将内容翻译成中文,还完美保留了原主持人和嘉宾的独特声音,为您呈现全球顶尖的AI财经健康与科技领域精品内容。 我是主播一凯 一位热衷于AI领域的产品经理 很荣幸能为您搭建这座跨越语言障碍的桥梁 接下来让我为您简单介绍本期我们克隆的这档节目 并分享几句非常精彩的原话 本期我们克隆的是Dorcas Podcast的一期硬核技术对谈 主持人Dorcas Patel继续请来Matex的CEO Rainer Pope 深入聊AI芯片到底怎样 从逻辑门成家单元Systolic Array 一层层搭成真正的算力机器 Ryner既是芯片创业者 也长期关注AI计算基础设施 Dorch则以刨根问底的方式 把很多底层硬件问题问到最细 节目里有几句原话 很值得先听一听 AI芯片最想计算的主要功能是矩阵乘法 光是把数据从register file移到logic unit 这部分工作就比logic unit本身贵很多很多倍 这个问题在整个技术战从上到下都会出现 你可以把芯片的吞吐量理解成两个东西的乘积 每个clock cycle能做多少事 再乘以每秒有多少个clock [00:00:00]
一凯:这些话背后是一整套关于计算通信和芯片取舍的底层逻辑 那我们就一起来听听这期完整对话 我又请来了Rainer Pope 他是MediaX的CEO MediaX是一家新的AI芯片公司 上次我们聊的是 数据中心内部会发生什么 现在我想弄明白 AI芯片内部会发生什么 芯片到底是怎么工作的 [00:01:31]
Reiner Pope:顺便先说明一下 我是MediaX的天使投资人 所以希望你们设计的是一颗好芯片 我会先从芯片设计里 最小最基础的单元讲起 然后我们再一层一层往上搭 看看一颗真正量产的芯片 整体上有哪些部分组成 在芯片最底层 我们使用的基本单元是逻辑门 它们是非常简单的东西 比如AND OR NOT 这些逻辑门之间用导线连接 导线在芯片上必须以金属走线的形式 真实地铺出来 AI芯片最想计算的主要功能是矩阵乘法 而在矩阵乘法里面最基础的原语 其实是对一对数字做peltify accumulate 也就是先乘再累加 所以我们会先手算一下这个计算长什么样 然后再推断对应的电路应该是什么样 为了讲起来最简单 我会用一个4bit数乘以另一个4bit数来演示 更准确地说 最清楚的原语其实是peltify accumulate
Reiner Pope:也就是先把这两个项相乘得到它们的成绩 然后再加上一个8bit数 我能先问一个澄清问题吗 为什么这会是计算机内部 各种计算里很自然的原语 这里有几个原因 它会稍微更高效一些 但它对AI芯片来说很自然 原因在于 如果你看矩阵乘法里发生了什么 矩阵乘法简单说就是 有一个over i over j over k的 for loop output ik 加等于input ij 乘以另一个input jk 所以在矩阵乘法的每一步 都会发生pelt ply accumulate 另外还有一个观察是 累加步骤里的精度 几乎总是会比乘法步骤里的精度更高 这可能是AI芯片特有的情况 你乘的是低精度数字 但累加的时候误差会很快积累 所以这里需要更高的精度 这就是为什么我们选择做4-bit乘法 [00:02:40]
Reiner Pope:再做8-bit加法 我确认一下我有没有理解 这里可以从两个角度理解 [00:03:22]
一凯:一个是结果的值会比输入更大 另一个是如果它是伏点数 情况可能是这样 那部分对我来说没那么直观 但也许是同一个原则 它本质上确实是同一个原则
Reiner Pope:不过我想另一个单独的原则是 当你在对这个数求和的时候 你其实是在把一大堆数加起来 所以会有很多 舍入误差不断积累 而在这个例子里 那条链上只有一次乘法 所以乘法里 不会积累很多舍入误差 为什么你是在 把一大堆数加起来 不就是两个数吗 我的意思是 这个求和会发生很多次 会沿着j重复很多次 所以任何误差都会积累 我明白了 那我们手算这个计算会怎么做 作为人类 我们可能会把它分成两步 但也可以用数式乘法 把它放在一起算 先看乘法这一项 我们要把这里这个4bit数 分别乘以另一个4bit数里的每一个bit位置
Reiner Pope:我们把它写出来 首先1001乘以这个bit位置 这个结果就是这个数本身 然后往左移一位 我们现在乘以0 所以得到一个全0的数 再往左多移一位去乘以这个1 我们得到1001 最后对于最后这个bit位置 我们又得到一个全0的数 这样我们就得到了一堆项 接下来要把它们相加 得到乘法的结果 在做这个求和的时候 我们也可以顺便把真正的累加项加进去 所以我们直接把它复制过来 所以这就是要算的总和 它是一个五项相加的和 首先为了得到这个中间步骤 我们到底用了哪些逻辑门 我们需要生成全部16个partial product 那我怎么生成其中一个partial product呢 我们拿这里这个一举例 它现在是1 那这个数是怎么来的 [00:04:16]
Reiner Pope:它是用这个数乘以那边这个1得到的 我们其实可以用一个and gate 来生成它这个数为1 当且仅当这个bit是1 并且那个bit也是1 如果其中任何一个是0 那么0乘以任何东西都是0 所以为了生成所有这些东西 我们最后用了16个and gate 更一般的说 如果我要做一个Pb的数 乘以一个Qb的数 那么这里就会需要 P乘以Q给And gate 最后我把它们加起来 其实大部分工作 都会发生在求和这一步 我先说一下 这里用到的另一种Logic gate 它几乎是芯片上 最简单的Logic gate 差不多也是最小的那种 另一个极端是 通常你会用到的 最大Logic gate
Reiner Pope:叫Full adder 它的作用是做加法 你如果从软件的角度看 可能会以为Full adder 是把两个32bit的数 加在一起 但在这里 它只是把三个单bit的数 加在一起 比如你可以理解成把011加起来 这三个数加起来 结果可能是012或者3 所以我只需要用两个bit 就能用二进制表示这个结果 也就是说它的输入是三个bit 输出是两个bit 比如这里2的二进制就是10 所以它也叫322 Compressor 因为它拿三个bit作为输入 铲除两个bit作为输出 这两个输入是不是一个x一个a的值 然后还有某个从前面传进来的carry 不是三个输入 都是同一个bit位置上的bit 也就是这里同一列里的三个bit两个输出 [00:05:31]
Reiner Pope:我这里一个化成竖着的 一个化成横着的 是为了对应这里的纵向和横向布局 这表示同一列里的东西 在同一个bit位置上 而相邻列里的东西 [00:06:11]
一凯:比如这个是carry out 另一个是sum 所以如果full adder的输入 比如是1101 那输出还是10 如果输入是111 输出就是1 1 输入是000 输出就是00 输入是010
Reiner Pope:输出还是01 对 本质上就是在数有多少个1 然后把这个数量用二进制表示出来 所以这个电路其实可以表达我们人类在按列做加法时自然而然会做的事 我可以演示一下用full adder来求和的一轮操作 我们这里的求和方式对人来说会有点不自然 人通常是按列相加 然后记住carry 但这里我们不去记carry 而是把它明确写出来 所以在这个例子里我们从最右边一列往左走 最右边这一列我们把1和1相加得到这里的0 以及一个carry1 也就是说我们在这一对bit上用了一个full adder电路 然后产出一对bit作为输出 现在我们可以对这一列做同样的事 这一列有1 2 3 4个数 那我们可以先拿其中前三个送进一个full adder 输出得到0和0 也就是说这几个数的和是00 这就是把full adder用在这些bit上 每当我用掉一些bit 我就把它们划掉 表示我已经处理过了
Reiner Pope:我们再继续往下做一点 我们到这里 拿这三个数把它们相加 得到1和0 这三个数就处理完了 然后我再拿1 2 甚至我现在也可以拿这三个数来相加 得到1和0 这些数也处理完了 所以你应该这样看 我有一整张需要相加的数字网格 我会不断地把full adder用在这里的各个bit上 每次从某一列里拿掉三个数 然后写出两个数作为输出 就这样一遍又一遍做下去 直到最后这里大概只剩下一个单独的数出来 大概就是这样 当然这个和可能算错了 我刚才描述的这种方法叫data multiplier 这基本上就是用full adder来做 面积高效multiplier的标准方式 我们试着量化一下这个电路的大小 这样我们就能大概知道这些东西有多大 后面也方便比较 我用了多少个full adder [00:07:23]
Reiner Pope:一开始我有多少个数 我有16个partial product 也就是这些项和那些项 两两相乘得到的结果 再加上这里要加进去的8个项 所以一开始我有24个bit 最后输出会产出8个bit 每一步我都会划掉3个数 然后写出两个数作为结果 所以每用一次full adder 都会消掉这里的一个bit 那需要多少个full adder 一定是24-8 所以这个电路里有16个full adder 一般情况也是这样 这个电路里会有PxQ个full adder [00:08:06]
一凯:我确认一下 我有没有理解这个逻辑 输入bit数是24 也就是P乘Q加P再加Q 对 输出bit数就是P加Q 所以P乘Q加P加Q 再减掉P加Q就等于P乘Q 对
Reiner Pope:所以我觉得这解释了 或者至少暗示了 我们为什么选择做 peltify accumulate的第二个原因 第一个原因是 它确实会出现在矩阵乘法里 第二个原因是 它给了我们一个非常漂亮的 P乘Q代数形式 非常简单 所以我们刚才基本描述了 整个流程 我在这里做的每一个原子步骤 都会变成一个logic gate 然后这些Gate之间用Wire连起来 比如我有这三个输入 用它们算出这两个输出 如果把它映射到真实的物理设备上 就会有一根Wire 把这三个东西接到一个Logic Gate里 然后产生这个输出 这就是AI芯片里面的主要Primitive 只是Bitwidth会不同 接下来我们会从这里往上搭 看怎么用它来跑
Reiner Pope:你可能需要的其他所有操作 [00:09:21]
一凯:现在问这个问题可能实际不对 但NVIDIA每次说某个芯片 能做多少FPP 或者一半数量的FP8 听起来好像这些电路是可以互换的 不是专门分成FPP和FP8 可是按你现在画出来的方式 如果它必须映射到logically 那似乎就需要一个专门的FPP multiply accumulate 再需要一个专门的FPP accumulate 它们能互换吗 按现在画出来的样子
Reiner Pope:它们其实不太能互换 这其实是设计芯片时 必须做的主要选择之一 我要放多少FP4多少FP8 有时候我会从客户需求的角度 来考虑这个问题 另一种角度是说 在FP4和FP8之间 怎么让Power Budget对齐
一凯:所以他们报这些数字的时候 比如刚好是 FP负的数量是FP倍的两倍 这是不是只是因为 他们选择给 所有Float Point单元 差不多的Die Area 所以最后结果就变成这样 [00:10:01]
Reiner Pope:你是问为什么 这个比例正好是2比1 对对没错 其中一部分原因是 它肯定不会真的 刚好等于相同的Die Area 其实还有一个 Data Movement的原因 等我们后面看数据 怎么进出Memory的时候 可能会再回到这一点 从software的角度看 有一件事很方便 我可以把两个4倍的数字 塞进一个8倍的数字 同样大小的storage里 所以当我把它存到 memory之类的地方时 芯片内部那些bus的尺寸
一凯:刚好会让这件事配合得非常好 我现在想起来 它其实不只是两倍 听起来它占用的area 是按平方增长的 对实际上是平方关系 跟bit length是平方关系 所以更低的precision
Reiner Pope:比刚才说的还要有利 这是一个很重要的原因 实际上NVIDIA做过一个变化 历史上在B200或B200之前 每次Bit Precision减半Flop Count就翻倍 这个比例正是因为你刚才说的平方Scaling 严格来说这个比例其实有点不对 你应该得到更大的Speed Up NVIDIA的产品规格从B300以及之后开始 算是承认了这一点 那里FP4比FP8快3倍 不过按理说应该是4倍 对我这里展示的是最简单的Integer Multiply情况 但你处理FP-和FP这种Float Point时 还有另一项也就是Exponent 它会让这个计算变复杂 所以我们现在已经能看出什么 我觉得你刚才提到的关键观察是 bit-width存在这种平方scaling 这非常有效 也是low-precision arithmetic 在neural net里这么好用的唯一原因 但接下来我们还要比较真正 画在multiplication本身上的area 和它周围所有circuitry画掉的area
Reiner Pope:所以我们稍微往回看一点 看看TESAR出现以前的GPU是怎么工作的 事实上它和CPU的工作方式一样 问题就是我们把这个 multiplier accumulate unit放在哪里 泛泛地说 一个corder core或者一个CPU 会有一个register file里面存一些entry 可能是8个entry 在这个例子里 我想这些是4bit数字 但通常会是32bit数字之类的数 所以在Cuda Core里面 我会有一个有一定depth的register file 然后我会有我的multiply and accumulate circuit 也就是multiply and accumulate circuit 它要做的是 从这个register file里 任意取三个register 执行multiply and accumulate 然后再写回register file 比如它可能写到这个register 但它可以从这个 还有另一个任意register里读 所以它会像这样取三个输入 [00:11:42]
Reiner Pope:这就是很多processor的核心 data path 大多数Processor看起来都是这样 你有一组Register 然后有一组Logic Unit或者ALU 我们想分析的是 数据从Register File移到ALU 再移回来的成本 最后一定会有某个Circuit来决定 我不一定总是选这个 我在任何时刻都可能选任意一个Register 所以第一个问题就是 我怎么搭一个Circuit 我们要看的这个Circuit叫Mux 在这个例子里它会有8个输入 Register File里的每一项各有一个输入 它会有一个输出 也就是产生这个输出 那这个东西的成本是多少 我们能用来搭它的 基本只有AND和OR 那怎么搭呢 我们用最笨的办法 先做一个mask 比如我们想读第三项 [00:12:23]
Reiner Pope:就根据是不是我们要读的那一项 把每一项都和1或者0做AND 然后再把它们全部OR到一起 我确认一下基础概念 Mux做的事情就是在选择 对吧 就是在选择 就是选择一个输入 对软件来说这是不可见的 你只是说我要第三号输入 这就意味着这里有一个Mux 那这个Mux的成本是多少 一个有n个输入处理PB的Mux 我会有n行 也就是这里的8行 每一行的宽度是Pbit 我必须对每一个bit做ND 所以会有N乘以P个ND gate 对每一个输入我都要判断 是不是要把它mask掉 然后我要把它们全部OR到一起 所以会有N-1再乘以P个OR gate 意思是我有这些不同的东西 其中几乎全都是0 但我需要把它们折叠下来
Reiner Pope:从8个选项变成一个选项 所以每一步我都要把一行OR 进一有的一行里 明白了 这其实挺有意思 [00:13:40]
一凯:你平时不会从硬件这个层面去想 只会想 我就选第三个元素 但这么简单的一件事 本身其实就是一个相当复杂的东西 一个circuit 这就是那些隐藏的数据移动成本的第一步
Reiner Pope:我们接下来只是比较一下 这个成本我必须付 而且这里有一个mux 事实上对于multiplier accumulator操作的三个输入 我还要再有两个这样的副本 所以这里的成本大概是3乘以n乘以p个and gate 而真正做我关心的那件事的circuit 大概是p乘以q个gate 如果代入实际数字 比如n等于8 那光数据移动这里就是24乘以p个gate 相比之下如果q等于4 那么在Faltify Adder里 只有4乘以PKG 抱歉 这里的3是从哪里来的 这里有三个不同的输入 我这里真正想提示的是 所有这些工作 都是随着Registered File的大小扩展的 而且这还是一个非常小的Registered File 光是把数据从Registered File 移到Logic Unit这部分工作 就比Logic Unit本身贵很多很多倍 也许看一下Mux长什么样 [00:14:00]
Reiner Pope:会有帮助 比如一个二维的 或者四维的Mux 好我们取一些输入 比如我们就做一个二维Mux 我们有两个不同的数字 有这两个输入 然后我们有一个selector 这个选择器的意思是 它可以表示我要这个 或者我要另一个 所以这是one hot encoding 我们一开始拿到的就是这些 然后我们想产生的输出 先看这个例子 也就是说 这是我们实际拿到的输入 我们只想把这个东西 作为结果输出出来 如果很机械的说 我们会把这个bit 和这一整行都做and 这样就相当于 把这个bit和这一行做and 同样地
Reiner Pope:我们把这个bit和这一行做and 结果就全是0 所以这里有4个and 这里也有4个and 最后我们把这两项作OR得到1 再把这两项作OR也得到1 把这两项作OR得到0 再把这两项作OR得到1 所以这里是4个OR 这最后看起来其实有点像加法 事实上我们这里做的AND 和前面那套AND是完全一样的 某种意义上 我们把这些东西都加在一起了 但最后收缩结果时 不是用那些full adder电路 而是用orgates 做了一个非常简单的收缩 但我有点不明白 这看起来不像是n乘以p 这里的例子是n等于2 也就是有两个输入 一般情况下我们会有n行 然后每一行有Pigabit 所以这就给了我们N乘以P个AND gates [00:15:15]
Reiner Pope:我刚才描述的这个电路 几乎所有成本大概八分之七 都是花在读写register file上 只有很小一部分成本在logic unit本身 所以这就是要解决的问题 这基本上就是NVIDIA GPU 在Volta这一代之前的状态 Cuda cores里面大概就是这种东西 这个问题也推动了TESAR cores的引入 更通用的叫法是systolic arrays 如果我们想要怎么解决这个问题 我们几乎把所有电路面积都花在了一个 其实不关心的东西上 而且这个东西对软件程序员来说还是隐藏的 我们真正关心的部分反而不是面积的大头 那目标就是以某种方式把真正关心的这部分做大 同时让读写这部分保持同样大小 演进过程大概是这样 在这个阶段我们已经把这么多东西固化进硬件了 这一行表示一次beltply accumulate 也就是成家操作 这个单独的东西已经被固化进硬件 systolic array的想法是往上走两层loop 把外面这一整个loop固化进硬件 也就是说如果我们有一个力度大得多的fixed function逻辑块
Reiner Pope:也许我们在输入和输出上付出的代价 就会小很多 [00:16:48]
一凯:听起来你的意思是 如果在Matrix Multiply的loop里 往上走一步 就能把权重更多的倾向compute 而不是communication 对大概是这样 这里有两个效果我们会利用 第一个是每次访问registered file之前
Reiner Pope:我们可以做更多事情 另一个是在这个loop的某些部分里 我们可以利用一些东西会保持不变 这一点我们用图来看这个Matrix Multiplication loop的这一部分 其实对应的是一次Matrix Vector Multiplication 我们会拿一个matrix乘以一个vector 那具体怎么做 每一列都会和这个vector相乘 然后求和 也就是说我们会沿着列的方向求和 所以0和3会分别乘以3和7 然后加起来 1和2也会分别乘以3和7 然后加起来 因此对于matrix里的每一个entry 都会有一个对应的multiply accumulate 我们就把这四个multiply accumulate 画出来 我确认一下 我有没有理解 为什么这里有四个 multiply accumulate [00:17:05]
一凯:如果输出vector里的每一个entry 对应的是一列的Dot Product 那在这个例子里就是两次乘法 然后把这两次乘法的结果相加 所以你是在做累加 对加法本身
Reiner Pope:其实每个Dot Product只有一次 但我们通常会从0开始 也就是用0做初始化 对 我们的目标是让compute 变成平方级更多 也就是说我们现在有 x乘以y倍的compute 比之前多这么多 但我们希望communication 只增加到x倍 思路就是这样 我们想让优势项按y来增长 我们已经把乘法单元铺好了 要输入一个大小为Y的向量 所以这一部分已经符合我们的通信目标 没问题 但我们还得想办法处理这个矩阵的通信量 这个矩阵会超过我们X的预算 所以在AI的场景里 关键是这个矩阵其实会在很长一段时间里保持不变 所以我们不想每次都从外面把它搬进来 比如这边有一个register file 我们不希望从这个register file里读出来的数据量太大 这个量在某种意义上是我们希望按X增长的那一项
Reiner Pope:我们不想每个cycle都从register file里把整个矩阵搬进来 因为资源不够 那会让从register file拉线的成本太高 所以我们换一种做法 关键技巧是这个矩阵可以本地存放在systolic array旁边 比如把0 1 2 3这些数存到一种叫registered gate里 它会在物理上保存这些数 然后我们会一遍又一遍的附用这些数 用在大量不同的向量上 [00:18:39]
一凯:所以这里的优化点是 矩阵乘法本身有这个性质 你可以把这个平方级的东西 直接存到逻辑运算发生的地方 它的维度比那些不断换进换出的输入更高 或者说多了一维 没错 矩阵乘法的本质就是 你要做很多次乘法 最后得到一个值 比如dot product 就是很多次乘法的结果 所以这个优化意味着 在得到某个输出值之前
Reiner Pope:你可以在里面塞进大量乘法 对 为了把这个图讲完整 具体看起来是这样 我这里把3和2换了一下 3和2这个0和3 会分别跟3和7相乘 我们会沿着列来形成一个dot product 也就是说我们会以某种方式 把3和7送进来 它们会参与运算 这个数会送进这个乘法 也会送进另一个乘法 同样 3也会送到这里 也会送到那里 然后我们会沿着这里求和 比如从一列的顶部 开始我们先位入0 最后从底部出来的就是结果 所以从视觉上看 这里发生的是 沿着矩阵的列做.product 而这件事正好映射到 这个systolic array里的空间结构上 [00:19:25]
Reiner Pope:这是一个.product 沿数值方向求和 这里是第二个.product 也是在数值方向求和 那么需要进出register file的数据是什么 输出这边有x量级的数据出来 输入这边也有来自 输入向量的x量级数据进来 所以至少对于输入向量和输出向量来说 我们达成了目标 进出register file的数据量只有X量级 这就留下了另一个问题 刚才说过 weight matrix是本地存放在systolic array里的 那它一开始是怎么进去的 某个时刻你总要启动芯片 把这些数据填进去 那这些数据从哪里来 技巧就是我们非常慢地把它送进去 非常慢地一点一点地灌进systolic array 最简单的策略是做一条daisy chain 先把一个数送到这里 下一个clock cycle 它就移动到systolic array的下一个位置 每一列都可以并行这样做
Reiner Pope:这样就会得到一种结构 这一部分也会从这里进来 也会带来大约X个单位的bandwidth 你能不能再把刚才那句话重复一遍 我们知道矩阵里的这些数 不会经常被送进来 只是偶尔更新 所以我们只需要想出任意一种结构 让真正跨过Systolic Array边界的布线数量 也就是这里这条边界上的线 保持在X的量级 我们不希望它变成X乘Y的量级 一个特别简单的策略是 把一个数送进Systolic Array的顶型 这是一派Clock Cycle里能做的事 然后接下来连续个clock cycle 我们每次都把顶型送进去 同时把其他所有型都往下移一格 这样一来需要从昂贵的register file 接出来的布线 就只需要x这个量级 而不是x乘y 我明白了 [00:20:42]
一凯:通信这件事里有两个问题 一个是通信时间 一个是通信带宽 你是说因为我们只会把这个值 加载进来一次 所以要尽量降低带宽 因为带宽就等于芯片面积 所以我们就用更窄的通道 慢慢把它加载进来 因为这个值会在里面保留一段时间 这很有意思 上次我们聊很多芯片之间做inference的时候 最高层要优化的事情是提高每单位内存带宽 也就是每单位通信对应的计算量 而这里我们也在提高真正的乘法加法 相对于从register到logic传输信息的比例 所以两种情况下都是在让计算相对于通信尽可能多 [00:21:22]
Reiner Pope:这个问题在整个技术站从上到下都会出现 这里已经很接近底层了 接近gate这一层 还有一个版本可能更接近gate 就是你选择使用的数字格式精度 我们也看到了同样的效果 这里有点像平方和立方的关系 或者说平方向和线性向的关系 它既出现在这个ALU的精度里 也出现在这个矩阵的大小里 很有意思 所以这个单元是我们刚才那个乘法电路之上的 下一个更大的单元 在它上面我们有一个相当大的Systolic Array 我刚才画成了2x2 但比如在一些更老的TPU里 它们被描述成128x128 也就是由这里这种电路组成 这个电路最后会变成 [00:22:02]
一凯:目前已知最有效的实现矩阵乘法的电路机制 我明白了 我们刚才聊到 尽量让计算相对于通信更多 这似乎是显而易见的 那有哪些不那么显然的取舍 会真的让你晚上睡不着
Reiner Pope:比如我们该做X还是该做Y 但答案并不明显 我觉得芯片设计里的大多数决策 都是尺寸怎么定的问题 就拿我们目前画出来的东西来说 AI芯片都有这种电路 它们有一个Systolic Array 然后在它附近有一个Register File 负责提供输入和输出 即使只看这个范围 你也会遇到两个尺寸问题 我的Systolic Array应该做多大 我的Register File应该做多大 而且这两个问题其实是偶合在一起的 一种理解方式是 我先给数据移动分配一个芯片面积预算 也就是说我想把芯片面积的百分之多少 花在数据移动上 比如我可以说我希望数据移动占10% Systolic Array占90% 然后我就可以决定Register File的大小 更大的Register File更灵活 能让我跑更多东西 也能带来更多应用层面的性能 但它也会占掉
Reiner Pope:本来可以给Systolic Array的面积 这说得通 芯片的clock cycle是从哪里来的 它有什么决定 还有芯片的clock cycle到底是什么 我觉得先从最基本的点说起 芯片本质上是极其并行的 一个芯片里可能有1000亿个晶体管 只要有这种大规模并行 你就必须在不同的并行单元之间做同步 在软件里通常会用一些代价很高的同步方法 比如Mutis 一个线程做完自己的事之后 会去拿一个存在内存里的锁 然后通知另一个线程它已经完成了 但在芯片里我们采用的是很不一样的办法 大概每隔一纳秒 芯片里的所有电路都会暂停一瞬间 然后同步一次 也就是说它差不多每一纳秒就同步一次 这就是clock cycle 通常整个芯片会在同一瞬间 以锁步的方式进入下一步操作 从电路上看它通常会被画成这样 可clock是通过register来协调的 [00:23:36]
Reiner Pope:register就是我们前面画过的那种存储器件 你可以这样理解 我有一块存储里面存着一个bit 可能是0也可能是1 然后我有一团逻辑电路 可能是Systolic Array 也可能是Multiplier 或者别的东西 这团逻辑会产生某个输出 也就是说我有一堆输入进入这团逻辑 过一段时间之后 会有一个输出Register 逻辑的结果会写到这个Register里 有一个全局Clock Signal 会驱动所有这些Register 它的意思是在某一个具体时刻 当Clock到来的那一瞬间 这根线上当时是什么值 就把什么值存进去 所以这里的挑战是 我希望Clock Speed尽可能快 因为如果我能跑到2GHz 那每秒能做的操作数 就是1GHz的两倍 但这也意味着 [00:24:25]
Reiner Pope:我会非常受这团逻辑延迟的影响 因为这里面要发生的任何计算 都必须在下一个clock cycle 到来之前完成 所以对任何芯片来说 一个很重要的优化点 就是尽量把从这里经过逻辑 到那里的延迟压到最短 有意思 [00:25:03]
一凯:这里的约束看起来是 如果你加了太多逻辑 就可能赶不上clock cycle 没错 但如果你加的不够 又等于把潜在算力浪费掉了 有没有这种情况 你会接受一种概率性的风险
Reiner Pope:读某个计算能完成 还是说不行 要么它能在我的clock cycle里完成 要么不能 在标准芯片设计里 你会流出margin 严格来说是有概率的 但那个概率会被推到 很多很多个标准差之外 非常远 所以从实际使用角度看 它就是一个可靠部件 它总是能赶上clock 当然也有一些奇怪的例外 比如clock domain crossing 也就是从一个clock域 跨到另一个clock域 这时候你确实需要考虑 这种概率问题 但在主路径上 你就是流出足够的margin 比如让它提前一个clock cycle的 25%左右到达 这样出问题的可能性就非常低 那在这个clock同步的位置
Reiner Pope:也就是register所在的位置 这不是你作为芯片设计者 [00:26:05]
一凯:完全手动决定的对吧 它更像是一个结果 我想要某一串逻辑 然后你用的软件 把RTL转成要交给TSMC的东西 这个软件会判断 为了让它工作 你得在这里 这里放register
Reiner Pope:这样才能保证没有哪一步太长 导致整个芯片的clock cycle 被迫拉得比必要的更长 其实插入这些register是芯片设计里非常大的一部分工作 它是手动和自动结合完成的 我用一个很简单很笨的版本来说明你能做什么 你可以把这团逻辑切成两半 也就是说不是只有一大团逻辑 而是变成两小团逻辑 它们做的是同一件事 但中间用一个register隔开输入 像这样接进去 如果你正好在中间把它切开 就可以达到两倍的clock frequency 这很好 你得到了两倍性能代价是多了 这个额外的register也就是多了一些存储 那我们退一步问 为什么需要同步整个芯片
一凯:比如你想象在玩Factorial之类的游戏里面 没有全局 clock cycle 事情做完 就是做完了传送带上有铁 你想拿就可以拿 用你刚才那个类比来说 需要注意的是 如果有两条不同的路径 穿过一些逻辑 [00:27:00]
Reiner Pope:比如这里要做一个计算F 然后这里做计算G 最后他们会在这里某个地方汇合 做计算H 这里会有制造差异 有些芯片上F可能会慢一点 另一些芯片上G可能会慢一点 所以如果有一个信号 沿着这里传播F和G的结果 又必须在H这里对齐 那可能出问题的地方 就是F先到了 结果它遇到的是 G的上一个值 或者G的下一个值 之类的情况 也就是说 H需要知道自己 到底什么时候开始 要知道 下一轮迭代 什么时候
一凯:真正准备好了 这也解释了 为什么同一个process node 同一种TSMC技术 做出来的不同芯片 clock cycle可能不一样 比如两个三纳米芯片 clock cycle可能不同 取决于它们
Reiner Pope:有没有优化好 确保不存在 某一条critical path太长 拖慢整颗芯片的clock cycle 没错 我刚才展示的这个优化 叫做pipeline register insertion 也就是插入pipeline register 我们是在pipeline中间插入了一个register 它本质上是在clock speed和面积之间做取舍 这是简单的情况 还有更难的情况 这里我把它画成了一条逻辑pipeline 但在其他情况下 可能有某个计算会反馈回自己 它运行某个函数f 然后像这样写回到自己 比如这可能是一个加法 你有一个数 每个clock cycle都往里面加一些东西 所以这个地方可以是一个加号 每个clock cycle都加进来一个数 这个小电路 本质上就是把不同clock cycle上 输入的所有数字都加起来
Reiner Pope:挑战在于 如果这个加号耗时太长 我能怎么办 如果我试着把它拆开 在中间插入一个pipeline register 比如插在这里 这会改变实际执行的计算 它就不再是对这里来的所有数 做running sum 而是会变成两个不同的running sum 最后会得到一个偶数项的running sum 和一个奇数项的running sum 所以一旦逻辑里有这种loop 而所有芯片在某些地方都会有 这其实就是最难处理的问题 也会决定clock cycle 我不太明白 为什么这会是个问题 或者说 [00:28:34]
一凯:我甚至不太确定 在那里放一个register 到底是什么意思 它是不是有点像 atomic operation Java其实并不是atomic 我觉得 就像你刚才言迟的那样 对做一次求和 [00:29:01]
Reiner Pope:其实需要很多步骤 所以你可以先做 前半部分工作 然后在中间塞一个register 再做后半部分工作 明白了 我猜接下来就取决于TSMC TSMC会提供一个PDK 规定好
一凯:这些是我们能在芯片里提供给你的逻辑primitive 然后由他们来确定 没有任何一个primitive会大到超过 他们希望这个process node达到的clock cycle 但除此之外还能做什么进一步优化呢 难道不能直接说这里是TSMC给的所有primitive 然后只要有需要 就在这些primitive之间不断加register 直到达到你想要的clock cycle吗 作为logic designer clock cycle是由芯片架构师设定的
Reiner Pope:举个例子 TSMC给你的Primitive 大概是Andigate或FullAdder这种级别 这很大程度上取决于 电压频率你选择的Library等等 但一般来说在一个Clock Cycle里 通常可以顺序放大约10个 20个或者30个这样的Primitive 所以这些Primitive非常快 可能就是10匹秒之类的量级 作为Logic Designer 原则上如果你真的只有一个Register 然后一个Andigate 再这样形成一个Loop 你可以得到极快的Clock Speed 比如超过4,5,6GHz之类 但如果你看这个非常简单的电路 再看这里花掉的面积 这个大概是一个gate equivalent 也就是面积单位事宜 而这个东西可能是面积单位8之类 所以这里几乎所有成本 又都花在了同步或者通信成本上 而不是花在真正的逻辑上 这就是一种走过头的情况
Reiner Pope:你把clock speed做得非常非常快 但代价是几乎所有面积 都花在了pipeline register上 有意思 你的意思是这里有一种动态关系 你可以有非常快的clock speed [00:30:35]
一凯:但每个周期实际完成的工作并不多 所以你可以有delatency 但bandwidth或者更准确的说 throughput会比较低 事实上这会伤害吞吐量 你可以把芯片的吞吐量理解成两个东西的成绩
Reiner Pope:每个clock cycle能做多少事 也就是前面说的面积效率 再乘以每秒有多少个clock 这其实很像我们上次聊batch size batch size d的时候 单个用户可以很快拿到下一个token
一凯:但比如一小时内处理的token总数 就会比本来能达到的水平低 对没错 如果你把clock speed拉高 实际能用到的并行性就会更少 我记得之前和Jane Street的一位FPGA工程师聊过 他还帮我准备过我们上一次访谈 他当时解释了他们为什么用FPGA 我想对于高频交易来说 吞吐量没有延迟那么重要 所以最关键的是 能以确定性的方式 非常精细地控制clock cycle 也许可以聊聊 为什么不能直接在ASC里做到这一点 或者说 为什么在高频交易这种场景里 你会用FPGA来获得确定性的clock cycle 先看FPGA和EAC的商业取舍 [00:31:11]
Reiner Pope:FPGA和EAC大体上使用同一种概念模型 用AND或XOR这些很小的primitive组成一串gate 再用固定的clock cycle 把它们和wire连接起来运行 所以任何你能在FPGA里表达的东西 也都能在ACC里表达 而且ACC大概会便宜一个数量级 能效也会比FPGA更好 代价是第一块FPGA可能只要你1万美元 但你做出来的第一块ACC可能要花3000万美元 因为它需要完整tape out 所以FPGA的商业使用场景是 我想要非常确定的延迟 很快的运行时间和高并行度 但我会非常频繁的改它 比如每个月都改一次要做的事情 这样我就不想每次都付一次Tape Out的成本 那FPGA到底是怎么实现的 它有点像是在一块固定硬件上 模拟ASA的编程模型 那这件事具体怎么做呢 它最底层有我们刚才讲过的两个组件 一个是Register作为存储设备 另一个叫UT也就是Lookup Table 它们实际提供所有的Gate
Reiner Pope:然后我们还会看到第三个组件 它有一大群Register和UT 所有这些东西都可以用 然后它们通过一大组Mux连接起来 在每一个组件前面都有类似这样的Mux 它会从其他所有地方里选一个输入 也就是从这些不同的东西里做选择 有很多不同的选项会输入到这些组件里 所以这允许我在编程FPGA的时候说 我要拿这些组件在上面叠加出一套特定的Wiring 比如信号先经过这个LUT再输入到那个LUT 然后到这个Register再输入到另一个LUT 类似这样 我用橙色画出来的就是FPGA里的Field Programming Gate Array里 被现场编程出来的部分 橙色代表在field里被编成的东西 白色则是FPGA里必须本来就存在的所有wire 只有这样这个device才能先被制造出来 programmed in the field是什么意思 就是说device已经部署在数据中心里了 它就在实际使用现场 然后你可以过去给它编成 这里的field是electric field 那种field吗 不是 [00:32:39]
一凯:是部署到外面真实环境里的field 明白了 那我看这里field programming是从第一个lookup table 出来进到第二个lookup table 它是怎么做到的 你是问让这件事发生的那些线在哪里 对吧 [00:33:33]
Reiner Pope:我这里画的有点偷懒 这里每一个device前面 其实都有一个Mux Mux可以从附近所有可用的电路里选择输入 所以FPGA的实际configuration 本质上就是Mux control 比如这个Mux里有data input 然后有control来选择用哪一路 所以每一个这样的Mux旁边 都会有一个小的存储单元 它会说你的输入要从这里来 明白了 所以所谓programming 就是配置这些Mux里的每一个 这样就说得通了 那lookup table里面发生了什么 lookup table的目的是 它也会有一点control输入 告诉它该做什么 lookup table的作用是 可以被配置成一个end gate or gate xor gate 或者其他不同的gate 你可以有很多种方式来做这件事
Reiner Pope:传统FPGA里的做法是 一个lookup table会支持 4个bit的输入 一个bit的输出 从4个bit到一个bit 一共有多少种不同的函数 有16种不同的输入组合 所以你其实可以 直接把它列成一张表 里面有16个值 比如01 11 001 一直到16个entry 他做的事情就是 这张表存储在这些蓝色的configuration bit里 然后他把这四个bit当成二进制数 去查表里对应的那一行再输出那一个bit 所以本质上这就是从truth table的角度来看lookup table 明白了 所以lookup table 如果你想一个and gate or gate xor gate 它们都是接收输入的函数 那些都是两个输入的函数 有时候我们会有更复杂的 比如三个输入的函数可以是three way xor 对吧 [00:34:23]
Reiner Pope:或者four way xor 在这个例子里有多少个输入 就取决于它有多大 对吧 Liotti的典型大小是四个输入 这是一个比较合适的折中点 这里也有计算和通信之间的权衡 如果输入太少 你就需要用更多Liotti [00:35:04]
一凯:如果输入太多也会有问题 但基本上lookup table 就像一张truth table 有了truth table 你就可以把任何 你想要的gate编进去 对 所以lookup table可以理解成一个 可编程的gate
Reiner Pope:对这里有一件事可以看出来 为什么会有一个经验法则说 FPGA大概比AC贵一个数量级 你可以数一下 lookup table里面会有多少个Gate 我们可以把这个lookup table 本质上看成一个这样的Mux 它是一个Mux 要从16个不同的值里面选一个 所以它是一个Mux N等于16个选项 P等于一个Bit 我们前面很早的时候看到过 这个电路的成本 大概是N乘以P个Gate 所以这里的成本 大概就是N乘以P 也就是16个Andy Gate 另外还有16个OR 你说的这个电路是指Mux 对没错 Mux是核心 是进入lookup table的那个Mux lookup table本身 你可以把它想成一个大的mux
Reiner Pope:它从16行里选出一行 最后得到一个输出 明白了 这就是lookup table 但你这里画的方式是 先有一个mux 然后有一个lookup table 它是 一层一层都是mux 我的意思是 这里面还有第二个mux mux就是这里这个mux 明白 [00:36:08]
一凯:然后另一个mux是在说明 它是从这一堆乱七八糟的门里 哪来的 是从这些gates来的 对 然后第二个mux的意思是 现在你有了一个值 但这个值还是一个4bit的值 对
Reiner Pope:我是从这一锅东西里选出了4个bit 然后我用这4个bit去选择 lookup table里的哪一个entry要被使用 对 好我只是想确认一下 假设第一个mux里有8个附近的输入
一凯:你是从8个附近的register里去输入 那总共就是32个bit进来 然后从里面出来4个bit 这4个bit进入第二个mux 也就是lookup table里面的那个mux 其实我会说在这个例子里
Reiner Pope:这些register是单bitregister 所以如果附近有8个register和lookup table 那附近总共进来的就是8个bit 我从8个值里选到4个不同的值 所以实际上这里有4个不同的max 每个输入bit对应一个小max 每个小max都是在8个里面选一个 那这8个是从哪来的 来自附近的register 还有其他一些lookup table 而且每个register都是一个bit 对 [00:37:02]
一凯:所以我猜AMD或者制造这些FPGA的公司 还是得对哪些register 应该连到哪些register有自己的取舍 你可以编程决定实际的Gates 但他们得先把Wire和连接方式
Reiner Pope:也就是通信拓布定下来 对吧 对你在局部力度上会有灵活性 也就是在附近的一小片范围里 可以选择 但更大范围更粗力度 更长距离的连接 他们就得先做设计取舍 对 那它慢十倍的原因是什么 如果你看构建这个Lookup Table的成本 大概是32个Gates 然后它能给我一个等价的东西 这里有什么有意思的例子呢 我可以做一个四输入Any Gate 也就是说我用32个Gates 组成的Lookup Table 去实现一个四输入AN 四输入AND是什么 就是AN 再对AND的结果做AN 所以这是一个电路 在AAC里 我可以直接用 这三个AN gates来实现
Reiner Pope:但用LUT也能实现 只是它要用大概32个gates 而不是三个 所以这个开销 [00:38:12]
一凯:真正来自lookup table和mux 也就是说对于一个truth table 其实有比 列出所有可能输入组合 更简洁的描述方式 那就是直接写出这个 gate 对也就是直接把polysilicon 和wires放下去 对有意思 他跟我说过一个重要点 就是他们更喜欢FPGA而不是CPU 原因是FPGA能给出确定性的clock cycles 他们知道一个packet什么时候进来 什么时候出去
Reiner Pope:为什么在CPU上不能保证这一点 其实你也可以设计出有确定性latency的CPU 而且很多AI芯片内部的processor 其实也有确定性latency Grogg就宣传过这一点 TPU的Core里也有难点在于 你要同时获得确定性的latency和高速 那么latency里的不确定性来自哪里 非确定性的latency来自CPU里 一些特定的设计选择 其实可以去掉这些设计选择 做出一个有确定性Latency的CPU 但这种CPU在市场上 没有那么有吸引力 所以现在人们不再做这种CPU了 不过从某种意义上说 确定性Latency 也许反而是一个 更简单的设计起点 后来一些芯片设计师 加进了一些东西 让它变得不确定 举一个具体例子 最重要的例子 大概就是CPU本身的Cache
Reiner Pope:在CPU里你有CPU 这就是CPU die本身 旁边有一块Memory 也就是旁边的DRM 然后里面有一个cache system 这里就是cache 难点在于同时做到确定性的延迟和高速度 那么延迟里的不确定性来自哪里 CPU里的非确定性延迟来自一些具体的设计选择 实际上你可以去掉这些设计选择 做出一颗延迟确定的CPU 只是这种CPU在市场上不太有吸引力 所以现在大家不再做了 但从某种意义上说 确定性延迟可能反而是一个更简单的设计起点 后来一些chip designer往里面加了一些东西 让它变得不确定 举一个具体例子 最重要的例子大概就是CPU上的Cache 在一颗CPU里 你有CPU本身 也就是这块CPU die 旁边还有内存 也就是DDR memory 然后在CPU里面 [00:39:22]
Reiner Pope:有一套Cache system里面就是Cache Cache会记住最近访问过的DDR内容 并把它们存下来 所以当CPU执行指令时 只要有一条指令要访问内存 它会先检查Cache 它会看数据是不是已经存在Cache里 如果不在才会去DDR里取 这是一个巨大的优化 Cache大概要比DDR快两个数量级 如果完全不用Cache 基本上所有程序都会慢100倍 所以Cache的存在 对CPU以合理速度运行来说是绝对必要的 但你能不能命中cache 取决于CPU当时的整体环境 比如还有哪些程序在运行 最近运行过什么 cache system里的随机数生成器在做什么 所以这是CPU运行时间里 一个很大的不确定性来源 这大概就是CPU的memory system 一个很大的不同做法是 不让硬件先说我要读内存 然后由硬件决定数据是不是来自cache [00:40:02]
Reiner Pope:你可以把这个决定写进软件里 另一种设计思路是 比如你在TPU里可能会看到这种做法 TPU这边我可以画同样的图 但把它叫做 Scratchpad 主要区别是这里如果是TPU 旁边就是HBM而不是DDR 但它仍然是Off-chip memory 软件不再只是说先访问内存 然后让硬件来决定 你会有一些指令访问这里 这是一类指令 还有另一类完全不同的指令 访问HBM 这种风格通常叫Scratchpad 而不是Cache 关键区别在于 有一种指令会说读写Scratchpad 另一种完全不同的指令 会说读写HBM
一凯:所以Scratchpad就是那个Cache 对这里的这个东西就是Scratchpad 我只想说清楚 我们退回很早以前学计算机的时候 大家会说计算机采用所谓John von Neumann architecture 也就是信息是串行处理的 可能只是因为我们一直在聊并行accelerator 但我感觉FPGA是高度并行的 这些AI accelerator也是高度并行的 甚至CPU也高度并行 如果你考虑它们有那么多core的话 所以现代硬件到底在什么意义上 还是von Neumann architecture 用这个说法来描述现代硬件 真的公平吗 我觉得用它描述CPU是公平的 [00:41:14]
Reiner Pope:CPU上的并行度大概是100个core乘以可能16路vector unit 所以在CPU上大概是1000路并行 我的问题是CPU会用到一块die 如果thread更少 那从transistor电压或者开关切换的角度看
一凯:是不是字面上只有一条control flow 也就是说die上只有一小块区域的电压在来回切换 或者说如果core数没那么多 CPU到底是怎么把这块die面积占满的 [00:42:04]
Reiner Pope:你的意思是如果core这么少 那里面到底在花什么面积 这些Core本身要大得多 也复杂得多 我们可以比较一下 一个CPU Core 可能占大约的1%到1% 而FPGA里的很多东西 其实只是LT一些Gate之类的小单元 所以很明显 为什么FPGA里 会有比CPU Core多得多的UT 但另一个问题可能是 为什么比如CTA Core的数量 会比CPU Core多 这其实就变成了 CPU和GPU的区别是什么 这会是一个很大的差别 在CPU里面 有一个很大的面积用途 CPU内部占面积最多的部分之一 是Cache 真正属于ALU的面积其实很少 主要占面积的其实是这些register file 而不是logic unit
Reiner Pope:这两类东西在GPU里也都有对应物 所以这不是最大的区别 但GPU里没有对应物的是branch predictor CPU里有一大块面积 用来放一整套predictor 它们会预测下一次branch什么时候出现 这个branch的目标地址在哪里 所以把很多这种东西去掉 再把这些register file做得更紧凑一些 这在很大程度上就是GPU获得优势的来源 branch predictor的作用是什么 是同时执行两个分支吗 还是它具体做什么 问题在于当我有一串instruction 比如instruction instruction instruction instruction 如果这里有一个branch 也就是这条instruction是branch 那么真正处理一条instruction的这个步骤 其实要花很长时间 可能要五纳秒左右 也就是说你真正发现这里有一个branch 然后计算那个Belarian 看它是真是假 再把program counter更新到新的目标地址 然后从对应的instruction memory里读取内容
Reiner Pope:这整套流程可能真的要五纳秒才能完成 所以在现实里 这件事可能要到后面某个位置才会完成 但我不想让clock speed被5纳秒限制住 5纳秒对应的是200MHz的clock speed 我希望跑在1到2GHz之类的速度上 所以在branch还在被评估的时候 我需要继续运行其他instruction 我其实就是想继续执行后面的那些instruction 但这可能是错的 如果最后发现这个branch确实要跳转 那我就得知道我不应该继续评估这些instruction 而应该跳到目标地址那边改为运行那里的instruction 所以branch predict的目的确实就是提前预测 [00:43:40]
一凯:甚至在你真正执行到这条instruction之前 比如提前5个cycle 就预测这里会出现一个branch 如果我从一个很高的层面 来比较大脑的工作方式 和你刚才描述的这些东西 差异可能在于 这些accelerator里 可以做structured sparsity 从而省下一些 本来要给gate用的面积 但大脑里是unstructured sparsity 任何neuron都可以连接到 任何其他neuron 而不是必须按列对齐 之类的方式连接 另外还有一点 memory和compute 可以说是在同一个地方 这在某种意义上正好就是memory的共制 没错 对也许这其实不是一个很大的区别 另一个可能更大的区别是 大脑的clock cycle比电脑慢得多 这部分是为了节省能量 [00:44:14]
一凯:因为clock cycle越快 电压就需要越高 这样才能识别信号 或者等信号稳定下来 判断transistor出在什么状态 对 我不知道你对大脑可能在做什么 和这些chip的工作方式相比 还有没有其他高层次的看法 我们先说clock speed这一点 chip上的clock speed相当高 因为这会带来更高的throughput
Reiner Pope:比如我们看一个GPU跑某个workload 它可能是在跑batch size 1000之类的规模 但大脑不是在跑batch size 1000 只有一个我 所以你可以想象这样说 拿一个GPU不要让它跑在一级赫兹 而是让它跑在一兆赫兹之类的速度 那它可能就会开始更像你说的大脑里那些对应的东西 但按照Silicon的工作方式 这并不会给你带来1000倍的能效优势 最后看起来是这样 你基本上只是让这个circuit运行一次 直到稳定 然后它会长时间空闲在那里 它空闲的时候不会消耗很多能量 因为大部分能量是在bit 从0切到1再切回来的过程中消耗的 我们其实可以说一下这种circuit的能耗 可以这样理解一个bit的存储 你其实是在chip里的某个地方 隐含的把一些电荷放进了一个capacitor 当它变成1的时候capacitor就被充电 等它下一次变成0的时候它就被放电 而这个给capacitor充电 再把电荷倒到ground里的循环 [00:45:20]
Reiner Pope:就是能量被消耗的地方 这叫dynamic power或者switching power 芯片的大部分能耗都来自这里 还有一些能耗是因为绝缘体并不是完美 绝缘体 但我们先不算那部分 大部分能耗 其实就来自充电和放电 也就是从0切到1 再从1切回0 所以如果你让一颗芯片跑得慢很多 比如每1000个clock cycle才clock一次 那transition的次数就少了1000倍左右 能耗大概也会少1000倍 但是从能效上看 这并不是一个很大的优势 你刚才从高层讲了TPU是怎么工作的 那从高层看GPU和TPU的工作方式有什么区别 我觉得有一个高层的组织原则不一样 然后在Core内部也有不同 不过我们先看外面看高层结构 以GPU为例 GPU顶层的block结构大概是什么样 如果把这个看成整颗芯片 那GPU的组织方式 [00:46:10]
Reiner Pope:基本上是一堆几乎一样的单元 也就是这些SM中间有一块alt memory 下面还有更多这样的SM 所以它大概是一个相当规则的Core网格 相比之下如果看TPU 你会看到逻辑单元的力度要粗得多 它可能会有一些数量比较少 但很大的matrix unit 这些就是很大的systolic array 中间有一些Vector Unit 底部还有Matrix Unit 也就是说Matrix Unit 加上中间的Vector Unit 这差不多就是整颗TPU芯片 你可以把这个东西 缩小成一个很小的单元 里面有更小的Matrix Unit 更小的Vector Unit 那大概就是一个SM 所以从非常高层的角度看 GPU就像是在整颗芯片上 铺了很多个很小很小的TPU
一凯:有意思 所以你是在说 Streaming SM Limited Tensor Core 和一个MXU是类似的 非常非常像 明白了 所以如果工作负载结构 没那么强 有一堆小TPU就很合理 但如果你基本上只有巨大的矩阵乘法 那就会想 为什么不避开MegaSM自己带register work schedulers这些东西的成本 为什么不直接做一个很大的东西 [00:47:30]
Reiner Pope:把这些成本摊到整个东西上 我觉得这会体现在你能把东西做多大 我们前面也反复看到这个主题 尤其是Systolic Array 更大的Systolic Array能更好地摊 保register file的成本 这种设计让你可以做更大的Systolic Array 而GPU那种设计 会把你限制在每种东西都只能是小单元 不过这里有一个取舍 因为这些东西是按比较粗的力度分开的 所以你需要把大量数据从Vector Unit移到Matrix Unit 也就是说你需要让很多数据穿过这里大概两条边界线 但如果看GPU里对应的东西Vector Unit到处都有 你需要让数据穿过这一条线 这一条线 这一条线 一条一条地走 所以在GPU里Vector Unit和Matrix Unit之间 能搬的数据量其实比TPU里高得多 因为在TPU里所有数据都要挤过这两条线 而在GPU里数据是通过大概16条Wiring线路来搬的 对不过你可能也需要跨越更小的面积 这本身也是一种节省是能耗上的节省 所以如果数据完全在一个SM里面处理
Reiner Pope:数据移动就小得多 但一旦你想跨SM操作 事情就会变得更复杂也更贵 你可以不用评论 [00:48:48]
一凯:不过有人可能会猜Mate X也许会想做的一件事 是采用GPU那种更小的结构 也就是由SRAM围绕的Systolic Array 但同时又把SM里那些为了支持CTA Architecture
Reiner Pope:而需要却占很多面积的东西去掉 我们公开聊过一个东西 叫Splittable Systolic Array 某种意义上你可以把它理解成 大的Systolic Array 同时也可以当小的Systolic Array用 很好 那我觉得用这个收尾不错 [00:49:06]
一凯:Rainer非常感谢 谢谢你Drakush