ai_compiler_tutorial_v2

第8章:稀疏计算与压缩技术

在自动驾驶和具身智能系统中,稀疏性无处不在——从激光雷达产生的点云数据,到经过剪枝优化的神经网络权重,再到注意力机制中的稀疏连接模式。本章将深入探讨AI编译器如何高效处理稀疏数据,通过专门的表示方法、优化策略和压缩技术,在保持精度的同时大幅提升计算效率和降低内存占用。我们将特别关注自动驾驶场景中的实际挑战,如何在有限的车载计算资源上处理海量稀疏数据。

8.1 稀疏张量的表示方法

稀疏性是现实世界数据的固有特征。在自动驾驶场景中,激光雷达扫描的三维空间大部分是空的,注意力机制只关注少数关键特征,剪枝后的网络包含大量零权重。如何高效表示这些稀疏数据是编译器优化的第一步。

稀疏张量表示的核心挑战在于平衡存储效率和访问性能。理想的稀疏格式应该最小化内存占用,同时保持高效的计算访问模式。编译器必须理解不同稀疏格式的特性,才能为特定的计算模式选择最优表示。这种选择不仅影响内存使用,还直接决定了后续优化的可能性——错误的格式选择可能导致性能下降数倍甚至数十倍。

在实际系统中,稀疏数据往往呈现多样化的模式。激光雷达点云在空间上呈现聚集性,神经网络权重在剪枝后可能形成块状稀疏,而注意力矩阵则展现出动态变化的稀疏模式。编译器需要能够识别这些模式,并在编译时或运行时做出智能的格式选择决策。

8.1.1 经典稀疏格式

稀疏矩阵的存储格式设计是一个经典的时空权衡问题。每种格式都针对特定的访问模式进行了优化,理解这些格式的设计原理对于编译器优化至关重要。

坐标格式 (COO - Coordinate)

COO格式是最直观的稀疏表示方法,存储每个非零元素的坐标和值。其简单性使其成为稀疏数据的通用交换格式:

稀疏矩阵 A (4×5):
[0  2  0  0  0]
[0  0  3  0  0]
[0  0  0  0  4]
[1  0  0  5  0]

COO表示:
rows:   [0, 1, 2, 3, 3]
cols:   [1, 2, 4, 0, 3]
values: [2, 3, 4, 1, 5]

COO格式的优势在于构建简单,支持高效的稀疏矩阵加法,但随机访问性能较差。在处理点云数据初始化时,COO格式常作为中间表示。

从编译器角度看,COO格式的主要价值在于其灵活性。它不对元素顺序做任何假设,允许任意的插入和删除操作。这使得COO成为构建阶段的理想选择。然而,无序的存储也意味着缺乏局部性,导致缓存性能差。编译器通常会在构建完成后将COO转换为更高效的格式。

COO格式的内存占用为:$3 \times nnz \times sizeof(type)$,其中系数3来自于两个坐标索引加一个值。对于64位系统,如果使用32位索引和32位浮点数,每个非零元素需要12字节。这个开销在稀疏度很高时是可以接受的,但对于中等稀疏度的矩阵可能不够经济。

压缩稀疏行格式 (CSR - Compressed Sparse Row)

CSR格式通过压缩行索引提高访问效率:

CSR表示:
row_ptr: [0, 1, 2, 3, 5]  # 每行的起始位置
col_idx: [1, 2, 4, 0, 3]  # 列索引
values:  [2, 3, 4, 1, 5]  # 非零值

访问第i行:values[row_ptr[i]:row_ptr[i+1]]

CSR格式特别适合行遍历操作,是稀疏矩阵向量乘法(SpMV)的首选格式。在自动驾驶的多传感器融合中,当需要按时间序列处理数据时,CSR格式能提供最佳性能。

CSR的设计精髓在于利用行的连续性来压缩索引。通过row_ptr数组,我们只需要$m+1$个整数来编码$m$行的边界信息,而不是为每个非零元素存储行索引。这种压缩不仅节省了内存(从$3 \times nnz$降到$2 \times nnz + m + 1$),还改善了缓存局部性——同一行的元素在内存中是连续的。

编译器在处理CSR格式时可以进行多种优化:

然而,CSR也有其局限性。列访问效率低下,插入新的非零元素代价高昂(可能需要移动大量数据)。因此,CSR最适合于稀疏模式固定、以行访问为主的场景。

压缩稀疏列格式 (CSC - Compressed Sparse Column)

CSC格式是CSR的列优先版本,在列访问密集的场景(如某些图神经网络)中更高效。

选择CSR还是CSC往往取决于算法的访问模式。在矩阵向量乘法$y = Ax$中,如果按行计算($y_i = \sum_j A_{ij}x_j$),CSR更优;如果按列计算(累加每列对结果的贡献),CSC更优。编译器可以通过分析计算图来自动选择最佳格式。

其他经典格式

除了COO、CSR、CSC,还有几种值得关注的稀疏格式:

对角格式(DIA):适合带状矩阵,将非零元素按对角线存储。在处理时序卷积或局部连接时特别有效。

ELL格式(ELLPACK):将每行填充到相同长度,适合GPU上的规则并行访问。缺点是对于行长度差异大的矩阵会浪费空间。

混合格式(HYB):结合ELL和COO,规则部分用ELL,不规则部分用COO,平衡了空间效率和访问规则性。

8.1.2 分块稀疏表示

实际的AI模型往往展现出块状稀疏模式,特别是在结构化剪枝后:

分块稀疏矩阵(块大小 2×2):
[B1  0   B2  0 ]
[0   B3  0   0 ]
[0   0   0   B4]
[B5  0   0   0 ]

其中 Bi 是密集的 2×2 块

BCSR (Block CSR) 格式:

分块稀疏的优势:

  1. 更好的缓存局部性:块内数据连续存储
  2. 向量化友好:块操作可以使用SIMD指令
  3. 减少索引开销:以块为单位索引

分块稀疏是现代AI编译器的重要优化方向。许多深度学习模型在结构化剪枝后自然形成块状稀疏模式。例如,当我们剪枝整个卷积核或注意力头时,权重矩阵中会出现规则的零块。

块大小的选择是一个关键的调优参数。较小的块(如2×2或4×4)提供更细粒度的稀疏性,但索引开销较高;较大的块(如16×16或32×32)更适合向量化和缓存,但可能包含更多的零元素。编译器可以通过启发式规则或自动调优来选择最优块大小:

\[\text{块大小} = \arg\min_{b} \left( \frac{\text{索引开销}(b)}{b^2} + \text{填充开销}(b) \right)\]

