阅读导航

这一篇在干嘛?

这篇论文(Zhang 等,江南大学,ACM TECS 2025)设计了一颗基于 RISC-V 的处理器,通过三条自定义指令把深度神经网络的卷积推理变成”硬件友好的稀疏矩阵乘法”,让大多数网络的推理时间缩短 70% 以上,能效峰值达到 675 GOPS/W。全文讲清楚四件事:为什么要稀疏化、卷积怎么变成矩阵乘法、硬件怎么负载均衡地并行计算、加速收益与面积功耗代价分别有多大。适合对计算机体系结构、RISC-V 和嵌入式 AI 感兴趣的初学者,只需要懂得卷积和矩阵乘法的基本概念即可阅读。

开场白:为什么这篇论文值得一读

如果你用过嵌入式设备跑神经网络——比如一块 STM32、一块 RISC-V 开发板上的智能摄像头——你一定体会过那种”模型在 GPU 上飞快、搬到开发板上就慢如蜗牛”的挫败感。这篇论文要解决的问题非常具体:在资源受限的边缘设备上,怎么让卷积神经网络(CNN)的推理跑得又快又省电?

论文出自江南大学的一个团队,发表在 ACM Transactions on Embedded Computing Systems(嵌入式计算系统汇刊,TECS)2025 年 10 月刊上。它没有走”造一颗专用加速芯片”的老路,而是选择了一个更聪明的切入点:RISC-V 的模块化指令集扩展机制。RISC-V 是一个开源指令集架构(Instruction Set Architecture,ISA),它的设计哲学是”最小核心 + 可插拔扩展”——就像乐高积木,基础指令集很小,但允许芯片设计者按需添加自定义指令。这给了论文团队一个独特的机会:他们可以在一个标准开源处理器(CV32E40P)内部”开一个洞”,塞进去一套专门为稀疏卷积设计的硬件加速单元,再配三条自定义指令把它暴露给 C 语言程序员。

这条路线的好处是双重的。第一,软件生态兼容:传统专用加速器往往需要专用编程工具甚至汇编语言,而这篇工作的加速单元通过自定义指令直接接入标准 C 编译器工具链,程序员写普通的 C 文件就能用上硬件加速。第二,硬件可复用:作者没有只为卷积定制一个”一次性”电路,而是把卷积统一抽象成稀疏矩阵乘法(sparse matrix multiplication,SpMM),这套硬件对任何稀疏矩阵乘法任务都有效,将来跑别的算法也能复用。

论文的贡献可以浓缩为四点:(1) 设计了带指令融合与高效地址计算单元的 RISC-V 自定义指令集;(2) 提出硬件友好的 CONV2M 结构,把卷积转成稀疏矩阵乘法;(3) 把经典的 Gustavson 稀疏矩阵乘算法引入卷积计算,并设计了配套的低开销稀疏存储格式;(4) 用”列主序 + 元素级并行”解决了 Gustavson 算法固有的负载不均衡问题。

下面这张图是全文的”问题—方案”总览,建议先花一分钟看懂它,后面所有内容都是围绕它展开的。

图 1:现有稀疏矩阵卷积框架存在的问题(左)与本文方案提供的解决路径(右)。(a) 是传统处理器取指开销大、资源利用低效;(b) 是传统 RISC-V 指令集做 MAC 运算的繁琐流程;(c) 是本文在 ISA、算法抽象、处理器结构与硬件设计四个层面的优化。

背景:稀疏化、卷积与 im2col 的烦恼

稀疏化:模型的”减肥手术”

先说背景中的背景:为什么大家都在谈稀疏化(sparsification)?神经网络参数量逐年暴涨,模型越做越深,推理所需的计算量和内存也水涨船高。但研究者很早就发现,大部分网络架构里存在大量冗余——许多权重对最终结果几乎没影响。于是”剪枝”(pruning)技术应运而生:训练时或训练后把不重要的权重直接置零,剪掉的权重比例通常在 20% 到 80% 之间,而精度几乎不掉。

剪枝分为两大流派:

  • 非结构化剪枝(unstructured pruning):按重要性指标逐个删除权重,零散地分布在整个矩阵里。优点是剪得狠、稀疏度高、精度损失小;缺点是零元素的分布毫无规律,硬件调度起来很头疼。
  • 结构化剪枝(structured pruning):按滤波器(filter)、通道(channel)或块(block)这样的粗粒度结构成块删除。优点是内存访问模式规整、硬件调度友好;缺点是稀疏度上限低,精度损失通常更大。

