4、稀疏权重存储格式概览:CSR、CSC、COO、BCSR、Blocked-Ellpack等格式的原理与适用场景

TinyML模型在设备端部署时,权重张量经过结构化剪枝或量化后,往往呈现出极高的稀疏度(通常超过80%,甚至达到95%以上)。如果仍然使用稠密矩阵格式存储这些权重,不仅浪费宝贵的Flash和RAM空间,还会导致大量无效的乘加运算。稀疏存储格式的核心目标,就是用“索引+非零值”的组合替代完整的稠密矩阵,从而在存储容量和计算效率两个维度同时获益。然而,不同的稀疏格式在压缩率、访存局部性、硬件友好性和可扩展性上差异显著,选择不当反而会引入额外的索引开销或导致计算单元空转。本章将系统梳理CSR、CSC、COO、BCSR和Blocked-Ellpack五种主流格式的原理、适用场景及工程选型要点。

稀疏权重存储格式选型决策框架 原始稠密权重张量 形状、稀疏度、硬件目标 稀疏度 > 90%? 且无块结构 是 否 CSR / CSC 行/列压缩,索引紧凑 是否存在规则块状非零模式? 如 4×4 / 2×2 块 是 否 BCSR 块压缩,适合规则剪枝 硬件是否支持并行索引? GPU / 向量化DSP 是 否 COO(坐标格式) Blocked-Ellpack 定长列,适合SIMD

上图给出了稀疏格式选型的基本决策路径。实际工程中,还需要结合具体的硬件架构(如是否有DMA、向量指令集、缓存大小)和模型结构(如卷积层还是全连接层)进行综合权衡。下面逐一剖析每种格式的内部机制与适用边界。

4.1 CSR(Compressed Sparse Row)格式:通用性与行访问友好的平衡

核心原理

CSR格式将稀疏矩阵按行压缩,使用三个一维数组存储:values(非零值)、column_indices(每个非零值对应的列号)、row_ptr(每行第一个非零值在values中的偏移位置)。row_ptr的长度为行数+1,其中row_ptr[i]和row_ptr[i+1]之间的区间即为第i行的非零元素范围。这种设计的核心优势在于:通过行偏移量即可快速定位任意行的数据起始位置,无需遍历索引。

数组长度示例(3×4矩阵,非零值5个)
valuesnnz[1.5, 2.0, 3.2, 4.1, 5.5]
column_indicesnnz[0, 2, 1, 3, 2]
row_ptrrows+1[0, 2, 3, 5]

判断方法与操作流程

选择CSR格式前,需确认以下条件:①矩阵行数远大于列数,且行内非零元素分布相对均匀;②推理时主要执行稀疏矩阵乘稠密向量(SpMV)操作,且按行并行计算;③目标硬件支持对row_ptr的随机访问。操作流程为:先统计每行非零个数,计算前缀和生成row_ptr,再按行顺序填充values和column_indices。

案例拆解:全连接层权重压缩

假设一个TinyML语音识别模型中的全连接层,输入维度512,输出维度256,经过结构化剪枝后稀疏度为92%。原始稠密存储需要512×256×4字节=512KB。采用CSR后,非零值约10,486个,values占40KB,column_indices占40KB(int16),row_ptr占1KB,总计约81KB,压缩比达到6.3:1。在Cortex-M7内核上,配合CMSIS-DSP的稀疏矩阵函数,推理耗时从原始的12.3ms降至4.1ms。但需要注意,如果行内非零值分布极不均匀(如某行只有1个非零,另一行有50个),会导致并行线程负载失衡。

风险提示:CSR的row_ptr使用int32存储时,对于超过2^31个非零值的超大矩阵会溢出;在TinyML场景中通常不会遇到,但若使用int16存储row_ptr,则行数不能超过32767。此外,CSR对列访问不友好,如果需要频繁按列读取(如反向传播),应转置为CSC。

4.2 CSC(Compressed Sparse Column)格式:列访问优化的镜像方案

核心原理与适用场景

CSC与CSR完全对称,按列压缩存储,使用values、row_indices和col_ptr三个数组。其优势在于按列访问时具有极佳的缓存局部性。在TinyML中,CSC常用于以下场景:①权重矩阵需要与输入特征图按列方向做点积;②模型部署在具有列优先存储架构的DSP上;③需要对权重进行列级量化或剪枝操作。

实战对比:CSR vs CSC

维度CSRCSC
行访问O(1)定位,缓存友好需遍历col_ptr
列访问需遍历所有行O(1)定位
典型应用SpMV、行并行列并行、稀疏矩阵乘稀疏矩阵
内存开销row_ptr(rows+1)+col_idx(nnz)col_ptr(cols+1)+row_idx(nnz)
经验之谈:在TinyML的模型转换工具链中(如TensorFlow Lite Micro),默认将全连接层权重转换为CSR格式,因为推理时输入是向量,按行读取权重与输入做点积最自然。但如果你的模型包含转置卷积或需要按列融合的算子,CSC可能更合适。

4.3 COO(Coordinate Format)格式:最直观但索引开销最大

核心原理

COO格式直接存储每个非零值的三元组(行号、列号、值),三个数组等长。它的最大优点是构建简单、插入删除方便,非常适合在模型剪枝过程中动态修改权重。但缺点也很明显:索引存储开销大(每个非零值需要两个整数索引),且无法直接支持高效的随机行/列访问。

适用场景与限制

在TinyML中,COO通常作为中间转换格式使用。例如,PyTorch的稀疏张量默认采用COO格式,在导出为TFLite或ONNX时,工具链会将COO转换为CSR或BCSR。如果直接在MCU上使用COO,每个非零值需要额外8字节(两个int32索引),对于高稀疏度矩阵,索引开销可能超过值本身的存储。

// COO格式示例:3×4矩阵,5个非零值
int row[] = {0, 0, 1, 2, 2};
int col[] = {0, 2, 1, 3, 2};
float val[] = {1.5f, 2.0f, 3.2f, 4.1f, 5.5f};
// 访问第i个非零元素:row[i], col[i], val[i]
工程建议:在模型转换流程中,将COO作为“标准交换格式”,在最终部署前转换为目标硬件友好的格式。例如,在STM32Cube.AI中,内部先解析ONNX的COO张量,再根据目标芯片的缓存大小自动选择CSR或BCSR。

4.4 BCSR(Block Compressed Sparse Row)格式:规则块结构的利器

核心原理

BCSR将矩阵划分为固定大小的块(如2×2、4×4),仅存储包含非零元素的块。它使用block_values、block_column_indices和row_ptr三个数组,但每个“非零”元素实际上是一个小矩阵块。这种格式特别适合结构化剪枝后的权重——当剪枝以块为单位进行时,非零元素天然聚集在规则块中。

判断方法与性能分析

选择BCSR需满足:①非零元素呈现明显的块状聚集,而非随机分布;②目标硬件支持块矩阵运算指令(如ARM的DOT指令或RISC-V的向量扩展);③块大小与硬件寄存器宽度匹配。以4×4块为例,相比CSR,BCSR的列索引数量减少为原来的1/4,同时可以利用SIMD指令一次性完成4×4矩阵乘。

块大小索引开销/非零值适合硬件典型压缩率提升
2×21/4列索引通用MCU15-25%
4×41/16列索引ARM Cortex-M55/M8530-40%
8×81/64列索引带矩阵加速器的NPU45-55%

案例:卷积核的BCSR转换

考虑一个3×3卷积,输出通道32,输入通道16。经过N:M剪枝(每4个连续权重保留2个)后,非零模式呈现2×1或1×2的块状。直接使用CSR需要存储每个非零值的列索引,而采用2×2块BCSR后,块数量减少约60%。在STM32F746上测试,BCSR格式的卷积推理速度比CSR快1.8倍,主要得益于减少了索引查找次数和更好的数据复用。

注意:BCSR的块大小并非越大越好。块越大,块内零值填充率越高,可能反而降低实际压缩率。例如,一个稀疏度95%的矩阵,如果非零值随机分布,使用4×4块可能导致块内填充率不足50%,此时BCSR的存储开销反而超过CSR。务必通过直方图分析非零值的空间分布后再决定块大小。

4.5 Blocked-Ellpack格式:面向SIMD的定长存储方案

核心原理

Blocked-Ellpack(BELLPACK)结合了Ellpack和BCSR的优点。它将矩阵按块划分,但每个块行(block row)内使用定长数组存储所有块,不足部分用零块填充。这种“定长”特性使得硬件可以预计算内存偏移,非常适合向量化加载和SIMD并行。与BCSR相比,BELLPACK不需要row_ptr数组,而是通过固定的块行宽度计算偏移,减少了间接寻址开销。

适用场景与实现要点

BELLPACK最适合以下条件:①块行内非零块数量相对均匀;②目标硬件支持向量加载(如NEON或Helium);③需要极低延迟的推理场景。实现时需注意:块行宽度的选择直接影响内存浪费率——如果某行只有1个非零块,而块行宽度设为8,则浪费87.5%的块存储。

// BELLPACK格式示意(块大小2×2,块行宽度=3)
// block_values: 按块行存储,每行固定3个块
// block_col_indices: 每个块对应的列块号
// 第0块行: [块(0,0), 块(0,2), 零块]
// 第1块行: [块(1,1), 零块, 零块]

对比总结与选型决策

格式索引开销行访问列访问SIMD友好构建难度典型场景
CSR中优差中低通用SpMV
CSC中差优中低列并行计算
COO高差差差最低动态剪枝/转换中间格式
BCSR低优中优中结构化剪枝权重
BELLPACK最低优中最优高SIMD加速器/向量DSP

4.6 常见误区与工程复盘

误区一:认为稀疏度越高,任何稀疏格式都能带来线性加速。实际上,当稀疏度低于80%时,索引开销和分支判断可能使稀疏格式比稠密格式更慢。
误区二:忽略硬件特性盲目选择格式。例如,在无SIMD指令的Cortex-M0上使用BELLPACK,反而因内存对齐要求导致额外开销。
误区三:转换过程中丢失数据局部性。CSR按行存储,但如果推理时输入特征图按列访问,应转置为CSC。

复盘:一个真实的声音分类模型部署

某智能音箱端侧唤醒词模型,包含3个卷积层和2个全连接层,原始大小1.2MB。初始使用CSR格式部署在Cortex-M4上,推理耗时38ms,内存占用420KB。分析发现:第一层卷积权重稀疏度仅75%,CSR收益有限;而全连接层稀疏度97%,但非零值分布随机,导致CSR的列索引缓存命中率低。优化方案:对卷积层保持稠密格式但使用int8量化,对全连接层改用BCSR(块大小2×2),最终内存降至280KB,推理耗时降至21ms。关键教训:不同层应根据自身稀疏特征选择不同格式,而非全模型统一。

选型口诀:随机稀疏用CSR,规则块状选BCSR,SIMD加速用BELLPACK,动态修改用COO,列访问频繁转CSC。始终以实际硬件benchmark为准,不要仅凭理论压缩率做决策。