其中索引开销随块数量增加,填充开销是将稀疏块填充为密集块引入的额外零元素。

编译器在检测到规则的稀疏模式时,可以自动转换为分块格式:

稀疏度分析:
if (稀疏模式规则 && 块内密度 > 阈值):
    转换为BCSR格式
    块大小 = 自动调优(硬件特性, 稀疏模式)

分块格式的变体

根据应用需求,分块稀疏有多种变体:

  1. 可变块大小(VBS):不同区域使用不同的块大小,适应稀疏度的空间变化。编译器需要额外的元数据来记录每个块的大小。

  2. 层次化分块:递归地应用分块,形成多级结构。大块内部可以进一步分解为小块,类似于缓存层次结构。

  3. 混合精度分块:不同的块可以使用不同的数值精度。关键块保持高精度,次要块使用低精度,实现精度和效率的平衡。

8.1.3 动态稀疏性vs静态稀疏性

静态稀疏性:稀疏模式在编译时已知

动态稀疏性:稀疏模式运行时才确定

编译器策略对比:

静态稀疏优化:
- 编译时生成专门的计算核
- 完全展开的循环
- 预计算的内存访问模式
- 零开销的格式转换

动态稀疏优化:
- JIT编译支持
- 自适应的格式选择
- 运行时稀疏度监控
- 渐进式优化策略

静态稀疏性允许编译器进行激进的优化。知道确切的稀疏模式后,编译器可以:

动态稀疏性则需要更灵活的处理策略。编译器必须生成能够适应不同稀疏模式的代码。这通常涉及:

稀疏性的传播分析

编译器需要追踪稀疏性在计算图中的传播。不同的操作对稀疏性有不同的影响:

8.1.4 混合精度稀疏表示

在量化场景中,稀疏性和低精度可以结合:

混合表示策略:
- 索引:int16 (节省内存)
- 非零值:int8/int4 (量化)
- 标量因子:fp16 (保持精度)

内存节省 = 稀疏化收益 × 量化收益

8.2 稀疏矩阵乘法优化

稀疏矩阵乘法(SpGEMM)是许多AI工作负载的核心操作。与密集矩阵乘法的$O(n^3)$复杂度不同,稀疏矩阵乘法的性能取决于非零元素的分布模式。这种对数据依赖的特性使得稀疏矩阵乘法的优化成为编译器设计中的一个关键挑战。

稀疏矩阵乘法的核心难题在于输出稀疏结构的不可预测性。给定两个稀疏矩阵A和B,它们的乘积C = A × B的稀疏模式取决于A和B中非零元素的具体分布。这种不确定性导致了内存分配、负载均衡和并行化等多方面的挑战。编译器必须在保守的静态分析和激进的动态优化之间找到平衡。

8.2.1 稀疏模式分析

编译器需要分析稀疏模式以选择最优算法。这种分析可以在编译时(对于静态稀疏)或运行时(对于动态稀疏)进行:

模式特征提取:
- 稀疏度 ρ = nnz/(m×n)
- 行/列非零分布方差
- 带宽(对角附近的集中度)
- 块状结构检测
- 对称性和反对称性
- 聚类系数(局部密度)

稀疏度的分层分析

简单的全局稀疏度指标往往不足以指导优化决策。编译器需要进行多尺度的稀疏度分析:

全局稀疏度: ρ_global = nnz/(m×n)
行稀疏度分布: ρ_row[i] = nnz(row_i)/n
列稀疏度分布: ρ_col[j] = nnz(col_j)/m
块稀疏度: ρ_block(b) = nnz(block)/block_size²

这种分层分析能够揭示稀疏数据的内在结构,帮助编译器选择合适的优化策略。例如,如果行稀疏度方差很大,说明存在热点行,需要特殊的负载均衡策略。

典型模式及优化策略:

  1. 均匀稀疏(如随机剪枝):
    • 使用哈希表加速乘累加
    • 负载均衡的并行策略
    • 预期的输出稀疏度可预测
    • 适合静态内存分配
  2. 幂律分布(如社交网络、注意力图):
    • 分离热点行/列特殊处理
    • 自适应的任务粒度
    • 两阶段执行:先处理热点,再处理长尾
    • 动态调度避免负载倾斜
  3. 带状稀疏(如时序卷积):
    • 利用带宽限制减少搜索空间
    • 向量化的带内计算
    • 缓存预取优化明显
    • 可以使用专门的带状矩阵算法
  4. 块对角稀疏(如多任务学习):
    • 独立处理各个块
    • 天然的并行性
    • 无需全局同步
    • 内存访问局部性极好
  5. 层次稀疏(如多分辨率表示):
    • 递归的分治策略
    • 多级缓存优化
    • 自适应的精度控制

8.2.2 计算核优化策略

稀疏矩阵乘法有多种算法,每种都有其适用场景。编译器需要根据输入特征选择或组合这些算法。

Gustavson算法(行优先):

对于 C = A × B,其中A是CSR格式:
for i in 行(A):
    for k in 非零(A[i,:]):
        for j in 非零(B[k,:]):
            C[i,j] += A[i,k] * B[k,j]

Gustavson算法的核心思想是按行累积结果。它的优势在于访问模式规则,适合CSR格式。但是,当B的某一行有大量非零元素时,内层循环会变得很长,影响性能。

优化技术:

  1. 稀疏累加器(SPA):用于高效累加中间结果
    • 使用密集向量作为工作空间
    • 标记数组记录非零位置
    • 避免重复的查找操作
    • 计算完成后压缩回稀疏格式
  2. 符号预测:预先计算输出的稀疏结构
    • 第一遍:只计算输出的非零模式
    • 第二遍:填充实际数值
    • 优点:精确的内存分配,无需重分配
    • 缺点:需要两次遍历,增加内存带宽
  3. 内存预分配:基于上界估计避免动态分配
    • 理论上界:min(nnz(A)×avg_nnz_per_row(B), m×n)
    • 实践中使用启发式:1.5×预期非零数
    • 渐进式扩容:初始分配+按需增长
  4. 向量化优化:
    • 将稀疏操作转换为小的密集操作
    • 使用SIMD指令处理连续的非零元素
    • 掩码操作处理边界情况
  5. 缓存阻塞:
    • 将矩阵分块以适应缓存大小
    • 块内使用密集算法
    • 块间保持稀疏表示

并行化策略:

任务划分方案:
1. 行级并行:
   - 每个线程处理若干行
   - 适合输出行负载均衡的情况
   - 无需同步,但可能负载不均
   - 实现简单,缓存友好

2. 非零元素并行:
   - 按非零元素数量均分任务
   - 需要原子操作或规约
   - 负载均衡好,但同步开销大
   - 适合GPU等大规模并行架构

