第 19 章:稀疏与变长数据支持

在现实世界的 AI 应用中,特别是自动驾驶和具身智能场景,数据往往呈现出高度的稀疏性和变长特性。激光雷达点云在三维空间中极度稀疏,视觉 Transformer 的注意力矩阵在推理时表现出结构化稀疏模式,而自然语言序列和传感器数据流本质上就是变长的。本章深入探讨 AI 编译器如何高效支持这些非规则数据结构,实现性能与内存效率的最优权衡。

学习目标

完成本章学习后,你将能够:

  1. 理解并选择合适的稀疏张量表示格式
  2. 设计高效的稀疏算子实现策略
  3. 优化变长序列的批处理性能
  4. 实现动态稀疏模式的自适应编译
  5. 将稀疏优化技术应用于实际场景

本章大纲

19.1 稀疏张量表示

  • 19.1.1 COO(Coordinate)格式
  • 19.1.2 CSR(Compressed Sparse Row)格式
  • 19.1.3 CSC(Compressed Sparse Column)格式
  • 19.1.4 格式选择与转换策略

19.2 稀疏算子实现

  • 19.2.1 稀疏矩阵乘法(SpMM)
  • 19.2.2 稀疏矩阵向量乘法(SpMV)
  • 19.2.3 稀疏卷积优化
  • 19.2.4 稀疏注意力机制

19.3 变长序列批处理

  • 19.3.1 序列打包与填充策略
  • 19.3.2 动态批处理
  • 19.3.3 序列并行优化
  • 19.3.4 内存管理挑战

19.4 动态稀疏模式

  • 19.4.1 运行时稀疏性检测
  • 19.4.2 自适应稀疏化策略
  • 19.4.3 混合稠密-稀疏执行
  • 19.4.4 稀疏性预测模型

19.5 自动驾驶与具身智能中的应用

  • 19.5.1 点云数据的稀疏表示
  • 19.5.2 视觉Transformer的稀疏优化
  • 19.5.3 动态障碍物检测的变长处理

19.1 稀疏张量表示

稀疏张量的高效表示是实现稀疏计算优化的基础。不同的稀疏格式在存储效率、访问模式和计算性能上各有权衡。编译器需要根据稀疏模式特征和目标硬件特性选择最优表示。

19.1.1 COO(Coordinate)格式

COO 格式是最直观的稀疏表示方法,通过显式存储每个非零元素的坐标和值。对于一个 $m \times n$ 的稀疏矩阵 $A$ 包含 $nnz$ 个非零元素,COO 格式使用三个数组:

  • 行索引数组 $I[nnz]$:存储每个非零元素的行坐标
  • 列索引数组 $J[nnz]$:存储每个非零元素的列坐标
  • 值数组 $V[nnz]$:存储非零元素的值

存储复杂度为 $O(3 \cdot nnz)$,其中索引通常使用 32 位或 64 位整数。

矩阵 A:           COO 表示:
[5  0  0  7]      I = [0, 0, 1, 2, 2]
[0  8  0  0]      J = [0, 3, 1, 1, 3]
[0  3  0  9]      V = [5, 7, 8, 3, 9]

COO 格式的优势

  • 构造简单,易于并行插入新元素
  • 不需要预先排序,支持随机访问模式
  • 格式转换灵活,可高效转换为其他格式
  • 适合极度稀疏的矩阵(稀疏度 > 99%)

COO 格式的劣势

  • 存储开销大,每个元素需要两个索引
  • 缓存局部性差,随机内存访问模式
  • 不利于向量化和 SIMD 优化
  • 矩阵运算需要额外的索引匹配开销

编译器优化策略

  1. 索引压缩:对于小矩阵使用 16 位索引减少内存带宽
  2. 排序优化:按行列顺序排序提高缓存命中率
  3. 分块存储:将矩阵分块,每块独立 COO 表示
  4. 混合精度:索引使用低精度,值使用高精度

19.1.2 CSR(Compressed Sparse Row)格式

CSR 格式通过压缩行索引信息,显著减少存储开销。对于 $m \times n$ 矩阵:

  • 行指针数组 $row_ptr[m+1]$:第 $i$ 行的非零元素在 $col_ind$ 中的起始位置
  • 列索引数组 $col_ind[nnz]$:非零元素的列坐标
  • 值数组 $values[nnz]$:非零元素值

存储复杂度降至 $O(m + 2 \cdot nnz)$。

矩阵 A:                CSR 表示:
[5  0  0  7]           row_ptr = [0, 2, 3, 5]
[0  8  0  0]           col_ind = [0, 3, 1, 1, 3]
[0  3  0  9]           values  = [5, 7, 8, 3, 9]

行 $i$ 的非零元素存储在 $values[row_ptr[i]:row_ptr[i+1]]$,对应列索引为 $col_ind[row_ptr[i]:row_ptr[i+1]]$。

CSR 的计算优势

  • 行遍历高效:$O(1)$ 时间定位任意行
  • SpMV 优化:天然适合稀疏矩阵向量乘法
  • 内存连续:同一行元素连续存储,缓存友好
  • 并行友好:不同行可独立并行处理

CSR 的编译优化

  1. 向量化优化: - 利用 SIMD 指令处理连续的行数据 - 对齐内存访问提高带宽利用率

  2. 负载均衡: - 根据 $row_ptr[i+1] - row_ptr[i]$ 动态分配线程 - 采用工作窃取策略处理不均匀分布

  3. 预取优化: - 基于 $row_ptr$ 预取下一行数据 - 软件流水线隐藏内存延迟

  4. 分层 CSR: - 热点行使用稠密表示 - 稀疏行使用压缩表示

19.1.3 CSC(Compressed Sparse Column)格式

CSC 格式是 CSR 的列优先版本,适合列遍历操作:

  • 列指针数组 $col_ptr[n+1]$:第 $j$ 列的起始位置
  • 行索引数组 $row_ind[nnz]$:非零元素的行坐标
  • 值数组 $values[nnz]$:非零元素值

CSC 格式在以下场景优于 CSR:

  • 频繁的列访问操作(如 $A^T x$ 计算)
  • 列并行算法(如某些分解算法)
  • 与列主序存储系统(如 Fortran)交互

19.1.4 格式选择与转换策略

编译器需要根据计算模式动态选择最优格式:

格式选择决策树

稀疏度评估
├─ 极度稀疏 (>99%)
│  └─ COO(构造阶段)→ CSR/CSC(计算阶段)
├─ 中度稀疏 (90-99%)
│  ├─ 行操作为主 → CSR
│  └─ 列操作为主 → CSC
└─ 轻度稀疏 (<90%)
   └─ 考虑块稀疏格式(BCSR)或稠密表示

格式转换开销模型

设转换开销为 $C_{convert}$,计算收益为 $B_{compute}$,只有当: $$B_{compute} > \alpha \cdot C_{convert}$$ 其中 $\alpha$ 是安全系数(通常取 1.5-2.0),才执行格式转换。

转换开销估算:

  • COO → CSR:$O(nnz \cdot \log(nnz))$(需要排序)
  • CSR → CSC:$O(nnz + m + n)$(转置操作)
  • CSR → COO:$O(nnz)$(直接展开)

多格式缓存策略

编译器可维护多格式缓存,避免重复转换:

稀疏张量 S
├─ native_format: CSR(原生格式)
├─ cached_formats: {
│     COO: (data, timestamp, access_count),
│     CSC: (data, timestamp, access_count)
│  }
└─ format_predictor: 基于历史访问模式预测

自动格式选择的编译指示: 编译器通过分析访问模式自动插入格式转换:

  • 静态分析:识别循环中的访问模式
  • 动态分析:运行时收集访问统计
  • 混合策略:JIT 编译时根据实际稀疏度调整

19.2 稀疏算子实现

稀疏算子的高效实现是 AI 编译器的核心挑战。不同于稠密算子的规则计算模式,稀疏算子需要处理不规则的内存访问、动态的计算负载和复杂的索引匹配。本节深入分析关键稀疏算子的编译优化技术。

19.2.1 稀疏矩阵乘法(SpMM)

稀疏矩阵与稠密矩阵乘法 $C = A \times B$,其中 $A$ 是 $m \times k$ 的稀疏矩阵,$B$ 是 $k \times n$ 的稠密矩阵。