论文的数据很有说服力:对 AlexNet 用基于幅值的剪枝方法,稀疏度可达 75%,且各层稀疏度差异巨大;对 VGG16,输入特征图的平均稀疏度在 40% 到 60% 之间。换句话说,卷积网络里将近一半的乘法是在算零——这就是巨大的浪费,也是加速的机会。下面三张图分别展示了 AlexNet 的逐层稀疏度、VGG16 的核与特征图稀疏度,以及三种网络在不同稀疏度下的推理表现。

图 2(左):AlexNet 基于幅值剪枝后各层的稀疏度分布,整体可达 75%,但层间差异明显。

图 2(中):VGG16 各层卷积核与输入特征图的稀疏度,输入特征图平均在 40%–60% 之间。

图 2(右):三种网络在不同稀疏度下的推理性能。稀疏化对结果与精度影响很小,推理速度随稀疏度提高而加快,但在传统处理器上,稀疏化引入的管理开销常常抵消收益。

但这里有个”但是”:在传统处理器上,稀疏化的收益经常被管理开销吃掉。跳过零元素需要额外的判断、索引和查表,省下来的计算可能还不够付这些”管理费”。所以稀疏化要真正落地,必须靠专门的硬件架构——这正是本文的出发点。

传统卷积为什么慢

再来看卷积本身。一个标准的二维卷积至少是五层嵌套循环:输出通道、输出高度、输出宽度、核高度、核宽度,核心计算是乘累加(Multiply-Accumulate,MAC)。用公式表达就是:

论文用的传统卷积伪代码如下(算法 1):

ALGORITHM 1: Traditional Convolution
1: for c = 0 to 1 do
2:    for i = offset to inputSize - offset - 1 do
3:    for j = offset to inputSize - offset - 1 do
4:    sum = 0
5:    for m = 0 to kernelSize - 1 do
6:    for n = 0 to kernelSize - 1 do
7:    sum += input[(i + m - offset), (j + n - offset)] × kernel[m, n]
8:    end for
9:    end for
10:    output[i, j] = sum
11:    end for
12:    end for
13: end for

为了提升速度,很多研究把卷积改写成通用矩阵乘法(General Matrix Multiplication,GEMM)来算——最著名的就是 im2col 方法:把滑窗扫过的每个感受野展开成矩阵的一列,卷积就变成了规整的矩阵乘。但 im2col 有个致命伤:滑窗重叠导致输入数据被大量重复拷贝,内存访问量约为原始数据的 9 倍(对 3×3 核而言)。特征图一大,存储就成了性能瓶颈。

更宏观地看,DNN 推理 90% 以上的时间花在 MAC 运算和频繁的内存访问上。而在传统 RISC-V 指令集上,一次 MAC 需要执行 4 条加载指令、2 条乘法指令、1 条加法指令和 1 条写回指令——光取指和访存的开销就把计算淹没了。指令之间并行度又低,卷积算起来自然慢。

和现有工作的区别

学术界已经有很多 DNN 加速方案,论文在第二部分做了对比,这里挑几个有代表性的:

  • HybridDNN(DAC’20)和 Eyeriss(JSSC’16):传统专用加速器的代表,性能强,但依赖专用硬件和专用工具链,改一次设计要重新做 RTL 综合,灵活性差。
  • XpulpNN(DATE’20):同样是 RISC-V 指令集扩展的思路,但面向的是低精度 SIMD 运算(量化网络),没有处理稀疏问题。本文的指令集则同时加速稀疏卷积推理,并支持稀疏矩阵乘法的硬件复用。
  • RI5CY-Accel(TC’24):用 Winograd 算法减少乘法次数,与本文”稀疏化 + 自定义计算流程”的路线完全不同。
  • SamurAI(VLSI’20):把深度学习模型与 RISC-V 平台整合的另一种尝试。

一句话总结本文的差异化定位:不是最快的加速器,而是最灵活、最省电的可编程稀疏加速方案——单核轻量设计,C 语言可编程,为边缘系统而生。

总体架构:在 CV32E40P 上加装速模块

看懂了动机,我们来拆硬件。本文的处理器基座是 CV32E40P——一个来自 OpenHW Group 的开源 32 位 RISC-V 处理器,四段流水线(取指、译码、执行、写回),在嵌入式领域用得非常多。作者的做法不是推倒重来,而是”外科手术式”地改造了三个部位:

  1. 执行段(execute stage):插入一个加速模块,负责卷积和稀疏稠密矩阵乘法。这是本文的核心。
  2. 译码模块(decode):改掉,让它能识别新的自定义指令。
  3. 写回段的 LSU(Load-Store Unit,加载存储单元):重新设计,支持地址计算和多周期读写控制。

