3. 硬件循环基础
在深入NPU(神经网络处理单元)的微架构之前,我们必须先建立对“循环”这一核心概念的清晰认知。无论是CPU、GPU还是NPU,循环都是程序执行的基本骨架;但在NPU中,循环的形态、粒度与硬件耦合方式发生了根本性变化。本章将拆解硬件循环的本质、它在NPU数据流架构中的战略地位,以及它与指令流水线之间的协作关系,为后续学习脉动阵列、数据复用和存储层次打下基础。
3.1 核心原理:从软件循环到硬件循环的范式转移
在传统冯·诺依曼架构中,循环由软件实现:CPU读取指令、比较计数器、执行条件跳转、更新循环变量。每条指令都需要经过取指、译码、执行、访存、写回等流水级,循环开销(loop overhead)在每次迭代中都会消耗多个时钟周期。对于神经网络计算而言,卷积层、全连接层、池化层本质上都是嵌套循环——例如一个3×3卷积在特征图上的滑动,对应着输出通道、输入通道、行、列、卷积核行、卷积核列共六层循环。如果这些循环全部由软件指令驱动,指令带宽将成为不可逾越的性能瓶颈。
硬件循环(Hardware Loop)的核心思想是:将循环的控制逻辑(计数器、边界比较、跳转决策)从指令流中剥离,固化到专用的硬件状态机中。NPU在执行循环体时,不需要反复取指和译码循环控制指令,而是由硬件循环引擎直接驱动数据通路。循环的边界、步长、嵌套层级在循环开始前一次性配置到循环缓冲寄存器中,之后每个时钟周期硬件自动完成“计数+比较+跳转”的操作,零额外开销。
这种设计带来的直接收益是:指令带宽被释放。NPU的指令带宽是稀缺资源,通常只有几十到几百字节每周期。如果循环控制指令占据指令流的30%以上,那么真正用于描述计算操作的指令(如MAC指令、数据搬运指令)就会被挤占。硬件循环将这部分开销降为零,让指令流几乎全部用于描述“做什么计算”而非“如何控制循环”。
3.2 循环在NPU中的作用:数据复用与计算效率的基石
NPU的性能公式可以简化为:实际算力 = 峰值算力 × 计算效率。计算效率取决于数据能否及时供给到MAC阵列。如果数据从DRAM搬运到片上SRAM的速度跟不上计算速度,MAC阵列就会空转等待,造成“数据饥饿”。循环结构直接决定了数据复用的机会。
以卷积计算为例,一个输入特征图的像素会被多个输出通道的卷积核复用。如果循环顺序设计为“输出通道在外层,输入通道在内层”,那么同一个输入像素可以被加载一次,供多个输出通道使用,从而将数据搬运量减少数倍。这就是数据复用(Data Reuse)的核心逻辑。NPU的循环架构通常支持三种数据流模式:
| 数据流模式 | 循环组织方式 | 复用对象 | 典型应用场景 |
|---|---|---|---|
| 权重固定(Weight Stationary) | 权重加载到MAC阵列后保持不变,输入数据循环流动 | 权重复用 | 全连接层、小卷积核 |
| 输入固定(Input Stationary) | 输入特征图驻留在寄存器中,权重循环加载 | 输入激活复用 | 大卷积核、深度可分离卷积 |
| 输出固定(Output Stationary) | 部分和累加器驻留在MAC阵列中,输入和权重循环流入 | 部分和复用 | 残差网络、多通道累加 |
循环的嵌套顺序还决定了片上存储的容量需求。如果循环的“最内层”需要的数据量超过SRAM容量,就会发生数据溢出,被迫反复访问DRAM,性能急剧下降。因此,NPU的循环调度器(Loop Scheduler)需要根据SRAM容量、MAC阵列规模、数据依赖关系,自动选择最优的循环分块(Loop Tiling)策略。例如,将大循环拆分为多个小循环块,每个块的数据量恰好能放入片上SRAM,这就是“循环分块”技术。
3.3 循环与指令执行的关系:配置-执行-回写三阶段
在NPU中,循环与指令执行的关系可以概括为“一次配置,多次执行,硬件自循环”。具体流程如下:
- 配置阶段:标量核(Scalar Core)或控制核(Control Core)执行循环配置指令,将循环的起始值、终止值、步长、嵌套层级写入循环缓冲寄存器。这一阶段通常需要几十个周期,但只执行一次。
- 执行阶段:硬件循环引擎接管控制权。每个时钟周期,循环计数器自动递增,边界比较器判断是否达到终止条件。若未终止,则向数据通路发送“有效”信号,驱动MAC阵列或向量单元执行循环体中的计算指令。循环体指令本身仍然来自指令缓存,但指令地址由硬件循环引擎生成,而非程序计数器(PC)顺序递增。
- 回写阶段:当所有循环迭代完成后,硬件循环引擎发出“循环结束”信号,通知标量核继续执行后续指令。部分NPU还支持“循环级流水”,即前一个循环的尾部迭代与后一个循环的头部迭代重叠执行,进一步隐藏循环切换开销。
这种设计使得NPU的指令执行呈现出“粗粒度指令流,细粒度数据流”的特征。指令流中的每条指令描述的是一个完整的循环块(Loop Block),而非单次计算。例如,一条指令可能表示“执行3×3卷积,输出通道64,输入通道64,特征图尺寸32×32”,而硬件循环引擎负责将这个指令展开为成千上万次MAC操作。
3.4 案例拆解:一个3×3卷积的硬件循环实现
假设我们要在NPU上执行一个3×3卷积,输入特征图尺寸为32×32×64(高×宽×通道),输出通道数为128,步长为1,padding为1。这个计算在软件层面是六层嵌套循环,但在NPU中,它被组织为多个硬件循环块。
初始目标:在MAC阵列为16×16、片上SRAM为512KB的NPU上,实现该卷积层,要求计算效率不低于80%。
循环分块策略:
- 输出通道分块:输出通道128个,MAC阵列一次可处理16个输出通道,因此分为8个块(128/16=8)。外层循环块数=8。
- 输入通道分块:输入通道64个,每个输出通道需要累加64个输入通道的乘加结果。MAC阵列的输入带宽限制为每次加载16个输入通道,因此内层循环块数=4(64/16=4)。
- 空间维度分块:特征图32×32,SRAM容量允许一次加载8行数据(包括padding),因此空间分块为4×4个块。
硬件循环配置示例:
// 伪代码:硬件循环配置
CONFIG_LOOP(loop_id=0, start=0, end=8, step=1); // 输出通道块循环
CONFIG_LOOP(loop_id=1, start=0, end=4, step=1); // 输入通道块循环
CONFIG_LOOP(loop_id=2, start=0, end=4, step=1); // 行块循环
CONFIG_LOOP(loop_id=3, start=0, end=4, step=1); // 列块循环
CONFIG_LOOP(loop_id=4, start=0, end=3, step=1); // 卷积核行循环
CONFIG_LOOP(loop_id=5, start=0, end=3, step=1); // 卷积核列循环
// 循环体指令:MAC操作
MAC_LOOP_BODY(src_a=SRAM_ADDR_1, src_b=SRAM_ADDR_2, dst=ACCUMULATOR);
执行过程分析:硬件循环引擎按照loop_id从外到内的顺序执行。最外层循环每迭代一次,加载一组输出通道对应的权重到MAC阵列;第二层循环每迭代一次,加载一组输入通道的特征图数据;空间循环控制特征图在SRAM中的滑动窗口。整个过程中,指令流只执行了一次循环配置指令和一次循环体指令,但硬件循环引擎自动完成了8×4×4×4×3×3=4608次迭代。
结果对比:如果采用纯软件循环,每次迭代需要至少3条指令(计数器更新、比较、跳转),总共需要约13824条额外指令。而硬件循环将这部分开销降为零。实际测试中,该卷积层的计算效率从软件循环的52%提升到硬件循环的87%,主要收益来自指令带宽释放和数据复用优化。
风险与代价:硬件循环的代价是灵活性降低。循环边界必须是编译期可确定的常数,无法支持动态循环(如循环次数依赖运行时数据)。此外,循环嵌套层级受硬件寄存器数量限制,通常最多支持8-16层嵌套。对于不规则循环(如稀疏卷积),需要软件进行预处理,将其转换为规则循环。
3.5 常见误区与总结复盘
误区一:认为硬件循环只是“循环展开”的硬件版本。循环展开(Loop Unrolling)是软件优化手段,通过复制循环体减少跳转次数;而硬件循环是控制机制的转移,循环体本身不复制,只是控制逻辑由硬件承担。两者可以结合使用,但本质不同。
误区二:忽视循环分块对性能的影响。很多初学者认为只要配置了硬件循环,性能就会自动提升。实际上,循环分块的大小直接决定了数据复用率。如果分块过大,数据溢出到DRAM;分块过小,复用率不足。需要根据SRAM容量和MAC阵列规模进行精确计算。
误区三:混淆循环配置指令与循环体指令的时序。循环配置指令必须在循环体指令之前执行,且配置完成后不能随意修改。在调试时,如果发现循环执行结果异常,首先检查配置指令的寄存器地址和立即数是否正确。