3. 2D分块:
   - 将矩阵分成块
   - 块间并行,块内串行
   - 减少同步开销
   - 需要仔细的块大小调优

4. 流水线并行:
   - 将SpGEMM分解为多个阶段
   - 不同阶段处理不同的数据
   - 隐藏内存延迟
   - 适合流式处理场景

GPU上的稀疏矩阵乘法

GPU的大规模并行特性为稀疏矩阵乘法带来了新的机遇和挑战:

GPU优化策略:
1. Warp级并行:
   - 32个线程协作处理一行
   - 使用shuffle指令通信
   - 减少全局内存访问

2. 合并内存访问:
   - 重排数据提高合并度
   - 使用纹理内存缓存不规则访问
   - 预取下一批数据

3. 动态并行:
   - 根据稀疏度动态启动kernel
   - 自适应的线程块配置
   - 避免线程发散

8.2.3 负载均衡问题

稀疏计算的最大挑战是负载不均衡。不同行或列的计算量可能相差数个数量级,导致某些处理器空闲而其他处理器过载。

负载估算模型:
工作量(row_i) = Σ(nnz(A[i,:]) × nnz(B[:,j]))

实际工作量还需考虑:
- 缓存未命中的开销
- 内存带宽竞争
- 同步等待时间
- 间接访问的延迟

负载均衡技术

  1. 静态负载均衡: ``` 预处理阶段:
    • 计算每行的预期工作量
    • 按工作量排序
    • 循环分配或分箱打包

    优点:无运行时开销 缺点:预测可能不准确 ```

  2. 动态负载均衡: ``` 动态调度策略:
    • 工作窃取队列
    • 自适应块大小
    • 两阶段执行(预处理+计算)

    优点:自适应实际负载 缺点:调度开销 ```

  3. 混合策略: ``` 分层调度:
    • 粗粒度静态分配
    • 细粒度动态调整
    • 基于历史的预测 ```

工作窃取算法

工作窃取是解决负载不均的有效方法:

每个线程维护本地队列:
while (有任务) {
    if (本地队列非空) {
        处理本地任务
    } else {
        从其他线程窃取任务
    }
}

窃取策略:
- 从最忙的线程窃取
- 窃取一半的任务
- 优先窃取大任务

自动驾驶场景优化:

在处理激光雷达和相机融合时,常见的稀疏模式是空间局部性。这种局部性可以被编译器利用来优化计算:

空间感知优化:
1. 将3D空间划分为体素
   - 自适应体素大小
   - 八叉树加速搜索
   - 哈希表快速索引

2. 只计算相邻体素间的交互
   - k-近邻搜索
   - 半径搜索
   - 基于距离的权重

3. 利用空间索引加速邻居查找
   - KD树索引
   - R树索引
   - 网格哈希

4. 预测移动物体的稀疏模式变化
   - 基于速度的外推
   - 时序一致性利用
   - 增量式更新

时序优化

自动驾驶系统的实时性要求带来了特殊的优化机会:

帧间相关性利用:
1. 增量计算:
   - 只更新变化的部分
   - 重用上一帧的结果
   - 差分编码

2. 预测性计算:
   - 基于运动模型预计算
   - 投机执行可能的路径
   - 异步计算未来帧

3. 优先级调度:
   - 关键区域优先
   - 近距离优先
   - 动态物体优先

这些优化策略需要编译器理解应用的语义,并生成相应的特化代码。通过领域特定的优化,可以获得比通用稀疏矩阵乘法高出数倍的性能。

8.3 模型量化与剪枝的编译器支持

模型压缩是部署AI模型到边缘设备的关键技术。编译器在这个过程中扮演重要角色,不仅要支持压缩后模型的高效执行,还要在压缩过程中提供优化。

在自动驾驶场景中,车载计算资源受限且对功耗敏感。一个未压缩的深度学习模型可能需要数GB的内存和数十TFLOPS的计算能力,远超出车载平台的能力。通过量化和剪枝,我们可以将模型大小减少90%以上,同时保持可接受的精度损失。

编译器的核心挑战在于如何在不同的压缩技术之间找到最优组合。量化降低数值精度,剪枝删除不重要的连接,两者结合可以达到更高的压缩率。但这种组合也带来了复杂性——量化后的稀疏计算需要特殊的硬件支持,而不同层的敏感度差异需要精细的策略控制。

8.3.1 量化策略与精度权衡

量化是将高精度浮点数转换为低精度整数的过程。这个过程不可避免地引入误差,关键是如何最小化这种误差对模型性能的影响。

量化类型:

  1. 均匀量化: \(x_{int} = round(\frac{x_{float} - z}{s})\) 其中s是缩放因子,z是零点

    均匀量化的关键参数:

    • 缩放因子s:决定量化的粒度
    • 零点z:允许非对称的表示范围
    • 位宽:INT8、INT4、甚至INT2

    编译器需要优化这些参数的选择,以最小化量化误差: \(\min_{s,z} \sum_i (x_i - dequant(quant(x_i, s, z), s, z))^2\)

  2. 非均匀量化:
    • 对数量化:更多比特表示小值
    • 学习型量化:通过训练学习量化函数
    • 查找表量化:使用码本实现任意映射

    非均匀量化能更好地适应数据分布,但需要更复杂的硬件支持。

  3. 向量量化: 将多个权重组合成向量进行联合量化:
    • 减少量化参数的存储开销
    • 利用权重间的相关性
    • 更好的压缩率

编译器的量化支持:

量化流程:
1. 量化模式分析
   - 逐层 vs 逐通道 vs 逐组
   - 对称 vs 非对称
   - 静态 vs 动态
   
2. 量化参数校准
   - 收集激活值统计
   - 计算最优缩放因子
   - 最小化量化误差
   - KL散度优化

3. 算子替换
   - float32 → int8/int4
   - 插入量化/反量化节点
   - 融合相邻量化操作
   - 重排序以减少转换

量化感知训练(QAT)vs 训练后量化(PTQ)

编译器需要支持两种不同的量化路径:

  1. QAT(Quantization-Aware Training): ``` 训练时模拟量化:
    • 前向传播:使用量化权重
    • 反向传播:使用全精度梯度
    • 直通估计器(STE)处理不可微

    编译器优化:

    • 融合模拟量化操作
    • 优化梯度计算
    • 支持混合精度训练 ```
  2. PTQ(Post-Training Quantization): ``` 无需重新训练:
    • 使用校准数据集
    • 统计激活值分布
    • 优化量化参数

    编译器策略:

    • 层级敏感度分析
    • 自适应位宽分配
    • 误差补偿机制 ```