加速模块内部又分三块(对应后面图 6(c)):

  • 控制单元(control unit):里面有一个同步调度器(synchronization scheduling unit),在并行计算时充当总指挥,协调整个数据流。
  • 数据处理单元(data processing unit):包含矩阵压缩单元(MCU,Matrix Compression Unit)和 CONV2M 单元,负责在计算前把数据”备好菜”——压缩核矩阵、切分特征图。
  • 计算处理单元(computation processing unit):真正的 PE 阵列,做乘法与加法。

这套架构最精妙的地方在于分工:CV32E40P 原有的流水线照常跑普通程序,只有遇到自定义指令时,工作才”下沉”到加速模块。这就好比一家普通餐厅后厨里加了一台专业烤炉——日常炒菜流程不变,遇到要烤的东西就交给烤炉,烤炉自己有温控和定时,厨师不用守着。

图 3:本文的 RISC-V 核心整体架构。在 CV32E40P 四段流水线的基础上,执行段嵌入加速模块,译码器扩展支持自定义指令,写回段重新设计了 LSU 以支持地址计算与多周期读写。

被重新设计的 LSU:一次写回一整批

值得单独讲讲 LSU 的改造,因为它解决了一个容易被初学者忽视的问题:写回也是开销。传统 LSU 每次只写回一个元素,一个矩阵算完要写回无数次。本文的设计里,地址控制单元(ACU,Address Calculation Unit)可以一次性写回整个计算结果,大幅减少写回指令的数量。

具体机制是”计数器 + 握手”(counter + handshake),如图 5 所示。当译码器解码 WB 指令并收到 ready 信号后,LSU 把要写回的数据拍数装入内部计数器,然后进入写回阶段:每个时钟周期发一次写请求,把写数据和写使能驱动到下游存储接口;存储接口通过握手返回 ready 响应;只有当写请求和 ready 同时为真,计数器才减一、发起下一次写。计数器归零、最后一个写信号确认后,LSU 在同一周期输出完成信号,反馈给流水线控制单元,流水线立刻取指执行下一条指令。

这个设计的巧妙之处在于:它天然适应存储系统可变的响应延迟——存储慢了就自动等(握手不通就不减计数),不需要额外的暂停或轮询,也不需要程序员插入一堆显式的写回指令。流水线吞吐量因此显著提升。

图 5:自定义写回指令的执行时间示意。“计数器 + 握手”机制在可变延迟下精确追踪写回完成时机,完成信号直接触发流水线取下一条指令。

CONV2M:把卷积变成稀疏矩阵乘法

现在进入本文的第一个核心技术:CONV2M(Convolution to Matrix,卷积转矩阵)单元。

im2col 的老大难问题

前面说过,im2col 方法把卷积展开成矩阵乘,代价是滑窗重叠造成的数据冗余拷贝——内存访问量约为原始数据的 9 倍。举个直观的例子:3×3 的核在 4×4 的输入上滑动,每个 2×2 的角落区域会被不同的滑窗重复读取。数据一重复,就要重复从内存搬、重复占存储空间。特征图越大,这个开销越离谱。

CONV2M 的两招:切分 + 核稀疏化

CONV2M 的思路是把输入特征图切分与卷积核稀疏化结合起来,直接在硬件里完成转换。论文给了一个具体例子(图 4):

  • 输入是单通道 4×4 特征图,卷积核是 3×3。
  • 第一步:把 4×4 输入特征图切成两个 3×4 矩阵——注意中间两行被两个矩阵复用,不需要重复加载,这就消除了 im2col 式的冗余数据搬运。
  • 第二步:给 3×3 卷积核边缘补零(zero-padding),让它变成一个稀疏矩阵。补出来的零元素天然参与稀疏化:既然矩阵里大量元素是零,就只需存非零元素。

这两招合起来的效果:与传统的 im2col 相比,冗余内存访问减少约 35%,在处理大特征图时优势尤其明显。同时核的零填充加速了稀疏化进程,存储空间和计算负载双双下降。

图 4:CONV2M 方法的全过程。(a) 结合零元素构造权重矩阵;(b) 输入特征图矩阵的复用过程;(c) 卷积核补零形成稀疏矩阵;(d) 用 Gustavson 计算进行矩阵展开。

常见坑

很多人第一次读到这里会以为”切分 + 补零”改变了计算结果——其实没有。切分只是把 4×4 特征图重新组织成两个 3×4 矩阵,中间两行复用;补零只是在核的边缘加零,零乘任何数还是零。数学上结果与原卷积完全一致,变的只是数据组织和访问方式。另一个常见误解是认为补零”增加”了计算量:恰恰相反,补零产生的稀疏结构让硬件可以跳过这些零,存储和计算反而都省了。

