Files
blackbird/docs/reviews/pybot_actpred_AUDIT_20260814_v2.md
T
qing 85bf68f505 feat(actpred): 固定范围归一化 + 逐列加权损失,附 pybot*actpred 审计修复
新增 action_norm.py 固定物理范围 min-max 归一化,无 stats 文件零状态部署;train.py 引入 _weighted_mse 逐列加权(舵机 1.0/spk 0.2/保留位 0.0)。审计修复: P0-1 pybot 读 normalize_mode, R2-1 NaN 阻断 best, P2-a 后端大小写不敏感, P1-3 utf-8-sig BOM, R1-1 data_dir 覆盖, R2-7 _run.sh 报错, R2-3 空 val 存 best.pt, P2-a2 误报警, R1-2 合并 parse_action_history, P2-b 合并 numpy import。评估修正 oob 物理界+error_growth 按 episode 中位数。推理部署全链支持 fixed_range。
2026-08-14 18:26:03 +08:00

10 KiB
Raw Blame History

pybot & actpred 审计报告(第二版 —— 归一化+逐列加权 落地后)

审查日期:2026-08-14 (Asia/Shanghai) 审查人:AI Assistant(SOLO Coder) 审查范围:pybot/(机器人运行时)与 actpred/(动作预测)两模块 代码基线:归一化+逐列加权方案落地后(12 个文件改动,531 行新增)

本报告对照三份历史审计报告追踪修复状态,并审查本轮新增代码。


一、审计结论摘要

级别 新发现 历史遗留 说明
P0(崩溃/数据损坏) 2 0 见 §二
P1(功能风险) 0 5 见 §三
P2(代码质量) 3 9 见 §四

重点:2 个 P0 必须在提交前修复。


二、P0 新发现

P0-1 pybot HailoPredictor 缺失 normalize_mode 传递

位置:pybot/main.py:180-183

return _P(checkpoint, stats_path=stats_path,
          predict_delta=predict_delta,
          pred_token=pred_token)

问题:HailoPredictor.__init__ 的 normalize_mode 参数默认 "zscore",pybot 调用时未传入此参数。对于 fixed_range 训练的 HEF,推理端应按 action_norm.py 固定物理范围对历史动作做 min-max→[0,1] 归一化,但 pybot 未传参导致 _fixed_range=False,历史动作以原始量纲送入 HEF。

影响:fixed_range 模式训练的 HEF 在 pybot 闭环中推理输出完全错误(舵机会被推到安全范围外)。zscore 模式不受影响(默认值偶然正确)。

修复建议:pybot 从同目录 checkpoint 读取 normalize_mode 并传入 HailoPredictor:

# 在 _load_predictor 中,从 companion checkpoint 读取 normalize_mode
normalize_mode = "zscore"
for name in ("pretrain_best_inference.pt", "best_inference.pt"):
    ckpt_path = os.path.join(hef_dir, name)
    if os.path.exists(ckpt_path):
        try:
            import torch
            ck = torch.load(ckpt_path, map_location="cpu", weights_only=True)
            normalize_mode = ck.get("config", {}).get("data", {}).get(
                "normalize_mode", "zscore")
        except Exception:
            pass
        break

return _P(checkpoint, stats_path=stats_path,
          predict_delta=predict_delta,
          pred_token=pred_token,
          normalize_mode=normalize_mode)

P0-2 DataConfig normalize_actions 默认值变更,与 README 不一致

位置:actpred/config.py:14

normalize_actions: bool = True   # 旧默认值:False

问题:默认值从 False 改为 True,但 README.md 配置表 仍标注 False。CLI 不经 --normalize-actions 参数时,apply_overrides 会使用 dataclass 默认值 True,导致行为变更:

  • 旧脚本(bash _run.sh train --data-dir ./data --epochs 100)之前不归一化,现在开启 zscore 归一化,且会自动计算 action_stats.npy。
  • 对预训练(pretrain_mt.py)同样影响:预训练数据从 .mt 脚本提取,zscore 归一化后分布与旧 checkpoint 不同。

影响:所有无 --normalize-actions 或无 --no-normalize-actions 的训练命令行为改变;旧 checkpoint 做 --init-from 时 warn_init_from_mismatch 会警告但不会阻止。

修复建议:二选一:

  • 方案 A:恢复默认值 False(保持向后兼容,推荐);
  • 方案 B:改 README 为 True,并在 CHANGELOG 中标注 breaking change。

三、P1 历史遗留(未修复)

# 来源 位置 问题 优先级
P1-3 闭环审计 actpred/mt_parser.py:33 read_text(encoding="utf-8") 不剥离 BOM,@Action 为首块时表头列名可能粘附  低(低概率触发)
P2-a 闭环审计 pybot/main.py:143 _load_predictor 仅用 endswith(".hef") 区分后端,大小写敏感,无 ONNX 分支 中
R2-1 actpred 审计 actpred/train.py:270 NaN val_loss 永久阻断 best checkpoint 保存(NaN < x 恒 False) 中
R1-1 actpred 审计 actpred/pretrain_mt.py:185 cfg.data.data_dir = args.mt_dir 覆盖,--save-config 导出语义错误 JSON 低
R2-7 actpred 审计 actpred/_run.sh:14-16 未知子命令静默回退到 predict.py,拼写错误难以发现 低

四、P2 历史遗留(未修复)

