fix(pybot×actpred): 审计修复 7 项——Hailo 部署链语义对齐 + NaN 安全
P1-1: export_deploy.py forward() 从硬编码 3 帧改为 *inputs 动态解析
P1-2: hailo_infer.py 补 predict_delta 支持(残差模式 + --ckpt 自动检测)
P1-3: pybot _load_predictor 自动查找 pred_token.npy/action_stats.npy/
companion checkpoint,恢复 Hailo 推理训练语义
P1-4: pretrain_mt.py random_images 每 batch 重新生成噪声,防 BN 退化
P2-1: predict.py/hailo_infer.py/robot.py 三层 NaN/Inf 防线
P2-3: recorder.py imu_dim 默认 8→0,避免外部直接使用时形状错误
P2-5: _npu_test.py imu_dim 移到函数定义前,消除执行顺序依赖
This commit is contained in:
1 parent
7ec8c9d0e9
commit
aa271e395a
9 files changed
+109
-13
No files matched your search
@@ -134,10 +134,57 @@ def _load_predictor(checkpoint):
|
||||
pybot 不硬依赖 actpred:仅 --loop 时才尝试 import,actpred 不在
|
||||
sys.path 时给出可操作提示。checkpoint 目录名决定后端(.hef → Hailo
|
||||
NPU,否则 → PyTorch),推理类由 actpred 提供。
|
||||
|
||||
HEF 后端会自动查找同目录下的 pred_token.npy 和 companion checkpoint
|
||||
(pretrain_best_inference.pt / best_inference.pt),以恢复 pred_token
|
||||
默认值和 predict_delta 训练配置——否则 Hailo 推理输出与训练语义不一致。
|
||||
"""
|
||||
import os
|
||||
try:
|
||||
if checkpoint.endswith(".hef"):
|
||||
from actpred.hailo_infer import HailoPredictor as _P
|
||||
hef_dir = os.path.dirname(os.path.abspath(checkpoint))
|
||||
|
||||
# pred_token:export_deploy.py --pred-token-out 的产物
|
||||
pred_token = None
|
||||
pt_path = os.path.join(hef_dir, "pred_token.npy")
|
||||
if os.path.exists(pt_path):
|
||||
pred_token = pt_path
|
||||
logging.info("found pred_token: %s", pt_path)
|
||||
|
||||
# stats_path:归一化模型的 action_stats.npy
|
||||
stats_path = None
|
||||
sp = os.path.join(hef_dir, "action_stats.npy")
|
||||
if os.path.exists(sp):
|
||||
stats_path = sp
|
||||
|
||||
# predict_delta:从同目录 checkpoint 读取训练配置
|
||||
predict_delta = False
|
||||
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)
|
||||
cfg = ck.get("config", {})
|
||||
predict_delta = cfg.get("train", {}).get(
|
||||
"predict_delta", False)
|
||||
if predict_delta:
|
||||
logging.info(
|
||||
"predict_delta=True from %s", ckpt_path)
|
||||
except Exception:
|
||||
pass
|
||||
break
|
||||
|
||||
try:
|
||||
return _P(checkpoint, stats_path=stats_path,
|
||||
predict_delta=predict_delta,
|
||||
pred_token=pred_token)
|
||||
except Exception:
|
||||
logging.exception("加载 HEF 推理器失败: %s", checkpoint)
|
||||
return None
|
||||
else:
|
||||
from actpred.predict import ActionPredictorInference as _P
|
||||
except ImportError:
|
||||
|
||||
@@ -34,7 +34,7 @@ class EpisodeRecorder:
|
||||
全程 None(无 IMU 硬件)则不产出 imu.npy,保持旧格式兼容。
|
||||
"""
|
||||
|
||||
def __init__(self, output_dir, action_dim=18, imu_dim=8):
|
||||
def __init__(self, output_dir, action_dim=18, imu_dim=0):
|
||||
self.action_dim = action_dim
|
||||
self.imu_dim = imu_dim
|
||||
self._frame_idx = 0
|
||||
|
||||
@@ -368,6 +368,10 @@ class RobotStruct:
|
||||
SpeakerVar.play 强制要求 delay 键;闭环已剔除 delay 列(避免发声
|
||||
阻塞卡死节拍),此处为其补 delay=0 —— 发声但不阻塞,节拍由 hz 掌控。
|
||||
"""
|
||||
import numpy as np
|
||||
if not np.all(np.isfinite(act)):
|
||||
log.warning("action vector contains NaN/Inf, skipping step")
|
||||
return
|
||||
row_sets = {}
|
||||
for j, dev, rp in exec_cols:
|
||||
row_sets.setdefault(dev, {})[rp] = float(act[j])
|
||||
|
||||
@@ -314,7 +314,7 @@ cam0.frame;
|
||||
|
||||
def test_imu_npy_written_with_dict(tmp_path):
|
||||
"""record 传姿态 dict → flush 产出 imu.npy(T×8),键序 roll..gz"""
|
||||
rec = EpisodeRecorder(str(tmp_path), action_dim=18)
|
||||
rec = EpisodeRecorder(str(tmp_path), action_dim=18, imu_dim=8)
|
||||
imu = {"roll": 1.0, "pitch": 2.0, "ax": 3.0, "ay": 4.0,
|
||||
"az": 5.0, "gx": 6.0, "gy": 7.0, "gz": 8.0}
|
||||
rec.record(None, [0.0], imu_vec=imu)
|
||||
@@ -328,7 +328,7 @@ def test_imu_npy_written_with_dict(tmp_path):
|
||||
|
||||
def test_imu_mixed_none_marks_zero(tmp_path):
|
||||
"""混合数据集:部分帧无姿态 → 该帧记零向量,仍产出 imu.npy"""
|
||||
rec = EpisodeRecorder(str(tmp_path), action_dim=18)
|
||||
rec = EpisodeRecorder(str(tmp_path), action_dim=18, imu_dim=8)
|
||||
rec.record(None, [0.0], imu_vec=None)
|
||||
rec.record(None, [0.0], imu_vec={"roll": 9.0})
|
||||
rec.flush()
|
||||
|
||||
Reference in new issue
Block a user