File size: 22,315 Bytes
b6a9d87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
# GR00T Real-World Deployment Guide

This guide covers building an end-to-end real-world VLA pipeline—from data collection and training to deployment—with practical engineering recommendations.

## Overview

A typical GR00T real-world deployment workflow includes:

1. **[Hardware Preparation](#1-hardware-and-environment-preparation-device-requirements)**: Verify that the robot platform, sensors, and compute resources are ready.
2. **[Data Collection](#2-data-collection)**: Choose an appropriate teleoperation setup and collect at least 100 valid episodes.
3. **[Data Preprocessing](#3-data-preprocessing)**: Clean data, align timestamps, and convert to LeRobot format.
4. **[Model Training](#4-vla-model-training)**: Fine-tune GR00T N1.*.
5. **[Model Evaluation](#validation)**: Run open-loop evaluation to validate convergence and model quality.
6. **[Deployment Setup](#5-deployment-and-closed-loop-control)**: Build a ZMQ Server-Client architecture.
7. **[Closed-Loop Testing](#5-deployment-and-closed-loop-control)**: Run closed-loop control on real hardware and monitor jittering and stop-and-go behavior.
8. **[Optimization](#6-common-issues-jittering-and-stop-and-go)**: Tune RTC parameters and trajectory smoothing strategies based on real-world performance.

## 1. Hardware and Environment Preparation (Device Requirements)

Ensure your robot hardware, sensor pipeline, and control interfaces are stable and available.

### Robot Platform

- **Recommended platforms**: Robotic arms with SDK-level control support (e.g., Franka, UR, Piper, SO101).
- **Basic requirements**:
  - Real-time joint state feedback.
  - High-frequency action execution (30 FPS recommended).
  - Stable control interface.

> **30 FPS vs inference rate:** The 30 FPS here (and for the cameras below) is the robot's **action-execution** and **camera-capture** rate — not the model **inference** rate. The policy returns a multi-step action *chunk* per inference, so a ~10 Hz inference rate can drive 30 FPS execution via action chunking + asynchronous inference. See the [inference-frequency guidance](hardware_recommendation.md#key-insights) for what each platform sustains.

### Multimodal Sensors

| Sensor Type | Specification | Purpose |
|-------------|---------------|---------|
| **Wrist-mounted camera** | 30 FPS, RGB | Capture close-range manipulation visuals |
| **Third-person camera (3rd view)** | 30 FPS, RGB | Capture global scene context |
| **Robot proprioceptive state** | Real-time acquisition | Joint states and gripper state |

### Compute Resources

- **Training phase**: NVIDIA GPU servers (e.g., H100 or H20) are recommended for larger batch sizes.
- **Deployment phase**: Edge hardware such as Jetson AGX Thor supports on-device inference.

> For details, see the [hardware recommendation guide](hardware_recommendation.md).

### Teleoperation Devices

Teleoperation device selection is critical for data quality.

### Teleoperation Device Comparison

In the table below:
- **Embodiment dependency**: how similar the teleoperation device and target robot must be in joint topology, degrees of freedom (DoF), and workspace. Higher dependency implies harder cross-embodiment transfer.
- **Operational intuition**: how naturally operator inputs map to robot motion. Higher intuition means faster onboarding and lower demonstration error.

| Device Type | Cost Level (Reference) | Embodiment Dependency | Operational Intuition | Notes |
|-------------|------------------------|-----------------------|-----------------------|-------|
| **Keyboard/Gamepad/SpaceMouse/Joylo** | Low | Low: command mapping via keys/controls | Medium: requires adaptation to key-motion mapping | Low entry cost; a good starting point and useful in mobile scenarios |
| **Master-Slave arm systems** | Medium | High: master/slave arms usually require similar kinematics and workspace | High: near one-to-one human-robot mapping | Suitable for single-robot setups; commonly used by robot OEMs; can reduce the risk of reaching joint limits during demonstrations |
| **UMI / Fast-UMI / Pika Sense** | Medium | Low: hardware-agnostic action representation reusable across arms | High: after calibration, end-effector (EEF) following is intuitive | Suitable for training general VLA models; low-DoF arms may still hit joint limits |
| **VR-based teleoperation** | Medium (headset + rendering + network) | Low: mainly depends on software integration | Medium: depends on immersive visual feedback and tracking quality | A flexible solution, but with higher integration overhead |
| **Glove / Motion Capture** | High (commercial mocap suite + data gloves) | Low: retarget through kinematic mapping to different embodiments | High: intuitive full-hand/full-body control | Suitable for full-body control and dexterous-hand tasks |
| **Exoskeleton** | High | High: usually requires matched joint structure | High: natural action correspondence | Extendable to multi-joint humanoid control |

## 2. Data Collection

Key considerations for data collection:

### Timestamp Synchronization

- The FPS of both camera streams should be strictly matched, and capture triggers should be as synchronized as possible.
- Joint state sampling frequency should exceed camera FPS to enable accurate downsampling.
- Record full timestamps during collection for downstream temporal alignment.

### Action Representation

- If training and collection use the same embodiment (e.g., master-slave arms), log joint-space `Joint States` during collection. For task-space models, compute EEF pose via forward kinematics (FK) in post-processing.
- If embodiments differ (e.g., collect with UMI, deploy on Piper), directly record task-space EEF pose during collection.

### Data Distribution

- Current imitation-learning-based models perform more reliably in previously seen scenarios. In early-stage experiments, start with data collection and validation in a limited domain.
- After pipeline validation, gradually expand the domain by varying lighting, object placement, and initial robot poses to improve generalization.

### Scene Consistency

- Keep third-person camera extrinsics fixed and ensure a rigid wrist-camera mount.
- In early experiments, prioritize scene consistency; avoid varying lighting, object placement, or initial robot poses.

### Joint Limits

- If collecting joint-space data, avoid operating near joint limits to reduce the number of samples in those regions.

## 3. Data Preprocessing

Raw data must be cleaned, synchronized, and converted before training.

### Trajectory Filtering

Data filtering is recommended in two stages: script-based filtering and manual review.

#### Script Filtering

- Check image timestamps and remove samples with:
  1. Excessive latency in a single camera stream.
  2. Excessive timestamp difference between the two camera streams.
- Detect and remove abnormal jumps in robot state sequences.

#### Manual Filtering

Replay trajectories with synchronized visualization to catch issues missed by scripts:

- Remove samples with poor synchronization between image and action sequences.
- Remove blurry frames.
- Remove failed task executions.
- Remove low-quality trajectories (e.g., redundant paths, discontinuous actions).

### Trajectory Preprocessing

1. Timestamp alignment: align camera frames and robot joint states to a shared time base.
2. Head-tail trimming: remove idle segments at the start and end of trajectories.
3. Split long trajectories (several minutes) into multiple subtasks.

### Format Conversion

Convert all data to a standard format (e.g., LeRobot) for GR00T compatibility:

- See the [data preparation guide](data_preparation.md) for format requirements.
- Use the provided conversion scripts to convert data to GR00T LeRobot format.

## 4. VLA Model Training

### Training Parameter Configuration

**Dataset size recommendations**

For single-task `finetune`:

- **Minimum data size**: Prepare at least **100 valid episodes**. For very narrow task domains, ~30 episodes may suffice. A capture frequency of 20–50 Hz is recommended for manipulation tasks.
- **Episode length**: No hard limit, but each episode must contain a complete action cycle with idle frames removed. Split overly long episodes into subtasks.
- **Recommended data size**: 200+ episodes usually provide more stable performance.

**Core parameters**

- **Input/output mode**: Default to `State-relative Action Prediction`. Compared with `Absolute Action`, it converges more easily and improves inter-chunk consistency.
- **Training space**: Both joint space and task space are valid. For low-DoF arms, joint space is often preferred to reduce singularity-related risks.
- **Action Chunk Size**: Default is 16. If combined with RTC to mitigate stop-and-go, set it to at least 32.
- **Batch Size**: Increase the batch size as much as GPU memory allows.

> For additional training options, see the [fine-tuning guide](finetune_new_embodiment.md).

**Compute resources**

- Fine-tuning requires significantly less compute than pretraining.
- You can start on a **single GPU** (40 GB+ VRAM). See the [hardware recommendation guide](hardware_recommendation.md#fine-tuning-hardware) for the minimum and quick-start configurations.
- A single compute node (8 x H100 or 8 x H20) is a **production-scale** setup for faster convergence on larger datasets — not a minimum requirement.

### Validation

After training, run open-loop validation to confirm convergence, then proceed to closed-loop deployment validation.

> Open-loop validation is only a preliminary check. Final performance must be verified with closed-loop testing on real robots. For details, see the [fine-tuning guide](finetune_new_embodiment.md).

## 5. Deployment and Closed-Loop Control

### System Architecture

GR00T supports two inference modes:

1. **Direct `Gr00tPolicy` usage**: Suitable when model inference and robot control run on the same machine.
2. **ZMQ Server-Client architecture**: Suitable for real-world deployment and decouples local robot control (`Local Client`) from remote inference (`Model Server`).

For real-world deployment, **ZMQ inference service** is recommended:

- Move compute-intensive inference to GPU servers.
- Keep robot-side control code lightweight.
- Avoid installing the full inference dependency stack on the robot side.

### On-Device Deployment Logic

Deployment code has two phases: **initialization** and the **main control loop**.
The pseudo-code below uses a synchronous workflow, which may cause stop-and-go. See later sections for mitigation via asynchronous execution + RTC.

**Pseudo-code workflow:**

```python
# ========== Initialization ==========
# 1. Initialize and test cameras
hand_camera = initialize_hand_camera()  # e.g., OrbbecSDK
env_camera = initialize_env_camera()    # e.g., RealSense
test_cameras()  # Show preview and verify normal operation

# 2. Connect and test robot
robot = connect_robot()  # e.g., Piper SDK
robot.enable()
robot.reset_to_initial_position()
test_robot()  # Send test command and verify robot response

# 3. Connect and test GR00T model server
gr00t_client = connect_to_gr00t_server(host, port)
if not gr00t_client.ping():
    raise ConnectionError("Failed to connect to model server")
test_model()  # Send test observation and verify inference

# ========== Main control loop ==========
while True:
    # 1. Acquire sensor data
    hand_image = hand_camera.get_frame()
    env_image = env_camera.get_frame()
    joint_states = robot.get_joint_states()
    gripper_state = robot.get_gripper_state()

    # 2. Format observation
    observation = format_observation(
        hand_image,
        env_image,
        joint_states,
        gripper_state,
        task_description,
    )

    # 3. Model inference (via ZMQ)
    actions = gr00t_client.get_action(observation)

    # 4. Trajectory post-processing
    actions_arm = actions["joint_states"]
    actions_arm = smooth_trajectory(actions_arm)  # smoothing
    actions_arm = check_safety_limits(actions_arm)  # safety checks

    # 5. Execute actions
    for action_step in actions_arm:
        robot.execute_action(action_step)
        sleep(1.0 / 30.0)  # 30 FPS
```

### Key Implementation Notes

**Important notes:**

- **Image format**: Use compressed formats such as JPG to reduce transmission bandwidth.
- **Safe operation**:
  - **Soft Limits**: Add joint-angle and EEF pose range checks. If a predicted action exceeds workspace bounds, raise an alarm and stop immediately.
  - **E-Stop logic**: Bind an emergency stop hotkey (e.g., Space) on the control PC, or use a physical E-Stop switch.
- **Action smoothing**: Apply interpolation and smoothing to predicted action sequences.

> For more deployment details, see the [policy API guide](policy.md).

## 6. Common Issues: Jittering and Stop-and-Go

The most common issues in real-world deployment are **jittering** and **stop-and-go**.

### Fixing Jittering

**Jittering** here refers to visible shaking or vibration of the end-effector or joints during task execution.

Jittering typically originates from **inconsistent model outputs** or **insufficient robot-side control quality**. Analyze these two components separately to localize the issue. The suggestions below are general guidelines and may not apply to every robot platform or control stack — always verify against your own hardware and environment.

```mermaid
flowchart TD
    A[Jittering observed] --> B[Save & visualize action chunks in 3D]
    B --> C{Where is the jitter?}
    C -->|Inside each chunk| D[Case A: Model undertrained or poor data quality]
    C -->|Between consecutive chunks| E[Case B: Inconsistent chunk predictions]
    C -->|Chunks look smooth| F[Case C: Robot hardware / low-level control issue]
    D --> D1[Add more data, train longer, check train/eval consistency]
    E --> E1[Use state-relative actions + RTC chunking strategy]
    F --> F1[Check drive control, interpolation, hardware status]
```

**Diagnosis and mitigation**

1. **Save and visualize Action Chunks**
   - Save all predicted `Action Chunks`.
   - Visualize continuous TCP (tool center point) trajectories in 3D.
   - **Note**: Convert joint-space outputs to task space via FK before visualization.

2. **Analyze visualization results**

   **Case A: Significant jitter inside each chunk**
   - **Cause**: The model is undertrained, or data quality is insufficient.
   - **Solution**: Improve data quality, add more training data, or train longer. Keep training and validation environments consistent.

   **Case B: Significant jitter between chunks**
   - **Cause**: Inconsistent adjacent `Action Chunk` predictions.
   - **Solution**:
     - Use `State-relative Action Prediction`. Predicting actions relative to the current state produces a more uniform output distribution, making the network easier to train.
     - Use RTC (`Real-Time Chunking`) or similar strategies.

   **Case C: Little jitter after visualization**
   - **Cause**: Likely a robot hardware or low-level control issue.
   - **Solution**: Check drive control, interpolation, and hardware status.

**Quantitative diagnostic metrics**

Trajectory jitter can also be quantified using these three metrics:

**Metric 1: Mean intra-chunk acceleration magnitude**

Measures intra-chunk smoothness. Only valid under fixed sampling frequency.

Formula: $a_t = pos_{t+1} - 2 \cdot pos_t + pos_{t-1}$

```python
def metric_intra_accel(chunks):
    """
    Args:
        chunks: numpy array with shape (N_chunks, Chunk_Length, Joint_Dim)

    Returns:
        float: Mean acceleration magnitude
    """
    velocity = np.diff(chunks, axis=1)  # first-order difference
    acceleration = np.diff(velocity, axis=1)  # second-order difference
    acc_magnitude = np.linalg.norm(acceleration, axis=-1)  # L2 norm per step
    return np.mean(acc_magnitude)
```

**Metric 2: Position jump at chunk boundary (L2 distance)**

Measures position continuity between chunks by comparing the last executed step of `Chunk[i]` with step 0 of `Chunk[i+1]`.

```python
def metric_boundary_jump(chunks, execute_steps=None):
    """
    Args:
        chunks: numpy array with shape (N_chunks, Chunk_Length, Joint_Dim)
        execute_steps: number of executed steps per chunk; if None, use full chunk length

    Returns:
        float: Mean position jump
    """
    chunks = np.array(chunks)
    exec_steps = chunks.shape[1] if execute_steps is None else execute_steps

    last_frame_prev = chunks[:-1, exec_steps - 1, :]  # last frame of previous chunk
    first_frame_curr = chunks[1:, 0, :]  # first frame of current chunk
    jumps = np.linalg.norm(first_frame_curr - last_frame_prev, axis=-1)  # Euclidean distance
    return np.mean(jumps)
```

**Metric 3: Cosine similarity of velocity direction at chunk boundary**

Measures velocity-direction consistency between chunks. Values closer to 1 indicate better consistency.

```python
def metric_momentum_shift(chunks, execute_steps=None):
    """
    Args:
        chunks: numpy array with shape (N_chunks, Chunk_Length, Joint_Dim)
        execute_steps: number of executed steps per chunk; if None, use full chunk length

    Returns:
        float: Mean cosine similarity
    """
    chunks = np.array(chunks)
    exec_steps = chunks.shape[1] if execute_steps is None else execute_steps

    # velocity at the end of previous chunk
    idx = exec_steps - 1
    if idx < 1:
        raise ValueError("execute_steps must be >= 2 to compute end velocity")
    v_end = chunks[:-1, idx, :] - chunks[:-1, idx - 1, :]

    # velocity at the start of current chunk
    v_start = chunks[1:, 1, :] - chunks[1:, 0, :]

    # cosine similarity
    dot_product = np.sum(v_end * v_start, axis=-1)
    norm_prev = np.linalg.norm(v_end, axis=-1)
    norm_curr = np.linalg.norm(v_start, axis=-1)
    epsilon = 1e-8
    cosine_sim = dot_product / (norm_prev * norm_curr + epsilon)

    return np.mean(cosine_sim)
```

### Fixing Stop-and-Go
Stop-and-Go here refers to a behavior in which the robot intermittently pauses during motion, producing periodic stop-and-go behavior.

#### Root Cause

In **synchronous single-step closed-loop** control, stop-and-go occurs when the **end-to-end latency** (observation capture → VLA inference → action conversion) exceeds control-frequency requirements.

- **Control-frequency requirement**: At 30 FPS, latency must stay below ~33 ms.
- **Typical latency sources**: Data capture, network transfer, model inference, and post-processing often exceed 33 ms combined.
- **Consequence**: The next prediction is not ready when the current action finishes, causing pauses.

#### Solutions

**Option 1: Optimize the inference pipeline (direct but difficult)**

Reduce full workflow latency below 33 ms:

- Optimize network bandwidth (reduce transfer time).
- Use edge inference (reduce network latency).
- Quantize the VLA model (speed up inference).
- Use a smaller model (e.g., ACT).

**Limitation**: For VLA models, meeting strict real-time requirements through optimization alone is often impractical.

**Option 2: Use algorithmic scheduling strategies (recommended)**

When direct optimization is insufficient, use one or more of the following:

- **Asynchronous Inference**: A background thread runs inference while the main thread executes actions.
- **Receding Horizon**: Execute only the first few steps of each `Action Chunk` before triggering a new inference.
- **Temporal Ensemble**: Aggregate predictions across multiple timesteps.
- **Real-Time Chunking (RTC)**: Overlap the start of the current prediction with unexecuted steps from the previous one.

**Recommended strategy**: `Asynchronous Inference + RTC` is usually the most effective.

> **RTC status (experimental):** Asynchronous inference is supported today. RTC is currently only a low-level model primitive: `action_head.get_action(..., options={"rtc_overlap_steps": ..., "rtc_frozen_steps": ..., "rtc_ramp_rate": ...})` with the previous action fed back in (`gr00t/model/gr00t_n1d7/gr00t_n1d7.py`). It is **not wired into `Gr00tPolicy` or the server-client path** (there `options` is currently unused), and it has no tests or ready-made example — so the RTC steps below require manual integration.

#### Real-Time Chunking (RTC) Details

**Principle**

RTC treats action prediction as an inpainting problem: overlapping the start of the new prediction with unexecuted steps from the previous one ensures smooth transitions.

**Applicability**

- Validated for **diffusion / flow-based** VLA policies.
- Requires `Action Chunk` length ≥ 32 steps.
- Should be combined with asynchronous inference.

**Implementation essentials**

1. **Predict longer Action Chunks**:
   - Increase from the default 16 steps to at least 32.
   - Provide a larger soft fusion window.

2. **Asynchronous inference architecture**:
   - **Background thread**: Continuously infer, capture observations, and prepare action batches.
   - **Main thread**: Execute the current action sequence.
   - Buffer predictions in a queue to avoid blocking.

3. **Action fusion mechanism**:
   - Use RTC for soft fusion in the overlap region.
   - Ensure smooth transitions between adjacent chunks.

**Pseudocode: Async Inference + RTC**

In the RTC (Real-Time Chunking) framework, two key parameters control how adjacent action chunks overlap and transition:

- **`overlap`**: The number of action steps retained from the previous prediction to constrain the current one, ensuring temporal consistency between consecutive chunks.
- **`frozen`**: The number of steps that remain completely frozen (i.e., not updated by the new prediction), typically set to match the inference latency.

Below is a simplified async inference + RTC loop. Note that official RTC support for GR00T is coming soon; the current implementation may require manual adaptation.

```
actions = policy.infer(obs)                        # blocking first call

loop:
    for i in range(action_horizon):
        if i == action_horizon - overlap - 1:
            future = async policy.infer(new_obs)   # non-blocking
        robot.execute(actions[i])
        if i == action_horizon - frozen - 1:
            actions = future.get()                 # swap in next chunk
            break                                  # discard frozen tail
```