阶段一:计算基础

3课:矩阵乘法阵列

📖 课程概述

矩阵乘法是神经网络的核心计算——全连接、卷积、注意力最终都归结为矩阵乘。本课实现4×4 PE阵列,用硬件并行执行矩阵乘法。

🔍 计算特征分析

C[i][j] = Σ A[i][k] × B[k][j]
操作乘法加法总FLOPs
GEMM M×K×K×NMKNMKN2MKN
4×4块6448128

🔧 数据复用模式

💻 Verilog实现

输出驻留4×4阵列:每个PE持续累加一个输出元素,A水平传递,B垂直传递,MAC每个周期执行一次。

📈 性能分析

16 MAC/cycle,4×4矩阵7周期完成。分块扩展:4×4阵列计算8×8矩阵需4次分块+累加。

🧪 练习

1) 扩展为8×8阵列 2) 实现4×4阵列计算8×8的分块策略 3) 添加双缓冲输入流水重叠

🔬 矩阵乘法的数据复用深度分析

矩阵乘法C=A×B的三种数据复用模式各有优劣,选择取决于网络类型和硬件约束:

输出驻留(OS)详解

每个PE固定计算一个输出元素C[i][j],A的行和B的列流过PE:

权重驻留(WS)详解

权重W[i][j]预加载到PE(i,j),激活和部分和流过:

行驻留(RS)详解

A的一行驻留在PE行中,B的列和部分和对流:

📊 分块(Tiling)策略与性能

当矩阵大于阵列尺寸时,必须分块计算。分块策略直接影响数据复用效率:

总DRAM访问 = M×K×N_blocks + K×N×M_blocks + M×N×K_blocks
分块方式权重读次数激活读次数结果写次数总访存
沿K分块M×KK×N×⌈M/N⌉M×N×⌈K/N⌉较高
沿M分块M×K×⌈N/N⌉K×NM×N中等
沿N分块M×KK×NM×N最优
实践策略:沿输出通道(N)维度分块,权重和激活各只需读一次,仅输出需要多次部分和累加。这是TPU和NVDLA的共同选择。

💡 PE设计的面积-性能权衡

PE类型乘法器累加器寄存器面积(门)功耗(mW@200MHz)
INT8 OS8×8→1632-bit2×8~600~0.1
INT8 WS8×8→1632-bit3×8~700~0.12
INT16 OS16×16→3240-bit2×16~2500~0.3
FP16 MACFP16 mulFP322×16~8000~0.8
🏆

成就解锁:矩阵乘法阵列

完成本课后,你已掌握 矩阵乘法阵列 的核心概念与硬件实现方法。

🔬 矩阵乘法阵列的完整设计考量

从4×4阵列扩展到实用规模需要解决多个工程问题:

大规模阵列的时钟问题

当阵列规模增大时,时钟信号传输延迟成为瓶颈:

// 时钟树设计策略:
// 
// 小阵列(≤8×8): 
//   单时钟域,全局时钟树
//   时钟偏斜 < 50ps,满足时序
//
// 中等阵列(16×16):
//   时钟网格(Clock Mesh) + 局部缓冲
//   需要仔细平衡时钟偏斜
//
// 大阵列(32×32+):
//   分区时钟,每个区域独立时钟树
//   区域间用FIFO缓冲跨时钟域数据
//   或者使用流水线切割:每N行插入寄存器

数据广播网络

矩阵乘法需要高效的广播/传递网络:

网络类型延迟面积功耗适用
全局广播总线1 cycle高(N×走线)小阵列
树形广播log(N) cycle中等阵列
传递链(脉动)N cycle低(局部走线)大阵列

📐 矩阵乘法的数学优化

除了硬件并行,数学优化也能减少计算量:

Strassen算法

2×2矩阵乘法:标准8次乘法→Strassen 7次乘法

递归应用,N×N矩阵乘法复杂度从O(N²·⁸⁰⁷)降到O(N²·⁸⁰⁷)。但常数项大,仅对大矩阵(>128×128)有效。

分块矩阵乘法的Cache优化

// 分块矩阵乘法伪代码:
// for i in range(0, M, BLOCK):
//   for j in range(0, N, BLOCK):
//     for k in range(0, K, BLOCK):
//       // 这个分块完全在片上SRAM中完成
//       C[i:i+B][j:j+B] += A[i:i+B][k:k+B] × B[k:k+B][j:j+B]
//
// 关键:选择BLOCK大小使三个分块同时放入SRAM
// BLOCK_size = sqrt(SRAM_capacity / 3)

💡 实际GEMM引擎的微架构

结合本课知识,一个实用的GEMM引擎包含以下组件:

