
A finished checkpoint does not move a servo. The real file paths, the config keys that must match, the three deployment stacks and the errors that eat a day.
Training finishes. The loss curve looks reasonable, a directory full of safetensors appears, and nothing at all happens to the robot. The last mile, taking a finished checkpoint and getting it to drive a real SO-100, is where most of a day disappears, and it is almost never the model's fault. It is a camera key spelled differently, an embodiment tag that does not exist, a backbone the checkpoint still needs to download, or an execution horizon longer than the chunk the model predicts.
This page walks that mile for the three stacks that ship today: lerobot's lerobot-rollout CLI, NVIDIA's Isaac-GR00T policy server, and openpi's websocket server. Real paths, real flags, real error text. Every command, file name and number below was read out of the upstream repositories and model cards on 24 August 2026, and where a stack changed recently the version is named. If you have not trained anything yet, train your first policy first and come back with a checkpoint in hand.
What you need to know
- •A checkpoint is weights plus a config plus normalization statistics. Copy only the weights and the arm still moves, just to the wrong place.
- •The most common last-mile failure is a key mismatch: the camera name your dataset used is not the camera name the policy expects.
- •A GR00T fine-tune saves its modality config into the checkpoint and loads it back automatically at inference. lerobot policies carry feature names in config.json, and --rename_map bridges the difference.
- •Run open-loop evaluation against a training episode before you power the arm. If the predicted curve does not track ground truth there, hardware will not rescue it.
- •lerobot-rollout --strategy.type=base is the shortest path from checkpoint to moving arm. Every other strategy is that same loop plus recording or human takeover.
- •Latency decides the architecture. NVIDIA measures GR00T N1.7 at 85.8 ms per inference on an H100 in PyTorch eager and 27.9 ms with the full TensorRT pipeline, at four denoising steps with one camera.
- •On AY-Robots the control loop runs 20 ms per action step for ACT and 485 ms for Pi0.5. Public-internet round trips on top of that turn a working policy into a hesitant one.
What training actually left you
Three trainers, three different trees on disk. None of them is a single file, and the difference between them is the reason a checkpoint that works on the training pod does nothing on the robot machine. The table below is what each stack writes and what reads it back.
| Stack | Where the checkpoint lands | Files that matter | What loads it |
|---|---|---|---|
| lerobot (ACT, SmolVLA, Pi0.5, GR00T N1.7 via the groot policy type) | outputs/train/<job_name>/checkpoints/<step>/pretrained_model/ | config.json, model.safetensors, train_config.json, policy_preprocessor.json and policy_postprocessor.json with their normalizer and unnormalizer safetensors | lerobot-rollout --policy.path=<dir or Hub id> |
| Isaac-GR00T gr00t/experiment/launch_finetune.py | <--output-dir>/checkpoint-<step>/ | config.json, embodiment_id.json, model-0000N-of-0000M.safetensors, model.safetensors.index.json, processor_config.json, statistics.json | gr00t/eval/run_gr00t_server.py --model-path=<dir> |
| openpi (JAX Pi0 / Pi0.5) | checkpoints/<config>/<exp_name>/<step>/ | params and train_state, plus an assets directory holding the normalization statistics | scripts/serve_policy.py policy:checkpoint --policy.config= --policy.dir= |
The base GR00T N1.7 repository on Hugging Face shows the shape clearly. This is the complete file list of nvidia/GR00T-N1.7-3B as the Hugging Face model API returned it on 24 August 2026, license and readme files included, because knowing which entries are noise is half the point.
.gitattributes
EXPLAINABILITY.md
LICENSE
PRIVACY.md
README.md
SAFETY_and_SECURITY.md
SUCCESS
config.json
embodiment_id.json
experiment_cfg/conf.yaml
experiment_cfg/config.yaml
experiment_cfg/dataset_statistics.json
experiment_cfg/final_model_config.json
experiment_cfg/final_processor_config.json
experiment_cfg/initial_actions.npz
latest
model-00001-of-00002.safetensors
model-00002-of-00002.safetensors
model-architecture.png
model.safetensors.index.json
processor_config.json
scheduler.pt
statistics.json
trainer_state.json
training_args.bin
wandb_config.json
zero_to_fp32.pyWhich of those does a fine-tune of your own actually need on the robot machine? NVIDIA's deployment guide answers it by example: when it pulls a checkpoint down to run inference, it fetches config.json, embodiment_id.json, the model-*.safetensors shards, model.safetensors.index.json, processor_config.json and statistics.json. That is the set to copy. trainer_state.json, scheduler.pt, training_args.bin and wandb_config.json are training bookkeeping. statistics.json is the one nobody misses until it is missing: it carries the normalization statistics that turn raw network output back into joint angles.
GR00T N1.7 uses nvidia/Cosmos-Reason2-2B (a Qwen3-VL architecture) as its vision-language backbone, and the Isaac-GR00T README states plainly that every GR00T checkpoint, the base nvidia/GR00T-N1.7-3B included, loads that gated repository on first use. On a fresh pod without a token the load fails with GatedRepoError or a 401. Request access on the model page and authenticate with hf auth login (or export HF_TOKEN) before you go anywhere near the arm. The same applies to lerobot's --policy.base_model_path=nvidia/GR00T-N1.7-3B.
The five things that have to match
A policy is a function from a named dictionary of observations to a named dictionary of actions. Deployment is the job of making the robot produce exactly the dictionary the checkpoint was trained on. Five things decide whether it does.
| What must match | Where it is defined | What you see when it does not |
|---|---|---|
| Camera keys | GR00T: meta/modality.json, where each video key carries an original_key such as observation.images.front. lerobot: the input features in the policy config.json | The policy runs and looks almost sane, but ignores the view that mattered. The gripper is usually the first casualty. |
| State and action keys | GR00T: the index ranges in modality.json plus the modality config registered under NEW_EMBODIMENT. lerobot: dataset feature names | Predicted curve is flat or constant. NVIDIA lists this exact symptom as a modality key mismatch rather than a model problem. |
| Embodiment tag | --embodiment-tag at fine-tune time. For NEW_EMBODIMENT the modality config is saved into the checkpoint and loaded automatically at inference | An error listing every known tag. Posttrain tags pointed at the base model fail outright, and a tag from a different robot fails on the state keys. |
| Execution horizon against predicted chunk length | GR00T: --execution-horizon at rollout, which must stay at or below action_horizon in the model config (40 for the base N1.7 checkpoint). lerobot RTC: --inference.rtc.execution_horizon, which is the number of steps blended with the previous chunk, not the same quantity despite the same name | GR00T refuses an execution horizon above the predicted chunk length. Too long and the arm commits to stale plans, too short and you pay for inference more often. |
| Normalization statistics | GR00T: statistics.json. lerobot: policy_preprocessor.json and policy_postprocessor.json plus their safetensors. openpi: the assets directory | Enormous or NaN error, and an arm that drives straight into a joint limit. |

