4、稀疏化基础:稀疏张量、稀疏表示、稀疏度计算、稀疏模式(不规则、行、列、块)及其对硬件的影响
在TinyML模型压缩的众多技术路线中,剪枝(Pruning)是最直观、最有效的手段之一。然而,剪枝本身只是“破坏”了原有的密集权重矩阵,真正让模型在端侧设备上实现加速与省电,则完全依赖于对剪枝后产生的稀疏张量进行高效的存储与计算。本章将深入剖析稀疏化的底层逻辑:从稀疏张量的定义出发,厘清不同的稀疏表示格式,掌握稀疏度的计算方法,并重点分析四种核心稀疏模式(不规则、行、列、块)对硬件加速器设计、内存带宽以及实际推理延迟的深远影响。理解本章内容,是后续学习剪枝算法与硬件协同设计(Software-Hardware Co-Design)的基石。
上图清晰地勾勒出本章的知识框架:稀疏张量是剪枝的直接产物,其核心价值在于稀疏度;而稀疏表示格式则是连接算法与硬件的桥梁;最终,不同的稀疏模式直接决定了硬件加速器能否高效利用稀疏性。下面我们逐一展开。
4.1 稀疏张量与稀疏度计算
4.1.1 核心原理:什么是稀疏张量
在深度学习模型中,权重和激活值通常以密集张量(Dense Tensor)的形式存储,即每一个元素都有实际意义。经过剪枝后,大量权重被置为零。如果一个张量中零元素的数量远大于非零元素,我们就称其为稀疏张量(Sparse Tensor)。稀疏张量并不神秘,它只是对“大部分元素为零”这一数学事实的抽象描述。
稀疏度的计算是量化剪枝效果的基础指标,公式如下:
稀疏度 (Sparsity) = 1 - (非零元素个数 / 张量总元素个数)
例如,一个 4x4 的卷积核权重矩阵,总共 16 个参数。如果剪枝后只有 4 个非零值,那么稀疏度 S = 1 - (4/16) = 75%。在工程实践中,我们通常关注权重稀疏度和激活稀疏度。权重稀疏度是静态的,在模型编译时即可确定;而激活稀疏度依赖于输入数据,是动态的,通常由 ReLU 等激活函数自然产生。
4.1.2 判断方法:稀疏度评估流程
在模型剪枝后,我们需要通过以下步骤来评估稀疏化的质量:
- 统计非零值分布:使用 Python 的
numpy.count_nonzero()或 PyTorch 的torch.count_nonzero()统计每一层权重的非零个数。 - 计算层间稀疏度差异:记录每一层的稀疏度,观察是否出现某些层被过度剪枝(稀疏度>95%)而某些层未被剪枝(稀疏度<10%)的极端不均衡现象。
- 验证推理精度:将稀疏权重导出为 ONNX 或 TFLite 格式,在开发板上运行推理,对比剪枝前后的 Top-1 准确率。通常要求精度下降不超过 2%。
- 评估硬件加速比:如果目标硬件(如 Cortex-M55 配合 Ethos-U55)支持稀疏加速,需要计算理论加速比。例如,4:2 结构化稀疏(每 4 个元素中 2 个为零)理论上可带来 2 倍的计算加速。
4.1.3 案例拆解:1D 卷积的稀疏度分析
假设我们有一个简单的 1D 卷积层,输入通道为 3,输出通道为 2,卷积核大小为 3。该层权重形状为 [2, 3, 3],共 18 个参数。经过幅度剪枝后,权重分布如下:
| 输出通道 | 输入通道 | 权重值(剪枝后) | 非零个数 |
|---|---|---|---|
| 0 | 0 | [0.5, 0.0, -0.2] | 2 |
| 0 | 1 | [0.0, 0.0, 0.0] | 0 |
| 0 | 2 | [0.0, 0.8, 0.0] | 1 |
| 1 | 0 | [0.0, -0.5, 0.0] | 1 |
| 1 | 1 | [0.3, 0.0, 0.0] | 1 |
| 1 | 2 | [0.0, 0.0, 0.0] | 0 |
该层总稀疏度 = 1 - (5/18) ≈ 72.2%。然而,仔细观察发现,输出通道 0 的输入通道 1 整个被剪掉了,这意味着计算该输出通道时,可以完全跳过对输入通道 1 的读取。这种结构性稀疏(整通道稀疏)比非结构性稀疏更有价值,因为它允许硬件直接跳过整个通道的 MAC 运算,而不需要逐个判断权重是否为零。
4.1.4 常见误区:只看全局稀疏度
4.2 稀疏表示:从密集存储到压缩存储
4.2.1 核心原理:为什么要改变存储格式
如果继续使用密集矩阵存储稀疏张量,那么 72% 的稀疏度意味着 72% 的存储空间和计算带宽被浪费在零值上。稀疏表示的核心思想是只存储非零值及其位置信息。常见的格式包括 CSR、CSC、COO 和 Bitmap。
- COO(Coordinate List):存储所有非零值的 (行号, 列号, 值) 三元组。实现简单,但索引开销大。
- CSR(Compressed Sparse Row):按行压缩。使用三个数组:值数组、列索引数组、行偏移数组。行偏移数组记录每行第一个非零元素在值数组中的起始位置。
- Bitmap(位图掩码):使用一个 bit 来标记对应位置是否为零。对于结构化稀疏(如 4:2 模式),位图非常高效。
4.2.2 操作流程:CSR 格式转换示例
假设我们有一个 3x3 的稀疏矩阵:
[[1, 0, 0],
[0, 2, 3],
[0, 0, 4]]
转换为 CSR 格式的步骤如下:
- 值数组 (Values):按行优先顺序记录非零值:
[1, 2, 3, 4]。 - 列索引 (Column Indices):记录每个值对应的列号:
[0, 1, 2, 2]。 - 行偏移 (Row Offsets):记录每行第一个非零值在 Values 中的下标。第 0 行从 0 开始,第 1 行从 1 开始,第 2 行从 3 开始,最后追加一个总长度 4。因此行偏移数组为
[0, 1, 3, 4]。
在 TinyML 部署中,我们通常使用 TFLite Micro 或 CMSIS-NN 库。CMSIS-NN 针对 Cortex-M 系列内核提供了优化的稀疏矩阵乘法函数,其内部采用类似 CSR 的变种格式,并利用 DSP 指令(如 SMLAD)进行加速。
4.2.3 案例拆解:不同格式的存储开销对比
以一个 64x64 的权重矩阵为例,假设稀疏度为 87.5%(即每 8 个元素中只有 1 个非零)。
| 存储格式 | 存储内容 | 内存占用(字节) | 适用场景 |
|---|---|---|---|
| 密集 (FP32) | 64*64*4 | 16384 | 无稀疏 |
| COO (FP32) | 非零值(512*4) + 行号(512*2) + 列号(512*2) | 4096 | 高度不规则,小矩阵 |
| CSR (FP32) | 值(512*4) + 列索引(512*2) + 行偏移(65*2) | 3170 | 行稀疏,通用 |
| Bitmap + 稠密值 | 位图(64*64/8) + 非零值(512*4) | 2560 | 结构化稀疏,硬件友好 |
从表中可以看出,Bitmap 格式在结构化稀疏下存储效率最高。但需要注意的是,CSR 格式在非结构化稀疏下更灵活,而 Bitmap 要求稀疏位置必须符合固定模式(如每 4 个元素中 2 个为零)。
4.2.4 常见误区:忽略索引开销
4.3 稀疏模式:从算法到硬件的鸿沟
4.3.1 核心原理:四种稀疏模式的定义
稀疏模式决定了非零元素在张量中的分布规律。根据分布的结构化程度,我们将其分为四类:
- 不规则稀疏(Fine-grained):非零元素随机分布,无任何规律。虽然压缩率最高,但硬件需要逐个判断权重是否为零,导致控制逻辑复杂,计算单元利用率低。
- 行稀疏(Row-wise):整行要么全零,要么全非零。硬件可以跳过全零行,适合向量化计算。
- 列稀疏(Column-wise):与行稀疏类似,但以列为单位。在输出通道维度上应用较多。
- 块稀疏(Block-wise):将矩阵划分为固定大小的块(如 2x2、4x4),块内要么全零,要么全非零。这是目前硬件加速器最欢迎的模式,因为块内可以连续读取数据,充分利用 SIMD(单指令多数据)流水线。
4.3.2 判断方法:如何选择稀疏模式
选择哪种稀疏模式,取决于目标硬件的计算单元设计。以下是判断路径:
- 检查硬件是否支持跳零:如果目标 MCU 的 DSP 指令不支持条件跳零(如 Cortex-M4 的 SMLAD 不支持),则必须使用结构化稀疏。
- 评估内存带宽瓶颈:如果模型是内存密集型(Memory-bound),优先选择块稀疏,因为块稀疏可以显著减少索引存储,提高有效带宽利用率。
- 考虑剪枝算法的可实现性:块稀疏通常需要专门的剪枝算法(如 N:M 剪枝)来保证块内非零值数量一致,这比简单的幅度剪枝更复杂。
4.3.3 案例拆解:NVIDIA 2:4 结构化稀疏与 TinyML 的对比
在 GPU 领域,NVIDIA Ampere 架构引入了 2:4 结构化稀疏,即每 4 个连续元素中恰好有 2 个非零。这种模式允许硬件将稀疏矩阵乘法转换为两个密集矩阵乘法,从而实现 2 倍的推理加速。然而,在 TinyML 领域,Cortex-M 系列处理器通常不具备这种专用的稀疏加速单元。
以 ARM Ethos-U55 微神经网络处理器为例,它支持 块稀疏,块大小通常为 4x4。这意味着权重矩阵必须被划分为 4x4 的块,且每个块要么全零,要么全非零。如果剪枝算法产生的是不规则稀疏,Ethos-U55 无法直接利用,需要先通过“权重重排”将非零值聚拢到某些块中,但这会破坏原始的网络结构,可能导致精度下降。
torch.nn.utils.prune.l1_unstructured 进行初步剪枝,然后通过 reshape 操作将权重矩阵重塑为 [C_out, C_in/16, 4, 4],对每个 4x4 块计算 L2 范数,将范数小于阈值的块整体置零。
4.3.4 常见误区:稀疏模式与硬件不匹配
4.4 稀疏化对硬件影响的深度剖析
4.4.1 核心原理:三个维度的硬件影响
稀疏化对硬件的影响主要体现在三个维度:存储带宽、计算效率和控制复杂度。
- 存储带宽:稀疏表示减少了需要从 Flash/ SRAM 加载的权重字节数。例如,一个 1MB 的密集模型,若稀疏度为 75% 且使用 CSR 格式,存储量可降至约 300KB,显著减少了片外存储访问次数,降低了功耗。
- 计算效率:对于支持跳零的 MAC 阵列,跳过零值意味着在相同时钟周期内完成更多有效乘法。但前提是硬件能够高效地识别零值位置。
- 控制复杂度:不规则稀疏需要复杂的索引解码逻辑,这会增加芯片面积和静态功耗。而块稀疏可以通过简单的位掩码判断,控制逻辑简单,时钟频率可以更高。
4.4.2 操作流程:评估硬件加速比的估算方法
在模型部署前,我们可以通过以下公式估算稀疏化带来的理论加速比:
理论加速比 = 1 / ( (1 - 稀疏度) + 稀疏度 * 硬件稀疏效率 )
其中,硬件稀疏效率取决于稀疏模式与硬件架构的匹配程度。对于块稀疏(4x4),若硬件支持完整的块跳过,效率接近 1;对于不规则稀疏,效率可能仅为 0.1~0.3。
举例:假设模型稀疏度为 80%,硬件稀疏效率为 0.5,则理论加速比 = 1 / (0.2 + 0.8*0.5) = 1 / 0.6 ≈ 1.67 倍。如果硬件稀疏效率为 0(不支持跳零),则加速比为 1.0,即无加速。
4.4.3 案例拆解:Cortex-M55 上的稀疏卷积实现
我们以 CMSIS-NN 库中的 arm_convolve_s8_sparse 函数为例,分析其硬件实现逻辑。该函数针对 Cortex-M55 的 MVE(M-Profile Vector Extension)指令集优化,支持 4:2 结构化稀疏。
- 输入准备:权重矩阵被预处理为
[N, 2]的结构,其中 N 为输出通道数,每个输出通道对应 2 个非零值(因为 4:2 模式)。同时生成一个 4 位的掩码,指示哪两个位置是非零。 - 向量加载:使用
vldrb.u8指令一次性加载 4 个输入激活值,使用vctp指令根据掩码生成谓词寄存器,仅对非零位置执行乘加运算。 - 结果累加:通过
vmla.s8指令完成向量乘加,最后将结果累加到输出缓冲区。
实测数据表明,在 200MHz 的 Cortex-M55 上,对于 3x3 深度可分离卷积,采用 4:2 稀疏后,推理延迟从 12.3ms 降至 7.8ms,加速比约 1.58 倍,功耗降低约 35%。
4.4.4 常见误区:忽略负载均衡问题
4.5 本章总结与工程实践清单
稀疏化是 TinyML 模型压缩的核心技术之一,但它绝不是简单的“置零”操作。本章的核心要点归纳如下:
| 维度 | 关键结论 | 工程建议 |
|---|---|---|
| 稀疏度计算 | 稀疏度 = 1 - 非零数/总数 | 输出逐层稀疏度报告,关注计算密集层 |
| 稀疏表示 | CSR/COO 适合不规则,Bitmap 适合结构化 | 稀疏度低于 70% 时慎用压缩存储 |
| 稀疏模式 | 块稀疏 > 行/列稀疏 > 不规则稀疏(硬件友好度) | 优先选择 N:M 结构化剪枝 |
| 硬件影响 | 存储带宽↓、计算效率↑、控制复杂度↑ | 部署前必须查硬件手册确认稀疏支持 |
在实际项目中,建议遵循以下操作流程:第一步,确定目标硬件支持的稀疏模式;第二步,选择合适的剪枝算法(如用于块稀疏的 N:M 剪枝);第三步,训练时加入稀疏正则化;第四步,剪枝后使用校准集微调;第五步,导出为 TFLite 或 ONNX 格式,并使用硬件厂商提供的工具链(如 Vela 编译器)进行编译优化;第六步,在开发板上进行端到端延迟和功耗测试,验证稀疏化的实际收益。