组件功能面积占比设计要点
PE阵列矩阵乘法40%INT8×INT8+INT32累加
权重Buffer存储当前块权重25%双缓冲,DMA预取
激活Buffer存储当前块激活20%双缓冲,IM2COL
输出Buffer存储结果+部分和10%支持部分和累加
控制器分块调度5%FSM+计数器

🔧 矩阵乘法的分块计算完整实现

本节展示如何用4×4阵列计算任意大小的矩阵乘法:

分块策略伪代码

// 4×4阵列计算M×K × K×N矩阵:
// for tm in range(0, M, 4):     // 遍历A的行块
//   for tn in range(0, N, 4):   // 遍历B的列块
//     C[tm:tm+4][tn:tn+4] = 0   // 清零输出块
//     for tk in range(0, K, 4): // 遍历K维度
//       // 加载A[tm:tm+4][tk:tk+4]到输入Buffer
//       // 加载B[tk:tk+4][tn:tn+4]到权重Buffer
//       // 阵列计算4×4块乘法
//       // 累加部分和到C[tm:tm+4][tn:tn+4]
//       C[tm:tm+4][tn:tn+4] += A_block × B_block
//
// 总块数 = ceil(M/4) × ceil(N/4) × ceil(K/4)
// 每块需要:4×4=16 cycles计算 + 开销

存储需求分析

矩阵大小分块数A数据重用B数据重用总DRAM访问
16×16 × 16×164×4×4=642×16²×16=8K
64×64 × 64×6416³=409616×16×2×64²×64=512K
256×256 × 256×25664³=26214464×64×2×256²×256=32M

分块策略的优化目标是最小化DRAM访问次数。沿K维度的部分和累加在片上完成,无需写回外存。

PE间互连的实现

// 输出驻留PE间的数据传递:
// A数据:从左到右水平传递
//   PE(i,j).a_out → PE(i,j+1).a_in
//   使用寄存器流水:always_ff @(posedge clk) a_out <= a_in;
//
// B数据:从上到下垂直传递  
//   PE(i,j).b_out → PE(i+1,j).b_in
//   使用寄存器流水:always_ff @(posedge clk) b_out <= b_in;
//
// 部分和:驻留在PE内部,不传递
//   PE(i,j).acc <= PE(i,j).acc + a_in * b_in;
//
// 连线资源:
//   水平:4条16-bit线 × 4行 = 256 bit
//   垂直:4条16-bit线 × 4列 = 256 bit  
//   总互连 = 512 bit (可管理)

📐 矩阵乘法的存储带宽计算

精确计算矩阵乘法的存储需求是系统设计的基础:

完整带宽模型

总DRAM访问 = 读A + 读B + 写C + 读C(部分和)

对于M×K × K×N矩阵乘法在4×4阵列上分块计算:

带宽需求与SRAM容量的关系

SRAM容量分块大小A重用B重用带宽节省
1KB2×250%
4KB4×475%
16KB8×887.5%
64KB16×1616×16×93.75%
256KB32×3232×32×96.9%

SRAM每增大4倍,带宽节省增加约一半。64KB是典型的成本效益平衡点。

实际系统中的带宽瓶颈分析

// 以ResNet-50第一层为例:
// Conv2D(3→64, 7×7, stride=2, input=224×224)
// 展开为矩阵乘法:64×147 × 147×12544
// 
// 总MAC = 64 × 147 × 12544 = 117.6M
// @200MHz 16PE = 117.6M / 3.2G = 36.7ms
// 
// 存储需求:
// A(权重): 64×147×2B = 18.8KB → 可完全放入SRAM
// B(输入): 147×12544×2B = 3.7MB → 需要分块
// C(输出): 64×12544×2B = 1.6MB → 需要分块写回
// 
// 带宽需求:B的3.7MB必须从DRAM读取
// @200MHz需要:3.7MB / 36.7ms = 100MB/s
// DDR4-2400可提供:~19GB/s → 带宽充足

📋 本课关键概念总结

概念定义硬件影响
计算密度FLOPs/访存字节数决定计算/带宽受限
数据复用同一数据被多个PE使用减少DRAM访问次数
流水线多级操作重叠执行提高吞吐量,增加延迟
双缓冲两组缓冲区交替使用隐藏数据加载延迟
分块计算大矩阵分成小块处理适配有限片上存储
饱和算术溢出时钳位到极值防止精度崩溃
时钟门控空闲模块关闭时钟降低动态功耗

🎯 学习目标检查

📚 拓展阅读

下一课预告:本课掌握的知识将在下一课中进一步深化。建议先完成练习,确保理解核心概念后再继续。