Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
217a70b484 |
@@ -72,6 +72,7 @@ def load_policy(path, device, prefer_ema=True):
|
|||||||
sd = ck.get('ema') if prefer_ema and ck.get('ema') is not None else ck['model']
|
sd = ck.get('ema') if prefer_ema and ck.get('ema') is not None else ck['model']
|
||||||
m.load_state_dict(sd)
|
m.load_state_dict(sd)
|
||||||
m.act_scale = float(ck.get('act_scale', 1.0))
|
m.act_scale = float(ck.get('act_scale', 1.0))
|
||||||
|
m.abs_target = bool(ck.get('abs_target', False))
|
||||||
return m.eval(), ck
|
return m.eval(), ck
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+11
-2
@@ -43,6 +43,9 @@ class Samples:
|
|||||||
sc = {float(m['act_scale']) for m in metas}
|
sc = {float(m['act_scale']) for m in metas}
|
||||||
if len(sc) > 1: raise SystemExit(f'样本目录的 act_scale 不一致 {sorted(sc)}({dirs}):DAgger 样本须与主数据同一版打包')
|
if len(sc) > 1: raise SystemExit(f'样本目录的 act_scale 不一致 {sorted(sc)}({dirs}):DAgger 样本须与主数据同一版打包')
|
||||||
self.act_scale = sc.pop(); self.data_ver = int(metas[0].get('data_ver', 1)) if metas else 1
|
self.act_scale = sc.pop(); self.data_ver = int(metas[0].get('data_ver', 1)) if metas else 1
|
||||||
|
ab = {bool(m.get('abs_target', False)) for m in metas}
|
||||||
|
if len(ab) > 1: raise SystemExit(f'样本目录的 abs_target 不一致 {sorted(ab)}({dirs}):绝对目标与增量标签不能混用')
|
||||||
|
self.abs_target = ab.pop() if ab else False
|
||||||
for d in dirs:
|
for d in dirs:
|
||||||
for fx in sorted(glob.glob(os.path.join(d, '*.X.npy'))):
|
for fx in sorted(glob.glob(os.path.join(d, '*.X.npy'))):
|
||||||
b = fx[:-6]
|
b = fx[:-6]
|
||||||
@@ -87,8 +90,14 @@ class Samples:
|
|||||||
km = (torch.arange(3, device=dev)[None] < pl[:, None]) & m[:, None] # [B,3] 前缀有效步
|
km = (torch.arange(3, device=dev)[None] < pl[:, None]) & m[:, None] # [B,3] 前缀有效步
|
||||||
eps = rn(B, 3, 6) * ps * km[..., None].to(X.dtype) # STEP 为单位
|
eps = rn(B, 3, 6) * ps * km[..., None].to(X.dtype) # STEP 为单位
|
||||||
X[:, PREFIX_SL] = X[:, PREFIX_SL] + eps.reshape(B, 18)
|
X[:, PREFIX_SL] = X[:, PREFIX_SL] + eps.reshape(B, 18)
|
||||||
Y[:, :3] = Y[:, :3] + eps / self.act_scale # Y 以 act_scale·STEP 为单位
|
if self.abs_target:
|
||||||
Y[torch.arange(B, device=dev), pl.clamp(max=Y.shape[1] - 1)] -= eps.sum(1) / self.act_scale
|
# 2026-10-19(方案 B):标签是绝对目标角(/180)。前缀噪声会真实执行 → 前 r 步绝对目标
|
||||||
|
# 跟着累计偏移(eps 加进前缀特征时就是度),第 r 步扣回、之后回到原轨迹。
|
||||||
|
Y[:, :3] = Y[:, :3] + torch.cumsum(eps, 1) / 180.0
|
||||||
|
Y[torch.arange(B, device=dev), pl.clamp(max=Y.shape[1] - 1)] -= eps.sum(1) / 180.0
|
||||||
|
else:
|
||||||
|
Y[:, :3] = Y[:, :3] + eps / self.act_scale # Y 以 act_scale·STEP 为单位
|
||||||
|
Y[torch.arange(B, device=dev), pl.clamp(max=Y.shape[1] - 1)] -= eps.sum(1) / self.act_scale
|
||||||
Y.clamp_(-1, 1)
|
Y.clamp_(-1, 1)
|
||||||
ds = float(cfg.get('aug_dq_sigma', 0.0))
|
ds = float(cfg.get('aug_dq_sigma', 0.0))
|
||||||
if ds > 0:
|
if ds > 0:
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ const DATA_VER = 2;
|
|||||||
const MIN_GAP_MM = +(process.env.MIN_GAP_MM || 3);
|
const MIN_GAP_MM = +(process.env.MIN_GAP_MM || 3);
|
||||||
const AUG_P = +(process.env.AUG_P || 0.4), AUG_SIGMA = +(process.env.AUG_SIGMA || 3), AUG_BACK = +(process.env.AUG_BACK || 12);
|
const AUG_P = +(process.env.AUG_P || 0.4), AUG_SIGMA = +(process.env.AUG_SIGMA || 3), AUG_BACK = +(process.env.AUG_BACK || 12);
|
||||||
const ACT_SCALE = +(process.env.ACT_SCALE || 1.25);
|
const ACT_SCALE = +(process.env.ACT_SCALE || 1.25);
|
||||||
const SIG = ['v' + DATA_VER, 's' + ACT_SCALE, 'p' + AUG_P, 'sg' + AUG_SIGMA, 'b' + AUG_BACK, 'g' + MIN_GAP_MM].join('|');
|
/* 2026-10-19(方案 B):ABS_TARGET=1 时标签改为 16 步绝对目标关节角(/180 归一化),执行端每步朝目标走;
|
||||||
|
prefix 仍存增量(度),与特征/部署的历史动作语义一致。未设时行为与原来完全一致。 */
|
||||||
|
const ABS_TARGET = +(process.env.ABS_TARGET || 0);
|
||||||
|
const SIG = ['v' + DATA_VER, 's' + ACT_SCALE, 'a' + ABS_TARGET, 'p' + AUG_P, 'sg' + AUG_SIGMA, 'b' + AUG_BACK, 'g' + MIN_GAP_MM].join('|');
|
||||||
if (process.argv[2] === '--sig') { console.log(SIG); process.exit(0); }
|
if (process.argv[2] === '--sig') { console.log(SIG); process.exit(0); }
|
||||||
const IN = process.argv[2], OUTDIR = process.argv[3] || path.join(__dirname, '../data/samples');
|
const IN = process.argv[2], OUTDIR = process.argv[3] || path.join(__dirname, '../data/samples');
|
||||||
if (!IN) { console.error('usage: build_dataset.cjs <shard.jsonl> [outDir] | --sig'); process.exit(1); }
|
if (!IN) { console.error('usage: build_dataset.cjs <shard.jsonl> [outDir] | --sig'); process.exit(1); }
|
||||||
@@ -74,14 +77,30 @@ for (let li = 0; li < NT; li++) {
|
|||||||
}
|
}
|
||||||
const pos = k => { const i = Math.min(N, t + k); const w = aug ? Math.max(0, 1 - k / AUG_BACK) : 0; return traj[i].map((v, j) => v + off[j] * w); };
|
const pos = k => { const i = Math.min(N, t + k); const w = aug ? Math.max(0, 1 - k / AUG_BACK) : 0; return traj[i].map((v, j) => v + off[j] * w); };
|
||||||
const y = new Float32Array(H * 6);
|
const y = new Float32Array(H * 6);
|
||||||
/* prevP = 按标签实际会走到的位置:被 ±1 截掉的亏空留在 prevP 与目标之差里,下一步接着补(2026-10-19,C2) */
|
if (ABS_TARGET) {
|
||||||
let prevP = q.slice();
|
for (let k = 1; k <= H; k++) {
|
||||||
for (let k = 1; k <= H; k++) {
|
const pk = pos(k);
|
||||||
const pk = pos(k);
|
for (let j = 0; j < 6; j++) y[(k - 1) * 6 + j] = Math.max(-1, Math.min(1, pk[j] / 180));
|
||||||
for (let j = 0; j < 6; j++) { const v = Math.max(-1, Math.min(1, (pk[j] - prevP[j]) / ASTEP[j])); y[(k - 1) * 6 + j] = v; prevP[j] += v * ASTEP[j]; }
|
}
|
||||||
|
} else {
|
||||||
|
/* prevP = 按标签实际会走到的位置:被 ±1 截掉的亏空留在 prevP 与目标之差里,下一步接着补(2026-10-19,C2) */
|
||||||
|
let prevP = q.slice();
|
||||||
|
for (let k = 1; k <= H; k++) {
|
||||||
|
const pk = pos(k);
|
||||||
|
for (let j = 0; j < 6; j++) { const v = Math.max(-1, Math.min(1, (pk[j] - prevP[j]) / ASTEP[j])); y[(k - 1) * 6 + j] = v; prevP[j] += v * ASTEP[j]; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const rr = Math.floor(rnd() * 4), prefix = [];
|
const rr = Math.floor(rnd() * 4), prefix = [];
|
||||||
for (let k = 0; k < rr; k++) { const row = []; for (let j = 0; j < 6; j++) row.push(y[k * 6 + j] * ASTEP[j]); prefix.push(row); }
|
for (let k = 0; k < rr; k++) {
|
||||||
|
const row = [];
|
||||||
|
for (let j = 0; j < 6; j++) {
|
||||||
|
if (ABS_TARGET) { // 前缀仍存「已提交的增量(度)」,特征语义不变
|
||||||
|
const cur = y[k * 6 + j] * 180, prev = k ? y[(k - 1) * 6 + j] * 180 : q[j];
|
||||||
|
row.push(cur - prev);
|
||||||
|
} else row.push(y[k * 6 + j] * ASTEP[j]);
|
||||||
|
}
|
||||||
|
prefix.push(row);
|
||||||
|
}
|
||||||
const remain = inter.filter(v => v.tau > taus[t] + 0.02);
|
const remain = inter.filter(v => v.tau > taus[t] + 0.02);
|
||||||
const sub = remain.length && rnd() < 0.8 ? remain[0].p : null;
|
const sub = remain.length && rnd() < 0.8 ? remain[0].p : null;
|
||||||
const gm = S.gapMatrix(q, POL.GAP_CAP);
|
const gm = S.gapMatrix(q, POL.GAP_CAP);
|
||||||
@@ -102,7 +121,7 @@ writeNpy(path.join(OUTDIR, base + '.V.npy'), Float32Array.from(V), [n]);
|
|||||||
writeNpy(path.join(OUTDIR, base + '.SG.npy'), cat(SG, 13), [n, 13]);
|
writeNpy(path.join(OUTDIR, base + '.SG.npy'), cat(SG, 13), [n, 13]);
|
||||||
writeNpy(path.join(OUTDIR, base + '.TID.npy'), TID, [n], 'int32');
|
writeNpy(path.join(OUTDIR, base + '.TID.npy'), TID, [n], 'int32');
|
||||||
/* meta 在 tasks.jsonl 之前写:tasks.jsonl 仍是「本 shard 打包完成」的标记 */
|
/* meta 在 tasks.jsonl 之前写:tasks.jsonl 仍是「本 shard 打包完成」的标记 */
|
||||||
fs.writeFileSync(path.join(OUTDIR, base + '.meta.json'), JSON.stringify({ data_ver: DATA_VER, act_scale: ACT_SCALE, aug_p: AUG_P, aug_sigma: AUG_SIGMA,
|
fs.writeFileSync(path.join(OUTDIR, base + '.meta.json'), JSON.stringify({ data_ver: DATA_VER, act_scale: ACT_SCALE, abs_target: ABS_TARGET, aug_p: AUG_P, aug_sigma: AUG_SIGMA,
|
||||||
aug_back: AUG_BACK, min_gap_mm: MIN_GAP_MM, aug_carry: true, sig: SIG }));
|
aug_back: AUG_BACK, min_gap_mm: MIN_GAP_MM, aug_carry: true, sig: SIG }));
|
||||||
// 2026-10-18 断点续跑:tasks.jsonl 最后写、先写 .tmp 再改名 —— 它存在就代表这个 shard 的 5 个 npy 都已写完,run_prep.sh 据此跳过
|
// 2026-10-18 断点续跑:tasks.jsonl 最后写、先写 .tmp 再改名 —— 它存在就代表这个 shard 的 5 个 npy 都已写完,run_prep.sh 据此跳过
|
||||||
const tf = path.join(OUTDIR, base + '.tasks.jsonl');
|
const tf = path.join(OUTDIR, base + '.tasks.jsonl');
|
||||||
|
|||||||
+26
-10
@@ -153,7 +153,10 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
|||||||
B = q0.shape[0]
|
B = q0.shape[0]
|
||||||
step = torch.tensor(STEP, device=device)
|
step = torch.tensor(STEP, device=device)
|
||||||
act_scale = oracle.act_scale if oracle is not None else float(getattr(model, 'act_scale', 1.0))
|
act_scale = oracle.act_scale if oracle is not None else float(getattr(model, 'act_scale', 1.0))
|
||||||
astep = step * act_scale # 动作反归一化:每单位 act 对应的度数
|
# 方案 B(2026-10-19):abs_target 模型输出 16 步绝对目标角(/180 归一化),执行时直接朝目标走;
|
||||||
|
# oracle / 旧 ckpt 仍是增量口径
|
||||||
|
abs_t = oracle is None and bool(getattr(model, 'abs_target', False))
|
||||||
|
astep = torch.full((6,), 180.0, device=device) if abs_t else step * act_scale
|
||||||
has_ref = torch.isfinite(ex)
|
has_ref = torch.isfinite(ex)
|
||||||
limit = time_limit(ex, max_sec)
|
limit = time_limit(ex, max_sec)
|
||||||
nmax = int(math.ceil(limit.max().item() / DT))
|
nmax = int(math.ceil(limit.max().item() / DT))
|
||||||
@@ -177,8 +180,13 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
|||||||
tnow = it * DT
|
tnow = it * DT
|
||||||
if (queue.shape[1] <= 3) if replan == 'r3' else (queue.shape[1] == 0):
|
if (queue.shape[1] <= 3) if replan == 'r3' else (queue.shape[1] == 0):
|
||||||
r = queue.shape[1]
|
r = queue.shape[1]
|
||||||
pre = torch.zeros(B, 3, 6, device=device); pre[:, :r] = queue[:, :r]
|
pre = torch.zeros(B, 3, 6, device=device)
|
||||||
qe = q + queue.sum(1)
|
if abs_t and r > 0: # queue 存绝对目标:前缀特征仍用增量(度)
|
||||||
|
prevq = torch.cat([q[:, None], queue[:, :r - 1]], 1)
|
||||||
|
pre[:, :r] = queue[:, :r] - prevq
|
||||||
|
else:
|
||||||
|
pre[:, :r] = queue[:, :r]
|
||||||
|
qe = (queue[:, -1] if r > 0 else q) if abs_t else q + queue.sum(1)
|
||||||
e, oe = goal_err(kin, qe, T, R)
|
e, oe = goal_err(kin, qe, T, R)
|
||||||
near = (e < 5) & ((R.abs().sum(-1) == 0) | (oe < 5))
|
near = (e < 5) & ((R.abs().sum(-1) == 0) | (oe < 5))
|
||||||
subs, nsub, sub_prev, sub_t = sub_advance(kin, subs, nsub, sub_prev, sub_t, qe, tnow, sub_rule)
|
subs, nsub, sub_prev, sub_t = sub_advance(kin, subs, nsub, sub_prev, sub_t, qe, tnow, sub_rule)
|
||||||
@@ -189,7 +197,7 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
|||||||
else:
|
else:
|
||||||
z = torch.randn(B * K, H, 6, device=device, generator=g) * z_scale
|
z = torch.randn(B * K, H, 6, device=device, generator=g) * z_scale
|
||||||
act, val, sub = model.sample_k(feat, K, z, steps=steps)
|
act, val, sub = model.sample_k(feat, K, z, steps=steps)
|
||||||
inc = act.float().clamp(-1, 1) * astep
|
inc = act.float().clamp(-1, 1) * astep # 增量口径=步长增量;B 口径=绝对目标角
|
||||||
if cold:
|
if cold:
|
||||||
need = sub[:, 12] > 0.5
|
need = sub[:, 12] > 0.5
|
||||||
subs = q[:, None] + sub[:, :12].float().view(B, 2, 6) * 180
|
subs = q[:, None] + sub[:, :12].float().view(B, 2, 6) * 180
|
||||||
@@ -203,7 +211,7 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
|||||||
nsub = torch.where(need, torch.full_like(nsub, 2), torch.zeros_like(nsub))
|
nsub = torch.where(need, torch.full_like(nsub, 2), torch.zeros_like(nsub))
|
||||||
if not use_sub: nsub = torch.zeros_like(nsub)
|
if not use_sub: nsub = torch.zeros_like(nsub)
|
||||||
sub_t.fill_(tnow); sub_prev = q.clone()
|
sub_t.fill_(tnow); sub_prev = q.clone()
|
||||||
fut = qe[:, None, None] + torch.cumsum(inc[:, :, r:], 2) # [B,K,H-r,6]
|
fut = inc[:, :, r:] if abs_t else qe[:, None, None] + torch.cumsum(inc[:, :, r:], 2) # [B,K,H-r,6] 绝对位置
|
||||||
if select == 'first':
|
if select == 'first':
|
||||||
score = torch.zeros(B, K, device=device); score[:, 0] = 1
|
score = torch.zeros(B, K, device=device); score[:, 0] = 1
|
||||||
elif cold or select == 'value':
|
elif cold or select == 'value':
|
||||||
@@ -231,8 +239,12 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
|||||||
chosen = torch.zeros(B, 3, 6, device=device); chosen_k = pick.clone()
|
chosen = torch.zeros(B, 3, 6, device=device); chosen_k = pick.clone()
|
||||||
for kk in range(K):
|
for kk in range(K):
|
||||||
ck = inc[ar, order[:, kk], r:r + 3]
|
ck = inc[ar, order[:, kk], r:r + 3]
|
||||||
pts = qe[:, None] + torch.cumsum(ck, 1)
|
if abs_t:
|
||||||
mids = torch.cat([qe[:, None], pts[:, :-1]], 1) + 0.5 * ck
|
pts = ck
|
||||||
|
mids = 0.5 * (torch.cat([qe[:, None], ck[:, :-1]], 1) + ck)
|
||||||
|
else:
|
||||||
|
pts = qe[:, None] + torch.cumsum(ck, 1)
|
||||||
|
mids = torch.cat([qe[:, None], pts[:, :-1]], 1) + 0.5 * ck
|
||||||
allq = torch.cat([pts, mids], 1).reshape(-1, 6)
|
allq = torch.cat([pts, mids], 1).reshape(-1, 6)
|
||||||
okk = (kin.gaps(allq, 0.5)['gm'].min(-1).values > 0).view(B, 6).all(-1)
|
okk = (kin.gaps(allq, 0.5)['gm'].min(-1).values > 0).view(B, 6).all(-1)
|
||||||
take = okk & ~ok_any
|
take = okk & ~ok_any
|
||||||
@@ -242,6 +254,7 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
|||||||
pick = chosen_k; committed = ok_any
|
pick = chosen_k; committed = ok_any
|
||||||
if near.any():
|
if near.any():
|
||||||
ref = dls_steps(kin, qe, T, R)
|
ref = dls_steps(kin, qe, T, R)
|
||||||
|
if abs_t: ref = qe[:, None] + torch.cumsum(ref, 1) # 增量 → 绝对目标
|
||||||
commit = torch.where(near[:, None, None], ref, commit)
|
commit = torch.where(near[:, None, None], ref, commit)
|
||||||
dls_it = torch.where(near & (dls_it < 0), torch.full_like(dls_it, it), dls_it)
|
dls_it = torch.where(near & (dls_it < 0), torch.full_like(dls_it, it), dls_it)
|
||||||
committed = committed & ~near
|
committed = committed & ~near
|
||||||
@@ -250,9 +263,12 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
|
|||||||
queue = torch.cat([queue, commit], 1)
|
queue = torch.cat([queue, commit], 1)
|
||||||
cold = False
|
cold = False
|
||||||
d = queue[:, 0]; queue = queue[:, 1:]
|
d = queue[:, 0]; queue = queue[:, 1:]
|
||||||
d = torch.where(done[:, None], torch.zeros_like(d), d)
|
if abs_t: # 绝对目标:直接走到目标;到位的题原地冻结
|
||||||
qp = q; q = kin.clamp_box(q + d)
|
qp = q; q = kin.clamp_box(torch.where(done[:, None], q, d)); travel += (q - qp).abs().sum(-1)
|
||||||
travel += d.abs().sum(-1)
|
else:
|
||||||
|
d = torch.where(done[:, None], torch.zeros_like(d), d)
|
||||||
|
qp = q; q = kin.clamp_box(q + d)
|
||||||
|
travel += d.abs().sum(-1)
|
||||||
j1_acc = j1_acc + d[:, 0]; j1_max = torch.maximum(j1_max, j1_acc); j1_min = torch.minimum(j1_min, j1_acc)
|
j1_acc = j1_acc + d[:, 0]; j1_max = torch.maximum(j1_max, j1_acc); j1_min = torch.minimum(j1_min, j1_acc)
|
||||||
j1_first = torch.where((j1_first == 0) & (j1_acc.abs() >= FIRST_DEG), torch.sign(j1_acc), j1_first)
|
j1_first = torch.where((j1_first == 0) & (j1_acc.abs() >= FIRST_DEG), torch.sign(j1_acc), j1_first)
|
||||||
if it + 1 == n_start: moved_start = travel.clone()
|
if it + 1 == n_start: moved_start = travel.clone()
|
||||||
|
|||||||
+23
-10
@@ -58,8 +58,12 @@ def open_loop(model, kin, vdata, stepv, fk_idx, n=1024, K=8):
|
|||||||
act, _, _ = model.sample_k(X, K)
|
act, _, _ = model.sample_k(X, K)
|
||||||
act = act.float().clamp(-1, 1)
|
act = act.float().clamp(-1, 1)
|
||||||
q = q_from_feat(X)
|
q = q_from_feat(X)
|
||||||
qt = q + (Y * stepv).sum(1)
|
if bool(getattr(model, 'abs_target', False)):
|
||||||
qh = q[:, None] + (act * stepv).sum(2) # [B,K,6]
|
qt = Y[:, -1] * 180.0
|
||||||
|
qh = act[:, :, -1] * 180.0 # [B,K,6]
|
||||||
|
else:
|
||||||
|
qt = q + (Y * stepv).sum(1)
|
||||||
|
qh = q[:, None] + (act * stepv).sum(2) # [B,K,6]
|
||||||
pt = fk_points(kin, qt, fk_idx)[:, None]
|
pt = fk_points(kin, qt, fk_idx)[:, None]
|
||||||
ph = fk_points(kin, qh.reshape(-1, 6), fk_idx).view(X.shape[0], K, -1, 3)
|
ph = fk_points(kin, qh.reshape(-1, 6), fk_idx).view(X.shape[0], K, -1, 3)
|
||||||
err = (ph - pt).norm(dim=-1).mean(-1) # [B,K] mm
|
err = (ph - pt).norm(dim=-1).mean(-1) # [B,K] mm
|
||||||
@@ -98,13 +102,13 @@ def main():
|
|||||||
kin = Kin(device=dev)
|
kin = Kin(device=dev)
|
||||||
# 2026-10-19(审查 F11):val 只取主数据目录(第一个 --samples);DAgger 目录的样本全部当 train
|
# 2026-10-19(审查 F11):val 只取主数据目录(第一个 --samples);DAgger 目录的样本全部当 train
|
||||||
data = Samples(a.samples, device=dev, split='train', val_dirs=a.samples[:1])
|
data = Samples(a.samples, device=dev, split='train', val_dirs=a.samples[:1])
|
||||||
act_scale = data.act_scale
|
act_scale = data.act_scale; abs_t = bool(data.abs_target)
|
||||||
val_tasks = [t for t in load_tasks(a.samples[:1]) if t['split'] == 'val'][:tc['eval_tasks']]
|
val_tasks = [t for t in load_tasks(a.samples[:1]) if t['split'] == 'val'][:tc['eval_tasks']]
|
||||||
try: vdata = Samples(a.samples[:1], device=dev, split='val')
|
try: vdata = Samples(a.samples[:1], device=dev, split='val')
|
||||||
except SystemExit: vdata = None
|
except SystemExit: vdata = None
|
||||||
if vdata is not None and vdata.n == 0: vdata = None
|
if vdata is not None and vdata.n == 0: vdata = None
|
||||||
print(f'train samples {data.n}, val tasks {len(val_tasks)}, act_scale {act_scale}, data_ver {data.data_ver}, device {dev}', flush=True)
|
print(f'train samples {data.n}, val tasks {len(val_tasks)}, act_scale {act_scale}, abs_target {abs_t}, data_ver {data.data_ver}, device {dev}', flush=True)
|
||||||
model = build(cfg).to(dev); model.act_scale = act_scale
|
model = build(cfg).to(dev); model.act_scale = act_scale; model.abs_target = abs_t
|
||||||
print(f'params {sum(p.numel() for p in model.parameters()) / 1e6:.2f} M', flush=True)
|
print(f'params {sum(p.numel() for p in model.parameters()) / 1e6:.2f} M', flush=True)
|
||||||
opt = torch.optim.AdamW(model.parameters(), lr=tc['lr'], betas=tuple(tc['betas']), weight_decay=tc['wd'])
|
opt = torch.optim.AdamW(model.parameters(), lr=tc['lr'], betas=tuple(tc['betas']), weight_decay=tc['wd'])
|
||||||
step0, best, bad_evals = 0, -1.0, 0
|
step0, best, bad_evals = 0, -1.0, 0
|
||||||
@@ -112,11 +116,14 @@ def main():
|
|||||||
s0 = float(ck.get('act_scale', 1.0))
|
s0 = float(ck.get('act_scale', 1.0))
|
||||||
if abs(s0 - act_scale) > 1e-9:
|
if abs(s0 - act_scale) > 1e-9:
|
||||||
raise SystemExit(f'{src} 的 act_scale={s0} 与样本 act_scale={act_scale} 不一致:标签尺度变了,须从头训练(FRESH_FROM=prep)')
|
raise SystemExit(f'{src} 的 act_scale={s0} 与样本 act_scale={act_scale} 不一致:标签尺度变了,须从头训练(FRESH_FROM=prep)')
|
||||||
|
a0 = bool(ck.get('abs_target', False))
|
||||||
|
if a0 != abs_t:
|
||||||
|
raise SystemExit(f'{src} 的 abs_target={a0} 与样本 {abs_t} 不一致:标签口径变了,须从头训练(FRESH_FROM=prep)')
|
||||||
if init_ck is not None and not a.resume:
|
if init_ck is not None and not a.resume:
|
||||||
check_scale(init_ck, a.init)
|
check_scale(init_ck, a.init)
|
||||||
model.load_state_dict(init_ck.get('ema') if init_ck.get('ema') is not None else init_ck['model'])
|
model.load_state_dict(init_ck.get('ema') if init_ck.get('ema') is not None else init_ck['model'])
|
||||||
print(f'微调:权重取自 {a.init}({"EMA" if init_ck.get("ema") is not None else "原始"}权重),优化器重置', flush=True)
|
print(f'微调:权重取自 {a.init}({"EMA" if init_ck.get("ema") is not None else "原始"}权重),优化器重置', flush=True)
|
||||||
ema = copy.deepcopy(model).eval(); ema.act_scale = act_scale
|
ema = copy.deepcopy(model).eval(); ema.act_scale = act_scale; ema.abs_target = abs_t
|
||||||
for p_ in ema.parameters(): p_.requires_grad_(False)
|
for p_ in ema.parameters(): p_.requires_grad_(False)
|
||||||
if a.resume:
|
if a.resume:
|
||||||
ck = torch.load(a.resume, map_location=dev); check_scale(ck, a.resume)
|
ck = torch.load(a.resume, map_location=dev); check_scale(ck, a.resume)
|
||||||
@@ -131,7 +138,8 @@ def main():
|
|||||||
lr_at = lambda s: tc['lr'] * (s / tc['warmup'] if s < tc['warmup'] else 0.5 * (1 + math.cos(math.pi * (s - tc['warmup']) / max(1, total - tc['warmup']))))
|
lr_at = lambda s: tc['lr'] * (s / tc['warmup'] if s < tc['warmup'] else 0.5 * (1 + math.cos(math.pi * (s - tc['warmup']) / max(1, total - tc['warmup']))))
|
||||||
amp = dev.startswith('cuda') and tc.get('amp') == 'bf16'
|
amp = dev.startswith('cuda') and tc.get('amp') == 'bf16'
|
||||||
stepv = torch.tensor(STEP, device=dev)
|
stepv = torch.tensor(STEP, device=dev)
|
||||||
astep = stepv * act_scale # 动作 → 度
|
# 方案 B:绝对目标标签(/180 归一化)→ 每单位动作 = 180°;增量口径 = act_scale·STEP
|
||||||
|
astep = torch.full((6,), 180.0, device=dev) if abs_t else stepv * act_scale
|
||||||
fk_idx = torch.linspace(0, kin.cap_a.shape[0] - 1, tc['fk_caps'], device=dev).long()
|
fk_idx = torch.linspace(0, kin.cap_a.shape[0] - 1, tc['fk_caps'], device=dev).long()
|
||||||
ema_d = float(tc.get('ema', 0.999))
|
ema_d = float(tc.get('ema', 0.999))
|
||||||
warm = 0 if model.head == 'wta' else int(tc.get('p_neq_warm', 5000))
|
warm = 0 if model.head == 'wta' else int(tc.get('p_neq_warm', 5000))
|
||||||
@@ -144,7 +152,7 @@ def main():
|
|||||||
saver = Saver(tc.get('ckpt_min', 10)) # 2026-10-18:每 10 分钟 + 收到停止请求时存 last.pt(原子写)
|
saver = Saver(tc.get('ckpt_min', 10)) # 2026-10-18:每 10 分钟 + 收到停止请求时存 last.pt(原子写)
|
||||||
def save(name, step, extra=None):
|
def save(name, step, extra=None):
|
||||||
d = {'model': model.state_dict(), 'ema': ema.state_dict(), 'opt': opt.state_dict(), 'cfg': cfg, 'step': step, 'best': best,
|
d = {'model': model.state_dict(), 'ema': ema.state_dict(), 'opt': opt.state_dict(), 'cfg': cfg, 'step': step, 'best': best,
|
||||||
'bad_evals': bad_evals, 'fp': kin.fp, 'act_scale': act_scale, 'data_ver': data.data_ver}
|
'bad_evals': bad_evals, 'fp': kin.fp, 'act_scale': act_scale, 'abs_target': abs_t, 'data_ver': data.data_ver}
|
||||||
if name == 'last.pt': d['rng'] = rng_state()
|
if name == 'last.pt': d['rng'] = rng_state()
|
||||||
if extra: d.update(extra)
|
if extra: d.update(extra)
|
||||||
atomic_save(d, os.path.join(a.out, name))
|
atomic_save(d, os.path.join(a.out, name))
|
||||||
@@ -172,12 +180,17 @@ def main():
|
|||||||
l_sm = torch.zeros((), device=dev); l_fk = torch.zeros((), device=dev)
|
l_sm = torch.zeros((), device=dev); l_fk = torch.zeros((), device=dev)
|
||||||
if xh.shape[0] > 0:
|
if xh.shape[0] > 0:
|
||||||
d2 = xh[:, 2:] - 2 * xh[:, 1:-1] + xh[:, :-2]
|
d2 = xh[:, 2:] - 2 * xh[:, 1:-1] + xh[:, :-2]
|
||||||
|
if abs_t: d2 = d2 * 180.0 / (sum(STEP) / len(STEP)) # 绝对目标 → 与增量口径同量纲(度/STEP)
|
||||||
l_sm = d2.pow(2).mean()
|
l_sm = d2.pow(2).mean()
|
||||||
if tc['w_fk'] > 0 and xh.shape[0] > 0:
|
if tc['w_fk'] > 0 and xh.shape[0] > 0:
|
||||||
q = q_from_feat(Xf)
|
q = q_from_feat(Xf)
|
||||||
ks = torch.tensor([3, 7, 11, 15], device=dev)
|
ks = torch.tensor([3, 7, 11, 15], device=dev)
|
||||||
qh = q[:, None] + torch.cumsum(xh.clamp(-1, 1) * astep, 1)[:, ks]
|
if abs_t:
|
||||||
qt = q[:, None] + torch.cumsum(Yf * astep, 1)[:, ks]
|
qh = (xh.clamp(-1, 1) * 180.0)[:, ks]
|
||||||
|
qt = (Yf * 180.0)[:, ks]
|
||||||
|
else:
|
||||||
|
qh = q[:, None] + torch.cumsum(xh.clamp(-1, 1) * astep, 1)[:, ks]
|
||||||
|
qt = q[:, None] + torch.cumsum(Yf * astep, 1)[:, ks]
|
||||||
B4 = qh.shape[0] * 4
|
B4 = qh.shape[0] * 4
|
||||||
ph = fk_points(kin, qh.reshape(B4, 6), fk_idx); pt = fk_points(kin, qt.reshape(B4, 6), fk_idx)
|
ph = fk_points(kin, qh.reshape(B4, 6), fk_idx); pt = fk_points(kin, qt.reshape(B4, 6), fk_idx)
|
||||||
l_fk = ((ph - pt).norm(dim=-1) / 10).mean()
|
l_fk = ((ph - pt).norm(dim=-1) / 10).mean()
|
||||||
|
|||||||
Reference in New Issue
Block a user