| 对称量化 | 非对称量化 | |
|---|---|---|
| zero_point | 0 | 非零 |
| 精度损失 | 大(不对称分布) | 小 |
| 计算 | 简单 | 需处理zp |
| 适用 | 权重 | 激活(ReLU后) |
MinMax(直接)、Percentile(99.9%分位)、MSE(最小化量化误差)、KL散度(TensorRT方案)。
支持对称/非对称/power-of-2量化,per-channel参数寻址,饱和截断,内置校准统计。
校准算法的选择对PTQ精度至关重要。以下是四种主流方法的详细对比:
优点:实现简单,计算量小。缺点:对异常值(outlier)极其敏感,一个异常值就会拉大scale导致大部分值量化精度下降。
取99.9%或99.99%分位数作为截断阈值,忽略极端值:
优点:抗异常值。缺点:百分位数选择需要实验,可能丢失极端但重要的值。
遍历多个候选阈值,选择使量化MSE最小的阈值:
优点:直接优化量化误差。缺点:搜索空间大,校准慢(需遍历数百个候选)。
最小化原始分布和量化分布的KL散度:
优点:TensorRT默认方案,效果最好。缺点:实现复杂,需要直方图统计和KL计算。
| 校准方法 | ResNet-50 Top-1 | 精度损失 | 校准时间 |
|---|---|---|---|
| FP32基线 | 76.13% | - | - |
| MinMax | 75.01% | 1.12% | 秒级 |
| Percentile(99.9%) | 75.64% | 0.49% | 秒级 |
| MSE | 75.82% | 0.31% | 分钟级 |
| KL散度 | 75.93% | 0.20% | 分钟级 |
| Per-channel KL | 75.98% | 0.15% | 分钟级 |
完成本课后,你已掌握 训练后量化(PTQ) 的核心概念与硬件实现方法。
量化误差在网络中逐层传播,理解其传播规律对设计鲁棒的量化系统至关重要:
对于均匀量化,量化误差的统计特性:
// 误差传播分析:
//
// 卷积层:y = conv(W, x) + b
// 量化后:ŷ = conv(Q(W), Q(x)) + Q(b)
//
// 误差分解:
// e_total = conv(W, x) - conv(Q(W), Q(x))
// = conv(W-e_w, x-e_x) - conv(W, x) (一阶近似)
// ≈ -conv(e_w, x) - conv(W, e_x)
//
// 全连接层类似:
// e_total ≈ -e_w × x - W × e_x
//
// 关键结论:
// 1. 权重误差乘以输入幅度
// 2. 输入误差乘以权重幅度
// 3. 累积K次后误差放大√K倍
// 4. 因此:大权重→量化敏感,大激活→量化敏感
逐通道量化的误差几乎总是小于逐张量量化,因为每个通道可以有最适合的scale。代价是多存储N组量化参数。
在ImageNet验证集上,不同量化策略的效果:
| 模型 | FP32 | INT8 PT | INT8 PC | INT4 PC |
|---|---|---|---|---|
| ResNet-18 | 69.76 | 69.10 | 69.60 | 67.20 |
| ResNet-50 | 76.13 | 75.40 | 75.93 | 73.50 |
| MobileNet-V2 | 71.87 | 69.80 | 70.51 | 65.30 |
| EfficientNet-B0 | 77.07 | 75.20 | 76.10 | 72.80 |
| ViT-B/16 | 81.80 | 78.50 | 79.90 | 73.20 |
PT=逐张量,PC=逐通道。MobileNet对量化更敏感(深度可分离卷积)。
本节提供该模块的详细实现指南、常见bug和调试方法:
每个模块应遵循统一的接口规范:
// 标准模块接口模板:
// module xxx #(
// parameter DATA_W = 16,
// parameter ...
// )(
// input wire clk, // 时钟
// input wire rst_n, // 异步复位,低有效
// input wire en, // 模块使能
// // 数据输入
// input wire [DATA_W-1:0] data_in,
// input wire data_valid,
// // 数据输出
// output reg [DATA_W-1:0] data_out,
// output reg data_out_valid,
// // 状态
// output wire busy,
// output wire error
// );
//
// 设计规则:
// 1. 所有时序逻辑使用posedge clk
// 2. 复位使用异步复位(negedge rst_n)
// 3. 参数化设计,便于配置
// 4. valid-ready握手协议
// 5. 每个模块有独立的en信号
| 症状 | 可能原因 | 调试方法 | 解决方案 |
|---|---|---|---|
| 输出全零 | 复位后未初始化 | 检查复位逻辑 | 确保复位释放后en有效 |
| 数据错位 | 流水线级数不匹配 | 波形对比输入输出 | 对齐valid信号延迟 |
| 溢出 | 累加器位宽不够 | 监测累加器最高位 | 增加位宽或加饱和逻辑 |
| 死锁 | valid-ready互等 | 追踪握手信号 | 确保下游始终能接收 |
| 时序违例 | 组合逻辑路径太长 | 查看综合报告 | 插入流水线寄存器 |
// Step 1: 语法检查
// verilator --lint-only module.v
//
// Step 2: 编写Testbench
// module tb;
// reg clk, rst_n;
// // 实例化DUT
// // 生成测试向量
// // 自动比对结果
// endmodule
//
// Step 3: 编译仿真
// verilator --cc module.v tb.v
// make -C obj_dir -f Vmodule.mk
// ./obj_dir/Vmodule
//
// Step 4: 波形调试(如果需要)
// verilator --trace --cc module.v tb.v
// 在testbench中: $dumpfile("wave.vcd"); $dumpvars;
// 模块集成清单:
// 1. 确认接口信号匹配(位宽、协议)
// 2. 时钟域是否一致(跨域需FIFO)
// 3. 复位策略是否统一
// 4. 信号命名是否规范
// 5. 是否有环路依赖(需打破)
// 6. 参数是否正确传递
Per-channel量化需要为每个通道存储scale和zero_point,硬件上需要高效的参数寻址机制:
// 量化参数存储方案:
// 方案1: 寄存器阵列(小规模)
// reg [SCALE_W-1:0] scale [0:NUM_CH-1];
// reg [QW-1:0] zp [0:NUM_CH-1];
// 面积: NUM_CH × (SCALE_W + QW) 寄存器
// 64通道INT8: 64×24 = 1536 bit ≈ 300门
//
// 方案2: SRAM(大规模)
// 参数存储在专用SRAM中,按通道索引读取
// 512通道: 512×24 = 12Kbit ≈ 1.5KB SRAM
// 延迟: 1-2 cycle (SRAM读取)
//
// 方案3: 压缩存储(功耗优化)
// Power-of-2 scale: scale = 2^n,只需存n
// 每通道仅需5-bit(scale指数) + 8-bit(zp) = 13 bit
// 512通道: 512×13 = 6.5Kbit ≈ 0.8KB
| 概念 | 定义 | 硬件影响 |
|---|---|---|
| 计算密度 | FLOPs/访存字节数 | 决定计算/带宽受限 |
| 数据复用 | 同一数据被多个PE使用 | 减少DRAM访问次数 |
| 流水线 | 多级操作重叠执行 | 提高吞吐量,增加延迟 |
| 双缓冲 | 两组缓冲区交替使用 | 隐藏数据加载延迟 |
| 分块计算 | 大矩阵分成小块处理 | 适配有限片上存储 |
| 饱和算术 | 溢出时钳位到极值 | 防止精度崩溃 |
| 时钟门控 | 空闲模块关闭时钟 | 降低动态功耗 |