存储格式与 Gustavson 算法:列主序压缩的妙处

卷积变成稀疏矩阵乘法之后,下一个问题是:稀疏矩阵怎么存?

为什么不用 CSR 和 CSC

教科书画风的标准答案是 CSR(Compressed Sparse Row,按行压缩)或 CSC(Compressed Sparse Column,按列压缩)。但论文指出,这两种格式在元素级并行的计算环境里都有先天缺陷:

  • CSR/CSC 需要**维护一个与矩阵尺寸相当的查找表(lookup table)**来追踪”最近一次读取的非零元素位置”。这个大查找表既吃内存,查询又复杂,严重拖慢访问速度。
  • 在本文的元素级并行架构和 Gustavson 式数据流下,CSR 把一整行的非零元素打包在一起,硬件调度器只能按行分派任务——当各行稀疏度差异大时,负载严重不均。
  • CSC 虽然按列组织,但一列内的所有非零元素必须顺序读取,无法把一列拆开分给多个 PE 并行执行。

一句话:CSR 和 CSC 的存储模式天然限制了处理粒度,不适合细粒度的元素级并行

列主序压缩格式:val、row、col 三个数组

论文提出的方案是一个列主序稀疏矩阵压缩格式(column-major sparse matrix compression format),用三个数组存矩阵:

  • val:存非零元素的值;
  • row:存非零元素的行索引;
  • col:存非零元素的列索引。

它与 CSR/CSC 的关键区别是:每个非零元素的位置由坐标显式给出,不需要额外的大查找表。数据访问快,查询过程简单。而且非零元素按列顺序存储——同一列的非零元素在 val 数组中连续排列,访问更连续,数据利用率和计算效率都更高。每个 PE 可以独立并发地处理不同的非零元素,避免了负载不均和同步问题。

与之配套,稠密特征图采用行主序(row-major)存储,每行作为连续块存放。这有两个好处:处理器可以一次把整行向量直接加载进处理单元,降低访问延迟;更重要的是,行主序与 Gustavson 算法”逐行处理”的计算模型天然契合——算法按行处理、重点处理非零元素,一次操作处理一整行,把频繁的访存和索引操作压到最低。

Gustavson 算法:稀疏乘稠密的主力

现在引出本文的核心算法:Gustavson 算法。它是一种逐行处理(row-wise)的稀疏矩阵乘法算法,利用稀疏矩阵的低秩特性,只处理非零元素,完全跳过零元素的乘法和加法,从而减少不必要的计算和访存。它的另一个优点是增量式构建结果矩阵:每处理一个非零元素就累加一次,逐步构造最终输出,避免生成巨大的中间结果矩阵。论文的算法 2 给出了它的伪代码:

ALGORITHM 2: Gustavson Sparse-Dense Matrix Multiplication
1: for i = 0 to i = num_of_rows_of_A - 1 do
2:    load values[i, :]
3:    load col_idx[i, :]
4:    NNZ = number of non-zeros in values[i, :]
5:    for j = 0 to NNZ-1 do
6:    col = col_idx[i, j]
7:    load B[col, :]
8:    s0 = values[i, j]
9:    C[i, :] += s0 × B[col, :]
10:    end for
11:    store C[i, :]
12: end for

行级并行的坑:负载不均衡

但 Gustavson 算法直接并行化会踩一个大坑。按行并行时,非零元素在各行之间的分布往往很不均匀——有的行密密麻麻全是非零元素,有的行几乎全是零。结果是:部分 PE 超载,另一部分 PE 早早算完在空等同步。整个系统的速度被最慢的 PE 拖住。图 6(b) 里的例子就是如此:PE1、PE2、PE3 只分到少量非零元素,PE0 却要处理一大堆,整体性能被 PE0 的长尾卡死。

解药:列主序元素级并行

论文的解法是列主序元素级并行(column-major element-level parallelism):不再按行分派,而是把稀疏矩阵的所有非零元素按列主序均匀撒到多个 PE 上,每个 PE 处理大约相同数量的非零元素。算法 3 是优化后的版本:

ALGORITHM 3: Optimized Gustavson Sparse-Dense Matrix Multiplication
1: for p = 0 to NUM_PE - 1 do
2: initialise C(p) ← 0
3: end for
4: for e in A do
5: Scheduler dispatches e to PE
6: load B[col, :]
7: C(p)[row, :] += val × B[col, :]
8: end for
9: synchronize all PEs
10: store C(p) to its block of C

图 6(b) 的例子里,四个 PE 各处理三个非零元素,工作量基本拉平。把任务粒度细化到”单个元素”级别后,PE 之间的依赖减少,同步开销随之下降。更妙的是,配合这套策略,Gustavson 算法可以让目标行在 PE 的寄存器里驻留一段时间做部分和(partial sum),不需要跨 PE 的广播或协议网络——数据通路简化了,功耗也降了。

通关标准

读到这里,你应该能不看书回答三个问题:(1) CSR/CSC 为什么不适合元素级并行?(2) 本文的列主序格式靠哪三个数组存稀疏矩阵,靠什么省掉了查找表?(3) Gustavson 算法按行并行时为什么会负载不均,列主序元素级并行是怎么解决的?三问全答上来,这一节的精髓你就吃透了。

PE 计算阵列:负载均衡与乘法器分解

上面讲了”算法层面”怎么把任务分匀,这一节讲”硬件层面”怎么把计算做快做省。图 6 是这一节的主角。

图 6:加速单元的详细架构。(a) 乘法器分解与动态移位方法;(b) 列优先元素级并行策略;(c) 加速单元的详细内部结构,包含控制单元、数据处理单元与计算处理单元。

元素分配:广播 + 近似均分

硬件上,压缩后的稀疏矩阵数据要按乘法处理单元(MulPE)的数量合理分配。实现上采用广播(broadcasting)技术:把列主序压缩格式存储的非零元素均匀广播分发给所有 MulPE,保证每个 MulPE 处理的非零元素数量大致相同,从硬件层面杜绝负载不均造成的性能瓶颈。

数据预取与行向量复用

稠密矩阵这边,数据处理依据 Gustavson 算法的天然需求做预取:一次性把整行向量加载进 MulPE,而不是每次计算都替换行向量。硬件会比较”待处理非零元素的列索引”与”上次处理元素的列索引”,据此判断是否需要替换数据。得益于列主序压缩格式(同一列的非零元素连续存放),稠密矩阵行向量的替换频率被大幅压低,数据局部性和复用率双双提升。

乘法器分解:不用大乘法器也能乘

接下来是硬件设计里最”抠门”也最聪明的一招。传统乘法器在位宽大的时候非常吃资源。本文不用传统乘法器,而是把乘数分解成小因子(7、5、3、1),用移位和加法完成乘法——也就是”乘法器分解 + 动态移位”(图 6(a))。例如乘以 7 等价于”左移 3 位减自己”,乘以 5 等价于”左移 2 位加自己”。这大幅降低了对乘法器硬件的依赖,显著降低了逻辑单元的复杂度和功耗。后面你会看到,这直接体现在 FPGA 资源报告里:DSP 数量零增长。

同步调度器与加法树

并行乘法完成后,各 PE 产出的部分结果需要一个”汇合”过程。本文设计了一个同步调度器:并行乘法单元完成的部分结果先暂时存入各自的队列,调度器根据第二阶段并行加法单元的状态,把缓冲的结果转移给加法单元计算,尽量压缩同步等待时间。

第二阶段用的是加法树(addition tree)结构。传统的顺序加法是一步步累加,每次加法都要等前一次完成,是线性的;加法树把加法操作分层组织,让计算在对数时间内完成——就像淘汰赛,第一轮 8 个数两两相加得 4 个,第二轮得 2 个,最后一轮出冠军。这能快速汇聚各 PE 的部分计算结果,生成最终输出矩阵。

整个数据计算的完整流程如图 7 所示。

图 7:单次计算的详细数据流。压缩核数据与切分特征图数据进入 PE 计算单元,经并行乘法、队列缓冲、同步调度、加法树汇聚,最终产出结果。

自定义指令集:K_LOAD、I_CAL 与 WB

硬件就绪,接下来看软件怎么”够得着”这些硬件。RISC-V 的指令集有六种基本格式:R 型(寄存器到寄存器)、I 型(短立即数与加载)、S 型(存储)、B 型(条件分支)、U 型(长立即数)、J 型(无条件跳转)。本文基于 R 型格式设计了三条专用指令,统一使用 opcode 0x77,具体功能由 funct7 和 funct3 字段的取值决定:两条负责加载数据和稀疏计算,一条负责写回计算结果。图 8(a) 展示了这三条指令的编码与功能说明。

图 8:(a) 定制的 RISC-V 自定义指令编码与说明;(b) 硬件执行时间的测量结果。

