diff --git a/docs/本机执行清单.md b/docs/本机执行清单.md index 79047d9..b6c5b2d 100644 --- a/docs/本机执行清单.md +++ b/docs/本机执行清单.md @@ -3,32 +3,31 @@ 本文件是 Hark 对本机计算的唯一要求清单。按顺序执行,每一步把「回传」里的文件或输出原样交给用户,不要跳步,不要自行改代码。 所有命令在仓库根目录(G:\Robot_v2\机器人手臂)的 Git Bash 里运行。完成的条目由 Hark 在下一次提交里删掉。 -## 已知结论(截至 2026-10-19 03:07 回传) +## 已知结论(截至 2026-10-19 03:37 回传) -- 特征口径 Python 与 JS 一致;子目标、抄上一帧速度、起步都已排除(老师轨迹中段带速度起步也 0% 到达、96% 碰撞)。 -- gaps 显存已过:三条都 1.05 GiB,与点数无关。 -- 稳定现象:策略动作幅度在开环、起点、闭环三处都是老师的 1.4–1.6 倍,开环 16 步末端却准(~6 mm)。幅度大、终点准 = 每步叠着来回抵消的抖动;闭环只执行前 3 步,抖动不抵消,表现为「动得多、走得少」。 -- 现在的嫌疑:MeanFlow 一步生成没把噪声去干净。第 1 步的 F 段专门验证。 +- 特征口径 Python 与 JS 一致;子目标、抄上一帧速度、起步都已排除;gaps 显存已过。 +- 一步采样残留噪声真实(抖动 42×、残差-噪声相关 −0.86),2 段以上基本修掉;4 段闭环碰撞 99.6%→93%、2 s 误差比 0.815→0.610,到达仍 0%。零噪声 0%/100%(均值样本偏老师 ~4°)。 +- 新发现的错误:闭环一致性打分错位一步(对齐上一计划的第 3 个点,应为第 4 个),等于每次重规划奖励「比上一计划慢一步」的候选。Python 闭环和浏览器部署已同时修正。第 1 步的 C 段和 G 段都按修正后口径跑。 ## 第 0 步:同步代码 ```bash git status --short # 有未提交改动就先 commit,不要丢 git pull --rebase gitea main -git log --oneline -1 # 应是 Hark 的「diag: F 段 …」 +git log --oneline -1 # 应是 Hark 的「fix(closed_loop): 一致性打分错位 …」 ``` - 训练控制台若开着先点「停止」,确认没有 python 训练进程在跑。 - 不要启动训练 / DAgger / M5,等 Hark 看完第 1 步结果再定。 -## 第 1 步:闭环失败定位(含新加的 F 段,约 6–10 分钟) +## 第 1 步:闭环失败定位(含新加的 G 段,约 10–15 分钟) ```bash PYTHONUTF8=1 python -m ml.eval.diag_cl --ckpt ml/runs/base/last.pt --n 256 --out ml/runs/diag_cl.json ``` - 专用显存超过 22 GB 就 Ctrl+C 并回传终端输出。 -- 新增输出:`F0_openloop_sampler`(开环 1/2/4/8 段采样与零噪声:幅度比、抖动比、残差与噪声相关、前 3 步与 16 步末关节误差);`F1_4step`、`F1_z0`(闭环用 4 段采样、零噪声:到达、碰撞、2 s 目标误差比)。E 段已删。 +- 新增输出:`G_1step_consist`、`G_4step_consist`、`G_4step_value`、`G_4step_first`、`G_4step_consist_safety`,每项含到达、碰撞、2 s 目标误差比、末端误差、首次碰撞时刻、前 1 s 速度比、刹停次数。F 段已删。 - 回传:`ml/runs/diag_cl.json`。 ## 回传汇总 @@ -39,37 +38,3 @@ PYTHONUTF8=1 python -m ml.eval.diag_cl --ckpt ml/runs/base/last.pt --n 256 --out | 任一步出错 | 终端最后 30 行输出 | --- - -## 执行回传(2026-10-19 04:25,本地 AI) - -### 第 0 步:同步代码 - -pull 前无未提交 tracked 改动;两个 untracked 文件原位保留(旧 `diag_collision.py`、用户个人 html)。log 顶部 = `1bf59e7`「diag: F 段 …」✓。无训练进程。 - -### 第 1 步:闭环失败定位(F 段;完整 JSON 已更新到 ml/runs/diag_cl.json) - -C 段复跑与基线一致:0% 到达 / 99.6% 碰撞 / 2 s 误差比 0.815。 - -**F0_openloop_sampler**(开环 4096 行;老师 amp 0.319、jitter 0.0084): - -| 采样 | 幅度比 | 抖动比 | 残差-噪声相关 | 前 3 步误差 (°) | 16 步末端误差 (°) | -|---|---|---|---|---|---| -| 1step | 1.438 | **41.9** | **−0.861** | 2.12 | 4.97 | -| 2step | 0.987 | 4.18 | 0.284 | 0.21 | 0.92 | -| 4step | 1.011 | 1.92 | −0.034 | 0.17 | 0.86 | -| 8step | 0.998 | 1.67 | 0.108 | 0.11 | 0.56 | -| 1step z=0 | 1.161 | 1.14 | — | 0.82 | 4.01 | - -- **残留噪声证实**:一步生成的动作帧间抖动是老师的 **42 倍**、残差与输入噪声相关 **−0.861**(x=z−u 中 z 没去干净)。 -- **多段采样有效**:2 段就把抖动降到 4.2、16 步误差 4.97°→0.92°;4 段 1.9 / 0.86°;8 段 1.7 / 0.56°。 -- **零噪声**:幅度 1.16、抖动 1.14、16 步末端 4.01°——纯「均值样本」本身离老师样本约 4°。 -- 1step 的前 3 步误差(2.12°)比 16 步(4.97°)小一半——抖动误差随步数累积;1.438 幅度比即这一抖动的表现。 - -**F1 闭环对照**(C 基线 0% / 99.6% / 0.815): - -- **F1_4step**(4 段采样):到达 **0%** / 碰撞 **93.0%** / 2 s 误差比 **0.610** / 末端误差 p50 449 mm -- **F1_z0**(零噪声):到达 **0%** / 碰撞 **100.0%** / 2 s 误差比 **0.724** / 末端误差 p50 418 mm - -**结论**:一步采样残留噪声真实存在(F0 证据充分),且 4 段采样把闭环碰撞从 99.6% 降到 93.0%、2 s 误差比 0.815→0.610——方向对,但**不是闭环失败的主因**:4 段与零噪声都到不了(0% 到达),零噪声甚至 100% 碰撞(比基线还差)。 - -### 本轮无出错,无终端报错段。 diff --git a/ml/eval/diag_cl.py b/ml/eval/diag_cl.py index 13d92a3..cdfed88 100644 --- a/ml/eval/diag_cl.py +++ b/ml/eval/diag_cl.py @@ -8,7 +8,7 @@ B. 同一噪声下,用两份特征得到的动作差(16 步末端关节差,度)——A 有差但 B 很小说明差异无害; C. 闭环失败形态:首次碰撞时刻分布、碰撞步数、首次碰撞的组对、全程最小目标误差、目标误差进度比; 以及同一批题开安全层(safety=True,近似部署)的到达率 / 刹停次数。 - F. 一步采样残留噪声:开环比较 1/2/4/8 段采样与零噪声的幅度、抖动、残差与噪声相关;闭环用 4 段采样、零噪声各跑一遍。 + G. 候选选择与采样段数:一致性打分错位修正后,1 段/4 段采样 × consist/value/first 三种选法,以及 4 段 + 安全层的闭环对照。 用法(仓库根目录): PYTHONUTF8=1 python -m ml.eval.diag_cl --ckpt ml/runs/base/last.pt --n 256 --out ml/runs/diag_cl.json """ @@ -123,43 +123,42 @@ def main(): 'safety_on': {'reach': reach_s / n, 'collided': col_s / n, 'brakes_p50': pct(brakes_s, .5)}} out['C']['goal_err_ratio_p50'] = {'%.1fs' % ts: pct(v, .5) for ts, v in prog.items()} # <1 = 在靠近目标 - # ---------- F:一步采样残留噪声(2026-10-19 加) ---------- - # 前几轮:子目标、抄速度、起步都已排除;但动作幅度在开环、起点、闭环三处一致是老师的 1.4–1.6 倍, - # 而开环 16 步末端 FK 误差只有 ~6 mm。幅度大、终点却准 = 每步动作里叠着来回抵消的抖动。 - # 嫌疑:MeanFlow 一步生成 x = z − u(z,0,1) 没把噪声 z 去干净(只有 25% 样本学区间平均速度、14k 步), - # 闭环每次只执行前 3 步,抖动不抵消,表现为「动得多、走得少」,再挤进碰撞。 - # F0:开环 val 随机 4096 行,比较 1/2/4/8 段采样和零噪声(z=0):幅度比、抖动比、残差与 z 的相关、前 3 步与 16 步末关节误差 - # F1:同一批闭环题,用 4 段采样、零噪声各跑一遍,对比 C 的基线 - stepv = torch.tensor(STEP, device=dev) - ri = torch.randperm(vd.X.shape[0], generator=torch.Generator().manual_seed(3))[:4096].to(dev) - Xr, Yr = vd.X[ri].float(), vd.Y[ri].float() - zr = torch.randn(len(ri), 1, H, 6, device=dev, generator=torch.Generator(device=dev).manual_seed(11)) - ymag = Yr.abs().mean().item(); yjit = (Yr[:, 1:] - Yr[:, :-1]).abs().mean().item() - f0 = {'teacher_amp': ymag, 'teacher_jitter': yjit} - for tag, steps, zs in (('1step', 1, 1.0), ('2step', 2, 1.0), ('4step', 4, 1.0), ('8step', 8, 1.0), ('1step_z0', 1, 0.0)): - with torch.no_grad(): ac, _, _ = model.sample_k(Xr, 1, zr * zs, steps=steps) - ac = ac[:, 0]; acc = ac.clamp(-1, 1); res = ac - Yr - zc = zr[:, 0] - zr[:, 0].mean(); rc_ = res - res.mean() - corr = ((zc * rc_).sum() / (zc.pow(2).sum().sqrt() * rc_.pow(2).sum().sqrt()).clamp(min=1e-9)).item() if zs > 0 else None - e3 = ((acc[:, :3] - Yr[:, :3]) * stepv).sum(1).abs().max(-1).values - e16 = ((acc - Yr) * stepv).sum(1).abs().max(-1).values - f0[tag] = {'amp_ratio': acc.abs().mean().item() / ymag, 'jitter_ratio': (acc[:, 1:] - acc[:, :-1]).abs().mean().item() / yjit, - 'resid_corr_z': corr, 'first3_err_deg_p50': pct(e3.tolist(), .5), 'end16_err_deg_p50': pct(e16.tolist(), .5)} - out['F0_openloop_sampler'] = f0 - for tag, kw in (('F1_4step', {'steps': 4}), ('F1_z0', {'z_scale': 0.0})): - rc = cc_ = 0; me = []; r2 = [] + # ---------- G:候选选择与采样段数(2026-10-19 加) ---------- + # 第四轮 F 段:一步采样残留噪声真实(抖动 42×、残差-噪声相关 −0.86),4 段采样闭环碰撞 99.6%→93%,但到达仍 0%; + # 零噪声(8 条候选完全相同,选择不起作用)0%/100%——均值样本本身偏老师 ~4°(多峰平均)。 + # 复查闭环发现一致性打分错位一步(对齐 last[3],应为 last[4]),等于每次重规划奖励「比上一计划慢一步」的候选; + # 已在 closed_loop.py 与 ik-policy.js 修正,上面 C 段即修正后的一步采样基线。 + # G 段在同一批题上比较:4 段采样 × 三种选法(consist 部署口径 / value 每次价值前瞻 / first 不选),以及 4 段 + 安全层。 + # 若 consist≈first,说明选择不是主因;若 value 明显好,说明一致性选法本身把闭环带偏;三者都 0% 则问题在策略本身(转向协变量偏移)。 + def variant(**kw): + rc = cc_ = 0; me, r2, fh, sp, br = [], [], [], [], [] for i in range(0, len(tasks), a.bs): ch = tasks[i:i + a.bs] with torch.no_grad(): - _, pp = rollout(model, kin, ch, dev, K=8, safety=False, seed=i, return_traj=True, **kw) + _, pp = rollout(model, kin, ch, dev, K=8, seed=i, return_traj=True, **kw) tr = pp['traj'].to(dev); B, Tn, _ = tr.shape - k2 = min(Tn - 1, int(round(2.0 / DT))) + k2 = min(Tn - 1, int(round(2.0 / DT))); k1 = min(Tn - 1, int(round(1.0 / DT))) Tt = torch.tensor([t['T'] for t in ch], dtype=torch.float32, device=dev) Rt = torch.tensor([t['R'] if t.get('R') else [0.] * 9 for t in ch], dtype=torch.float32, device=dev) e0, _ = goal_err(kin, tr[:, 0], Tt, Rt); e2, _ = goal_err(kin, tr[:, k2], Tt, Rt) r2 += (e2 / e0.clamp(min=1e-3)).tolist() - rc += int(pp['done'].sum()); cc_ += int((pp['col'] > 0).sum()); me += pp['err'].tolist() - out[tag] = {'reach': rc / n, 'collided': cc_ / n, 'goal_err_ratio_2s_p50': pct(r2, .5), 'end_goal_err_mm_p50': pct(me, .5)} + # 前 1 s 关节行程速度 / 老师平均速度(travel/exec_s) + v1 = (tr[:, 1:k1 + 1] - tr[:, :k1]).abs().sum((1, 2)) / (k1 * DT) + sp += [float(v1[b]) / max(1e-6, ch[b]['travel'] / ch[b]['exec_s']) for b in range(B) if ch[b].get('travel') and ch[b].get('exec_s')] + flat = tr.reshape(-1, 6) + gm = torch.cat([kin.gaps(flat[s0:s0 + 1024], 0.5)['gm'] for s0 in range(0, flat.shape[0], 1024)]).view(B, Tn, 49) + hit = gm.min(-1).values <= 0 + for b in range(B): + h = hit[b].nonzero().flatten() + if len(h): fh.append(int(h[0]) * DT) + rc += int(pp['done'].sum()); cc_ += int((pp['col'] > 0).sum()); me += pp['err'].tolist(); br += pp['brakes'].tolist() + return {'reach': rc / n, 'collided': cc_ / n, 'goal_err_ratio_2s_p50': pct(r2, .5), 'end_goal_err_mm_p50': pct(me, .5), + 'first_hit_s_p50': pct(fh, .5), 'speed_ratio_1s_p50': pct(sp, .5), 'brakes_p50': pct(br, .5)} + out['G_1step_consist'] = variant(steps=1, select='consist') + out['G_4step_consist'] = variant(steps=4, select='consist') + out['G_4step_value'] = variant(steps=4, select='value') + out['G_4step_first'] = variant(steps=4, select='first') + out['G_4step_consist_safety'] = variant(steps=4, select='consist', safety=True) s = json.dumps(out, indent=1, ensure_ascii=False); print(s) os.makedirs(os.path.dirname(a.out) or '.', exist_ok=True) open(a.out, 'w', encoding='utf-8').write(s) diff --git a/ml/sim/closed_loop.py b/ml/sim/closed_loop.py index 75a3185..6cc3aac 100644 --- a/ml/sim/closed_loop.py +++ b/ml/sim/closed_loop.py @@ -65,8 +65,8 @@ def dls_steps(kin, q, T, R, n=3, scale=0.3, lam=0.5): @torch.no_grad() -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): - """use_sub=False:不用子目标(sf=0);sub_override=(subs [B,2,6] 绝对关节角, need [B] bool):冷启动时用给定途经点代替子目标头;qp0 [B,6]:起点的上一帧(默认 = q0,即静止起步)。steps:MeanFlow 采样段数;z_scale:噪声缩放(0 = 只取「均值」样本)。均为诊断用""" +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'): + """use_sub=False:不用子目标(sf=0);sub_override=(subs [B,2,6] 绝对关节角, need [B] bool):冷启动时用给定途经点代替子目标头;qp0 [B,6]:起点的上一帧(默认 = q0,即静止起步)。steps:MeanFlow 采样段数;z_scale:噪声缩放(0 = 只取「均值」样本);select:consist(部署口径:冷启动价值前瞻、之后一致性)/ value(每次都价值前瞻)/ first(不选,取第 0 条)。均为诊断用""" g = torch.Generator(device=device); g.manual_seed(seed) q0, T, R, ex, tr = tasks_to_tensors(tasks, device) B = q0.shape[0] @@ -113,7 +113,9 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None, if not use_sub: need = torch.zeros_like(need) nsub = torch.where(need, torch.full_like(nsub, 2), torch.zeros_like(nsub)); sub_t.fill_(tnow) fut = qe[:, None, None] + torch.cumsum(inc[:, :, r:], 2) # [B,K,H-r,6] - if last is None: + if select == 'first': + score = torch.zeros(B, K, device=device); score[:, 0] = 1 + elif last is None or select == 'value': # 短前瞻:候选末态的价值 qa = fut[:, :, -1].reshape(B * K, 6); qb = fut[:, :, -2].reshape(B * K, 6) f2 = features(kin, qa, qb, T.repeat_interleave(K, 0), R.repeat_interleave(K, 0), torch.zeros(B * K, 3, 6, device=device), @@ -121,9 +123,12 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None, v2, _ = model.heads(model.encode(f2)) score = -v2.view(B, K) else: - n = min(fut.shape[2], last.shape[1] - 3) + # 2026-10-19 修错位:last[i] = 上一计划从 qe_old 走 i 步后的位置;提交 3 步后 qe_new = last[3], + # 新候选 fut[0] 是 qe_new 再走 1 步 = 对应 last[4]。原来对齐 last[3],等于奖励「比上一计划慢一步」的候选, + # 每次重规划都挑最拖的那条,闭环越走越慢。JS 部署(ik-policy.js)同一处同样修正。 + n = min(fut.shape[2], last.shape[1] - 4) w = 0.85 ** torch.arange(n, device=device, dtype=torch.float32) - d2 = (((fut[:, :, :n] - last[:, None, 3:3 + n]) / step) ** 2).sum(-1) + d2 = (((fut[:, :, :n] - last[:, None, 4:4 + n]) / step) ** 2).sum(-1) score = -(d2 * w).sum(-1) order = score.argsort(-1, descending=True) pick = order[:, 0] diff --git a/webapp/js/ik-policy.js b/webapp/js/ik-policy.js index 141d555..47a3e00 100644 --- a/webapp/js/ik-policy.js +++ b/webapp/js/ik-policy.js @@ -297,10 +297,11 @@ class Runner { const fut = []; let qq = qe.slice(); for (let s = r; s < H; s++) { qq = qq.map((v, j) => v + inc[s][j]); fut.push(qq); } let j1 = 0; for (let s = 0; s < H; s++) j1 += inc[s][0]; let score = -2.0 * out.value[k]; - if (this.lastPlan) { // 与上一条已选计划的未来重叠步对齐(时间平移 3 步) + if (this.lastPlan) { // 与上一条已选计划的未来重叠步对齐:lastPlan[i] = 旧 qe 走 i 步,新 qe = lastPlan[commit],fut[s] 对应 lastPlan[s+commit+1] + // 2026-10-19 修错位(原对齐 lastPlan[s+commit],奖励比上一计划慢一步的候选;Python closed_loop.py 同步修正) let c = 0, w = 1; - for (let s = 0; s < fut.length && s + o.commit < this.lastPlan.length; s++, w *= 0.85) { - let d2 = 0; for (let j = 0; j < 6; j++) d2 += Math.pow((fut[s][j] - this.lastPlan[s + o.commit][j]) / STEP[j], 2); + for (let s = 0; s < fut.length && s + o.commit + 1 < this.lastPlan.length; s++, w *= 0.85) { + let d2 = 0; for (let j = 0; j < 6; j++) d2 += Math.pow((fut[s][j] - this.lastPlan[s + o.commit + 1][j]) / STEP[j], 2); c += w * d2; } score -= 0.02 * c;