AI Infra 自学教材

1.1 一次访存到底有多贵

建立「访存比计算贵」的量化直觉,并区分延迟与带宽

学习目标

读完这一节,你应该能够:

  1. 说出从寄存器到内存、从内存到磁盘,各级存储的代价大约差多少个数量级。
  2. 用自己的话解释延迟带宽的区别,并说出各自的瓶颈场景。
  3. 解释为什么"优化程序"经常等于"减少搬运"而不是"减少运算"。

一、一个反直觉的事实

大多数人写代码时形成的直觉是:计算很贵,读内存很便宜——不就是读个变量吗?

在现代计算机上,这个直觉是反的。

要看清这一点,需要观察两条曲线在过去几十年里是怎么分岔的:

  • CPU 的运算能力,随着工艺进步、流水线加深、超标量发射、SIMD 向量化和多核,持续快速提升。
  • 内存的访问延迟,主要由 DRAM 的物理特性决定,几十年来只从一百纳秒量级缓慢改善到几十纳秒量级。

两条曲线的差距越拉越大。这个缺口在 1990 年代就有了名字:存储墙(memory wall)

后果非常直接:CPU 经常在空等内存。

这里不需要记住具体年份和倍数。要记住的是趋势的形状——运算能力在指数增长,而访存延迟几乎是平的。所有后面的结论都从这个形状推出来。


二、用两把尺子量存储层次

尺子一:周期数

CPU 内部的一切以时钟周期为单位。现代处理器的时钟频率大约是每秒 3 至 5 个吉周期(GHz),所以:

1 个周期0.20.35 纳秒1 \text{ 个周期} \approx 0.2 \sim 0.35 \text{ 纳秒}

下面是一张典型量级的表。注意:具体数字随微架构变化,不同机器之间可能相差 2 至 3 倍,但量级关系是稳定的——而量级关系才是你要记住的东西。

存储层级典型延迟约合周期数
寄存器几乎为零0
L1 cache约 1 纳秒3 – 5
L2 cache约 4 纳秒10 – 15
L3 cache约 15 – 30 纳秒40 – 90
主存(DRAM)约 60 – 100 纳秒200 – 300
NVMe SSD约 100 微秒约 30 万
机械硬盘约 10 毫秒约 3000 万
同一机房内网络往返约 0.5 毫秒约 150 万
跨洲网络往返约 150 毫秒约 5 亿

尺子二:比例化时间

周期数对人不直观。一个更有效的方法是把 CPU 的一个周期放大成 1 秒,然后看看每一级存储对应多长时间。

按这个尺度(相当于把时间放慢约 33 亿倍):

存储层级如果 1 个周期 = 1 秒
寄存器0 秒
L1 cache4 秒
L2 cache13 秒
L3 cache约 1 分钟
主存(DRAM)约 4.5 分钟
NVMe SSD约 4 天
机械硬盘约 1 年
跨洲网络约 15 年

💡 这张表是本节最该记住的东西。

它说明:如果一次 L1 命中是"看一眼桌子上的便利贴",那么一次主存访问就是"下楼去隔壁楼的档案室,而且档案员现在不在座位上"。你会毫不犹豫地在便利贴上多写几遍,来避免跑那一趟——这正是 Cache 和分块优化的全部动机。


三、延迟与带宽是两件事

这两个词经常被混用,但它们是不同的物理量,在不同场景下成为不同的瓶颈。

