1. 缓存基础与存储层次:为什么需要缓存?

大家好,我是你们这堂课的主讲人。今天咱们聊聊缓存,这个在算子映射里绕不开的话题。

说实话,我刚开始做AI芯片那会儿,也犯过嘀咕:不就是个存储吗?直接拿DRAM当主存用不就完了?后来被现实狠狠教育了一顿。你想想看,一个卷积算子在DRAM里读数据,和从缓存里读,速度能差出两个数量级。这可不是闹着玩的。

1.1 存储层次的延迟差异

先看一组数据。我习惯把这些数字刻在脑子里,因为做算子映射时,每一步决策都跟它有关。

存储层级 典型延迟 相对CPU周期 容量
寄存器 ~0.3 ns 1 cycle 几十字节
L1 Cache (SRAM) ~1 ns 3-4 cycles 32-64 KB
L2 Cache (SRAM) ~3-5 ns 10-20 cycles 256-512 KB
L3 Cache (SRAM) ~10-15 ns 30-50 cycles 几MB
主存 (DRAM) ~50-100 ns 200-400 cycles 几GB

看到没?从L1到主存,延迟差了将近100倍。我在项目中遇到过最夸张的一次,一个矩阵乘法算子因为缓存命中率从90%掉到30%,性能直接腰斩。嗯,从那以后我再也不敢小看缓存了。

1.2 SRAM vs DRAM:为什么差这么多?

说白了,SRAM和DRAM的差异,根子在电路结构上。

  • SRAM(静态随机存取存储器):用6个晶体管存1个bit。速度快,但面积大、功耗高。适合做缓存。
  • DRAM(动态随机存取存储器):用1个晶体管+1个电容存1个bit。密度高、成本低,但需要定期刷新,速度慢。适合做主存。

你想想看,SRAM一个bit要6个管子,DRAM只要1个。同样面积下,DRAM容量能大出五六倍。但代价就是慢——电容充放电需要时间,还得时不时刷新一下,不然数据就丢了。

核心观点:缓存存在的根本原因,就是SRAM比DRAM快,但贵。所以我们用少量SRAM做缓存,把最常用的数据放进去,大部分数据还是留在DRAM里。

1.3 局部性原理:缓存能工作的理论基础

缓存不是万能的。它之所以有效,全靠程序的局部性原理。我建议每个做算子映射的工程师,都把这两个概念刻在脑子里。

时间局部性

如果一个数据被访问了,那么它很可能在不久的将来再次被访问。

举个例子:你在写一个卷积循环,权重矩阵会被反复读取。这就是时间局部性。缓存会把这个权重矩阵留在L1里,下次用的时候直接命中。

空间局部性

如果一个数据被访问了,那么它附近的数据也很可能被访问。

还是卷积的例子:你读了一个像素点,接下来大概率会读它旁边的像素点。缓存会把一整块数据(比如一个cache line,通常是64字节)都拉进来。这样你读下一个像素时,它已经在缓存里了。

我的经验:做算子映射时,我习惯先分析数据流的局部性特征。如果时间局部性强,就尽量把数据留在缓存里;如果空间局部性强,就调整数据布局,让连续访问的数据在内存里也连续存放。

1.4 缓存命中的代价

这里有个概念叫缓存命中率。命中率越高,性能越好。但一旦发生缓存缺失,代价就大了。

我曾经在一个项目中,因为没注意数据对齐,导致大量缓存缺失。一个简单的加法算子,延迟从2个周期飙到了50个周期。你想想看,25倍的差距,这谁受得了?

缓存缺失的典型场景:

  • 强制缺失:第一次访问某个数据,缓存里肯定没有。这是不可避免的。
  • 容量缺失:缓存太小,装不下所有需要的数据。这时候就得考虑分块(tiling)了。
  • 冲突缺失:多个数据映射到同一个缓存行,互相踢来踢去。这通常是因为地址映射策略不好。

避坑指南:我曾经在映射一个深度可分离卷积时,没注意输入特征图和权重的缓存冲突。结果两个数据反复互相踢,命中率从95%掉到了40%。后来调整了数据排布,把权重和特征图放在不同的缓存组里,问题才解决。

1.5 知识体系总览

下面这张图,是我自己总结的缓存基础知识框架。你把它理解了,后面讲算子映射时就能跟上节奏。

缓存基础与存储层次知识体系 存储层次 寄存器 → L1 → L2 → L3 → 主存 延迟差异 SRAM: ~1ns vs DRAM: ~50ns 局部性原理 时间局部性 + 空间局部性 缓存缺失类型 强制缺失(首次访问) 容量缺失(缓存太小) 冲突缺失(地址映射) 算子映射优化策略 数据分块(Tiling) 数据排布优化 预取(Prefetch) 对齐访问 核心目标:最大化缓存命中率,最小化主存访问

1.6 小结

好了,这一章的内容就这些。说白了,缓存就是利用SRAM的速度优势,配合程序的局部性原理,把最常用的数据放在离CPU最近的地方。

你想想看,一个算子映射做得好不好,很大程度上就看你能不能把数据留在缓存里。后面几章我们会深入讲具体的映射策略,但今天这些基础概念,是绕不开的。

嗯,今天就到这儿。记住一句话:缓存命中率,就是算子的生命线。

课后思考:你手头正在做的算子,数据访问模式是时间局部性强,还是空间局部性强?如果是后者,你会怎么调整数据布局?

可可精品资料库