Files
blackbird/actpred/export_onnx.py
T
qing 35ee614ca9 fix(pybot×actpred): v3 审计 P1 修复 3 项——零依赖承诺 + ONNX 元数据/残差全链路
- robot.py numpy 恢复函数级 lazy import,纯 .mt 脚本执行不再需要 numpy(v3-P1-1)
- export_onnx 写入训练配置元数据;onnx_infer 自动识别 normalize_mode/predict_delta 并支持残差加回(v3-P1-2)
- pybot --loop 补 .onnx 后端分支,zscore 缺 action_stats.npy 时告警(P2-a 闭环)
- 回归测试:actpred TestOnnxMetadata +7、pybot 零依赖导入 +1(88/148 全绿)
- 附 v3 审计报告:13 项 v2 修复回归核对 + 7 项新发现(P0-2 遗留 + P2/P3 六项)
2026-08-14 22:13:06 +08:00

85 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import argparse
import torch
from torch.export import Dim
from config import parse_checkpoint_config
from model import ActionPredictor
def main():
parser = argparse.ArgumentParser(description="Export actpred model to ONNX")
parser.add_argument("--model", required=True, help="checkpoint path (best_inference.pt)")
parser.add_argument("--output", default="model.onnx", help="output ONNX file path")
args = parser.parse_args()
checkpoint = torch.load(args.model, map_location="cpu", weights_only=True)
cfg = parse_checkpoint_config(checkpoint["config"])
data_cfg = cfg.data
model_cfg = cfg.model
model = ActionPredictor(model_cfg, data_cfg, pretrained=False)
model.load_state_dict(checkpoint["model_state_dict"])
model.eval()
dummy_images = torch.randn(
1, data_cfg.frame_len, 3, data_cfg.image_height, data_cfg.image_width)
dummy_history = torch.randn(1, data_cfg.history_len, data_cfg.action_dim)
# R2 方案一:imu_dim>0 时追加归一化姿态输入 (1, imu_dim)
imu_dim = data_cfg.imu_dim
extra_inputs = (torch.randn(1, imu_dim),) if imu_dim > 0 else ()
extra_names = ["imu"] if imu_dim > 0 else []
batch = Dim("batch")
dynamic = {"images": {0: batch}, "action_history": {0: batch}}
if imu_dim > 0:
dynamic["imu"] = {0: batch}
torch.onnx.export(
model, (dummy_images, dummy_history, *extra_inputs),
args.output,
input_names=["images", "action_history"] + extra_names,
output_names=["predicted_action"],
dynamic_shapes=dynamic,
opset_version=18,
)
print(f"ONNX model exported to: {args.output}"
f"{'(含 imu 输入)' if imu_dim > 0 else ''}")
# 写入训练配置元数据:onnx_infer 据此自动识别归一化方式与残差模式,
# 避免推理端配置与训练不一致导致静默错误(normalize_actions=False 时
# 记为 "none",推理端不做任何归一化)
import onnx
m = onnx.load(args.output)
meta = {
"normalize_mode": (data_cfg.normalize_mode
if data_cfg.normalize_actions else "none"),
"predict_delta": ("1" if getattr(cfg.train, "predict_delta", False)
else "0"),
"imu_dim": str(imu_dim),
"action_dim": str(data_cfg.action_dim),
}
for k, v in meta.items():
p = m.metadata_props.add()
p.key, p.value = k, v
onnx.save(m, args.output)
print(f"ONNX 元数据已写入: {meta}")
# 可选:验证导出的 ONNX 模型一致性
try:
import onnxruntime as ort
import numpy as np
sess = ort.InferenceSession(args.output)
feeds = {"images": dummy_images.numpy(),
"action_history": dummy_history.numpy()}
for name, t in zip(extra_names, extra_inputs):
feeds[name] = t.numpy()
ort_out = sess.run(None, feeds)[0]
with torch.no_grad():
torch_out = model(dummy_images, dummy_history, *extra_inputs).numpy()
max_diff = np.abs(ort_out - torch_out).max()
print(f"ONNX 一致性验证通过(最大差异: {max_diff:.6e})")
except ImportError:
print("提示: 安装 onnxruntime 可验证导出模型一致性 (pip install onnxruntime)")
if __name__ == "__main__":
main()