延迟(latency)带宽(bandwidth)
含义一次请求从发出到拿到数据要多久单位时间内能搬运多少字节
单位纳秒GB/s
类比一辆货车跑一趟要多久有多少条车道、车跑多快
什么时候是瓶颈请求次数少、每次都要等(串行依赖数据量大、且能并发搬运

关键洞察:延迟和带宽是可以分开优化的。

  • 如果瓶颈是延迟,解决办法通常是并发:一次发很多个请求,让等待时间重叠起来。CPU 用乱序执行和预取做到这一点,GPU 用海量线程切换做到这一点。
  • 如果瓶颈是带宽,解决办法是减少要搬的字节数:压缩、量化、复用、或者干脆别搬。

⚠️ 常见误区:看到"内存慢"就以为要减少访问次数。但如果数据量很大且访问是独立的,减少次数并不解决问题——真正的瓶颈是带宽。第 1.5 节会用这个区分来判断 GPU 上的场景。


四、算一笔账:为什么"访存比计算贵"

现在把两个事实放在一起:

  1. CPU 每个周期可以完成多次浮点运算(超标量发射 + SIMD 向量化,一个周期处理 4 到 16 个单精度浮点数很常见)。
  2. 从主存取回一个 4 字节浮点数,需要几百个周期

也就是说,为了让 CPU 的运算单元不闲着,每搬来 4 个字节,就得让它做几十甚至上百次运算。

这个比值有一个正式的名字:计算强度(arithmetic intensity)

计算强度=完成的浮点运算次数 (FLOPs)搬运的字节数 (Bytes)\text{计算强度} = \frac{\text{完成的浮点运算次数 (FLOPs)}}{\text{搬运的字节数 (Bytes)}}

计算强度越高,程序越"划算"——因为搬运的代价被更多的计算摊薄了。计算强度低的程序,无论 CPU 多快,性能都被访存卡死。

这就解释了为什么现代程序的优化重点常常是"减少搬运"而不是"减少运算":减少运算只能让本来就空闲的运算单元更空闲,而减少搬运直接去掉了瓶颈。


五、与 AI Infra 的连线

这一节的全部内容,就是为了让你在下一次看到下面这些说法时,知道它们在说什么。

1. 为什么大模型推理的 decode 阶段特别慢?

生成一个 token 时,模型要把全部权重从显存里读一遍,而每个权重只参与很少的运算。计算强度大约只有 1 到 2——远低于硬件的"划算线"。所以 decode 阶段是纯粹的带宽受限:GPU 的运算单元绝大多数时间在等显存。

这就是后面量化、投机解码、MoE 这些技术存在的根本原因——它们全都建立在"decode 卡在带宽上,算力是富余的"这个判断上。

2. 为什么 GPU 的显存带宽是核心指标?

一块数据中心 GPU 的算力和显存带宽之比,决定了它以多高的计算强度才能跑满。这个比值就是后面 1.5 节要讲的 Roofline 拐点。买 GPU 时只看算力(TFLOPS)而不看带宽,是新手最常见的错误。

3. 为什么"减少通信"在分布式训练里是头等大事?

因为跨卡通信的延迟和带宽代价,比访问本机显存还要高出好几个数量级。第四篇里所有的并行策略,本质上都是在"多算一点"和"少传一点"之间做权衡。


关键结论

  1. 运算能力在指数增长,访存延迟几乎是平的,所以 CPU 经常在等内存。
  2. 各级存储的代价相差几个数量级:L1 是秒级,主存是分钟级,磁盘是年级(按 1 周期 = 1 秒的比例化尺度)。
  3. 延迟带宽是两件事:延迟靠并发隐藏,带宽靠减少数据量解决。
  4. 计算强度决定一个程序是否划算。计算强度低,再快的算力也没用。
  5. 因此,优化常常是减少搬运而不是减少运算。

自测问题

  1. 按"1 个周期 = 1 秒"的尺度,一次主存访问大约相当于多长时间?这个尺度下,一次 L1 命中呢?
  2. 延迟和带宽分别在什么情况下成为瓶颈?各举一个例子。
  3. 一个程序的计算强度是 1 FLOP/Byte。如果硬件的算力是 100 TFLOPS、带宽是 2 TB/s,这个程序能跑到多少性能?它受限在哪一侧?
  4. 用你自己的话解释:为什么"把循环里的重复计算挪到循环外"能提升性能?(提示:从搬运的角度想,不一定要从运算的角度想。)
  5. 有人告诉你"这个 kernel 慢是因为内存访问次数太多"。你需要追问哪两个问题才能判断他说得对不对?

延伸阅读

  • 教材Computer Systems: A Programmer's Perspective(CSAPP)第 6 章开头对存储层次与技术趋势的讨论。本站的讲解是重新组织的,教材里更完整的背景数据和图表值得对照看。
  • 原始概念:Wulf 与 McKee 在 1995 年提出"存储墙"的那篇文章(搜索 "Hitting the Memory Wall: Implications of the Obvious")。

下一节 → 1.2 存储层次与局部性

On this page