混合精度优化:

敏感度分析:
for layer in model:
    敏感度 = 计算量化损失(layer)
    if 敏感度 > 阈值:
        保持高精度
    else:
        应用激进量化

自动混合精度策略:
- 关键路径保持fp16
- 非关键路径int8
- 大矩阵乘法int4
- 残差连接fp16(保持梯度流)

编译器在混合精度优化中的角色:

  1. 自动精度分配:基于性能模型和精度约束自动决定每层的精度
  2. 转换插入:在不同精度之间自动插入转换操作
  3. 融合优化:将多个转换操作合并,减少开销
  4. 布局优化:根据硬件特性优化数据布局

8.3.2 结构化剪枝vs非结构化剪枝

剪枝是通过删除不重要的权重来减小模型大小的技术。不同的剪枝策略对编译器优化提出了不同的要求。

非结构化剪枝:

非结构化剪枝的挑战:

结构化剪枝:

结构化剪枝的粒度:

  1. 细粒度:单个通道或过滤器
  2. 中粒度:通道组或卷积核组
  3. 粗粒度:整个层或子网络

半结构化剪枝:

近年来出现的一种折中方案:

N:M稀疏:每M个元素中最多保留N个非零
例如 2:4 稀疏:
[a, b, 0, 0]  ✓
[a, 0, b, 0]  ✓
[a, b, c, 0]  ✗ (超过2个非零)

优势:
- 规则的稀疏模式
- 硬件可加速(如NVIDIA A100)
- 平衡灵活性和效率

编译器的剪枝感知优化:

结构化剪枝优化:
1. 通道重排
   - 将保留的通道连续存储
   - 消除间接访问
   - 更新布局信息

2. 维度压缩
   - 更新张量shape
   - 调整相关算子参数
   - 重新计算内存分配

3. 死代码消除
   - 移除被剪枝部分的计算
   - 简化控制流
   - 合并相邻操作

非结构化剪枝优化:
1. 稀疏格式选择
   - 基于稀疏度选择COO/CSR/BCSR
   - 考虑硬件特性
   - 平衡存储和计算
   
2. 计算核特化
   - 为特定稀疏模式生成代码
   - 向量化优化
   - 循环展开
   
3. 动态调度
   - 运行时选择最优实现
   - 自适应算法选择
   - 性能监控与调优

剪枝策略的选择

编译器需要根据多个因素选择剪枝策略:

决策因素:
1. 硬件支持:
   - 是否有稀疏加速单元
   - 内存带宽限制
   - 缓存大小

2. 模型特性:
   - 层的类型和大小
   - 稀疏度分布
   - 精度要求

3. 部署约束:
   - 实时性要求
   - 内存限制
   - 功耗预算

策略选择算法:
if 硬件支持稀疏:
    if 稀疏度 > 90%:
        非结构化剪枝
    elif 稀疏度 > 50%:
        N:M半结构化
    else:
        结构化剪枝
else:
    结构化剪枝

8.3.3 编译时优化机会

量化和剪枝为编译器提供了丰富的优化机会。通过理解压缩后模型的特性,编译器可以生成更高效的代码。

常量折叠与传播:

量化感知的常量折叠:
- 预计算量化参数
- 折叠静态量化/反量化对
- 合并连续的缩放操作
- 消除冗余转换

示例:
原始:x → 量化 → 反量化 → 缩放(2.0) → 量化
优化:x → 量化(scale*2.0)

更复杂的例子:
原始:
  x → Quant(s1) → Dequant(s1) → Conv → 
  Quant(s2) → Dequant(s2) → Add → Quant(s3)
优化后:
  x → QuantizedConvAdd(s1, s2, s3)

编译器通过模式匹配识别这些优化机会,并应用预定义的转换规则。关键是保持数值等价性,同时减少运行时开销。

算子融合:

量化算子融合模式:
1. Conv + BN + ReLU + Quantize
   → QuantizedConvBNReLU
   优势:
   - 减少内存读写
   - 避免中间结果的量化误差
   - 更好的指令级并行
   
2. Dequantize + MatMul + Quantize  
   → QuantizedMatMul
   优势:
   - 直接在INT8域计算
   - 减少类型转换开销
   - 利用专门的硬件指令

3. 多个Requantize
   → 单个Requantize(组合scale)
   优势:
   - 减少舍入误差累积
   - 更少的计算操作

4. 量化感知的层归一化:
   LayerNorm + Quantize → QuantizedLayerNorm
   - 在归一化过程中直接量化
   - 避免中间的浮点表示

融合的关键在于识别数据流依赖关系,确保融合后的算子在数值上等价于原始序列。编译器需要维护一个融合模式库,并根据硬件能力选择合适的融合策略。

内存布局优化:

量化张量的打包策略:
- INT4: 两个元素打包成一个字节
  [a|b] 其中 a占高4位,b占低4位
  提取:a = (byte >> 4) & 0xF
        b = byte & 0xF
  
- 位打包:利用位操作提取
  INT2: 4个元素一字节
  INT1: 8个元素一字节(二值网络)
  
- SIMD友好的交错布局
  将相邻元素排列以适应向量寄存器宽度
  例:INT8×16 对齐到 AVX512

自动驾驶优化:
- 将多个传感器的int8数据打包
  相机RGB:3×INT8 → 打包成32位
  雷达深度:INT16
  组合成缓存行对齐的结构
  
- 利用向量指令并行处理
  单指令处理多个传感器数据
  
- 减少内存带宽需求
  打包后带宽减少75%(INT8 vs FP32)

编译器的布局选择策略:

布局决策树:
1. 分析访问模式
   - 顺序访问 → 连续布局
   - 跨步访问 → 分块布局
   - 随机访问 → 哈希布局

2. 考虑硬件特性
   - 缓存行大小
   - SIMD宽度
   - 内存对齐要求

3. 优化目标
   - 最小化内存占用
   - 最大化带宽利用
   - 减少解包开销

8.4 激光雷达点云的稀疏处理

激光雷达是自动驾驶的核心传感器,产生的点云数据天然稀疏。一个64线激光雷达每秒产生约200万个点,但这些点在3D空间中的分布极其稀疏(通常<0.01%的体素被占据)。这种极端的稀疏性既是挑战也是机遇——挑战在于如何高效表示和处理如此稀疏的数据,机遇在于可以通过稀疏性大幅减少计算量。

点云处理的独特之处在于其三维空间特性和时序连续性。与图像或文本数据不同,点云在空间中没有规则的网格结构,点的分布受物理规律支配——近处密集、远处稀疏,反射表面产生密集点,而空气中没有点。编译器需要理解这些特性,才能生成高效的处理代码。

