fix(kin/diag): gaps 默认不拼胶囊对(pairs=True 仅 APG 用;实测 4096×40 峰值 4.46 GiB 全在拼接 8.6e7 对);diag_cl 删 D 段(子目标已排除)、加 E 段(开环幅度与抹 dq 敏感度、老师中段带速度/静止起步闭环);rollout 加 qp0;本机执行清单更新

This commit is contained in:
Hark
2026-10-18 18:53:39 +00:00
parent 9a7404856e
commit 527bc25c11
5 changed files with 93 additions and 97 deletions
+11 -50
View File
@@ -3,23 +3,20 @@
本文件是 Hark 对本机计算的唯一要求清单。按顺序执行,每一步把「回传」里的文件或输出原样交给用户,不要跳步,不要自行改代码。 本文件是 Hark 对本机计算的唯一要求清单。按顺序执行,每一步把「回传」里的文件或输出原样交给用户,不要跳步,不要自行改代码。
所有命令在仓库根目录(G:\Robot_v2\机器人手臂)的 Git Bash 里运行。完成的条目由 Hark 在下一次提交里删掉。 所有命令在仓库根目录(G:\Robot_v2\机器人手臂)的 Git Bash 里运行。完成的条目由 Hark 在下一次提交里删掉。
## 已知结论(2026-10-19 02:35 回传) ## 已知结论(截至 2026-10-19 02:43 回传)
- 特征口径 Python 与 JS 一致(最大差 3.6e-6),同噪声动作差 ~1e-5°:排除「闭环输入没见过」。 - 特征口径 Python 与 JS 一致(最大差 3.6e-6):排除「闭环输入没见过」。
- 闭环关安全层 0% 到达 / 99.6% 碰撞,首撞中位 1.46 s,主碰撞组对 2-4;全程最小目标误差中位 254 mm:策略根本没朝目标走。开安全层碰撞 5.5%,到达仍 0.4%。 - 子目标排除:不用子目标、用老师真实途经点,与基线完全一样(0% 到达 / 99.6% 碰撞)。
- 开环正常:ol_fk_mm 5.98、ol_best_mm 2.92、ol_div 0.655。老师回放 pass。 - 闭环方向对但极慢:2 s 只把目标误差缩小 18.5%,首撞中位 1.46 s,主碰撞组对 2-4。开环(老师状态上)几乎精确。
- 下一个嫌疑:子目标。训练时特征里的子目标是老师真实途经点,闭环里换成子目标头自己的预测。第 2 步的 D 段专门验证它。 - 现在的嫌疑:模型「抄上一帧速度」。第 2 步的 E 段专门验证。
- gaps 显存:上一版只给精算段分块,粗筛段 4096 点仍 9.3 GiB。本版粗筛也按点分块(每块 ≤ 446 点)。 - gaps 显存:粗筛分块已生效(1.05 / 1.40 GiB),4096×40 的 4.46 GiB 全在拼接胶囊对。本版默认只返回 gm,不再拼胶囊对(只有 APG 要)。
## 第 0 步:同步代码(不要 reset --hard) ## 第 0 步:同步代码
本地有未提交的 `ml/monitor.html`、`ml/pstate.py` 改动和一个未推送的合并提交,`reset --hard` 会把它们丢掉。
```bash ```bash
git add ml/monitor.html ml/pstate.py && git commit -m "monitor: 开环指标两张图;pstate: train 归档加 review_*" git status --short # 有未提交改动就先 commit,不要丢
git pull --rebase gitea main git pull --rebase gitea main
git log --oneline -3 # 最上面应是你的 monitor 提交,其下是 Hark 的「fix(kin/diag): …」 git log --oneline -1 # 应是 Hark 的「fix(kin/diag): gaps 默认不拼胶囊对 …」
git push gitea main
``` ```
- 训练控制台若开着先点「停止」,确认没有 python 训练进程在跑。 - 训练控制台若开着先点「停止」,确认没有 python 训练进程在跑。
@@ -44,14 +41,14 @@ EOF
- 专用显存接近 22 GB 或共享显存开始上涨:立刻 Ctrl+C,回传已打印的行,不要继续第 2 步。 - 专用显存接近 22 GB 或共享显存开始上涨:立刻 Ctrl+C,回传已打印的行,不要继续第 2 步。
- 回传:终端 3 行。 - 回传:终端 3 行。
## 第 2 步:闭环失败定位(含新加的 D 段,约 5–8 分钟) ## 第 2 步:闭环失败定位(含新加的 E 段,约 5–8 分钟)
```bash ```bash
PYTHONUTF8=1 python -m ml.eval.diag_cl --ckpt ml/runs/base/last.pt --n 256 --out ml/runs/diag_cl.json 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 并回传终端输出。 - 专用显存超过 22 GB 就 Ctrl+C 并回传终端输出。
- 新增输出:`C.goal_err_ratio_p50`(0.5/1/2 s 时目标误差 ÷ 起点误差,<1 表示在靠近);`D0_subhead`(冷启动子目标头精度);`D1_no_sub`(不用子目标);`D2_oracle_sub`(用老师真实途经点)。 - 新增输出:`E0_openloop`(开环动作幅度 ÷ 老师幅度;把 dq 抹成 0 后的幅度;起点行幅度);`E1_mid_teacher_vel` / `E1_mid_zero_vel`(从老师轨迹 30% 处起步,带老师速度 vs 静止起步:到达、碰撞、1 s 目标误差比、前 1 s 速度 ÷ 老师速度)。D 段已删。
- 回传:`ml/runs/diag_cl.json`。 - 回传:`ml/runs/diag_cl.json`。
## 回传汇总 ## 回传汇总
@@ -63,39 +60,3 @@ PYTHONUTF8=1 python -m ml.eval.diag_cl --ckpt ml/runs/base/last.pt --n 256 --out
| 任一步出错 | 终端最后 30 行输出 | | 任一步出错 | 终端最后 30 行输出 |
--- ---
## 执行回传(2026-10-19 03:55,本地 AI)
### 第 0 步:同步代码
monitor/pstate 按清单提交 `1f2f2a2`("monitor: 开环指标两张图;pstate: train 归档加 review_*"),pull --rebase 后已推送。log 与预期一致:`1f2f2a2`(monitor)→ `0653cf0`(Hark fix(kin/diag))。无训练进程在跑。
### 第 1 步:gaps 显存峰值(实测)
```
n= 512 cap= 40 peak 1.05 GiB
n= 4096 cap= 4 peak 1.40 GiB
n= 4096 cap= 40 peak 4.46 GiB
```
前两条过线;4096×40 仍 **4.46 GiB**(>2 GiB 判据)。
分层定位(独立测量):
- **粗筛分块工作正常**:单粗块(446 点,粗筛+块内全部精算)峰值 1.05 GiB。
- **峰值全在顶层 `_cat_gaps` 拼接**:完整 gaps 4.54 GiB == 只做 `_cat_gaps`(parts 备好后单独调)4.54 GiB。
- 机制:4096 点 cap=40 共 **8.63e7 细对**;顶层拼接时 10 个粗块的 parts(b/i/j int64 各 0.64 GiB、gap fp32 0.32 GiB,合计 2.25 GiB)与拼接输出(同样长大)同时存活,加上 `p['b']+s` 的临时副本,瞬时到 4.5 GiB 量级。
- 三个可行的修法方向(未改代码):① 逐键消费——`torch.cat([p.pop('b')+s …])` 一类,每拼完一个键释放该键 parts,峰值降到 ~2.5 GiB;② b/i/j int32 化(细对数 8.6e7 < 2^31,索引完全够),内存直接减半到 ~2.3 GiB;③ 顶层不拼大数组,消费方(dagger/collect、dag 收集)按块消费。
- 版本对比:上一版(`573082d`)同配置 9.30 GiB → 本版 4.54 GiB;旧版(`0b47905`)36.9 GiB。
### 第 2 步:闭环失败定位(含 D 段;完整 JSON 已更新到 ml/runs/diag_cl.json)
A/B/C 与上轮一致(A/B 全过;C:0% 到达 / 99.6% 碰撞,主碰撞对 J3-J5)。新增输出:
- **C.goal_err_ratio_p50**:0.5 s **0.967**、1.0 s **0.892**、2.0 s **0.815** —— 误差比值 <1,方向没错、在缓慢靠近,但 2 秒只缩 **18.5%**,从未接近。
- **D0_subhead**:需子目标的题占 28.1%;need 二分类准确率 71.9%;冷启动预测的途经点 1 每关节最大误差 p50 **66.8°** / p90 **137.1°** —— 子目标头确实基本预测不对。
- **D1_no_sub**(完全不用子目标):到达 **0%** / 碰撞 **99.6%** / 末端误差 p50 430 mm。
- **D2_oracle_sub**(用老师真实途经点):到达 **0%** / 碰撞 **99.6%** / 末端误差 p50 435 mm。
**D 段结论:子目标不是元凶。** D2(最有利输入)与 D1、C 基线三者到达率/碰撞率完全相同(0% / 99.6%),末端误差也一致(430/435 mm)——拿掉子目标或换成老师真值都毫无区别。子目标头虽然预测很差(p50 66.8°),但不是闭环失败的原因;策略在闭环状态里本身就完全走不动(2 s 只靠近 18.5% 且 99.6% 撞)。
### 第 1、2 步都没出错,无终端报错段。
+1 -1
View File
@@ -118,7 +118,7 @@ def main():
d = queue[:, k] d = queue[:, k]
qp = q; q = kin.clamp_box(q + d) qp = q; q = kin.clamp_box(q + d)
# 碰撞铰链:端点 + 中点(与仿真/运行时的离散检查一致) # 碰撞铰链:端点 + 中点(与仿真/运行时的离散检查一致)
G = kin.gaps(torch.cat([q, 0.5 * (q + qp)], 0), M) G = kin.gaps(torch.cat([q, 0.5 * (q + qp)], 0), M, pairs=True)
hinge = torch.relu(M - G['gap']) hinge = torch.relu(M - G['gap'])
col = torch.zeros(2 * B, device=dev).index_add(0, G['b'], hinge).view(2, B).mean(0) # 端点与中点取均值:与只查端点时同量纲,w_col 无需重标 col = torch.zeros(2 * B, device=dev).index_add(0, G['b'], hinge).view(2, B).mean(0) # 端点与中点取均值:与只查端点时同量纲,w_col 无需重标
sm = ((d / step) ** 2).sum(-1) + (((d - prev_d) / step) ** 2).sum(-1) sm = ((d / step) ** 2).sum(-1) + (((d - prev_d) / step) ** 2).sum(-1)
+16 -12
View File
@@ -129,24 +129,27 @@ class Kin:
@staticmethod @staticmethod
def _cat_gaps(parts, starts): def _cat_gaps(parts, starts):
return {'gm': torch.cat([p['gm'] for p in parts], 0), out = {'gm': torch.cat([p.pop('gm') for p in parts], 0)}
'b': torch.cat([p['b'] + s for p, s in zip(parts, starts)]), if 'b' in parts[0]:
'i': torch.cat([p['i'] for p in parts]), 'j': torch.cat([p['j'] for p in parts]), for p, s in zip(parts, starts): p['b'] += s
'gap': torch.cat([p['gap'] for p in parts])} for k in ('b', 'i', 'j', 'gap'): out[k] = torch.cat([p.pop(k) for p in parts]) # 逐键拼、拼完即释放
return out
def gaps(self, q, cap=40.0, xf=None, max_pairs=None): def gaps(self, q, cap=40.0, xf=None, max_pairs=None, pairs=False):
"""返回 dict: """返回 dict:
gm [B,49]:组对最小间隙 min(gap, cap)(gA*7+gB,gA<gB;其余为 cap) gm [B,49]:组对最小间隙 min(gap, cap)(gA*7+gB,gA<gB;其余为 cap)
b, i, j, gap:幸存胶囊对(gap 可微) pairs=True 时另有 b, i, j, gap:幸存胶囊对(gap 可微;只有 APG 的 hinge 与 check_parity 需要)
2026-10-19(用户实测 4096 点 cap 40 峰值 4.46 GiB 全在拼接 8.6e7 对的 b/i/j/gap):
默认只返回 gm,每块算完就丢掉胶囊对,峰值只剩单块。
""" """
xf = xf if xf is not None else self.cum_xf(q) xf = xf if xf is not None else self.cum_xf(q)
Rs, Ts = xf Rs, Ts = xf
B = q.shape[0] B = q.shape[0]
step = max(1, self.COARSE_ENTRIES // int(self.cp_a.shape[0])) step = max(1, self.COARSE_ENTRIES // int(self.cp_a.shape[0]))
if B <= step: if B <= step:
return self._gaps_split(q, cap, xf) return self._gaps_split(q, cap, xf, pairs)
st = list(range(0, B, step)) st = list(range(0, B, step))
return self._cat_gaps([self._gaps_split(q[s:s + step], cap, (Rs[s:s + step], Ts[s:s + step])) for s in st], st) return self._cat_gaps([self._gaps_split(q[s:s + step], cap, (Rs[s:s + step], Ts[s:s + step]), pairs) for s in st], st)
def _coarse(self, q, cap, xf): def _coarse(self, q, cap, xf):
Rs, Ts = xf Rs, Ts = xf
@@ -155,7 +158,7 @@ class Kin:
del cc del cc
return d2 <= (self.cp_lim + cap).pow(2) return d2 <= (self.cp_lim + cap).pow(2)
def _gaps_split(self, q, cap, xf): def _gaps_split(self, q, cap, xf, pairs):
"""粗筛已在显存预算内:按幸存细对数再切块做精算""" """粗筛已在显存预算内:按幸存细对数再切块做精算"""
Rs, Ts = xf Rs, Ts = xf
B = q.shape[0] B = q.shape[0]
@@ -171,10 +174,10 @@ class Kin:
bounds.append(B) bounds.append(B)
del per del per
if len(bounds) == 2: if len(bounds) == 2:
return self._gaps_one(q, cap, xf) return self._gaps_one(q, cap, xf, pairs)
return self._cat_gaps([self._gaps_one(q[s:e], cap, (Rs[s:e], Ts[s:e])) for s, e in zip(bounds[:-1], bounds[1:])], bounds[:-1]) return self._cat_gaps([self._gaps_one(q[s:e], cap, (Rs[s:e], Ts[s:e]), pairs) for s, e in zip(bounds[:-1], bounds[1:])], bounds[:-1])
def _gaps_one(self, q, cap, xf): def _gaps_one(self, q, cap, xf, pairs):
Rs, Ts = xf Rs, Ts = xf
B = q.shape[0] B = q.shape[0]
with torch.no_grad(): with torch.no_grad():
@@ -195,6 +198,7 @@ class Kin:
gap = d - self.pair_need[pidx].to(q.dtype) gap = d - self.pair_need[pidx].to(q.dtype)
gm = torch.full((B * 49,), float(cap), dtype=q.dtype, device=q.device) gm = torch.full((B * 49,), float(cap), dtype=q.dtype, device=q.device)
gm = gm.scatter_reduce(0, rep_b * 49 + self.pair_gp[pidx], gap.clamp(max=cap), reduce='amin', include_self=True) gm = gm.scatter_reduce(0, rep_b * 49 + self.pair_gp[pidx], gap.clamp(max=cap), reduce='amin', include_self=True)
if not pairs: return {'gm': gm.view(B, 49)}
return {'gm': gm.view(B, 49), 'b': rep_b, 'i': pi, 'j': pj, 'gap': gap} return {'gm': gm.view(B, 49), 'b': rep_b, 'i': pi, 'j': pj, 'gap': gap}
def min_gap(self, q, cap=40.0): def min_gap(self, q, cap=40.0):
+62 -31
View File
@@ -1,13 +1,15 @@
#!/usr/bin/env python #!/usr/bin/env python
"""闭环失败定位(只读,手动运行,约 2–4 分钟;训练进行中也可以另开终端跑,显存约 2–3 GB)。 """闭环失败定位(只读,手动运行,约 5–8 分钟;训练进行中也可以另开终端跑,显存约 2–3 GB)。
2026-10-19 新增:第二次预训练(979a581)开环已经正常(候选0 末端误差 ~6 mm、K 中最好 ~2.9 mm、多样性 0.65), 2026-10-19 新增:第二次预训练(979a581)开环已经正常(候选0 末端误差 ~6 mm、K 中最好 ~2.9 mm、多样性 0.65),
闭环却仍 100% 碰撞、0% 到达。开环用的是 JS 生成的特征 X,闭环用的是 Python features() 现算的特征, 闭环却仍 100% 碰撞、0% 到达。开环用的是 JS 生成的特征 X,闭环用的是 Python features() 现算的特征,
两者之间只要有一处口径不同,模型在闭环里看到的就是没见过的输入。本脚本回答三件事: 两者之间只要有一处口径不同,模型在闭环里看到的就是没见过的输入。本脚本回答三件事:
A. 特征口径:val 任务起点(非增广、老师状态)上,Python features() 与数据集 X 的逐组最大差; A. 特征口径:val 任务起点(非增广、老师状态)上,Python features() 与数据集 X 的逐组最大差;
B. 同一噪声下,用两份特征得到的动作差(16 步末端关节差,度)——A 有差但 B 很小说明差异无害; B. 同一噪声下,用两份特征得到的动作差(16 步末端关节差,度)——A 有差但 B 很小说明差异无害;
C. 闭环失败形态:首次碰撞时刻分布、碰撞步数、首次碰撞的组对、全程最小目标误差; C. 闭环失败形态:首次碰撞时刻分布、碰撞步数、首次碰撞的组对、全程最小目标误差、目标误差进度比;
以及同一批题开安全层(safety=True,近似部署)的到达率 / 刹停次数。 以及同一批题开安全层(safety=True,近似部署)的到达率 / 刹停次数。
E. 速度:开环动作幅度 / 老师幅度、抹掉 dq 后的幅度(是否「抄上一帧速度」);
从老师轨迹中段起步的闭环,带老师速度 vs 静止起步(是否只坏在起步)。
用法(仓库根目录): 用法(仓库根目录):
PYTHONUTF8=1 python -m ml.eval.diag_cl --ckpt ml/runs/base/last.pt --n 256 --out ml/runs/diag_cl.json PYTHONUTF8=1 python -m ml.eval.diag_cl --ckpt ml/runs/base/last.pt --n 256 --out ml/runs/diag_cl.json
""" """
@@ -122,36 +124,65 @@ def main():
'safety_on': {'reach': reach_s / n, 'collided': col_s / n, 'brakes_p50': pct(brakes_s, .5)}} '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 = 在靠近目标 out['C']['goal_err_ratio_p50'] = {'%.1fs' % ts: pct(v, .5) for ts, v in prog.items()} # <1 = 在靠近目标
# ---------- D:子目标是不是元凶(2026-10-19 加:C 显示全程最小目标误差中位 254 mm,策略根本没朝目标走) ---------- # ---------- E:速度与「抄速度」(2026-10-19 加) ----------
# 训练时特征里的子目标是老师真实途经点(80% 概率给),闭环里却换成子目标头自己的预测;预测偏了,策略就会忠实地朝错的方向走。 # 上一轮 D 段:不用子目标 / 用老师真实途经点,结果与基线完全一样(0% / 99.6%),子目标排除。
sg_by_tid = {} # C 段 2 s 只把目标误差缩小 18.5%:方向对、但走得极慢。开环(数据集里的老师状态)却几乎精确。
for i, t in zip(idx, tk): # 嫌疑:特征里的 dq(上一帧速度)在老师数据里总是满速,模型学会「照上一帧速度往下走」;
qrow = torch.atan2(vd.X[i, 0:12:2].float(), vd.X[i, 1:12:2].float()) * 180 / torch.pi # 闭环从静止起步 dq=0,模型就给小动作,小动作又喂回 dq,一直慢,慢慢挤进自碰撞。
sgr = vd.SG[i].float() stepv = torch.tensor(STEP, device=dev)
sg_by_tid[t['tid']] = (qrow[None] + sgr[:12].view(2, 6) * 180, bool(sgr[12] > 0.5)) gen = torch.Generator(device=dev).manual_seed(11)
# D0:冷启动子目标头精度(起点特征里抹掉数据集的真子目标,与闭环冷启动同口径) # E0:开环,val 全部时刻随机 4096 行。动作幅度 / 老师幅度;把 dq 抹成 0 后幅度与 16 步末端差
Xs = X0.clone(); Xs[:, 68:75] = 0 ri = torch.randperm(vd.X.shape[0], generator=torch.Generator().manual_seed(3))[:4096].to(dev)
with torch.no_grad(): _, sp = model.heads(model.encode(Xs)) Xr, Yr = vd.X[ri].float(), vd.Y[ri].float()
lab = torch.stack([vd.SG[i].float() for i in idx]) zr = torch.randn(len(ri), 1, H, 6, device=dev, generator=gen)
need_l = lab[:, 12] > 0.5; need_p = sp[:, 12] > 0.5 def amp(X):
via_err = ((sp[:, :6] - lab[:, :6]) * 180).abs().max(-1).values with torch.no_grad(): ac, _, _ = model.sample_k(X, 1, zr)
out['D0_subhead'] = {'n': len(idx), 'need_label_rate': need_l.float().mean().item(), 'need_acc': (need_l == need_p).float().mean().item(), return ac[:, 0].clamp(-1, 1)
'via1_maxjoint_err_deg_p50': pct(via_err[need_l].tolist(), .5), 'via1_maxjoint_err_deg_p90': pct(via_err[need_l].tolist(), .9)} a_full = amp(Xr); X0dq = Xr.clone(); X0dq[:, 15:21] = 0; a_0dq = amp(X0dq)
# D1 不用子目标、D2 用老师真实途经点(oracle),同一批题、同一随机种子,对比 C 的基线 ymag = Yr.abs().mean().item()
def run(tag, **kw): moving = Xr[:, 15:21].abs().max(-1).values > 0.3 # 老师此刻在动(|dq| > 30% 满速)
rc = cc_ = 0; me = [] out['E0_openloop'] = {'n': len(ri), 'teacher_amp': ymag, 'policy_amp_ratio': a_full.abs().mean().item() / ymag,
for i in range(0, len(tasks), a.bs): 'dq_zeroed_amp_ratio': a_0dq.abs().mean().item() / ymag,
ch = tasks[i:i + a.bs]; extra = dict(kw) 'dq_zeroed_amp_ratio_on_moving_rows': a_0dq[moving].abs().mean().item() / max(1e-6, Yr[moving].abs().mean().item()),
if kw.get('sub_override') == 'oracle': 'dq_zeroed_end_diff_deg_p50': pct(((a_full - a_0dq) * stepv).sum(1).abs().max(-1).values.tolist(), .5),
subs = torch.stack([sg_by_tid[t['tid']][0] if t['tid'] in sg_by_tid else torch.zeros(2, 6, device=dev) for t in ch]) 'start_rows_amp_ratio': None}
need = torch.tensor([sg_by_tid[t['tid']][1] if t['tid'] in sg_by_tid else False for t in ch], device=dev) st = first.nonzero().flatten()[:4096]
extra['sub_override'] = (subs, need) with torch.no_grad(): a_st, _, _ = model.sample_k(vd.X[st].float(), 1, torch.randn(len(st), 1, H, 6, device=dev, generator=gen))
out['E0_openloop']['start_rows_amp_ratio'] = a_st.clamp(-1, 1).abs().mean().item() / max(1e-6, vd.Y[st].float().abs().mean().item())
# E1:从老师轨迹中段(每题第 30% 行)起步闭环,带老师上一帧速度 vs 静止起步,同一批题同一噪声
tid_all = vd.TID.long(); starts = first.nonzero().flatten().tolist() + [len(tid_all)]
mid_rows = []
for s0, s1 in zip(starts[:-1], starts[1:]):
if int(tid_all[s0]) in by_tid and s1 - s0 >= 4: mid_rows.append(s0 + int(0.3 * (s1 - s0)))
mid_rows = mid_rows[:a.n]
Xm = vd.X[mid_rows].float()
qm = torch.atan2(Xm[:, 0:12:2], Xm[:, 1:12:2]) * 180 / torch.pi
qm = torch.where(kin.cont, qm, kin.clamp_box(qm))
qpm = qm - Xm[:, 15:21] * stepv
mtasks = []
for r_, q_ in zip(mid_rows, qm.tolist()):
t = dict(by_tid[int(tid_all[r_])]); t['q0'] = q_; t['exec_s'] = float(vd.V[r_]) * 5; mtasks.append(t)
rem = [t['exec_s'] for t in mtasks]
ym = vd.Y[mid_rows].float().abs().mean().item()
for tag, use_v in (('E1_mid_teacher_vel', True), ('E1_mid_zero_vel', False)):
rc = cc_ = 0; ratio1 = []; spd = []
for i in range(0, len(mtasks), a.bs):
ch = mtasks[i:i + a.bs]
with torch.no_grad(): with torch.no_grad():
_, pp = rollout(model, kin, ch, dev, K=8, safety=False, seed=i, **extra) _, pp = rollout(model, kin, ch, dev, K=8, safety=False, seed=i, return_traj=True,
rc += int(pp['done'].sum()); cc_ += int((pp['col'] > 0).sum()); me += pp['err'].tolist() qp0=qpm[i:i + a.bs] if use_v else None)
out[tag] = {'reach': rc / n, 'collided': cc_ / n, 'end_goal_err_mm_p50': pct(me, .5)} tr = pp['traj'].to(dev); B, Tn, _ = tr.shape
run('D1_no_sub', use_sub=False) k1 = min(Tn - 1, int(round(1.0 / DT)))
run('D2_oracle_sub', sub_override='oracle') 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); e1, _ = goal_err(kin, tr[:, k1], Tt, Rt)
ratio1 += (e1 / e0.clamp(min=1e-3)).tolist()
dd = (tr[:, 1:k1 + 1] - tr[:, :k1]) / stepv
spd.append(dd.abs().mean().item())
rc += int(pp['done'].sum()); cc_ += int((pp['col'] > 0).sum())
nm = len(mtasks)
out[tag] = {'n': nm, 'teacher_remaining_s_p50': pct(rem, .5), 'reach': rc / nm, 'collided': cc_ / nm,
'goal_err_ratio_1s_p50': pct(ratio1, .5), 'speed_ratio_first_1s': sum(spd) / len(spd) / max(1e-6, ym)}
s = json.dumps(out, indent=1, ensure_ascii=False); print(s) s = json.dumps(out, indent=1, ensure_ascii=False); print(s)
os.makedirs(os.path.dirname(a.out) or '.', exist_ok=True) os.makedirs(os.path.dirname(a.out) or '.', exist_ok=True)
open(a.out, 'w', encoding='utf-8').write(s) open(a.out, 'w', encoding='utf-8').write(s)
+3 -3
View File
@@ -65,8 +65,8 @@ def dls_steps(kin, q, T, R, n=3, scale=0.3, lam=0.5):
@torch.no_grad() @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): 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):
"""use_sub=False:不用子目标(sf=0);sub_override=(subs [B,2,6] 绝对关节角, need [B] bool):冷启动时用给定途经点代替子目标头(诊断用)""" """use_sub=False:不用子目标(sf=0);sub_override=(subs [B,2,6] 绝对关节角, need [B] bool):冷启动时用给定途经点代替子目标头;qp0 [B,6]:起点的上一帧(默认 = q0,即静止起步)。均为诊断用"""
g = torch.Generator(device=device); g.manual_seed(seed) g = torch.Generator(device=device); g.manual_seed(seed)
q0, T, R, ex, tr = tasks_to_tensors(tasks, device) q0, T, R, ex, tr = tasks_to_tensors(tasks, device)
B = q0.shape[0] B = q0.shape[0]
@@ -74,7 +74,7 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
has_ref = torch.isfinite(ex) has_ref = torch.isfinite(ex)
limit = torch.where(has_ref, torch.clamp(2 * ex, min=3.0), torch.full_like(ex, NOREF_SEC)) if max_sec is None else torch.full_like(ex, max_sec) limit = torch.where(has_ref, torch.clamp(2 * ex, min=3.0), torch.full_like(ex, NOREF_SEC)) if max_sec is None else torch.full_like(ex, max_sec)
nmax = int(math.ceil(limit.max().item() / DT)) nmax = int(math.ceil(limit.max().item() / DT))
q, qp = q0.clone(), q0.clone() q, qp = q0.clone(), (qp0.to(device) if qp0 is not None else q0.clone())
queue = torch.zeros(B, 0, 6, device=device) queue = torch.zeros(B, 0, 6, device=device)
last = None last = None
subs = torch.zeros(B, 2, 6, device=device); nsub = torch.zeros(B, dtype=torch.long, device=device); sub_t = torch.zeros(B, 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)