3、硬件稀疏加速器架构:脉动阵列的稀疏化改造、稀疏感知的数据流设计、稀疏索引处理单元

好,咱们今天聊点硬核的。稀疏计算在算法层面看着挺美——省计算、省存储,但真到了硬件落地,你会发现事情没那么简单。我当年第一次把稀疏模型往FPGA上移植的时候,差点被数据流的混乱给整崩溃。说白了,稀疏加速的核心就三个字:怎么跳。怎么跳过那些零,怎么让计算单元不空转,怎么把索引信息高效地喂给硬件。

这一节,我们就从脉动阵列的改造讲起,一步步拆解稀疏加速器的硬件设计。

3.1 脉动阵列的稀疏化改造

传统的脉动阵列,每个PE(处理单元)老老实实地做乘加。遇到稀疏权重?它也得算,只不过乘了个零。这太浪费了。我见过一个项目,稀疏度80%的模型,在普通脉动阵列上跑,有效算力连标称的20%都不到。

怎么改?核心思路就一条:让PE只处理非零数据

改造要点:

  • PE的输入多路选择:每个PE不再固定从邻居拿数据,而是从稀疏索引单元拿。非零值来了,PE干活;零来了,PE直接旁路,省电。
  • 权重预取与对齐:稀疏权重在送入阵列前,先解压成密集格式?不行,那太占带宽。更好的做法是只送非零值和它的坐标,PE内部自己做对齐。
  • 输出累加器改造:稀疏计算的结果往往需要按原始坐标累加。每个PE后面挂一个小型累加器,用坐标做地址,避免乱序写回。

我记得有一次,团队里有人提议把脉动阵列的每个PE都加上一个零检测器。想法挺好,但实测发现零检测器本身的面积开销不小。后来我们换了个思路——在数据进入阵列前,先做一次粗粒度的稀疏过滤,效果反而更好。

3.2 稀疏感知的数据流设计

数据流设计,说白了就是决定数据怎么在阵列里流动。传统的数据流有权重固定、输入固定、输出固定三种。稀疏场景下,这些都不太灵。

为什么会这样?你想想看,稀疏矩阵的非零分布是随机的。如果还用固定的数据流,某个PE可能忙死,旁边的PE闲死。这就是负载不均。

我建议的做法是:采用稀疏感知的行-列交织数据流

数据流类型 传统方式 稀疏感知改造
权重固定 权重驻留PE,输入数据广播 权重按非零位置动态加载,输入数据按需路由
输入固定 输入驻留,权重广播 输入分块,权重按稀疏索引分片广播
输出固定 部分和驻留,权重和输入流动 部分和按坐标累加,避免写冲突

嗯,这里要注意:没有万能的数据流。我在项目中踩过最大的坑,就是试图用一种数据流适配所有稀疏模式。后来学乖了——根据稀疏度动态切换数据流。比如稀疏度高于70%时用输出固定,低于50%时用权重固定。

实战小技巧:

设计数据流时,可以预留一个配置寄存器。运行时根据当前层的稀疏特征,动态调整数据流动方向。这个寄存器位宽不用大,2-3个bit就够了。

3.3 稀疏索引处理单元

这是稀疏加速器里最容易忽视、但最关键的模块。没有好的索引处理,前面那些改造全是白搭。

稀疏索引处理单元(Sparse Index Processing Unit, SIPU)负责三件事:

  1. 解析稀疏格式:COO、CSR、CSC……不同格式的索引结构不同,SIPU要能快速解析。
  2. 生成PE的调度信号:告诉每个PE,下一个非零值在哪,该跟谁做乘加。
  3. 处理边界情况:比如某一行全是零,SIPU要能直接跳过,不给PE发任何数据。

我曾经在一个项目里,SIPU设计得太简单,只支持COO格式。结果客户拿来的模型全是CSR格式的,我们不得不临时加一个格式转换器,白白浪费了20%的芯片面积。所以,SIPU一定要支持多种稀疏格式,至少COO和CSR是标配。

下面是一个简化的SIPU状态机逻辑,用伪代码表示:

// 稀疏索引处理单元核心逻辑
while (有未处理的非零值) {
    // 从稀疏矩阵中读取下一个非零值
    (row, col, value) = read_next_nonzero();
    
    // 计算目标PE的坐标
    pe_row = row % PE_ROW_NUM;
    pe_col = col % PE_COL_NUM;
    
    // 生成调度信号
    if (pe_row 在有效范围内 && pe_col 在有效范围内) {
        send_to_pe(pe_row, pe_col, value);
    } else {
        // 边界情况:跳过或缓存
        buffer_nonzero(row, col, value);
    }
    
    // 更新索引指针
    advance_index_pointer();
}

你看,逻辑不复杂,但实现时有很多细节。比如send_to_pe这一步,如果多个非零值同时映射到同一个PE,就需要仲裁。我建议用轮询仲裁器,公平且实现简单。

3.4 整体架构与数据流图

说了这么多,咱们用一张图把整个架构串起来。下面这张SVG图展示了稀疏加速器的核心数据流:

稀疏加速器核心架构数据流 稀疏权重存储 (非零值+索引) 稀疏输入存储 (非零值+索引) 稀疏索引处理单元 (SIPU) 格式解析·调度·仲裁 权重索引 输入索引 稀疏化脉动阵列 PE PE PE PE PE PE PE PE PE 调度信号+数据 输出累加器 按坐标累加部分和 输出缓冲 控制单元 数据流模式配置 模式切换 图例: 存储 索引处理 计算阵列 累加 缓冲 控制 控制信号

从这张图你能看到,整个数据流是围绕SIPU展开的。权重和输入的索引先送到SIPU,SIPU解析后生成调度信号,告诉脉动阵列里的每个PE该干什么。控制单元则负责切换数据流模式——比如当前层适合权重固定还是输出固定。

避坑指南:

我曾经在设计SIPU时,忽略了索引对齐的问题。结果非零值和它的坐标在流水线里错位了,算出来的结果全错。排查了整整两天才发现是索引延迟没处理好。所以,一定要在SIPU的输出端加一个对齐缓冲,确保数据和索引同步到达PE。

好了,这一节的内容就到这。稀疏加速器的设计,说白了就是跟零值做斗争。脉动阵列的改造让PE不再空转,稀疏感知的数据流让计算负载更均衡,而SIPU则是整个系统的调度大脑。这三块做好了,稀疏加速器才能真正发挥出理论上的加速比。


公众号:蓝海资料掘金营,微信deep3321