Files
blackbird/docs/reports/actpred_hef_insensitivity_rootcause_20260813.md
T
qing 52c0ade3c4 fix(actpred): 定位 HEF 输入不敏感根因,补齐归一化预训练链路
根因:预训练全零图像占位使 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
2026-08-13 13:57:38 +08:00

6.3 KiB
Raw Blame History

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 证据链

  1. pretrain_best_inference.pt 中全部 4 个 BatchNorm 的 running_var 均为 3.7e-4(每通道逐位相同)。预训练用全零图像占位,BN 从未 见到真实数据,running_var 经 ~75 次零方差更新指数衰减至此。
  2. 推理(eval 模式)下每级 BN 放大 1/sqrt(3.7e-4 + 1e-5) ≈ 51 倍, 4 级串联放大 ~7e6 倍。实测(torch 复算部署版图):
    • CNN 末级输出:±1.4e6 ~ 2.5e6(真实帧 / 全零帧均是)
    • img_tokens:±2.2e5 ~ 9.6e5
    • act_tokens:±537 ~ 694(历史 0~500 × act_proj 权重,量级正常)
  3. 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 建议路径

  1. x86 微调:随机图像 + 混入真实帧(本机 6 episode × 30 帧可拷贝 过去),动作目标继续用 .mt 动力学;微调后 BN 统计恢复。
  2. 重走链路:export_deploy.py(自带量级检查)→ gen_calibration.py --deploy → dfc_compile.py(连通性检查)→ 拷回 HEF。
  3. _npu_test.py 4 角敏感性复验作为验收门槛。

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