三条指令各自的分工

  • K_LOAD:接收两个参数,RS1 是卷积核的起始地址,RS2 是卷积核的尺寸。该指令把核数据加载进来后,立即执行稀疏化和压缩,按列主序压缩格式(Column-major Compress Format)存好,为后续计算做好准备。对应硬件里的矩阵压缩单元(MCU)。
  • I_CAL:同样两个参数,RS1 是输入特征图的起始地址,RS2 是特征图尺寸。特征图数据先送入 CONV2M 单元做重排(切分),然后与之前加载的卷积核数据一起执行基于 Gustavson 的计算,完成卷积操作。
  • WB:执行卷积计算结果的写回,只接收一个参数——写回基地址。它是一条多周期指令,执行时间取决于卷积核与输入特征图的乘积规模。前面讲过的”计数器 + 握手”机制就服务于这条指令。

注意这套指令设计的一个精妙之处:指令数与问题规模解耦。无论矩阵多大、卷积多少通道,程序员只需要循环调用这三条指令,地址偏移由地址计算单元(ACU)自动算,写回由 WB 一次搞定。传统写法里,问题规模每翻一倍,指令数跟着翻倍;在这里,指令数保持稳定,计算开销不会随矩阵维度或通道数增长而膨胀。

元素级并行分配的数学

在 K_LOAD 的加载过程中,MCU 会遍历整个稀疏矩阵的元素,每遇到一个非零元素,除了按列主序压缩格式存储外,还同步递增一个非零元素计数器(NNZ_CNT)。一次扫描、零额外通路,就得到了全局非零元素总数 。分发元素到 PE 时,只需用这个计数器和综合阶段固定的 PE 数量做简单的位运算。设 PE 数量 ,分配公式为:

翻译成人话:把 N 个非零元素分成 P 份,每份至少 base 个,前 rem 个 PE 各再多拿 1 个。这样任意两个 PE 的任务量之差不超过 1,用简单的移位与位与操作就实现了理论上最小的静态负载方差——完全不涉及除法或浮点运算,硬件成本几乎为零。

完整执行流程

把三条指令串起来,一次完整卷积的执行流程是(对应图 8(b)):

  1. 自定义卷积指令被译码后进入加速单元,通过 LSU 从内存取回核数据;
  2. 处理器激活 MCU,对卷积核做稀疏化和列主序压缩;
  3. 特征图通过 LSU 从内存的输入通道取回,激活 CONV2M 单元把数据切成两段;
  4. 压缩后的核数据与切分后的特征图一起送入 PE 计算单元处理;
  5. ACU 全程保持激活,自动计算地址偏移;
  6. 循环迭代直到整个计算完成,WB 指令一次性把结果写回。

常见坑

初学者容易把这三条指令理解成”三条独立的加速指令”,以为可以随意穿插使用。实际上它们是有状态依赖的流水协作:K_LOAD 必须先于 I_CAL 执行(核要先压缩好),I_CAL 产生的结果要由 WB 统一写回,三者通过加速模块内部的状态寄存器衔接——参数地址一旦读入,状态寄存器会随数据加载、卷积计算等操作自动递增,通过比较状态变化前后的值来推进整个计算流程。如果你的 C 代码里调用顺序不对,硬件状态机就会乱掉。

实验结果:加速比、面积与功耗的账本

技术讲完了,现在来算账。所有加速到底值不值?论文的实验部分给出了相当完整的答案。先看实验框架(图 9):

  1. PyTorch 训练各种神经网络,得到权重参数的精度,把训练好的权重嵌入网络结构、写进一个 .c 文件;
  2. 设计好自定义指令集并验证后,把这些指令集成进 RISC-V 工具链,通过链接器把上述文件整合成可执行文件(ELF);
  3. 硬件开发用 Xilinx Vivado 做 FPGA 开发、用 Synopsys Design Compiler 做 ASIC 设计,完善处理器核心的 RTL 描述;
  4. 所有数据和生成的 ELF 交给 Verilator(周期精确仿真器)做系统级性能评估。

其中步骤 (2)(3) 每次测试只需做一次,后续迭代只需重复 (1) 和 (4)。

图 9:实验框架。PyTorch 训练模型 → 权重嵌入 C 文件 → 集成自定义指令的工具链生成 ELF → Vivado/Design Compiler 完成硬件开发 → Verilator 周期级仿真评估。

矩阵乘法:规模越大,加速越猛

本文的硬件阵列并行执行 4 个乘数和 4 个被乘数,所以以 4×4 矩阵为基本单元;更大的矩阵乘法先切成 4×4 子矩阵再迭代执行。实验测试了不同 K 值(K = 16、32、64、128、256、512)下、三种随机稀疏度区间(30%–50%、50%–60%、60%–80%)的矩阵乘法性能,对比 CV32E40P(RV32IM 基线)与本文处理器。

图 10(a):随机稀疏度 30%–50% 下不同规模矩阵乘法的加速效果评估。

图 10(b):随机稀疏度 50%–60% 下不同规模矩阵乘法的加速效果评估。

