Re-quantization

详解“重量化”操作

1. 为什么需要“重量化”?(背景与动机)

在你的设计中,卷积层和全连接层的输入、权重都是 INT8(8位整型)
- 当你执行一次乘法时:INT8 × INT8 = INT16
- 卷积操作包含了大量的乘加运算(MAC)。比如在你的 Conv1 中,一个输出点需要进行 \(1 \times 11 \times 7 = 77\) 次乘加。多个 INT16 累加在一起,如果还用 INT16 存,必然会溢出(Overflow)
- 因此,硬件上必须使用更宽的寄存器(规范中要求是 INT32)来保存累加的中间结果(Partial Sum)。

问题来了:如果当前层的输出是 INT32,那喂给下一层的输入就变成 INT32 了。下一层如果做 INT32 × INT8 的乘法,硬件乘加器(MAC)的面积和功耗将呈指数级爆炸,完全违背了课程要求的 PPA(功耗、性能、面积)指标。

解决办法:在把结果传给下一层(或存入SRAM)之前,必须把 INT32 重新缩放(映射)回 INT8。这个过程就叫 重量化(Re-quantization)


2. 数学原理与硬件设计的妥协

在算法端(比如 TensorFlow/PyTorch),重量化通常是一个简单的浮点数乘法:

\[O_{INT8} = M \times I_{INT32}\]

这里的 \(M\) 是一个浮点数(通常是介于 0 到 1 之间的小数,比如 0.00345),由算法训练和量化过程决定。

从数字芯片设计的角度看:
如果在芯片里放一个浮点乘法器来算这个 \(M\),面积和功耗代价极大。数字逻辑设计最讨厌浮点数,最喜欢的是整数乘法移位(Shift)

因此,规范中给出了定点化实现公式

\[M = 2^{-n} \times M_0\]

也就是说,我们用一个整数 \(M_0\)(INT16) 配合向右移位(除以 \(2^n\),来逼近那个浮点数 \(M\)

所以最终在硬件中实际执行的公式是:

\[O_{INT8} = (I_{INT32} \times M_0) \gg n\]

3. 详细举例说明

假设算法工程师告诉你,当前卷积层算完后,要把 INT32 压回 INT8,给出的浮点比例系数是 \(M = 0.0054321\)

Step 1: 助教/算法端的参数转换(软件提前做好的事)

助教会帮你把 \(M = 0.0054321\) 转换成硬件友好的 \(M_0\)\(n\)
假设选择右移 \(n = 16\) 位(即除以 \(2^{16} = 65536\)):

\[M_0 = M \times 2^{16} = 0.0054321 \times 65536 \approx 356\]

所以,助教提供给你的参数就是:\(M_0 = 356\) (INT16), \(n = 16\)
(注意:\(356 \div 65536 = 0.005432128\),这与原始浮点数极为接近,产生的误差在神经网络中完全可接受。)

Step 2: 硬件执行重量化(你在Verilog里要做的事)

假设你的 MAC 算完之后,某个通道输出的累加结果是:\(I_{INT32} = 15000\)

  1. 整数乘法(Multiplier)

$\(15000 (\text{INT32}) \times 356 (\text{INT16}) = 5340000 (\text{内部通常用 48 位 wire 保存})\)$

二进制:5340000 = 48'b000...0101_0001_0111_1000_0000_0000_0000

  1. 移位操作(Shift)
    规范要求右移 \(n=16\) 位。在 Verilog 中,右移 16 位就是直接截断低 16 位(如果是固定值 \(n\),在硬件上连逻辑门都不需要,就是单纯的连线变换 / Wire Routing,完全不消耗面积!):
    5340000 >> 16
    相当于截掉低 16 位,剩下:1010001(二进制),也就是十进制的 \(81\)

  2. 结果验证
    我们用纯浮点算一遍:\(15000 \times 0.0054321 = 81.4815\)
    硬件定点算出来是 \(81\)。两者完美契合!这个 \(81\) 就可以用一个 INT8 寄存器存下来,作为输入传给下一层(或者去过 ReLU 激活函数)了。


4. 总结:作为数字 IC 工程师,你在写 Verilog 时需要注意什么?

如果你在写 Verilog RTL 代码,对于重量化模块,你需要实现以下逻辑:

  1. 乘法器:例化一个 32-bit × 16-bit 的有符号乘法器($signed(I_INT32) * $signed(M0))。
  2. 移位逻辑:根据助教给的 \(n\),进行算术右移 >>> n
  3. 饱和截断(Saturation / Clipping)重点!高分项!):
    虽然理论上算出来应该在 INT8 范围内,但实际硬件计算中,经过乘法和移位后的结果仍可能超出 [-128, 127] 的有符号 INT8 范围。作为严谨的设计,你需要加一个钳位电路:
  4. 如果结果 > 127,强制输出 127
  5. 如果结果 < -128,强制输出 -128

一句话总结
重量化操作,就是为了防止中间数据位宽越积越大拖垮硬件硬件,利用“乘以一个整数再截掉低几位(右移)”这种极具硬件性价比的方式,把 32 位大数据重新压缩回 8 位数据的过程。

希望这个解释对你接下来的 RTL 编码有帮助!祝你在课程设计中成功流片(跑通综合和物理设计),拿到高分!如果对具体的 Verilog 实现(如符号位扩展、饱和截断怎么写最高效)有疑问,随时可以继续探讨。




Enjoy Reading This Article?

Here are some more articles you might like to read next: