博客算法工具链把模型扔上开发板②:J6 上 PTQ 掉点先别急着 QAT,我把问题拆成了 4 段

把模型扔上开发板②:J6 上 PTQ 掉点先别急着 QAT,我把问题拆成了 4 段

hailong2026-09-11
43
0
摘要: 模型在 GPU 上精度正常,到了 J6 做 INT8 量化却开始掉点,很多人的第一反应是换校准数据、调量化参数,甚至直接上 QAT。但实际工程中,问题可能早在 ONNX、图优化、量化转换或编译部署阶段就已经出现。本文结合 J6 工具链,把 PyTorch 模型到 J6 HBM 的精度变化明确拆成 4 段,通过逐阶段验证定位第一处误差,再决定到底该修模型、调 PTQ,还是进入 QAT。
关键词: J6、OpenExplorer、PTQ、QAT、INT8、ONNX、HBIR、HBM、hb_verifier、模型量化

1. PTQ 掉点了,真的是 PTQ 的锅吗?

最近在整理 J6 模型量化和部署流程时,又遇到了一个端侧部署中非常典型的现象:

看到这里,最自然的反应通常是继续修改 Calibration,或者干脆进入 QAT。但现在我越来越不喜欢这么快做决定,因为模型从训练环境到真正运行在 J6 上,中间远不止一次“FP32 → INT8”。

J6 工具链本身就把模型检查、性能验证、精度配置、量化、编译和部署拆成了多个阶段。官方 PTQ 深度使用指南也推荐先确认模型结构和平台支持情况,再逐渐进入性能和精度调优,而不是拿到 ONNX 就直接把所有问题交给量化解决。

图 1:J6 PTQ 推荐使用流程。图中模型检查、性能上限验证、配置生成和精度调优本身就是多个独立检查点。图片来源:地平线开发者社区。

所以现在碰到掉点,我会先把问题拆成四段:

阶段

核心问题

第一段:PyTorch → ONNX

模型还没进入 J6 工具链之前是否已经产生偏差

第二段:ONNX → Optimized Float

图优化或模型转换是否改变浮点模型行为

第三段:Float → INT8

真正的量化误差究竟在哪里

第四段:Quantized BC → HBM → J6

编译、输入和板端运行是否保持一致

这四段里,哪一段第一次明显出问题,就优先查哪一段。

这也是整篇文章唯一需要记住的原则。


第一段:PyTorch → ONNX

还没进入 J6,误差可能已经出现了

这一段其实和 J6 本身没有直接关系,却是我现在最不愿意跳过的一步。

训练模型导出:

然后执行一次:

模型能跑、Shape 对、结果看着也正常,于是默认:

ONNX 没问题。

实际上,“ONNX 能执行”和“ONNX 与 PyTorch 一致”完全是两个概念。

真实项目里最容易出现的问题往往也并不复杂,例如:

尤其是 PyTorch 验证程序和 ONNX 推理程序分别维护一套 preprocess() 时,很容易出现一种比较讨厌的 Bug:

程序完全正常,模型也有输出,但结果已经悄悄变了。

因此现在我会尽量让 PyTorch 和 ONNX 使用同一份已经完成前处理的数据,先比较原始模型 Tensor,再跑完整验证集。

例如最简单的输出检查:

单张图片主要用于 Debug,真正判断模型有没有发生变化,还是要重新计算 Accuracy、Precision、Recall、mAP 等完整指标。

检测模型还有一个额外的坑

检测模型比分类模型更容易把这种差异藏起来。

假设:

检测阈值:

两个模型最终都会画出这个框。

肉眼看:

但数值实际上已经开始发生变化。

如果后面的 INT8 再产生一点误差:

最终立刻变成:

模型

最终结果

PyTorch

TP

ONNX

TP

INT8

FN

最后看到的是:

INT8 漏检。

但真正的问题并不是从 INT8 才突然发生。

所以第一段的结论很简单:

PyTorch 和 ONNX 没对齐之前,不要急着讨论 PTQ。


第二段:ONNX → Optimized Float

“只是图优化”也应该验证一次

