根因:预训练全零图像占位使 BN running_var 停在初始小值,推理时 特征放大至 ±1e6,DFC 在 token merge 点按最大幅度分配共享量化 scale,pred_token/act_tokens 全部被压成 0,HEF 输出对任何输入 恒定(详见 docs/reports/actpred_hef_insensitivity_rootcause_20260813.md) - export_deploy.py:新增 check_token_ranges 导出前 token 量级检查 (|img|>100 中止导出并提示微调;act/img 比例过大警告;归一化 模型在 z 空间采样,避免零方差列爆炸) - pretrain_mt.py:新增 --random-images(ImageNet 归一化噪声占位, 恢复 BN 统计)、--init-from(微调起点,含动作空间一致性警告)、 --normalize-actions 完整支持(训练集 stats 计算与保存、验证集复用) - gen_calibration.py:归一化模型的校准集自动 z-score 动作历史 - _onnx_validate.py / _npu_test.py:归一化模型适配(torch 参考 z-score、HailoPredictor 传 stats_path) - README:预训练章节补充新参数与 NPU 部署警告 - 单测 76/76 通过;冒烟验证:1 epoch 噪声微调后 |img| 2.46e5→8, 归一化后 |act| 694→2.9
6.3 KiB
actpred HEF 输出不敏感问题根因分析报告
日期:2026-08-13 设备:Raspberry Pi 5 + AI HAT 2(Hailo-10H);编译端 x86 + Hailo DFC 5.3 前情:actpred_hailo_hat2_inference_research_20260813.md §5
1. 背景
首版 HEF 在 NPU 上输出对全部 5 个输入不敏感(全 0 / 全 255 / 真实数据
输出完全一致)。修复连通性检查后 x86 重编译,新 HEF(224×224,含
_optimized.har 连通性 PASS)拷回树莓派复验,输出仍对输入不敏感。
本报告记录复验实验与最终根因定位。
2. 复验实验
2.1 新 HEF 扩展敏感性测试(_npu_test.py + 变体)
对 6 种输入变体逐组测试,与基线输出对比(max_diff):
| 变体 | max_diff | 结论 |
|---|---|---|
| 全零帧 | 0.0000 | 帧路径无效 |
| 全 255 帧 | 0.0000 | 帧路径无效(极端值同样无效) |
| 全零历史 | 0.0000 | 历史路径无效 |
| 全 500 历史 | 0.0000 | 历史路径无效 |
| pred_token=0 | 0.0000 | pred_token 无效 |
| pred_token=±0.09 | 0.0000 | pred_token 无效 |
输出为恒定向量 [11.814, -1.575, 9.451, 4.725, 11.814, 3.15, ...]
(uint8 q=[17,0,14,8,17,6],zp=2,scale=0.7876)。
2.2 新 HEF vstream 量化参数
| 输入 | 形状 | zp | scale | 判定 |
|---|---|---|---|---|
| input_layer1~3(帧) | (224,224,3) | 114 | 0.0187 | 正常(≈归一化像素范围/255) |
| input_layer4(历史) | (1,5,18) | 0 | 1.961 | 正常(=500/255,与数据范围 0~500 一致) |
| input_layer5(pred_token) | (1,1,128) | 129 | 1965.92 | 退化(数据 ±0.09,合理 scale 应 ~0.0007) |
与 x86 备份 tgz 中旧 HEF(64×64)对比:pred_token scale
18571.7 → 1965.92。scale 随校准数据变化,说明抖动校准确实生效——
推翻了"抖动校准未生效"的初始假设。
2.3 控制组(排除环境因素)
resnet_v1_18.hef 同 API、同驱动实测:真实图 top1=984 vs 全零图 top1=111,输出随输入变化。硬件、驱动 5.3.0、hailo_platform 封装 全部正常,问题在 HEF 本身。
3. 根因:BN 统计退化 → 特征量级 ±1e6 → 量化压死全部信号
3.1 证据链
pretrain_best_inference.pt中全部 4 个 BatchNorm 的running_var均为3.7e-4(每通道逐位相同)。预训练用全零图像占位,BN 从未 见到真实数据,running_var 经 ~75 次零方差更新指数衰减至此。- 推理(eval 模式)下每级 BN 放大
1/sqrt(3.7e-4 + 1e-5) ≈ 51倍, 4 级串联放大 ~7e6 倍。实测(torch 复算部署版图):- CNN 末级输出:±1.4e6 ~ 2.5e6(真实帧 / 全零帧均是)
img_tokens:±2.2e5 ~ 9.6e5act_tokens:±537 ~ 694(历史 0~500 × act_proj 权重,量级正常)
- torch 浮点推理为何不受影响:pre-LN 架构的 LayerNorm 在注意力前 归一化 token 幅度,±1e6 的残差流在 float32 下不损失精度。但 uint8 量化无法表达这种动态范围。
3.2 失效机理(量化视角)
DFC 在 token merge 点(concat(img, act, pred) + pos_embed 的
ew_add)按最大幅度分配共享量化 scale:
merge scale ≈ 250k / 127.5 ≈ 1965.92 ← 与 HEF 边缘实测一致
该 scale 下各支路命运:
| 支路 | 幅度 | 量化结果 |
|---|---|---|
| img_tokens | ±235k | 勉强存活(~120 级),但中间激活 ±1e6 超出 uint8 校准范围 → 饱和,任何输入差异被抹平 |
| act_tokens | ±537 | 537/1965.92 ≈ 0.27 → 全部量化到 zp(信号归零) |
| pred_token | ±0.09 | 全部量化到 zp(信号归零) |
| pos_embed | ±0.06 | 全部量化到 zp(信号归零) |
图像路径经 CNN 内各级退化激活的饱和 + 其余支路归零,最终网络退化为 对任何输入输出同一常量——与 2.1 的实测完全吻合。
3.3 为什么连通性检查通过仍然失败
dfc_compile.py 的检查验证的是图拓扑(输入层 BFS 可达输出层),
拓扑确实连通。问题是数值量级:量化把信号压成 0,等价于数值上的
断连。两者不矛盾,但都必须在编译前拦截。
4. 结论
- 当前 pretrain checkpoint 的图像支路数学上无法 uint8 量化 (特征 ±1e6 远超表达范围),无论重编译多少次都不可用。
- 必须先用真实/随机图像数据微调,恢复 BN 统计(特征回到 O(1)), 再重走导出→编译链路。
- pred_token 抖动校准本身没有 bug:merge 点 scale 由 ±235k 的图像 token 主导,pred_token 无论怎么抖都会被压死。
5. 修复与后续
5.1 已落地:导出前量级前置检查
actpred/export_deploy.py 新增 check_token_ranges():导出前用随机
噪声帧 + 0~500 均匀历史实测 token 量级,|img| > 100 直接中止导出
并提示微调;act/img 比例 > 32 时警告 merge 会粗量化图像路径。
实测:退化 checkpoint |img|=2.46e5 → 正确拦截;模拟健康 BN
(var=1)|img|=0.117 → 放行。全量单测 76/76 通过。
5.2 建议路径
- x86 微调:随机图像 + 混入真实帧(本机 6 episode × 30 帧可拷贝 过去),动作目标继续用 .mt 动力学;微调后 BN 统计恢复。
- 重走链路:
export_deploy.py(自带量级检查)→gen_calibration.py --deploy→dfc_compile.py(连通性检查)→ 拷回 HEF。 _npu_test.py4 角敏感性复验作为验收门槛。
5.3 遗留风险
- 健康场景下
act_tokens(±700)仍远大于img_tokens(O(1)): merge 共享 scale 会把图像路径粗量化到 ~2 级。后续可在部署图把action_history输入除以 255 并将act_w放大 255 倍(线性等价, 数学不变),把 act_tokens 压到 O(1) 量级。 - 随机图像微调后 BN 统计匹配噪声分布,部署端真实摄像头帧的统计 略有差异,特征量级可能偏大——导出时的新检查会兜底拦截,混入 真实帧可进一步降低风险。
5.4 验证链(防再犯)
| 阶段 | 检查 | 脚本 |
|---|---|---|
| 导出时 | token 量级 | export_deploy.py check_token_ranges |
| 编译前 | ONNX 数值正确性 | _onnx_validate.py |
| 编译时 | 输入连通性 | dfc_compile.py check_input_connectivity |
| 部署后 | 4 角输入敏感性 | _npu_test.py |