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
This commit is contained in:
qing committed 2026-08-13 13:57:38 +08:00
1 parent 8c58770280
commit 52c0ade3c4
8 files changed
+394 -27

No files matched your search

+22
View File
@@ -22,6 +22,7 @@
"""
import argparse
import os
import numpy as np
@@ -77,6 +78,27 @@ def build_deploy_set(args):
history = build_action_history_samples(
args.mt_dir, args.samples, cfg.history_len, cfg.action_dim, rng)
history = history[:, None, :, :] # (N, 1, H_len, D)
if cfg.normalize_actions:
# 归一化模型的 HEF 输入是 z-score 历史(hailo_infer --stats 同语义),
# 校准数据必须走同一变换,否则量化范围与推理分布不匹配
stats_path = cfg.action_stats_path
if not stats_path:
stats_path = os.path.join(
os.path.dirname(os.path.abspath(args.model)),
"action_stats.npy")
elif not os.path.isabs(stats_path):
stats_path = os.path.join(
os.path.dirname(os.path.abspath(args.model)), stats_path)
if not os.path.exists(stats_path):
raise FileNotFoundError(
f"模型训练时启用了动作归一化,但未找到统计文件: {stats_path}")
stats = np.load(stats_path)
if stats.shape != (2, cfg.action_dim):
raise ValueError(
f"action stats 形状应为 (2, {cfg.action_dim}),"
f"实际为 {stats.shape}: {stats_path}")
history = (history - stats[0]) / stats[1]
print(f" action_history: 已 z-score 归一化({stats_path})")
model = ActionPredictor(parse_checkpoint_config(ckpt["config"]).model,
cfg, pretrained=False)