QAT 量化配置的等效构建方法 —— 从 Base 之争到"量化"

一、背景:为什么大家几乎都会从 base_int16 开始?

在 QAT 项目中,只要遇到精度问题,工程师的第一反应通常是:

先上全 int16,看精度上限。

这是完全合理的。

原因:

  • int16 动态范围更大
  • 量化误差更小
  • 更接近浮点
  • 能快速验证"模型是否具备量化可行性"

如果全 int16 精度仍不好,问题往往不在 bit-width,而在:

  • scale 分布异常
  • observer 未收敛
  • 插桩位置不合理
  • 数据分布问题

因此:

base_int16 是"精度上限探测工具"。

这一步是科学且必要的。

二、工程现实:最终目标往往是性能

但真实部署环境通常是:

  • 延时受限
  • 带宽受限
  • 片上存储受限

在这种前提下:

全 int16 基本不可能成为最终部署形态。

所以工程上更合理的路径应该是:

以 base_int8 作为默认底座对精度敏感区域做局部升级

这意味着:

  • int16 用来探上限
  • int8 用来做工程

这两个阶段目标不同。

三、真正的困难:从 base_int16 切回 base_int8

问题往往出现在这里。

当我们在 base_int16 下完成精度探索后,会得到大量细节信息:

  • 哪些 layer 敏感
  • 哪些 layer 需要 fix_scale
  • 哪些模块 output 必须 int16
  • 哪些 Conv / Matmul 必须 int16 输入

但当切换到 base_int8 时,会发现:

  • 默认 ModuleNameTemplate 不同
  • 默认 ConvDtypeTemplate 不同
  • 默认 MatmulDtypeTemplate 不同
  • 输出 dtype 传播链改变

结果:

相同 prefix 写法,生效行为完全不同。

这就意味着:

base_int16 的配置不能直接复制到 base_int8。

四、问题的本质:不要让 base 决定量化形态

量化系统本质是"分层覆盖系统"。

如果让 base 决定形态,你就会被 base 牵着走。

真正应该控制的是:

每个模块最终生效的 dtype 拓扑。

五、方法论框架:量化拓扑设计

整个方法可以抽象为五个阶段:

Plain 复制代码
1. 精度上限探测(全 int16)
2. 敏感层识别
3. 结构分析
4. 等效拓扑构建
5. int8 工程落地

我们逐步展开。

六、第一阶段:全 int16 精度上限探测

典型配置:

Plain 复制代码
ModuleNameTemplate({"": qint16})
ConvDtypeTemplate(input_dtype=qint16, weight_dtype=qint8)
MatmulDtypeTemplate(input_dtypes=qint16)

目标:

  • 验证量化可行性
  • 建立精度上限参考

七、第二阶段:使用 GlobalFakequantSwitch 定位问题

无论哪种路径,都建议使用:

Plain 复制代码
GlobalFakeQuantSwitch.disable()
需要去量化的操作
GlobalFakeQuantSwitch.enable()

典型使用思路:

  • 全局关闭 FakeQuant
  • 单模块开启
  • 或单模块关闭

确认:

  • 精度损失是否来自 bit-width
  • 是否来自 scale 更新
  • 是否来自某个具体模块

这一步可以避免盲目升位宽。

八、第三阶段:基于模型结构识别敏感模块

量化配置必须依赖模型结构。

例如:

  • backbone 多为线性卷积 → int8 风险低
  • head 中 aggregation / attention → 敏感

必须回答:

  • 哪些模块属于 backbone?
  • 哪些属于 neck?
  • 哪些属于 head?
  • 哪些包含 matmul?
  • 哪些包含 feature aggregation?

没有结构分析,就没有精准升级。

九、第四阶段:构建"等效量化拓扑"

核心思想:

默认 int8 + 精准 prefix 升级

Step 1:统一默认 base_int8

Plain 复制代码
ModuleNameTemplate({"": qint8})
ConvDtypeTemplate(input_dtype=qint8, weight_dtype=qint8)
MatmulDtypeTemplate(input_dtypes=qint8)