图 10(c):随机稀疏度 60%–80% 下不同规模矩阵乘法的加速效果评估。

结果呈现清晰的规律:本文处理器在各种规模下的时钟周期数都显著少于 CV32E40P,且矩阵尺寸 K 越大、加速效果越明显。当矩阵规模达到 512、稀疏度在 60%–80% 时,时钟周期加速比高达 70 倍。原因也很直观:矩阵越大,传统 RV32IM 指令集的读写操作越频繁,大量时钟周期耗在访存上——而这恰恰是本文硬件绕开的开销。

卷积:GOPS 与 stride 的关系

卷积性能用 4×4 输入特征图加 3×3 卷积核、不同通道数来评估。规律与矩阵乘法类似:通道数越多加速比越高;而且由于卷积包含大量矩阵乘法,卷积的整体加速效率比单次矩阵乘法更高——这要归功于 CONV2M 专用转换硬件。卷积加速的峰值 GOPS 达到 2.12。论文还测了 stride 大于 1 的情况:stride 增大后滑窗步长变大、输出特征图变小、计算量下降,同样输入尺寸下 stride 从 1 变 2,GOPS 从 2.12 降到 0.92。

整网推理:三个经典网络的检验

接着上真实网络:ResNet18、VGG16 和 MobileNetV2,逐层统计时钟周期。

图 11(a):ResNet18 各卷积层在 CV32E40P 与本文处理器上的时钟周期对比。

图 11(b):VGG16 各卷积层在 CV32E40P 与本文处理器上的时钟周期对比。

图 11(c):MobileNetV2 各卷积层在 CV32E40P 与本文处理器上的时钟周期对比。

图 11(d):三个网络整体加速效果评估,总时钟周期分别减少 63%(ResNet18)、68%(VGG16)、72%(MobileNetV2)。

数据非常有说服力:各卷积层的时钟周期加速比约为 ResNet18 达 60 倍、VGG16 达 63 倍、MobileNetV2 达 71 倍。整体推理的总时钟周期分别下降 63%、68%、72%。MobileNetV2 表现最好的原因是它大量使用深度可分离卷积(depthwise separable convolution),这种结构在本文的处理器架构中加速效率格外突出。另一个有趣的观察:参数量更大的 VGG16 比 ResNet18 加速更明显——说明网络参数规模越大,这套自定义指令的优势越突出。

FPGA 资源账单

加速不是免费的。表 1 给出了两颗处理器在 Xilinx ZCU3EG 平台、100 MHz 下的 FPGA 资源对比:

资源CV32E40POur_Work增幅
时钟频率100 MHz100 MHz-
LUT5,45220,277271%
FF2,6323,82145%
DSP550%
IO5620%
BUFG31275%
静态功耗0.115 W0.210 W82%
动态功耗0.144 W0.195 W35%
总功耗0.259 W0.405 W56%

三个值得注意的细节:LUT 暴涨 271%,因为复杂的控制逻辑要用查找表实现;DSP 零增长——这正是乘法器分解与动态移位技术的功劳,乘法不靠 DSP 硬核,代价是转嫁到了 LUT 上;FF 增长 45%,主要分配给乘法单元的流水线级,用额外寄存器存中间数据保证流水线高效运转。功耗方面,静态功耗涨 82%(逻辑和存储单元复杂度提高),动态功耗涨 35%(控制信号频繁翻转、逻辑活动率上升),总功耗涨 56%。

ASIC 资源账单

ASIC 综合用 SMIC CMOS LL 55nm 1.2V 工艺、TT 角(Typical corner),表 2 是综合报告:

资源CV32E40POur_Work增幅
时钟频率300 MHz300 MHz-
端口数8,72020,149131%
线网数23,19797,659320%
单元数19,08586,247352%
核心单元面积0.0466 mm²0.1718 mm²268%
总单元面积2.94 mm²3.06 mm²4.08%
内部功耗2.22 mW2.72 mW22.5%
翻转功耗0.27 mW0.33 mW22.2%
漏电功耗80.2 µW91.2 µW13.7%
总功耗2.57 mW3.14 mW22.1%

注意一个容易误读的数据:核心单元面积涨了 268%,但总单元面积只涨了 4.08%(从 2.94 mm² 到 3.06 mm²)。因为整个芯片的面积基数大,加速模块只是其中一小块。功耗的增长则温和得多:总功耗只涨 22.1%,换来的是 60 倍以上的能效提升——这笔账显然是划算的。

横向对比:能效之王

最后和业界已发表的处理器/加速器同台竞技(表 3):

处理器JSSC’16JSSC’22TC’24DAC’20AICAS’24TCAD’25ESSCIRC’21Our_Work
架构加速器加速器处理器加速器处理器处理器处理器处理器
工艺/器件CMOS 65nmCMOS 22nmCMOS 90nmPYNQ Z1CMOS 55nmCMOS 22nmCMOS 65nmCMOS 55nm
面积12.25 mm²12 mm²6.44 mm²-0.58 mm²-10 mm²3.06 mm²
功耗140 mW25.4 mW5.8 mW2.6 W2.4 mW43.38 mW49.5 mW3.14 mW
时钟频率200 MHz450 MHz100 MHz-200 MHz800 MHz205 MHz300 MHz
性能33.6 GOPS15.6 GOPS0.225 GOPS83.3 GOPS0.131 GOPS5.232 GOPS15 GOPS2.12 GOPS
面积效率2.74 GOPS/mm²1.3 GOPS/mm²0.034 GOPS/mm²-0.225 GOPS/mm²-1.5 GOPS/mm²0.69 GOPS/mm²
能效240 GOPS/W614 GOPS/W38.79 GOPS/W32.0 GOPS/W54.42 GOPS/W120.87 GOPS/W303 GOPS/W675 GOPS/W

这组数据要分三个维度读:

  • 能效(本文的王牌):300 MHz、1.2 V 供电下达到 675 GOPS/W,超过所有列出的基线。相比 JSSC’22 的 614 GOPS/W(10 核设计 + MRAM 低功耗保持),本文用单核设计做到相当甚至更高的效率,复杂度低得多;更是基线 RISC-V 核 CV32E40P(10.92 GOPS/W)的 60 倍以上
  • 性能(诚实的短板):2.12 GOPS 不及 JSSC’16(33.6 GOPS)、JSSC’22(15.6 GOPS)、DAC’20(83.3 GOPS)等专用加速器——但那些是针对峰值吞吐优化的多核或专用架构,功耗和设计复杂度也完全不是一个量级(JSSC’16 功耗 140 mW,是本文的 44 倍;TCAD’25 功耗超过本文 16 倍)。
  • 面积效率(居中):0.69 GOPS/mm²,优于 TC’24 和 AICAS’24,不及为峰值性能优化或用先进工艺的作品。

一句话总结定位:性能、面积效率与能效的平衡三角。JSSC’22 和 TCAD’25 追求高频或多核吞吐,代价是高功耗与高复杂度;本文单核 RISC-V 强调轻量、省电、软件可编程,更适合资源受限的嵌入式系统,稀疏计算架构也正好回应了未来边缘设备网络部署的演进需求。

通关标准

你应该能凭记忆说出这组关键数字:大多数网络整体推理时间减少 70% 以上;矩阵规模 512、稀疏度 60%–80% 时周期加速比 70 倍;能效 675 GOPS/W,是基线 CV32E40P 的 60 余倍;代价是 FPGA 上 LUT 涨 271%(DSP 零增长)、ASIC 上总功耗涨 22.1%。能把”收益”和”代价”两边都数出来,才算真正读懂了这篇论文的实验设计。

总结与自测

回顾全文,这篇论文给嵌入式 AI 加速提供了一个”教科书级”的完整闭环:

  • 指令集层:基于 RISC-V 模块化扩展机制,用三条 R 型自定义指令(opcode 0x77)把稀疏卷积暴露给 C 程序员,指令数与问题规模解耦;
  • 算法层:CONV2M 把卷积转成稀疏矩阵乘法(比 im2col 省约 35% 冗余访存),Gustavson 算法做稀疏乘稠密,列主序元素级并行解决负载不均;
  • 存储层:val/row/col 三数组的列主序压缩格式省掉大查找表,稠密侧行主序存储配合整行加载与数据复用;
  • 硬件层:MCU + CONV2M + PE 阵列 + 同步调度器 + 加法树 + 乘法器分解 + “计数器 + 握手”式 LSU,每一环都在削减取指、访存和同步开销。

最终成绩单:FPGA 验证功能,55nm CMOS 综合,大多数网络推理时间减少 70% 以上,能效 675 GOPS/W。它证明了在 RISC-V 生态里,“自定义指令 + 稀疏化 + 算法硬件协同设计”是一条切实可行的边缘 AI 加速路径。

如果你对 RISC-V 自定义指令扩展或稀疏计算感兴趣,这篇论文的参考文献里还有两篇值得顺藤摸瓜:Eyeriss(JSSC’16)是能效架构设计的经典范本,Gamma(ASPLOS’21)则展示了 Gustavson 算法在通用稀疏矩阵乘加速中的更多玩法。

此文件夹下有0条笔记。