# 策略部署（lerobot-rollout）

`lerobot-rollout` 是用于在真实机器人上部署已训练策略的统一命令行工具。它支持多种执行策略和推理后端，涵盖从快速评估到持续录制以及人在回路数据采集的多种场景。

## 快速入门

除了机器人和策略的额外依赖项之外，无需其他额外依赖项。

```bash
lerobot-rollout \
    --strategy.type=base \
    --policy.path=lerobot/act_koch_real \
    --robot.type=koch_follower \
    --robot.port=/dev/ttyACM0 \
    --task="pick up cube" \
    --duration=30
```

这会在不录制任何内容的情况下运行策略 30 秒。

### 选择设备

在目标主机上，如果该设备可用，请传入 `--device=cpu` 或 `--device=cuda` 以覆盖检查点设备。解析后的设备会在加载策略并构建其处理器之前应用于策略配置。例如，如果策略支持 CPU 执行，可以在上述命令中添加 `--device=cpu`，以便在仅配备 CPU 的主机上运行使用 CUDA 训练的检查点。在部署到机器人之前，请确认 CPU 推理能够满足所需的控制频率。

当省略 `--device` 时，rollout 会使用策略配置中指定的设备（如果已设置），否则会自动选择一个可用设备。`RolloutConfig(device=...)` 与 `build_rollout_context` 的行为与此相同。

---

## 策略

使用 `--strategy.type=<name>` 选择一种策略。每种策略都定义了不同的控制循环，并拥有各自的录制和交互语义。

### Base (`--strategy.type=base`)

自主策略执行，不记录数据。适用于快速评估、演示或仅需观察机器人的场景。

```bash
lerobot-rollout \
    --strategy.type=base \
    --policy.path=${HF_USER}/my_policy \
    --robot.type=so100_follower \
    --robot.port=/dev/ttyACM0 \
    --robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" \
    --task="Put lego brick into the box" \
    --duration=60
```

| 标志             | 描述                                            | 默认 |
| ---------------- | ------------------------------------------------------ | ------- |
| `--duration`     | 运行时间（秒）（0 = 无限）                     | 0       |
| `--task`         | 传递给策略的任务描述                  | --      |
| `--display_data` | 将观察/操作流式传输到 Rerun 以进行可视化 | false   |

### Sentry (`--strategy.type=sentry`)

持续自主录制，并定期上传至 Hugging Face Hub。剧集边界会根据相机分辨率和 FPS 自动计算，确保每个保存的剧集都能生成完整的视频文件，从而保持上传的高效性。

策略状态（隐藏状态、RTC 队列）在 episode 边界之间持续存在：机器人在 episode 之间不会重置。

```bash
lerobot-rollout \
    --strategy.type=sentry \
    --strategy.upload_every_n_episodes=5 \
    --policy.path=${HF_USER}/my_policy \
    --robot.type=so100_follower \
    --robot.port=/dev/ttyACM0 \
    --robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" \
    --dataset.repo_id=${HF_USER}/rollout_eval_data \
    --dataset.single_task="Put lego brick into the box" \
    --duration=3600
```

| 标志                                   | 描述                                           | 默认 |
| -------------------------------------- | ----------------------------------------------------- | ------- |
| `--strategy.upload_every_n_episodes`   | 每 N 个回合推送到 Hub                          | 5       |
| `--strategy.target_video_file_size_mb` | 用于剧集轮换的目标视频文件大小           | auto    |
| `--dataset.repo_id`                    | **必填。** 用于记录数据集的 Hub 仓库 | --      |
| `--dataset.push_to_hub`                | 是否在拆卸时推送到 Hub                    | true    |

### Highlight (`--strategy.type=highlight`)

通过内存受限的环形缓冲区实现自主展开与按需录制。该缓冲区会捕获最近 N 秒的遥测数据。按下保存键以刷新缓冲区并开始实时录制；再次按下以保存该回合。在使用异步后端时，每次切换都会暂停推理，使已排队或插值的运动失效，并在阻塞式排空或保存之前请求受支持的本地保持。运行正常时，随后将从新的捕获中恢复。

```bash
lerobot-rollout \
    --strategy.type=highlight \
    --strategy.ring_buffer_seconds=30 \
    --strategy.save_key=s \
    --strategy.push_key=h \
    --policy.path=${HF_USER}/my_policy \
    --robot.type=koch_follower \
    --robot.port=/dev/ttyACM0 \
    --dataset.repo_id=${HF_USER}/rollout_highlight_data \
    --dataset.single_task="Pick up the red cube"
```

**键盘控制：**

| 密钥                | 操作                                                   |
| ------------------ | -------------------------------------------------------- |
| `s`（可配置） | 开始录制（刷新缓冲区）/ 停止并保存剧集 |
| `h`（可配置） | 将数据集推送到 Hub                                      |
| `ESC`              | 停止会话                                         |

| 标志                                   | 描述                                    |
| -------------------------------------- | ---------------------------------------------- |
| `--strategy.ring_buffer_seconds`       | 缓冲遥测的持续时间（默认值：10）   |
| `--strategy.ring_buffer_max_memory_mb` | 环形缓冲区的内存上限（默认值：1024） |
| `--strategy.save_key`                  | 切换录制的按键（默认：`s`）         |
| `--strategy.push_key`                  | 用于推送到 Hub 的按键（默认：`h`）              |

### DAgger (`--strategy.type=dagger`)

Human-in-the-loop 数据收集。在自主策略执行与通过遥操作器进行的人工干预之间交替进行。干预帧会被标记为 `intervention=True`。需要配置遥操作器（`--teleop.type`）。

有关详细操作指南，请参阅[人机协同数据收集](./hil_data_collection)。

**仅修正模式**（默认）：仅记录人工修正窗口。每次修正构成一个 episode。

```bash
lerobot-rollout \
    --strategy.type=dagger \
    --strategy.num_episodes=20 \
    --policy.path=outputs/pretrain/checkpoints/last/pretrained_model \
    --robot.type=bi_openarm_follower \
    --teleop.type=bi_openarm_mini \
    --dataset.repo_id=${HF_USER}/rollout_hil_data \
    --dataset.single_task="Fold the T-shirt"
```

**连续录制模式**（`--strategy.record_autonomous=true`）：自主帧和校正帧均会进行录制，并采用基于时间的 episode 轮换（与 Sentry 相同）。

```bash
lerobot-rollout \
    --strategy.type=dagger \
    --strategy.record_autonomous=true \
    --strategy.num_episodes=50 \
    --policy.path=${HF_USER}/my_policy \
    --robot.type=so100_follower \
    --robot.port=/dev/ttyACM0 \
    --teleop.type=so101_leader \
    --teleop.port=/dev/ttyACM1 \
    --dataset.repo_id=${HF_USER}/rollout_dagger_data \
    --dataset.single_task="Grasp the block"
```

**键盘控制**（默认输入设备）：

| 密钥     | 操作                                      |
| ------- | ------------------------------------------- |
| `Space` | 暂停 / 恢复策略执行             |
| `Tab`   | 开始 / 停止人工校正               |
| `Enter` | 将数据集推送到 Hub（仅更正模式） |
| `ESC`   | 停止会话                            |

也支持通过 `--strategy.input_device=pedal` 使用脚踏板输入。使用 `--strategy.pedal.*` 标志配置踏板代码。

当机器人支持 rollout 位置保持时，暂停操作会刷新驱动程序实际应用的最后一个位置目标，包括其裁剪和夹爪目标。在第一条命令之前，经过验证的测量位姿将作为目标提供。这保留了命令位置误差，而不是将目标更改为测量位姿；在策略轨迹推进停止期间，机器人可以继续向该目标收敛。请在实际机器人上验证负载臂和夹爪的行为；此软件保持功能不保证保持抓握或物理静止。

| 标志                                 | 描述                                                                                                                                                    |
| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--strategy.num_episodes`            | 要记录的纠正回合数（默认值：`--dataset.num_episodes`）                                                                                    |
| `--strategy.record_autonomous`       | 同时记录自主帧（默认：false）                                                                                                                  |
| `--strategy.upload_every_n_episodes` | 每 N 个回合推送到 Hub（默认值：5）                                                                                                                      |
| `--strategy.input_device`            | 输入设备：`keyboard` 或 `pedal`（默认：keyboard）                                                                                                        |
| `--strategy.smooth_handover`         | 在暂停/校正开始时平滑地移交控制权（默认值：true）。对于在接合时以当前机器人位姿重新参考的离合器式遥操作，请禁用此功能 |
| `--teleop.type`                      | **必填。** 遥操作类型                                                                                                                                |

### Episodic (`--strategy.type=episodic`)

采用以回合（episode）为导向的录制方式，其行为与 `lerobot-record` 保持一致。在每个回合中，策略驱动机器人；在回合之间的重置阶段，可选的遥操作者可以驱动机器人。

```bash
lerobot-rollout \
    --strategy.type=episodic \
    --policy.path=${HF_USER}/my_policy \
    --robot.type=so100_follower \
    --robot.port=/dev/ttyACM0 \
    --teleop.type=so100_leader \
    --teleop.port=/dev/ttyACM1 \
    --dataset.repo_id=${HF_USER}/rollout_eval_data \
    --dataset.num_episodes=20 \
    --dataset.episode_time_s=30 \
    --dataset.reset_time_s=10 \
    --dataset.single_task="Pick up the red cube"
```

Teleop 是可选的——如果省略，机器人在重置阶段将保持当前位置。

**键盘控制：**

| 密钥         | 操作                           |
| ----------- | -------------------------------- |
| `→`（右） | 提前结束当前回合    |
| `←`（左）  | 丢弃该回合并重新录制 |
| `ESC`       | 停止录制会话       |

| 标志                                            | 描述                                                                                                                                                |
| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--dataset.num_episodes`                        | 要记录的剧集数量                                                                                                                               |
| `--dataset.episode_time_s`                      | 每个录制片段的持续时间（以秒为单位）。如果设置了`--duration`且未设置此项，则默认使用`--duration`的值。                                               |
| `--dataset.reset_time_s`                        | 剧集之间重置阶段的持续时间（以秒为单位）                                                                                                    |
| `--teleop.type`                                 | 可选。在重置期间由遥操作员控制机器人                                                                                                    |
| `--strategy.reset_to_initial_position`          | 是否在回合之间将机器人重置到其初始位置                                                                                        |
| `--strategy.smooth_leader_to_follower_handover` | 是否开启或关闭领导者 -> 跟随者的平滑交接行为。                                                                                 |
| `--strategy.smooth_handover`                    | 在重置开始时平滑地将控制权移交给遥操作（默认值：true）。对于在接合时以当前机器人位姿重新参考的离合器式遥操作，请禁用此功能 |

### 自带策略

`--strategy.type` 接受环境中注册的任何策略，而不仅仅是上述内置策略。一个策略是 pip 可安装包中的两个类：一个注册在 `RolloutStrategyConfig` 上的配置，以及一个以其名称去掉 `Config` 后缀命名的 `RolloutStrategy` 子类——与 [自定义硬件](./integrate_hardware#the-4-core-conventions) 相同的 `SomethingConfig`/`Something` 约定，从配置的模块或包中解析。

```python
from dataclasses import dataclass
from typing import ClassVar

from lerobot.rollout import RolloutContext, RolloutStrategy, RolloutStrategyConfig

@RolloutStrategyConfig.register_subclass("patrol")
@dataclass
class PatrolStrategyConfig(RolloutStrategyConfig):
    dataset_mode: ClassVar[str] = "required"  # the engine creates the dataset for you
    legs: int = 2  # becomes --strategy.legs

class PatrolStrategy(RolloutStrategy):
    def run(self, ctx: RolloutContext) -> None:
        ...  # the paced control loop: see the contract on RolloutStrategy.run

    def teardown(self, ctx: RolloutContext) -> None:
        ctx.data.dataset.finalize()
        self._teardown_hardware(ctx.hardware, ctx.runtime.cfg.return_to_initial_position)
```

配置声明了引擎代表策略所安排的内容，因此 LeRobot 中的任何部分都无需了解其类型：

| 声明                     | 效果                                                                                                                                     |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `dataset_mode`                  | `"none"`（默认值）会拒绝 `--dataset.*` 标志，`"optional"` 会在提供时记录它们，`"required"` 会使 `--dataset.repo_id` 成为必填项 |
| `requires_teleop`               | 使 `--teleop.type` 成为必填项                                                                                                            |
| `supports_interactive`          | 允许使用 `--interactive=true`；此时 `run()` 必须遵循 `RolloutStrategy` 中记录的可重启契约                             |
| `supports_sim_robot`            | 允许使用 `--robot.type=sim`；此时 `run()` 必须在每个回合结束时启动一个新的回合（参见 `lerobot.robots.sim_robot`）                        |
| `requires_streaming_encoding()` | 当帧从控制循环内部写入时返回 `True`：`--dataset.streaming_encoding` 将被强制启用                            |
| `extra_dataset_features()`      | 策略拥有的数据集列（例如 DAgger 的 `intervention`）；每个记录的帧都必须包含这些列                               |

将分发命名为 `lerobot_strategy_<name>`，`lerobot-rollout` 会在解析 CLI 之前导入该包——此时仅执行其 `__init__.py`，因此请在此处导入配置。任何其他名称的包（包括其子模块）都会通过 `--strategy.discover_packages_path=<package>` 加载。策略的数据类字段会成为 `--strategy.*` 标志：

```bash
lerobot-rollout \
    --strategy.type=patrol \
    --strategy.legs=3 \
    --policy.path=${HF_USER}/my_policy \
    --robot.type=so100_follower \
    --robot.port=/dev/ttyACM0 \
    --dataset.repo_id=${HF_USER}/rollout_patrol \
    --dataset.single_task="patrol the aisle"
```

`SentryStrategy`（支持录制，交互安全）和 `BaseStrategy`（不录制）是可供参考的实现；`lerobot.rollout.strategies` 中的 `send_next_action`、`safe_push_to_hub` 和 `estimate_max_episode_seconds` 是它们所依赖的辅助函数。

对于异步后端，分发契约是强制性的。使用 `engine.notify_observation(obs_processed)` 发布每个捕获的观测值，在 **每个自主电机节拍**（包括插值和保持节拍）上调用 `send_next_action(...)`，然后调用 `engine.pump_query(obs_processed)`。仅调用 `engine.get_action()` 会绕过权限和保持握手：计划中的文本查询可能会无限期等待，且终端故障可能永远不会发出关闭信号。

第三方策略可以调用 `self._tick(timer, new_cycle=interpolator.needs_new_action())` 来替代 `timer.tick(...)`，以便在 `_init_engine(ctx)` 之后包含定期健康报告。

`send_next_action` 基于 `lerobot.inference.dispatch_tick` 及其组成部分（`next_interpolated_action`、`commit_action`、`dispatch_permitted`）构建，这些部分一次性编码了该顺序：在拉取动作之前和之后，以及在发送之前调用 `dispatch_allowed()`；当被拒绝时，执行插值失效处理、支持的本地保持以及 `acknowledge_hold()`；在发送之后调用 `record_dispatch()`。失败的保持操作永远不会被确认。请使用这些辅助函数，而不是重新实现该序列；`send_next_action` 仅添加了数据集帧、机器人端的动作处理器以及节奏核算。远程上下文中，`ctx.policy.policy`、`preprocessor` 和 `postprocessor` 均设置为 `None`；请使用 `ctx.policy.inference` 以及 `ctx.processors` 中的机器人端流水线。

将运行中已知的阻塞性录制工作包裹在 `self._pause_for_recording(ctx)` 中，包括环形缓冲区排空和回合结束处理。这会在录制停顿前使异步运动失效，并且仅在运行仍然健康且处于活动状态时才恢复。具有操作员控制阶段的策略必须将其当前的运动权限作为 `resume_allowed` 传入。下一个动作必须来自新的观测；已保存的工作不得使已暂停、已停止或已发生故障的运行恢复。

---

## 可视化策略预测

使用 `--display_data` 时，查看器还会显示策略除动作之外的预测内容：世界模型想象出的相机帧、预测的子任务，以及绘制在相机画面上的边界框。每个策略自行决定其预测内容，并通过各自的配置将其启用，例如对于 LingBot-VA，使用 `--policy.return_predicted_video=true`。预测项以其预测的对象命名：Rerun 将预测的 `observation.images.top` 显示为 `prediction.images.top`，将预测的子任务显示为 `prediction.subtask`，并将边界框显示在其所属相机下（`<camera>/prediction`）；Foxglove 则在 `/prediction/...` 主题以及 `<camera topic>/prediction` 图像注释中发布这些内容。在使用 RTC 时，预测内容每个 chunk 刷新一次。请参阅 [Bring your own policy](./bring_your_own_policies) 以了解如何从策略返回预测结果。

## 推理后端

使用 `--inference.type=<name>` 选择后端。本地同步推理和 RTC 推理使用下方配置的策略。[远程后端](./remote_inference) 从服务器获取其策略契约，并且不会在机器人计算机上加载权重。

### Sync （默认）

每个控制周期调用一次策略。主循环会阻塞，直到动作计算完成。

适用于所有策略。无需额外标志。

### 实时分块（`--inference.type=rtc`）

后台线程会异步生成动作块。主控制循环轮询下一个已就绪的动作，同时策略并行计算下一个块。

使用 RTC 与兼容的 VLA 模型（Pi0、Pi0.5、SmolVLA）以重叠推理和运动。持续执行仍需足够的可用动作播放来覆盖推理延迟及其波动。

```bash
lerobot-rollout \
    --strategy.type=base \
    --inference.type=rtc \
    --inference.rtc.execution_horizon=10 \
    --inference.rtc.max_guidance_weight=10.0 \
    --policy.path=${HF_USER}/pi0_policy \
    --robot.type=so100_follower \
    --robot.port=/dev/ttyACM0 \
    --robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" \
    --task="Pick up the cube" \
    --duration=60 \
    --device=cuda
```

| 标志                                        | 描述                                                                                         |
| ------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `--inference.rtc.execution_horizon`         | 与上一个块进行混合的步骤（默认值：因策略而异）                                      |
| `--inference.rtc.mode`                      | `guided`（默认）或兼容 Pi05 检查点的训练前缀 `trained`                      |
| `--inference.rtc.max_guidance_weight`       | 一致性强制强度（默认值：因策略而异）                                        |
| `--inference.rtc.prefix_attention_schedule` | 混合计划：`LINEAR`、`EXP`、`ONES`、`ZEROS`                                                    |
| `--inference.queue_threshold`               | 背压阈值；训练 RTC 至少需要其最大延迟                             |
| `--inference.action_starvation_grace_s`     | 在支持的机器人上，等待饥饿状态后的新运动（默认值为 `1.0`；`0` 会立即触发故障） |

有关调整 RTC 参数的详细信息，请参阅 [Real-Time Chunking](./rtc) 指南。

本地 RTC 在 rollout 进程的后台线程中运行，不使用传输层。同一台计算机上的服务器和客户端应改用 `--inference.type=remote`。有关块对齐/混合以及异步后端共有的[等待行为](./remote_inference#what-the-robot-does-while-waiting-or-stopping)，请参阅[远程指南](./remote_inference)。

**等待功能需要受支持的机器人。**普通的本地 RTC 在没有该功能的情况下仍然可用，但计划中的文本/指令变更暂停以及可恢复的饥饿状态需要它。位置控制的 SO、OMX、Koch、HopeJr 和 Rebot 跟随器支持此功能；OpenArm、Reachy2 和双手包装器存在配置限制，详见[机器人支持表](./integrate_hardware#waiting-between-predictions)。在受支持的情况下，空缓冲区可以保留最后应用的目标，持续时间为 `action_starvation_grace_s`（默认 1 秒），包括排空旧推理并从新捕获进行预测所需的时间。在运动开始后，如果宽限期为零或机器人不受支持，耗尽时会立即发生故障；启动有其自身的截止时间。与以前的本地 RTC 路径不同，耗尽不再是一个无限期跳过的 tick。宽限期永远不会延长已过期的请求或新鲜度限制。

Local RTC 通过 `--inference.` 公开了这些额外的预算：

| 参数               | 默认 | 含义                                      |
| ----------------------- | ------- | -------------------------------------------- |
| `max_observation_age_s` | `5 s`   | 通过操作执行实现的最大源年龄  |
| `action_timeout_s`      | `10 s`  | 待处理的操作请求                   |
| `startup_timeout_s`     | `120 s` | 初始准备和新鲜操作启动 |
| `language_timeout_s`    | `120 s` | 计划中的语言操作                   |

这些是初始值，而非实测的硬件保证。RTC 必须保留足够的**修剪后播放**时间，以应对下一次推理及时间余量。如果推理通常会耗尽可用的预测时域，那么提高`queue_threshold`也无济于事；请降低延迟，或验证更长的时域/不同的动作频率。

终端故障绝不会自动恢复策略运动。在可用的观测获取和执行器控制下，关机操作会遵循 `--return_to_initial_position`（默认值为 true）并断开连接。返回过程需要读取完整的观测数据，因此持续的相机故障可能会阻止该过程。命令应用失败会抑制所有后端（包括同步后端）的进一步关机运动。这不会改变同步预测，也不会引入针对相机的特定恢复机制。

编译和指令更改

RTC 编译会预热初始调用和带前缀的调用（包括一个具有代表性的非零延迟），然后才执行运动。尽管 `--compile_warmup_inferences` 的默认值为 2，但启用 RTC 至少需要 3；启动日志会报告实际生效的数量。预热必须在 `startup_timeout_s` 内完成。后续更改张量形状的指令可能会触发重新编译，从而导致超出播放或操作截止时间。

推理期间的任务变更会丢弃过时的结果。受支持的机器人会暂停并请求新的动作；如果机器人不具备这种等待能力，剩余的动作可能会耗尽。对于这类机器人，建议在两次运行之间更改任务。

---

## 交互式会话

添加 `--interactive=true` 以从终端驱动 rollout，而不是立即开始。硬件连接和策略加载照常进行，但 **在您输入 `/start` 之前，机器人保持静止** — 当您想先布置场景、在多次尝试之间重新指示策略，或在不重复支付加载时间的情况下运行多次拍摄时，这非常有用。

```bash
lerobot-rollout \
    --strategy.type=base \
    --policy.path=${HF_USER}/my_smolvla_policy \
    --robot.type=so100_follower \
    --robot.port=/dev/ttyACM0 \
    --robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}}" \
    --task="pick up the cube" \
    --interactive=true
```

| 命令             | 操作                                                                                                                                                                                                                                                                                                                  |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `/start`            | 启动（或重启）策略控制循环                                                                                                                                                                                                                                                                              |
| `/subtask <text>`   | 更改当前运行中的指令；异步推理可能会进入计划中的暂停状态。不带参数时，将打印当前任务。仅影响基于语言进行条件判断的策略（SmolVLA、π0/π0.5 及类似策略）。当使用 [外部规划器](#external-planner) 时，`/subtask` 还会将控制权从 autosteer 交还 |
| `/vqa <text>`       | 询问机器人当前看到的内容。仅在运行进行中，且使用策略文本头或[外部规划器](#external-planner)时可用。                                                                                                                                                       |
| `/autosteer <goal>` | 提供一个高层目标，让策略的文本头部或[外部规划器](#external-planner)选择子任务。`/autosteer off`将控制权交还；不带参数则报告当前目标                                                                                                                  |
| `/reset`            | 停止移动，将机器人返回到启动位置，并恢复`--task`指令                                                                                                                                                                                                                           |
| `/stop`             | 结束会话并运行正常的关机例程                                                                                                                                                                                                                                                                    |
| `/help`             | 列出命令                                                                                                                                                                                                                                                                                                       |

```text
> /start
Rollout running — task 'pick up the cube'. /subtask <text> to change it, ...
> /subtask put the cube in the box
Task: 'pick up the cube' → 'put the cube in the box' (applies from the next policy inference)
> /vqa is the cube inside the box?
Asked: 'is the cube inside the box?' — answering from the next observation...
Q: is the cube inside the box?
A: No — the cube is on the table to the left of the box.
> /reset
Task restored to 'pick up the cube'
Resetting — returning the robot to its initial position...
Robot reset — holding at initial position. /start to run.
> /stop
```

以 `>` 开头的行是您输入的内容；该会话本身不会显示任何提示符。

`Ctrl-C` 仍会按常规方式关闭，而关闭标准输入（`Ctrl-D`，或管道脚本的末尾）会结束会话——因此，管道脚本必须保持标准输入在预期时长内处于打开状态：

```bash
(printf '/start\n'; sleep 60; printf '/stop\n') | lerobot-rollout ... --interactive=true
```

**`/subtask`** 用于更新后续推理的任务。在使用 `--inference.type=sync` 时，预计算的块动作会被丢弃。在本地 RTC 或远程推理中，如果在动作请求期间发生更改，过时的结果将被丢弃。对于支持本地保持（local hold）的机器人，引擎会使队列中/插值中的运动失效，等待控制循环确认保持状态，然后在新任务下请求新的运动。在此过渡期间预计会出现停止；这不保证是连续的混合。请求之间的更改可以保留已接受的动作以保持连续性。在不支持保持的机器人上使用本地 RTC 时，仍然会丢弃过时的结果，但可能会耗尽剩余的动作并终止。如果该机器人无法适当保持，请在运行之间更改指令。

在本地使用 `--use_torch_compile=true` 时，改变处理后张量形状的指令可能会触发重新编译。填充（Padding）可能使某些任务变更保持相同的形状，但后续的编译并不保证能适配剩余的播放或指令变更截止时间：这可能导致终端故障。除非已验证这些转换，否则在探索指令时应保持编译关闭。在初始编译预热期间的变更不会请求运动保持，因为策略运动尚未开始。

`/vqa` 和 `/autosteer` 需要一个声明了文本生成支持并实现了 `PreTrainedPolicy.generate_text()` 的策略，例如经过适当训练的 EO1 检查点，或一个 [外部规划器](#external-planner)。在接受查询之前会检查该能力；特定于检查点的行为仍需验证。

**`/vqa`** 会基于下一帧观测进行回答，并在就绪时打印结果，因此您可以在此期间继续输入；同一时间只有一个问题在处理中（autosteer 自身的查询也共享该槽位）。若使用外部规划器，问题将在工作线程上回答，而机器人继续执行动作，以下描述均不适用。异步后端首先进入已确认的本地保持状态，并使队列中或插值后的运动失效，然后将新的观测提交给文本路径。健康的活跃运行会在回答后从新的动作块恢复；暂停或停止的运行则保持非活跃状态。同步文本生成在控制线程上内联执行，并丢弃策略预计算的动作，因此下一个 tick 将从新的推理恢复。查询时间会计入 `--duration`。仅在运行进行中时才会接受问题——空闲引擎没有当前视图。如果指令或执行上下文使异步回答失效，问题将收到一条取消通知，而不是过时的回答。片段结束时也会报告未回答的问题，包括仍在生成中的问题；报告取消不会中断正在运行的模型调用，也不会提前释放其查询槽位。

**`/autosteer`** 向策略（policy）提供一个目标，并每隔 `--autosteer_interval_s` 秒（默认 10 秒）请求下一个子任务，通过与 `/subtask` 相同的路径应用每个子任务，并在聊天中宣布。每次查询都是一次完整的文本生成，因此该间隔是一个成本调节旋钮：更低的值会更快重新规划，但会花费更多循环时间用于生成文本而非执行动作。计划进度保存在策略中，而非 rollout 中，因此计划会随 segment 结束而终止；sequencer 也会在 `/reset`、`/subtask`、`/autosteer off` 以及首次规划失败时停止。

**会话运行期间，低于 ERROR 级别的日志会被静音**，以避免常规输出与您正在输入的内容交错显示；错误和致命的推理失败仍会显示，会话结束后恢复正常日志记录。[节奏摘要](#cadence-reporting) 会打印在聊天中，而不使用 `--interactive` 运行则会恢复实时日志。

会话通过 SSH 运行，也适用于无头机器——无需显示服务器。

**交互过程中录制。** `--strategy.type=sentry` 还支持 `--interactive=true`，在您操控时持续录制：每个 `/start`…`/reset` 片段会保存完整的 episode 以及一个最终的 partial episode，并且数据集在关闭前保持打开状态。帧携带实际发送动作的任务来源信息，因此已在旧任务下排队或插值的动作仍保留旧标签。

运行中途的阻塞式 episode 保存会暂停异步推理，使已排队或插值的动作失效，并请求受支持的本地保持。仅当运行仍处于活跃且健康状态时，才会从新的捕获中恢复。相同的保护机制也适用于 DAgger 保存以及 Highlight 环形缓冲区的排空/保存。录制暂停和普通饥饿共享相同的物理等待行为，但它们的截止时间以及恢复原因保持独立。

```bash
lerobot-rollout \
    --strategy.type=sentry \
    --policy.path=${HF_USER}/my_smolvla_policy \
    --robot.type=so100_follower \
    --robot.port=/dev/ttyACM0 \
    --dataset.repo_id=${HF_USER}/rollout_cube_sessions \
    --task="pick up the cube" \
    --interactive=true
```

其他录制策略（episodic、DAgger、highlight）不受支持：它们会绑定自己的键盘控制，并在 `run()` 退出时完成数据集，因此无法按片段重启。

### 外部规划器

`--planner.model_id=<name>` 使 `/vqa` 和 `/autosteer` 通过标注流水线的 VLM 客户端（`annotations` 附加组件）运行，而不是通过策略的文本头。调用在 worker 线程上执行，机器人在此期间继续行动，并且不会为这些调用请求语言保持，但有一个刻意的例外：当 `/autosteer` 启动时，推理后端会暂停，直到规划器返回第一个回复，因为目标是规划器的上下文，而不是给策略的指令。异步后端（`rtc`、`remote`）会丢弃已排队或正在为上一条指令计算的动作；同步后端则会保持机器人状态。一旦第一个子任务被应用，后端将从新的观测中恢复。 `/subtask` 仍然直接应用您的指令并取消自动规划；规划器晚到的回复无法覆盖它。

规划器适用于所有推理后端。在本地推理中，如果已加载的处理器存在子任务配方，则复用该配方；否则，请求一条简短的指令；但这并不能证明检查点能够遵循该指令。`--planner.instructions='["pick up the cup","open the drawer"]'` 会限制规划器的输出：VLM 被要求从这些字符串中选择一个，而任何不在列表中的下一子任务回复都会被视为错误。大小写、多余的空格和末尾的句号会被忽略，并且列表本身的措辞会发送给策略。如果不使用此标志，列表将从检查点训练数据集的任务标签中读取（通过其 `train_config.json`）。由于每个标签都会进入提示词，因此对于包含许多任务的数据集，请通过该标志传入一个更短的列表。如果无法读取数据集，则会记录一条警告，并且规划器的输出将保持自由格式。远程推理拥有策略，因此本地无法访问配方或训练词汇表：请显式传入 `--planner.instructions`，否则答案将保持自由格式。规划器仅在配备本地推理的硬件上经过测试。

外部规划器会接收最近 `--planner.history` 个观测/指令对（默认为 4 个），随后是当前图像。每个对包含用于请求指令的观测，以及引擎随后接受的指令；接受并不表示执行成功。重复的指令会保留在历史中，以便 VLM 评估是否缺乏进展。在手动接管、重置、停止、片段结束或新的 autosteer 目标时，历史会被清空。设置 `--planner.history=0` 可禁用此功能。`/vqa` 可以检查此历史，但不会添加条目。对于下一个子任务请求，VLM 必须先描述场景并说明上一条指令是已完成、进行中还是失败，然后才能给出下一条指令。`done` 的回复表示目标已完成：autosteer 停止，运行结束，机器人保持其姿态，直到执行 `/reset` 或 `/start`。在 `/subtask`、`/reset` 或新目标之前发出的迟到的 `done` 将被丢弃。每个应用的子任务都会打印出模型的场景评估和请求延迟。

单独启动一个 OpenAI 兼容的 VLM 服务器，然后进行连接：

```bash
transformers serve Qwen/Qwen3-VL-8B-Instruct --port 8000
# Alternatively: vllm serve Qwen/Qwen3-VL-8B-Instruct --port 8000
lerobot-rollout ... --task="pick up the cup" --interactive=true \
    --planner.model_id=Qwen/Qwen3-VL-8B-Instruct \
    --planner.api_base=http://localhost:8000/v1
> /start
> /vqa What is the next short instruction to clear the table?
> /subtask put the cup in the sink
> /autosteer clear the table
```

托管模型的工作方式相同。此示例使用 Hugging Face Inference Providers，并使用具有“调用 Inference Providers”权限的令牌：

```bash
export HF_TOKEN=hf_...
lerobot-rollout ... --task="put all colored cubes in the green bin" --interactive=true \
    --planner.model_id=Qwen/Qwen3.8-27B:novita \
    --planner.api_base=https://router.huggingface.co/v1 \
    --planner.api_key_env=HF_TOKEN
```

`/vqa` 仅报告答案；`/autosteer` 应用指令。使用 `--planner.api_base` 配置端点，使用 `--planner.api_key_env=<variable>`（推荐，密钥不会出现在命令和日志中）或 `--planner.api_key` 配置密钥。`--planner.request_timeout_s`（默认 600）和 `--planner.request_max_retries`（默认 2）限制每个请求；`--planner.camera_key` 选择一个摄像头；`--planner.log_path` 将每次交互追加为一行 JSON。模型服务依赖项应位于服务器环境中。`--autosteer_interval_s` 在应用回复后测量机器人运动时间，此外还包括请求延迟。多级转向和自动场景清单不在该适配器的范围内。

### 编程控制

`RolloutController` 暴露了与库 API 相同的操作——线程安全的 `start()` / `reset()` / `stop()` / `set_task()` 以及一个 `RolloutEvent` 回调，且不附带任何标准输入、打印或日志静默功能：

```python
from threading import Event, Thread

from lerobot.rollout import (
    LinkedEvent,
    RolloutController,
    RolloutEvent,
    build_rollout_context,
    create_strategy,
)

parent = Event()  # your application's shutdown signal
ctx = build_rollout_context(cfg, LinkedEvent(parent))  # loads policy, connects robot
strategy = create_strategy(cfg.strategy)
strategy.setup(ctx)

controller = RolloutController(strategy, ctx, on_event=print)  # or your own observer
serve_thread = Thread(target=controller.serve)  # serve() blocks; run it where you like
serve_thread.start()

controller.start()                        # robot starts executing the policy
controller.set_task("grab the red cube")  # re-instruct mid-run
controller.reset()                        # stop movement, return home, stay warm
controller.stop()                         # end serve()

serve_thread.join()
strategy.teardown(ctx)  # teardown stays with the caller
```

`RolloutController` 要求上下文的关闭事件是一个 `LinkedEvent`，构建方式如上所述：控制器通过其本地标志结束运行段，而您的 `parent` 事件仍会强制完全关闭。除非您希望听到语音播报，否则请设置 `play_sounds=False`。

---

## 常用标志

| 标志                              | 描述                                                       | 默认 |
| --------------------------------- | ----------------------------------------------------------------- | ------- |
| `--policy.path`                   | **必填。**HF Hub 模型 ID 或本地检查点路径            | --      |
| `--robot.type`                    | **必填。** 机器人类型（例如 `so100_follower`、`koch_follower`） | --      |
| `--robot.port`                    | 机器人的串行端口                                         | --      |
| `--robot.cameras`                 | 相机配置（JSON 字典）                                  | --      |
| `--fps`                           | 策略推理和数据集记录频率                  | 30      |
| `--status_interval_s`             | 健康日志之间的秒数（0 表示禁用；仅限非交互模式）    | 5       |
| `--duration`                      | 运行时间（秒）（0 = 无限；交互模式下按段计算）    | 0       |
| `--device`                        | Torch 设备（`cpu`、`cuda`、`mps`）                               | auto    |
| `--task`                          | 任务描述（在未提供数据集时使用）               | --      |
| `--display_data`                  | 将遥测数据流式传输到 Rerun 可视化                           | false   |
| `--display_ip` / `--display_port` | 远程 Rerun 服务器地址                                       | --      |
| `--interpolation_multiplier`      | 每个策略动作对应的机器人指令（以 `fps × N` Hz 的频率进行控制）        | 1       |
| `--interactive`                   | 在 stdin 上进行聊天会话；机器人在收到 `/start` 之前保持空闲（base, sentry）  | false   |
| `--use_torch_compile`             | 为推理启用 `torch.compile`                              | false   |
| `--resume`                        | 恢复之前的录制会话                               | false   |
| `--play_sounds`                   | 活动语音合成                                        | true    |

---

## Cadence 报告

非交互式 rollout 会每五秒记录一次其状态。使用 `--status_interval_s=10` 可更改该间隔，或使用 `--status_interval_s=0` 将其禁用。

```text
Rollout: loop 29.7/30 Hz · over budget 2/149 · starved 0 · infer 18.7 ms (worst 40.1) · process 2.41 GiB · GPU 5.12/79.6 GiB
```

延迟为区间内的中位数/最差值（RTC/远程为`turnaround`）。不可用的字段将被省略；GPU 内存为设备级，远程内存为本地。当循环阻塞时，报告将暂停。

每种策略都会报告其控制循环实际达成的结果，`lerobot-record`、`lerobot-teleoperate` 和 `lerobot-replay` 也是如此——它们都按照同一个计时器推进。每个保存的 episode 都会生成一行摘要，当循环结束时——包括在按下 `Ctrl-C` 时——会输出一个涵盖整个运行过程的代码块：

```
Cadence (episode 1): 29.84 Hz policy vs 30 Hz target · 1200 ticks, 20.1 s measured · 6/598 cycles over the 33.3 ms budget (work mean 18.7 ms, worst 48.1 ms)
Cadence (episode 2): 29.84 Hz policy vs 30 Hz target · 1200 ticks, 20.1 s measured · 6/599 cycles over the 33.3 ms budget (work mean 18.7 ms, worst 48.1 ms)
Cadence summary — whole run, 2 episodes · target 30 Hz × 2 (16.7 ms tick slot, 33.3 ms cycle budget): 2400 ticks, 1197 cycles judged
  effective cadence: 29.84 Hz policy / 59.67 Hz commands over 40.2 s measured
  cycles over the 33.3 ms work budget: 12/1197 (1.0%) — work mean 18.7 ms, worst 48.1 ms
  ticks over their 16.7 ms slot: 40/2400 (costs interpolation smoothness only)
  loop-body steps (share of measured work):
    observe      mean   3.13 ms · worst   9.00 ms ·  31.7% of work · 2400 calls
    process_obs  mean   0.50 ms · worst   1.00 ms ·   5.1% of work · 2400 calls
    infer        mean   5.18 ms · worst  40.00 ms ·  52.4% of work · 2400 calls
    send         mean   0.40 ms · worst   0.40 ms ·   4.1% of work · 2400 calls
    record       mean   0.67 ms · worst 620.00 ms ·   6.8% of work · 2402 calls
  pacing headroom: 7.4 ms slept per tick on average (max 13.0 ms) — near zero means the loop is saturated
```

自上而下阅读：**有效节奏** 是相对于 `--fps` 的实际达成值，**超出预算的周期数** 表示循环未能维持该节奏的频率，**步骤分解** 说明了时间消耗在哪里，而接近零的 **节奏余量** 意味着没有剩余空间来吸收缓慢的节拍。较高的 `infer` 占比指向策略或设备；较高的 `observe` 占比指向相机。某一步骤的 `worst` 远高于其 `mean` 属于一次性事件——在上文中，`record` 为每帧 0.67 毫秒，而 620 毫秒的异常值是关闭剧集时阻塞的 `save_episode`。

此处的_cycle_指一个策略动作所对应的 tick 数量（即 `--interpolation_multiplier` 个）。只有当完整的 cycle 超出 `1/fps` 的预算时，才会导致掉帧；单个 tick 超出其时间槽只会使插值后的运动不够平滑，因此这两者被分开统计。在没有插值的情况下，一个 cycle 就是一个 tick，因此报告会取消这种区分，并始终按 tick 进行统计。

在 [交互式会话](#interactive-sessions)中，每个 `/start` 片段都拥有自己的计时器，因此报告会在每个片段（`/reset`、`/stop` 或 `--duration`）结束时发送到聊天中，而不是在每个进程结束时发送。

---

## 编程式用法

对于扩展功能，`lerobot.inference` 是用于后端配置、本地引擎和共享调度契约的公共包。`lerobot.remote_inference` 负责远程引擎、协议、部署和会话的实现；`lerobot.rollout` 负责机器人循环、策略、插值和记录。请从各自的包中导入公共符号，而不是从实现文件中导入。仅在使用远程后端或服务器时，才需要可选的远程依赖项。

对于自定义部署（例如使用运动学处理器），请直接使用 rollout 模块 API：

```python
from lerobot.rollout import BaseStrategyConfig, RolloutConfig, build_rollout_context
from lerobot.inference import SyncInferenceConfig
from lerobot.rollout.strategies import BaseStrategy
from lerobot.utils.process import ProcessSignalHandler

cfg = RolloutConfig(
    robot=my_robot_config,
    policy=my_policy_config,
    strategy=BaseStrategyConfig(),
    inference=SyncInferenceConfig(),
    fps=30,
    duration=60,
    task="my task",
)

signal_handler = ProcessSignalHandler(use_threads=True)
ctx = build_rollout_context(
    cfg,
    signal_handler.shutdown_event,
    robot_action_processor=my_custom_action_processor,       # optional
    robot_observation_processor=my_custom_obs_processor,     # optional
)

strategy = BaseStrategy(cfg.strategy)
try:
    strategy.setup(ctx)
    strategy.run(ctx)
finally:
    strategy.teardown(ctx)
```

请参阅 `examples/so100_to_so100_EE/rollout.py` 和 `examples/phone_to_so100/rollout.py` 以获取包含运动学处理器的完整示例。