8.4.1 点云数据特性

空间分布特征:

点云稀疏性分析:
- 近处密集,远处稀疏(1/r²衰减)
  10m处:~10000点/m³
  50m处:~400点/m³
  100m处:~100点/m³
  
- 垂直方向固定角分辨率
  64线雷达:0.4°间隔
  128线雷达:0.2°间隔
  导致垂直方向的非均匀采样
  
- 水平方向均匀采样
  典型:0.1°-0.4°分辨率
  10Hz旋转频率
  
- 动态物体的时序连续性
  运动物体产生拖尾效应
  需要运动补偿

点云的统计特性:

大规模点云数据分析显示了几个重要模式:

  1. 空间聚类性:点倾向于聚集在物体表面
  2. 稀疏度的空间变化:不同区域的稀疏度差异可达1000倍
  3. 时序相关性:连续帧之间70-90%的点云结构相似
  4. 噪声特性:边缘和远距离点噪声较大

这些特性指导了编译器的优化策略选择。

数据结构选择:

  1. 体素网格(Voxel Grid): ``` 3D空间划分:
    • 固定大小体素: 优点:简单、快速索引 O(1) 缺点:内存浪费严重 适用:小范围、高密度区域

    • 八叉树: 优点:自适应分辨率、内存高效 缺点:访问慢 O(log n)、构建开销大 适用:大范围、变密度场景

    • 哈希表: 优点:平衡存储和访问、动态扩展 缺点:哈希冲突、缓存不友好 适用:中等规模、动态场景

    体素大小选择:

    • 0.1m:高精度,适合近距离检测
    • 0.2m:平衡精度和效率
    • 0.5m:快速处理,远距离感知 ```
  2. 点柱(Pillar)表示: ``` 将3D转2D+特征:
    • xy平面划分网格(如0.16m×0.16m)
    • z方向编码为特征向量
    • 每个pillar最多N个点(如32或64)
    • 适合2D卷积处理

    编码策略:

    • 中心化坐标:相对于pillar中心
    • 增强特征:添加反射强度、时间戳
    • 统计特征:均值、方差、最大最小值

    优势:

    • 将稀疏3D转为密集2D
    • 利用成熟的2D CNN
    • 计算效率高 ```
  3. 范围图像(Range Image): ``` 球坐标投影:
    • 保持激光雷达的采样模式 水平:0-360°映射到图像宽度 垂直:扫描线映射到图像高度

    • 规则的2D表示 64线 → 64×2048 图像 每像素存储:深度、强度、坐标

    • 丢失部分3D信息 遮挡关系丢失 多次反射合并

    优化技巧:

    • 利用图像处理硬件加速
    • 卷积可直接应用
    • 适合实时处理 ```

混合表示策略:

实践中常采用混合策略:

近距离(<30m):体素网格,高分辨率
中距离(30-70m):点柱表示,平衡效率
远距离(>70m):范围图像,快速处理

动态切换:
根据点密度自动选择表示方法
编译器生成多版本代码
运行时根据输入特性分发

8.4.2 空间索引结构

高效的空间索引是点云处理的基础。不同的索引结构适合不同的查询模式和更新频率。

KD树优化:

平衡策略:
- 中位数分割:
  保证平衡,但可能不优化查询
  O(n log n)构建时间
  
- SAH(表面积启发式):
  最小化查询代价期望
  代价 = 遍历代价 + 相交测试代价
  更好的查询性能,构建慢
  
- 动态重平衡阈值:
  不平衡度 > 1.5时触发重平衡
  局部重构而非全局重建
  
- 缓存友好的节点布局:
  BFS顺序存储提高局部性
  节点大小对齐缓存行
  热点节点聚集存储

并行构建:
- 自顶向下并行分割
  每层并行处理所有节点
  同步开销随深度增加
  
- 任务队列+工作窃取
  动态负载均衡
  减少线程空闲
  
- SIMD加速距离计算
  4-8个点同时计算距离
  利用AVX2/AVX512指令

自适应空间索引:

根据查询模式选择索引:

1. K近邻查询为主 → KD树
   优化分割策略倾向于球形区域
   
2. 范围查询为主 → R树
   最小化边界框重叠
   
3. 动态插入删除 → 哈希网格
   O(1)插入删除
   牺牲一定查询效率

4. 混合查询 → 多级索引
   粗粒度:网格索引
   细粒度:局部KD树
   平衡各种操作

哈希表索引:

空间哈希函数:
hash(x,y,z) = ((x*p1) XOR (y*p2) XOR (z*p3)) mod M

其中p1,p2,p3是大质数(如73856093, 19349663, 83492791)
M是哈希表大小(通常为2的幂)

改进的哈希函数:
1. Morton编码(Z-order):
   保持空间局部性
   相邻体素映射到相近位置
   
2. 分层哈希:
   不同LOD使用不同哈希表
   粗粒度快速过滤
   细粒度精确查询

冲突处理:
- 开放寻址:
  线性探测:缓存友好
  二次探测:减少聚集
  双重哈希:更均匀分布
  
- 链表法:
  动态性好
  内存不连续
  适合稀疏场景
  
- 布谷鸟哈希:
  最坏O(1)查询
  插入可能级联移动
  适合静态场景

动态扩容策略:
- 负载因子 > 0.7时扩容
- 渐进式rehash避免卡顿
- 保留旧表直到迁移完成

编译器的索引优化:

编译时优化:
1. 索引特化:
   根据体素大小生成特化代码
   消除运行时除法(用位移代替)
   
2. 内联关键路径:
   哈希计算内联
   短路径遍历内联
   
3. 预取优化:
   预测访问模式
   提前加载可能的节点

运行时优化:
1. 自适应策略:
   监控查询模式
   动态切换索引类型
   
2. 缓存优化:
   热点区域缓存
   LRU替换策略
   
3. 批量查询:
   合并相似查询
   共享中间结果

8.4.3 实时处理优化

实时性是自动驾驶系统的硬性要求。点云处理必须在100ms内完成(10Hz),为后续的规划控制留出时间。

流式处理架构:

管道并行:
激光数据 → 预处理 → 体素化 → 特征提取 → 检测
    ↓         ↓         ↓          ↓         ↓
  缓冲区    缓冲区    缓冲区     缓冲区    缓冲区
   (2帧)     (1帧)     (1帧)      (1帧)     (1帧)

时序分析:
- 预处理:10ms(运动补偿、去噪)
- 体素化:15ms(空间索引构建)
- 特征提取:20ms(几何特征计算)
- 检测:30ms(目标识别)
- 总延迟:75ms < 100ms要求

