episode 运行时
三层加一个安全层(shield):模型编写程序(高层),程序调用有界 skill(低层),guard 和 控制器在每个控制 tick 运行(反应层),一个固定的安全层在每条命令到达机器人之前对其进行检查。
| 进程 | 由谁启动 | 做什么 |
|---|---|---|
| Worker | harness run(或 harness serve) |
benchmark 的一个任务:reset、tick(经过安全层的一个控制步)、state、observe、fk / ik / plan、结果 |
| 服务 | harness up,或首次使用时由 broker 启动 |
skill 调用的模型(服务与 deploy 文件) |
| Driver | harness run / harness program |
模型循环、trace、预算 |
| Kernel | driver,每个 episode 一个 | 在 skill 的命名空间中运行模型的程序 |
Driver:模型何时被调用
Section titled “Driver:模型何时被调用”src/harness_policy/runtime/driver.py。每次模型调用都是一个决策点,在 summary.json
中按原因统计(decisions):
| 原因 | 何时 |
|---|---|
start |
第一次调用:指令、任务描述(如果配置使用它们)、机器人状态、相机图像 |
escalation |
程序因一个其中没有任何部分处理的事件而停止 |
error |
程序抛出了异常 |
program_end |
程序未调用 finish() 就结束了 |
verify |
模型向成功验证器发出询问(<verify>) |
format |
上一条回复没有有效的块 |
action |
直接模式(R0/R1):每个动作之后 |
一条回复在可选的思考之后恰好包含一个块:<python>(一个程序)、<verify>(交给验证器的
一个断言)或 <finish>(完成);在直接模式下为 <action>{JSON}</action> 或 <finish>。
没有有效块的回复会消耗一次调用,并得到 “Format error … Nothing ran” 的答复;一旦收到
有效回复,这些错误的往来就会从历史中移除。
一个程序 cell 结束后,模型得到:cell 的状态和输出、使其停止的事件、最近的执行、已启用
skill 的每轮报告(按物体 id 调用的工具:重新测量的场景,为下一个程序打上标签,附上每只持物
手臂的抓握状态)、机器人状态和已用的环境步数、程序用 show() 附上的图像以及最新的相机视图
(在每轮报告描出轮廓的地方描出轮廓),以及剩余的调用次数。使用学习型策略时,
start(strategy, kind) 是第一个动作:在它被调用之前,每个移动手臂的执行都以 SKILL_ERROR
结束(runtime.strategy_first),summary.json 把这一选择记录为 strategy
(分工)。
策略运行之后。 用 start("A" or "C") 交出的任务运行完之后,模型还有
post_policy_calls 次调用(agent 中为 4 次;每一轮都会说明还剩多少次)。用完之后,driver
运行 kernel 的 __policy_to_end__() 钩子(事件 policy_to_end):策略从当前状态继续运行
到 episode 结束,不会因静止而停止。如果此后 episode 仍在运行,driver 会结束它(finish,
source: "driver")并执行收尾。
预算与状态。 每个 episode:max_model_calls(agent 的预算:30)、
max_wall_seconds(在每次模型调用之前检查;正在运行的 cell 受 cell_timeout_seconds
限制),以及可选的 max_cost_usd。一个 episode 的结束状态为 finished(<finish> 或
finish())、episode_ended(benchmark 结束了它:成功或达到步数上限)、
budget_exhausted、wall_time_exhausted、cost_exhausted、interrupted 或 error。
历史。 文本历史全部保留;图像只保留最近 keep_images 轮(更早的变为
“[earlier image omitted]”);max_history_turns 保留第一条消息加最近 N 次往来,用于
上下文较小的模型。
验证器。 当 runtime.verifier 开启时(默认),<verify> 和程序中的 verify() 会
带着一次新的观测,询问验证器角色(除非另行指定,否则为主模型)该断言是否成立:YES/NO 加
一句话。
收尾与结果。 finish 之后,若 embodiment 要求(RoboDojo 要求),driver 执行收尾:
每个没有持物的夹爪张开,然后两只手臂回到起始姿态。RoboDojo 的 42 个任务中有 23 个只有在
每个夹爪至少张开 0.8 时才计分,而且大多数只有在手臂回到原位时才算成功;持物的夹爪保持闭合,
因为抬举类任务要求物体仍在空中才计分。结果通过 driver 在 reset 时领取的一个 token 读取;
程序和模型都看不到它。
Kernel:程序在哪里运行
Section titled “Kernel:程序在哪里运行”src/harness_policy/runtime/kernel.py。每个 episode 一个 Python 进程;变量从一个程序
(cell)保留到下一个。一个 cell 的结束状态为 ok、escalated(附带该事件的 trace)、
episode_ended、finished 或 error(traceback 只保留程序自己的帧)。输出截断为
8000 个字符。每个 cell 之后,kernel 会恢复被程序重新绑定的任何 API 名称(def pick(...)),
并在输出中说明。
程序在隔离中运行:它们只能看到 Python 安装和 harness 源码(只读)以及一个全新的工作区, 只能访问其选项中的服务,并且不能启动进程。参见隔离程序。
Session:作为有界执行的 skill
Section titled “Session:作为有界执行的 skill”session 类是针对某一种 embodiment 类型特化的 episode 核心
(src/harness_policy/runtime/episode.py),再加上该类型所有 skill 的 mixin
(skills/<name>/);已启用的 skill 决定程序能看到哪些名称。episode 核心持有与 embodiment
无关的部分:worker 连接、模型客户端和学习型策略、handler、反应层以及有界执行。一种
embodiment 类型的 session 规定:一步的命令是什么以及它如何到达 worker,保持一切不变的命令,
guard 观察什么,命令何时离开执行的范围,以及它自己执行哪些 guard 动作。机器人的 session 是
RobotSession(runtime/session.py):一条命令是两只手臂的关节向量,范围是某只手臂的
link6 必须停留的区域,guard 还可以张开夹爪。benchmark 决定类型(RoboDojo 和 RoboTwin 驱动
机器人),worker 在其 profile 中报告类型(kind,默认为 robot),如果一次运行的 worker
驱动的是另一种类型,运行会报错停止。每个动作都是一个有界执行:它逐 tick 运行,直到完成、
某个谓词或 guard 触发、离开其范围或超过 max_seconds,并以一个事件和一条执行 trace
结束。
| 事件 | 含义 |
|---|---|
COMPLETED、PREDICATE_SATISFIED |
正常:执行完成了要求的事(until 成立) |
TIMEOUT |
超过了 max_seconds |
BLOCKED |
手臂在到达目标前停下(稳定后偏差超过 1 cm 或 0.05 rad) |
GUARD_TRIGGERED(或 guard 自己的事件名) |
反应层的一个 guard 触发了 |
REGION_EXIT |
执行离开了其范围(机器人手臂离开了其区域) |
SHIELD_REJECTED |
安全层拒绝了一条命令(附带原因) |
REACTIVE_FAULT |
guard 或控制器失败,或超出了其时间预算 |
SKILL_ERROR |
skill 无法完成要求的事 |
EPISODE_ENDED |
benchmark 结束了 episode |
非正常事件会交给程序用 on(event, handler) 为它注册的 handler。如果没有 handler,或
handler 返回 False,事件就会上报:cell 停止,模型带着 trace 被调用(escalation)。
finish(summary) 在程序内部结束 episode。
guard 和控制器在每个控制 tick(RoboDojo 上为 25 Hz)运行,无需询问模型:
when(predicate, action)— guard:当谓词成立时,执行stop、hold、open_gripper,或抛出以该名称命名的事件。watch(signal, low, high)— 针对数值信号离开某个区间的 guard。clear_guards(names=None)。run_controller(fn, until, max_seconds, region, hz, cameras, camera_hz)—fn以hz的频率把观测映射为关节、末端执行器(left_eef/right_eef,由 IK 求解)或夹爪目标; 使用cameras时,可获得用于视觉伺服的最新图像和标定。
反应代码每次调用都有一个时间预算(runtime.reactive_budget_ms;agent 中为 20 ms),且不得
进行 I/O、休眠或调用模型;违反即为 REACTIVE_FAULT。
src/harness_policy/runtime/shield.py 在 worker 中运行,程序无法触及,它检查每个 tick 的
每条命令。它拒绝属于 bug 的命令,此外不做任何改动:它从不对命令进行截断、缩短或减速。
| 检查 | 拒绝 |
|---|---|
| 形状、有限值 | 格式错误的命令 |
| 关节限位 | 目标超出限位的程度大于关节当前已超出的程度(外加 1e-3 rad 容差) |
| 关节跳变 | 手臂关节相对于上一条被接受的命令和测量状态都跳变超过 max_joint_jump_rad |
| 工作空间 | 末端执行器在工作空间盒之外的程度大于其当前已在外的程度 |
| 笛卡尔跳变 | 末端执行器相对于上一条命令和测量位姿都跳变超过 max_cartesian_jump_m |
被拒绝的命令不会执行;执行以 SHIELD_REJECTED 和原因结束,于是程序得知它的命令是错的。
RoboDojo 的限值在 embodiment 中(embodiments/arx_x5):0.3 rad、0.25 m 以及工作空间盒。