基础算法复杂度分析

  • 稠密实现:$O(m \cdot k \cdot n)$
  • 稀疏实现:$O(nnz_A \cdot n)$
  • 加速比:$\frac{m \cdot k}{nnz_A}$

CSR 格式的 SpMM 核心循环

for i in 0..m:
    for j in row_ptr[i]..row_ptr[i+1]:
        col = col_ind[j]
        val = values[j]
        for k in 0..n:
            C[i,k] += val * B[col,k]

编译器优化技术

  1. 寄存器分块(Register Blocking): 将 $B$ 矩阵分成 $r \times c$ 的小块,充分利用寄存器: $$C_{i,[k:k+c]} += \sum_{j \in row_i} A_{ij} \cdot B_{j,[k:k+c]}$$ 优化参数选择:
  • $r$:通常取 SIMD 宽度(如 AVX-512 取 16)
  • $c$:根据可用寄存器数量,典型值 4-8
  1. 内存访问优化: - 行缓冲:预取整行 $A$ 的非零元素到 L1 缓存 - 列打包:将 $B$ 的访问列打包成连续内存 - 流式存储:使用非临时存储指令写入 $C$

  2. 负载均衡策略: 基于 $row_ptr$ 的工作量估算: $$W_i = (row_ptr[i+1] - row_ptr[i]) \cdot n$$ 动态调度策略:

  • 静态分块:将行按 $W_i$ 累积和均匀分配
  • 动态调度:使用工作队列,粒度自适应
  • 混合策略:大行独立处理,小行批量处理
  1. 向量化优化: 利用 SIMD 指令并行处理多个输出元素:
向量寄存器 v_val = broadcast(A[i,j])
for k in 0..n step SIMD_WIDTH:
    v_B = load(B[j, k:k+SIMD_WIDTH])
    v_C = load(C[i, k:k+SIMD_WIDTH])
    v_C += v_val * v_B
    store(C[i, k:k+SIMD_WIDTH], v_C)

19.2.2 稀疏矩阵向量乘法(SpMV)

SpMV 是许多迭代算法的核心:$y = A \cdot x$,其中 $A$ 是稀疏矩阵。

性能瓶颈分析

  • 计算密度低:$\frac{2 \cdot nnz}{sizeof(val) \cdot nnz + sizeof(idx) \cdot nnz}$
  • 内存带宽受限:典型只能达到峰值带宽的 10-20%
  • 不规则访问:$x[col_ind[j]]$ 缺乏局部性

优化技术

  1. 分段优化(Segmented Optimization): 根据行长度分类处理:
  • 短行($\leq 4$ 个元素):标量处理,减少开销
  • 中等行(5-32 个元素):SIMD 向量化
  • 长行($> 32$ 个元素):分块并行
  1. 预取策略
距离自适应预取
prefetch_distance = min(L2_size / avg_row_length, 8)
for i in current..current+prefetch_distance:
    prefetch(x[col_ind[row_ptr[i]]])
  1. 坐标重排序: 通过重排序减少 $x$ 向量的缓存失效:
  • Cuthill-McKee 算法:最小化带宽
  • 嵌套剖分:改善局部性
  • 基于访问频率的贪心重排

19.2.3 稀疏卷积优化

稀疏卷积在点云处理和稀疏 3D 网络中广泛应用。

稀疏卷积的特殊性

  • 输入输出均可能稀疏
  • 卷积核的有效位置动态变化
  • 需要构建输入-输出映射表

基于哈希表的稀疏卷积

对于 3D 稀疏卷积,使用哈希表管理活跃体素: $$H: (x, y, z) \rightarrow index$$ 卷积计算分两阶段:

  1. 规则生成阶段:确定输出位置和对应的输入位置
  2. 聚合阶段:执行实际的乘累加操作

编译优化

  1. 规则表压缩: 使用增量编码减少索引存储: $$\Delta_{ij} = index_j - index_i$$

  2. kernel 分组: 将卷积核按稀疏模式分组,相同模式共享计算路径

  3. 输出驱动 vs 输入驱动: - 输出驱动:遍历输出位置,收集输入 - 输入驱动:遍历输入位置,散布到输出 - 自适应选择:基于稀疏度比 $\frac{nnz_{out}}{nnz_{in}}$

19.2.4 稀疏注意力机制

Transformer 模型中的注意力矩阵常表现出结构化稀疏性。

