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
@@ -0,0 +1,71 @@
{
"config": {
"ckpt": "ml/runs/base_dag2_dag1/best.pt",
"samples": "ml/data/samples",
"tasks": null,
"split": "val",
"n": 256,
"K": 8,
"commit": 2,
"fb": 2.2,
"ki": 0.0,
"fb_scale": 0.0,
"fb_near": 0.2,
"fb_fallback": 0.0,
"kdecay": 0.9,
"kimax": 0.5,
"fb_every": 1,
"safety_try": 0,
"sel_w": 0.1,
"dls_safety": 1,
"fb_clear": 0.0,
"fb_cmin": 0.3,
"sub_refresh": 0,
"fb_block_gain": 0.5,
"dump": null,
"out": "docs/experiment_round34_cert_batch_check.json",
"trace": null,
"select": "endpoint",
"replan": "empty",
"safety": 1,
"partial": 1,
"inc_cap": 0,
"gap_margin": 0.0,
"cert_batch": 1,
"max_sec": null,
"seed": 0,
"dls": "analytic",
"device": "cuda"
},
"batch_size": 256,
"batch_seed_rule": "same seed per batch",
"checkpoint_sha256": "f7f686fd8ee92fb6948fbcc4ee954636065ad87bb54f661c4f8f575bc16c2ce1",
"tasks_sha256": "249bd93457825cda98c59e64d9a096d3473c69aeb3553de1dea6d9e56be71772",
"kin_fp": "2ebcbf17",
"act_scale": 1.0,
"metrics": {
"n": 256,
"n_ref": 256,
"success": 0.57421875,
"collision_free_success": 0.57421875,
"time_ok_1p3": 0.390625,
"collision_rate": 0.0,
"collision_steps_mean": 0.0,
"travel_ratio_median": 2.4449527263641357,
"time_mean_s": 2.3076188564300537,
"brakes_mean": 19.25,
"start_fail": 0.0,
"goal_ratio_2s": 0.20997506380081177,
"err_fail_med": 229.68746948242188,
"err_success_med": 0.18899640440940857,
"dls_share": 0.625,
"time_budget": "teacher×2(≥3 s)",
"speed_ratio_p50": 1.6600004434585571,
"speed_ratio_p95": 1.6600035429000854,
"speed_ratio_max": 1.660003662109375,
"over_vmax_rate": 1.0,
"accel_ratio_p95": 1.5835107564926147,
"accel_ratio_max": 1.6600004434585571
},
"simulation_wall_s": 306.9490996999957
}
+629
View File
@@ -0,0 +1,629 @@
{
"device": "NVIDIA GeForce RTX 3090",
"B": 1,
"K": 8,
"steps": 1,
"reps": 90,
"design": "\u540c\u4e00\u6279 4 \u4e2a\u53d8\u4f53\u9010\u8f6e\u4ea4\u66ff\uff08\u5947\u6570\u8f6e\u53cd\u5e8f\uff09\uff0c\u6d88\u9664 GPU \u65f6\u949f\u6f02\u79fb\uff1b\u539f\u59cb\u8017\u65f6\u5168\u90e8\u843d\u76d8",
"equivalence": {
"features_reuse_bitwise_equal": true,
"gm_groups_vs_full_table_max_abs_mm": 0.0,
"cert_matrix_bitwise_equal_at_same_cap": true,
"shared_xf_cert_bitwise_equal_at_cap1": true,
"max_abs_shared_diff_mm": 0.0,
"max_abs_cert_diff_mm": 0.0,
"decision_equal_by_threshold": {
"thr_0.0": true,
"thr_0.5": true,
"thr_1.0": true
},
"per_point_min_cur_mm": [
8.833421,
8.849185,
8.859405,
8.83829
],
"per_point_min_fused_mm": [
8.833421,
8.849185,
8.859405,
8.83829
]
},
"results": {
"current_discrete": {
"p50": 32.932,
"p95": 42.589,
"p99": 55.568,
"max": 55.568,
"hz_p50": 30.4,
"raw_ms": [
30.458,
30.624,
30.789,
31.17,
31.229,
31.283,
31.371,
31.394,
31.454,
31.473,
31.564,
31.58,
31.719,
31.772,
31.78,
31.785,
31.859,
31.875,
31.906,
31.973,
31.979,
32.002,
32.017,
32.063,
32.099,
32.113,
32.123,
32.127,
32.13,
32.137,
32.191,
32.286,
32.308,
32.324,
32.33,
32.388,
32.48,
32.508,
32.564,
32.63,
32.64,
32.702,
32.78,
32.822,
32.834,
32.932,
32.964,
32.975,
32.996,
33.071,
33.163,
33.191,
33.192,
33.402,
33.477,
33.509,
33.583,
33.956,
34.014,
34.04,
34.047,
34.047,
34.154,
34.302,
34.595,
34.658,
34.943,
35.296,
35.522,
35.67,
35.757,
35.828,
35.859,
36.459,
36.503,
36.562,
36.956,
37.047,
37.067,
37.142,
37.442,
38.632,
39.285,
41.269,
41.932,
42.589,
43.199,
43.329,
54.407,
55.568
]
},
"fused_discrete": {
"p50": 24.515,
"p95": 30.02,
"p99": 42.085,
"max": 42.085,
"hz_p50": 40.8,
"raw_ms": [
22.146,
22.266,
22.409,
22.653,
22.66,
22.695,
22.76,
22.827,
22.848,
23.001,
23.033,
23.087,
23.249,
23.36,
23.488,
23.573,
23.607,
23.625,
23.649,
23.651,
23.652,
23.652,
23.656,
23.676,
23.716,
23.737,
23.745,
23.795,
23.871,
23.883,
23.92,
23.922,
23.945,
23.959,
24.046,
24.13,
24.184,
24.282,
24.291,
24.313,
24.318,
24.319,
24.32,
24.34,
24.399,
24.515,
24.532,
24.541,
24.552,
24.592,
24.618,
24.731,
24.741,
24.745,
24.766,
24.807,
24.813,
24.898,
25.066,
25.088,
25.137,
25.18,
25.223,
25.376,
25.474,
25.603,
25.68,
25.744,
25.82,
25.936,
25.95,
25.953,
26.002,
26.174,
26.312,
26.88,
27.036,
27.209,
27.329,
27.424,
27.715,
28.796,
28.824,
29.427,
29.662,
30.02,
31.515,
31.839,
36.18,
42.085
]
},
"shared_xf_discrete": {
"p50": 26.864,
"p95": 34.544,
"p99": 67.243,
"max": 67.243,
"hz_p50": 37.2,
"raw_ms": [
24.433,
24.441,
24.585,
24.777,
24.862,
24.935,
25.054,
25.08,
25.105,
25.193,
25.313,
25.44,
25.466,
25.483,
25.516,
25.554,
25.577,
25.689,
25.702,
25.708,
25.854,
25.896,
25.898,
26.011,
26.097,
26.175,
26.255,
26.289,
26.297,
26.306,
26.328,
26.336,
26.346,
26.375,
26.397,
26.456,
26.502,
26.523,
26.563,
26.613,
26.634,
26.658,
26.708,
26.752,
26.798,
26.864,
26.869,
26.874,
27.315,
27.341,
27.668,
27.67,
27.732,
27.774,
27.812,
27.84,
27.882,
27.921,
28.24,
28.302,
28.421,
28.694,
28.722,
28.739,
28.817,
28.914,
28.944,
28.998,
29.12,
29.122,
29.259,
29.292,
29.334,
29.658,
29.847,
30.181,
30.206,
30.292,
30.492,
30.782,
31.168,
31.222,
31.607,
31.614,
32.187,
34.544,
35.274,
35.565,
35.915,
67.243
]
},
"current_cert_n8": {
"p50": 42.624,
"p95": 49.434,
"p99": 57.771,
"max": 57.771,
"hz_p50": 23.5,
"raw_ms": [
38.546,
38.666,
39.152,
39.265,
39.344,
39.441,
39.584,
39.626,
39.738,
39.841,
40.228,
40.306,
40.514,
40.805,
40.838,
40.875,
40.884,
40.907,
40.952,
40.971,
41.185,
41.193,
41.193,
41.277,
41.279,
41.422,
41.448,
41.463,
41.465,
41.473,
41.504,
41.551,
41.684,
41.733,
41.841,
41.947,
41.967,
42.011,
42.077,
42.241,
42.249,
42.269,
42.301,
42.314,
42.388,
42.624,
42.661,
42.767,
42.773,
42.837,
42.865,
42.87,
42.913,
43.343,
43.35,
43.356,
43.376,
43.501,
43.695,
43.708,
43.842,
43.923,
43.972,
44.243,
44.476,
44.536,
44.648,
44.745,
44.894,
45.034,
45.144,
45.236,
45.697,
45.859,
45.944,
46.039,
46.126,
46.467,
46.575,
46.887,
46.983,
47.234,
47.621,
47.685,
48.547,
49.434,
49.683,
50.646,
54.754,
57.771
]
},
"fused_cert_n8": {
"p50": 24.826,
"p95": 31.721,
"p99": 49.398,
"max": 49.398,
"hz_p50": 40.3,
"raw_ms": [
22.127,
22.553,
22.598,
22.624,
22.789,
22.798,
22.878,
22.983,
23.002,
23.134,
23.192,
23.273,
23.323,
23.345,
23.377,
23.445,
23.466,
23.549,
23.611,
23.613,
23.861,
23.868,
23.885,
23.885,
23.906,
23.918,
23.919,
23.933,
23.947,
24.052,
24.064,
24.067,
24.08,
24.103,
24.13,
24.164,
24.249,
24.301,
24.429,
24.515,
24.603,
24.609,
24.7,
24.748,
24.812,
24.826,
24.855,
24.858,
24.954,
24.99,
25.052,
25.097,
25.195,
25.209,
25.241,
25.252,
25.264,
25.298,
25.63,
25.659,
25.767,
25.804,
25.928,
25.976,
26.494,
26.871,
26.886,
26.988,
26.995,
27.004,
27.307,
27.322,
27.352,
27.937,
28.144,
28.165,
28.216,
28.288,
28.331,
29.37,
29.611,
30.6,
30.704,
31.026,
31.552,
31.721,
32.875,
34.001,
40.223,
49.398
]
},
"shared_xf_cert_n8": {
"p50": 36.185,
"p95": 41.136,
"p99": 64.287,
"max": 64.287,
"hz_p50": 27.6,
"raw_ms": [
33.051,
33.153,
33.201,
33.406,
33.819,
33.845,
33.94,
34.083,
34.145,
34.235,
34.3,
34.372,
34.605,
34.697,
34.714,
34.755,
34.817,
34.855,
34.925,
34.957,
34.963,
34.967,
34.986,
35.167,
35.239,
35.314,
35.325,
35.402,
35.444,
35.518,
35.544,
35.582,
35.594,
35.662,
35.675,
35.724,
35.775,
35.874,
35.908,
35.929,
35.965,
36.019,
36.04,
36.126,
36.164,
36.185,
36.2,
36.259,
36.263,
36.266,
36.301,
36.324,
36.383,
36.393,
36.609,
36.667,
36.696,
36.779,
36.799,
36.836,
36.881,
36.978,
37.001,
37.033,
37.043,
37.078,
37.183,
37.206,
37.278,
37.3,
37.411,
37.485,
37.722,
37.923,
37.965,
38.197,
38.588,
38.871,
39.082,
39.099,
39.135,
39.351,
39.505,
40.204,
40.892,
41.136,
41.449,
42.611,
44.687,
64.287
]
}
}
}
+630
View File
@@ -0,0 +1,630 @@
{
"device": "NVIDIA GeForce RTX 3090",
"B": 1,
"K": 8,
"commit_n": 2,
"reps": 80,
"design": "5 \u4e2a\u53d8\u4f53\u9010\u8f6e\u4ea4\u66ff\uff08\u5947\u6570\u8f6e\u53cd\u5e8f\uff09\uff1b\u53ea\u6d4b\u53ef\u5b9e\u73b0\u7684\u53e3\u5f84\uff1a\u7279\u5f81/\u63a8\u7406/\u8ba4\u8bc1\u6309\u771f\u5b9e\u987a\u5e8f",
"equivalence": {
"batch_cert_matrix_equal_to_loop": true,
"features_xf_reuse_bitwise_equal": true
},
"results": {
"A_today": {
"p50": 33.494,
"p95": 46.143,
"p99": 64.693,
"max": 64.693,
"raw_ms": [
28.984,
29.251,
29.361,
29.549,
29.899,
29.944,
30.181,
30.605,
30.624,
30.744,
30.815,
30.838,
30.862,
30.9,
31.024,
31.092,
31.115,
31.262,
31.433,
31.463,
31.52,
31.572,
31.642,
31.741,
31.871,
31.902,
31.924,
31.933,
31.976,
32.001,
32.03,
32.06,
32.285,
32.384,
32.624,
32.825,
32.919,
33.063,
33.168,
33.197,
33.494,
33.589,
33.624,
33.632,
33.666,
33.708,
33.727,
33.735,
33.754,
33.773,
33.869,
33.907,
34.116,
34.219,
34.314,
34.337,
34.897,
34.957,
35.436,
35.469,
35.999,
36.106,
36.523,
36.559,
36.846,
36.866,
36.922,
37.354,
37.375,
37.581,
37.933,
38.026,
40.541,
40.882,
44.161,
44.233,
46.143,
54.047,
58.36,
64.693
]
},
"B_xf_reuse": {
"p50": 30.122,
"p95": 42.349,
"p99": 66.368,
"max": 66.368,
"raw_ms": [
26.698,
26.958,
26.984,
27.007,
27.018,
27.158,
27.308,
27.369,
27.987,
28.066,
28.219,
28.288,
28.362,
28.517,
28.537,
28.539,
28.593,
28.611,
28.638,
28.735,
28.828,
28.871,
28.922,
28.945,
28.978,
29.088,
29.202,
29.226,
29.226,
29.31,
29.497,
29.506,
29.531,
29.549,
29.649,
29.875,
29.978,
29.992,
30.025,
30.102,
30.122,
30.243,
30.245,
30.263,
30.407,
30.578,
30.665,
30.717,
30.863,
30.884,
30.889,
30.95,
31.023,
31.314,
31.525,
31.537,
31.543,
31.549,
31.62,
32.011,
32.098,
32.168,
32.188,
32.22,
32.578,
32.818,
32.851,
32.929,
32.991,
33.76,
33.805,
33.831,
33.94,
34.124,
34.891,
39.001,
42.349,
43.636,
62.045,
66.368
]
},
"C_batch_cert": {
"p50": 38.048,
"p95": 50.171,
"p99": 59.86,
"max": 59.86,
"raw_ms": [
33.625,
33.736,
33.848,
34.235,
34.49,
34.917,
34.918,
35.108,
35.407,
35.919,
36.023,
36.035,
36.087,
36.263,
36.318,
36.531,
36.627,
36.638,
36.722,
36.778,
36.791,
36.801,
36.816,
36.859,
36.97,
36.998,
37.029,
37.055,
37.057,
37.093,
37.208,
37.318,
37.408,
37.438,
37.463,
37.501,
37.503,
37.605,
37.63,
37.984,
38.048,
38.283,
38.347,
38.524,
38.547,
38.667,
38.816,
38.995,
39.028,
39.34,
39.617,
39.679,
39.845,
39.852,
39.871,
39.894,
39.923,
40.195,
40.514,
41.421,
41.684,
41.804,
41.879,
41.933,
42.011,
42.127,
42.483,
42.551,
42.656,
43.038,
43.248,
43.269,
45.68,
46.514,
46.755,
48.909,
50.171,
50.906,
55.33,
59.86
]
},
"D_both": {
"p50": 34.87,
"p95": 39.811,
"p99": 48.258,
"max": 48.258,
"raw_ms": [
30.044,
30.934,
31.005,
31.007,
31.064,
31.492,
31.675,
31.758,
32.114,
32.153,
32.338,
32.374,
32.442,
32.494,
32.681,
32.882,
32.942,
32.991,
33.167,
33.18,
33.302,
33.305,
33.342,
33.449,
33.58,
33.694,
33.739,
33.784,
33.832,
33.835,
33.866,
33.961,
34.037,
34.135,
34.315,
34.395,
34.465,
34.556,
34.656,
34.819,
34.87,
34.98,
35.047,
35.176,
35.19,
35.37,
35.461,
35.528,
35.557,
35.59,
35.643,
35.716,
35.742,
35.804,
35.991,
36.018,
36.063,
36.105,
36.293,
36.312,
36.326,
36.537,
36.541,
36.602,
36.717,
36.934,
36.945,
37.088,
37.141,
37.46,
37.561,
37.608,
37.765,
37.904,
37.974,
39.253,
39.811,
40.076,
46.504,
48.258
]
},
"E_both_cont_n8": {
"p50": 43.725,
"p95": 51.084,
"p99": 65.681,
"max": 65.681,
"raw_ms": [
37.733,
39.087,
39.154,
39.238,
39.36,
39.454,
39.514,
40.167,
40.44,
40.837,
40.86,
40.868,
41.237,
41.276,
41.346,
41.408,
41.551,
41.815,
41.824,
41.912,
41.932,
41.973,
42.074,
42.296,
42.341,
42.365,
42.435,
42.505,
42.592,
42.598,
42.637,
42.85,
43.045,
43.048,
43.18,
43.209,
43.359,
43.361,
43.373,
43.411,
43.725,
43.728,
43.787,
43.833,
43.985,
44.133,
44.185,
44.401,
44.458,
44.585,
44.689,
44.819,
44.841,
44.86,
45.081,
45.087,
45.55,
45.701,
45.939,
46.06,
46.128,
46.264,
46.368,
46.43,
46.957,
47.145,
47.231,
47.256,
47.463,
47.637,
47.819,
47.863,
48.382,
48.87,
49.676,
50.884,
51.084,
51.17,
53.859,
65.681
]
},
"F_worst_loop8": {
"p50": 80.224,
"p95": 88.436,
"p99": 89.553,
"max": 89.553,
"raw_ms": [
67.204,
70.008,
70.436,
71.432,
72.454,
72.503,
73.006,
73.12,
73.492,
73.971,
74.203,
74.358,
74.756,
74.816,
74.893,
75.536,
75.98,
76.049,
76.164,
76.25,
76.547,
76.669,
76.843,
77.268,
77.534,
77.658,
77.872,
78.149,
78.343,
78.377,
78.445,
78.483,
78.86,
78.863,
78.905,
79.048,
79.315,
79.857,
80.035,
80.041,
80.224,
80.602,
80.644,
80.677,
80.848,
81.061,
81.094,
81.155,
81.243,
81.384,
81.628,
81.679,
81.75,
82.117,
82.148,
82.363,
82.444,
82.511,
82.651,
82.828,
82.921,
83.371,
83.592,
83.894,
83.926,
84.386,
84.519,
84.608,
85.06,
85.078,
85.094,
85.26,
87.212,
87.744,
87.847,
88.098,
88.436,
88.461,
88.919,
89.553
]
},
"G_worst_batch": {
"p50": 37.139,
"p95": 46.06,
"p99": 52.215,
"max": 52.215,
"raw_ms": [
32.718,
33.447,
33.569,
33.692,
33.81,
34.236,
34.28,
34.475,
34.496,
34.746,
35.19,
35.392,
35.435,
35.564,
35.569,
35.669,
35.693,
35.734,
35.741,
35.812,
35.828,
35.934,
36.015,
36.036,
36.097,
36.144,
36.197,
36.205,
36.487,
36.637,
36.71,
36.775,
36.79,
36.83,
36.831,
36.885,
36.894,
36.903,
36.947,
37.122,
37.139,
37.154,
37.198,
37.225,
37.249,
37.478,
37.615,
37.617,
38.114,
38.164,
38.288,
38.637,
38.651,
38.724,
38.813,
39.108,
39.15,
39.848,
39.866,
39.946,
39.969,
39.992,
40.04,
40.28,
40.35,
40.697,
40.727,
40.893,
40.972,
41.029,
41.138,
41.479,
41.51,
41.711,
45.036,
45.784,
46.06,
48.597,
50.186,
52.215
]
}
}
}
@@ -0,0 +1,70 @@
{
"config": {
"ckpt": "ml/runs/base_dag2_dag1/best.pt",
"samples": "ml/data/samples",
"tasks": null,
"split": "val",
"n": 256,
"K": 8,
"commit": 2,
"fb": 2.2,
"ki": 0.0,
"fb_scale": 0.0,
"fb_near": 0.2,
"fb_fallback": 0.0,
"kdecay": 0.9,
"kimax": 0.5,
"fb_every": 1,
"safety_try": 0,
"sel_w": 0.1,
"dls_safety": 1,
"fb_clear": 0.0,
"fb_cmin": 0.3,
"sub_refresh": 0,
"fb_block_gain": 0.5,
"dump": null,
"out": "docs/experiment_round34_xf_reuse_check.json",
"trace": null,
"select": "endpoint",
"replan": "empty",
"safety": 1,
"partial": 1,
"inc_cap": 0,
"gap_margin": 0.0,
"max_sec": null,
"seed": 0,
"dls": "analytic",
"device": "cuda"
},
"batch_size": 256,
"batch_seed_rule": "same seed per batch",
"checkpoint_sha256": "f7f686fd8ee92fb6948fbcc4ee954636065ad87bb54f661c4f8f575bc16c2ce1",
"tasks_sha256": "249bd93457825cda98c59e64d9a096d3473c69aeb3553de1dea6d9e56be71772",
"kin_fp": "2ebcbf17",
"act_scale": 1.0,
"metrics": {
"n": 256,
"n_ref": 256,
"success": 0.57421875,
"collision_free_success": 0.57421875,
"time_ok_1p3": 0.390625,
"collision_rate": 0.0,
"collision_steps_mean": 0.0,
"travel_ratio_median": 2.4449527263641357,
"time_mean_s": 2.3076188564300537,
"brakes_mean": 19.25,
"start_fail": 0.0,
"goal_ratio_2s": 0.20997506380081177,
"err_fail_med": 229.68746948242188,
"err_success_med": 0.18899640440940857,
"dls_share": 0.625,
"time_budget": "teacher×2(≥3 s)",
"speed_ratio_p50": 1.6600004434585571,
"speed_ratio_p95": 1.6600035429000854,
"speed_ratio_max": 1.660003662109375,
"over_vmax_rate": 1.0,
"accel_ratio_p95": 1.5835107564926147,
"accel_ratio_max": 1.6600004434585571
},
"simulation_wall_s": 317.96790459999465
}
+27
View File
@@ -694,6 +694,33 @@
- **50 Hz 结论(当前状态)**:真实配方一个规划周期 **36.7 ms(P99 49.2)≈ 27 Hz**,仍不满足"P99<20 ms";且必须记住这是在**不限幅**(可超速 1.66×)的策略分布下测的——限幅后的到达率代价见上表,两者不能混着谈。 - **50 Hz 结论(当前状态)**:真实配方一个规划周期 **36.7 ms(P99 49.2)≈ 27 Hz**,仍不满足"P99<20 ms";且必须记住这是在**不限幅**(可超速 1.66×)的策略分布下测的——限幅后的到达率代价见上表,两者不能混着谈。
- f32 与 f64 的间隙在 3 个点上完全相同(最大偏差 0.0 mm、无符号翻转),但 **3 个点不足以支持"认证可以改用 float32"**,需按真实轨迹批量对比才能下结论。 - f32 与 f64 的间隙在 3 个点上完全相同(最大偏差 0.0 mm、无符号翻转),但 **3 个点不足以支持"认证可以改用 float32"**,需按真实轨迹批量对比才能下结论。
## 轮次 34:GPU 周期的可实现优化——复用 FK 与压认证最坏情况(exp: `exp/dagger-deploy`)
- **起因与一次自我撤回**:轮次 33 记下"合并几何查询看能不能进 20 ms"后,先按 `docs/experiment_round34_fused_cycle.json` 测出"整周期 41.9→24.5 ms"。这个数**作废**,两个独立原因:
1. 第一版的"合并"把 `xf=` 传给了包含 5 行的 `gaps` 调用,而 `gaps` 的 `xf` 是按行对应的 → 只有第 0 行走完宽相位,认证点 4 行全部退回截顶值 40.0 mm,即**少干了活**(表现为认证矩阵差 31.17 mm)。修好后同一 cap 下矩阵逐位一致(差 0.0 mm)。
2. 更根本:真实管线顺序是 特征 → 推理 → 候选 → 认证,**认证点依赖网络输出**,所以"特征用的 q 间隙与认证点间隙合成同一次调用"在实现上做不到。该文件保留作记录,但不能当作可实现收益。
- **只测可实现的口径**(`docs/experiment_round34_geom_opt.json`;B=1、K=8、commit 2;7 个变体逐轮交替、奇数轮反序,80 轮;等价性两项都 `True`:批量认证矩阵与逐条循环逐位一致、features 复用 xf 前后逐位一致):
| 变体 | 含义 | P50 | P95 | P99 |
|---|---|---|---|---|
| A_today | 现口径 | 33.49 | 46.14 | 64.69 |
| B_xf_reuse | `gm_groups` 复用 features 已算的 `cum_xf` | **30.12** | 42.35 | 66.37 |
| C_batch_cert | K 条候选的认证点并成 1 次 gaps | 38.05 | 50.17 | 59.86 |
| D_both | B + C | 34.87 | 39.81 | 48.26 |
| E_both_cont_n8 | B + C + 连续认证 n=8 | 43.73 | 51.08 | 65.68 |
| F_worst_loop8 | **最坏情况**:所有候选被拒(今天逐条串行 K 次调用) | 80.22 | 88.44 | 89.55 |
| G_worst_batch | 同一最坏情况但并成 1 次调用 | **37.14** | 46.06 | 52.22 |
- **复用 FK 是无条件收益**:省一次 `cum_xf`(约 3.4–3.7 ms),特征输出逐位一致,已直接实现进 `ml/common/kin_torch.py` 的 `gm_groups(..., xf=)` 与 `ml/common/features.py`(默认行为不变,新参数仅在调用方已算过 xf 时传)。
- **批量认证是"典型换尾部"**:常见路径(第一条候选就通过)贵 4.6 ms;但今天一旦前面的候选被拒就要串行调用,实测最坏 **80.2 ms(12.5 Hz)→ 37.1 ms**,P99 64.7→48.3 ms(D vs A)。硬实时看的是最坏情况,所以这条值得做,但要按"典型略变慢、尾部大幅变稳"来汇报,不能只报 P50。
- **仍未到 20 ms**:即便 D 档 P50 34.9 ms、P99 48.3 ms。连续认证挂在 D 上是 43.7 ms(E)。所以 GPU 侧下一步只能是更结构性的:认证降 float32(需先批量量化 f32/f64 判据差异,不能凭 3 个点下结论)、把宽相位做成可增量复用的核、或 `torch.compile`/CUDA Graph 把碎 kernel 合批。
- **等价性闸门(纯效率改动不许改变任何结果)**:
- 复用 FK:同一 256 题、同 seed、同配方重跑(`docs/experiment_round34_xf_reuse_check.json`),**22 个指标逐项与轮次 33 base 完全相同**(含 success 0.57421875、time_ok 0.390625、brakes 19.25、speed_ratio_max 1.660003662 等)。整批墙钟 296.3 → 318.0 s,是两次独立运行的差,**不能当作开销结论**:省下的那次 `cum_xf` 只在 B=1 交错实测里量到 −3.4 ms,批量评估里一次调用已覆盖 256 行,摊薄后不足以抵消运行间波动。
- 批量认证(`--cert_batch 1`,默认 0 不变):同一 256 题重跑 **22 项指标与 base 逐项相同**(`docs/experiment_round34_cert_batch_check.json`,success 0.57421875、time_ok 0.390625、speed_ratio_max 1.660003662 等全部一致),整批墙钟 296.3 → 306.9 s(批量口径把 8 次串行调用换成 1 次 K×256 点的大调用,均值略涨符合预期,与 B=1 交错实测方向一致)。合成夹具里也断言两条路径选到同一条候选、同一前缀长度、逐位相同的轨迹与证书。
- **测试 32→34 项**:`CertBatchEquivalenceTest` 两条(候选 0 前缀不完整 → 选候选 1;所有候选第 2 步都不安全 → 两条路径都停在第 1 步)。写夹具时踩到一个自己的坑:桩里掩码写成 `q[:, :1]` 再 `[:, None]` 变成 3 维,广播出 (4,4,49) 才报"size 16",是**测试桩错**不是库错——先证形状再改代码。
## 分支清理(2026-10-20)
## 分支清理(2026-10-20) ## 分支清理(2026-10-20)
- `feat/fb-mix`(8e5431c):反馈混合方案的中间分支,**已验证是 `exp/dagger-deploy` 的祖先**(所有提交都在后者历史里),本地/远端分支删除,内容保留在 exp/dagger-deploy。 - `feat/fb-mix`(8e5431c):反馈混合方案的中间分支,**已验证是 `exp/dagger-deploy` 的祖先**(所有提交都在后者历史里),本地/远端分支删除,内容保留在 exp/dagger-deploy。
+1 -1
View File
@@ -52,7 +52,7 @@ def features(kin, q, q_prev, T, R, prefix, prefix_len, sub, sub_flag, gm=None, x
out.append(sd) out.append(sd)
out.append(sub_flag[:, None].to(dt)) out.append(sub_flag[:, None].to(dt))
if gm is None: if gm is None:
out.append(kin.gm_groups(q, GP, GAP_CAP) / GAP_CAP) # 2026-10-20:只算特征用到的 8 组 out.append(kin.gm_groups(q, GP, GAP_CAP, xf=xf) / GAP_CAP) # 2026-10-20:只算特征用到的 8 组;xf 复用不重复算 FK
gm = None gm = None
if gm is not None: if gm is not None:
out.append(torch.stack([gm[:, a * 7 + b].clamp(max=GAP_CAP) / GAP_CAP for a, b in GP], -1)) out.append(torch.stack([gm[:, a * 7 + b].clamp(max=GAP_CAP) / GAP_CAP for a, b in GP], -1))
+4 -3
View File
@@ -235,9 +235,10 @@ class Kin:
if not pairs: return {'gm': gm.view(B, 49)} 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 gm_groups(self, q, gp, cap=40.0): def gm_groups(self, q, gp, cap=40.0, xf=None):
"""只算指定组对 gp=[(a,b),...] 的最小间隙 [B,len(gp)](特征用,避免 49 组全量)。 """只算指定组对 gp=[(a,b),...] 的最小间隙 [B,len(gp)](特征用,避免 49 组全量)。
数值与 gaps(q,cap)['gm'][:, a*7+b] 一致;组对在碰撞粗对里不存在时返回 cap。""" 数值与 gaps(q,cap)['gm'][:, a*7+b] 一致;组对在碰撞粗对里不存在时返回 cap。
xf 可传入调用者已算好的 cum_xf(q),避免同一姿态重复算 FK(特征路径每周期省一次)。"""
key = tuple(gp) key = tuple(gp)
if getattr(self, '_gp_key', None) != key: if getattr(self, '_gp_key', None) != key:
m = torch.full((49,), -1, dtype=torch.long, device=q.device) m = torch.full((49,), -1, dtype=torch.long, device=q.device)
@@ -247,7 +248,7 @@ class Kin:
sel = torch.isin(cg, torch.tensor([min(a, b) * 7 + max(a, b) for a, b in gp], device=q.device)) sel = torch.isin(cg, torch.tensor([min(a, b) * 7 + max(a, b) for a, b in gp], device=q.device))
self._gp = (m, sel, sel.nonzero(as_tuple=True)[0]); self._gp_key = key self._gp = (m, sel, sel.nonzero(as_tuple=True)[0]); self._gp_key = key
self._gp49, sel, idx = self._gp self._gp49, sel, idx = self._gp
xf = self.cum_xf(q); Rs, Ts = xf; B = q.shape[0] xf = xf if xf is not None else self.cum_xf(q); Rs, Ts = xf; B = q.shape[0]
with torch.no_grad(): with torch.no_grad():
cc = torch.einsum('bkij,kj->bki', Rs[:, self.cl_g], self.cl_c.to(q.dtype)) + Ts[:, self.cl_g] cc = torch.einsum('bkij,kj->bki', Rs[:, self.cl_g], self.cl_c.to(q.dtype)) + Ts[:, self.cl_g]
d2 = (cc[:, self.cp_a[idx]] - cc[:, self.cp_b[idx]]).pow(2).sum(-1) d2 = (cc[:, self.cp_a[idx]] - cc[:, self.cp_b[idx]]).pow(2).sum(-1)
+2 -1
View File
@@ -92,6 +92,7 @@ def main():
ap.add_argument('--partial', type=int, default=1) ap.add_argument('--partial', type=int, default=1)
ap.add_argument('--inc_cap', type=int, default=0, help='1=反馈叠加后再按每轴 VMAX·DT 限幅') ap.add_argument('--inc_cap', type=int, default=0, help='1=反馈叠加后再按每轴 VMAX·DT 限幅')
ap.add_argument('--gap_margin', type=float, default=0.0, help='安全层要求的间隙余量 mm(>0 时宽相位 cap 自动抬到 margin+0.5)') ap.add_argument('--gap_margin', type=float, default=0.0, help='安全层要求的间隙余量 mm(>0 时宽相位 cap 自动抬到 margin+0.5)')
ap.add_argument('--cert_batch', type=int, default=0, help='1=一次 gaps 调用覆盖全部候选(压最坏情况,典型路径略贵)')
ap.add_argument('--max_sec', type=float, default=None) ap.add_argument('--max_sec', type=float, default=None)
ap.add_argument('--seed', type=int, default=0) ap.add_argument('--seed', type=int, default=0)
ap.add_argument('--dls', default='analytic', choices=['analytic', 'fd']) ap.add_argument('--dls', default='analytic', choices=['analytic', 'fd'])
@@ -118,7 +119,7 @@ def main():
safety_partial=(a.commit if (a.safety and a.partial) else 0), commit_n=a.commit, safety_partial=(a.commit if (a.safety and a.partial) else 0), commit_n=a.commit,
select=a.select, replan=a.replan, fb_gain=a.fb, fb_scale=a.fb_scale, fb_near=a.fb_near, fb_fallback=a.fb_fallback, fb_ki=a.ki, fb_decay=a.kdecay, fb_imax=a.kimax, select=a.select, replan=a.replan, fb_gain=a.fb, fb_scale=a.fb_scale, fb_near=a.fb_near, fb_fallback=a.fb_fallback, fb_ki=a.ki, fb_decay=a.kdecay, fb_imax=a.kimax,
fb_every=a.fb_every, safety_try=a.safety_try, sel_w=a.sel_w, dls_safety=a.dls_safety, fb_clear=a.fb_clear, fb_cmin=a.fb_cmin, sub_refresh=a.sub_refresh, fb_block_gain=a.fb_block_gain, fb_every=a.fb_every, safety_try=a.safety_try, sel_w=a.sel_w, dls_safety=a.dls_safety, fb_clear=a.fb_clear, fb_cmin=a.fb_cmin, sub_refresh=a.sub_refresh, fb_block_gain=a.fb_block_gain,
inc_cap=a.inc_cap, gap_margin=a.gap_margin, return_traj=bool(a.trace)) inc_cap=a.inc_cap, gap_margin=a.gap_margin, cert_batch=a.cert_batch, return_traj=bool(a.trace))
wall = time.perf_counter() - t0 wall = time.perf_counter() - t0
result = {**meta, 'metrics': r, 'simulation_wall_s': wall} result = {**meta, 'metrics': r, 'simulation_wall_s': wall}
print(json.dumps(json_finite(result), ensure_ascii=False, allow_nan=False), flush=True) print(json.dumps(json_finite(result), ensure_ascii=False, allow_nan=False), flush=True)
+16 -1
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', 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_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, 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, progress=None): inc_cap=0, gap_margin=0.0, cert_batch=0, progress=None):
"""use_sub=False:不用子目标(sf=0);sub_override=(subs [B,M,6] 绝对关节角, n [B] 个数 或 need [B] bool):冷启动时用给定途经点代替子目标头; """use_sub=False:不用子目标(sf=0);sub_override=(subs [B,M,6] 绝对关节角, n [B] 个数 或 need [B] bool):冷启动时用给定途经点代替子目标头;
qp0 [B,6]:起点的上一帧(默认 = q0,即静止起步)。steps:MeanFlow 采样段数;z_scale:噪声缩放; qp0 [B,6]:起点的上一帧(默认 = q0,即静止起步)。steps:MeanFlow 采样段数;z_scale:噪声缩放;
select:consist(部署口径:冷启动价值前瞻、之后与上一条已提交计划的一致性)/ value(每次都价值前瞻)/ first(不选,取第 0 条)。 select:consist(部署口径:冷启动价值前瞻、之后与上一条已提交计划的一致性)/ value(每次都价值前瞻)/ first(不选,取第 0 条)。
@@ -329,6 +329,21 @@ def rollout(model, kin, tasks, device, K=8, safety=False, seed=0, max_sec=None,
maxlen = torch.zeros(B, dtype=torch.long, device=device) maxlen = torch.zeros(B, dtype=torch.long, device=device)
chosen = torch.zeros(B, commit_n, 6, device=device); chosen_k = pick.clone() chosen = torch.zeros(B, commit_n, 6, device=device); chosen_k = pick.clone()
ntry = min(K_eff, safety_try) if safety_try > 0 else K_eff ntry = min(K_eff, safety_try) if safety_try > 0 else K_eff
if cert_batch: # 一次 gaps 覆盖全部候选:典型路径略贵,但把「前面候选被拒→串行调用 K 次」的最坏情况封成 1 次
cks = inc[ar[:, None], order[:, :ntry], r:r + commit_n].reshape(B * ntry, commit_n, 6)
pts, mids = execution_points(kin, qe.repeat_interleave(ntry, 0), cks)
gaps6 = kin.gaps(torch.cat([pts, mids], 1).reshape(-1, 6), gcap)['gm'].min(-1).values.view(B, ntry, 2 * commit_n)
plen = torch.cumprod(((gaps6 > gap_margin)[:, :, :commit_n] &
(gaps6 > gap_margin)[:, :, commit_n:]).long(), -1).sum(-1)
maxlen = plen.max(-1).values
best = plen.argmax(-1) # 与串行同口径:严格更大者胜 ⇒ 首个最大值
chosen = cks.view(B, ntry, commit_n, 6)[ar, best]
chosen_k = order[ar, best]
if return_traj:
cert_pts = pts.view(B, ntry, commit_n, 6)[ar, best]
cert_mids = mids.view(B, ntry, commit_n, 6)[ar, best]
cert_gaps = gaps6[ar, best]
else:
for kk in range(ntry): for kk in range(ntry):
ck = inc[ar, order[:, kk], r:r + commit_n] ck = inc[ar, order[:, kk], r:r + commit_n]
pts, mids = execution_points(kin, qe, ck) pts, mids = execution_points(kin, qe, ck)
+80
View File
@@ -620,5 +620,85 @@ class SpeedAndMarginTest(unittest.TestCase):
self.assertTrue((per['plans']['gaps'] > 0.5).all().item()) 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__': if __name__ == '__main__':
unittest.main() unittest.main()