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

+18 -1
View File
@@ -56,6 +56,20 @@ model.load_state_dict(ck["model_state_dict"])
model.eval()
pred_learned = model.pred_token.data.unsqueeze(1).numpy() # (1,1,1,d)
# 归一化模型:torch 参考在喂入前 z-score;HailoPredictor 传 stats_path
# 由封装内部归一化(hist_real/hist_zero 保持原始值,避免双重归一化)
import os
stats_path = None
if cfg.data.normalize_actions:
stats_path = cfg.data.action_stats_path or os.path.join(
os.path.dirname(os.path.abspath(CKPT)), "action_stats.npy")
if not os.path.isabs(stats_path):
stats_path = os.path.join(os.path.dirname(os.path.abspath(CKPT)),
stats_path)
if not os.path.exists(stats_path):
raise FileNotFoundError(f"统计文件不存在: {stats_path}")
print(f"HailoPredictor stats: {stats_path}")
from torchvision import transforms
tf = transforms.Compose([
transforms.Resize((cfg.data.image_height, cfg.data.image_width)),
@@ -67,6 +81,9 @@ tf = transforms.Compose([
def torch_pred(imgs, hist, use_learned_pred=True):
x = torch.stack([tf(Image.fromarray(im)) for im in imgs]).unsqueeze(0)
h = torch.tensor(hist).unsqueeze(0)
if stats_path is not None:
stats = np.load(stats_path)
h = (h - torch.tensor(stats[0])) / torch.tensor(stats[1])
if use_learned_pred:
with torch.no_grad():
return model(x, h).squeeze(0).numpy()
@@ -78,7 +95,7 @@ def torch_pred(imgs, hist, use_learned_pred=True):
return out
with HailoPredictor(HEF_PATH) as p:
with HailoPredictor(HEF_PATH, stats_path=stats_path) as p:
print(f"\ndims: frame_len={p.frame_len} img={p.image_width}x{p.image_height} "
f"history={p.history_len} action_dim={p.action_dim}")
t0 = time.perf_counter()