这是性能底座。

Step 2:定义敏感模块列表

Plain 复制代码
int16_modules = [
    "head.anchor_encoder",
    "head.lidar_shared_conv",
    "head.layers",
]

Step 3:输出 dtype 升级

Plain 复制代码
ModuleNameTemplate({
    name: qint16 for name in int16_modules
})

Step 4:Conv 输入升级

Plain 复制代码
ConvDtypeTemplate(
    input_dtype=qint16,
    weight_dtype=qint8,
    prefix=int16_modules
)

Step 5:Matmul 输入升级

Plain 复制代码
MatmulDtypeTemplate(
    input_dtypes=qint16,
    prefix=int16_modules
)

十、等效性的关键点

如果你在 base_int16 下:

  • backbone output=int8
  • head output=int16

那么你必须保证:

在 base_int8 下通过 prefix 升级后,

每个模块最终 output dtype 完全一致。

验证方法:

  • 打印每层最终 dtype
  • 单层剔除测试
  • 对比精度曲线

十一、fix_scale 的位置

fix_scale 与 dtype 是两个维度:

  • dtype 控制动态范围
  • fix_scale 控制 scale 是否锁定

某些 head 模块:

  • 可能必须 int16
  • 也可能必须 fix_scale

但不要把 fix_scale 当成"精度万能补丁"。

十二、工程调优路径建议

推荐流程:

  1. 全 int8 → 测性能
  2. 全 int16 → 测精度上限
  3. GlobalFakequantSwitch 定位问题
  4. 结构分析敏感模块
  5. 构建统一 int8 base
  6. prefix 升级
  7. 单层剔除
  8. 构建精度-性能 Pareto 曲线

十三、常见误区

❌ 误区 1:int16 一定比 int8 精度高

实际很多 backbone 层 int8 几乎无损。

❌ 误区 2:回退法可以长期维护

回退法适合探测上限,不适合工程维护。

❌ 误区 3:忽略输出 dtype 传播

输出 dtype 会影响下游模块。

十四、最终总结

量化优化不是:

  • 从 int16 往下退
  • 从 int8 往上加

而是:

设计一个清晰、可迁移、可验证的量化拓扑结构。

当我们做到:

  • base 可替换
  • prefix 可迁移
  • 最终 dtype 可验证
  • FakeQuant 可局部控制

我们就掌握了 QAT 的量化配置体系。

相关推荐
Ai173163915797 小时前
10大算力芯片某某XXU全解析:CPU/GPU/TPU/NPU/LPU/FPGA/RPU/BPU/DPU/GPGPU
大数据·图像处理·人工智能·深度学习·计算机视觉·自动驾驶·知识图谱
康谋自动驾驶11 小时前
软实时、NTP还是PTP?矿山数采时间同步方案实测与选型
自动驾驶
aidesignplus12 小时前
扩散模型在自动驾驶路径规划中的技术演进与产业格局
人工智能·机器学习·自动驾驶
steven_yzx12 小时前
自动驾驶视觉相关的坐标系
自动驾驶
AGV算法笔记1 天前
CVPR 2025 最新感知算法解读:GaussianLSS 如何用 Gaussian Splatting 重构 BEV 表示?
算法·重构·自动驾驶·3d视觉·感知算法·多视角视觉
kobesdu1 天前
【ROS2实战笔记-13】Foxglove Studio:ROS可视化工具的另一条路
笔记·机器人·自动驾驶·ros
steven_yzx1 天前
自动驾驶相机坐标系转换
人工智能·数码相机·自动驾驶
硅谷秋水1 天前
《自动驾驶系统开发》英文版《Autonomous Driving Hanbook》推荐
人工智能·深度学习·机器学习·计算机视觉·语言模型·自动驾驶
steven_yzx1 天前
自动驾驶相机坐标系转换2
人工智能·数码相机·自动驾驶