任务失败时,改提示词、换数据集通常是成本最高、信息量最低的两个动作。更省时间的顺序是:先把同一次失败任务的指令、画面、本体状态、输出动作对齐到同一条时间轴,再在输入侧确认模态是否齐全、时间戳是否错位,然后在输出侧确认动作数值是否合法,最后用同一份输入跑两次,看分叉落在哪一段。这条顺序不预设模型能力不行,只依赖日志、配置和可重复的推理入口。
判断方向:先归因到“输入没喂进去或喂晚了”,再归因到“动作数值不合法或抖动”,最后才讨论模型本身。适用场景是保留了完整日志的单次失败任务;操作动作是四路时间戳对齐、输入侧模态清单、输出侧越界检查、同输入重跑对照;验证方式是越界帧位置和重跑分叉区段能被他人复现;边界是时钟源不统一或采样未固定的任务,重跑对照的结论不成立,需要先固定随机性再判断。
把一次失败任务的状态、输入画面、指令和输出动作按时间戳对齐
对齐的目的是让后续讨论有共同对象。建议选一个时间基准:优先用机器人控制器的单调时钟(如推理服务里记录的控制 tick 或 time.monotonic()),图像采集和控制日志都换算到它上面。容差通常取一个控制周期或一帧间隔,具体数值需要结合实际的采集频率和控制频率确认,不要凭感觉设。凡是换算后仍落不进容差的片段,先单独标成“无法对齐”,不要混入后面的判断。
| 时间戳(统一到控制时钟) | 画面帧 | 指令 | 本体状态 | 输出动作 | 备注 |
|---|---|---|---|---|---|
| t0 | frame_id / 有无丢帧 | 指令文本或指令 ID | 关节角度 / 夹爪宽度 | 动作块起止帧 | — |
| t1 | frame_id | 同上 | 同上 | 逐帧动作 | 越界或抖动标记 |
| t2 | 时间戳缺失 | — | — | — | 无法对齐,单独归档 |
常见无法对齐的片段有三类:日志里压根没有时间戳,图像管线用墙钟而控制用单调时钟,以及采集进程重启导致的时钟跳变。这三类都要在表里明确标注,否则后面的越界检查和重跑对照会被污染。
在输入侧检查模态是否齐全、时间戳是否错位
这一步只排除低级原因:数据根本没送到,或者送到的是上一帧。检查项按模态拆开写:图像是否每帧都有、指令字段是否为空或只含空白、本体状态的维度是否与训练配置一致、每一路的时间戳相对控制时钟的偏移是否稳定。
- 图像:帧数、分辨率、通道顺序、是否有整帧重复
- 指令:非空、长度、是否被截断
- 本体状态:维度、单位、是否用了过期值
- 时间戳:每路的偏移均值与抖动范围
看日志时可以先用一个通用关键字过滤,字段名按实际日志替换:
# 用途:快速看出哪一路模态缺失或延迟
# 关键字需替换成项目里真实的字段名
grep -nE 'modality|missing|drop|stale|empty|ts_delta' run.log | head -50
如果日志不便加字段,也可以在推理入口做一次轻量断言,把缺失和延迟直接暴露出来:
# 通用接入骨架,字段名与方法名按实际 SDK 替换
def check_inputs(obs, now):
assert obs.get('image') is not None, 'image missing'
assert obs.get('instruction', '').strip(), 'instruction empty'
assert len(obs.get('state', [])) == EXPECTED_STATE_DIM, 'state dim mismatch'
for name, ts in obs.get('ts', {}).items():
if abs(now - ts) > MAX_SKEW:
print('stale or lagging modality:', name, now - ts)
在输出侧检查动作序列是否越界或抖动
输入齐全不代表输出合法。允许的数值范围要有明确来源,通常来自三处:机器人 URDF 或控制器里的关节限位,夹爪等执行器的硬件行程规格,以及模型训练时使用的动作归一化配置。这三处不一致时,以控制器限位为准,归一化范围只用来判断是否发生了缩放错配。
检查时记录越界位置和对应帧,而不是只报一个“有越界”:
# 记录越界帧号、通道与数值,便于回到对齐表定位
for i, a in enumerate(action_seq):
for j, v in enumerate(a):
if v < low[j] or v > high[j]:
print('out_of_range', 'frame=', i, 'joint=', j,
'value=', v, 'limit=', (low[j], high[j]))
抖动则看相邻帧的差分是否远超正常运动速度,或同一关节在几帧内来回换向。差分阈值同样要结合控制周期和关节速度上限确认,不能直接套用别的本体。越界帧如果是连续一段,更可能是归一化或单位问题;如果是零星几帧,更可能是数值噪声或推理输入被污染。
用同一份输入重复跑两次
重跑前先固定随机性:把采样温度设为零或等价配置,固定随机种子,关闭会引入随机的数据增强。没有这一步,两次分叉无法区分是模型随机性还是环境问题。
把两次输出并排对齐,按区段标注一致或分叉:
# 同输入重跑对照,输出按帧列出
diff -u run_a.action run_b.action
# 或按区段统计:哪里一致、哪里开始分叉
python compare_runs.py `--a` run_a.npz `--b` run_b.npz `--start-frame` 0
如果两次在开头一段一致、中段开始分叉,重点看分叉点之前的输入是否已经变化,常见原因是本体状态或图像帧在那几帧被替换。如果两次从头就不一致,先确认随机性是否真的固定,再考虑实现侧的不确定性。
按段位给失败贴标签并留档
标签只用三类,避免把问题描述成一团:感知、动作、接口。感知指输入模态缺失、错位、内容错误;动作指越界、抖动、单位或归一化错配;接口指调用失败、超时、字段名不一致、版本不匹配。每条标签都要附上证据文件路径,下一次复现可以直接比对,而不是从零开始猜。
task_id: demo-001
label: 感知
frame_range: 12-15
evidence:
- logs/infer_run.log # 含 stale modality 行
- traces/align_table.csv # 无法对齐的片段
action_on_next_run: 固定图像管线时钟源后重跑同一输入
留档时把对齐表、输入侧检查输出、越界记录、两次重跑的动作文件放在同一目录,标签写在该目录的说明文件里。这样下一次遇到相似失败,先看标签再决定是修输入、修动作映射还是修接口,不必重新改提示词试一轮。