perf+exp: 轮次34——复用cum_xf进库(22项指标逐位不变)、批量认证压最坏尾部(80→37ms)、FP16/BF16实测无收益

- 撤回一条自己的假收益:先前"合并几何查询→24.5ms"不成立。①第一版把 xf= 传给含5行的 gaps,
  只有第0行走完宽相位,认证点全部退回截顶40.0(少干活);②真实顺序 特征→推理→候选→认证,
  认证点依赖网络输出,故"特征间隙与认证点合并一次调用"实现上做不到
- 可实现并已落地:gm_groups(..., xf=) 复用 features 已算的 cum_xf,B=1 交错实测 P50 −3.4ms;
  256题重跑 22 项指标与轮次33 base 逐项相同
- 新增 --cert_batch(默认0不变):K条候选并成1次 gaps 调用。合成夹具(B=2)断言选择/轨迹/证书逐位一致,
  变异检查(换回错误索引)2项测试立即失败;256题重跑同样 22 项指标逐位相同
  实测交换:典型 33.5→37.6ms,最坏(前面候选被拒)80.2→37.1ms,P99 64.7→48.3ms
- FP16/BF16 实测不比 FP32 快(推理 7.68→8.31→8.58ms),整周期不变而 P99 更差,动作偏差 0.005°/0.038°
  → 小批量下不是算力受限,半精度不划算,推理保持 FP32
- 仍不达 20ms:下一刀是流水线复用(下次规划的特征间隙=本次已认证的末点),可整块省掉特征 ~13ms
- 测试 32→34 项
This commit is contained in:
cbb
2026-10-20 20:53:43 +08:00
parent 48cc566c3f
commit 34019c1a34
10 changed files with 1545 additions and 21 deletions
+80
View File
@@ -620,5 +620,85 @@ class SpeedAndMarginTest(unittest.TestCase):
self.assertTrue((per['plans']['gaps'] > 0.5).all().item())
class FeatureGeometryReuseTest(unittest.TestCase):
"""#16 第①条:gm_groups 复用调用者已算好的 cum_xf,省一次 FK,但结果必须与原口径逐位一致。"""
def test_gm_groups_with_supplied_xf_is_bitwise_identical(self):
from ml.common.features import features, GP, GAP_CAP
kin = Kin()
q = torch.tensor([[-129.9, 46.68, 43.49, -97.46, -85.1, -139.7],
[10.0, -20.0, 30.0, -40.0, 50.0, -60.0]])
xf = kin.cum_xf(q)
torch.testing.assert_close(kin.gm_groups(q, GP, GAP_CAP, xf=xf), kin.gm_groups(q, GP, GAP_CAP),
rtol=0, atol=0)
idx = [a * 7 + b for a, b in GP]
table = kin.gaps(q, GAP_CAP, xf)['gm'][:, idx]
torch.testing.assert_close(kin.gm_groups(q, GP, GAP_CAP), table, rtol=0, atol=0)
z = torch.zeros(len(q), 6)
f = features(kin, q, z, q[:, :3], torch.zeros(len(q), 9), z[:, None, :].expand(len(q), 3, 6),
torch.zeros(len(q)), z, torch.zeros(len(q)))
cols = 12 + 3 + 6 + 3 + 6 + 3 + 6 + 3 + 3 + 2 + 18 + 3 + 6 + 1 # 间隙块之前的特征数 = 75
self.assertEqual(cols + 8, 83)
torch.testing.assert_close(f[:, cols:cols + 8], table / GAP_CAP, rtol=0, atol=0)
class CertBatchEquivalenceTest(unittest.TestCase):
"""#16 第②条:把 K 条候选的认证并成一次 gaps 调用,选择结果必须与串行循环逐位一致。"""
class StubModel:
act_scale = 1.0
def __init__(self, act):
self.act = act
def sample_k(self, feat, K, z, steps=1):
B = feat.shape[0]
return (self.act.expand(B, K, 16, 6).clone(), torch.zeros(B, device=feat.device),
torch.zeros(B, 13, device=feat.device))
def run_both(self, acts, reject_first):
class StepKin:
def clamp_box(self, q):
return q
def gaps(self, q, cap):
bad = (q[:, 0] > 0.5) if reject_first else torch.zeros(len(q), dtype=torch.bool, device=q.device)
return {'gm': torch.where(bad[:, None], -torch.ones(len(q), 49, device=q.device),
torch.full((len(q), 49), 1.0, device=q.device))}
acts = torch.tensor(acts, dtype=torch.float32) # [K,16,6] 动作(±1 归一化)
model = self.StubModel(acts)
tasks = [{'q0': [0] * 6, 'T': [10, 0, 0]}] * 2 # B=2:批量索引形状必须在多行下也成立
out = []
for batched in (0, 1):
with patch('ml.sim.closed_loop.features', return_value=torch.zeros(len(tasks), 83)), \
patch('ml.sim.closed_loop.goal_err',
side_effect=lambda kin, q, T, R: (torch.full((q.shape[0],), 10.0), torch.zeros(q.shape[0]))):
_, per = rollout(model, StepKin(), tasks, 'cpu', K=acts.shape[0], safety=True, max_sec=0.04,
return_traj=True, use_sub=False, select='first', replan='empty', commit_n=2,
safety_partial=1, cert_batch=batched)
out.append(per)
return out
def test_batched_certification_selects_the_same_candidate(self):
# 候选 0 第 2 步撞上 q0>0.5 的障碍 ⇒ 只有前缀 1 步;候选 1 全程安全 ⇒ 应被选中
acts = [[[0.2, 0, 0, 0, 0, 0], [0.5, 0, 0, 0, 0, 0]] + [[0, 0, 0, 0, 0, 0]] * 14,
[[0.1, 0, 0, 0, 0, 0], [0.1, 0, 0, 0, 0, 0]] + [[0, 0, 0, 0, 0, 0]] * 14]
seq, batched = self.run_both(acts, reject_first=True)
torch.testing.assert_close(batched['traj'], seq['traj'], rtol=0, atol=0)
torch.testing.assert_close(batched['plans']['commit'], seq['plans']['commit'], rtol=0, atol=0)
torch.testing.assert_close(batched['plans']['certified'], seq['plans']['certified'], rtol=0, atol=0)
self.assertEqual(batched['brakes'].tolist(), seq['brakes'].tolist())
self.assertAlmostEqual(seq['traj'][0, -1, 0].item(), 0.24, places=5) # 候选 1 的两步 0.1×STEP=0.12°
def test_batched_certification_keeps_partial_prefix_choice(self):
# 所有候选都只走得了第 1 步 ⇒ 两条路径都取第 1 条候选、前缀长度 1
acts = [[[0.2, 0, 0, 0, 0, 0], [0.5, 0, 0, 0, 0, 0]] + [[0, 0, 0, 0, 0, 0]] * 14,
[[0.3, 0, 0, 0, 0, 0], [0.6, 0, 0, 0, 0, 0]] + [[0, 0, 0, 0, 0, 0]] * 14]
seq, batched = self.run_both(acts, reject_first=True)
torch.testing.assert_close(batched['traj'], seq['traj'], rtol=0, atol=0)
torch.testing.assert_close(batched['plans']['commit'], seq['plans']['commit'], rtol=0, atol=0)
self.assertAlmostEqual(seq['traj'][0, 1, 0].item(), 0.24, places=5)
self.assertAlmostEqual(seq['traj'][0, 2, 0].item(), 0.24, places=5) # 第 2 步被拒后停在原地
if __name__ == '__main__':
unittest.main()