Skip to content

feat: demo mode for showing a trained policy off live - #75

Open
ZouzouWP wants to merge 1 commit into
huggingface:mainfrom
ZouzouWP:feat/demo-mode
Open

feat: demo mode for showing a trained policy off live#75
ZouzouWP wants to merge 1 commit into
huggingface:mainfrom
ZouzouWP:feat/demo-mode

Conversation

@ZouzouWP

Copy link
Copy Markdown

Showing a trained policy to someone (recruiters, teammates, a booth) meant re-entering the full inference config (checkpoint, robot, cameras) from scratch every time, with no simple live camera preview separate from the full recording UI.

What changed

  • New /demo page: one click loads the last successfully started inference config (policy, robot, cameras) as a preset from localStorage — no re-configuration needed right before a demo.
  • New lelab/rollout_frames.py: a thin wrapper around lerobot.scripts.lerobot_rollout that runs inference exactly as before, but also tees each camera frame to a JPEG on disk (~10fps). Served through a new /demo-camera/{cam_id} endpoint for a live, low-latency preview on the demo page.
flowchart LR
    A["InferenceModal:\nstart inference"] -->|saves preset| B["localStorage\npolicy + robot + cameras"]
    B --> C["/demo page\none-click preset"]
    C --> D["lerobot_rollout subprocess\nvia rollout_frames.py wrapper"]
    D -->|tees JPEG ~10fps| E["%TEMP%/lelab_demo_frames"]
    E --> F["GET /demo-camera/cam_id"]
    F --> G["Live preview\non /demo page"]
Loading

Dead end tried and reverted

Real-Time Chunking (--inference.type=rtc) was tried to remove a periodic ~200ms stall from ACT's chunk recompute. This lerobot version's RTC engine calls predict_action_chunk(inference_delay=...), which ACTPolicy doesn't accept — RTC targets flow-matching policies (pi0, smolvla), not ACT. Reverted cleanly to the standard sync engine; left a comment in the code for whoever revisits ACT-specific prefetching.

Testing

Run against a checkpoint trained on ~250 episodes: verified the live preview updates (279 frame updates observed in one run) and that the preset correctly restores policy/robot/cameras without manual re-entry.

Showing a trained policy to someone (recruiters, teammates, a booth) meant re-entering the full inference config (checkpoint, robot, cameras) from scratch every time, with no simple live camera preview separate from the full recording UI.

## What changed

- New `/demo` page: one click loads the **last successfully started inference config** (policy, robot, cameras) as a preset from `localStorage` — no re-configuration needed right before a demo.
- New `lelab/rollout_frames.py`: a thin wrapper around `lerobot.scripts.lerobot_rollout` that runs inference exactly as before, but also tees each camera frame to a JPEG on disk (~10fps). Served through a new `/demo-camera/{cam_id}` endpoint for a live, low-latency preview on the demo page.

```mermaid
flowchart LR
    A["InferenceModal:\nstart inference"] -->|saves preset| B["localStorage\npolicy + robot + cameras"]
    B --> C["/demo page\none-click preset"]
    C --> D["lerobot_rollout subprocess\nvia rollout_frames.py wrapper"]
    D -->|tees JPEG ~10fps| E["%TEMP%/lelab_demo_frames"]
    E --> F["GET /demo-camera/cam_id"]
    F --> G["Live preview\non /demo page"]
```

## Dead end tried and reverted

Real-Time Chunking (`--inference.type=rtc`) was tried to remove a periodic ~200ms stall from ACT's chunk recompute. This lerobot version's RTC engine calls `predict_action_chunk(inference_delay=...)`, which `ACTPolicy` doesn't accept — RTC targets flow-matching policies (pi0, smolvla), not ACT. Reverted cleanly to the standard sync engine; left a comment in the code for whoever revisits ACT-specific prefetching.

## Testing

Run against a checkpoint trained on ~250 episodes: verified the live preview updates (279 frame updates observed in one run) and that the preset correctly restores policy/robot/cameras without manual re-entry.

@nicolas-rabault nicolas-rabault left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three blockers inline. Two are about what happens when something goes wrong mid-motion: a single failed status poll takes the STOP button away, and the page loses the reason a run died. The third is the -m slot collision with #85.
before I merge: a normal run, STOP mid-motion, and the renumbered-port case.

Comment thread lelab/rollout.py
"lerobot.scripts.lerobot_rollout",
# Thin wrapper around lerobot.scripts.lerobot_rollout that also
# tees camera frames to disk for the demo page's live preview.
"lelab.rollout_frames",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a real conflict with #85, not a textual one. #85 replaces this same argument with lelab.rollout_metrics, its own wrapper with an identical main() that installs a control-loop rate meter and then calls lerobot_rollout.main(). There is exactly one -m slot, so whichever of the two merges second leaves the other's wrapper on disk and never imported: the frame tee (or the rate meter) just stops happening, with no error, no log line and no test that would catch it. The demo page would show empty camera tiles and nobody would know why.

Worth resolving as one wrapper module that installs both hooks and then hands over, rather than two modules competing for this line. Happy to coordinate that with #85's author if you prefer.

try {
const s = await getInferenceStatus(baseUrl, fetchWithHeaders);
if (!cancelled) setStatus(s);
} catch {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One failed status fetch nulls the status, which makes active false, which unmounts the STOP button while the arm is still moving. A single dropped request during a demo should not remove the only way to stop the robot, and a demo is exactly when the laptop is on unfamiliar wifi or being carried around.

Keep the last known status on a failed poll and show a "lost connection" banner over it instead. Inference.tsx already takes this shape: its catch toasts and leaves status alone.

<Play className="w-9 h-9 fill-current" />
{submitting ? "STARTING…" : "START"}
</button>
{status?.exited && status.exit_code !== 0 && status.exit_code != null && (

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will almost never be seen. handle_inference_status returns the exited payload only on the single poll where it notices proc.poll() is not None, then sets _inference_proc = None, so every later poll returns the live dict with no exited and no exit_code. The page polls once a second and assigns straight into state, so this line renders for roughly one second and then disappears.

That matters more here than on the inference page, because of what triggers it. The preset is a snapshot living in the browser, and every field in it can go stale against the machine: follower_port gets renumbered on replug or reboot, camera_index moves when a webcam is unplugged, and a local policy_ref is an absolute path that a cleaned training dir invalidates. Missing calibration and unresolvable policy refs fail loudly at start, which is fine, but a stale port or a missing camera lets the start succeed and only fails inside the subprocess, landing exactly here. The result on stage is: press START, "the robot will move in ~30 s", nothing moves, and the page is back on START with no reason given.

Worth latching the exit payload in a ref and keeping it rendered until the next START. /inference-status also returns error (the real exception pulled out of the log) and hint (a plain-language line for the common SO-101 failures, including a wrong or unplugged port). Neither is declared on InferenceStatus in inferenceApi.ts yet, and both are far more useful to a presenter than an exit code plus "check the inference page for logs".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants