|
Download README.md from Daniel-F/rtc-pi05: direct link, hf CLI and curl.
- Browser
- Download file 7.06 kB
-
https://huggingface.co/Daniel-F/rtc-pi05/resolve/main/README.md
- Command line
-
hf download hf://Daniel-F/rtc-pi05/README.md
-
curl -L -o README.md https://huggingface.co/Daniel-F/rtc-pi05/resolve/main/README.md
7.06 kB
| license: mit | |
| library_name: rtc-pi05 | |
| tags: | |
| - robotics | |
| - pi0 | |
| - pi05 | |
| - flow-matching | |
| - real-time-chunking | |
| - openpi | |
| # rtc-pi05 — Real-Time Chunking for openpi pi0.5 | |
| Standalone, inference-time [**Real-Time Chunking (RTC)**](https://arxiv.org/abs/2506.07339) | |
| for [openpi](https://github.com/Physical-Intelligence/openpi) pi0.5 flow policies. | |
| > Black, Galliker, Levine. *Real-Time Execution of Action Chunking Flow Policies.* | |
| > NeurIPS 2025. arXiv:2506.07339. | |
| **You bring your own pi0.5 base checkpoint** (loaded through openpi). RTC only | |
| replaces the inference path: while the current action chunk executes, a | |
| background thread generates the *next* chunk by **guided inpainting** — freezing | |
| the actions already committed to the robot and softly agreeing with the | |
| overlapping ones — so consecutive chunks stay consistent and execution never | |
| stalls waiting on inference. | |
| This repo is the RTC algorithm plus a thin openpi adapter and a no-robot demo. | |
| It contains **no robot / camera / task-specific code**. | |
| ## What's inside | |
| ``` | |
| rtc/ | |
| masking.py soft prefix masks + PiGDM guidance weight (backend-agnostic, numpy) | |
| controller.py Algorithm 1: async execution + background inference thread | |
| sampler.py guided-inpainting denoising loop (PyTorch, torch.autograd.grad) | |
| pi0.py adapter for openpi PI0Pytorch pi0.5 (torch + openpi, imported lazily) | |
| examples/ | |
| pi05_realtime.py load a pi0.5 ckpt -> run the async RTC controller (synthetic obs) | |
| tests/ | |
| test_masking.py property tests for the soft masks | |
| ``` | |
| `import rtc` pulls in **neither torch nor openpi** — the sampler and adapter are | |
| lazy, so the controller/masking core is usable in a plain-numpy process. | |
| ## Install | |
| Requires Python ≥ 3.11 and a working [openpi](https://github.com/Physical-Intelligence/openpi) | |
| install (it provides `PI0Pytorch` and the checkpoint/normalization machinery; it | |
| is **not** on PyPI, so install it from source into the same environment). | |
| ```bash | |
| # in the environment that already has openpi + torch: | |
| pip install -e ".[torch,dev]" | |
| pytest # runs the masking property tests | |
| ``` | |
| ## Quickstart (no robot) | |
| ```bash | |
| python examples/pi05_realtime.py \ | |
| --config pi05_libero \ # your openpi TrainConfig name | |
| --checkpoint /path/to/pi05_ckpt_dir \ # dir with model.safetensors + assets/ | |
| --device cuda --steps 60 --hz 30 | |
| ``` | |
| It loads the policy, samples an initial chunk, then simulates a 30 Hz control | |
| loop while the background thread regenerates chunks RTC-style, printing the | |
| observed inference delay `d` and confirming the real-time constraint `d ≤ H − s` | |
| holds. Pass `--obs-npz` to feed a recorded observation instead of the synthetic | |
| one (its keys must match your checkpoint's input transform). | |
| ## Using it in your own inference loop | |
| ```python | |
| import torch | |
| from openpi.training import config as _config | |
| from openpi.policies import policy_config as _policy_config | |
| from rtc.controller import RealTimeChunkingController | |
| from rtc.pi0 import build_observation, sample_initial_chunk, rtc_sample_actions, decode_actions | |
| policy = _policy_config.create_trained_policy( | |
| _config.get_config("pi05_libero"), "/path/to/ckpt", pytorch_device="cuda") | |
| H = int(policy._model.config.action_horizon) | |
| # 1. seed the first chunk (model space) from the same velocity field RTC uses | |
| obs0 = build_observation(policy, raw_obs_dict) | |
| init_chunk, _ = sample_initial_chunk(policy, obs0, num_steps=5) | |
| # 2. the controller calls this in a background thread | |
| def inference_fn(obs, prev_chunk, inference_delay, execution_horizon): | |
| new_b, _ = rtc_sample_actions( | |
| policy, obs, prev_chunk.unsqueeze(0), | |
| inference_delay=inference_delay, execution_horizon=execution_horizon, | |
| num_steps=5, max_guidance_weight=5.0, prefix_attention_schedule="exp") | |
| return new_b[0] | |
| ctrl = RealTimeChunkingController(inference_fn, horizon=H, initial_chunk=init_chunk[0], | |
| smin=6, initial_delay=4, delay_buffer_size=10) | |
| ctrl.start() | |
| # 3. every control period dt: | |
| action = ctrl.get_action(build_observation(policy, latest_raw_obs)) # (A,) model space, returns immediately | |
| robot_action = decode_actions(policy, action[None, None], {"state": None}) # -> robot space | |
| ``` | |
| Chunks flow in the model's **normalized** action space (width `action_dim`, | |
| usually padded to 32). Decode to robot space with `rtc.pi0.decode_actions`, which | |
| runs your policy's output transforms (unnormalize / delta→absolute). | |
| ## RTC hyperparameters | |
| | flag / arg | default | paper | meaning | | |
| |---------------------|:-------:|:-----:|------------------------------------------------------| | |
| | `hz` | 30 | 1/Δt | control rate | | |
| | `smin` | 6 | s_min | min execution horizon; `s = max(d, s_min)` | | |
| | `initial_delay` | 4 | d_init| delay-buffer seed (control steps) | | |
| | `delay_buffer` | 10 | b | recent-delay buffer size (delay estimate = its max) | | |
| | `num_steps` | 5 | n | denoising steps | | |
| | `beta` | 5.0 | β | guidance-weight clip (App. A.2) | | |
| | `schedule` | exp | — | soft mask; `zeros` = naive hard-masking baseline | | |
| **Real-time constraint:** `d ≤ s ≤ H − d`. With pi0.5's action horizon `H`, if a | |
| guided inference takes longer than `H − s` control steps the current chunk is | |
| exhausted and `get_action` raises. If that happens: lower `hz`, raise `smin`, or | |
| lower `num_steps`. Watch the printed observed delay `d` — it must stay `≤ H − s`. | |
| Compare RTC against the naive async baseline with `--schedule zeros` (hard | |
| masking), and against synchronous inference (plain `policy.infer`). | |
| ## How the adapter works (conventions) | |
| openpi integrates `t=1` noise → `t=0` data and its action expert regresses | |
| `v_pi = noise − data`. RTC's core integrates the other way (`tau=0` noise → | |
| `tau=1` data). The adapter reconciles them with | |
| ``` | |
| t = 1 − tau , v_rtc(x, tau) = − v_pi(x, t = 1 − tau) | |
| ``` | |
| Crucially, openpi's `PI0Pytorch.denoise_step` is **not** `@torch.no_grad` | |
| (only the compiled `sample_actions` wrapper is), so RTC calls `denoise_step` | |
| directly and autograd flows back to the noisy chunk `x` — no model surgery | |
| needed. The VLM prefix KV cache is built once per observation under `no_grad`; | |
| each denoising step reuses it. See `rtc/pi0.py` and `rtc/sampler.py` for details. | |
| ## Status / caveats | |
| - The `examples/pi05_realtime.py` obs keys are libero-style defaults; adapt them | |
| (or use `--obs-npz`) to your checkpoint's input transform. | |
| - The paper's real-world π0.5 used a larger horizon (`s_min=25, b=10`); with a | |
| smaller `H` the defaults here are what fit `d ≤ H − s`. Tune on your setup. | |
| - RTC's benefit shows up under inference latency / perturbation; on clean, | |
| fast-inference setups it is roughly neutral vs. synchronous. | |