每阶段可以处理不同帧,提高吞吐量
吞吐量 = 1 / max(各阶段时间) = 1/30ms = 33Hz

数据并行优化:

空间分区并行:
1. 将点云空间分成多个区域
2. 每个线程/核心处理一个区域
3. 边界区域需要同步

分区策略:
- 均匀网格:简单但负载可能不均
- 八叉树分区:自适应但有开销
- 基于密度:均衡负载但需预处理

并行粒度选择:
粗粒度(区域级):
  - 通信开销小
  - 负载不均衡风险
  
细粒度(点级):
  - 负载均衡好
  - 同步开销大
  
混合粒度:
  - 区域间粗粒度
  - 区域内细粒度

增量更新策略:

时序优化:
- 只更新变化的体素
  变化检测:新增、删除、移动
  典型情况:<20%体素变化
  加速比:3-5x
  
- 运动补偿的点云配准
  使用IMU/里程计信息
  补偿车辆运动
  对齐到世界坐标系
  
- 滑动窗口的地图维护
  保持最近N帧(如10帧)
  指数衰减的时间权重
  关键帧选择策略

差分计算:
new_features = update(old_features, new_points, removed_points)
而非完全重算

具体实现:
1. 标记变化体素
2. 增量更新特征
3. 传播影响区域
4. 局部重计算

性能收益:
- CPU时间减少60-70%
- 内存带宽减少50%
- 缓存命中率提高80%

运动预测优化:

基于历史的预测:
1. 跟踪动态物体
   - 卡尔曼滤波预测轨迹
   - 提前分配计算资源
   
2. 预计算可能区域
   - 基于速度外推
   - 生成兴趣区域(ROI)
   
3. 自适应采样率
   - 静态区域降采样
   - 动态区域高密度
   
4. 预取优化
   - 预测内存访问模式
   - 提前加载数据

