exp: 轮次29——全拒后减半反馈到达55.1%,补齐无碰撞指标与首撞回放
512题无碰撞到达54.7%、碰撞1.8%;9次首撞均在J5限位且双精度复现。保留小样本消融,撤回未经实际调度验证的50Hz达标结论,附六项回归测试及完整配置结果。
This commit is contained in:
@@ -25,9 +25,58 @@ def pair_names():
|
||||
return {a * 7 + b: f'{nm(a)}-{nm(b)}' for a in range(7) for b in range(a + 1, 7)}
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def replay_first_hits(path, out=None):
|
||||
saved = torch.load(path, map_location='cpu', weights_only=True)
|
||||
k32, k64 = Kin(), Kin(dtype=torch.float64)
|
||||
rows, offset = [], 0
|
||||
for _, per, count in saved['batches']:
|
||||
for b in (per['col'] > 0).nonzero().flatten().tolist():
|
||||
it = int(round(float(per['first_hit'][b]) / DT))
|
||||
prev, q = per['traj'][b, it - 1:it + 1]
|
||||
mid = prev + 0.5 * (q - prev)
|
||||
points = torch.stack([per['traj'][b, 0], prev, mid, q])
|
||||
gm32 = k32.gaps(points, 0.5)['gm']
|
||||
gm64 = k64.gaps(points.double(), 0.5)['gm']
|
||||
gap32, gp32 = gm32.min(-1)
|
||||
gap64, gp64 = gm64.min(-1)
|
||||
nseg = max(1, math.ceil(float((q - prev).abs().max()) / 0.01))
|
||||
dense = prev.double() + torch.linspace(0, 1, nseg + 1, dtype=torch.float64)[:, None] * (q.double() - prev.double())
|
||||
dense_gap = torch.cat([k64.gaps(chunk, 0.5)['gm'].min(-1).values for chunk in dense.split(32)])
|
||||
boundary = (~k32.cont) & ((q - k32.lo).abs().lt(1e-5) | (q - k32.hi).abs().lt(1e-5))
|
||||
row = {
|
||||
'i': offset + b, 'task': saved['tasks'][offset + b],
|
||||
'first_hit_s': float(per['first_hit'][b]), 'dls_it': int(per['dls_it'][b]),
|
||||
'point_order': ['q0', 'previous', 'midpoint', 'endpoint'],
|
||||
'gap32_mm': gap32.tolist(), 'gap64_mm': gap64.tolist(),
|
||||
'pair32': gp32.tolist(), 'pair64': gp64.tolist(),
|
||||
'boundary_joints': (boundary.nonzero().flatten() + 1).tolist(),
|
||||
'previous': prev.tolist(), 'endpoint': q.tolist(),
|
||||
'dense_nsegments': nseg, 'dense_gap64_min_mm': float(dense_gap.min()),
|
||||
}
|
||||
rows.append(row)
|
||||
print('task %d first_hit %.2fs gap32 %s gap64 %s boundary %s' %
|
||||
(row['i'], row['first_hit_s'], row['gap32_mm'], row['gap64_mm'], row['boundary_joints']), flush=True)
|
||||
offset += count
|
||||
result = {
|
||||
'trace': path, 'n': offset, 'reported_collision_tasks': len(rows),
|
||||
'replayed_first_hits32': sum(min(r['gap32_mm'][2:]) <= 0 for r in rows),
|
||||
'replayed_first_hits64': sum(min(r['gap64_mm'][2:]) <= 0 for r in rows),
|
||||
'scope': 'Replay reported first hits only; same geometry kernel at float32/float64, not a full safety audit.',
|
||||
'rows': rows,
|
||||
}
|
||||
if out:
|
||||
with open(out, 'w', encoding='utf-8') as f:
|
||||
json.dump(result, f, indent=2, allow_nan=False)
|
||||
print('replayed first hits: %d/%d float32, %d/%d float64' %
|
||||
(result['replayed_first_hits32'], len(rows), result['replayed_first_hits64'], len(rows)), flush=True)
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('--ckpt', required=True)
|
||||
ap.add_argument('--ckpt')
|
||||
ap.add_argument('--trace')
|
||||
ap.add_argument('--samples', default='ml/data/samples')
|
||||
ap.add_argument('--split', default='val')
|
||||
ap.add_argument('--n', type=int, default=512)
|
||||
@@ -38,6 +87,11 @@ def main():
|
||||
ap.add_argument('--device', default='cuda' if torch.cuda.is_available() else 'cpu')
|
||||
ap.add_argument('--out', default=None)
|
||||
a = ap.parse_args()
|
||||
if a.trace:
|
||||
replay_first_hits(a.trace, a.out)
|
||||
return
|
||||
if not a.ckpt:
|
||||
ap.error('--ckpt or --trace is required')
|
||||
|
||||
ck = torch.load(a.ckpt, map_location=a.device)
|
||||
model = build(ck['cfg']).to(a.device)
|
||||
|
||||
+77
-25
@@ -1,32 +1,57 @@
|
||||
"""小规模闭环评估(部署口径):
|
||||
python -m ml.eval.eval_cl --n 256 --commit 3 --fb 1.8 --partial [--ki 0.1 --select consist]
|
||||
输出:到达 / 碰撞 / ratio2s / 刹停 / 精修接管 / 墙钟。直接调 rollout,参数与部署一致。"""
|
||||
import argparse, functools, json, time
|
||||
"""小规模同步闭环评估;仿真墙钟不等于控制延迟。"""
|
||||
import argparse, functools, hashlib, json, math, time
|
||||
from pathlib import Path
|
||||
import torch
|
||||
|
||||
from ml.common.ckpt import load_policy
|
||||
from ml.common.kin_torch import Kin
|
||||
from ml.data import load_tasks
|
||||
from ml.sim.closed_loop import rollout
|
||||
from ml.sim.closed_loop import rollout, tasks_to_tensors
|
||||
import ml.sim.closed_loop as clmod
|
||||
|
||||
|
||||
def run(model, kin, tasks, device, bs=256, **kw):
|
||||
agg, per = {}, []
|
||||
per = []
|
||||
for i in range(0, len(tasks), bs):
|
||||
r, p = rollout(model, kin, tasks[i:i + bs], device, **kw)
|
||||
t0 = time.perf_counter()
|
||||
print('batch %d-%d/%d start' % (i + 1, min(i + bs, len(tasks)), len(tasks)), flush=True)
|
||||
def progress(step, total):
|
||||
print('batch %d step %d/%d wall %.1fs' % (i // bs + 1, step, total, time.perf_counter() - t0), flush=True)
|
||||
r, p = rollout(model, kin, tasks[i:i + bs], device, progress=progress, **kw)
|
||||
per.append((r, p, len(tasks[i:i + bs])))
|
||||
n = sum(w for _, _, w in per)
|
||||
for r, _, w in per:
|
||||
for k, v in r.items():
|
||||
if k in ('n', 'time_budget') or v is None: continue
|
||||
agg[k] = agg.get(k, 0.0) + v * w
|
||||
out = {k: v / n for k, v in agg.items()}; out['n'] = n
|
||||
out['goal_ratio_2s'] = torch.cat([p['goal_ratio_2s'] for _, p, _ in per]).median().item()
|
||||
out['err_fail_med'] = torch.cat([p['err'] for _, p, _ in per])[~torch.cat([p['ok'] for _, p, _ in per])].median().item()
|
||||
out['dls_share'] = torch.cat([p['dls_it'] for _, p, _ in per]).ge(0).float().mean().item()
|
||||
out['time_budget'] = per[0][0]['time_budget']
|
||||
return out
|
||||
print('batch %d done wall %.1fs' % (i // bs + 1, time.perf_counter() - t0), flush=True)
|
||||
flat = {k: torch.cat([p[k] for _, p, _ in per]) for k in per[0][1] if k != 'traj'}
|
||||
_, _, _, ex, tr = tasks_to_tensors(tasks, 'cpu')
|
||||
ok, col = flat['ok'], flat['col'] > 0
|
||||
has_ref = torch.isfinite(ex)
|
||||
okr = ok & has_ref & torch.isfinite(tr)
|
||||
median = lambda x: x.median().item() if x.numel() else None
|
||||
out = {
|
||||
'n': len(tasks), 'n_ref': int(has_ref.sum()),
|
||||
'success': ok.float().mean().item(),
|
||||
'collision_free_success': (ok & ~col).float().mean().item(),
|
||||
'time_ok_1p3': (ok & has_ref & (flat['t_done'] <= 1.3 * ex)).sum().item() / int(has_ref.sum()) if has_ref.any() else None,
|
||||
'collision_rate': col.float().mean().item(),
|
||||
'collision_steps_mean': flat['col'].mean().item(),
|
||||
'travel_ratio_median': median(flat['travel'][okr] / tr[okr].clamp(min=1)),
|
||||
'time_mean_s': flat['t_done'][ok].mean().item() if ok.any() else None,
|
||||
'brakes_mean': flat['brakes'].mean().item(),
|
||||
'start_fail': flat['start_fail'].float().mean().item(),
|
||||
'goal_ratio_2s': median(flat['goal_ratio_2s']),
|
||||
'err_fail_med': median(flat['err'][~ok]),
|
||||
'err_success_med': median(flat['err'][ok]),
|
||||
'dls_share': flat['dls_it'].ge(0).float().mean().item(),
|
||||
'time_budget': ('fixed %.3f s' % kw['max_sec']) if kw.get('max_sec') is not None else
|
||||
(per[0][0]['time_budget'] if len({r['time_budget'] for r, _, _ in per}) == 1 else 'mixed'),
|
||||
}
|
||||
return out, per
|
||||
|
||||
|
||||
def json_finite(value):
|
||||
if isinstance(value, dict): return {k: json_finite(v) for k, v in value.items()}
|
||||
if isinstance(value, list): return [json_finite(v) for v in value]
|
||||
if isinstance(value, float) and not math.isfinite(value): return None
|
||||
return value
|
||||
|
||||
|
||||
def main():
|
||||
@@ -48,6 +73,14 @@ def main():
|
||||
ap.add_argument('--fb_every', type=int, default=1)
|
||||
ap.add_argument('--safety_try', type=int, default=0)
|
||||
ap.add_argument('--sel_w', type=float, default=0.1)
|
||||
ap.add_argument('--dls_safety', type=int, default=0)
|
||||
ap.add_argument('--fb_clear', type=float, default=0.0)
|
||||
ap.add_argument('--fb_cmin', type=float, default=0.3)
|
||||
ap.add_argument('--sub_refresh', type=int, default=0)
|
||||
ap.add_argument('--fb_block_gain', type=float, default=1.0)
|
||||
ap.add_argument('--dump', default=None)
|
||||
ap.add_argument('--out', default=None)
|
||||
ap.add_argument('--trace', default=None)
|
||||
ap.add_argument('--select', default='consist')
|
||||
ap.add_argument('--replan', default='r3')
|
||||
ap.add_argument('--safety', type=int, default=1, help='1=安全层+部分前缀;0=关')
|
||||
@@ -57,6 +90,7 @@ def main():
|
||||
ap.add_argument('--dls', default='analytic', choices=['analytic', 'fd'])
|
||||
ap.add_argument('--device', default='cuda' if torch.cuda.is_available() else 'cpu')
|
||||
a = ap.parse_args()
|
||||
print('config ' + json.dumps(vars(a), ensure_ascii=False), flush=True)
|
||||
if a.dls == 'fd':
|
||||
clmod.dls_steps = functools.partial(clmod.dls_steps, analytic=False)
|
||||
model, ck = load_policy(a.ckpt, a.device)
|
||||
@@ -65,17 +99,35 @@ def main():
|
||||
tasks = ([json.loads(l) for l in open(a.tasks, encoding='utf-8') if l.strip()] if a.tasks
|
||||
else [t for t in load_tasks([a.samples]) if t['split'] == a.split])
|
||||
tasks = tasks[:a.n]
|
||||
if not tasks: ap.error('no tasks selected')
|
||||
meta = {
|
||||
'config': vars(a), 'batch_size': 256, 'batch_seed_rule': 'same seed per batch',
|
||||
'checkpoint_sha256': hashlib.sha256(Path(a.ckpt).read_bytes()).hexdigest(),
|
||||
'tasks_sha256': hashlib.sha256(json.dumps(tasks, sort_keys=True).encode()).hexdigest(),
|
||||
'kin_fp': kin.fp, 'act_scale': float(model.act_scale),
|
||||
}
|
||||
t0 = time.perf_counter()
|
||||
r = run(model, kin, tasks, a.device, K=a.K, safety=bool(a.safety), seed=a.seed, max_sec=a.max_sec,
|
||||
r, pers = run(model, kin, tasks, a.device, K=a.K, safety=bool(a.safety), seed=a.seed, max_sec=a.max_sec,
|
||||
safety_partial=(a.commit if (a.safety and a.partial) else 0), commit_n=a.commit,
|
||||
select=a.select, replan=a.replan, fb_gain=a.fb, fb_scale=a.fb_scale, fb_near=a.fb_near, fb_fallback=a.fb_fallback, fb_ki=a.ki, fb_decay=a.kdecay, fb_imax=a.kimax,
|
||||
fb_every=a.fb_every, safety_try=a.safety_try, sel_w=a.sel_w)
|
||||
fb_every=a.fb_every, safety_try=a.safety_try, sel_w=a.sel_w, dls_safety=a.dls_safety, fb_clear=a.fb_clear, fb_cmin=a.fb_cmin, sub_refresh=a.sub_refresh, fb_block_gain=a.fb_block_gain,
|
||||
return_traj=bool(a.trace))
|
||||
wall = time.perf_counter() - t0
|
||||
print('ckpt=%s n=%d K=%d commit=%d fb=%.2f ki=%.2f select=%s replan=%s safety=%d' %
|
||||
(a.ckpt, r['n'], a.K, a.commit, a.fb, a.ki, a.select, a.replan, a.safety))
|
||||
print('success %.3f time_ok_1p3 %.3f coll %.3f ratio2s %.3f brakes %.1f dls_share %.3f err_fail %.2f wall %.1fs (%.0f ms/题)'
|
||||
% (r['success'], r['time_ok_1p3'] or 0, r['collision_rate'], r['goal_ratio_2s'], r['brakes_mean'],
|
||||
r['dls_share'], r['err_fail_med'], wall, wall / max(1, len(tasks)) * 1000))
|
||||
result = {**meta, 'metrics': r, 'simulation_wall_s': wall}
|
||||
print(json.dumps(json_finite(result), ensure_ascii=False, allow_nan=False), flush=True)
|
||||
if a.out:
|
||||
Path(a.out).write_text(json.dumps(json_finite(result), indent=2, ensure_ascii=False, allow_nan=False) + '\n', encoding='utf-8')
|
||||
if a.dump:
|
||||
rows = []
|
||||
keys = ['done', 'ok', 't_done', 'limit', 'col', 'brakes', 'err', 'first_hit', 'dls_it', 'goal_ratio_2s']
|
||||
flat = {k: torch.cat([p[k] for _, p, _ in pers]).tolist() for k in keys}
|
||||
for i, t in enumerate(tasks):
|
||||
rows.append({**t, 'i': i, **{k: flat[k][i] for k in keys}})
|
||||
Path(a.dump).write_text(json.dumps(json_finite(rows), allow_nan=False), encoding='utf-8')
|
||||
print('dump', a.dump, flush=True)
|
||||
if a.trace:
|
||||
torch.save({**meta, 'tasks': tasks, 'batches': pers}, a.trace)
|
||||
print('trace', a.trace, flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
+46
-12
@@ -66,11 +66,10 @@ def dls_steps(kin, q, T, R, n=3, scale=0.3, lam=0.5, analytic=True):
|
||||
xf = kin.cum_xf(x)
|
||||
e0 = err(x, T, Rt, pose, xf)
|
||||
if analytic:
|
||||
# J = −∂err/∂q:位置 err = T−p → +Jv;姿态 err = 100·rotvec(Rt·Rqᵀ) → +100·Jw
|
||||
# 几何角速度 Jw 仅在姿态误差接近零时等于该 skew 残差的负导数。
|
||||
Jv, Jw = kin.jac(x, xf)
|
||||
J = torch.cat([Jv, 100 * Jw * pose[:, None, None]], 1) # [B,6(err),6(q)]
|
||||
J = torch.cat([Jv, 100 * Jw * pose[:, None, None]], 1)
|
||||
else:
|
||||
# 2026-10-20:6 次串行有限差分合并为一次批量 FK(数值等价,单次规划 ~25ms→~7ms)
|
||||
dx = torch.zeros(B, 6, 6, device=q.device); dx[:, ar, ar] = 1e-3
|
||||
xp = (x[:, None, :] + dx).reshape(B * 6, 6)
|
||||
ep = err(xp, T.repeat_interleave(6, 0), Rt.repeat_interleave(6, 0), pose.repeat_interleave(6, 0))
|
||||
@@ -153,7 +152,8 @@ class TeacherOracle:
|
||||
def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None, return_traj=False, use_sub=True, sub_override=None,
|
||||
qp0=None, steps=1, z_scale=1.0, select='consist', oracle=None, replan='r3', sub_rule='cross',
|
||||
fb_gain=0.0, fb_scale=0.0, fb_near=0.2, fb_fallback=0.0, safety_partial=0, commit_n=3, collect=None,
|
||||
fb_exec=0.0, exec_safety=0, fb_ki=0.0, fb_decay=0.9, fb_imax=0.5, fb_every=1, safety_try=0, sel_w=0.1):
|
||||
fb_exec=0.0, exec_safety=0, fb_ki=0.0, fb_decay=0.9, fb_imax=0.5, fb_every=1, safety_try=0, sel_w=0.1, dls_safety=0, fb_clear=0.0, fb_cmin=0.3, sub_refresh=0, fb_block_gain=1.0,
|
||||
progress=None):
|
||||
"""use_sub=False:不用子目标(sf=0);sub_override=(subs [B,M,6] 绝对关节角, n [B] 个数 或 need [B] bool):冷启动时用给定途经点代替子目标头;
|
||||
qp0 [B,6]:起点的上一帧(默认 = q0,即静止起步)。steps:MeanFlow 采样段数;z_scale:噪声缩放;
|
||||
select:consist(部署口径:冷启动价值前瞻、之后与上一条已提交计划的一致性)/ value(每次都价值前瞻)/ first(不选,取第 0 条)。
|
||||
@@ -174,6 +174,7 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
||||
last = None; last_ok = torch.zeros(B, dtype=torch.bool, device=device)
|
||||
fb_i = torch.zeros(B, 6, device=device) # 积分项:累积持续同向的目标反馈(慢变偏差)
|
||||
fb_cache = None; pit = 0 # fb_every>1:复用上一次规划的目标反馈(误差方向慢变)
|
||||
last_brake = torch.zeros(B, dtype=torch.bool, device=device)
|
||||
subs = torch.zeros(B, 2, 6, device=device); nsub = torch.zeros(B, dtype=torch.long, device=device)
|
||||
sub_t = torch.zeros(B, device=device); sub_prev = q0.clone()
|
||||
done = torch.zeros(B, dtype=torch.bool, device=device); t_done = torch.full((B,), float('nan'), device=device)
|
||||
@@ -203,6 +204,14 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
||||
else:
|
||||
z = torch.randn(B * K, H, 6, device=device, generator=g) * z_scale
|
||||
act, val, sub = model.sample_k(feat, K, z, steps=steps)
|
||||
if sub_refresh and not cold: # 子目标头作为反应式规划:没有待走途经点时,用当前状态的预测补一个(训练标签在每个状态都有 sub)
|
||||
need = sub[:, 12] > 0.5
|
||||
newsubs = q[:, None] + sub[:, :12].float().view(B, 2, 6) * 180
|
||||
rf = need & (nsub <= 0)
|
||||
subs = torch.where(rf[:, None, None], newsubs, subs)
|
||||
nsub = torch.where(rf, torch.full_like(nsub, 2), nsub)
|
||||
sub_prev = torch.where(rf[:, None], q, sub_prev)
|
||||
sub_t = torch.where(rf, torch.full_like(sub_t, tnow), sub_t)
|
||||
inc = act.float().clamp(-1, 1) * astep
|
||||
if fb_gain > 0: # 实验(2026-10-20):目标反馈加在所有候选上(安全层也评估带反馈的候选)
|
||||
if fb_cache is None or fb_every <= 1 or pit % fb_every == 0:
|
||||
@@ -210,6 +219,10 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
||||
else:
|
||||
fb = fb_cache
|
||||
pit += 1
|
||||
gm_ = None
|
||||
if fb_clear > 0: # 近障碍衰减:反馈方向可能把候选推进障碍,贴近时让模型自己的绕行占主导
|
||||
dgc = kin.gaps(qe, fb_clear)['gm'].min(-1).values
|
||||
gm_ = torch.clamp(dgc / fb_clear, fb_cmin, 1.0)
|
||||
if fb_ki > 0:
|
||||
fb_i = fb_decay * fb_i + fb[:, 0] * (~done)[:, None]
|
||||
lim = step * fb_imax
|
||||
@@ -218,9 +231,14 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
||||
elif fb_scale > 0: # 距离衰减:远离目标全增益,贴近目标减弱(减少过冲/刹停)
|
||||
dg, _ = goal_err(kin, qe, T, R)
|
||||
gdec = fb_gain * torch.clamp(dg / fb_scale, fb_near, 1.0)
|
||||
if gm_ is not None: gdec = gdec * gm_
|
||||
inc = inc + gdec[:, None, None, None] * fb[:, :, None, :]
|
||||
else:
|
||||
inc = inc + fb_gain * fb[:, :, None, :]
|
||||
gv = torch.full((B,), fb_gain, device=device, dtype=fb.dtype)
|
||||
if gm_ is not None: gv = gv * gm_
|
||||
if fb_block_gain != 1.0: # 上一次被安全层全拒:减弱目标反馈,让模型自己的绕行候选有机会通过认证
|
||||
gv = torch.where(last_brake, gv * fb_block_gain, gv)
|
||||
inc = inc + gv[:, None, None, None] * fb[:, :, None, :]
|
||||
if cold:
|
||||
need = sub[:, 12] > 0.5
|
||||
subs = q[:, None] + sub[:, :12].float().view(B, 2, 6) * 180
|
||||
@@ -237,10 +255,16 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
||||
fut = qe[:, None, None] + torch.cumsum(inc[:, :, r:], 2) # [B,K,H-r,6]
|
||||
if select == 'first':
|
||||
score = torch.zeros(B, K, device=device); score[:, 0] = 1
|
||||
elif select == 'endpoint':
|
||||
elif select in ('endpoint', 'adapt'):
|
||||
qa = fut[:, :, -1].reshape(B * K, 6)
|
||||
e_end, _ = goal_err(kin, qa, T.repeat_interleave(K, 0), R.repeat_interleave(K, 0))
|
||||
score = -e_end.view(B, K)
|
||||
if select == 'adapt' and last is not None:
|
||||
n = min(fut.shape[2], last.shape[1] - (commit_n + 1))
|
||||
w = 0.85 ** torch.arange(n, device=device, dtype=torch.float32)
|
||||
d2 = (((fut[:, :, :n] - last[:, None, commit_n + 1:commit_n + 1 + n]) / step) ** 2).sum(-1)
|
||||
sc = torch.where(last_ok[:, None], -(d2 * w).sum(-1), torch.zeros_like(d2[..., 0]))
|
||||
score = torch.where(last_brake[:, None], sc, score)
|
||||
elif cold or select == 'value':
|
||||
# 短前瞻:候选末态的价值
|
||||
qa = fut[:, :, -1].reshape(B * K, 6); qb = fut[:, :, -2].reshape(B * K, 6)
|
||||
@@ -272,6 +296,7 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
||||
if collect is not None: # 反馈蒸馏:收集与部署完全一致的 (特征, 复合动作=模型输出+目标反馈)
|
||||
collect.append((feat.detach().float().cpu(), inc[ar, pick].detach().float().cpu()))
|
||||
committed = torch.ones(B, dtype=torch.bool, device=device)
|
||||
braked = torch.zeros(B, dtype=torch.bool, device=device)
|
||||
if safety and safety_partial: # 2026-10-20:允许「部分安全前缀」——执行到第一个不安全步为止,而不是全拒刹停
|
||||
maxlen = torch.zeros(B, dtype=torch.long, device=device)
|
||||
chosen = torch.zeros(B, commit_n, 6, device=device); chosen_k = pick.clone()
|
||||
@@ -297,7 +322,7 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
||||
mskf = (maxlen == 0) & ok1
|
||||
commit = torch.where(mskf[:, None, None], fbcom, commit)
|
||||
msk = msk | mskf[:, None]
|
||||
brakes += (maxlen == 0).float()
|
||||
brakes += (maxlen == 0).float(); braked = maxlen == 0
|
||||
pick = chosen_k; committed = maxlen > 0
|
||||
elif safety:
|
||||
ok_any = torch.zeros(B, dtype=torch.bool, device=device)
|
||||
@@ -316,17 +341,24 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
||||
fb1 = dls_steps(kin, qe, T, R, n=1) * fb_fallback
|
||||
ok1 = kin.gaps(qe + fb1[:, 0], 0.5)['gm'].min(-1).values > 0
|
||||
commit = torch.where((~ok_any & ok1)[:, None, None], fb1.expand(-1, commit_n, -1), commit)
|
||||
brakes += (~ok_any).float()
|
||||
brakes += (~ok_any).float(); braked = ~ok_any
|
||||
pick = chosen_k; committed = ok_any
|
||||
if near.any():
|
||||
ref = dls_steps(kin, qe, T, R, n=commit_n)
|
||||
commit = torch.where(near[:, None, None], ref, commit)
|
||||
dls_it = torch.where(near & (dls_it < 0), torch.full_like(dls_it, it), dls_it)
|
||||
committed = committed & ~near
|
||||
take = near
|
||||
if dls_safety and safety: # 2026-10-20:近目标 DLS 接管也要过安全层(部署端 _certCommit 本来就会验;不过则退回候选计划)
|
||||
pts = qe[:, None] + torch.cumsum(ref, 1)
|
||||
mids = torch.cat([qe[:, None], pts[:, :-1]], 1) + 0.5 * ref
|
||||
allq = torch.cat([pts, mids], 1).reshape(-1, 6)
|
||||
okd = (kin.gaps(allq, 0.5)['gm'].min(-1).values > 0).view(B, 2 * commit_n).all(-1)
|
||||
take = near & okd
|
||||
commit = torch.where(take[:, None, None], ref, commit)
|
||||
dls_it = torch.where(take & (dls_it < 0), torch.full_like(dls_it, it), dls_it)
|
||||
committed = committed & ~take
|
||||
# 2026-10-19(F5):last 记真正提交的那条候选(安全层换了候选时是换后的那条);刹停 / DLS 接管的行无效
|
||||
if fb_ki > 0: # 刹停/精修接管时清积分(防止原地累积到限幅后突然冲出去)
|
||||
fb_i = torch.where(committed[:, None], fb_i, torch.zeros_like(fb_i))
|
||||
last = torch.cat([qe[:, None], fut[ar, pick]], 1); last_ok = committed
|
||||
last = torch.cat([qe[:, None], fut[ar, pick]], 1); last_ok = committed; last_brake = braked
|
||||
queue = torch.cat([queue, commit], 1)
|
||||
cold = False
|
||||
d = queue[:, 0]; queue = queue[:, 1:]
|
||||
@@ -348,6 +380,8 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
||||
if it + 1 == k2s: e2 = e.clone()
|
||||
newly = (~done) & (e < 1) & (oe < 0.5)
|
||||
t_done = torch.where(newly, torch.full_like(t_done, tn), t_done); done |= newly
|
||||
if progress is not None and (it == 0 or (it + 1) % 50 == 0 or it + 1 == nmax):
|
||||
progress(it + 1, nmax)
|
||||
if done.all(): break
|
||||
ok = done & (t_done <= limit + 1e-6)
|
||||
if moved_start is None: moved_start = travel.clone()
|
||||
|
||||
Reference in New Issue
Block a user