Camera keys deserve their own paragraph because the fix is unintuitive. lerobot ships a rename map, a JSON dictionary passed on the command line that renames observation keys inside the preprocessor pipeline so the policy always sees the names baked into its config. The convention is always source key first, policy key second. It is supported for Pi0, Pi0.5, Pi0Fast, SmolVLA and XVLA, and it renames observations only: action keys are untouched.
A rename map written backwards does not announce itself. lerobot/pi0fast-libero expects observation.images.base_0_rgb and observation.images.left_wrist_0_rgb; your recording probably says observation.images.front and observation.images.wrist. Write --rename_map='{"observation.images.front": "observation.images.base_0_rgb"}', not the reverse. Only listed keys are renamed and everything else passes through unchanged, so a mapping whose source key does not exist in your data simply renames nothing, the policy never receives the view it expects, and the gripper never closes. If your dataset has fewer cameras than the policy expects, set --policy.empty_cameras=N, which adds masked placeholder image features instead of making you feed the model black frames.
Route A: lerobot-rollout, one command from checkpoint to motion
In current lerobot, lerobot-rollout is the single CLI for deploying a trained policy on a real robot, and the imitation-learning guide now points at it rather than at the recording command. It covers ACT, SmolVLA, Pi0.5 and GR00T N1.7 through one interface. Two things you choose: a strategy, which is the shape of the control loop, and an inference backend, which is how chunks are produced.
- 1Install the deployment extras on the robot machine
The training environment and the robot environment are not the same install. You need the serial driver for the servos and, if you want the live view, the visualization extra. This is the exact line the GR00T policy page uses before its hardware rollout.
bashpip install "lerobot[feetech,viz]" - 2Find the camera indices, do not guess them
USB camera indices move between reboots and between machines. Enumerate them, then write the index into the rollout command explicitly.
bashlerobot-find-cameras opencv - 3Run the base strategy for a fixed duration
No recording, no dataset, no Hub push. This is the smallest thing that proves the checkpoint drives the arm. Keep a hand near the power switch for the first run.
bashlerobot-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 \ --display_data=true - 4Read the cadence report instead of guessing
Every strategy reports what its control loop actually achieved against the --fps target, and so do lerobot-record, lerobot-teleoperate and lerobot-replay, because they pace through the same timer. The breakdown names observe, process_obs, infer, send and record. A high infer share points at the policy or the device, a high observe share points at the cameras, and pacing headroom near zero means the loop is saturated and the next slow tick will cost a frame.
textCadence summary - whole run, 2 episodes - target 30 Hz x 2 (16.7 ms tick slot, 33.3 ms cycle budget) 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 loop-body steps (share of measured work): observe mean 3.13 ms - 31.7% of work process_obs mean 0.50 ms - 5.1% of work infer mean 5.18 ms - 52.4% of work send mean 0.40 ms - 4.1% of work record mean 0.67 ms - 6.8% of work pacing headroom: 7.4 ms slept per tick on average
| --strategy.type | What the loop does | Use it when |
|---|---|---|
| base | Autonomous execution, nothing recorded | First run, demos, latency measurement |
| episodic | Episode-oriented recording that mirrors lerobot-record, with an optional teleoperator driving during the reset phase | Building an evaluation set with clean episode boundaries |
| sentry | Continuous autonomous recording with periodic Hub upload; policy state persists across episode boundaries and the robot does not reset between them | Long unattended runs you want on the Hub |
| highlight | Runs continuously with a ring buffer of the last N seconds (default 30); press s to flush the buffer and save the interesting window | Catching rare failures without recording hours of nothing |
| dagger | Alternates policy execution with human takeover through a teleoperator; intervention frames are tagged intervention=True | Fixing a policy that drifts, without recording from scratch |
The second choice is the backend. The default is synchronous: one policy call per control tick, and the loop blocks until the action comes back. That is fine for ACT at 20 ms per step and painful for a 3 B parameter VLA. Real-Time Chunking runs a background thread that produces the next action chunk while the current one is still being consumed, and guides the new chunk so its opening steps blend with the motion already under way. The technique comes from the Real-Time Execution of Action Chunking Flow Policies paper (Black, Galliker and Levine, June 2025) and lerobot exposes it as a backend flag.
export MODEL_ID=your_trained_model_on_huggingface
lerobot-rollout \
--strategy.type=base \
--inference.type=rtc \
--inference.rtc.execution_horizon=8 \
--inference.queue_threshold=0 \
--policy.path=$MODEL_ID \
--policy.base_model_path=nvidia/GR00T-N1.7-3B \
--policy.n_action_steps=8 \
--robot.type=so101_follower \
--robot.port=/dev/ttyACM0 \
--robot.id=follower_robot \
--robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30, fourcc: \"MJPG\"}, wrist: {type: opencv, index_or_path: 2, width: 640, height: 480, fps: 30, fourcc: \"MJPG\"} }" \
--task="place the vial in the rack" \
--duration=60 \
--device=cuda \
--display_data=true--policy.path loads weights and the checkpoint's config.json, so feature names, n_action_steps and every other stored setting come along. --policy.pretrained_path loads weights only, takes feature names from your dataset, and resets stored settings to their defaults; it also requires --policy.type, which --policy.path forbids. The lerobot Pi0.5 page spells out the consequence: a checkpoint trained with n_action_steps=10 and empty_cameras=1 silently falls back to 50 and 0 when it is loaded the second way.
LeRobot removed GR00T N1.5 support: current releases handle N1.7 only, and N1.5 checkpoints and configs are rejected with a migration note. To keep running an existing N1.5 checkpoint, pin pip install 'lerobot==0.5.1'; to move forward, retrain against nvidia/GR00T-N1.7-3B. Both GR00T N1.5 and GR00T N1.7 are still trainable on AY-Robots, so check which one produced the checkpoint in your hand before you debug the loader.
Route B: the Isaac-GR00T policy server and a thin client
NVIDIA's own stack splits the problem in two. The policy runs inside a server process on the GPU; the robot side is a small client that speaks ZeroMQ with msgpack serialization and carries none of the model dependencies. Upstream gives two reasons for the split, and both are practical: the inference GPU does not have to be the machine holding the serial port, and the robot environment does not have to satisfy the model's dependencies. Isaac-GR00T ships a worked SO-100 example, which is the closest thing upstream has to this article.
- 1Check the checkpoint open-loop before touching hardware
Predict on an episode the model was trained on and compare against ground truth. The script logs Average MSE and Average MAE across trajectories as unnormalized action error and saves a ground-truth-versus-predicted plot. NVIDIA deliberately publishes no target MSE, because the number does not transfer between datasets: what you read is plot overlap on a training trajectory first, then whether the error falls across successive checkpoints. Pass --save-plot-path explicitly, since the SO-100 guide says plots land in /tmp/open_loop_eval/traj_<id>.jpeg by default while the parameter table lists no default at all.
bashuv run python gr00t/eval/open_loop_eval.py \ --dataset-path examples/SO100/finish_sandwich_lerobot/izuluaga/finish_sandwich \ --embodiment-tag NEW_EMBODIMENT \ --model-path /tmp/so100_finetune/checkpoint-10000 \ --traj-ids 0 \ --execution-horizon 16 \ --steps 400 \ --modality-keys single_arm gripper \ --save-plot-path /tmp/open_loop_eval - 2Start the policy server on the GPU machine
Bind to 0.0.0.0 only if the robot machine is elsewhere and the link is trusted; 127.0.0.1 plus an SSH tunnel is the safer default. The server prints its embodiment tag, model path, device and listening address before it accepts anything, which is the fastest way to catch a wrong checkpoint path.
bashuv run python gr00t/eval/run_gr00t_server.py \ --model-path /tmp/so100_finetune/checkpoint-10000 \ --embodiment-tag NEW_EMBODIMENT \ --device cuda:0 \ --host 0.0.0.0 \ --port 5555 - 3Run the SO-100 client in its own environment
The client environment is created separately under gr00t/eval/real_robot/SO100 with its own uv sync, then the repo root is installed into it with --no-deps, precisely so the robot side does not pull in flash-attn, onnx and TensorRT. The keys in --robot.cameras have to be the video keys from modality.json (front and wrist in the SO-100 example), and the language instruction has to be the instruction the episodes were labelled with.
bashcd "$GR00T_REPO/gr00t/eval/real_robot/SO100" uv sync uv pip install --no-deps -e ../../../../ uv run --no-sync python eval_so100.py \ --robot.type=so101_follower \ --robot.port=/dev/ttyACM2 \ --robot.id=orange_follower \ --robot.cameras="{ front: {type: opencv, index_or_path: 6, width: 640, height: 480, fps: 30}, wrist: {type: opencv, index_or_path: 2, width: 640, height: 480, fps: 30}}" \ --policy_host=localhost \ --policy_port=5555 \ --lang_instruction="finish the ham cheese olives sandwich"
Start the server with --dataset-path instead of --model-path and it replays recorded actions from that dataset instead of running the network. That is ReplayPolicy, and upstream recommends it as the first step when integrating a new environment: if the arm cannot replay a recorded episode cleanly, no policy will fix it. Give it the same --execution-horizon your environment executes, because that is how many dataset steps it advances per call. Note the client timeout too: timeout_ms defaults to 15000 to absorb cold-start model loading on the first call, and after that a timeout raises zmq.error.Again, which your control loop has to answer by retrying or halting.
Route C: openpi over a websocket
If you fine-tuned Pi0 or Pi0.5 with Physical Intelligence's own openpi rather than through lerobot, the deployment surface is a websocket server on port 8000 and a client package with deliberately minimal dependencies, installed into the robot environment from packages/openpi-client. The observation dictionary is flat and prefixed, the client resizes images itself to keep bandwidth down, and the server does the normalization. Note the shape of the return value: it is a whole action chunk, and upstream expects you to execute several steps of it open-loop before asking again.
from openpi_client import image_tools
from openpi_client import websocket_client_policy
# localhost and 8000 are the defaults
client = websocket_client_policy.WebsocketClientPolicy(host="localhost", port=8000)
observation = {
"observation/image": image_tools.convert_to_uint8(
image_tools.resize_with_pad(img, 224, 224)
),
"observation/wrist_image": image_tools.convert_to_uint8(
image_tools.resize_with_pad(wrist_img, 224, 224)
),
"observation/state": state,
"prompt": task_instruction,
}
# shape (action_horizon, action_dim)
action_chunk = client.infer(observation)["actions"]The matching server command is uv run scripts/serve_policy.py policy:checkpoint --policy.config=<config name> --policy.dir=<checkpoint dir>, where the checkpoint directory looks like checkpoints/pi05_libero/my_experiment/20000. The config name is not decorative: it selects the input and output transforms that were used during training, the same classes that map your robot's observations into the model and its actions back out, so a checkpoint served under the wrong config produces well-formed nonsense. This is the openpi equivalent of GR00T's embodiment tag.
The errors in between
Collected from the upstream troubleshooting tables and from what actually goes wrong in the first hour. Read the first column, then check the third before you change anything about the model.
| Message or symptom | What it actually is | First thing to check |
|---|---|---|
| GatedRepoError, or 401 Client Error on model load | The gated Cosmos-Reason2-2B backbone that every GR00T checkpoint pulls on first use | Access granted on the model page, and a token present on the machine that loads the model |
| Address already in use when the policy server starts | Port 5555, the default, is held by an earlier server you did not kill | Pass --port, or find the stale process |
| zmq.error.Again on the client | The server did not answer inside timeout_ms, default 15000 | Cold start on the first call is normal; repeated timeouts mean the GPU is too slow or the link is too long |
| An error listing all known embodiment tags | A typo, or a posttrain tag pointed at the base model | Tags are case-insensitive, but posttrain tags require a fine-tuned checkpoint and the tag has to match the dataset |
| Predicted action curve is flat or constant | modality.json keys or --modality-config-path do not map your action keys | The modality config, not the learning rate |
| MSE extremely large, or NaN loss during training | Action and state normalization | meta/stats in the dataset, and whether the action ranges are physically sane |
| RuntimeError: Could not load libtorchcodec ... We support versions 4, 5, 6 and 7 | FFmpeg 8, which ships on Ubuntu 25.10 and newer, against the pinned torchcodec 0.8.0 | Install an FFmpeg below 8 and put its libraries on LD_LIBRARY_PATH |
| Tracks traj 0 well, fails on held-out episodes | Data scarcity, not a deployment bug | Episode count. NVIDIA calls this out explicitly for their five-episode demo set |