稀疏模式类型

  1. 局部注意力:每个 token 只关注固定窗口
  2. 跨步注意力:固定间隔的全局注意力
  3. 学习稀疏:通过阈值或 top-k 动态稀疏化

Flash Attention 的稀疏扩展

将注意力计算分块,只计算非零块: $$\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}} \odot M\right)V$$ 其中 $M$ 是稀疏掩码矩阵。

块稀疏优化

  1. 块大小选择: $$b_{opt} = \min\left(\sqrt{\frac{SRAM_{size}}{3 \cdot d_{model}}}, warp_{size}\right)$$

  2. 块调度: - 静态调度:预计算块依赖关系 - 动态调度:运行时根据稀疏度调整

  3. 混合精度策略: - 注意力分数:FP16/BF16 - Softmax 累加:FP32 - 输出投影:FP16/BF16

编译器的模式识别

编译器通过模式匹配识别可优化的稀疏注意力:

识别模式:

- 对角带状:|i-j| < window_size
- 块对角:block_id(i) == block_id(j)
- 混合模式:局部 + 全局锚点

基于识别的模式生成专门化代码,避免通用稀疏开销。

19.3 变长序列批处理

变长序列是自然语言处理和时序数据分析的常见输入形式。高效的批处理策略对于提升硬件利用率和降低推理延迟至关重要。

19.3.1 序列打包与填充策略

填充(Padding)策略

将所有序列填充到批次内最大长度 $L_{max}$: $$\text{Efficiency} = \frac{\sum_{i=1}^{B} L_i}{B \cdot L_{max}}$$ 其中 $B$ 是批大小,$L_i$ 是第 $i$ 个序列的实际长度。

填充开销分析

  • 计算开销:$(L_{max} - L_{avg}) \cdot B \cdot C_{op}$
  • 内存开销:$(L_{max} - L_{avg}) \cdot B \cdot d_{model} \cdot sizeof(dtype)$
  • 典型效率:50-70%(自然语言场景)

动态打包(Dynamic Packing)

将多个短序列打包成一个长序列:

原始序列:[A1, A2, A3], [B1, B2], [C1, C2, C3, C4]
打包结果:[A1, A2, A3, B1, B2, C1, C2, C3, C4]
位置编码:[0,  1,  2,  0,  1,  0,  1,  2,  3]
序列 ID: [0,  0,  0,  1,  1,  2,  2,  2,  2]

最优打包算法

装箱问题的在线近似算法:

  1. First Fit Decreasing:序列按长度降序排列,贪心打包
  2. Best Fit:选择剩余空间最小的箱子
  3. 动态规划:对小批量求解最优打包

打包效率目标: $$\min \sum_{j=1}^{M} \max(L_j) \quad \text{s.t.} \quad \sum_{i \in bin_j} L_i \leq L_{target}$$

19.3.2 动态批处理

连续批处理(Continuous Batching)

允许不同序列在不同时间加入和离开批次:

时刻 t0: [Seq1, Seq2, Seq3, ----]
时刻 t1: [Seq1, Seq2, ----, Seq4]  # Seq3 完成,Seq4 加入
时刻 t2: [----, Seq2, Seq5, Seq4]  # Seq1 完成,Seq5 加入

编译器支持

  1. 动态形状推导: $$shape_{t+1} = update(shape_t, completions_t, arrivals_t)$$

  2. 内存池管理: - 预分配最大批次的内存 - 使用环形缓冲区管理序列槽位 - 碎片整理策略

  3. 调度优化: - 优先级队列管理等待序列 - 批次组装的贪心/启发式算法 - 延迟敏感的抢占机制

19.3.3 序列并行优化

序列维度的并行切分

对于超长序列(如长文档),在序列维度并行: $$X[L, D] \rightarrow X_1[L/P, D], X_2[L/P, D], ..., X_P[L/P, D]$$ Ring Attention 机制

将注意力计算分布到多个设备:

  1. 每个设备持有 Q、K、V 的一部分
  2. K、V 在设备间循环传递
  3. 每轮计算局部注意力并累积

通信复杂度:$O(P \cdot L \cdot d_{model})$ 计算复杂度:$O(L^2 \cdot d_{model} / P)$

编译器的自动并行化

检测可并行化的维度:

并行化决策:
if seq_length > threshold_length:
    if num_devices > 1 and seq_length > batch_size * 64:
        选择序列并行
    else:
        选择张量并行
else:
    选择数据并行

19.3.4 内存管理挑战

变长序列的内存碎片问题

碎片率定义: $$\text{Fragmentation} = 1 - \frac{\text{Used Memory}}{\text{Allocated Memory}}$$ 内存管理策略

  1. 固定槽位分配: - 预定义几种长度规格(如 128, 256, 512, 1024) - 序列分配到最近的规格 - 内部碎片 vs 外部碎片权衡

  2. 伙伴系统(Buddy System): - 内存块大小为 2 的幂 - 分裂与合并操作 O(log n) - 适合频繁的分配释放

  3. 内存池化

内存池结构:
Pool = {
    small: [size <= 256],
    medium: [256 < size <= 1024],
    large: [size > 1024]
}

PagedAttention 优化

将 KV 缓存按页管理,支持非连续存储:

  • 页大小:通常 16 或 32 个 token
  • 页表:映射逻辑地址到物理地址
  • 优势:减少 40-50% 的内存浪费

19.4 动态稀疏模式

实际应用中的稀疏性往往是动态的,编译器需要支持运行时自适应优化。

19.4.1 运行时稀疏性检测

稀疏性度量

  1. 全局稀疏度: $$\rho = 1 - \frac{nnz}{m \times n}$$

  2. 结构化稀疏度: - 块稀疏度:$\rho_{block} = 1 - \frac{\text{非零块数}}{\text{总块数}}$ - 行/列稀疏度:$\rho_{row} = \frac{\text{全零行数}}{m}$

  3. 模式稀疏度: - 对角带宽:$bandwidth = \max_{A_{ij} \neq 0} |i - j|$ - 聚类系数:非零元素的空间聚集程度

在线检测算法

采样检测(开销 O(sample_size)):
sample_indices = random_sample(total_elements, sample_size)
estimated_sparsity = count_zeros(sample_indices) / sample_size

if estimated_sparsity > threshold:
    切换到稀疏kernel

19.4.2 自适应稀疏化策略

magnitude pruning

动态阈值剪枝: $$A_{sparse} = A \odot (|A| > \tau)$$ 其中阈值 $\tau$ 可以是:

  • 固定值:$\tau = constant$
  • 百分位数:$\tau = \text{percentile}(|A|, p)$
  • 自适应:$\tau = \alpha \cdot \text{std}(A)$

结构化剪枝

保持硬件友好的稀疏模式:

  • 2:4 稀疏(NVIDIA Ampere):每 4 个元素保留 2 个
  • 块稀疏:$b \times b$ 块为单位
  • 通道剪枝:整个通道置零

编译器需要生成对应的专门化kernel。

19.4.3 混合稠密-稀疏执行

双路径执行策略

