这份讲义怎么用
这是老师的草稿,不是给学员的材料。学员看
课件 L200,你看这一份。
课程作者 Chris Yang · Google Cloud AI Infra 架构师。
| 🎯 | 这一讲要留下什么 讲完之后学员脑子里应该剩的那一句话。 只有一句 —— 讲散了就是没讲到。 |
| 🗣 | 讲稿 接近逐字,可以照着念。黄底的是必须说出口的原话, 其余按自己的语感改。 |
| 🖥 | 屏幕 这一段该滚到课件的哪里、该指哪张图的哪一格。 |
| ❓ | 可能被问到 真实会被问的问题 + 答法。 答不上来的就写「这个我不确定」 —— 讲义里编一个,现场你会照着念。 |
| ⚠️ | 别讲什么 这一讲最容易跑偏的方向。时间就是被这些吃掉的。 |
舞台指示这几个词,具体是什么意思
下面这几个词在全篇反复出现。它们不是形容词,是有具体做法的。 台上照这个执行,不要凭当时的感觉。
| 停死 | 念完最后一个数立刻闭嘴,心里默数两拍再开口。 不是「稍微停一下」—— 临场的默认动作是接着讲, 不给自己一个可数的量就一定会滑过去。 |
| 念出来 | 屏幕上照原样打出来,嘴里报中文、一行一个数。 英文原文(报错、指令名)念不出去,硬念台下也接不住。 |
| 只念不解释 | 念完必须补一句前向指路: 「先记着,×× 时候会回来用它。」 不交代去处的伏笔不是伏笔,是噪音;而且要说清在哪一节收。 |
| 语速放慢 | 把那一句拆成两个短句说,中间断一次。 「放慢」控制不住,断句控制得住。 |
| 要真的等 | 等到有人出声或举手为止,不设时间上限; 真没人应,点一个人问。自问自答等于这三十秒白花。 |
| 指着 | 指完立刻念出那一格上的字, 别让台下自己找。手势不携带信息,念出来才携带。 |
⛔ 还有一条通则:凡是解释「为什么这么讲」的句子 (例如「惊讶感一点不减,这句经得起追问」)—— 那是给你自己的判断依据,不要念出来。 念出来会把听众从内容拉到你的方法论上。
⭐ 全课那条线:算子融合
这门课表面在讲硬件差别,真正在讲的是一件事:一个算子该不该跟邻居合成一个 kernel,以及这件事由谁来决定。先记住这张对照 —— 不要另开一段讲融合,在这五处各点一句名字就够了:
| 在哪 | 这一处怎么点 |
|---|---|
| 第 1 节 | 那条线刚定出来、还没用过的那一刻,给它起名字:想让慢算子变快只有一条路, 把分母做小,而最有效的办法就是让中间结果别落 HBM。约 40 秒 |
| 第 2 节 | 「朴素 vs Flash」就是融合前后。FLOP 一个没变,强度差一千倍 —— 这是全课对融合最有力的一次演示,不用额外说明 |
| 第 3.6 节 | 同一个融合,在两边分别怎么落地:块开多大、谁来切 |
| 第 5 节 | ⭐ 「运行时还是编译期」问的正是融合由谁决定。 全课在这里收口 —— 前面所有铺垫都是为了这一句 |
| 第 9 节 | 收尾也收在这条线上:不比谁强,只说这个决定权交给了谁 |
这些时间怎么分
下面这张表是计划,不是实测 —— 每讲过一遍,回到生成脚本里把真实分钟数改回去(表是按那些数生成的, 改一处就够)。
| 分钟 | 讲什么 | 这一段的落点 |
|---|---|---|
| 0–8 | 开场:两种出身(不再有那张路线图) | 把「处处都不同,但只有一个成因」这个论点立住。它现在只是论点,不是结论 |
| 8–18 | 第 1 节 · 定分界线:算力 ÷ 带宽,两边撞在同一个数上 | 第一个「坐直」的时刻:312.9 对 312.5,参数表在这里被拆穿。节末给全课那条线起名字:算子融合,并当场说清它不是让搬运消失、只是换了一层 |
| 18–28 | ⭐ 第 2 节:同一次取数,一边路上 4 个运行时决定,一边 0 个 | 论点第一次变成可看见的东西。也是全课对算子融合最有力的一次演示 —— 同一个注意力,FLOP 一个没变,强度差一千倍 |
| 28–70 | ⭐ 第 3 节 · 上:整颗 → 一个核 → 核里那块地 → 并行层级 → 一条指令吃多大一块 | 这半节只交零件,不交结论。每一层都要能指出「两边差什么、是谁的决定造成的」—— 结论在下半节 |
| 70–80 | ⭐⭐ 第 3 节 · 下:FlashAttention 两边并排走完一遍 + 回收第 1 节那个峰值巧合 | 全课的高潮就在这 16 分钟里。讲不完别的都能砍,这段不能砍 —— 砍了这门课就退回成参数对比 |
| 80–84 | ⭐ 第 4 节:同样是 64 颗 —— 一边在一个 NVL72 域里(一跳),一边是 4×4×4 环面(最远 6 跳) | 它是第 6 节那组实测的前置 —— 那两个数跑在两种结构不同的网上,而这门课没拆开量过 |
| 84–96 | ⭐ 第 5 节 · 把那个词正式拆开:运行时,还是编译期 | 开场那个论点在这里收口 —— 四次出场合成一件事。换句话说:融合这件事,两边分别交给了谁 |
| 96–104 | 第 6 节 · 跑完之后:两个数摆上桌,能不能比。分母看入场券,分子看 MFU / HFU | 产出不是那个倍数,是「这个数往哪边都有理由」。⭐ MFU 与 HFU 在这里正经讲一遍 —— 它是分子那一侧的口径,也是「边界」那张图第 ③ 行的前提 |
| 104–109 | 第 9 节 · 收尾:那到底各自擅长什么 | 回到最开始那句:不比谁强,只说由谁决定 |
主线全讲 109 分钟(这一版没有可跳的岔路:
§4 已经从「岔路」提回主线,它是第 6 节那组实测的前置)。
⭐ 这不是一小时的课:2026-09-03 拍板不砍内容,
要一个准点的时段就按这个数往上取整要。真只给 60 分钟,用下面那份应急清单。
讲不完的时候先砍哪个
顺序是固定的,临场别现想:
① 第 4 节压成两句(不要整节跳 —— 它是第 6 节那两个数的前置,
但左右两张图各指一眼、把「一跳 vs 六跳」说掉就够,−4);
② 第 6 节只留那张两个数的实测图,边界那张压成一句(−4)
—— ⛔ 但 6.2 那段 MFU / HFU 不在可砍范围内,它是分子那一侧的口径,
砍了下面「边界」图的第 ③ 行就没词可讲;
③ 第 3 节前三小节各砍一张图(−4)。
⛔ 第 3.6(FlashAttention 并排走那一段)和第 5 节,任何情况下都不能砍 ——
砍掉它们,这节课就退回成「两块硬件参数对比」,
而那正是开场第一句话说了不做的事。
开场:两道热身题 + 两种出身
⏱ 8 分钟讲稿
好,我们开始。这一讲叫「TPU 与 GPU」,但我先说清楚, 这门课不给你念参数表。参数表恰恰是我们第一个要拆穿的东西。
⭐ 往下滚一点,停在那道黄色的选择题上。 四个选项先别点 —— 点下去答案就出来了。不过在开始之前,先回一道上一课的题,热热身。 上一课我们拆的是 DeepSeek V3,总参数 671B,每个 token 只激活 37B。 那 MoE 到底省下了什么?
⭐ 这里一定要真的等。举手、或者让他们喊出来都行, 但要等到有人给出答案再点。 —— 这道题的作用不是考知识,是把房间从「听」切到「答」。 自己念完自己点开,这三十秒就白花了。答案是「省算力,不省显存」。算的时候只走 37B 那条路,算力是真省了; 可 671B 的权重一个字节都不能少放 —— 你不知道下一个 token 会挑中哪几个专家,所以全都得在显存里待着。
点开答案,指着最后那一段(⭐ 那行)念。但我真正想让大家听见的不是这个答案,是它背后那件事 ——
显存和算力是两样东西,会各走各的。上一课整整一课都在分开算这两笔账。
而这一课要加的是第三样:带宽。
—— 而且待会儿会看到,这三样里真正决定一个算子快不快的,常常不是前两样。
还有一道,这道纯猜。这门课后面会把两颗芯片拆开、一层一层往里看。 在开始之前先猜一下 —— B200 上那个矩阵单元叫 Tensor Core, v7 上那个叫 MXU,干的是同一件事。整颗芯片上,两边各有几个?
⭐ 这道一定会有人猜错,而且错得很整齐 —— 大多数人会觉得两边一个量级。让他们错,这道题的价值全在这儿。B200 是 592 个,v7 是 4 个。份数差 148 倍。 —— 但真正该惊讶的是下一句:这两颗一拍能做的乘加总数只差 15%。 同样一份算力,一边切成 592 小块,一边切成 4 大块。
⭐ 念完就停,不要解释「为什么」。 指着答案区最后那一行念 —— 数字给你了,「为什么」先欠着。 ⛔ 这里最容易犯的错是当场展开。 一展开,这道题就从钩子变成了剧透, 第 3 节那二十四分钟就没人有问题了。 —— 要的效果是他们揣着「为什么好几百对个位数」听完前两节。 到第 3 节 3.7 那笔账的时候,会有人自己喊出来。整节课就两个问题,一条路线。第一个问题,这两块硬件在硬件层面 到底哪儿不一样。第二个问题,我们拿同一个算子,从头到尾跑一遍, 看那些不一样分别在哪一步冒出来。那个算子就是 FlashAttention。
往下滚一屏,停在第 0 节那张图。整张图只有两个框, 不用找别的东西 —— 这一讲要做的就是把这两个框说透,然后立刻往下走。现在看第一张图。全课只有这一个前提,就这两个框。
左边 GPU,出身是图形处理器。它从第一天起要伺候的, 就是一大堆互不相干的小任务 —— 形状不知道,访问模式不知道, 控制流也不知道。所以它的芯片面积,大半花在「应付各种情况」上。 cache 层次、warp 调度器、海量并发线程,全是为了这个。
右边 TPU,出身是为矩阵乘定做的专用芯片。它从第一天就只服务神经网络。 形状是规则的,访问是可预测的,控制流是静态的。 所以那一整套「应付各种情况」的电路,它一样都不用做。
⛔ 这里最容易顺嘴说错,而且我自己说错过: 别说「省下来的面积全给了计算单元」。省下来的既没变成算力,也没变成片上存储 —— 第 3 节下半会拿两张表把这两条都堵死:乘加总量 GPU 反而多 16%, 片上 SRAM GPU 多 73%。
那它省下来的到底是什么?—— 是复杂度本身。 没有 cache、没有调度器、布局在编译期就定死; 同样多的算力,它只切成四大块,而不是五百九十二小块 —— 少了一百四十八套控制电路。
⚠️ 台下多半会接一句「那是不是更省电」。 这一问有官方答案,而且很硬 —— 但答的时候要带边界。可以答,念两样就够。第一样是 TPU 初代论文里的原话 —— 它把省掉的东西一口气列完:没有 cache、没有分支预测、没有乱序执行、 没有多线程、没有上下文切换。然后下一句是: 「Minimalism is a virtue of domain-specific processors.」
⭐ 第二样是**一个数**,比什么形容词都管用: 「那颗 die 上,控制逻辑只占百分之二。」数据缓冲三成七、计算三成、I/O 一成 —— 控制,两个点。 论文紧接着写:这一块在 CPU 和 GPU 上大得多。 ⛔ 然后必须刹车,三条边界一条都不能省:
一,那篇论文比的是 2015 年的 TPU v1 对 K80 —— 那一代省下来确实换成了算力,同样一颗小 die 塞了二十五倍的 MAC。 而到 v7 对 B200,第 3 节下半会数出来 GPU 反而多百分之十六。 同一个机制,十年之间结果反过来了。
二,Ironwood 官方那个每瓦性能翻倍,比的是上一代 TPU,不是 GPU。
三,良率和可靠性 —— 没有出处,不说。 官方讲的是「确定性执行」,那是尾延迟,不是可靠性,别混。 停一拍。下面这两句是这一讲要背下来的。
一个是「不知道你要跑什么,所以处处留一手」; 另一个是「早就知道你要跑什么,所以一手都不留」。
不用滚了,就停在这两个框上说下面这段。那么这门课要讲的是什么呢。两边处处都不同 —— 但这些不同,全是同一个决定的后果。
哪一个决定?就是把一块数据从 HBM 搬进片上的时候,谁来安排。 GPU 那边有 cache 在运行时替你猜;TPU 那边没有,编译期就排死了。
形状要对齐到 8 乘 128、一条指令吃多大、kernel 谁来写、 连一台机器要付什么代价 —— 全都是从这一处长出来的。
这里语速放慢。这句话是整节课的论点, 后面每一节都在给它找证据。说太快,学员会当成一句口号听过去。开课前还有一句话我要说死:这门课不比谁强。 两种出身各自都是当年的正确决定,代价也各自都还在。 所以整节课你不会听到「谁更好」这种句子, 只会听到「这件事在这一边由谁决定」。
根立完了。下面直接进第 1 节 —— 它只做一道除法,很快; 做完那道除法,第 2 节就开始拆硬件。
可能被问到
说「只有一个成因」,会不会又太一刀切了?
现象上确实是十几处,说「一个」指的是因果上的一个。 cache、形状对齐、kernel 谁写、互联怎么连 —— 这些都是真的不一样, 但它们不是并列的十几个事实,是同一个决定往下长出来的十几个后果。
这句话在开场是一个论点,不是一个已证的结论。 可以坦白说:「你现在完全可以不信,第 3 节和第 5 节我会分别验证它。」 —— 这样比硬撑更有说服力,而且给了他们一个听下去的理由。
TPU 真的一点 cache 都没有?那 VMEM 是什么?
准确的说法是:TPU 没有「硬件自动管的缓存」那一层。 片上存储是有的,而且不小 —— 但它是软件管理的 scratchpad: 什么时候搬进来、放哪儿、什么时候倒掉,全部由编译器在编译期写死, 运行时没有任何东西会替你做决定。
这个区别正是本课的主线,所以被问到是好事,顺势就能接:
「对,缺的不是容量,是『谁做决定』。」
⚠️ 但别在开场展开讲 VMEM 有多大、分几块 —— 第 3 节有专门的图。
为什么拿 FlashAttention 当主角?换个 GEMM 或者跑一整个模型不行吗?
因为它同时满足两个很难同时满足的条件。
一是单卡就跑得完 —— 不用把卡间通信拽进来, 故事能一条线走到底(这也是第 4 节被画成岔路的原因)。
二是它一次压到四件事:访存、形状、片上容量、kernel 怎么写。 纯 GEMM 太干净,压不到后面三件;跑一整个模型又太杂, 每一步都在换话题,最后什么都没讲透。
第 4 节为什么画成岔路?卡间通信不重要吗?
重要,但它不在这条主线上。判据很硬: 第 4 节的正文里 FlashAttention 出现 0 次(grep 过的)—— 它单卡就跑完了,根本撞不到卡间。
与其让人读到那儿觉得「怎么突然跑题了」,不如在地图上就标成岔路。 L200 里这一节是折叠的,主线时间不够可以整节跳过, 对第 5 节完全没有影响。
别讲什么
② 别讲两家的历史年表。 出身只需要两句话(「不知道你要跑什么」/「早就知道」), 多一句都是在花时间。
③ 别在这儿解释 MXU 是什么、warp 是什么。 第 3 节有八张显微镜图专门拆。 开场做术语扫盲是最贵的一种跑偏 —— 一开口就是十分钟。
④ 别说「谁更好」。 一旦有人把话题带到选型,直接用课件上那句挡回去: 「这门课只回答『这件事由谁决定』。」
如果开场发现台下背景很杂,宁可砍掉出身那两段的细节, 也要把「处处都不同,但全是同一个决定的后果」这句说完 —— 没有那句话,后面每一节都会变成孤立的知识点。
⚠️ 这句话图上没有,它只在你嘴上、和课件那个绿框里 —— 更不能漏。
先定那条分界线:两边的胃口一样大
⏱ 10 分钟⭐ 而且要在节末说清这条线是干什么用的:判一次算子融合值不值得做。 全课那条线的名字,在这一节出现。
讲稿
上一段我说了「只有一个成因」。但在比任何东西之前,得先知道这台机器上「算」和「搬」的兑换比是多少。 这一节就干这一件事 —— 把那个数算出来,不下结论。
两边的官方规格表都给了两个数:每秒能算多少次,和每秒能搬多少字节。 我们把它们除一下。这个比值的意思很实在 —— 搬一个字节要花多少时间,那段时间里这块硬件能算几次。
这句话的方向特别容易讲反,慢一点说。 不是「算 312 次要配 1 个字节的原料」,是「搬 1 个字节的工夫,机器能干 312 次活」。 所以它是一条最低要求:你的算法每搬一个字节,配套得算够 312 次, 才刚好把算力喂饱;算不够,多出来的算力就在那儿空等。 停在那张两栏对照图(fig1-4)。不要急着往下滚, 让数字在屏幕上待一会儿。左边 TPU v7,一颗芯片 2,307 TFLOPS,带宽 7.37 TB 每秒 (= 8 × 921.6 GB/s;本课统一用 7.37,别处写过的 7.38 / 7.4 都是同一个数的取整)。 除出来 312.9。
右边 GB200,NVL72 里那一颗,2,500 TFLOPS 配 8.0 TB 每秒。 除出来 312.5。
这里停死。数字念完不要马上解释, 让台下自己反应过来这两个数是一样的。这是第 1 节唯一的爆点。⭐ 具体怎么停(2026-09-10 口述后补):念完「312.5」就闭嘴, 心里默数两拍再开口。「停死」只写了要停、没写停多久, 而临场默认动作是接着往下讲 —— 不写出做法的指令, 到台上就等于没写。(同型教训:外传那条 XLA 报错, 讲义只写「念出来」,可整句英文根本念不出去。)
两家公司,两套架构,两种设计哲学 —— 比值撞在小数点后一位上。 换成 FP8 也一样,625.8 对 625.0。
⛔ 这里千万别说「相对差万分之三」。 那个第三位是假的 —— GB200 那个「8 TB/s」官方没写口径, 十进制和二进制两种读法本身就差 2.4%。要说的是这一句:「两家、两套架构、两种哲学 —— 算力除以带宽,落在同一个量级上。」 惊讶感一点不减,而且这句经得起追问。
⛔ 这一句是给你自己的判断依据,不要念出来(2026-09-10 口述时 我把它念出去了)。判据:凡是解释「为什么这么讲」的句子都属于讲义, 不属于讲台 —— 念出来会把听众从内容拉到你的方法论上。 指主线上那条蓝线(「同样 8 颗 24 GB 的 HBM3E, 带宽为什么不一样」)。这一句主动讲,别等人问 —— 它每次都会被问。 ⭐ 一句话答完:「HBM3E」只说了代次,没说档位。
每颗 stack 一千零二十四根数据线,带宽就是这个宽度乘以每根引脚的速率。 而这一代从八点零到九点六 Gbps 都叫 HBM3E —— v7 跑七点二,GB200 跑七点八上下。差别全在引脚速率。
再补一句最要紧的:用哪一档是买方挑的,按功耗、散热、可靠性挑, 不是芯片能力的上限。 ⭐ 先补一句口头过桥,别硬接(2026-09-10 补): 引出 HBM3E 那问用「说到这儿,有个问题每次都会被问,我主动讲」; 答完转到下面这句用「然后是这一节我最想说的一句话」。
原来这两处是两条舞台指示直接相邻,中间没有可念的句子 —— 台上会出现一个突兀的跳转。
⭐ 然后这一问就把这门课的判据打回到自己头上了。
⭐ 然后把那句最值钱的说出口:「而『这个数是二进制还是十进制』, 正是第 6 节要教的那四句问法之一 —— 它现在打在我自己的招牌数字上。 那四句不是拿来审别人材料的,是先拿来审自己的。」
但接下来这句必须跟着说,不能省:「都落在 312」是真的, 「为什么都落在 312」是我的推断,没有任何一家这么解释过。 而且它挑型号 —— 换成 HGX 板上那颗 B200,就是 281.3,差 11%。
所以我们后面每次拿 312 当判据,用的都是那个带限定的版本。 往外引的时候不能把限定甩掉只留数字。
往下滚一屏,图还没完 —— 下半段有三根长短不一的条。 只念不解释,二十秒。顺手说一句:刚才那个除法,其实每一层都能做一次。 HBM 是 312,L2 是 119,共享内存是 64。 —— 越靠近计算,这条线越低。
这里不要展开。台下现在还不知道要它干嘛, 解释了也存不住。本节最后讲融合时会回来用它,那时候才有落点。⚠️ 但要念一句前向指路:「先记着,这一节最后会回来用它。」 (2026-09-10 口述时补的)「只念不解释」如果什么都不交代, 台下会当成一句没头没尾的插话直接丢掉 —— 钩子必须说清在哪儿收,否则它不是钩子,是噪音。 同一条判据在外传那边也踩过:X1 埋的钩子写错了在第几节收。 回到那两个面板 —— 还是同一张图,不用换。
比值一样,还有一种可能:分子分母凑巧成了比例。也不是。 面板上四个数就摆在那儿:算力 2,307 对 2,500,带宽 7.37 对 8.00 —— 两项都在 8% 以内。
这句是这一节真正的收获: 不只是比值一样,分子和分母各自也几乎一样。 凑巧成比例这条退路,堵死了。顺一句规格表:十行并排看每行都一样重,但值得记的只有一类 —— 一边有、另一边整个没有。TPU 没有硬件自动管的片上缓存那一层, GPU 没有可编程的专用协处理器那一层。缺的不是容量,是「谁做决定」。 —— 那两层具体长什么样,第 3 节有专门的图,这里一个字都不要多说。 完整十行折在本节最后,回查用的手册,现在不展开。
滚到「这条线怎么用」那张三栏图(本节第二张图), 先停在第 ① 栏。 这一段是本节时间的第二个重点,别当收尾一带而过。线定出来了,但光有 312 没用 —— 它是硬件那一侧的数, 你还得会算算子那一侧的数,两个数才能比。
算子那一侧的数叫算术强度,算法只有一句话: 数它搬几个字节,数它算几次,除一下。
这句是这一节最实用的一句,说慢一点。 它比 312 本身更值钱 —— 312 会随硬件换代变,这个算法不会。 滚到「这条线怎么用」那张三栏图, 先停在第 ① 栏。算式就印在图上,你只要念着走。我们当场量两个。
第一个,向量加。y = a + b,bf16。
读 a 两个字节,读 b 两个字节,写 y 两个字节 —— 一共搬 6 个字节。
中间做了几次运算?一次加法。所以强度是 1 除以 6,0.17。
跟 312 比 —— 低了一千八百多倍。
这意味着什么?这行代码在 GB200 上把 8 TB 每秒的带宽全部跑满, 也只有 1.3 TFLOPS —— 那颗芯片峰值是 2,500。 用掉万分之五。算力再翻十倍,它一秒都不会快。
这里可以停一下问:「所以买更贵的卡, 能让这行代码变快吗?」让他们自己答「不能」。 移到第 ② 栏。第二个,矩阵乘。N 乘 N 的方阵,bf16。 乘加次数是 2 N 的三次方,搬进搬出三个矩阵是 6 N 的平方 字节。 约一下 —— 强度等于 N 除以 3。
那要多大才够到 312?边长接近一千。
反过来说:边长 256 的矩阵乘,强度只有 85,还在带宽那一侧。 —— 「矩阵乘就是算力受限」是个错觉,它得够大才算数。
这一句一般会有人抬头。它推翻的是一个 几乎所有人都默认成立的常识。 同一张图,移到第 ③ 栏。 这四十秒是全课那条线的起点,别赶。到这儿可以给这门课真正的主角起名字了。
刚才那两行算术其实说的是同一件事:一个算子快不快, 取决于它每搬一个字节做多少次运算。 那想让一个慢算子变快,就只有一条路 —— 把分母做小,少搬。
而少搬最有效的办法,是把连着的几个算子合成一个 kernel, 让中间结果根本不落 HBM。这就是算子融合。
这句停一下:它一个 FLOP 都没省。 分子分毫不动,变的只有分母 —— 很多人以为融合是「少算了」,不是。所以融合在这条线上的效果,是把算子往右推 —— 从带宽那一侧,推到算力那一侧。
还是第 ③ 栏,指下半那个黄框(312 → 64 那两个数)。 这一段是补上去的,因为上面那句话会被听岔。刚才我说「中间结果根本不落 HBM」。这句话很容易被听成 「中间结果消失了」。—— 它没有消失。
它被塞进了片上暂存:GPU 上是那块最多 227 KB 的共享内存, TPU 上是 VMEM。HBM 那一层确实少搬了,可那批数据总得在片上落脚。
这句停一下:搬运没有消失,是换了一层。 刚才那三根条,用处就在这儿。而片上那一层有它自己的线 —— 刚才念过,是 64,不是 312。 所以融合完要重新对一次账:拿新的强度跟 64 比。
这里必须多说三句,否则上一句自相矛盾: 门槛从 312 降到 64,是降不是升。那为什么还会撞墙?因为动的不只是门槛,还有分母。 融合把 HBM 那一层的流量转成了片上流量 —— 片上搬的字节数能涨几十倍, 而门槛只降了 5 倍。撞墙不是因为门槛变高,是因为分母变大。
台下常会接一句:「那干脆留在寄存器里不就完了?」 —— 大多数时候做不到。一个 SM 的寄存器堆总共 256 KB, 跟共享内存一边大;而且你多占一份,能待命的 warp 就少一批。 这笔账第 3 节那张 SM 拆解图上有。
1.2 方阵只是特例 —— 强度由最小的那个维度说了算(图 1-8)
滚到 图 1-8。⏱ 这一段 3 分钟, §1 相应地从 10 分钟变成 13 分钟。⛔ 三问全对,而且它暴露了课件上一条断掉的线: §1 只讲了方阵,§2 直接跳到 Flash,§3.4 又单说收缩维 —— 三处讲的是同一个 128,但从没接起来过。
⚠️ 所以这一段只要三分钟,但不能跳:它是 §1 → §2 → §3.4 的接缝。
刚才那两个例子里的矩阵乘是方阵,三边都是 N,约出来是 N/3。 但真实的矩阵乘是 M×K · K×N。那强度跟哪一个有关?
还是同一道除法。算的次数是 2 M N K, 搬的字节是 2 乘以 MK 加 KN 加 MN。约完是一个很干净的形式 —— 强度的倒数,等于一比 M 加一比 N 加一比 K。
「倒数相加」这四个字要单独说一遍,它是这一段的钥匙: 倒数相加,意味着谁最小谁说了算。所以「K 越长强度越高」是对的,而且是单调的。 但它有天花板 —— 图上第 ② 栏那五行就是拿来看这个的: M 和 N 都固定在 512,K 从 64 拉到一百万,强度从 51 涨到 256 就不动了。 上限是 MN 除以 M 加 N,正好 256。
K 救不了 M 或者 N 太小。最狠的一个特例:batch 等于 1 的 decode, M 就是 1,于是强度上限就是 1 —— K 和 N 再大都没用。 这就是 LLM 解码怎么都卡在带宽上的那一行算术。
现在把注意力代进去。QK 转置的两个外维都是序列长 S, 而收缩维就是 head_dim。多数模型 head_dim 是 128, 于是强度约等于 128 —— 低于 v7 那条 312,也低于 v6e 那条 560。
⭐ 这句要说出口,它推翻一个默认成立的常识: 「注意力是矩阵乘,当然算力受限」是个错觉 —— 不做融合的话它落在带宽那一侧,而卡住它的正是 head_dim。那 Flash 改的是什么?方向先给,完整那一笔下一节第一张图会推: 它一个 FLOP 都没省,改的是分母。 中间那个 S 乘 S 不再落 HBM,过 HBM 的只剩 Q、K、V、O —— 强度变成 S 除以 2。拿 Wan 那个 S 等于 75,600 代进去, 就是 37,800。
⭐⭐ 这一段真正的落点在最后这句,别赶:同一个 128,咬了两口。 一口是这里 —— 收缩维只有 128,强度上不去; 另一口在 §3.4 / §3.6 —— MXU 是 256 见方,只喂进 128, 方阵一半是空的。
前者问「要不要等数据」,后者问「算的时候算得满不满」。 两件事,同一个数字。
图 1-8 说注意力强度 ≈128,可 §2 的图 2-4 写的是 64。 两个都对,分母不一样 —— 128 是单个矩阵乘孤立看(A/B/C 各过一次); 64 是朴素注意力整体看(中间矩阵 S 和 P 各走一个来回,分母多一倍)。
⭐ 图 1-8 底下那条黄带就是写这个的,指着念一遍就行。 不说破的话,细心的人翻到 §2 会以为其中一张画错了。
好,线定出来了,也会用了,也知道它是干什么用的。 第 1 节的全部产出就一样 —— 一个数,和一个算它的方法。 数会随硬件换代变,方法不会。
⛔ 2026-09-05 起课件上那个「产出就两样」的框已经删了 —— 原来第 ② 条讲「一边有、另一边整个没有才值得记」, 那条判据在 §1 是悬空的(本节根本没推出它),而它在 3.5 / 3.6 用到的地方 上下文里自明,不需要预先定义。 台上也别补讲,讲了就是把删掉的东西又搬回来。—— 接下来每一节都在这条线上。第 2 节看融合前后差多少: 同一个注意力,两种写法 FLOP 一模一样,强度差一千倍; 第 3 节看它在两边分别怎么落地; 第 5 节回答那个真正的问题 —— 这件事该由谁来决定。
可能被问到
这个 312 是 BF16 的,跑 FP8 呢?判据要换吗?
要换,换成 625。图上两行都算了:TPU 625.8,GB200 625.0。
道理很简单:算力翻倍,带宽没变,所以比值也翻倍 —— 而且两边都翻倍,所以「撞在一起」这件事在 FP8 上依然成立。
判据要按你实际跑的精度取。拿 BF16 的 312 去判一个 FP8 的算子, 会把本来算力受限的判成带宽受限。
为什么 TPU 那栏写「每 chip」?我看日志里的数对不上。
因为 v7 是一颗 chip 里有两个 device,而框架日志一律按 device 报。 所以看日志换算:per-chip = 日志上的 per-device × 2。
这里两边都按「一颗封装」的口径比 —— TPU 一个 chip 对 GB200 一颗 GPU, 这样才是同一个东西在比。口径写在图里那一行小字上,讲的时候可以指一下。
⚠️ 这个 1 比 2 还会影响 batch:per_device_batch_size 里的
「device」在 v7 上是半颗芯片。但这里不要展开,是另一门课的事。
算力除带宽这个数,有正式名字吗?
有 —— 它是 Roofline 模型里屋脊点的横坐标,也叫机器平衡点。 算子那一头对应的量叫算术强度,单位一样,所以能直接比大小。
提一句名字就走,别画 Roofline 图。画那张图要五分钟, 而第 2 节那张访存图讲同一件事讲得更具体。
⭐ SM 有 148 个 —— 共享内存那条 64,是不是把所有 SM 加起来算的近似?
先肯定他 —— 这是全课最会算账的一个问题。 但答案比他以为的更硬:那个数没有加总,它本来就是按一个 SM 算的。 分子 8,192 FLOP、分母 128 字节,都是「每 SM 每周期」。
那换成全芯片口径会不会不一样?不会,而且这不是近似,是恒等。 全片算力 = 148 × 每 SM × 时钟,全片共享内存带宽 = 148 × 128 B × 时钟 —— 148 和时钟同时站在分子分母上,直接约掉。 课件底下那行反推校验走的就是这条路:2,500 ÷ 64 = 39 TB/s 全片, 再除以 148 × 128 得到时钟 2.06 GHz —— 这是 GB200 那颗(§1 全节用的都是它)。
⛔ 这里原来写的是「跟 B200 对得上」,标签错了
(2026-09-10 复核 §3 时查出来)。2.06 GHz 属于 GB200:
2,500 ÷ (606,208 × 2) = 2.062。
而 HGX 上那颗 B200 是另一组数:峰值约 2,250 TFLOPS、
第三方 boost 1.83 GHz(按 8,192 FLOP/SM 反推约 1.856)——
代进同一条式子得 ≈ 35 TB/s,正是 §3.2c 那一行。
⭐ 所以 §1 的 39 和 §3.2c 的 35 不是打架,是两颗不同的芯片;
被问到就这么答。反过来,台上千万别说「2.06 跟 B200 对得上」 ——
本课自己的 GPU 显微镜把 B200 钉在 1.83/1.856,会当场被戳穿。
⚠️ 但另外两条线(312、119)确实是全芯片口径。 三条摆在一起能比,靠的正是上面那个约分 —— 这一点值得当场说破, 否则细心的人会一直觉得这张图偷换了口径。
HBM 那 8 TB/s 是 148 个 SM 抢的一份共享资源 —— 你一个 SM 拿不满,148 个一起读则人均只剩几十 GB/s。
共享内存那 128 B/周期是每个 SM 自己的 —— 一个 SM 跑是这么多,148 个一起跑还是每人这么多,总量线性放大。
—— 所以融合的好处不只是「门槛从 312 降到 64」, 更是从一份要抢的资源,换到一份自己就有的资源。
⛔ 顺带纠一个方向:门槛低是好事 —— I* 越小,算子越容易越过它变成算力受限。 那为什么融合过头还会撞墙?不是门槛变高了,是分母变大了: 门槛只降了 5 倍,而融合把 HBM 流量转成片上流量,片上搬的字节数能涨几十倍。
算术强度是算子的属性,还是硬件的属性?
算子的。312 是硬件那一侧 —— 「搬一个字节的工夫,我能算几次」; 算术强度是算子那一侧 —— 「我每搬一个字节,实际算了几次」。 同一个单位,所以能直接比大小。
但有个地方特别容易搞错,而且它正好是下一节的引子: 算术强度不只由数学决定,也由你怎么实现决定。
同一个注意力,朴素写法和 Flash 写法,FLOP 一模一样,强度差一千倍 —— 差别全在搬了多少字节。这就是第 2 节要讲的事, 被问到就顺势预告一句,别在这儿讲完。
⭐ 312 的分子只取了矩阵单元。CUDA Core 和 VPU 就不算了吗?
这是这一节最好的一个问题,一定会有人问 —— 先肯定他。 312 确实是「矩阵那条 roofline」的屋脊点,向量单元没算进去。
先答为什么这么取:本课要判的算子(注意力的两次矩阵乘、MLP 的大矩阵乘) FLOP 几乎全在矩阵单元上,softmax、归一化那些是零头。
再给量级:GB200 那 18,944 个 CUDA Core,
每周期 2 FLOP、约 2.06 GHz,合起来 78 TFLOPS ——
矩阵那边 2,500,差 32.5 倍。TPU 那边结构上一样:
两个 MXU 一拍 262,144 次乘加(每个 MXU 131,072),VPU 一拍 1,024 个元素。
⚠️ 那个时钟是推出来的,讲的时候要说出来 ——
NVIDIA 没公布 GB200 的 boost 频率。
然后承认他是对的:严格讲,判 element-wise 算子该用向量那条 roofline, 屋脊点是 9.6 不是 312。
最后收回来:结论不翻。向量加强度 0.17,比 9.6 还低 58 倍, 照样卡在带宽上。换哪个分母都惨。
⭐ 然后把它变成一个收获,这才是这个问题真正的价值: 向量单元弱不是疏忽,是两边共同的设计选择 —— 算子一旦落到向量单元上,你输了两次:带宽受限一次,可用算力只剩三十分之一又一次。 这就是为什么必须做算子融合:融合不是为了少算, 是为了别让数据在这条又窄又弱的通路上来回跑。
课件里这一段折在第 1 节末尾,不要主动展开讲,被问到再翻 —— 它是分母之争,对本课的结论不影响。
两家参数为什么会这么像?是互相抄的吗?
这个我不确定,没有任何一家公开解释过。直说就行。
可以给一个标明是推断的说法:两边都在拿 HBM 带宽去配矩阵算力, 用的还是同一代 HBM,比值撞在一起并不奇怪。但这是我倒推的,不是出处。
⚠️ 这里特别容易顺口讲成因果 —— 「两边被同一批负载钉在同一个比值上」 这种话听着很漂亮,但它是从两个数字反推的,而且换个 SKU 就不成立了(281.3)。
别讲什么
② 别把十行表摊开逐行念。 它在课件里是折起来的,就是为了不念。 主线只要一句「值得记的只有一类:一边有、另一边整个没有」,十行是回查用的。
③ 别在 SparseCore、CMEM 上停留。 第 1 节只需要说「各缺对方一层」, 那一层具体是什么,第 3 节有专门的图。
④ 别把 312 讲成「TPU 和 GPU 是一样的」。 这一节证明的是 「参数表分不出它们」,不是「它们一样」 —— 这两句差得很远, 而且下一节就要打脸第一句。
⑤ ⭐ 别在这儿讲「怎么做融合」。 这一节只给它起名字, 四十秒 —— 什么算子能合、编译器怎么找融合机会、为什么有时候合不了, 一个字都不要说。这里是全课最容易一脚踩进去讲二十分钟的地方, 因为它确实是最重要的话题。但它的证据在第 2、3、5 节,不在这儿 —— 在这儿讲,就是拿没有证据的结论去换后面三节的时间。
重点一是 312.9 对 312.5 那个停顿 —— 念完留住三十秒不说话, 让台下自己发现。这是全课第一个「坐直」的时刻,赶过去就没有了。
重点二是最后那两行算术。很多人会把它当收尾一带而过,那是最亏的 —— 没有它,312 就只是一个背下来的数字;有了它,学员当场就能自己量任何一个算子。 「算力再翻十倍这行代码也不会快」和「边长 256 的矩阵乘是带宽受限」 这两句,一句都不要省。
重点三是最后那四十秒 —— 给「算子融合」这条线起名字。 它必须紧接在那两行算术后面说,不能挪到别处: 台下刚亲手算出「分母决定一切」,这时候说「所以办法就是把分母做小」, 是他们自己推出来的;换个位置说,就成了一句要背的口号。
这一讲只有一张图(312 那张)—— 2026-09-03 删掉了原来那张 「三格落点图」,它三格里两格是复述、一格在说「这里没信息」。 时间省下来给最后那四十秒(给算子融合起名字)。 收口,指课件最后那条蓝线: 「这一节到此结束,你手上多了一把尺子 —— 它只回答一个问题:这个算子卡在带宽还是卡在算力。」
然后立刻抛钩子:「下一节把它用在一个真算子上, 那个算子会给出全课第一个大数 —— 融合前后,强度差一千倍。」
⭐ 就是从这儿开始不同:同一次取数,一边路上 4 个决定,一边 0 个
⏱ 10 分钟开场那句「只有一个成因」,在这一讲第一次变成看得见的东西。
讲稿
上一节的结论是两边胃口一样大。这一节说的是 —— 吃法完全不同。
⭐ 第一个决策点要单独解释一句,别当术语滑过去。「coalescing 就是合并访存:一条 load 发出去,warp 里那三十二个线程 各算出一个地址,硬件得把它们合并成尽可能少的内存事务。」
再补一句量级:合得好不好,差八倍带宽 —— 而且只有跑起来才知道, 因为编译器不知道你的地址是怎么算出来的。」 ⛔ 这一讲最容易讲砸的方式:讲成「TPU 没有 cache」。 那是把一整套机制缩成一个零件 —— 而且第一个决策点根本不是 cache,是 warp 那三十二个地址能不能合并。
要交的是「4 对 0」,不是「有没有 cache」。
我们不看任何参数,就跟着一次取数走一遍: 从 HBM 里拿一块数进片上。这一路要做几个决定,每个决定是谁做的、什么时候做的。
先只讲左半边(GPU)。一个决策点一个决策点地指, 每指一个说一句「这个答案,编译的时候不存在」。左边 GPU。一个 warp 三十二个线程,各给出一个地址。 第一个决策点:这些地址能合并成几条 cache line?—— 跑起来才知道。
接着走到 L1,第二个决策点:命中还是没命中?再走到 L2, 第三个:命中还是没命中?没命中要换出一块,第四个:换谁出去?
四个。而且注意 —— 这四个我一个都没说「慢」。 红色的意思不是慢,是「跑起来才知道」。
切到右半边(TPU)。这边节奏要快,因为它没有东西可数。右边 TPU。编译期:XLA 决定每个数组切成 8 乘 128 的块。编译期:决定什么时候搬、 搬多大一块。编译期:算准 VMEM 装不装得下。然后运行时只剩两件事 —— DMA 整块搬过去,向量单元直接取。
路上零个运行时决策点。而且下面那一格是空的:没有 cache 这一层 —— 所以也就没有命中、未命中、替换这些概念。
这里停死,念图上那句红字:「四个,对零个。」 这是全课的中心,第 0 节那个论点在这一刻第一次落地。 念完不要解释,让它挂在屏幕上。那绿色是什么意思?绿的也不是「快」,是「写代码的时候就已经定死了」。
所以两边的分工从这里就定了。GPU 那一侧,把这几个决定交给硬件在运行时处理
—— 也就是交给了人,交给写 kernel 的那只手,你能做的是让硬件更容易猜对:
访问连续、避开 bank、手工 tiling。
TPU 那一侧,交给编译器在编译期算准 —— 也就是接受它的脾气。
GPU 靠运行时适应 —— 形状对不对都能跑起来;
代价是命中率要跑起来才知道,不好的时候只能反复试。
TPU 靠编译期算准 —— 形状提前定死,一个决策周期都不浪费;
代价是运行时没有补救手段,形状不对只能回去改代码。
⛔ 说到这儿就停,不要接「所以省下来的面积给了谁」 —— 那个方向本课撤回过,没有出处。
—— 后面第 3 节、第 5 节看到的每一样东西,都是这一句的后果。
⛔ lane / sublane 这一段不在这儿讲了 —— 它搬到了 3.3 开头(那儿才第一次真用上)。 这一讲到这里就收,直接滚到 2.3。 滚到 2.3,停在 Flash 那张对照图(fig2-3)。判据有了,坐标有了。现在挑一个算子跑到底 —— FlashAttention。 理由很实际:它是今天每个大模型都要跑的那一步, 而且它正好卡在 312 的错误一侧,所以两边都必须动手。
先记一个数。一个头,128K 序列,bf16,那个注意力分数矩阵是 整整 32 GiB。
给一个对照再停一下:一个 v7 device 一共只有 94.74 GiB 可分配 —— 装不下三个头。而模型有 128 个头。朴素写法是怎么走的?算出分数矩阵 S,写回 HBM;读回来做 softmax, 再写回去;再读出来乘 V。一个头,来回搬了四趟 32 GiB —— 而真正的乘加,只有那两次矩阵乘。
Flash 怎么做的?把 Q、K、V 按行切成块,一次只取一块。 这一块的 S 只在片上出现,不落地;softmax 改成 online 的, 只维护两个标量 —— running max 和 running sum;累加进输出块, S 当场丢掉,换下一块。
结果是:那个 seq 乘 seq 的矩阵,一次都没在 HBM 里出现过。
指第 ③ 栏。这一栏是这一节和第 3 节之间的桥。第 ③ 栏问的是「那一块该多大」。两边问的是同一个问题,答案交给不同的人。
GPU:kernel 作者自己定,预算是 228 KB 的 shared memory,
块大小是个可调超参 —— 调错了也能跑,只是慢。
TPU:编译器定,预算是 64 MiB 的 VMEM,但形状必须对齐 8 乘 128
—— 对不齐就补零,而补出来的零照样占 VMEM、照样过 MXU。
现在把这笔账放回上一节那条线上。
朴素:算术强度 64。它每搬 1 个字节只算了 64 次; 而搬这 1 个字节的工夫,硬件本来能算 312 次 —— 差了近五倍,算力有八成在空等,死死卡在带宽上。
Flash:65,536。同样的 FLOP,搬的字节少了一千零二十四倍, 直接跨到右边,卡在算力上 —— 而那才是该卡的地方。
⚠️ 这句要跟着说:65,536 是理想上界。真实的 Flash 会重复读 K 和 V, 到不了这个数。但结论不变 —— 它是从 312 的左边跨到了右边。
先别急着滚过去 —— 这张轴下面新加了一个绿框 「那它过了片上那道门槛吗」,两分钟,讲完再进橙框。刚才说 Flash「跨到右边」,跨过的是 HBM 那条线 —— 312。
可片上还有一条:共享内存是 64。
而融合干的事,恰恰是把字节从 HBM 挪到片上。 —— 所以那道除法得拿新的分母,再做一遍。
指绿框里那五行推导,一行一行念,不要跳。设块的行数 Br、列数 Bc,head_dim 是 d。
每走一步,从共享内存读进 K 块和 V 块,各 Bc 乘 d。Q 那一块一直待在寄存器里不动。
算的是两次矩阵乘,4 乘 Br 乘 Bc 乘 d 次。搬的是 4 乘 Bc 乘 d 字节。
Br 取 128,就是 128 对 64,过关。
Br 取 32,就是 32 对 64 —— 在片上重新变成带宽受限。
⭐ 然后把锚点给他们:真实的 FlashAttention kernel, 块行数一律取 64 或者 128。不是巧合,是这道除法逼出来的。
⚠️ 限定要说:这笔账只数了共享内存那一层, softmax 的向量运算和寄存器那一层都没算。它给的是量级,不是精确值。
⚠️ TPU 那一侧的分子是 1,153.5,不是 2,307 —— VMEM 是每个 core 一块, 分母是 per core,分子就得跟着取 per core。这就是那四句问法的第三句, 说错一倍。
最后那行小字(TPU 侧)照念,一个字都不要加。TPU 那一侧算法完全一样,分子取 1,153.5(per core),分母换成 VMEM 带宽 ——
而这个数官方没有公开,我们不推测。
能说的只有方向:片上带宽比 HBM 高一个量级,门槛就低一个量级,
落到几十这一档,和 GPU 那个 64 同一个数量级。
—— 再加上 TPU 的工作台是 64 MiB,卡住它的不是这条线,是 3.4 那个刀宽。
好,回头看一眼刚才发生了什么。Flash 做的事, 就是把「算分数 → softmax → 再乘 V」这一串合成了一个 kernel —— 这就是上一节起的那个名字:算子融合。
这句停一下:它连一次乘加都没有省。 图上第 ①② 栏是一模一样的。省的全在分母。所以到这儿,故事的前提齐了:判据 312,朴素是 64, 所以必须改写。
而真正的问题不是「要不要融合」 —— 是「这个融合谁来做」。Flash 这一次,是人写出来的, 一篇论文加一堆手写 kernel。第 5 节要问的就是:非得靠人吗。
可能被问到
GPU 也有 shared memory 啊,那不就是可编程的 scratchpad 吗?
是,而且这是这一节最该被问的问题 —— 先肯定他。 shared memory 确实就是软件管理的片上暂存,跟 VMEM 是同一类东西。
差别有两条,都得说:
① TPU 只有这一类,GPU 是两类并存。图上那四个决策点全在 global load 这条路上 —— 不管你用不用 shared memory, 数据都得先从 HBM 经过那条路。你没法把它绕开。
② 谁来管它。shared memory 归kernel 作者管, VMEM 归编译器管。这正是第 ③ 栏那句「同一个问题,交给不同的人」, 也正是第 5 节的题目。
⚠️ 别在这儿展开讲 shared memory 有多大、怎么分块 —— 第 3 节有专门的图。这里只说「绕不开」和「归谁管」两句。
bank conflict 为什么画成虚线的支路,不算进那四个?
因为 bank 是 shared memory 的分区机制,不在 global load 这条路上。 把它算进主路会让「4 对 0」这个对比失真 —— 而这个对比是整节课的中心,不能为了多一个数就模糊掉。
图上写了「走 shared 再加 1 个」,被问到就指那行小字, 说明我们是知道它、并且故意把它放在支路上的。
32 GiB 那个数怎么来的?
图上写了推导,指给他看,不要口算。 序列 128K 就是 2 的 17 次方,分数矩阵是 seq × seq, bf16 一个数 2 字节 —— (2¹⁷)² × 2 = 2³⁵ 字节 = 恰好 32 GiB。
「恰好」这个词可以强调一下:它不是估的,是正好撞在 2 的整数次幂上。
Flash 强度 65,536 是真的能达到吗?
达不到,而且这句必须我们自己先说,不能等人问。 真实的 Flash 分块之后会重复读 K 和 V,实际强度比上界低不少。
65,536 那个数的算法是「Q / K / V / O 各过一次」, 那是「一个字节都不多搬」的理想情况。
但结论一点都不受影响:判据是 312,就算实际只有上界的百分之一 也还有 655,照样在右边。这一节要的是「跨过去了」,不是那个具体数。
⭐ 省下来的搬运,是不是拿多算的 FLOP 换的?那多出来的算「有用」吗
这是这一节第二好的问题,而且它是我们自己招来的 —— 橙框刚说完「一个 FLOP 都没省」,听着像白赚。先承认:通常是要付的。
付的形式有三种,性质完全不同,分开说:
① 重算 —— Flash 前向没有(图上 ①② 栏一样),
但反向有:中间矩阵没存,反向得重新算一次。
② 补零 —— 形状对不齐补出来的零,占空间、过 MXU、什么都不产出。
三种里最亏的,连换来了什么都说不上。
③ 额外的向量活儿 —— online softmax 每块要重新缩放一次累加结果。
次数不多但落在向量单元上,数 FLOP 会低估它。
然后回答「算不算有用」:这个有正式口径,就叫 MFU 和 HFU ——
分子取模型数学上需要的叫 MFU,取硬件实际执行的叫 HFU,
重算和补零在 MFU 里一律不算有用。
⛔ 在这儿只说这一句,然后马上说「第 6 节我们会正经讲这两个词」。
—— 那一节讲的就是「两个数能不能比」,这两个口径是那节的正题。
在第 2 节展开,等于把第 6 节的内容提前讲了一半,两头都讲不透。
⭐ 最后把它接回那条线,这才是这个问题的价值。但有一句必须先说准:
算术强度的分子是硬件实际执行的 FLOP ——
硬件不认识哪些是重算。所以判在 312 的哪一侧,用的是含重算的那个强度。
于是天花板有两块,谁低听谁的:
有用吞吐 = min(峰值 × 有用比例,带宽 × 有用强度)
举个数:含重算的强度 600,一半是重算 ——
算力那块只剩 50% 峰值,带宽那块 300 ÷ 312 = 96%,取小的,只到屋顶线一半。
⚠️ 这时候带宽也没用满(只有 312÷600 ≈ 52%)—— 矩阵单元 100% 忙着,一半忙在垃圾上,带宽在旁边闲着。 这是「换过头了」的指纹。
所以规则:带宽受限区里多算的成本是零, 一越过 312 就变成按比例扣产出 —— 这是一道悬崖,不是一条汇率曲线。 换到含重算的强度刚好顶到 312 就停。
⛔ 别记成「强度不该超过 312 太多」 —— 有用的强度越高越好,没有上限(边长 4096 的矩阵乘强度 1,365、零浪费, 那是 100% 峰值)。该贴着 312 的只是你自己制造出来的那部分。
Flash 自己离那个坏情况很远:前向 2 次矩阵乘、反向 4 次, 它反向多重算 1 次 —— 7 比 6,有用比例 85.7%。 付 17% 的多算,换掉三个数量级的搬运。
课件里这一段是折叠的(橙框下面那条),不要主动展开 —— 被问到再翻。展开讲满是三分钟,而第 2 节一共只有七分钟。
那 TPU 上有没有 FlashAttention?还是说它不需要?
有,而且同样是必须的 —— 312 那条线两边一样, 朴素写法在 TPU 上一样卡在带宽上,那个 32 GiB 一样放不下。
差别在「那一块开多大」由谁决定:GPU 上是 kernel 作者调超参, TPU 上是编译器算,而且形状被 8 × 128 卡着。
这正是第 3.6 节整段要讲的事,所以这里只回答「有」,然后往前走。 —— ⛔ 不要在第 2 节讲 Pallas。
别讲什么
② 别展开 cache 的工作原理。 组相联、替换策略、TLB —— 一个都不要碰。这一节只需要「有这么一层,它的答案在编译期不存在」。
③ 别在 lane / sublane 上超过一分钟。 它是词汇表, 是为了第 3 节能听懂而提前发的。在这儿讲透 8 × 128 的代价,第 3 节就没东西了。
④ 别讲 online softmax 的数学。 「只维护 running max 和 running sum」 这一句就够。推那个递推式要五分钟,而它跟硬件差别毫无关系。
fig2-1 给三分钟,其中「四个对零个」那个停顿要留够。 这是全课的中心,第 0 节那句论点在这一刻第一次有了证据。 后面第 3 节十四张图、第 5 节整节,都是在给这一张图做注脚。
fig2-2 一分钟,词汇表,讲完就走。
fig2-3 和 fig2-4 合起来三分钟:32 GiB 一个停顿, 64 对 65,536 一个停顿,「一个 FLOP 都没省」一个停顿。
⚠️ 最容易超时的是 fig2-3 的第 ③ 栏 —— 那一栏信息很密, 很容易顺着讲进第 3 节的内容。它在这里的唯一任务是提出问题,不是回答。
⚠️ 第二容易超时的是最后那个折叠(融合要付什么代价 / MFU 对 HFU)。 它是折起来的,主线不讲 —— 但这个问题几乎一定会有人问, 而且问的往往是全场最懂的那个人。答法在「可能被问到」里写全了, 照着答,答完就走,别顺势开讲记账口径。
拆开看:同一份复杂度,两边放在了不同的地方
⏱ 42 分钟① 第 2 节那个「4 对 0」不是一句口号 —— 从整颗芯片一路拆到一条指令,每一层都能指出它的物理来源。
② 每一层的差别都要能指回一个具体的部件或一条具体的指令 —— 说不出物理来源的差别,这一节不讲。
一句话记法 这半节只交零件 —— 装回去和收账在下半节。
讲稿 · 3.1 先看整颗
前两节我们说了「两边胃口一样大」,也说了「吃法完全不同」。 这一节把两颗芯片真的拆开,看那个不同长在哪儿。
先看整颗。B200 一个封装里是两颗 die,中间用一条十 TB 每秒的桥连起来。 但你在 CUDA 里看到的是一个设备 —— 缝被藏起来了。
切到 TPU 那张(ms-t1)。两张图要连着指,它们是一对。TPU v7 也是两颗 die。但它如实暴露成两个 device,两半各有自己的 九十六 G HBM,地址空间互不可见 —— 想用对面的,你得自己写一步。
停一拍:「缝在明处,还是缝在暗处。」 这就是第 2 节那个决定在封装这一层的样子。藏起来的那一边,代价不会因为藏起来就消失 —— 你付了钱,只是不知道自己付了。跨 die 那一趟,带宽从二十一掉到十六点八, 数字在 3.2c 那张表里。
⚠️ 图上那个 131,072 先别急着往峰值上乘。
它数的是格子 —— 两个 MXU 一共两乘二五六平方个。
而每个格子一拍做两次乘加,这一步图上没有。
一次乘加本身就是一乘一加 —— a 乘 b 再加 c,一个周期里一起做完,算两个 FLOP。
所以一格一拍是四个 FLOP,不是两个。 这一步听岔了,后面整条链就差一倍。 在白板上写:1 次乘加 = 1 乘 + 1 加 = 2 FLOP;2 次乘加 = 4 FLOP。
把错算式念出来,别只说「会算错」: 131,072 乘 2 个 core 乘 2 FLOP 乘 2.2 GHz,得 1,153 —— 刚好是图头那个 2,307 的一半。少的就是「每格两次」那一步。
⭐ 如果有人当场追问「那正确的链呢」, 3.4 现在补了一句桥,可以先给他一句预告: 「图上那个是格子数,3.4 那个 262,144 是每 MXU 每周期的 FLOP —— 一除就是每格 4 FLOP,也就是两次乘加。」 不要在 3.1 就展开算,节奏会塌。讲稿 · 3.2 把一个核拆开
再往里一层。先看 GPU 有什么,再看 TPU 没有什么 —— 顺序不能反,反了就会以为 TPU 是个简化版。
一个 SM 里:warp 调度器、二百五十六 KB 寄存器堆、 二百二十八 KB 的 L1 加共享内存、记分板。 这些部件加起来只干一件事 —— 在运行时动态地藏住延迟和分支。
切到 TPU 那张(ms-t2)。指右边那五个空格。TPU 这边,这五样一样都没有。
但重点不是「少了五样」,是这五样其实是一件事。 要不要在运行时换任务跑?答「要」,你就必须同时驻留几十份上下文、 必须有东西挑下一个、必须知道谁的数据到了 —— 五样缺一不可。 答「不要」,五样同时失去存在的理由。
这句是这一小节的落点,值得停: 「有没有 cache」和「有没有 warp 调度器」不是两件事, 是同一个决定的两个面。—— 而第 2 节主路那四个红框,就是答「要」之后的必然产物。
讲稿 · 3.2b 「GPU 片上明明更多啊」
这里必须正面接一个反问:「你说 TPU 把面积给了暂存, 可 GPU 片上 SRAM 明明更多啊。」
—— 他是对的。我们把两边的片上 SRAM 一格不漏地列出来: GPU 二百三十一 MiB,TPU 一百三十四 MiB,GPU 多百分之七十三。 这一条别嘴硬。
承认完,再翻面。这个顺序很重要 —— 先认下来,后面那两个口径才听得进去。但同一张表可以读出三个方向不同的结论,看你在问什么。
第一个口径:一共多少 SRAM —— GPU 赢。
第二个:只数「软件当数据暂存用的那一行」——
TPU 一百三十对三十七,约三点五倍。
第三个,也是真正有用的那个:一个计算单元能当成一整块连续工作台用的,有多大?
—— TPU 六十四 MiB,GPU 二百二十七 KB,二百八十九倍。
为什么差这么多?因为 GPU 那三十七 MiB 不是一整块, 是一百四十八个互相看不见的小岛,每个二百五十六 KB, 而里面 L1 还占着一部分 —— 一个线程块能显式管到的最多二百二十七 KB。
这里停死。「二百八十九倍」这个数, 3.6 决定两边把块切多大的时候会直接用上。还有一句必须说:第一个口径虽然对,却最容易把人带沟里。 GPU 领先的那九十七 MiB,几乎全来自 L2 那一行 —— 而 L2 是这张表里唯一一格你没法安排的空间。 你不能把一块权重「放进 L2 待着」,只能发访存指令,然后希望它还在。
讲稿 · 3.2c 「那速度呢」
上面回答的是「有多大、归谁管」,还差一半:跑多快。 补上之后会撞出一个几乎所有人都猜错的结果。
大多数人默认「cache 总该快一个数量级吧」。B200 的 L2 不是。 HBM 八 TB 每秒,L2 只有二十一,两点六倍。
停一下,让这个数落地。二点六倍。 ⛔ 这里马上要补一句,不然「只有二点六倍」会被听反。 台下很容易得出「片上存储没什么了不起」—— 完全相反。二点六倍答的是一个很窄的问题:这一个字节,命中 L2 比去 HBM 快多少。
换个问法就完全不同:每一字节容量能供多少带宽?
—— L2 是用一千五百分之一的容量做到二点六倍带宽的,
两者一乘,它的带宽密度是 HBM 的四千倍。
⛔ 说完立刻拉回来,别让它盖过主线: 「但优化的时候还是只能用二点六倍那个数 —— 因为你搬的是字节,不是容量。」
所以 L2 的价值不在「它本身快」,在「省掉一趟 HBM 往返」。 命中了,你拿到二点六倍;没命中,你什么都没省,还多绕了一圈。 —— 这就是为什么「让它落在 L2 里就好了」不是一个能依赖的策略。
为什么慢?因为 L2 是全片共享的,数据得穿过片上互连才到得了 SM。 而共享内存四点三倍、VMEM 高约一个数量级 —— 它们就贴在计算单元旁边。
这句是这两小节合起来的落点: 「真正快的那两块,恰好都是软件说了算的那一类。」⚠️ TPU 那一行我们只给量级,不给数 —— VMEM 带宽是未公开规格,官方只给容量。 能说的到此为止:它比 HBM 高约一个数量级。 别往这儿填一个精确数,也别说成某个整数倍 —— HBM 带宽是公开的,写出倍数等于把那个未公开的数说出来了。
指最后那个 ⛔ 框「别把这两小节读成 TPU 赢了三局」。 这个框一定要念,它是整节课的护栏。那三条领先都是真的,但每一条都有对价。
① 暂存又大又快,代价是没有兜底 ——
GPU 猜错只是慢一点,TPU 编译器切错了就是真的停在那儿等。
② GPU 那两个三十七 MiB 看着「没用在数据上」,
它买的是别的东西:形状不规则、长度会变、分支不确定的活,照样能跑得不太难看。
—— 所以真正的结论不是「谁的片上存储更好」,是这一句: 两边都在处理同一个东西 —— 不确定性,只是把它放在了不同的地方。 GPU 花硅去兜住它,TPU 把它消灭在编译期。
讲稿 · 3.2d 「那等多久呢」
容量讲完了,带宽也讲完了。还差第三个轴:等多久。 而这个轴,是三个里面唯一一个 GPU 自己也答不上来的。
⭐ 先把问题原样抛给台下,别急着给数: 「一个 warp 要一块数据,L1 里没有,去 L2 查,L2 也没有,再去显存搬 —— 这一串下来要多久?」看图。L1 命中,三四十拍。L2 命中,二百七十三拍。一路到显存,六百五十九拍。 —— 同一条 load 指令,在源码里长得一模一样。
停一下。三十拍到六百五十九拍,二十倍。⛔ 这里要拦一个几乎人人都会犯的算法: 「L1 查一次,加 L2 查一次,加 HBM 搬一次」—— 不能这么加。 六百五十九是一次全没命中的端到端耗时,前面那两次扑空已经含在里面了。
⭐ 然后是这一小节真正要问的那句: 「每种情况耗时都不一样,那这东西怎么提前规划?」—— 答案很干脆:规划不了,所以 GPU 根本不规划。
它的三条对策,没有一条在试图算准。
第一,超量线程 —— 一个 SM 上驻着几十个 warp,这个卡住就切下一个。
延迟不是被消除的,是被别人的活盖住的。
第二,记分板 —— 硬件在运行时盯着操作数到没到,到了才发。
第三,缓存本身就是一台猜测机器 —— 它赌你还会再用一次。
TPU 走的是反方向:把不确定性从源头删掉。 没有硬件缓存,就没有缓存缺失,也就没有可变延迟。 VMEM 里的数在那儿,不是因为运气好,是因为编译器自己发的那条 DMA 把它放在那儿的 —— 而且它也知道该在第几拍等。
⭐⭐ 这一句是上半节的落点,念慢一点: 「GPU 用『总有别的活可干』来盖住不确定的延迟; TPU 用『把不确定性删掉』来避免它。 一个是统计学的答案,一个是确定性的答案。」—— ⭐ 紧接一句:「所以 TPU 没有 L2 不是缺功能。 缓存是一台猜测机器,而一台编译期就把每一拍排好的机器,不需要猜。」
⭐ 顺手送台下一条实用的: 这也解释了为什么两边的「调优」没有一样东西是重合的。 GPU 上你调的是「怎么让别的活足够多」—— 占用率、warp 数、访存合并; TPU 上你调的是「怎么让编译器排得开」—— 对齐、分块、双缓冲。 同一个词叫调优,动的旋钮一样都不挨着。
⚠️ 最后别让它变成「两边越走越远」: 「现在追峰值的 CUDA kernel,异步拷贝、显式 barrier、自己管双缓冲 —— 那就是在 GPU 上手写一个静态调度。 一旦你真的要峰值,猜就不够用了。」⭐ 这句给 3.5 那几个框埋了扣,讲到那儿会回收。
⚠️ 数的口径要交代一句:这组数是 H100 和消费级 Blackwell 上量的, B200 没有同类公开数据。量级和形状可以借,别当成 B200 的实测值。
讲稿 · 3.3 并行层级
先插一对词。下面要谈「一个线程其实是一条什么」,还有 3.4 那个「八乘一百二十八」, 都得先有 TPU 那边的坐标词,而这两个词的字面会骗人。
两步就能把画面立起来。
第一步,先只看一条:一个 sublane 就是横着的一条,
128 格那么长,512 字节 —— 这是 VMEM 的架构字长,不是凑出来的数。
第二步,再叠 8 条:就是一个向量寄存器,8 × 128 = 1,024 格。
两个词到这儿各归各位:lane 量的是长度,第几格,0 到 127;
sublane 量的是第几条,0 到 7。
—— 「对齐 8 乘 128」就是大白话:竖着凑够 8 条,横着凑够 128 格。
⚠️ 名字骗人的地方在这里:sublane 不是 lane 的一小段, 它是横跨全部 128 条 lane 的一整条。「sub」说的是第二维,不是更小。
这里可以轻松一点:「几乎所有人第一次都理解反, 包括我。」承认自己也搞反过,比强调「别搞反」有用。记法只有一句:最内维是 lane,次内维是 sublane。
然后是这张图真正要留的东西:XLA 故意把内存布局做成跟寄存器一样的形状。 搬进来就能直接算,一次重排都不用。
⛔ 这里有个说法我讲错过,别照老版本念: 「一行在内存里不再连续」—— 那不是代价,那正是目的。 一段一百二十八个元素正好铺满一百二十八条 lane,这是要的效果。」真正的代价是另一样:这个形状被钉死了。 三笔,都能点名 —— 换最内维要真搬数据(分块绑在最后两维上, 转置和某些 reshape 不是改一下步长,是物理搬运);不整除就补零, 补出来的零占 VMEM、过 MXU、还算进 FLOP;跟外部交接要重排。 这笔钱第 3 节要付。
⭐ 顺手接一句对照,它把这一段接回主线: 「GPU 那边的合并访存也想要连续 —— 但那是软约束, 没对齐照样跑,只是慢。这边的分块是硬约束 —— 形状不对,要么补零,要么插一个重排。一边罚款,一边直接不让过。」刚才少掉的是硅片上的部件。往上一层,编程模型里也会少掉东西。 同一个原因,第二次露面。
这一节我先归到一个问题上。
停一拍,这句要慢:「你写 GPU 代码的时候, 不知道这堆活会落在哪个 SM 上,也不知道什么时候落 —— 是硬件在运行的时候才决定的。」 白板上就写四个字:「运行时才定」。既然不知道,你就没法点名。写不出「让三号 SM 干这个」。
你能做的只剩一件事:描述关系。 哪些活必须待在一块儿,哪些落哪儿都行。
「必须待在一块儿」的那一批,就是一个 block。
为什么是「必须」?因为这批活之间要互相传中间结果。
而中间结果放在 SM 自己身上那块小内存里,不在外面。
所以一批活一旦落到某个 SM 上,就不许再搬走 —— 搬走了,中间结果就找不着了。
反过来,互相不用说话的活,落哪儿都行。全部加起来,叫 grid。
这两层是你说了算的。 你写代码,其实就是在说两句话:这些必须在一起,总共这么多。
硬件那边还有它自己的分组,理由完全不一样,是为了省电路。
取一条指令很贵。那就让三十二份数据共用同一条指令,一起算。 这三十二个绑成一捆,就是 warp。这个数你改不了,硬件焊死的。
到这儿四层齐了:底下两层硬件定死,上面两层你定。
剩下两层是后来补的,各被一个很具体的麻烦逼出来。
warp group —— 新一代的矩阵指令,要四捆一起发才凑得出形状。
cluster —— 相邻几个 SM 想直接够到对方那块小内存,不想绕远路。
而 TPU 那边,编译的时候就已经把一切定死了,不存在「不知道」。 所以这一整套语言,它一层都不需要。
⚠️ 顺手把一个陷阱按住:两边的层根本不在回答同一个问题。 GPU 那些层是用来描述「谁必须在一起」的,TPU 压根不需要描述这件事。 所以这张按问题排,不按名词排。
空的第一格:没有「一组线程」。GPU 有 thread block;TPU 没有这个中间概念。
空的第二格:没有「换一个跑」。GPU 有四个 warp 调度器、六十四个 warp 槽;
TPU 一个都没有。
—— 空的那两行,正好是 3.2 里少掉的那五样在编程模型上的投影。 硅片上少了部件,代码里就少了概念。
⭐ 三处「不对齐」里,现场只讲第一条,另外两条留给提问: 「一个 SM 上同时住着好几个 block,它们挨着,但互相看不见对方那块小内存。 第 5 节讲的『占用率』,量的就是这个 SM 还能塞下几批。」 滚到「一个 SM 拆开」那张四格图。再往里拆一层。「一个 SM」到这儿为止一直是个黑盒。 里面是四个格子,每个格子自己一套调度器和寄存器。 三十二是一个格子的宽度;一百二十八是四个格子各出一捆。
这里有个问题值得单独说:一个线程到底独占什么。
算的部件,它一样都不占。 CUDA core 也好、Tensor Core 也好,都不是分给谁的 —— 它们是流水线上的一个位置,哪一捆这一拍发了指令就用这一拍,下一拍换别人。
它真正独占的只有寄存器。一批活落到 SM 上就划走,跑完才还,中途从不搬动。
⭐ 这一条同时解释了两件事,两句连着说: 「正因为寄存器不搬动,换一捆上来才是零成本的; 也正因为独占,先卡住你的一定是寄存器不够,不是算的部件不够。」⛔ 三十二位、四个字节、上限二百五十五、按八取整 —— 这些全在折叠里,现场一个都别念。
滚到最后一张(三个都叫「线程」)。最后回到最底下那一格。一捆是三十二个线程, 每个线程在这一捆里有个编号,零到三十一。那个位置,就是一条 lane。
所以 GPU 上也有 lane,硬件上就是一条 SIMD 通道 —— 跟 TPU 那边的 lane 是同一样东西。
⭐ 这一节最后一句,说完就走: 「差别只在一件事 —— GPU 的 lane 有名字,TPU 的没有。 GPU 那条你能对它单独写 if;TPU 那条你写不出『第三十七号 lane 干点别的』。 所以 CUDA 说的线程,是一条有名字的 SIMD lane。」讲稿 · 3.3a 那一条 lane 在一次真矩阵乘里干什么
这个画面在没有 Tensor Core 的年代是对的,在今天是错的。
先讲对的那个年代。一个线程就是输出矩阵里一格的所有者 —— 它自己跑那个 K 循环,从共享内存读 A 的一段、B 的一段进自己的寄存器, 乘加,累加在自己手上,最后写回。搬运和计算,它都干。
⭐ 然后把它推翻,这一句要慢:「用上 Tensor Core 之后,矩阵乘指令是整个 warp 一起发的一条指令。 它的操作数散在三十二个线程的寄存器里,每人拿一小撮。 单个线程,自己算不出任何一个完整的输出元素。」
要具体就给一条:一条典型的指令里,每个线程交出 A 的八个数、 B 的四个数、累加器四个数。就这些。 ⭐⭐ 这一段的落点: 「所以『一个线程负责哪一格』这个问题,到 Tensor Core 上就失去意义了。 线程退化成两样东西 —— 一份寄存器的持有者, 和发那条指令的三十二分之一。」
—— 这才是「一个线程其实是一条 lane」的完整含义。 指下面那个「到底是不是 SIMD」的框。
这一问答案是分层的,两个层面结论相反。 硬件上是 SIMD —— 一个 warp 的三十二条 lane 共享取指、译码、发射, 同一拍执行同一条指令。编程模型上不是 —— 你写标量代码, 而且 Volta 起每个线程真有自己的程序计数器,可以各走各的分支。
⭐ 一句话收: 「SIMT 就是:用标量的写法,跑在 SIMD 的硬件上。 代价写在分支上 —— 真发散了,各条分支只能轮流走。」然后接回主线:「而这一整套,就是『靠运行时适应』要付的钱。 TPU 不做这个决定,所以它那边连『线程』这个词都没有。」 ⚠️ 被问到搬运再展开,别主动讲(会吃掉两分钟): 线程管不管搬运?管,但这一半正在退场 —— Ampere 之前线程自己搬、搬的时候被占住;Ampere 起 cp.async 绕过寄存器; Hopper 起 TMA 一个线程发一条描述符就搬一整块,另外三十一个不参与。
讲稿 · 3.3b 那套东西具体是什么(⚠️ 可整段跳)
底下那个折叠是机制(四分钟,全讲最容易超时的一段)—— 台下没人追问就别展开,直接跳到 3.4。 这一句念出口就行,别加料: 「记分板就是一张表,记着每条指令要的操作数到齐了没有; 调度器每一拍扫一遍,谁齐了让谁发射。」
—— 然后立刻接回主线:「TPU 那边这张表整个不存在。」
刚才说了两遍「GPU 把 lane 暴露给你,代价是一整套东西」。 这一小节就回答一句话:那一整套,具体是哪几样。 三样,各管一个很窄的问题。
三个问题先一口气报出来,报完再逐个展开 —— 学员知道一共几件、现在在第几件,才不会中途掉线:「一,谁现在能发指令;二,谁跟谁怎么对齐; 三,数据怎么在不占用线程的前提下就位。」 最后一件事,指着 3.3b 那张三代对照图。这是这一小节的钩子,别省。
线程管搬运的那一半,正在退场 —— 三代,一代剥一层。
Ampere 之前,线程自己搬:从全局到寄存器,再到共享内存。
搬的时候它就被占住了,什么也干不了。
Ampere 起,多了一条指令,让数据绕过寄存器直通共享内存 —— 线程不用当二传手了。
Hopper 起更狠:一个线程发一条描述符,就能搬一整块,
另外三十一个完全不参与。
—— 而线程被剥空之后,真正决定「一次能吃多大一口」的就不再是线程了, 是指令。那一维两边差 16 倍,3.4 会当场量给你看。
但先别跳过去。下一小节 3.3b 要先把账单打开。上面这一整套 —— 让你写标量、硬件替你凑成 SIMD、发散了替你排队、 再替你在几十批活之间来回换 —— 「靠运行时适应」这五个字,到底要花多少钱? 下一小节把它拆成三套具体的电路,一样一样看。
第一样,记分板。先送一个记得住的钩子: 这个名字是从一九六四年那台 CDC 6600 借来的 —— 那是第一台指令能不按顺序完成的机器,它里面那个部件的正式名字就叫 Scoreboard。 (为什么挑这个字、以及它和现在这块已经不是一回事, 折在课件里,别现场展开。)
回到 GPU。调度器每一拍要挑一个「就绪」的 warp 发指令 ——
「就绪」是谁判的?NVIDIA 把指令按一条线劈成两半:
编译器知不知道它要跑多久。
知道的那一半根本不用硬件管,编译器在指令里直接写「停几拍」。
不知道的那一半 —— 访存、特殊函数、矩阵指令 —— 才是记分板:
每个 warp 六个计数器,发出去加一、写回来减一;
等它的那条指令带个掩码,写明要等哪几号归零。
第二样一句话带过:SM 里一小组硬件 barrier,warp 陆续到达,数满全放行。 机制折在下面,回查用。
⛔ 报了「三样」就三样都要有下落 —— 说「第二样跳过」等于当众漏掉一件。只念这一条就够: 「在 barrier 上等着的 warp 不腾位子 —— 所以同步密集的 kernel,占用率不会因为『大家都在等』而变好看。」第三样值一张图。
滚到那张四行的数据通路图。只指中转那一列,别逐行念。四行,两头一模一样 —— 左边都是全局内存,右边都是共享内存加计算单元。
在变的只有中间那一列。
最早,数据得在寄存器里过一道,地址和循环全靠线程自己算;
Ampere 起,cp.async 把寄存器那一道砍掉了,地址还是自己算;
Hopper 的 TMA,一个线程填一张描述符发出去就走 ——
地址生成整个交给硬件。
—— 落点:记分板管「谁能发指令」,barrier 管「谁跟谁对齐」, TMA 管「数据怎么不占用线程就位」。三套加起来,就是「跑起来才决定」那笔账的明细。 TPU 一样都不需要 —— 不是它简陋,是这三个问题它在编译期就全答完了。
讲稿 · 3.3c ⭐ 矩阵乘到底按什么顺序跑
两边的部件到这儿都拆完了。但有一件更基本的事,我一直没说: 一次矩阵乘,在硬件上究竟是按什么顺序算的。
⭐ 先别看图,先让台下自己说一遍。 问:「矩阵乘怎么算?」—— 十个人有十个会说: 拿左边第一行、右边第一列,对应相乘加起来,得到左上角那个数。」让他们说出口,这一步很重要。
对。数学上完全正确。 —— 而真实硬件,无论 GPU 还是 TPU,一个都不这么算。
停。让这句话空一拍。先数一下朴素算法这笔账。看图上第一栏。
第一步:A 的整个第 0 行乘 B 的整个第 0 列,得到 c₀₀,写出去。
第二步:还是 A 第 0 行,换 B 第 1 列 ——
注意,A 第 0 行那三个数,你又读了一遍。
四步走完:A 的六个数各读两遍,B 的六个数各读两遍,总共读 24 次,
只换来 12 次乘加。
硬件干的是另一件事:把 k 挪到最外层。
第一步,k 等于 0:只读两样 —— A 的第 0 列(两个数)、
B 的第 0 行(两个数),一共四个数。
然后这四个数两两配对,四次乘加,
分别加进四个格子里 —— 四个格子同时都动了。
第二步、第三步同理。三步走完,四个格子同时算完,一起倒出去。 —— 总共读 12 次,做 12 次乘加。整整好一倍。
⭐⭐ 这是整段最该被记住的一句,念慢: 「不是一个格子被反复用,是所有格子被同时慢慢填。」顺带把那个具体问题答掉:每个格子恰好被更新 K 次, 左上和右下一模一样,没有谁多谁少。
⭐ 那为什么不能只留一行累加器?看那条算式。
累加器是 M×N 的一块,K 每走一步,读进来 M+N 个数,能做 M×N 次乘加。
所以复用率就是 M×N 除以 M+N。
128 乘 256 的块是多少?八十五。
所以这一句是这一节真正的结论: 累加器阵列不是用来装结果的,它是用来买复用的。 你多开一个累加器,就等于让搬进来的那批操作数多配对一次。 —— 这正是第 1 节那条算术强度的主线,第一次长在了硅上。
滚到第三栏(TPU)。TPU 换了个方向。B 先装进阵列,装完就不动。 A 一行一行从左边流进去,部分和一路往下累加 —— 从底下出来的时候,已经是算完的一行 C 了。
⭐ 这里的关键一句: 「K 方向那几项求和,是在空间上完成的 —— 三个 PE 各做一次乘加,把部分和接力往下传。这几项根本不经过外部累加器。」 滚到最下面那张对照表。这是整节的落点。⭐ 把三栏收成一句:同一个数学,区别只在「哪个维度摊在空间上,哪个走时间」。
GPU 把 M 和 N 摊在空间上、让 K 走时间;
TPU 把 K 和 N 摊在空间上、让 M 走时间。
⚠️ 两条边界,讲的时候顺口带一下就行,别展开: ① 图上 GPU 那栏画的是算法级;指令级上一条 MMA 已经在内部把 K 的一小段 规约掉了 —— 形状一样,只是成批做。 ② TPU 那边外部累加器只在跨 K 分块时才用到,真机 K 常常几千, 阵列一次只吃得下 256。
讲稿 · 3.4 一条指令吃多大一块
上一节讲的是谁来发这条指令。这一节换一个问题 —— 一条指令发出去,一次能吃下多大一块?
听起来很内部,但它决定一件很外部的事 —— 你的 head_dim 选多少不吃亏。
现在滚 ms-g4,再切 ms-t4。两张图只看同一个位置: 求和的那一维有多深。GPU 这边,一路五代指令,动员的线程越来越多、一次吃的块越来越大, 但求和那一维五代都没变,一直是十六。
TPU 这边,MXU 是二百五十六乘二百五十六的脉动阵列,那一维就是二百五十六。
停死,念这个数:「十六,对二百五十六。差十六倍。」 这是这一小节唯一要记住的数。这一维有个名字,叫收缩维 —— 就是矩阵乘里被加掉的那一维,一条指令一次要沿着它累加多少项。 名字不重要,重要的是刚才那两个数。
差十六倍意味着什么?拿一个真形状套进去:注意力的 head_dim 常取 128。
GPU:一百二十八除以十六等于八,切成八条指令,一点不浪费;
TPU:一百二十八对二百五十六,只喂满一半 —— 另一半空着。
那空着的那一半,找点别的活填进去不行吗?
不行。收缩维是一条累加链 —— 塞进去的任何别的数据都会被加进同一个部分和,谁也顶不上来。
反过来问:那 GPU 为什么不吃这个亏?
不是因为它有调度器。调度器换的是 warp,填的是时间上的气泡 —— 它填不了收缩维里空着的那一半。 是因为它的 K 只有十六,几乎什么形状都是十六的整数倍。
⛔ 上面这句照着念。 早先的版本说「GPU 有调度器所以对形状不敏感」,那是错的 —— 台下懂 CUDA 的人一句话就能顶回来。⚠️ 这不是哪边设计得差。GPU 敢把 K 做这么窄、把对齐这件事留到运行时收拾, 代价就是 3.2 里 TPU 少掉的那五样。 —— 「按谁的粒度对齐」这个问题,本来就有两个答案。
⭐ 滚到 3.4 最后那张四条带的大图(图上标题是 「四条带,从『谁不动』一路走到『要重搬几次』」) (四条带:三种驻留 / 切块 / 乒乓 / 重搬账)。⛔ 别四条带平均用力,按这个优先级: ① 先讲第三条带(乒乓)—— 这是别人没讲明白的那一段。
「装一块权重要 256 个周期。但每个格子有两套权重寄存器 —— 一套在算,一套在装。所以那 256 藏在算的那 2048 里面, 只有第一块要干等。」
⭐ 然后立刻给判据:「藏得住的条件就一条:一段的行数要大于 256。」
② 再回到第一条带(三种驻留),一句话带过: 左驻、右驻、中间累加驻,区别只有一个 —— 谁不动。 TPU 走的是右驻。 而「谁不动」决定了哪两维是空间、哪一维是时间。
③ 第二、四条带留给追问: 168 块怎么来的、为什么 M 不能一口气流完(累加器要 128 MiB,比 VMEM 还大)、 段切得越碎右矩阵重搬越多次。 台下不问就不展开。
⭐ 这张图的收口只有一句,一定要说: 「段开长,右矩阵少搬;段开长,累加器变大。所以这个旋钮拧到底, 还是被片上那口灶台顶住的 —— 跟 3.6 里 GPU 那堵墙是同一堵。」 ⚠️ 这里几乎一定有人举手算账: 「一条指令才十六深,怎么也凑不出两千二百五十 TFLOPS。」提前拦一句:指令形状是「一次交代多少活」,每周期做多少乘加是另一回事。 要对账就报课件上那条:4,096 × 2 × 148 SM × 1.83 GHz = 2,218,官方 2,250,差 1.4%。 ⛔ 别顺着他那条错推法往下推。 指本小节末尾那条峰值链。这一段是给会算账的人的, 语速可以快,但结论要清楚。
顺手把 TPU 的峰值从头乘一遍,验证我们没在瞎说: 一个 MXU 一拍 262,144 次浮点运算,乘二点二 GHz,乘四个 MXU —— 等于 2,307 TFLOP/s。官方公布 2,307,一分不差。
⚠️ 这里有一条必须带走的跨代警告: TPU 那个数里有一半来自「每个 cell 每周期做两次乘加」 —— 这是二百五十六乘二百五十六这几代才有的。 一百二十八乘一百二十八的老几代,每 cell 只做一次。 跨代沿用这个常数,会直接错一倍。
这一小节的收口,一定要说: 「能融多大,由两个数一起卡死 —— 一个是工作台多大(3.2b 那个 289 倍), 另一个就是这里的粒度多粗。3.6 切块的时候,这两个数会同时出现。」讲稿 · 3.5 各自多出来的那一块
上半节到这儿为止,数的都是「一边有、另一边没有」。 还有两块,是各自额外长出来的。
但这一节要问的不是「它们是什么」。慢一点,问这个: 「宣传页上写着支持的东西,你到底用不用得上?」白板上就写四个字:「用不用得上」。
这一节就两个例子,两边各一个。
现在滚 ms-p37。左边一个例子,右边一个例子, 下半张是两道门槛。第一个例子在 GPU 这边,叫块量化。宣传口径是「B200 支持 FP4」。
这句话是真的。门槛在于 —— 带块量化的指令有两条通路, 而它们不在同一颗 die 上,B200 只有其中一条。 所以听的人默认「两条都有」,那就错了。
第二个例子在 TPU 这边,叫 SparseCore —— 一颗专门查嵌入表的核。 宣传口径是「TPU 有个专门查 embedding 表的核」。
这句话也是真的。门槛在于 —— 它只对「同一批行被反复查」的负载划算。 图上那个 duplication factor 量的就是这件事。
这里停一下,这是给这屋里的人的那一句: 「语言模型的重复度大约是一点零一 —— 一行几乎只查一次。 编译器根本不往那儿派,生产任务里它一次表都没查过。」 ⛔ 紧接着必须补一句,不能停在上面那里: 「但『用不上』不等于『没在用』—— 这颗核你天天在用。」它在大模型训练里真正干的活是卸载集合通信 —— 把 All-Gather 和 Reduce-Scatter 从 TensorCore 手上接过去, 让 TensorCore 专心算,通信在旁边并行跑完。 这就是「算和通信重叠」在 TPU 上的落地方式之一。
⭐ 如果有人追问「不规则访存到底怎么处理的」 —— 课件里有一张 SparseCore 拆开图,台上别滚过去(会吃掉三分钟)。口头给一句就够:「TensorCore 一次搬 (8,128) 一整块, SparseCore 一次只动 8 个数,而且地址可以是刚算出来的 —— 它分成十六个小单元各追各的地址,靠同时欠着很多次取数来扛延迟。」
要细节让他课后看那张图。 ⛔ 被追问粒度时,这两句不要说错:
「(8,128) 是向量寄存器的形状,不是 DMA 的最小粒度。」
「SparseCore 在 v7 上的 DMA 粒度是 64 字节 —— 这个数是跑 get_tpu_info 直接报出来的,不是文档里的形容词。」
还有一个陷阱问题:「它操作 VMEM 还是 HBM」—— 答「两块都碰,但它碰的那块 VMEM 是它自己的,不是 TensorCore 那块」。 有人要出处就报这两个,都是公开的:Google Cloud 那篇 《Training large models on Ironwood TPUs》把它单列成一条调优手段; MaxText 公开仓库的 XLA flag 库里就有 SparseCore All Gather。 ⛔ 不要报任何内部实测数字。 ⭐ 这一节的教训到这里才完整,慢慢说: 「宣传口径不只会让你用不上它说的那个能力 —— 还会让你看不见你已经在用的那部分。你拿『查表的核』这个名字去 profile 里 找查表,找不到,就以为这颗核在闲着。」
两个例子摆完,顺带能看出一件事:这两块东西连层级都不一样 —— 块量化是一条指令的变体,SparseCore 是物理上独立的一颗核。 「两边各多出一块」这个说法本身,也是宣传口径的产物。
两句宣传语都没说错 —— 错的是把「支持」读成「我的负载用得上」。
收口给一个动作,不要只给感慨: 「看到一个『支持 X』,先去找那道门槛写在哪一行。找不到, 就当它不适用于你。」「五层拆下来,TPU 那边每一层都少了点东西 —— 但少掉的从来不是算的部件。少掉的是 cache、是记分板、 是那几层用来『等到跑起来再决定』的机构。 每一样都是为『不知道你要跑什么』准备的。」
—— 然后点破:「第 0 节那句话,到这里已经被验了五遍。」 ⚠️ 这句必须说,它是全课中段唯一一次回到开场那句论点。 不说的话,台下走过整个 §3 都不会意识到自己一路在看同一件事的五个证据。