| 格式 | 范围 | 精度 | 用途 |
|---|---|---|---|
| Q0.15 | [-1, 1-2⁻¹⁵] | 3.05e-5 | 高精度分类 |
| Q7.8 | [-128, 128-2⁻⁸] | 3.9e-3 | 通用CNN |
| Q0.7(INT8) | [-1, 1-2⁻⁷] | 7.8e-3 | 量化推理 |
同Q格式直接加减,结果Q不变,注意溢出保护。
Q7.8 × Q7.8 = Q15.16,需截断回Q7.8。
实现了5种运算(ADD/SUB/MUL/MAC/SHIFT)的定点单元和浮点-定点转换器。乘法后自动截断高位和小数位,MAC支持三操作数累加。
理解定点数的精度限制对设计高质量AI加速器至关重要。我们来量化分析不同Q格式的误差特性:
| 格式 | n(小数位) | SNR(dB) | 均方误差 | 最大误差 |
|---|---|---|---|---|
| Q0.15 | 15 | 91.06 | 7.6e-10 | 3.05e-5 |
| Q7.8 | 8 | 49.92 | 1.3e-5 | 3.9e-3 |
| Q0.7(INT8) | 7 | 43.90 | 5.1e-5 | 7.8e-3 |
| Q0.3(INT4) | 3 | 19.82 | 3.3e-3 | 1.25e-1 |
在矩阵乘法中,量化误差会随着累加而增大:
对于K=1024的累加,误差放大约32倍。这就是为什么累加器位宽通常比数据位宽大——INT8乘法需要INT32累加器。
饱和算术是AI加速器中防止溢出导致精度崩溃的关键机制:
// 饱和加法伪代码
if (a > 0 && b > 0 && result < 0) → 正溢出,结果 = MAX
if (a < 0 && b < 0 && result > 0) → 负溢出,结果 = MIN
否则 → 结果 = a + b
| 舍入方式 | 硬件开销 | 误差偏向 | 应用 |
|---|---|---|---|
| 截断(Truncate) | 0(直接丢弃) | 偏向负无穷 | 面积敏感场景 |
| 四舍五入(Round) | 1个加法器 | 无偏 | 通用推荐 |
| 收敛舍入(Conv.) | LFSR+1个加法器 | 无偏+等概率 | 高精度训练 |
| 随机舍入(Stoch.) | LFSR+比较器 | 期望无偏 | 理论研究 |
累加器位宽的选择是精度和面积的权衡:
| 数据格式 | 乘法结果位宽 | 累加K=256 | 累加K=1024 | 典型选择 |
|---|---|---|---|---|
| INT8 × INT8 | 16 | 24 | 26 | 32(留余量) |
| INT16 × INT16 | 32 | 40 | 42 | 40 |
| FP16 × FP16 | FP16 | FP32 | FP32 | FP32 |
以ResNet-18在ImageNet上的精度为例,不同数值表示的效果:
| 数值格式 | Top-1精度 | 精度损失 | 面积效率 |
|---|---|---|---|
| FP32 | 69.76% | 基线 | 1× |
| FP16 | 69.74% | 0.02% | 1.8× |
| INT16(Q7.8) | 69.72% | 0.04% | 2.5× |
| INT8(per-ch) | 69.50% | 0.26% | 4.0× |
| INT8(per-tensor) | 68.90% | 0.86% | 4.0× |
| INT4(per-ch) | 65.20% | 4.56% | 8.0× |
完成本课后,你已掌握 定点数运算 的核心概念与硬件实现方法。
本节深入探讨定点数运算的Verilog实现细节,包括边界条件和时序优化:
// 饱和加法器:检测溢出并钳位
// 关键信号:
// sign_a, sign_b: 操作数符号位
// sign_sum: 和的符号位
// overflow_pos: 两个正数相加得到负数
// overflow_neg: 两个负数相加得到正数
//
// always_ff @(posedge clk)
// {carry, sum} <= a + b; // 宽一位的加法
// if (sign_a == sign_b && sign_sum != sign_a)
// result <= sign_a ? MIN_VAL : MAX_VAL; // 饱和
// else
// result <= sum;
//
// 延迟:1 cycle(流水线)
// 面积:比普通加法器多约10%(溢出检测逻辑)
16×16→32位乘法后需要截断到16位。截断方式对精度影响显著:
// 乘法结果截断策略:
// 假设 Q7.8 × Q7.8 = Q15.16 (32位)
// 需要截断回 Q7.8 (16位)
//
// 策略1: 直接截断 (Truncate)
// result = product[23:8]; // 取中间16位
// 误差范围: [0, -1 LSB] 偏向性误差
//
// 策略2: 四舍五入 (Round-Half-Up)
// result = (product + (1 << 7))[23:8]; // 加0.5后截断
// 误差范围: [-0.5, +0.5 LSB] 无偏
//
// 策略3: 收敛舍入 (Convergent/Round-To-Even)
// 当低位恰好=0.5时,舍入到偶数
// 需要: if(低8位==128) result[0]==0 ? 截断 : 加1
// 误差范围: [-0.5, +0.5 LSB] 无偏且等概率
MAC(乘累加)是神经网络的核心操作,累加器位宽选择至关重要:
// MAC: result <= result + a * b
// 位宽分析:
// a: Q7.8 (16位有符号)
// b: Q7.8 (16位有符号)
// a*b: Q15.16 (32位有符号)
// 累加K次需要额外 ⌈log2(K)⌉ 位
//
// K=256: 需要 32+8 = 40位累加器
// K=1024: 需要 32+10 = 42位累加器
// K=4096: 需要 32+12 = 44位累加器
//
// 实际设计通常选32位(8×8乘法)或40位(16×16乘法)
// 溢出时通过饱和或右移处理
训练用FP32,推理用INT8/INT16,需要高效的转换电路:
| 步骤 | 操作 | 延迟 | 面积 |
|---|---|---|---|
| 提取字段 | 位选择 | 0 cycle | ~0 |
| 缩放 | 乘法(24×16) | 1-2 cycle | ~2000门 |
| 舍入 | 加法+截断 | 1 cycle | ~200门 |
| 饱和 | 比较+MUX | 1 cycle | ~300门 |
| 总计 | - | 3-4 cycle | ~2500门 |
以4×4矩阵乘法阵列为例,对比不同精度格式的面积和功耗:
| 格式 | PE面积(gate) | 16个PE面积 | 功耗(mW@200MHz) | 精度损失 |
|---|---|---|---|---|
| INT4×INT4 | ~150 | 2,400 | 0.5 | 5-10% |
| INT8×INT8 | ~600 | 9,600 | 1.6 | <1% |
| INT16×INT16 | ~2,500 | 40,000 | 6.4 | <0.1% |
| FP16×FP16 | ~8,000 | 128,000 | 20.5 | 基线 |
INT8相比FP16面积减少12.5倍,功耗减少12.8倍,精度损失仅1%!这是INT8成为推理标准的根本原因。
本节展示关键运算的完整Verilog代码片段和设计考量:
// 饱和乘法:Q7.8 × Q7.8 → Q7.8
// 步骤1: 16×16→32位乘法
// 步骤2: 取中间16位[23:8]作为Q7.8结果
// 步骤3: 检查高8位是否全部为符号位扩展
// 如果高位不一致 → 溢出 → 饱和
//
// wire signed [31:0] product = a * b; // 32位乘法结果
// wire signed [15:0] truncated = product[23:8]; // 截断到Q7.8
// wire overflow_pos = product[31:24] != 8'h00 && !(product[23] && product[31:24] == 8'hFF);
// wire overflow_neg = product[31:24] != 8'hFF && product[23];
// assign result = overflow_pos ? 16'sd32767 : // 正饱和
// overflow_neg ? 16'sd32768 : // 负饱和
// truncated;
// 3级流水MAC单元:
// Stage 1: 乘法 (组合逻辑,需要关键路径优化)
// Stage 2: 累加 (寄存器,1 cycle延迟)
// Stage 3: 截断/饱和 (组合逻辑)
//
// 优点:时序好,可以跑更高频率
// 缺点:3 cycle延迟,但吞吐量1 MAC/cycle
//
// always_ff @(posedge clk)
// stage1_mul <= a * b;
// stage2_acc <= stage2_acc + stage1_mul;
// stage3_result <= saturate(stage2_acc);
//
// 吞吐量 = f_max × 1 MAC/cycle
// 200MHz下 = 200 MAC/s/PE
| 运算 | 8-bit | 16-bit | 32-bit |
|---|---|---|---|
| 加法器 | 0.0001mm² | 0.0002mm² | 0.0005mm² |
| 乘法器 | 0.0005mm² | 0.002mm² | 0.01mm² |
| MAC单元 | 0.0006mm² | 0.0025mm² | 0.012mm² |
| 延迟(cycle) | 1 | 1 | 2 |
| 功耗(mW@200MHz) | 0.05 | 0.15 | 0.6 |
| 概念 | 定义 | 硬件影响 |
|---|---|---|
| 计算密度 | FLOPs/访存字节数 | 决定计算/带宽受限 |
| 数据复用 | 同一数据被多个PE使用 | 减少DRAM访问次数 |
| 流水线 | 多级操作重叠执行 | 提高吞吐量,增加延迟 |
| 双缓冲 | 两组缓冲区交替使用 | 隐藏数据加载延迟 |
| 分块计算 | 大矩阵分成小块处理 | 适配有限片上存储 |
| 饱和算术 | 溢出时钳位到极值 | 防止精度崩溃 |
| 时钟门控 | 空闲模块关闭时钟 | 降低动态功耗 |