第4章:数据复用性与局部性
各位同学,今天我们来聊聊芯片设计中一个绕不开的话题——数据复用性与局部性。说实话,我见过太多团队在架构设计阶段忽略了这一点,结果后期性能怎么调都上不去。我自己就踩过这个坑,所以今天想把这些经验分享给你们。
4.1 时间局部性与空间局部性
先说说两个最基础的概念:时间局部性和空间局部性。你想想看,程序运行时是不是经常反复访问同一块数据?这就是时间局部性。而空间局部性呢,就是访问了某个地址后,大概率会访问它旁边的地址。
举个例子,我在做图像处理芯片时,一个像素点被反复读取做滤波运算。这就是典型的时间局部性。而读取一行像素时,相邻像素也会被加载到缓存中,这就是空间局部性在起作用。
核心要点:
- 时间局部性:同一数据在短时间内被多次访问
- 空间局部性:相邻地址的数据被连续访问
- 两者共同决定了缓存命中率
4.2 输入特征图复用
在卷积神经网络中,输入特征图的复用是个大头。我刚开始做AI芯片时,总觉得这很简单,不就是把数据存好嘛。结果第一次流片回来,性能惨不忍睹。
为什么会这样?因为卷积运算时,同一个输入特征图会被多个卷积核反复使用。如果每次都用从DRAM读,那延迟和功耗都受不了。
我个人习惯的做法是:
- 把输入特征图按行分块,每块大小刚好能放进SRAM
- 让多个卷积核共享同一块输入数据
- 用双缓冲机制,一边计算一边加载下一块
避坑指南:我曾经在某个项目中,把输入特征图分块分得太小了,结果频繁切换块导致缓存抖动。后来调整为每块包含4行数据,性能提升了30%。
4.3 权重复用
权重复用,说白了就是让同一个权重被多个输入数据共享。在卷积层中,一个卷积核会在整个特征图上滑动,权重被反复使用。
我记得有个项目,团队把所有权重都放在片外存储,每次计算都要从DRAM读权重。结果功耗高得吓人,芯片烫得能煎鸡蛋。
正确的做法是:
- 把当前层的所有权重预加载到片上SRAM
- 在计算过程中,权重只读不写
- 等这一层算完了,再加载下一层的权重
这里有个关键点:权重的存储格式。我建议用NHWC格式,这样在读取时能充分利用空间局部性。
4.4 输出部分和复用
输出部分和复用,这个概念可能有些同学不太熟悉。简单说,就是在做卷积时,多个输入通道会累加到同一个输出像素上。如果每次累加都写回DRAM,那效率太低了。
我的做法是:在片上维护一个输出缓冲区,把部分和暂存在里面。等所有输入通道都算完了,再一次性写回DRAM。
注意事项:输出缓冲区的大小要精心设计。太小了,部分和写回太频繁;太大了,片上资源不够用。我一般按输出特征图的一行大小来设计。
4.5 三种复用策略的对比
| 复用类型 | 复用对象 | 典型场景 | 优化目标 |
|---|---|---|---|
| 输入特征图复用 | 输入数据 | 多卷积核并行 | 减少DRAM读取次数 |
| 权重复用 | 权重参数 | 卷积核滑动 | 降低带宽需求 |
| 输出部分和复用 | 中间计算结果 | 多通道累加 | 减少写回次数 |
4.6 知识体系结构图
4.7 实战经验总结
说了这么多,我给大家总结几条实战经验:
- 先分析数据流:在设计缓存架构前,先画出数据流图,标出哪些数据被反复使用
- 合理分配片上资源:输入缓冲区、权重缓冲区、输出缓冲区要平衡分配
- 注意数据对齐:不对齐的数据访问会浪费带宽,我吃过这个亏
- 用仿真验证:别光靠理论,跑仿真看看缓存命中率
最后说一句:数据复用性设计得好,芯片性能能翻倍。设计得不好,再牛的算力也白搭。嗯,今天就到这里,大家回去好好消化一下。