diag: H 段(老师 oracle 走 rollout / 前缀消融 / 子目标越过顶点规则 / 分轴与分类别指标);训练:EMA、选模不落在 p_neq 预热、闭环分数选模、24000 步余弦、p_neq 0.5 + p_edge 0.1、子目标头切断抄输入、前缀/dq 增广;仿真:last 记真正提交的候选、碰撞与到达按每题时限、回放门禁读 pass+指纹并走 rollout;数据:动作标签按 1.25·STEP 归一化(act_scale 随 meta/ckpt/ONNX 传递,旧模型 1.0)、收回亏空结转、DAgger 带 qPrev 与起步速度、打包签名变更自动重打;ik-policy.js 同步(actScale、子目标规则、DLS 时清 lastPlan);本机执行清单与 README 更新

This commit is contained in:
Hark
2026-10-19 10:43:05 +00:00
parent 86323f1691
commit 406f180cab
31 changed files with 923 additions and 317 deletions
+26 -20
View File
@@ -6,7 +6,10 @@
2026-10-18:展开长度不再定长 60 次推理(3.6 s,会截掉老师时长 > 3.6 s 的绕远题),改为覆盖本批 exec_s p90×1.2,上限 plans_max;
"到达"仍是每 seg_plans 段末的距离项(无到达即停),属有意设计:段末项给出持续的接近梯度。
margin_train 3 mm:与训练数据过滤线一致(认证最小间隙 < 3 mm 的老师轨迹不进训练集),碰撞项不与 BC 标签对抗;
运行时投影护栏独立保持 5 mm。端点与中点两个铰链取均值,与只查端点同量纲,w_col 无需重标。"""
运行时投影护栏独立保持 5 mm。端点与中点两个铰链取均值,与只查端点同量纲,w_col 无需重标。
2026-10-19:起点取 EMA 权重(ckpt.load_policy);动作反归一化乘 act_scale(与数据同一尺度,不一致报错),产物存 act_scale;
子目标推进改用 closed_loop.sub_advance(默认「越过顶点」,与标签同口径);子目标头 BC 项用 sub 特征置零的输入(同 train.py);
val 只取主数据目录(第一个 --samples)。"""
import argparse, json, math, os, random, sys, time
import torch
@@ -15,7 +18,7 @@ from ml.common.features import features, STEP, H, wrap180
from ml.data import Samples, load_tasks
from ml.model.policy import build, wta_loss
from ml.model.meanflow import meanflow_loss
from ml.sim.closed_loop import evaluate, tasks_to_tensors, goal_err
from ml.sim.closed_loop import evaluate, tasks_to_tensors, goal_err, sub_advance
from ml.common.ckpt import Saver, atomic_save, rng_state, set_rng_state
from ml.common import vram
@@ -41,18 +44,23 @@ def main():
dev = a.device; torch.manual_seed(a.seed); random.seed(a.seed)
vram.limit()
os.makedirs(a.out, exist_ok=True)
ck = torch.load(a.init, map_location=dev)
mcfg = ck['cfg']
model = build(mcfg).to(dev); model.load_state_dict(ck['model'])
from ml.common.ckpt import load_policy
model, ck = load_policy(a.init, dev); mcfg = ck['cfg']
kin = Kin(device=dev)
data = Samples(a.samples, device=dev, split='train')
data = Samples(a.samples, device=dev, split='train', val_dirs=a.samples[:1])
act_scale = float(ck.get('act_scale', 1.0))
if abs(act_scale - data.act_scale) > 1e-9:
raise SystemExit(f'起点 act_scale={act_scale} 与样本 act_scale={data.act_scale} 不一致(BC 项标签尺度不同),换同一版数据训练的模型')
model.act_scale = act_scale
all_tasks = load_tasks(a.samples)
train_tasks = [t for t in all_tasks if t['split'] == 'train'] + load_pool(a.pool)
val_tasks = [t for t in all_tasks if t['split'] == 'val'][:C['eval_tasks']]
main_tid = {t['tid'] for t in load_tasks(a.samples[:1]) if t['split'] == 'val'}
train_tasks = [t for t in all_tasks if t['tid'] not in main_tid] + load_pool(a.pool)
val_tasks = [t for t in load_tasks(a.samples[:1]) if t['split'] == 'val'][:C['eval_tasks']]
w = [C['cat_weights'].get(t.get('cat', 'A'), 1.0) * (2.0 if t.get('hard') else 1.0) for t in train_tasks]
opt = torch.optim.AdamW(model.parameters(), lr=C['lr'], betas=(0.9, 0.95), weight_decay=0.0)
amp = dev.startswith('cuda') and C.get('amp') == 'bf16'
step = torch.tensor(STEP, device=dev)
astep = step * act_scale # 动作 → 度
it0 = 0
if a.resume and os.path.exists(a.resume):
rk = torch.load(a.resume, map_location=dev); model.load_state_dict(rk['model'])
@@ -68,11 +76,11 @@ def main():
base, _ = evaluate(model.eval(), kin, val_tasks, dev)
print('EVAL0', json.dumps(base), flush=True); log.write(json.dumps({'eval': base, 'step': 0}) + '\n')
best = base['success']
atomic_save({'model': model.state_dict(), 'cfg': mcfg, 'step': 0, 'fp': kin.fp, 'eval': base}, os.path.join(a.out, 'best.pt'))
atomic_save({'model': model.state_dict(), 'cfg': mcfg, 'step': 0, 'fp': kin.fp, 'eval': base, 'act_scale': act_scale}, os.path.join(a.out, 'best.pt'))
M = C['margin_train_mm']; t0 = time.time()
saver = Saver(C.get('ckpt_min', 10)) # 2026-10-18:每 10 分钟 + 收到停止请求时存 last.pt(不再只在每 500 次验证时存)
def save_last(n, ev=None):
atomic_save({'model': model.state_dict(), 'opt': opt.state_dict(), 'cfg': mcfg, 'step': n, 'fp': kin.fp, 'eval': ev, 'rng': rng_state()}, os.path.join(a.out, 'last.pt')); saver.mark()
atomic_save({'model': model.state_dict(), 'opt': opt.state_dict(), 'cfg': mcfg, 'step': n, 'fp': kin.fp, 'eval': ev, 'rng': rng_state(), 'act_scale': act_scale}, os.path.join(a.out, 'last.pt')); saver.mark()
for it in range(it0, C['steps']):
model.train()
batch = random.choices(train_tasks, weights=w, k=C['batch'])
@@ -81,7 +89,7 @@ def main():
z = torch.randn(B, H, 6, device=dev)
q, qp = q0.clone(), q0.clone()
queue = torch.zeros(B, 0, 6, device=dev)
subs = torch.zeros(B, 2, 6, device=dev); nsub = torch.zeros(B, dtype=torch.long, device=dev); sub_t = torch.zeros(B, device=dev)
subs = torch.zeros(B, 2, 6, device=dev); nsub = torch.zeros(B, dtype=torch.long, device=dev); sub_t = torch.zeros(B, device=dev); sub_prev = q0.clone()
opt.zero_grad(set_to_none=True)
stats = {'col': 0.0, 'tgt': 0.0, 'smooth': 0.0}
# 展开长度随本批任务时长:覆盖本批 exec_s 的 p90 × 1.2(无老师参考题按 6 s),夹在 [plans, plans_max]
@@ -95,11 +103,7 @@ def main():
pre = torch.zeros(B, 3, 6, device=dev); pre[:, :r] = queue[:, :r]
qe = q + queue.sum(1)
with torch.no_grad():
if (nsub > 0).any():
cur = subs[:, 0]; dd = torch.where(kin.cont, wrap180(cur - qe), cur - qe).abs().max(-1).values
adv = (nsub > 0) & ((dd < 5) | (tnow - sub_t > 1.5))
subs = torch.where(adv[:, None, None], torch.cat([subs[:, 1:], subs[:, 1:]], 1), subs)
nsub = torch.where(adv, nsub - 1, nsub); sub_t = torch.where(adv, torch.full_like(sub_t, tnow), sub_t)
subs, nsub, sub_prev, sub_t = sub_advance(kin, subs, nsub, sub_prev, sub_t, qe.detach(), tnow)
feat = features(kin, q, qp, T, R, pre, torch.full((B,), r, device=dev), subs[:, 0], (nsub > 0).float())
with torch.autocast('cuda', dtype=torch.bfloat16, enabled=amp):
act, val, sub = model.sample_k(feat, 1, z)
@@ -108,8 +112,8 @@ def main():
with torch.no_grad():
need = sub[:, 12].float() > 0.5
subs = q.detach()[:, None] + sub[:, :12].float().view(B, 2, 6) * 180
nsub = torch.where(need, torch.full_like(nsub, 2), torch.zeros_like(nsub))
inc = act.clamp(-1, 1) * step # [B,16,6]
nsub = torch.where(need, torch.full_like(nsub, 2), torch.zeros_like(nsub)); sub_prev = q.detach().clone()
inc = act.clamp(-1, 1) * astep # [B,16,6]
commit = inc[:, r:r + 3]
if r == 0: # 冷启动:先提交一块,下一轮才开始执行
queue = commit; continue
@@ -141,7 +145,9 @@ def main():
with torch.autocast('cuda', dtype=torch.bfloat16, enabled=amp):
mem = model.encode(X)
l_bc = wta_loss(model, mem, Y)[0] if model.head == 'wta' else meanflow_loss(model, mem, Y)[0]
v, s = model.heads(mem)
v, _ = model.heads(mem)
Xs = X.clone(); Xs[:, 68:75] = 0
_, s = model.heads(model.encode(Xs))
l_bc = l_bc + 0.1 * (v.float() - V).abs().mean() + 0.1 * (s.float() - SG).abs().mean()
(C['w_bc'] * l_bc).backward()
gn = torch.nn.utils.clip_grad_norm_(model.parameters(), C['grad_clip'])
@@ -160,7 +166,7 @@ def main():
res, _ = evaluate(model.eval(), kin, val_tasks, dev)
res['it'] = it + 1; res['t'] = time.time()
print('EVAL', json.dumps(res), flush=True); log.write(json.dumps({'eval': res}) + '\n'); log.flush()
sd = {'model': model.state_dict(), 'cfg': mcfg, 'step': it + 1, 'fp': kin.fp, 'eval': res}
sd = {'model': model.state_dict(), 'cfg': mcfg, 'step': it + 1, 'fp': kin.fp, 'eval': res, 'act_scale': act_scale}
save_last(it + 1, res) # last 带优化器状态供续跑;best 不带(导出/门禁用)
if res['success'] > best + 1e-4:
best = res['success']; atomic_save(sd, os.path.join(a.out, 'best.pt'))