1.7 自测与延伸
综合自测题、常见误区,以及如何接上论文清单
一、本篇综合自测
先自己回答,再展开看答案要点。答不上来的题,回去读对应小节。
1. 用一句话说明什么是计算强度,以及它为什么重要。
答案要点
计算强度 = 完成的浮点运算次数 ÷ 搬运的字节数。它决定一个程序在 Roofline 图上落在哪个位置:低于拐点就是带宽受限,高于拐点就是算力受限。它把"我的程序慢"这个模糊问题,变成了"我该减少运算还是减少搬运"这个可操作的问题。(1.1 节、1.5 节)
2. 延迟和带宽的区别是什么?各自靠什么手段解决?
答案要点
延迟是一次请求往返的时间,带宽是单位时间搬运的字节数。延迟靠并发隐藏(CPU 的乱序执行与预取、GPU 的海量线程切换);带宽靠减少数据量解决(量化、复用、压缩、增大 batch 提高计算强度)。(1.1 节)
3. 按"1 个周期 = 1 秒"的尺度,L1 命中、主存访问、磁盘访问各相当于多长时间?
答案要点
L1 约 4 秒;主存约 4.5 分钟;磁盘约 1 年。数量级差异是关键,具体数字不必精确。(1.1 节)
4. 时间局部性和空间局部性分别是什么?各举一个代码例子。
答案要点
时间局部性:刚访问的数据很快会再被访问,例如循环里的累加器变量。空间局部性:相邻地址的数据会被连续访问,例如顺序遍历数组。空间局部性正是 Cache 按 64 字节块搬运能获益的前提。(1.2 节)
5. 三类 Cache 缺失分别是什么?各对应什么优化手段?
答案要点
冷启动缺失(必然发生,靠预取隐藏)、容量缺失(工作集太大,靠分块/降低工作集/压缩)、冲突缺失(工作集装得下但映射冲突,靠改数据布局/填充/提高相联度)。先分类再动手,下错药是最常见的时间浪费。(1.3 节)
6. 写出 AMAT 的公式,并说明为什么"靠上层级的权重最高"。
答案要点
。因为每一级都乘以前一级的缺失率,越靠上的层级被乘的次数越多、覆盖的访问越多。L1 缺失率从 5% 降到 2% 带来的收益,远大于在 L3 上做同样的相对改进。(1.3 节)
7. 分块为什么能把搬运量从 降到 ,而运算次数不变?
答案要点
不分块时,外层每换一次循环变量,整个大矩阵都要重新从慢存储进入。分块后,一个块被载入快存储后能在被挤出之前被复用 次。运算次数没变,变的是"同一份数据被搬了几次"。(1.3 节)
8. Amdahl 定律:程序 70% 的时间在一个函数里,把它优化 5 倍,整体加速比是多少?如果能优化到无限快呢?
答案要点
倍。优化到无限快则是 倍。注意这两个数字差别不大——说明再往下抠这个函数的收益已经很小了,应该去找那 30%。(1.4 节)
9. 编译器会帮你做的事有哪些?不会帮你做的有哪些?
答案要点
会做:寄存器分配、指令选择、局部 CSE、强度削减、循环展开、自动向量化。不会做:跨编译单元优化、跨循环重构(如循环交换、分块)、算法替换、改变浮点运算顺序、在存在指针别名时重排访存。人的价值在结构层面。(1.4 节)
10. 某 GPU 算力 300 TFLOPS,带宽 2 TB/s。一个算子的计算强度是 20 FLOP/Byte。它受限在哪一侧?理论性能上限是多少?
答案要点
拐点 FLOP/Byte。因为 ,所以带宽受限。性能上限 TFLOPS——只有峰值算力的约 13%。(1.5 节)
11. FlashAttention 增加了浮点运算次数,为什么反而更快?
答案要点
因为它把算子的瓶颈从带宽侧移向了算力侧。朴素注意力要把 的中间矩阵反复写回、读回 HBM;FlashAttention 用分块把中间结果留在片上存储,用重算代替存储。在"算力富余、带宽紧缺"的前提下,用算力换带宽是划算的。(1.5 节)
12. 你面前有一段很慢的循环。列出你会依次做哪四件事。
答案要点
① 度量,确认它真的是瓶颈(用 profiler,不要凭感觉);② 用控制变量判断瓶颈类型(改循环顺序→访存模式;改数据规模→容量;改布局→冲突);③ 按优先级优化(算法/数据结构 → 访存模式 → 循环内低效 → 手工微调);④ 重新度量,验证是否真的变快,以及瓶颈是否转移到了别处。(1.4 节、1.6 节)
二、常见误区
⚠️ 误区一:用复杂度分析代替性能分析
只数了运算次数。本篇反复强调:真实性能由搬运次数决定,两者可以差一个数量级。看到"复杂度相同"就认为"性能相同",是最根深蒂固的错误。
⚠️ 误区二:以为"编译器会帮我优化"
编译器负责的是局部指令级优化。它不会帮你把按列遍历改成按行遍历,不会帮你分块,不会帮你换算法。"改循环顺序提升了 5 倍"这类优化,永远得你自己做。
⚠️ 误区三:不分类就动手
看到"Cache 缺失"就盲目减少数据量,但问题是冲突缺失——白忙一场。先判断属于哪一类(冷启动 / 容量 / 冲突),再选手段。
⚠️ 误区四:过早优化
优化之前先问 Amdahl:这块占了总时间的多少?优化它最多能带来多少收益?优化一个占 5% 时间的部分,是典型的白费力。
⚠️ 误区五:只优化固定开销,不动 CPE
把初始化、函数调用的开销优化得很仔细,但每个元素的代价(CPE)一点没动。先看斜率,再看截距。
⚠️ 误区六:优化完不重新度量
优化一处之后,瓶颈往往跑到别处去了。Amdahl 定律是上限,不是预测——因为它假设了瓶颈不变。
⚠️ 误区七:忽略"已有库已经做过优化"
实验三 的 NumPy 例子就是教训:在一个已经做过分块的库上面再手工分块,只会更慢。先搞清楚手上工具做了什么。
三、接上论文清单
现在你已经有了读第一篇论文所需的全部背景。正确的顺序是:先读下面这两篇,再读第三篇的 FlashAttention。
| 论文 | 用本篇的哪些概念去读 |
|---|---|
| Roofline: An Insightful Visual Performance Model · CACM 2009 | 1.5 节的 Roofline 是它的简化版。原文有完整的推导、多核场景和实测 |
| In-Datacenter Performance Analysis of a Tensor Processing Unit · ISCA 2017 | 用 1.1 节的"延迟 vs 带宽"和 1.5 节的 Roofline 去解释:为什么 TPU 的算力利用率常常远低于峰值? |
完整的论文清单位于仓库的 material/ 目录,里面有:
- 25 篇 P0 必读论文,按模块组织(
material/README.md有全局勾选清单) - 12 周学习计划,与本教材的章节对应
- 每篇论文的「为什么读 / 读到什么程度 / 前置知识 / 建议用时」
💡 教材负责建立判断依据,清单负责带你看真实系统。一篇教材正文配几篇论文,交替进行,比一口气读完任何一边都有效。
四、下一步
你已经完成了整套教材的地基。下一篇(⏳ 待补充)会回答:
你天天在用的 PyTorch,背后到底做了什么?
它会用到本篇的哪些东西:
- 1.4 节的"不必要的内存引用" → 理解为什么激活值会占掉大量显存
- 1.5 节的存储层次对照 → 理解为什么动态图会带来调度开销
- 全篇的搬运视角 → 理解训练显存的四个占用者分别对应哪些优化手段
在你等待下一篇的同时,建议:
- 把 1.6 的动手实验 全部做完。这一篇的价值有七成在实验里。
- 读上面那两篇论文。读的时候刻意用"这是算力受限还是带宽受限"作为问题去套。
- 把结果记下来。换机器、换编译器时对比,你会对"量级关系稳定、具体数字浮动"这件事有真实的体感。