1. 稀疏计算基础:什么是稀疏性、稀疏矩阵的存储格式(CSR/CSC/COO)、稀疏计算的硬件动机

大家好,我是你们这堂课的主讲人。咱们直接进入正题——稀疏计算。说实话,我做了这么多年AI芯片,稀疏性这个话题几乎每次架构评审都会被拿出来讨论。为什么?因为它太诱人了,但坑也实在不少。

1.1 什么是稀疏性?说白了就是“大部分是零”

稀疏性这个概念,其实你每天都在接触。举个例子,一张1024x1024的图片,如果只有几个像素点有值,其他全是黑色(零值),那它就是稀疏的。在AI模型里,这种现象更常见——尤其是经过剪枝(Pruning)或者ReLU激活函数之后,权重矩阵和特征图里会出现大量的零。

我个人的习惯是这么理解的:稀疏性 = 有效信息密度低。一个矩阵如果90%的元素都是零,那它的稀疏度就是90%。你想想看,如果我们还按照传统方式去存储和计算这些零,那90%的算力和带宽都浪费了。这不是钱多烧得慌吗?

关键定义:稀疏度 = 零值元素个数 / 总元素个数。通常稀疏度超过50%我们就认为这个矩阵是稀疏的,超过90%就是高度稀疏。

我在项目中遇到过这样一个场景:一个经过结构化剪枝的BERT模型,权重稀疏度达到了85%。如果按照稠密矩阵去推理,延迟是12ms;但如果我们利用稀疏性做优化,延迟能降到3ms以内。嗯,这就是我们研究稀疏计算的硬件动机——省带宽、省算力、省功耗

1.2 稀疏矩阵的存储格式:CSR / CSC / COO

既然大部分元素是零,那我们就没必要存它们了。问题是,怎么存才能既省空间又方便计算?这里我给大家介绍三种最经典的格式,也是我在实际芯片设计中用得最多的。

1.2.1 COO格式(Coordinate List)——最直观,但效率一般

COO格式的思路很简单:只记录非零元素的行号、列号和值。每个非零元素对应一个三元组 (row, col, value)。

// 假设有一个 4x4 的稀疏矩阵
// 非零元素: (0,0)=5, (1,2)=3, (3,1)=7

COO 存储:
rows   = [0, 1, 3]
cols   = [0, 2, 1]
values = [5, 3, 7]

这种格式的好处是构建简单,适合动态添加非零元素。但缺点也很明显——它没有对行或列做任何排序,导致在硬件上做矩阵乘法时,访存模式非常随机。我曾经在FPGA上试过COO格式做稀疏卷积,结果访存带宽利用率不到20%。说白了,就是“存得省,但算得慢”。

1.2.2 CSR格式(Compressed Sparse Row)——工业界最常用

CSR格式在COO的基础上做了优化:它把行号压缩了。具体来说,它用三个数组:

  • values:按行优先顺序存储所有非零元素的值
  • col_indices:每个非零元素对应的列号
  • row_ptr:长度为 (行数+1),记录每行第一个非零元素在 values 中的起始位置
// 同样的矩阵,CSR 格式
values     = [5, 3, 7]
col_indices = [0, 2, 1]
row_ptr    = [0, 1, 2, 2, 3]
// 解释:第0行有1个非零(从索引0开始),第1行有1个(从索引1开始),第2行有0个,第3行有1个(从索引2开始)

我个人习惯用CSR做推理加速。为什么?因为它的行指针结构让硬件可以很方便地做“行并行”——每行的非零元素在内存中是连续存储的,非常适合DMA批量搬运。避坑指南:我曾经在CSR格式上踩过一个坑——当某一行全是零时,row_ptr里相邻两个值相等,这会导致硬件控制逻辑出现空循环。后来我加了一个“空行跳过”的硬件状态机才解决。

1.2.3 CSC格式(Compressed Sparse Column)——CSR的列版本

CSC和CSR是对称的。它按列压缩,用三个数组:values、row_indices、col_ptr。在需要按列访问的场景下(比如某些矩阵乘法),CSC比CSR更高效。

CSC 格式(按列存储):
values      = [5, 7, 3]
row_indices = [0, 3, 1]
col_ptr     = [0, 1, 1, 2, 3]

嗯,这里要注意:选择CSR还是CSC,取决于你的计算模式。如果矩阵乘法是“行×列”,那CSR更适合左矩阵,CSC更适合右矩阵。我在设计NPU的稀疏计算单元时,通常两种格式都支持,让编译器根据数据流自动选择。

我的经验:对于AI推理芯片,CSR格式用得最多。因为权重矩阵通常是固定的,我们可以提前把权重转成CSR格式,然后在运行时只加载非零权重。这样带宽能省下好几倍。

1.3 稀疏计算的硬件动机——为什么我们要在芯片上折腾这些?

你可能会问:软件层面做稀疏优化不就行了吗?为什么非要硬件支持?

原因很简单:软件稀疏优化有天花板。CPU上做稀疏矩阵乘法,由于分支预测失败和缓存不命中,性能提升往往远低于理论值。我做过一个实验:在ARM Cortex-A76上,一个85%稀疏的矩阵,用CSR格式做乘法,实际加速比只有2.3倍,远低于理论上的6.7倍。为什么?因为软件需要频繁判断“这个元素是不是零”,还要处理不规则的内存访问。

硬件加速的核心思路就三个字:去判断

  • 去判断零值:硬件直接跳过零值,不产生计算指令
  • 去判断地址:硬件用专用的地址生成器,按CSR/CSC格式直接计算目标地址
  • 去判断流水线:硬件用固定的流水线处理非零元素,没有分支预测失败

说白了,硬件做稀疏计算,就是把“软件里那些if-else判断”变成“硬连线逻辑”。这样每个时钟周期都能稳定输出结果,不会因为分支跳转而停顿。

注意:不是所有稀疏都能加速。如果稀疏度低于30%,硬件加速的收益可能被格式转换的开销抵消。我建议在稀疏度超过50%时再考虑硬件稀疏加速。

1.4 本章知识体系总览

为了让大家更直观地理解本章的知识结构,我画了一张图。这张图展示了从“稀疏性定义”到“存储格式”再到“硬件动机”的完整逻辑链条。

稀疏计算基础:知识体系 稀疏性定义 大部分元素为零 存储格式 CSR / CSC / COO 硬件动机 省带宽/省算力 COO: 三元组 (row,col,val) 直观但访存随机 CSR: 行压缩 + 列索引 工业标准,行连续 CSC: 列压缩 + 行索引 适合列访问场景 硬件加速核心:去判断 ① 去判断零值 → 跳过零计算 ② 去判断地址 → 专用地址生成器 ③ 去判断流水线 → 无分支停顿 稀疏度 > 50% 时,硬件加速收益显著

这张图从左到右展示了我们的逻辑主线:先理解什么是稀疏性,然后选择合适的存储格式,最后搞清楚为什么要在硬件层面做这件事。我个人建议你把这张图记在脑子里,后面讲稀疏卷积和稀疏Transformer时,我们会反复用到这些概念。

一句话总结:稀疏计算就是“不存零、不算零、不传零”。硬件要做的事,就是让这三个“不”变成芯片的肌肉记忆。


专注资料整理