如果 PyTorch 和 ONNX 已经基本一致,下一步才进入 J6 工具链。

这一阶段我以前比较容易忽略:

因为直觉上会认为:

图优化都是等价变换,不应该影响结果。

大多数时候确实如此。

但工程上我越来越喜欢区分两个概念:

“理论上应该一样”

和:

“实际验证过一样”

J6 PTQ 转换过程中会产生不同阶段的模型产物,例如原始浮点模型、优化后的浮点模型以及后续量化模型。这恰好给了我们逐级验证的机会。

如果出现:

那就暂时不用继续研究 Calibration。

因为此时:

都还不是第一嫌疑人。

J6 官方的一致性分析同样采用这种“先定位阶段”的思路。QAT 链路中会明确区分 qat.pt、qat_export.pt、qat.bc、quantized.bc、nv12_quantized.bc 和最终 HBM,而不是只比较训练模型和板端模型。

图 2:J6 QAT/定点链路中,不同阶段模型本身就是一致性定位的重要依据。图片来源:地平线开发者社区。

虽然这张图来自 QAT 一致性教程,但它体现的排错方法同样适用于 PTQ:

不要只检查:

而应该找到:

这会让问题空间小很多。


第三段:Float → INT8

到这里,才真正轮到“量化问题”

前两段确认正常以后,现在终于可以比较:

如果明显掉点发生在这里,那么“PTQ 精度问题”这个判断才真正成立。

这时候我通常不会先问:

Calibration 再加多少张图?

而是先看三个方向。

3.1 Calibration 数据有没有代表性

校准集最大的价值不是“数量足够多”,而是能不能覆盖真实输入分布。

如果模型实际运行场景和校准数据差异很大,那么量化得到的 Activation Range 本身就可能不合理。

所以:

不一定天然优于:

3.2 Activation Range 有没有被异常值拉开

假设某层绝大部分激活都集中在:

但极少量数据跑到了:

量化范围被扩大以后,有限的 INT8 表示区间需要覆盖更宽的数值范围,正常数据区域的量化精度自然会受到影响。

这也是实际项目中比较值得观察数据分布的原因。

3.3 是整个网络敏感,还是只有几个节点敏感?

这一点尤其重要。

如果模型大部分节点量化都很稳定,只有少数节点出现明显误差,那么它已经从:

“这个模型不适合 INT8”

变成了:

“这几个节点需要处理。”

J6 QAT 调优指南同样非常强调敏感度分析和混合精度,而不是全局简单地从 INT8 切换到高精度。J6E/M 主要采用 INT8+INT16 混合精度思路,而 J6P/H 可以进一步利用 FP16 等配置处理难量化结构。

图 3:J6E/M QAT 精度调优思路中,会分别从 Float、Calibration、QAT 等阶段继续缩小量化问题范围,而不是直接把整个模型切到更高精度。图片来源:地平线开发者社区。

官方 PTQ Debug 工具的设计也很类似:通过量化误差、敏感度和数据分布继续判断问题来自 Weight 还是 Activation,并进一步决定是否使用高精度、调整网络或进入 QAT。J5→J6 迁移指南也明确说明,J6 与 J5 的 PTQ 精度 Debug 推荐思路保持一致。

图 4:PTQ 精度 Debug 的定位流程。真正有价值的并不是“模型掉了多少点”,而是继续判断误差来源及敏感节点。图片来源:地平线开发者社区。

到这里,如果确定:

那么才值得认真讨论:


第四段:Quantized BC → HBM → J6

模型没坏,也可能“部署坏了”

第四段是我觉得特别容易被忽略的一段。

假设:

这时候如果继续回去调整 QAT,很可能又找错方向。

J6 的模型从定点 HBIR 到实际板端部署,还需要经过 compile,并且图像输入场景还可能涉及 NV12 前处理节点、Stride、Padding 等问题。官方 J6 一致性教程专门把 convert、NV12 节点插入以及 compile 分成独立阶段排查。

其中非常实用的一个工具就是:

