exp: 轮次45——tie(等长安全前缀按沿路最小间隙定夺)判掉

两臂 n=256 同 seed 背靠背(配方=轮次42 K8+cert_batch=1+inc_cap=1):到达 0.5234→0.5039
(-1.95pp,逐题配对丢6赢1净-5,McNemar 精确 p=0.125 与零不可分),时限内 +1.17pp、
刹停 -9%、行程比 -0.076、执行碰撞仍为 0(判据未放松)。丢的 6 题 5 题在 A 类(开阔区)
=买余量卖进度的签名。

仪表与离线数交叉验证:改选率 5.58% 对上轮次44 独立测得的"执行点间隙落在 0.2–0.5mm 占
5.6%",并列率 79.0% 对上"满前缀 79.4%" ⇒ 分辨率上界被宽相位截顶 gcap=max(0.5,margin+0.5)
锁死,5.6% 是这个家族在 cap=0.5 下的天花板,不是实现没调好。

结构性发现:#24 作用在错的人群——tie 只在 maxlen>0 触发,而失败主人群是全拒(maxlen=0,
未到达题 22% 的规划);全拒那条已由 backoff(轮次43)判掉。前置条件记为 #28(gcap 与
gap_margin 解耦)。

实现账:新增 tie 与触发计数器(生产端 per 数组+dump 键、消费端汇总 tie_* 同批改完,
tie=0 时 22/22 老键逐位复现);修掉 tie 分支局部名 nmax 顶掉主循环总步数的遮蔽 bug,
守卫单测做过变异检查;计数器正对照可手算(tie_opp=tie_changed=n_plans)。47 项测试绿。
This commit is contained in:
cbb
2026-10-21 00:33:44 +08:00
parent 350f386c02
commit 72c8835229
8 changed files with 564 additions and 5 deletions
+17 -2
View File
@@ -163,7 +163,7 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=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, dls_safety=0, fb_clear=0.0, fb_cmin=0.3, sub_refresh=0, fb_block_gain=1.0,
inc_cap=0, gap_margin=0.0, cert_batch=0, backoff=0, len_w=0.0, progress=None):
inc_cap=0, gap_margin=0.0, cert_batch=0, backoff=0, len_w=0.0, tie=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,10 +174,11 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
gap_margin>0:安全层要求间隙大于该毫米余量而不是 >0;宽相位 cap 自动抬到 margin+0.5,否则 0.5 的截顶值会让余量门限恒假。
backoff>0:全拒(连第 1 步都不安全)时不再站住,把首选候选按 1/2、1/4…(共 backoff 档)缩步一起送进同一次认证,
取能通过的第 1 档执行;缩步候选不参与"前缀更长者优先"的比较,只在原候选全被拒时顶上。需 cert_batch=1。
tie=1:等长安全前缀之间改用「沿路最小间隙更大」定夺(默认 0=沿用选择器分数顺序)。需 cert_batch=1。
select='ep_len' + len_w>0:在末态误差打分上再减 len_w·Σ|Δ|/STEP(每轴归一的候选累计行程)。§9.2 要行程≤规划器×1.10,
而实测行程比 2.4~2.6 没有任何机制在管;len_w=0 与 select='endpoint' 逐位相同(有回归断言)。
两者默认值下行为与第32轮逐位相同。"""
if backoff and not cert_batch: raise ValueError('backoff 目前只实现在 cert_batch=1 口径')
if (backoff or tie) and not cert_batch: raise ValueError('backoff/tie 目前只实现在 cert_batch=1 口径')
g = torch.Generator(device=device); g.manual_seed(seed)
q0, T, R, ex, tr = tasks_to_tensors(tasks, device)
B = q0.shape[0]
@@ -199,6 +200,8 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
done = torch.zeros(B, dtype=torch.bool, device=device); t_done = torch.full((B,), float('nan'), device=device)
col = torch.zeros(B, device=device); travel = torch.zeros(B, device=device); brakes = torch.zeros(B, device=device)
bo = torch.zeros(B, device=device) # 缩步顶上的次数(backoff>0 才会计)
to = torch.zeros(B, device=device) # tie=1 才计:出现「等长安全前缀并列」的规划次数(钩子可用)
tx = torch.zeros(B, device=device) # tie=1 才计:并列里真的由余量改选了那条的次数(钩子有效)
first_hit = torch.full((B,), float('nan'), device=device); dls_it = torch.full((B,), -1, dtype=torch.long, device=device)
# 2026-10-20(轮次33):速度/加速度按「实际执行位移」算,不看指令——第30轮证明反馈是在限幅之后叠加的
vratio = torch.zeros(B, device=device); aratio = torch.zeros(B, device=device); prev_exec = torch.zeros(B, 6, device=device)
@@ -363,6 +366,15 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
plen = pl_all[:, :ntry]
maxlen = plen.max(-1).values
best = plen.argmax(-1) # 与串行同口径:严格更大者胜 ⇒ 首个最大值
if tie: # 等长前缀时不再只看选择器分数:取"沿路最小间隙更大"的那条(零额外几何调用,判据仍是 gap>0)
ci = torch.arange(2 * commit_n, device=device)
keep = (ci < maxlen[:, None]) | ((ci >= commit_n) & (ci < commit_n + maxlen[:, None])) # [B,2cn] 只看会被执行的那些点
clr = gaps6[:, :ntry].where(keep[:, None, :], torch.full((), float('inf'), device=device)).amin(-1)
cand = torch.where(plen == maxlen[:, None], clr, torch.full_like(clr, -float('inf'))).argmax(-1)
ntile = (plen == maxlen[:, None]).sum(-1) # 并列最长的候选条数(勿用 nmax:那是主循环的总步数)
to += (ntile > 1) & (maxlen > 0)
tx += (cand != best)
best = cand
if backoff: # 缩步候选只在全拒时顶上:不参与「前缀更长者优先」的比较,所以正常路径逐位不变
full0 = maxlen == 0
bb = pl_all[:, ntry:].argmax(-1) # 平手取缩得最少的(档 0 = 1/2)
@@ -516,6 +528,9 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
per = {'done': done.cpu(), 'ok': ok.cpu(), 't_done': t_done.cpu(), 'limit': limit.cpu(), 'col': col.cpu(), 'travel': travel.cpu(), 'err': e_end.cpu(),
'side': side.cpu(), 'start_fail': start_fail.cpu(), 'brakes': brakes.cpu(), 'backoff': bo.cpu(), 'first_hit': first_hit.cpu(), 'dls_it': dls_it.cpu(),
'n_plans': nplan.cpu(), 'brake_rate': (brakes / nplan.clamp_min(1.0)).cpu(),
'tie_opp': to.cpu(), 'tie_changed': tx.cpu(), # tie=1 才有意义:并列次数/改选次数(每题)
'tie_opp_rate': (to / nplan.clamp_min(1.0)).cpu(), # 分母与 brake_rate 同口径 = 规划次数
'tie_changed_rate': (tx / nplan.clamp_min(1.0)).cpu(),
'goal_ratio_2s': ratio2.cpu(), 'speed_ratio': vratio.cpu(), 'accel_ratio': aratio.cpu(),
# 口径与离线分解一致:换向的 (步,轴) 对数 / 活动的 (步,轴) 对数 ∈ [0,1](除以 6 个轴)
'reverse_rate': (rev / (6.0 * nact.clamp_min(1.0))).cpu(),