Several of these have a dedicated page: camera not detected, arm not detected, policy freezes mid-motion, loss falls but the policy does nothing and policy only works in one setup. The last one is worth reading before you conclude the deployment is broken, because a policy that works only under the exact lighting it was recorded in is a data problem wearing a deployment costume.
How fast it runs once it runs
Deployment is not finished when the arm moves; it is finished when the arm moves at a usable rate. NVIDIA publishes measured end-to-end timings for GR00T N1.7 at four denoising steps with one camera, comparing PyTorch eager against torch.compile against the full TensorRT pipeline. These are the model's own numbers on the GPU, data processing included, before any camera capture or serial cost. They are not AY-Robots figures and they are not a benchmark reproduced here.
| Device | PyTorch eager | torch.compile | TensorRT full pipeline | Rate with TensorRT |
|---|---|---|---|---|
| H100 80 GB HBM3 | 85.8 ms | 48.6 ms | 27.9 ms | 35.9 Hz |
| RTX Pro 6000 Blackwell | 78.4 ms | 50.7 ms | 27.9 ms | 35.9 Hz |
| L40 | 128.3 ms | 69.0 ms | 38.4 ms | 26.0 Hz |
| DGX Spark | 126.4 ms | 108.8 ms | 98.6 ms | 10.1 Hz |
| AGX Thor | 112.8 ms | 102.3 ms | 80.4 ms | 12.4 Hz |
| Jetson Orin | 354.0 ms | 227.1 ms | 150.9 ms | 6.6 Hz |
Two things stand out. The end-to-end speedup from TensorRT is real but bounded, from 1.28x on DGX Spark to 3.34x on the L40 in NVIDIA's own table, and it is largest on the cards that were already fastest. And the edge devices sit an order of magnitude behind the datacenter cards, which is the honest reason most SO-100 setups keep a GPU on the desk rather than on the robot.

The engine build takes roughly 2 to 5 minutes depending on the GPU, and the engines are specific to the GPU architecture they were built on, so they must be rebuilt for a different card. The --batch-size value is baked in as a static dimension: engines built for one batch size cannot run at another, and changing it means re-running export and build. Budget for this if your inference host is a rented spot instance whose GPU model changes between runs.
- No network in the control loop, so the only latency is the model plus the cameras.
- Nothing to bill by the hour once the card is paid for, and it works when the internet does not, which matters in a workshop more than it sounds.
- One machine to configure. The single most common self-hosted failure, two machines with different ideas about the camera keys, cannot happen.
- Predictable timing. A Jetson Orin at 150.9 ms with the TensorRT pipeline is slow, but a control loop prefers slow and steady to fast and variable.
- Edge devices are an order of magnitude off the datacenter cards in NVIDIA's own table: 150.9 ms on Jetson Orin against 27.9 ms on an H100.
- The robot machine has to carry the full model stack, flash-attn and TensorRT included, instead of a thin ZeroMQ or websocket client.
- You buy a card instead of renting one for an afternoon, and TensorRT engines have to be rebuilt whenever the card changes.
- One local GPU serves one arm, where a server can serve several, as long as none of them has to be fast.
Doing it yourself vs doing it on AY-Robots
The manual path above is complete and it works. What it costs you is environment management: two Python environments, a gated backbone, a serial port, camera indices that renumber on reboot, and a GPU that has to be alive at the moment you want to test. Here is the same goal, both ways.
- Pull the checkpoint from wherever the trainer wrote it, with the config and the statistics file, not just the weights.
- Build an inference environment: CUDA, uv, an FFmpeg below 8 for torchcodec, and the model stack with its submodules.
- Request access to the gated backbone and put a Hugging Face token on that machine.
- Verify open-loop against a training episode and record the MSE and MAE as your baseline before anything touches hardware.
- Build a second, smaller environment on the robot machine for the client and the serial driver.
- Enumerate cameras, map their keys to what the checkpoint expects, and write the language instruction the episodes were labelled with.
- Start the server, tunnel if it is remote, then run 60 seconds with a hand on the power switch.
- Read the cadence report, and shut the GPU down when you walk away.
The most common self-hosted failure is not exotic: the robot client and the policy server end up with different ideas about the camera keys, because they were configured on different days on different machines. Write both configurations into one file and read it from both sides.
- Pick the trained policy from the run that produced it. Checkpoints are written to object storage by the trainer, so there is nothing to copy by hand.
/api/inference/podauto-provisions a cloud GPU pod that serves that policy, and the local robot client talks to that endpoint.- Base checkpoints come from the vendors:
nvidia/GR00T-N1.7-3B,nvidia/GR00T-N1.5-3Bandlerobot/pi05_base. ACT has no base model at all, because it only exists after training on your task. - Pods carry an idle watchdog and destroy themselves after an idle period, so a forgotten session does not bill silently.
- The same operations are available from the terminal via the CLI and to AI agents via the MCP server, and the desktop side is documented in the client guide.
The key matching is still yours. A hosted pod does not know that your wrist camera was called cam18 during recording and wrist during deployment. And the network is still in the loop, which is the subject of the honest limits section below.