编译器优化机会:

  1. 自动向量化:
    点云变换的SIMD优化:
    for点并行:
      [x',y',z'] = R*[x,y,z] + t
       
    编译器识别并生成:
    - AVX512的16点并行
      vmovups zmm0, [points]      // 加载16个x坐标
      vmulps zmm1, zmm0, R[0]     // 乘以旋转矩阵
      vaddps zmm2, zmm1, t[0]     // 加上平移
         
    - NEON的4点并行
      vld1q_f32 加载4个坐标
      vmulq_f32 矩阵乘法
      vaddq_f32 平移
         
    - GPU的1024+点并行
      每个线程处理一个点
      warp级别同步
    
  2. 内存预取: ``` 基于激光扫描模式的预取:
    • 预测下一个扫描位置 方位角增量固定 预取下一扇区数据

    • 提前加载相关体素 基于光线追踪预测 预取可能相交的体素

    • 隐藏内存延迟 计算与预取重叠 双缓冲机制

    效果:

    • 缓存命中率提升30%
    • 内存停顿减少50% ```
  3. 稀疏卷积优化: ``` Submanifold稀疏卷积:
    • 只在有点的位置计算 输入稀疏 → 输出稀疏 避免稀疏性扩散

    • 保持稀疏性不扩散 严格保持相同的稀疏模式 计算量与稀疏度成正比

    • 规则化内存访问模式 预计算卷积索引表 连续内存访问

    编译器优化:

    • 生成特化的卷积核
    • 消除零乘法
    • 循环展开和向量化
    • 利用稀疏模式对称性

    性能提升:

    • 相比密集卷积:10-100x
    • 相比通用稀疏:2-3x ```
  4. 融合算子: ``` 点云处理算子融合:
    • 体素化+特征提取 避免中间体素表示 直接生成特征

    • 卷积+池化+激活 单次遍历完成 减少内存访问

    • 多尺度特征融合 并行计算不同尺度 共享中间结果 ```

本章小结

稀疏计算和压缩技术是AI编译器优化的核心领域,特别在资源受限的边缘部署场景中至关重要。本章的关键要点:

  1. 稀疏表示的选择依赖于数据特性和访问模式:
    • COO适合构建和随机插入
    • CSR/CSC适合矩阵向量乘法
    • BCSR适合块状稀疏和SIMD优化
  2. 稀疏矩阵乘法的优化需要考虑:
    • 稀疏模式分析和算法选择
    • 负载均衡和并行化策略
    • 内存访问优化和缓存利用
  3. 模型压缩需要编译器的全方位支持:
    • 量化的精度-性能权衡
    • 结构化vs非结构化剪枝的不同优化路径
    • 编译时和运行时的协同优化
  4. 点云处理展示了领域特定的稀疏优化:
    • 空间数据结构的选择
    • 流式处理和增量更新
    • 硬件感知的并行化策略

关键公式总结:

常见陷阱与错误 (Gotchas)

1. 稀疏格式转换开销

陷阱:频繁的格式转换可能抵消稀疏计算的收益

错误模式:
密集 → CSR → 计算 → 密集 → COO → 计算 → 密集

正确做法:
- 维持一致的稀疏格式
- 批量转换而非逐个操作
- 考虑转换成本的分摊

2. 负载不均衡导致的性能退化

陷阱:稀疏计算的并行效率可能很低

诊断方法:
- 监控各线程的执行时间
- 检查任务队列的长度分布
- 分析等待时间占比

解决方案:
- 动态任务调度
- 工作窃取
- 两级并行化

3. 缓存不友好的访问模式

陷阱:稀疏数据的随机访问破坏局部性

优化技巧:
- 重排序提高局部性
- 分块处理
- 预取关键数据

4. 量化精度损失的累积

陷阱:级联的量化操作导致精度崩溃

缓解策略:
- 跳跃连接使用高精度
- 关键层保持fp16
- 定期的精度校验点

5. 动态稀疏的内存管理

陷阱:动态分配导致内存碎片和性能抖动

最佳实践:
- 内存池预分配
- 基于历史的容量预测
- 渐进式扩容策略

练习题

🟢 练习 8.1:稀疏格式选择

给定一个1000×1000的矩阵,其中只有对角线和第一行、第一列有非零元素(共2999个非零元素)。请问:

  1. 计算该矩阵的稀疏度
  2. 选择最适合的稀疏存储格式并说明理由
  3. 估算使用该格式相比密集存储的内存节省比例

💡 提示:考虑不同格式的存储开销和访问模式。

📝 参考答案 1. 稀疏度 ρ = 2999/(1000×1000) ≈ 0.003 (0.3%) 2. 最适合的格式是CSR或CSC: - 第一行有1000个非零元素(密集) - 其余行最多2个非零元素(极稀疏) - CSR格式可以高效表示这种行稀疏度差异大的矩阵 3. 内存节省计算: - 密集存储:1000×1000×4字节 = 4MB(假设float32) - CSR存储: - values: 2999×4 = 11,996字节 - col_idx: 2999×4 = 11,996字节 - row_ptr: 1001×4 = 4,004字节 - 总计:约28KB - 节省比例:1 - 28/4000 ≈ 99.3%

🟢 练习 8.2:稀疏矩阵乘法复杂度

两个n×n的稀疏矩阵A和B相乘,A有m个非零元素,B平均每列有k个非零元素。

  1. 估算使用CSR格式的Gustavson算法的时间复杂度
  2. 在什么条件下稀疏矩阵乘法比密集矩阵乘法更快?

💡 提示:考虑算法的嵌套循环结构和实际执行的运算次数。

📝 参考答案 1. 时间复杂度分析: - 外层循环:遍历A的所有行(n次) - 中层循环:遍历每行的非零元素(平均m/n个) - 内层循环:遍历B对应行的非零元素(平均k个) - 总复杂度:O(n × m/n × k) = O(mk) 2. 稀疏优于密集的条件: - 密集矩阵乘法:O(n³) - 稀疏矩阵乘法:O(mk) - 当 mk < n³ 时稀疏更快 - 即:当 m < n³/k 时 - 例如:如果B每列平均10个非零元素,A的非零元素需少于n³/10 实际考虑: - 稀疏计算有额外的索引开销 - 通常当稀疏度 < 10%时才有明显优势 - 还需考虑缓存效率和并行化难度

🟡 练习 8.3:量化误差分析

一个神经网络层的权重分布近似为正态分布N(0, 0.5²),现要量化为INT8(-128到127)。

  1. 设计对称量化方案(零点为0)
  2. 计算理论量化误差的期望值
  3. 如果该层对精度特别敏感,提出改进方案

💡 提示:利用3σ原则确定量化范围,考虑截断误差和舍入误差。

📝 参考答案 1. 对称量化方案设计: - 使用3σ原则:范围 [-1.5, 1.5] 覆盖99.7%的值 - 缩放因子:s = 1.5/127 ≈ 0.0118 - 量化公式:q = round(x/s) - 反量化:x' = q × s 2. 量化误差分析: - 舍入误差:均匀分布在[-s/2, s/2] - 舍入误差期望:0 - 舍入误差方差:s²/12 ≈ 1.16×10⁻⁵ - 截断误差(|x|>1.5的0.3%): - 期望误差贡献很小 - 总体RMSE ≈ 0.0034 3. 改进方案: - **非均匀量化**:在0附近分配更多量化级别 - **逐通道量化**:每个输出通道独立量化 - **混合精度**:该层使用INT16或保持FP16 - **知识蒸馏**:用全精度模型指导量化模型训练 - **学习型量化**:通过反向传播学习最优量化参数

🟡 练习 8.4:结构化剪枝策略

一个卷积层有64个输入通道和128个输出通道,卷积核大小3×3。现需要剪枝50%的参数。

  1. 比较通道剪枝和非结构化剪枝的参数分布
  2. 分析两种方案对推理速度的影响
  3. 设计一个渐进式剪枝策略

💡 提示:考虑硬件对不同稀疏模式的支持程度。

📝 参考答案 1. 参数分布比较: - 总参数:64×128×3×3 = 73,728 - 目标:剪除36,864个参数 **通道剪枝**: - 方案A:剪除32个输入通道 - 剩余:32×128×3×3 = 36,864参数 - 方案B:剪除64个输出通道 - 剩余:64×64×3×3 = 36,864参数 - 规则的密集计算 **非结构化剪枝**: - 任意位置剪除50%权重 - 不规则的稀疏模式 - 需要存储索引信息 2. 推理速度影响: **通道剪枝**: - 实际FLOPS减少50% - 可使用标准密集计算库 - 内存访问连续 - 预期加速:1.8-2.0x **非结构化剪枝**: - 理论FLOPS减少50% - 需要专门的稀疏计算库 - 随机内存访问 - 实际加速:1.2-1.5x(受限于内存带宽) 3. 渐进式剪枝策略: ``` 阶段1(epoch 1-10):非结构化剪枝30% - 识别不重要的权重 - 保持网络容量用于适应 阶段2(epoch 11-20):转换为结构化 - 分析通道重要性得分 - 将稀疏通道完全剪除 阶段3(epoch 21-30):细粒度调整 - 结构化剪枝到45% - 剩余5%非结构化微调 阶段4(epoch 31-40):恢复训练 - 固定稀疏模式 - 只更新非零权重 ```

🔴 练习 8.5:点云体素化优化

设计一个高效的点云体素化算法,输入是N个3D点(每秒200万点),输出是稀疏体素网格。要求:

  1. 分析不同体素大小(0.1m, 0.2m, 0.5m)的权衡
  2. 设计支持动态点云流的增量更新算法
  3. 提出GPU并行化方案

💡 提示:考虑自动驾驶中的实时性要求和动态场景特性。

📝 参考答案 1. 体素大小权衡分析: **0.1m体素**: - 空间分辨率高,保留细节 - 100m×100m×10m空间需要10⁸个体素 - 稀疏度极高(<0.01%) - 内存需求大,哈希冲突多 - 适合近距离精确建模 **0.2m体素**: - 平衡精度和效率 - 体素数量减少8倍 - 每体素平均2-3个点 - 推荐用于一般场景 **0.5m体素**: - 快速处理,低内存 - 损失细节,但保留整体结构 - 适合远距离和快速运动检测 2. 增量更新算法: ``` 数据结构: - 双缓冲哈希表(当前帧/历史帧) - 时间戳数组记录体素更新时间 - 脏标记位图 算法流程: 1. 新点到达: - 计算体素索引 - 哈希表查找/插入 - 更新体素特征(均值、协方差) - 标记脏体素 2. 时序融合: - 运动补偿对齐 - 加权平均(按时间衰减) - 置信度更新 3. 过期清理: - 滑动窗口(保留最近1秒) - 基于置信度的选择性保留 - 压缩稀疏结构 ``` 3. GPU并行化方案: ``` 三阶段流水线: 阶段1:点分配(高并行) - 每个线程处理一个点 - 计算体素索引 - 原子操作分配到体素 - 使用共享内存缓存 阶段2:体素处理(中等并行) - 每个线程块处理一组体素 - 计算统计特征 - 局部归约操作 - 写入全局内存 阶段3:稀疏压缩(低并行) - Stream compaction - 前缀和计算新索引 - 并行拷贝到输出 优化技术: - 空间局部性排序减少冲突 - Warp级原子操作 - 纹理内存加速查表 - 异步拷贝隐藏延迟 性能目标: - 200万点/帧 < 10ms - GPU利用率 > 80% - 内存带宽效率 > 60% ```

🔴 练习 8.6:稀疏注意力机制优化

Transformer模型中的自注意力计算复杂度是O(n²)。设计一个编译器优化方案,针对稀疏注意力模式(如局部注意力、跨步注意力)进行加速。考虑序列长度n=8192的场景。

💡 提示:考虑不同稀疏模式的计算和内存访问特征。

📝 参考答案 1. **稀疏模式分析**: **局部窗口注意力**(窗口大小w=256): - 每个token只关注前后128个token - 稀疏度:256/8192 = 3.125% - 带状矩阵结构 **跨步注意力**(步长s=64): - 每隔64个token采样 - 稀疏度:128/8192 = 1.56% - 规则的跳跃模式 **混合模式**: - 局部(近处)+ 跨步(远处) - 自适应稀疏度 2. **编译器优化方案**: ``` 静态分析阶段: 1. 模式识别 - 检测注意力mask的规律 - 分类:局部/跨步/随机/混合 2. 分块策略 - 将8192分成32个256大小的块 - 块内密集计算 - 块间稀疏连接 IR转换阶段: 1. 算子分解 - QKᵀ分解为分块矩阵乘法 - Softmax分解为局部归一化 2. 内存布局优化 - Q,K,V按块连续存储 - 预计算索引映射表 代码生成阶段: 1. 特化内核生成 for block_i in range(32): # 局部注意力 local_attn = compute_local(block_i) # 跨步连接 if block_i % stride_blocks == 0: strided_attn = compute_strided(block_i) # 融合结果 output[block_i] = combine(local_attn, strided_attn) 2. 向量化和预取 - SIMD处理连续的注意力权重 - 预取下一个块的QKV ``` 3. **性能优化技术**: ``` 内存优化: - Flash Attention思想:分块计算减少HBM访问 - 重计算vs存储权衡 - 工作集大小:256×d×3(QKV)< L2 cache 计算优化: - 密集块使用优化的GEMM - 稀疏块使用专门的稀疏核 - 混合精度:注意力分数FP16,累加FP32 并行策略: - 序列维度并行(处理不同块) - 注意力头并行 - 批次并行 预期性能提升: - 理论FLOPS减少:32x(3.125%稀疏度) - 实际加速:8-12x(考虑内存带宽) - 内存使用:减少90% ``` 4. **动态稀疏支持**: ``` JIT编译pipeline: 1. 运行时稀疏度监控 2. 触发重编译阈值(稀疏模式变化>20%) 3. 后台异步编译新kernel 4. 热切换到优化版本 ```

🟢 练习 8.7:压缩格式的算术运算

给定两个CSR格式的稀疏向量a和b(长度1000),分别有20和30个非零元素。设计算法计算:

  1. 向量加法 c = a + b
  2. 向量点积 d = a · b 估算两种运算的时间复杂度。

💡 提示:考虑非零元素的位置重叠情况。

📝 参考答案 1. **向量加法 c = a + b**: 算法(双指针法): ``` i, j = 0, 0 while i < len(a.indices) and j < len(b.indices): if a.indices[i] < b.indices[j]: c.add(a.indices[i], a.values[i]) i += 1 elif a.indices[i] > b.indices[j]: c.add(b.indices[j], b.values[j]) j += 1 else: # 相同位置 c.add(a.indices[i], a.values[i] + b.values[j]) i += 1 j += 1 # 处理剩余元素 ``` 时间复杂度:O(nnz(a) + nnz(b)) = O(20 + 30) = O(50) 输出稀疏度:最少20个(完全重叠),最多50个(无重叠) 2. **向量点积 d = a · b**: 算法(双指针扫描): ``` i, j, result = 0, 0, 0 while i < len(a.indices) and j < len(b.indices): if a.indices[i] < b.indices[j]: i += 1 elif a.indices[i] > b.indices[j]: j += 1 else: # 位置匹配 result += a.values[i] * b.values[j] i += 1 j += 1 ``` 时间复杂度:O(min(nnz(a), nnz(b))) = O(20) 实际计算量:仅在索引匹配处(期望值约0.6个位置) 3. **复杂度对比**: - 密集向量加法:O(1000) - 稀疏向量加法:O(50) - 加速比:20x - 密集向量点积:O(1000) - 稀疏向量点积:O(20) - 加速比:50x

🟡 练习 8.8:编译器的稀疏性传播分析

编译器需要追踪稀疏性在计算图中的传播。给定计算序列:

A: 稀疏矩阵(稀疏度5%)
B: 密集矩阵
C = A @ B
D = ReLU(C)
E = D + A

分析每步操作后的稀疏性,以及编译器应该选择的优化策略。

💡 提示:不同操作对稀疏性的影响不同。

📝 参考答案 1. **稀疏性传播分析**: - **A**: 稀疏度5%(给定) - **C = A @ B**: - 稀疏矩阵×密集矩阵 - 输出通常是密集的 - 但保留A的行稀疏模式 - 预期稀疏度:接近5%(若B不扩散稀疏性) - **D = ReLU(C)**: - ReLU将负值置零 - 增加稀疏性 - 假设C有50%负值 - 预期稀疏度:52.5%(5% + 95%×50%) - **E = D + A**: - 稀疏+稀疏 - 稀疏度介于max和sum之间 - 预期稀疏度:约52.5%(D主导) 2. **编译器优化策略**: ``` 阶段1:A @ B - 使用CSR格式的稀疏密集乘法 - 只计算A的非零行 - 输出可保持密集格式 阶段2:ReLU(C) - 融合到前一个矩阵乘法 - 生成稀疏输出(动态稀疏) - 使用mask记录零位置 阶段3:D + A - 检测到D是动态稀疏 - 转换为稀疏格式 - 使用稀疏加法算法 整体优化: - 算子融合:A@B+ReLU - 延迟物化:直到需要时才生成密集表示 - 格式选择:基于后续操作决定 ``` 3. **内存和计算节省**: ``` 原始(全密集): - 内存:4个密集矩阵 - 计算:密集矩阵乘法 + 密集加法 优化后: - 内存:2个稀疏 + 1个密集(C可以复用) - 计算:稀疏密集乘法 + 稀疏加法 - 节省:约50%内存,30%计算 ``` 4. **动态决策**: ``` if (稀疏度 < 10%): 使用稀疏算法 elif (稀疏度 < 50%): 使用混合算法 else: 使用密集算法 ```