执行路径选择:
if sparsity < 0.5:
    使用稠密kernel(优化的 GEMMelif sparsity < 0.9:
    使用半结构化稀疏kernel
else:
    使用高度稀疏kernelCOO/CSR

渐进式稀疏化

训练过程中逐步增加稀疏度: $$\rho_t = \rho_{final} \cdot \left(1 - \left(1 - \frac{t}{T}\right)^3\right)$$ 编译器支持:

  • JIT 重编译触发
  • kernel 缓存管理
  • 平滑过渡机制

19.4.4 稀疏性预测模型

基于历史的预测

使用滑动窗口预测未来稀疏度: $$\hat{\rho}_{t+1} = \alpha \cdot \rho_t + (1-\alpha) \cdot \hat{\rho}_t$$ 基于模型的预测

训练轻量级预测器:

输入特征:

- 层类型和位置
- 输入数据统计(均值、方差)
- 之前层的稀疏度

输出:

- 预测稀疏度
- 推荐的执行策略

预测器的编译集成

  1. 离线训练:收集profiling数据训练预测器
  2. 在线部署:预测器作为运行时组件
  3. 反馈更新:实际稀疏度更新预测模型

预测准确率要求:> 90% 才能带来净收益。

19.5 自动驾驶与具身智能中的应用

19.5.1 点云数据的稀疏表示

激光雷达点云在 3D 空间中极度稀疏(通常 < 0.01% 占用率)。

体素化表示

将 3D 空间划分为体素网格: $$V_{ijk} = \{p \in P : i \leq x_p < i+1, j \leq y_p < j+1, k \leq z_p < k+1\}$$ 哈希表加速

使用空间哈希避免存储空体素: $$h(i, j, k) = (i \cdot p_1 + j \cdot p_2 + k \cdot p_3) \mod M$$ 其中 $p_1, p_2, p_3$ 是大质数。

编译优化

  • 动态体素分配
  • 邻域查询优化
  • 稀疏卷积的规则生成

19.5.2 视觉 Transformer 的稀疏优化

Token 剪枝

动态移除不重要的 token: $$importance_i = |\text{Attention}[:, i]|_1$$ 保留 top-k 重要的 token,减少 50-70% 计算量。

注意力稀疏化

利用注意力的局部性:

  • 近邻注意力:物体检测中的空间局部性
  • 层次注意力:不同分辨率的特征交互

19.5.3 动态障碍物检测的变长处理

时序数据的变长特性

不同传感器采样率不同:

  • 相机:30 FPS
  • 激光雷达:10 Hz
  • 毫米波雷达:20 Hz

多速率融合

编译器需要处理不同速率的数据流:

融合策略:

- 同步融合:等待所有传感器
- 异步融合:使用最新可用数据
- 预测补偿:推断缺失时刻数据

动态批处理优化

根据场景复杂度调整批大小:

  • 高速公路:大批量,简单场景
  • 城市街道:小批量,复杂场景
  • 紧急情况:单样本,最低延迟

本章小结

本章系统地探讨了 AI 编译器对稀疏与变长数据的支持技术。我们学习了三种主要的稀疏张量表示格式(COO、CSR、CSC)及其适用场景,深入分析了稀疏矩阵乘法、稀疏卷积和稀疏注意力等关键算子的优化策略。对于变长序列处理,我们讨论了序列打包、动态批处理和内存管理的挑战与解决方案。动态稀疏模式部分介绍了运行时检测、自适应执行和性能预测技术。最后,我们将这些技术应用于自动驾驶和具身智能的实际场景。

关键要点

  1. 稀疏格式选择需要权衡存储效率、访问模式和计算性能
  2. 稀疏算子优化的核心是减少不规则内存访问和改善负载均衡
  3. 变长序列批处理需要在硬件利用率和内存效率间找到平衡
  4. 动态稀疏性要求编译器支持运行时自适应和 JIT 重编译
  5. 实际应用中的稀疏优化需要领域特定知识和硬件协同设计

练习题

基础题

练习 19.1:给定一个 $1000 \times 1000$ 的矩阵,包含 10,000 个非零元素,计算使用 COO、CSR、CSC 格式的存储开销(假设值为 float32,索引为 int32)。

提示

考虑每种格式需要存储的数组数量和大小。

答案
  • COO 格式:
  • 行索引:10,000 × 4 字节 = 40,000 字节
  • 列索引:10,000 × 4 字节 = 40,000 字节
  • 值数组:10,000 × 4 字节 = 40,000 字节
  • 总计:120,000 字节

  • CSR 格式:

  • 行指针:1,001 × 4 字节 = 4,004 字节
  • 列索引:10,000 × 4 字节 = 40,000 字节
  • 值数组:10,000 × 4 字节 = 40,000 字节
  • 总计:84,004 字节

  • CSC 格式:

  • 列指针:1,001 × 4 字节 = 4,004 字节
  • 行索引:10,000 × 4 字节 = 40,000 字节
  • 值数组:10,000 × 4 字节 = 40,000 字节
  • 总计:84,004 字节

CSR/CSC 比 COO 节省约 30% 的存储空间。

练习 19.2:对于批大小为 32 的变长序列批次,序列长度分别为 [10, 20, 30, ..., 320],计算填充策略的效率。

提示

效率 = 实际token数 / (批大小 × 最大长度)

答案
  • 实际 token 数:$\sum_{i=1}^{32} 10i = 10 \times \frac{32 \times 33}{2} = 5,280$
  • 填充后总 token 数:$32 \times 320 = 10,240$
  • 效率:$\frac{5,280}{10,240} = 51.56\%$

这表明简单填充策略浪费了近一半的计算资源。

练习 19.3:如果一个稀疏矩阵的稀疏度为 95%,使用 CSR 格式进行 SpMV 运算相比稠密实现的理论加速比是多少?

提示

比较稠密和稀疏实现的运算次数。

答案
  • 稀疏度 95% 意味着 5% 的元素非零
  • 稠密 SpMV:$m \times n$ 次乘加运算
  • 稀疏 SpMV:$0.05 \times m \times n$ 次乘加运算
  • 理论加速比:$\frac{1}{0.05} = 20$ 倍

实际加速比会因内存访问模式和硬件特性而低于理论值。

挑战题

练习 19.4:设计一个算法,将 COO 格式的稀疏矩阵转换为 CSR 格式,要求时间复杂度为 $O(nnz \log nnz + m)$。描述算法步骤和关键数据结构。

提示

需要先对 COO 格式的元素按行列顺序排序。

答案

算法步骤:

  1. 对 COO 三元组 (row, col, val) 按 (row, col) 字典序排序,$O(nnz \log nnz)$
  2. 初始化 row_ptr[m+1],所有元素为 0,$O(m)$
  3. 遍历排序后的 COO 数组: - 统计每行的非零元素数量 - 构建 col_ind 和 values 数组
  4. 计算 row_ptr 的累积和

关键优化:

  • 使用基数排序可将复杂度降至 $O(nnz + m)$
  • 如果 COO 已部分有序,可使用归并排序
  • 并行化:不同行的处理可以并行

练习 19.5:推导 PagedAttention 中,给定页大小 $P$ 和序列长度分布,计算期望的内存浪费率。假设序列长度服从均值为 $\mu$、方差为 $\sigma^2$ 的正态分布。

提示

内存浪费来自最后一页的未使用部分。

答案

设序列长度为 $L$,需要的页数为 $\lceil L/P \rceil$。

最后一页的浪费: $$W(L) = P - (L \mod P) = P - L + P \lfloor L/P \rfloor$$ 当 $L \mod P \neq 0$ 时,期望浪费: $$E[W] = \frac{P}{2}$$ 总体内存浪费率: $$\text{浪费率} = \frac{E[W]}{E[\lceil L/P \rceil] \cdot P} \approx \frac{P/2}{\mu + P/2} = \frac{P}{2\mu + P}$$ 对于 $\mu = 512$,$P = 32$: $$\text{浪费率} \approx \frac{32}{1024 + 32} \approx 3\%$$

这比简单填充的 40-50% 浪费率显著降低。

练习 19.6:分析在 3D 点云稀疏卷积中,使用哈希表 vs 八叉树(Octree)数据结构的时空复杂度权衡。

提示

考虑构建时间、查询时间和内存占用。

答案

哈希表

  • 构建:$O(N)$,N 为非空体素数
  • 查询:期望 $O(1)$,最坏 $O(N)$
  • 内存:$O(N)$
  • 优势:快速随机访问,实现简单
  • 劣势:无空间层次信息,冲突处理开销

八叉树

  • 构建:$O(N \log D)$,D 为树深度
  • 查询:$O(\log D)$
  • 内存:$O(N)$ 到 $O(8^D)$
  • 优势:自然的空间层次,支持多分辨率查询
  • 劣势:树遍历开销,指针追逐

选择建议:

  • 均匀分布 + 频繁随机访问 → 哈希表
  • 聚集分布 + 邻域查询 → 八叉树
  • 混合方案:顶层八叉树 + 叶节点哈希表

练习 19.7:设计一个自适应的稀疏度预测器,基于前 k 层的稀疏度预测第 k+1 层的稀疏度。给出特征工程和模型选择的建议。

提示

考虑时序特征和网络结构信息。

答案

特征工程

  1. 历史特征: - 前 3 层的稀疏度:$\rho_{k}, \rho_{k-1}, \rho_{k-2}$ - 稀疏度变化率:$\Delta\rho_k = \rho_k - \rho_{k-1}$ - 移动平均:$\bar{\rho} = \frac{1}{w}\sum_{i=k-w+1}^{k} \rho_i$

  2. 结构特征: - 层类型编码(卷积/全连接/注意力) - 相对位置:$k / \text{total_layers}$ - 层参数量:$\log(\text{num_params})$

  3. 数据特征: - 输入激活值统计:均值、标准差、峰度 - 梯度范数(训练时)

模型选择

  • 轻量级:线性回归或决策树(< 1μs 推理)
  • 中等:随机森林或 XGBoost(< 10μs 推理)
  • 高精度:小型 MLP(< 100μs 推理)

在线更新策略

  • 使用指数加权移动平均更新模型参数
  • 维护预测误差的置信区间
  • 误差超过阈值时触发重训练

练习 19.8:对于自动驾驶场景,设计一个多传感器数据的变长序列融合策略,处理不同采样率和延迟的问题。

提示

考虑时间对齐、插值和优先级。

答案

多速率融合框架

  1. 时间戳对齐
基准时间 t_base = 当前系统时间
对每个传感器 s
    t_aligned[s] = round(t_raw[s] / t_resolution) * t_resolution
  1. 缓冲区管理: - 每个传感器维护环形缓冲区 - 大小:$\text{buffer_size} = 2 \times \frac{\text{max_latency}}{\text{sample_rate}}$

  2. 插值策略: - 激光雷达:最近邻(避免创造虚假点) - 相机:双线性插值(平滑视觉特征) - IMU:线性插值(保持连续性)

  3. 优先级融合

优先级权重:
w_lidar = 0.5 * freshness + 0.3 * reliability + 0.2 * coverage
w_camera = 0.3 * freshness + 0.4 * reliability + 0.3 * coverage
w_radar = 0.4 * freshness + 0.3 * reliability + 0.3 * coverage
  1. 编译器优化: - 静态调度:编译时确定融合顺序 - 动态批处理:根据数据到达情况调整 - SIMD 加速:向量化插值运算

常见陷阱与错误

1. 稀疏格式选择错误

  • 错误:盲目使用 CSR 格式处理所有稀疏矩阵
  • 后果:列操作性能极差,转置开销大
  • 正确做法:分析访问模式,必要时维护多种格式

2. 忽视负载不均衡

  • 错误:按行均匀分配稀疏矩阵计算任务
  • 后果:某些线程早早完成,其他线程成为瓶颈
  • 正确做法:基于非零元素数量进行工作量估算和动态调度

3. 过度的格式转换

  • 错误:每次操作都转换到"最优"格式
  • 后果:转换开销超过计算收益
  • 正确做法:建立转换代价模型,只在收益明确时转换

4. 变长序列的内存泄漏

  • 错误:动态分配后忘记释放,或释放时机不当
  • 后果:长时间运行导致内存耗尽
  • 正确做法:使用内存池,明确生命周期管理

5. 稀疏度预测过于激进

  • 错误:基于少量样本就切换到稀疏 kernel
  • 后果:预测错误导致性能退化
  • 正确做法:设置保守的阈值,逐步调整

6. 忽略硬件特性

  • 错误:不考虑缓存行大小和 SIMD 宽度
  • 后果:缓存利用率低,向量化效果差
  • 正确做法:根据目标硬件调整数据布局和算法参数

最佳实践检查清单

设计阶段

  • [ ] 分析数据的稀疏模式(随机/结构化/块状)
  • [ ] 评估稀疏度的动态范围和变化频率
  • [ ] 确定性能目标和内存约束
  • [ ] 选择合适的稀疏表示格式组合

实现阶段

  • [ ] 实现高效的格式转换例程
  • [ ] 设计负载均衡的并行策略
  • [ ] 优化内存访问模式(预取、缓存友好)
  • [ ] 支持混合精度计算

优化阶段

  • [ ] 使用 profiling 工具识别瓶颈
  • [ ] 针对目标硬件进行特化(SIMD、GPU)
  • [ ] 实现自适应的运行时策略选择
  • [ ] 建立性能模型指导优化决策

测试阶段

  • [ ] 覆盖不同稀疏度级别(0%-99%)
  • [ ] 测试各种稀疏模式(对角、带状、随机)
  • [ ] 验证数值稳定性和精度
  • [ ] 压力测试内存管理(长时间运行)

部署阶段

  • [ ] 监控实际稀疏度分布
  • [ ] 收集性能指标和异常
  • [ ] 支持在线参数调优
  • [ ] 准备回退策略(稠密计算)