# 来源 位置 问题
P2-b 闭环审计 pybot/robot.py 等 3 处 numpy 在闭环路径 3 个函数各自 import
P2-c 闭环审计 pybot/tests/test_loop.py 未覆盖闭环 + recorder 集成、predictor 异常清理
P2-d 闭环审计 actpred/dataset.py 图像 Resize 不保持宽高比
P2-e 闭环审计 actpred/train.py resume 时改 --epochs 会重算 lr scheduler
P2-f 闭环审计 actpred/export_deploy.py ManualAttnBlock 依赖 PyTorch 内部布局约定
R2-3 actpred 审计 actpred/train.py 空验证集时 best.pt 从不创建
R2-4 actpred 审计 actpred/_set.sh CUDA 版 torch 误装风险
R1-2 actpred 审计 predict/onnx_infer/hailo_infer parse_action_history 三处重复
R2-9 actpred 审计 actpred/predict.py --action-dim override 参数无用且是陷阱

五、已完成修复的审计项

# 来源 修复内容
P1-1 闭环审计 闭环节拍 overrun 告警已实现(robot.py:307-320),含比例统计
P1-2 闭环审计 闭环帧源改走 _loop_recent_frames(robot.py:323-338),从守护线程队列取帧
P1-4 闭环审计 DataRecorder output_dir 已有 mkdir(parents=True, exist_ok=True)(recorder.py:46)
R2-2 actpred 审计 ActionDataset 外部 stats 文件已补 shape 校验(dataset.py:92-95)

六、本轮新增代码审查(归一化+逐列加权方案)

6.1 action_norm.py — 通过,有 1 处 P2 建议

总体评价:常量表清晰、文档完善、clamp 防御到位。18 个单测覆盖正常值/边界/越界/多维度。

P2-a1(建议):default_column_weights 的 action_dim 默认为 18,但与 action_bounds 的行为不同——action_bounds 对 action_dim > 18 自动补零,default_column_weights 对此类情况返回全零权重(因为 min(10, action_dim) 和 min(12, action_dim) 仅覆盖前 12 列,其余列保持 np.zeros 初始值)。建议对齐行为:action_dim > 18 时对超出列也赋默认权重(如 0.2)。

6.2 config.py — 通过,有 1 处 P2

总体评价:normalize_mode 和 column_weights 的 CLI 解析、值域校验、checkpoint 兼容性警告均实现完整。

P2-a2(建议):warn_init_from_mismatch 中 init_mode = init_data.get("normalize_mode", "zscore") 对旧 checkpoint 正确回退。但旧 checkpoint 的 normalize_actions=False 时,normalize_mode 无意义,此时警告 init_mode != current.data.normalize_mode 是误报。建议仅在 init_norm and current.data.normalize_actions 时比较 normalize_mode。

6.3 dataset.py — 通过

固定范围归一化集成正确:_fixed_range 标志位在 __init__ 中设置,_norm 方法按标志位分发。stats shape 校验(R2-2 修复)已补。

6.4 train.py — 通过,有 1 处 P2 行为变更

总体评价:_weighted_mse 实现正确,weights.mean() 归一化保证加权 Loss 与无权重 Loss 量级可比。column_weights 为 None 时自动取 default_column_weights。

P2-a3(行为变更):column_weights 现在始终生效(None 时取默认值),即使 normalize_actions=False。这意味着不归一化训练的模型也会按列加权损失——模型对舵机列(权重 1.0)的优化力度高于 spk 列(0.2)。这并非 bug,但行为变更需在 README 注明。

6.5 evaluate.py — 通过

物理界 OOB 与 action_norm.py 常量表共用(_action_bounds 调用 action_bounds)。error_growth 按 episode 分别计算取中位数,消除长短 episode 混杂偏差。实现正确。

6.6 推理/部署链 — 通过,P0-1 除外

文件 审查结论
predict.py _fixed_range 从 checkpoint config 读取,normalize/denormalize 正确
onnx_infer.py normalize_mode 构造参数,predict 中 normalize/denormalize 正确
hailo_infer.py normalize_mode 构造参数,predict 中 normalize/denormalize 正确
gen_calibration.py fixed_range 模式校准数据走 normalize_action 变换,与推理端一致
export_deploy.py check_token_ranges 按 normalize_mode 在正确空间采样,stats_path 逻辑正确

唯独 pybot 的 _load_predictor 未传 normalize_mode 给 HailoPredictor(P0-1)。

6.7 test_actpred.py — 通过

新增 TestFixedRangeNorm 类 18 个测试用例,覆盖:

  • 已知值归一化(舵机中位/边界、spk 中位)
  • 越界 clamp(>180→1.0、<0→0.0)
  • denormalize 往返一致性
  • 默认列权重结构
  • 自定义列权重
  • 多维度批次归一化
  • 全 0 输出验证

全量 148 passed / 11 skipped。


七、修复优先级

优先级 项 理由
P0 P0-1 HailoPredictor normalize_mode 缺失 fixed_range HEF 在 pybot 闭环中推理完全错误
P0 P0-2 normalize_actions 默认值变更 所有无此参数的命令行为改变,README 不一致
P1 R2-1 NaN val_loss 训练健壮性,一行 isfinite 修复
P1 P2-a 后端检测 路径大小写敏感,ONNX 无分支
P2 P2-a2 误报警告 旧 checkpoint 的不必要警告
P2 P2-a1 action_dim>18 未来扩展性
P2 P2-a3 行为变更文档 README 注明 column_weights 始终生效

本报告为归一化+逐列加权方案落地后的专项审计。建议先修复 2 个 P0 再提交。