0读之前:三件必须先说清的事
口径、来源等级、以及这份文档跟原来那份 embedding 专题的关系。
一、chip 和 device 的比例是 1 : 2,这是全文最容易翻车的地方
一颗 TPU v7 封装里是两个 chiplet,而它们如实暴露成两个独立的 JAX device。
所有框架日志、jax.devices()、GKE 机型名里的数字,按 device 算。
所以:tpu7x-128 是 64 颗芯片;日志里的「1,153 TFLOP/s per device」
正是官方 2,307 的一半,不是掉了性能。算 MFU 时分母要么全用 2,307(按 chip),
要么全用 1,153.5(按 device)—— 混用会让结论直接差两倍。
官方
直接引用
Cloud TPU 产品文档、Google 官方博客与工程博客、JAX 开源代码、
以及 Google 自己发表的论文(IEEE Micro 2021 的 TPUv2/v3、ISCA 2023 的 TPU v4)。图上不加标记。
第三方
标灰
非产品文档的来源 —— 包括 Google 作者写的教科书式博客。
它们看着很权威,但本文实测到其中至少一条是错的(见 §4),所以一律降级标灰。
本文推导
给出推导链
官方没公布、但能从公开数字算出来的。图上标「推导值」,
正文把每一步写清楚,并且尽量拿一代四个变量全公开的老芯片当验尸台先验证公式。
本文实测
说清怎么测的
我们自己在 v7 上跑开源模型量出来的,任何文档里都查不到。
判断可不可信只能看实验是怎么做的 —— 所以每一条都连着模型、尺寸和口径一起给(§9)。
二、这份文档和原来那份 embedding 专题的关系
原来那份是单点深挖:把「embedding 查表在 v7 上到底怎么执行」这一件事挖到底。
这份把镜头拉远成通用架构 —— embedding / SparseCore 收缩成其中一节(§5)。
它仍然是全文最好的具体案例,但不再是主线。主线是:这颗芯片的每一层结构,
是为了回答「怎么把一个大矩阵乘做快」还是「怎么把上万颗芯片连成一台机器」。
1先看全景:一颗 v7 chip 里有些什么
封装 → 两个 die → TensorCore + SparseCore → HBM → 六个对外出口。
图 T-1 一颗 TPU v7 全景。两个 chiplet 结构完全相同,所以说明文字只写在 die 0 上 —— 对称该由形状来表达,不该把字誊两遍。注意 HBM 那两块:96 GiB 各归各的 die,两套地址空间互不可见,die-to-die 互连只是让跨 die 搬运比出封装便宜,并没有把它们缝成一个 device。
这张图最值得停下来的一处,是它和 B200 正好相反的那个选择。
两家都在同一个物理事实面前:一颗 die 做不到想要的算力,只能上双 die 封装;
而双 die 之间的通路必然比 die 内部慢。区别只在于把这条缝交给谁。
NVIDIA 用 NV-HBI 这条一致性总线把两个 die 缝起来,对软件装成一个 GPU。
好处是你什么都不用改;代价是缝还在 —— L2 跨 die 访问从约 21 TB/s 掉到 16.8 TB/s,
而这件事不会出现在任何 API 里,只会出现在你的性能曲线里。
Google 选了另一头:如实暴露成两个 device。代价是你必须自己决定怎么切分,
好处是缝在明处 —— 一旦你写下了分片策略,跨不跨 die 就是你自己写的,
不会有一条看不见的慢路径在背后偷走性能。
这不是谁对谁错,是两种成本转移。但它解释了后面七张图里几乎所有的设计差异:
当你决定把并行性交给软件之后,硬件里那些专门用来「自动藏起复杂度」的部件
—— 调度器、乱序、大寄存器堆、多级缓存 —— 就都可以省掉。省下的面积去了哪里,
就是下一张图的内容。
2把一个 TensorCore 拆开 —— 省掉的部件比留下的更说明问题
2 个 MXU、1 个 VPU、标量单元、VMEM、SMEM,一个不落地画出来。
图 T-2 一个 TensorCore 的显微镜展开。左边是留下的部件,右边是一个 GPU SM 里有、而这里没有的五样东西。这张图的信息量主要在右边 —— 一颗芯片的性格,往往是被它不做的事定义的。
数一遍留下的:2 个 MXU、1 个 VPU、2 个跨 lane 单元、一个标量单元、
64 MiB VMEM、1 MiB SMEM。就这些。
再数一遍没有的:没有 warp 调度器,没有几十份并发上下文,没有 256 KB 那种规模的寄存器堆,
没有会自动填充的 L1,没有记分板和乱序发射。这五样在 GPU 的 SM 里加起来占了相当可观的一片硅,
而它们的共同职能只有一个 —— 在运行时动态地藏住延迟和分支。
这五样东西是一根绳上的
它们不是五个独立的取舍,是同一个决定的五个后果。
一旦你决定「运行时要能换一个任务跑」,就必须同时驻留几十份上下文(要大寄存器堆)、
必须有东西来挑下一个(要调度器)、必须知道谁的数据到了(要记分板)、
必须让访存不至于把流水线卡死(要自动缓存)。
反过来,一旦你决定不在运行时换任务,这五样同时失去存在理由。
这就是为什么 TPU 的核看起来「少了很多东西」却依然自洽 —— 它不是被砍出来的,是另一条线上长出来的。
省下的面积去了哪里,图上给了一个直接的对照:一颗 TPU v7 chip 上的 VMEM 是 128 MiB
(两个核各 64 MiB),而一整颗 B200 的共享内存合计约 34 MB。
差别不只在容量 —— 更在于那 128 MiB 的每一个字节都由编译器显式安排,
不是硬件按访问历史猜着填的。§6 会把这件事讲透。
⚠️ 上面比的是「软件可控暂存」这一个口径 —— 换个口径结论会反过来
比片上 SRAM 总量,B200 是赢的:寄存器堆 37 MiB + L1/共享 37 MiB
+ TMEM 37 MiB + L2 126 MB(≈ 120 MiB)= 约 231 MiB;
TPU v7 一颗 chip 是 VMEM 128 + SMEM 2 + 累加器 4 = 约 134 MiB。
—— 所以不能说「TPU 片上存储更多」,只能说「TPU 软件说了算的那部分更多」
(130 对 37,约 3.5 倍)。B200 总量领先的那近 100 MiB 几乎全在 L2 里,
而 L2 恰恰是唯一一格你没法显式安排的空间。
再收窄一档:单个计算单元能当成一整块连续工作台用的,是 227 KiB 对 64 MiB —— 差 289 倍。
这一档才是决定「一次融合能融多大」的那个数。
⚠️ 但要读准:这是上限比上限。227 KiB 是一个线程块的上限,
而那个 SM 上有 4 个 Tensor Core、还可能驻着别的块;64 MiB 那边也要跟权重预取分账
(由 xla_tpu_scoped_vmem_limit_kib 控制)。两边的「独占」都有水分。
还有一处容易被略过:标量单元在这颗芯片上比你以为的重要得多。
它不参与矩阵乘,但 DMA 的描述符是它写出来的 —— 也就是说「什么时候搬、搬多少、搬到哪」
这件事,是标量指令流里的一条指令,不是硬件后台自动发生的行为。
一颗以矩阵乘为业的芯片,把「安排搬运」这件事放在了标量单元上,
这个安排在 §6 和 §7 会各出现一次。
3并行层级:lane / sublane 分别对应硬件上的什么
和 GPU 的 thread / warp / block / grid 逐层对照 —— 层数少得多,少掉的那几层正是关键。
图 T-3 并行层级逐层对照。这张表刻意按问题对齐,不按名词对齐 —— 左边一列是问题,中右两列各自回答。名词对名词地排会得到一张翻译表,而按问题排,有两行 TPU 那一栏是空的,那才是要看的东西。
七个问题里,TPU 有两个答不出来:「共享一块暂存的是哪一组」和
「运行时谁决定接下来跑哪个」。
这两行不是随便哪两行。GPU 的 thread block 是一个运行时概念 ——
有几个 block、落在哪个 SM 上,启动的时候才知道;而 warp 调度器则是
延迟出现之后才发挥作用的东西。它们俩合起来,就是 GPU 藏延迟的全部机制。
TPU 把这两层都拿掉了,于是也就把「运行时还能补救」这条路一起拿掉了。
最实用的一句话:两边的调优直觉不能互相搬运
GPU 的层级是「执行体的层级」 —— thread、warp、block 说的都是谁在跑;
TPU 的层级是「数据形状的层级」 —— lane、sublane、tile、slice 说的都是数据被切成什么样。
所以 GPU 的性能问题多半出在「占用率」上(同时驻留的 warp 够不够多、能不能把延迟盖住),
TPU 的性能问题多半出在「形状」上(矩阵维度对不对齐、切片切得均不均匀)。
一个熟练的 CUDA 工程师第一次调 TPU,最常见的挫败感就来自这里:
他熟悉的那套旋钮在这颗芯片上根本不存在。
顺带解释了一件常被问到的事:为什么在 TPU 上「跑一个不规则的算法」这么别扭。
不是编译器不肯,是硬件的层级里根本没有一个能承载「不规则」的单位 ——
最小的可指名单位是「向量里的一格」,而它没有程序计数器,也不能走自己的分支。
你能表达的最小的「不同」,是让数据的形状不同,不是让某一格的行为不同。
4MXU:256×256 到底意味着什么
脉动阵列里数是怎么流的,以及那个「每 cell 每周期几个乘加」的常数 —— 这一节还附带一个我自己犯过的错。
图 T-4 MXU 脉动阵列的一个快照。颜色深浅表示这个单元什么时候开始干活,同一条反对角线上的单元在同一拍工作 —— 这就是「脉动」两个字的字面意思。右边三张卡分别讲:峰值那条链是官方给的、256 是粒度不是上限、以及一个真实到扎心的例子(head_dim=128)。
先说结论,因为它反直觉:K 比 256 大一点不亏,K 比 256 小才是真亏。
阵列的收缩边是物理的 256 行。K = 1,024 就是走 4 趟,累加器一直不落地,
效率跟一趟算完几乎一样;而 K = 128 时,有一半的行在空转,
并且 —— 这才是关键 —— 没有办法把别的活塞进那一半。
回到 §3:TPU 没有「换一个跑」这一层,所以空着就是空着。
一个真实例子:注意力的 head_dim = 128
多头注意力里,QKᵀ 的收缩维和 PV 的输出维都等于 head_dim。
head_dim = 128 时,这两处各只喂满 256 的一半 —— 纯几何,
不需要任何内部信息就能推出上限是 50%。
实测佐证:把 Qwen3-30B 的注意力从 32 头 × 128
改成 16 头 × 256(参数量和 FLOP 完全不变),MFU 在 8K / 16K / 32K 序列长度上
分别提升 21% / 32% / 46%。
结论不是「TPU 不适合注意力」,是模型配置和硬件的收缩边要一起选。
同样的改动搬到 GPU 上收益接近于零,因为 GPU 的收缩维是 16,
而 128 是 16 的整整 8 倍 —— 同一个模型配置,在两边的「浪费」完全不在一个位置。
插一段:我在这个常数上错了两次,而第二次错得更值得讲
那条峰值公式 —— 262,144 FLOP / 周期 / MXU × 2.2 GHz × 4 个 MXU = 2,307 TFLOP/s
—— 是 Google 工程博客逐字给出的。但在找到这条公式之前,我先后写下过两个版本,
两个都是错的,而第二个错误比第一个危险得多。
| 版本 | 当时写的 | 问题出在哪 |
| v1 | 4 个 MXU、每个 cell 每周期双发 |
结论其实是对的,但当时没有任何证据 —— 它是为了凑平 2,307 这个已知答案倒推出来的 |
| v2 | 拿 v5e / v6e 当验尸台反算,判定「每 cell 一次乘加」,于是改成 8 个 MXU |
反而错了。反算用的输入里,「v6e 有几个 MXU」这一项是我自己填的,没有独立出处 |
| v3 | 官方公式出现,回到 4 个 MXU、每 cell 2 次乘加 |
— |
v2 那次的错法值得单独说。我用的方法本身是对的:找一代四个变量全公开的老芯片,
拿它反算公式,验证通过了再套到新芯片上。问题在于 v6e 那次反算里,
我算的是「4 个 MXU × 256×256 × 每 cell 1 次 @ 1.75 GHz = 917.5 TFLOP/s ✓」——
数字完美对上了官方的 918。
但官方文档白纸黑字写着「每个 TensorCore 有 2 个 MXU」,而 v6e 是 1 个 TensorCore/chip,
所以是 2 个 MXU,不是 4 个。正确的算式是
「2 个 MXU × 262,144 FLOP/周期 × 1.75 GHz = 918 ✓」—— 同样对得上。
为什么这类错误最危险:一个对得上的答案,掩护了两个错误的前提
MXU 个数错了 2 倍(4 应为 2),每 cell 的乘加数也错了 2 倍(1 应为 2),
两个 2 倍方向相反,乘起来正好抵消。于是我得到一个和官方完全吻合的数字,
并且拿这个「验证通过」去否定了原本正确的 v1。
教训不是「要小心」,而是一条可执行的规则:「用已知样本反算」这招要真正有效,
被反算的那几个输入必须逐个有独立出处。
只要其中任何一个是你自己填进去的,这个方法就从「验证」退化成了「凑答案」——
而凑出来的答案看起来和真的一模一样。
还有一件事图上写了、这里再强调一遍:「每 cell 2 次乘加」在硅上到底怎么实现,
公开资料答不了。是每个 cell 里真放了两个乘法器,还是 256×256 只是一个逻辑视图、
底下另有排布?我不知道,所以本文不猜。这一条会出现在 §10。
5SparseCore:第二种核,以及它到底在干什么
原来那份 embedding 专题的核心结论,压缩成一节 —— 包括那个最反直觉的一条。
图 T-5 SparseCore 与它在芯片里的归属。左边那张有向图是重点:两颗核之间的数据流是单向的。右下角那张卡则是全节的落点 ——在我们真实跑的生产任务里,SparseCore 一次表都没查过。
关于 SparseCore,流传最广的一句话是「TPU 上有个专门查 embedding 表的核」。
这句话不算错,但它会让你在看自己的 profile 的时候彻底看错。
先把结构说清楚:一颗 chip 上有 4 个 SparseCore(每个 device 2 个),
每个由 1 个标量子核 + 16 个向量子核组成,lane 宽 16,私有 SRAM 512 KiB。
它没有 MXU,做不了矩阵乘。它能做的是 cumsum、sort_key_val、
fetch_and_add、addupdate_scatter 这一类
不规则访存 + 归约的活 —— 这些在开源的 Pallas SparseCore 接口里能直接看到。
最容易讲错的一条:「细 128 倍」指的是 tile 形状,不是 DMA 更快
两颗核共用同一个 HBM 控制器,DMA 的通道宽度都是 32 B,
SparseCore 并没有一个更快的搬运器。差别在于最小可寻址的一块:
TensorCore 的 tile 是 (8,128) = 4,096 B,SparseCore 是 (8,) = 32 B。
散落在一张大词表里的几百行,用 4 KB 的块去取,取回来的绝大部分都会被扔掉 ——
省的是无效流量,不是提高了带宽上限。
那生产里的 embedding 到底怎么跑的?
MaxText 里有一个开关叫 use_iota_embed。打开时,「查表」被写成
「把 token id 展成 one-hot,再和整张词表做一次矩阵乘」 ——
一条 dot,跑在 MXU 上。关掉才是真的 gather。
上游的默认值是关的(configs/base.yml 里写 false),
但仓库里 34 份配置显式打开、0 份显式关闭 —— 连给 GPU 用的那几份也是打开的。
所以判断某一次跑走了哪条路,只能看那一次实际生效的配置,不能看模型的 yml。
别说「反正 MXU 闲着,用算力换带宽很划算」—— 算术直接否掉了它
Hunyuan3 那个尺寸下(M=7×4096,K=120,832,N=4096),这条 matmul 是
28.38 TFLOP/device,占一步的 0.62%,约 24.6 ms;
同样的事用 gather 只要约 1.0 ms —— matmul 慢约 24 倍。
它不省时间,它花时间。这个选择成立的唯一理由是分母够大。
真实动机我没查实,最强的候选是反向传播 —— scatter-add 在分片下不好做,
而 matmul 的反向天然还是 matmul。但这只是候选,不写成结论。
至于 SparseCore 为什么没被派去干这件事:看的是同一批里「重复取同一行」的程度。
推荐系统那边少量热行被反复命中,重复率极高,那是 SparseCore 的主场;
而语言模型的取行重复度不够高 —— 注意不是「没有重复」:
一条十几万 token 的序列里不同的词只有几千个,绝大部分命中最高频那一两千行。
重复确实有,只是跟搜广推差着量级,落在最不划算的那一端,
编译器于是根本没把它派过去。
所以这一节真正的收获不是「SparseCore 是什么」,是「不要用宣传口径去解释自己的 profile」。
那颗核确实存在、确实为稀疏访问而生,但在你手上这个任务里它可能在干完全不同的事
(比如集合通信卸载),甚至什么都没干。看 trace,不要看宣传页。
6一个数走完全程:HBM → VMEM → MXU → 累加器
每一站的容量、谁负责搬、以及「暂存」和「缓存」到底差在哪。
图 T-6 一个数从 HBM 到乘加单元要经过几站。每一站只问一个问题:这一步是谁决定的。从头到尾没有一站的答案是「硬件自己看着办」—— 这就是全图的落点。
几乎每份 TPU 材料都会列容量和带宽。但真正决定你写代码时会撞上什么的,
是图上那一整行 「谁决定搬」。
VMEM 不是缓存,这不是措辞讲究,是两种不同的机器
| 问题 | GPU 的 L1 / L2(缓存) | TPU 的 VMEM(暂存) |
| 放什么进去 | 硬件按访问历史猜 | 编译器写死 |
| 有没有命中率 | 有,而且是主要调优指标 | 没有这个概念 |
| 猜错 / 排错的后果 | 变慢(多跑一趟内存) | 停住(没有别的活可切) |
| 你能控制到什么程度 | 间接:改访问顺序去哄它 | 直接:改分片和 tile 形状 |
把这张小表念一遍就明白:TPU 上不存在「缓存没命中」这种性能问题。
不是因为它命中率高,是因为根本没有「命中」这个事件 —— 数据要么按班表到了,要么没到。
没到就是编译器排错了,而排错了没有兜底。
再说一处很多人第一次看会愣一下的:搬运不是后台自动发生的。
HBM ↔ VMEM 的每一次搬运都由 DMA 完成,而 DMA 的描述符是标量单元写出来的。
也就是说「什么时候搬、搬多少、搬到哪」跟乘加指令一样,占着同一条指令流里的槽位。
这解释了 §2 里那个看起来很怪的设计:为什么标量单元在一颗以矩阵乘为业的芯片上还这么重要
—— 它不算数,它安排搬运。
带宽落差是这条通路的真正约束:片上暂存的读写带宽比 HBM 高约一个数量级(具体数值官方未公开,这里只给量级)。所以「尽量让数在暂存里多待一会儿」不是风格建议 —— 它是绝大多数 TPU 调优工作的实际内容。
最后一站顺手给一个能自己验的推导:累加器是 128 个、形状 (8, 256)、32 bit,
所以 128 × 8 × 256 × 4 B = 1,048,576 B,正好 1 MiB / MXU。
这类能对上整数的推导值得多做几次 —— 它是检查自己有没有把口径搞错的最便宜的办法。
7延迟被藏到哪里去了:VLIW 与编译期排班
GPU 靠切换 warp 藏延迟,TPU 靠编译器提前排好 —— 代价是排错了没有兜底。
图 T-7 VLIW 一拍多槽与编译期排班。上面那一排是公开论文里 TPU v2/v3 的 bundle 构成(v7 的官方没有公开);下面的甘特图是示意,不是实测 trace —— 它演示的是排班这件事的形状。灰色格子表示那一拍那个槽真的空着。
VLIW 的意思很朴素:一拍要干的所有事,打成一包。包里给标量、向量、矩阵、
杂项各留了固定的槽,谁往哪个槽里填、在第几拍填 —— 全部写死在指令流里。
没有乱序,没有记分板,没有「运行时再看」。
看甘特图的时候要盯两处。第一处是第 1–6 拍的向量槽:那里塞的是
上一块的后处理,和这次搬运没有依赖关系,所以能被提前挪过来盖住 DMA 的延迟。
这就是编译器「藏延迟」的全部手段 —— 找独立的活。
第二处是第 14 拍之后:独立的活用完了,所有槽都空着,
而且不会有任何东西自动顶上来,因为没有第二个线程可切。
把 §2 那句话补完整
§2 说 TPU「省下了调度器、寄存器堆、记分板那片硅」。这张图是那句话的另一半:
省下来的东西并没有消失,它被搬到了编译期。
调度这件事总得有人做 —— 区别只在于是硬件每次运行时重做一遍,还是编译器一次做完。
所以「TPU 更简单」是个误解:它不是把复杂度删掉了,是把复杂度换了个地方放 ——
从硅上换到了编译器里,也从运行时换到了你写模型配置的那一刻。
这个取舍在日常里长成三件事,而这三件事其实是同一件:
- 形状一不规则,性能就掉得很难看。batch 小、序列不齐、专家路由不均 ——
这几种情况的共同点都是「后一步紧跟着前一步」,空拍没东西填。
- 编译很慢,而且慢得有道理。v7 上一次 XLA 编译常见 10–17 分钟。
它不是在「翻译」,是在替硬件把整张班表排完;GPU 那边这件事是每次运行时由调度器现做的,
成本摊在了跑的时候。
- 但跑起来非常稳。同一个形状重复跑,步时几乎没有抖动 —— 因为根本没有运行时决策可抖。
可预测性是这个设计买来的东西,不是附带效果:容量规划和性能回归检测在 TPU 上都因此简单不少。
8从一颗到一个 pod —— 全文的落点
ICI、3D 环面、cube、slice:为什么说 scale-out 是这颗芯片的第一性设计。
图 T-8 从一颗 chip 到 9,216 颗。主图是一根对数刻度的横轴 ——因为这件事的关键不是「谁能连更多」,而是在哪个规模上你被迫换一套编程模型。线性轴会把 72 和 9,216 压成一个点和一条线,那一刀就看不见了。
每颗 v7 有 6 条 ICI 物理链路,对应三维的正负方向,接成 3D 环面。
「环面」而不是「网格」的差别是实打实的:4×4×4 如果只是网格,最远要走 3+3+3 = 9 跳;
接成环面之后是 2+2+2 = 6 跳。规模越大差得越多,而这直接决定了 all-reduce 的最坏时延。
但环面不是白拿的。它要求切片在物理上必须是连续的一块立方体 ——
所以在 TPU 上你申请的不是「64 颗芯片」,是「一个 4×4×4」。
形状本身是调度的一部分,这一点在 §3 那张表里就已经埋下了伏笔。
128 倍的差距不在带宽上,在「不换协议能连多远」上
B200 的 NVLink 域是 72 颗,再往外就得换 InfiniBand 或以太网 ——
带宽掉一个量级,而且集合通信要重写。
TPU 的 3D 环面一路铺到 9,216 颗,全程同一套 ICI,
这两个数量级之间通信代码一个字都不用改。9,216 ÷ 72 = 128。
但也别夸大。单颗算力两边几乎打平(HGX B200 约 2,250 TFLOP/s BF16 dense,TPU v7 一颗 chip 2,307 —— 换成 NVL72 里那颗 GB200 是 2,500,反过来略高),
而且在 72 颗以内,NVLink 的每颗带宽还更高(1.8 TB/s 对 1.2 TB/s)。
这里比的是拓扑能延展多远,不是单芯片谁快。
两份文档合起来的那一句话
《GPU 显微镜》的最后一张图讲的是:一颗 B200 里有 592 个 Tensor Core,
而一颗 TPU v7 里只有 4 个 MXU —— 同样一块矩阵乘的活,两边被切成的份数差 128 倍。
GPU 的协调主要发生在芯片内部。
这张图讲的是另一半:TPU 一颗芯片里只有两个核要协调,
但不换协议能一路连到 9,216 颗,而 GPU 在 72 颗上就得换。
TPU 的协调主要发生在芯片之间。
两个「128 倍」出现在完全不同的位置,这不是巧合,是同一个设计取向的两个侧面:
一边把复杂度收进一颗芯片里由硬件消化,另一边把复杂度摊到芯片之间由软件面对。
八张图从头到尾说的都是这同一件事 —— 不是谁更强,是把同一份复杂度放在了不同的地方。
9来源等级总表
逐条列出每个数字来自哪里 —— 这样你可以只信你愿意信的那几行。
四级的划分标准写在 §0。这里只补一句:「第三方」不等于「不可信」,
但它确实被降级了 —— 因为本文实测到其中至少一条是错的(那本流传很广的 TPU 教科书式博客
把 256×256 说成 131,072 FLOP/周期,比官方的 262,144 少一半,本文不采用)。
官方 —— 可直接引用
| 事实 | 值 | 出处 |
| 峰值那条完整算式 | 262,144 × 2.2 GHz × 4 = 2,307 TF/s |
Google 工程博客(三个数逐字给出,乘出来精确等于官方峰值) |
| MXU 尺寸 / 每核个数 | 256×256 · 2 个 / TensorCore | JAX 开源代码 |
| 累加器 | 128 个 · (8,256) · 32 bit | JAX 开源代码 |
| lane / sublane | 128 / 8 | JAX 开源代码 |
| VMEM / SMEM 容量 | 64 MiB / 1 MiB 每 core | JAX 开源代码 |
| HBM 容量与带宽 | 192 GiB · 7,372.8 GB/s(= 7.37 TB/s)· 8 stack ⚠️ 官方另一处写 7,380 GB/s,差 0.1%;引脚反推恰好落在 8 × 921.6 = 7,372.8,本文除法一律用 7.37 |
Cloud TPU 产品文档(表头写的是 GiB) |
| SparseCore 内部构成 | 16 子核 · lane 16 · 512 KiB · 粒度 32 B |
JAX 开源代码 |
| SparseCore 的指令面 | cumsum / sort / scatter … |
开源 Pallas SparseCore 接口(其中没有任何矩阵乘原语) |
| ICI 链路数与总带宽 | 6 条 · 1,200 GB/s 每 chip |
Cloud TPU 产品文档(措辞自相矛盾,见 §10) |
| 3D 环面 / cube / pod | 4×4×4 = 64 · pod 9,216 | Cloud TPU 产品文档 |
| 一台主机挂几颗 | 4 chips · 224 vCPU · 960 GB | Cloud TPU 产品文档 |
| VLIW bundle 构成 | 322 bit = 2+4+2+1 + 6 立即数 |
IEEE Micro 2021(v2 / v3,不是 v7) |
| 老代次时钟 | v3 940 MHz · v4 1,050 MHz | ISCA 2023 论文 |
use_iota_embed 的默认值与用法 | 默认 false,34 份配置显式开 |
MaxText 开源仓库 |
第三方 —— 一律标灰
| 事实 | 值 | 为什么降级 |
| B200 的 SM 数与每 SM 构成 | 148 SM × 4 Tensor Core |
非官方拆解;本文只用它做数量级对照 |
| B200 共享内存合计 | 约 34 MB | 同上 |
| B200 峰值与 NVLink | 约 2,250 TF/s BF16 dense · 1.8 TB/s |
厂商规格页;这是 HGX B200 的口径,NVL72 里的 GB200 为 2,500 —— 用前先确认是哪个 SKU |
| 「256×256 每周期 131,072 FLOP」 | 131,072 |
与官方的 262,144 差 2 倍。本文不采用,并在此明确标出。 |
本文推导 —— 每条都给推导链
| 结论 | 怎么算出来的 |
| 每个 cell 每周期 2 次乘加 |
262,144 FLOP ÷ 2(一次乘加算 2 FLOP)÷ (256×256) = 2。
并且这是新架构才有的 —— 128×128 的 v3 / v4 / v5e / v5p 全部还原成每 cell 1 次 |
| v6e 反算校验 |
2 个 MXU × 262,144 × 1.75 GHz = 918 TF/s,对上官方 918。
这条校验的每一个输入都有独立出处(MXU 个数来自官方文档,不是我填的)—— 见 §4 那段自陈 |
| 累加器合计 1 MiB / MXU | 128 × 8 × 256 × 4 B = 1,048,576 B |
| 环面直径 6 跳 | 4×4×4 网格 3+3+3 = 9;环面 ⌊4/2⌋×3 = 6 |
| 「128 倍」(拓扑) | 9,216 ÷ 72 = 128 |
| SparseCore 4 / chip |
JAX 那张表在 per-device 口径下读出 2,而一颗 chip = 2 个 device ⇒ 4。
注意:该字段在不同代次之间口径不一致,不能只拿它推口径 |
| 官方博客的「pod HBM 1.77 PB」是单位混用 |
9,216 × 192 = 1,769,472 —— 恰好等于把 GiB 当成 GB 直接乘。
按十进制算应为约 1.90 PB。这不是两个矛盾的数,是同一个数的两种单位写法 |
本文实测 —— 我们自己在 v7 上跑出来的
下面这几条不是从任何文档里查来的,是在 v7 上跑开源模型量出来的。
所以它们既不属于「官方」也不属于「第三方」—— 判断它们可不可信,
唯一的依据是这份材料自己说了实验是怎么做的。
| 数字 | 值 | 怎么测的 / 怎么读 |
注意力换形状后的 MFU 提升 (§4) | 21% / 32% / 46% |
Qwen3-30B,把注意力从 32 头 × 128 改成 16 头 × 256,参数量和 FLOP 完全不变,
只是让收缩维正好填满 MXU 的 256。三个数分别对应 8K / 16K / 32K 序列长度 ——
序列越长收益越大,因为注意力在整步里的占比越来越高 |
one-hot embedding 的代价 (§5) | 28.38 TFLOP/device · 0.62% · 24.6 ms |
Hunyuan3 那个尺寸(M=7×4096,K=120,832,N=4096)。
同样的事用 gather 约 1.0 ms —— matmul 慢约 24 倍。
请连着分母一起读:它占一步只有 0.62%,所以「能接受」,不是「更快」 |
embedding 的取行重复度 (§5) | 一批 131,072 个 token · 词表 129,280 行 |
不是「几乎不重复」—— 十几万 token 里不同的词只有几千个,重复确实有;
但集中度跟搜广推差着量级,所以落在 SparseCore 最不划算的那一端。⚠️ 本页只给这个观察,不给判据的具体算式 |
XLA 编译耗时 (§7) | 10–17 分钟 |
v7 上大模型一次编译的常见区间。它和硬件规格无关,跟模型大小、
切片规模、XLA 版本都有关,所以只当量级读,不要当基准 |
10还没查实的
查不到就写查不到 —— 这一节是这份文档最该被信任的部分。
下面每一条都是我试着查了、没查到的。它们没有出现在前面任何一张图的数字里,
出现的地方一律画成灰色虚线或直接写「查不到」。
一、公开资料里找不到的
| 问题 | 现状 |
| v6e / v5e / v5p 的官方时钟 |
只有 v3(940 MHz)和 v4(1,050 MHz)有论文出处;v7 的 2.2 GHz 来自工程博客。
中间那几代没找到可引用的官方数字 —— 所以本文不做「逐代时钟趋势」这种图 |
| v7 向量寄存器的形状和个数 |
v2 / v3 论文给的是每 sublane 32 深。v7 这一层官方没有公开,
本文不拿旧代的数字顶替(§6 那一站因此画成虚线框) |
| v7 的 VLIW bundle 宽度与槽位构成 |
只有 v2 / v3 的 322 bit 是公开的。图上明确标了代次 |
| 「每 cell 2 次乘加」在硅上怎么实现 |
是每个 cell 真放了两个乘法器?还是 256×256 只是逻辑视图?公开资料答不了,本文不猜 |
| v7 的 INT8 / INT4 峰值 |
JAX 那张表里这两项都是 0。但「0」到底是「不支持」还是「没填」,
我没能确认 —— 所以本文全篇不提 v7 的整数峰值 |
| die 面积 / 晶体管数 / TDP / 制程 | 官方一项都没公开 |
| 一个作业能调度到的最大切片 |
物理上 9,216 连成一个环面是官方数字,但「一次能拿到多大一块」取决于调度系统,
公开资料里没有可引用的上限 |
| 边缘切片能不能拿到环绕链路 |
也就是「环面在多大规模上会退化成非环」。公开资料同样没说。不猜 |
二、官方资料自己对不上的
这类比「查不到」更麻烦 —— 它会让你以为自己查到了。
| 矛盾 | 本文怎么处理 |
| ICI:正文写「每轴双向 200 GB/s」,
但 3 轴 × 200 = 600,对不上同一页表格里的 1,200 |
只有读成「每条链路 200」才自洽(6 条 × 200 = 1,200)。
本文按这个读法画,并在图上写明原文是另一种措辞 |
| 「pod」有两个官方定义:9,216 颗 / 256 颗 |
两个都是官方说法,没有一处说明哪个作准。
本文一律指 9,216 那个,并且每次都把数字写出来 |
| HBM 同一页里既写 GiB 又写 GB |
表头写的是 GiB。对外一律说 192 GiB,不说 206 GB
(那是同一个容量的十进制写法,业界没人这么报) |
| SparseCore 私有 SRAM:512 KiB / 子核 vs 公开 v4 材料的 2.5 MB / SC |
差一个量级,我没能判定这两个是不是在说同一个东西。
本文只用 JAX 开源代码里的 512 KiB,并标明它的口径是「每子核」 |
为什么把这一节放进讲课材料
一份材料的可信度,不取决于它讲对了多少,取决于它有没有能力说「这个我不知道」。
上面这十几条如果强行填上一个「看起来合理」的数字,整份文档会显得更完整、更好看,
而且几乎没有人会当场发现 —— 这正是它危险的地方。
§4 那段自陈是这条原则的最好注脚:我一度用一个算得完全正确的结果,
去支撑两个错误的前提。能对上的答案不等于对的推理;
唯一的解法是把每一个输入都追到独立出处,追不到就如实标出来。