J6 的 hb_verifier 支持 ONNX、HBIR、HBM 之间进行一致性比较,可以用来快速判断问题究竟发生在模型转换的哪一段。J5→J6 迁移指南也明确列出了它在 J6 工具链中的用途。
图 5:J6 compile 阶段的一致性定位。quantized.bc 正常而 HBM 异常时,可以先通过 hb_verifier 判断 compile 前后是否已经产生偏差。图片来源:地平线开发者社区。

这一阶段如果模型本身已经对齐,排查重点就应该转向:

而不是:

也就是说:

板端精度异常,不一定意味着模型量化异常。

这是第四段最重要的结论。


5. 四段全部检查完,什么时候才值得上 QAT?

经过前面四段以后,QAT 的位置其实已经很清楚了。

当前状态

是否优先进入 QAT

PTQ 已达到业务精度

没必要

PyTorch → ONNX 已经掉点

Optimized Float 出现明显偏差

BC / HBM 一致性异常

板端输入或后处理不一致

Float → INT8 存在明确量化损失

可以

Calibration 调优后仍明显掉点

可以

少数量化敏感节点影响整体精度

更值得尝试

J6 官方 QAT 调优本身也不是“打开 QAT 再训练几轮”这么简单。针对不同 J6 平台,需要根据硬件能力选择 INT8、INT16、FP16 等混合精度策略,并通过模型检查、Calibration、敏感度和 Debug 工具不断缩小问题。

所以我现在更愿意把 QAT 定义成:

确认模型确实不适应当前量化误差以后,再让模型通过训练主动适应这种误差。

而不是:


6. 最终目标:把“四段排查”变成自动回归

如果只部署一个模型,人工检查四段问题还可以接受。

但真实项目往往是:

模型每迭代一次,都重新手动检查 PyTorch、ONNX、BC、HBM,很快就会变成重复工作。

所以我更希望最后把这套流程固定下来:

最终先看一个简单的结果:

Check

Result

PyTorch Baseline

PASS

PyTorch ↔ ONNX

PASS

ONNX ↔ Optimized Float

PASS

Float ↔ INT8

FAIL

BC ↔ HBM

-

J6 Regression

-

这样看到的第一件事情就是:

第三段 FAIL。

而不是打开几十个 Log,再凭经验猜到底哪里出了问题。


7. 最近一个月的一个趋势:工具链开始从“量化模型”走向“诊断模型”

最近一个月看其他 AI 工具链时,这个趋势也越来越明显。

9 月 8 日,Ultralytics v8.4.143 给 YOLO26 加入了 INT8 QAT 支持,开发者可以直接在训练中模拟 INT8 量化误差。

AMD 最近则更进一步,在 Quark 量化工具中引入了 AI Agent Skills,可以从模型检查、量化方案选择、Calibration 到结果验证和 Debug 逐步组织量化工作流。

这让我觉得模型部署工具正在从过去的:

逐渐走向:

J6 里的 hb_verifier、QAT Debug、敏感度分析等能力,本质上也是同一个方向:

模型转换成功只是第一步,快速知道它为什么不对,才是真正提高工程效率的地方。


8. 写在最后

现在再遇到 PTQ 掉点,我不会第一时间问:

“QAT 怎么调?”

而会先把整个过程拆成四段:

然后只做一件事情:

找到第一处明显发生变化的位置。

第一段错了,就查导出和前后处理;第二段错了,就查 Float 图转换;第三段错了,才真正研究 Calibration、敏感层和 QAT;第四段错了,则重点检查 compile、输入格式和板端 Runtime。

把问题这样拆开以后,很多原本看起来很玄学的“量化掉点”,其实都会变成一个范围非常有限的工程问题。

模型部署最怕的不是掉点,而是把所有掉点都叫作量化误差。


参考资料

地平线 J6 相关流程和工具使用参考官方《PTQ 深度使用指南》《J6 平台 QAT 精度一致性问题分析流程》《J5 到 J6 算法部署迁移指南》《J6E/M、J6P/H QAT 精度调优》等资料;不同 OpenExplorer 版本的工具接口和默认策略可能继续变化,实际工程使用应以当前版本官方文档为准。

算法工具链
技术深度解析征程6
评论0
0/600