Where none of this helps
Worth saying plainly, because the failure is silent and gets blamed on the model. Inference latency on this platform runs from 20 ms per action step for ACT to 485 ms for Pi0.5, with GR00T N1.7 at 152 ms and SmolVLA at 245 ms. Those are the numbers before you add anything. A public-internet round trip added to a 485 ms step does not make the policy worse at the task; it makes the arm arrive late, over and over, which looks identical to a bad policy.
- Remote inference is viable for slow pick-and-place. It is not viable for fast reactive motion, and no amount of chunking hides a link that is genuinely far away.
- Nothing here rescues a dataset problem. A policy that only works under the lighting it was recorded in is a recording issue, and recording is where it gets fixed.
- A LeRobot v3.0 dataset crashes the GR00T loader; it has to be converted down to v2.1 first, which is a training-side problem that surfaces as deployment-side confusion. The dataset format is worth checking before you blame the checkpoint.
- GR00T N1.5, GR00T N1.7 and Pi0.5 are cloud-only on this platform. SmolVLA and ACT also run locally, which makes them the reasonable first target if you want the whole loop on your desk.
- GR00T's fine-tuning entry point exposes no seed, so two GR00T runs on identical data are not bit-for-bit identical. Do not chase a deployment difference that is really run-to-run variance.
The failure modes, with the checks in order
Arm not detected, gripper does not close, policy freezes mid-motion, loss falls but nothing moves. Each one is a page with the diagnostic sequence rather than a list of guesses.
Open the fix indexFrequently asked questions
My checkpoint loads and the arm moves, but it moves to the wrong place. What is wrong?▾
Almost always normalization or key mapping, not the weights. Check that the statistics travelled with the weights: GR00T keeps them in statistics.json, lerobot in policy_preprocessor.json and policy_postprocessor.json plus their normalizer and unnormalizer safetensors, openpi in the assets directory next to params. If those are present, check the camera keys next. A policy that gets the wrong image for a view still produces confident, smooth, wrong motion.
Do I have to run open-loop evaluation before I run on hardware?▾
No, but it is the cheapest test you have. Predicting on an episode that was in the training set and comparing to ground truth separates a broken checkpoint from a broken robot setup in about a minute. NVIDIA's guidance is to read the plot overlap first and the error trend across checkpoints second, and deliberately not to chase a fixed target MSE, because that number does not transfer between datasets.
What should I set the execution horizon to?▾
In Isaac-GR00T it has to be at or below the model's predicted chunk length; the base GR00T N1.7 checkpoint predicts 40 steps, and fine-tuned checkpoints can differ. Isaac-GR00T uses 16 as the default for open-loop evaluation and calls 8 common for real-time deployment where the plan is refreshed often. Longer means fewer inference calls and more commitment to a stale plan, shorter means the opposite. Watch out for the name collision: lerobot's --inference.rtc.execution_horizon is how many steps get blended with the previous chunk, not how many are executed before replanning.
Can I run the policy server on a rented GPU and the robot at home?▾
Yes, and all three stacks are built for it: Isaac-GR00T uses a ZeroMQ server-client pair, openpi a websocket, and lerobot ships an asynchronous inference stack with its own policy server and robot client. Whether it is usable depends on the task. Adding a public-internet round trip to a step budget that is already 152 ms for GR00T N1.7 or 485 ms for Pi0.5 is fine for slow pick-and-place and not fine for anything reactive. Measure the round trip before you design around it.
Why does my GR00T checkpoint still want to download a model at inference time?▾
Because the fine-tune does not contain the vision-language backbone. Every GR00T N1.7 checkpoint loads nvidia/Cosmos-Reason2-2B on first use, and that repository is gated. On a machine with no token you get GatedRepoError or a 401, which reads like a checkpoint problem and is not one.
What does it cost to rent a GPU for this?▾
The platform publishes figures for training runs, not for inference pods: on the A100 80 GB or H100 tier a run is 3 to 6 hours at 1.20 to 2.00 USD per hour, about 4 to 12 USD; on the RTX 4090 or 24 GB tier it is 2 to 5 hours at 0.30 to 0.60 USD per hour, about 1 to 3 USD. What the platform does say about inference pods is that they carry an idle watchdog and destroy themselves after an idle period, which is the part that keeps a forgotten session from quietly running all weekend.
The short version
Getting a checkpoint to move an arm is four questions in order. Did the whole checkpoint arrive, config and statistics included? Does the observation dictionary the robot produces use the names the checkpoint expects? Does the checkpoint predict sensible actions on an episode it has already seen? And does the loop close fast enough for the task? Only the last one is about hardware. The first three are file paths and key names, which is why this is a tedious problem rather than a hard one.
If you want the surrounding context, the complete SO-100 guide covers assembly through training, collecting high-quality VLA training data covers the recording side that decides whether deployment can work at all, and the VLA overview covers why these models expect a language instruction in the first place. For the models themselves, the policy comparison has the parameter counts and latencies side by side, and the GR00T N1.7 on SO-100 guide is the training run that produces the checkpoint this page starts from.
Sources
- NVIDIA Isaac-GR00T repository README: N1.7 backbone change, gated Cosmos-Reason2-2B access, torchcodec and FFmpeg constraints
- Understanding the GR00T Policy API: embodiment tags, execution horizon, PolicyServer and PolicyClient, ReplayPolicy
- Isaac-GR00T: finetuning and evaluating models on the SO100/SO101 robot, open-loop and closed-loop evaluation
- Isaac-GR00T: fine-tune on custom embodiments (NEW_EMBODIMENT), modality config, open_loop_eval parameters and the symptom-to-cause table
- GR00T Deployment and Inference Guide: TensorRT full pipeline, per-device latency benchmarks, engine build constraints
- The SO-100 modality.json used by GR00T: state and action index ranges, video original_key mapping
- LeRobot: Policy Deployment (lerobot-rollout), strategies, inference backends, cadence reporting
- LeRobot GR00T policy: N1.5 removal notice, training command and the hardware rollout command
- LeRobot: Rename Map and Empty Cameras, the observation key bridge
- LeRobot Pi0.5 policy: --policy.path vs --policy.pretrained_path and quantile statistics
- LeRobot asynchronous inference: policy server, robot client, actions_per_chunk and chunk_size_threshold
- LeRobot: imitation learning on real-world robots, training output paths and checkpoint upload
- openpi: running models remotely, serve_policy.py and the websocket client policy
- nvidia/GR00T-N1.7-3B model card and repository file listing
- Real-Time Execution of Action Chunking Flow Policies (Black, Galliker, Levine, June 2025)
Sources
- NVIDIA Isaac-GR00T repository README: N1.7 backbone change, gated Cosmos-Reason2-2B access, torchcodec and FFmpeg constraints
- Understanding the GR00T Policy API: embodiment tags, execution horizon, PolicyServer and PolicyClient, ReplayPolicy
- Isaac-GR00T: finetuning and evaluating models on the SO100/SO101 robot, open-loop and closed-loop evaluation
- Isaac-GR00T: fine-tune on custom embodiments (NEW_EMBODIMENT), modality config, open_loop_eval parameters and the symptom-to-cause table
- GR00T Deployment and Inference Guide: TensorRT full pipeline, per-device latency benchmarks, engine build constraints
- The SO-100 modality.json used by GR00T: state and action index ranges, video original_key mapping
- LeRobot: Policy Deployment (lerobot-rollout), strategies, inference backends, cadence reporting
- LeRobot GR00T policy: N1.5 removal notice, training command and the hardware rollout command
- LeRobot: Rename Map and Empty Cameras, the observation key bridge
- LeRobot Pi0.5 policy: --policy.path vs --policy.pretrained_path and quantile statistics
- LeRobot asynchronous inference: policy server, robot client, actions_per_chunk and chunk_size_threshold
- LeRobot: imitation learning on real-world robots, training output paths and checkpoint upload
- openpi: running models remotely, serve_policy.py and the websocket client policy
- nvidia/GR00T-N1.7-3B model card and repository file listing
- Real-Time Execution of Action Chunking Flow Policies (Black, Galliker, Levine, June 2025)
Ready for high-quality robotics data?
AY-Robots connects your robots to skilled operators worldwide.
Get Started