- M1 数据生成器:采样/老师/距离下界认证/时间律/统计/一致性门禁;确定性老师(碰撞次数计时 + resetSession)、pos 任务冻结 J6、--consensus 邻域共识过滤、Dr 类。 - 全流程代码与运行手册:数据打包/可微运动学与对拍/MeanFlow+WTA 模型/闭环仿真/APG/挖错循环/ONNX 导出;webapp 策略运行时 ik-policy.js、?policy=1 接入、tests/policy_bench.cjs;ik-solver 增 pairList/clusterExport/criticalPairs/pairGap/leverTable。
54 lines
2.6 KiB
Python
54 lines
2.6 KiB
Python
"""导出部署包(方案 §8.4):
|
||
python -m ml.export_onnx --ckpt ml/runs/current.pt --out webapp/models/traj_policy.onnx [--fp16]
|
||
输出 traj_policy.onnx(输入 feat[B,83]、z[B,16,6];输出 act[B,16,6] 归一化增量、value[B]、sub[B,13])
|
||
与 traj_policy.meta.json(碰撞模型指纹、特征版本、维度)。导出后用 onnxruntime 与 PyTorch 对拍。"""
|
||
import argparse, json, os
|
||
import numpy as np
|
||
import torch
|
||
|
||
from ml.common.kin_torch import Kin
|
||
from ml.common.features import F_DIM, H, FEAT_VER, STEP
|
||
from ml.model.policy import build
|
||
|
||
|
||
class Wrap(torch.nn.Module):
|
||
def __init__(self, m):
|
||
super().__init__(); self.m = m
|
||
|
||
def forward(self, feat, z):
|
||
return self.m.sample(feat, z)
|
||
|
||
|
||
def main():
|
||
ap = argparse.ArgumentParser()
|
||
ap.add_argument('--ckpt', required=True); ap.add_argument('--out', default='webapp/models/traj_policy.onnx')
|
||
ap.add_argument('--fp16', action='store_true'); ap.add_argument('--opset', type=int, default=17)
|
||
a = ap.parse_args()
|
||
ck = torch.load(a.ckpt, map_location='cpu')
|
||
m = build(ck['cfg']); m.load_state_dict(ck['model']); m.eval()
|
||
kin = Kin()
|
||
if ck.get('fp') and ck['fp'] != kin.fp: raise SystemExit(f"checkpoint 指纹 {ck['fp']} ≠ 当前碰撞模型 {kin.fp}:须重训")
|
||
os.makedirs(os.path.dirname(a.out) or '.', exist_ok=True)
|
||
feat, z = torch.randn(8, F_DIM), torch.randn(8, H, 6)
|
||
torch.onnx.export(Wrap(m), (feat, z), a.out, input_names=['feat', 'z'], output_names=['act', 'value', 'sub'],
|
||
dynamic_axes={'feat': {0: 'B'}, 'z': {0: 'B'}, 'act': {0: 'B'}, 'value': {0: 'B'}, 'sub': {0: 'B'}},
|
||
opset_version=a.opset, dynamo=False)
|
||
if a.fp16:
|
||
from onnxconverter_common import float16
|
||
import onnx
|
||
mo = float16.convert_float_to_float16(onnx.load(a.out), keep_io_types=True)
|
||
onnx.save(mo, a.out)
|
||
import onnxruntime as ort
|
||
s = ort.InferenceSession(a.out, providers=['CPUExecutionProvider'])
|
||
o = s.run(None, {'feat': feat.numpy(), 'z': z.numpy()})
|
||
with torch.no_grad(): t = m.sample(feat, z)
|
||
err = max(float(np.abs(o[i] - t[i].numpy()).max()) for i in range(3))
|
||
meta = {'fp': kin.fp, 'tcp0': kin.tcp0.tolist(), 'head': m.head, 'feat_ver': FEAT_VER, 'f_dim': F_DIM, 'h': H, 'step': STEP, 'fp16': a.fp16,
|
||
'ckpt': os.path.abspath(a.ckpt), 'eval': ck.get('eval'), 'max_abs_err_vs_torch': err}
|
||
json.dump(meta, open(a.out.replace('.onnx', '.meta.json'), 'w'), indent=1)
|
||
print(json.dumps({'onnx': a.out, 'MB': os.path.getsize(a.out) / 1e6, 'max_abs_err_vs_torch': err}))
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|