
Una guida pratica e testata per il fine-tuning di NVIDIA GR00T N1.7 su un dataset LeRobot SO-100: flag reali, modality.json, il requisito v2.1, il costo di un'esecuzione e le insidie.
NVIDIA fornisce un esempio di fine-tuning per esattamente il braccio che probabilmente possiedi. All'interno del repository Isaac-GR00T c'è una cartella chiamata demo_data/cube_to_bowl_5: cinque episodi, 4.148 frame a 30 fps, già scritti come LeRobot v2.1, con una configurazione di modalità corrispondente sotto examples/SO100/. Il suo meta/info.json riporta robot_type: so101_follower, che in LeRobot è la stessa classe di configurazione di so100_follower. Questo è davvero utile, perché significa che il percorso di riferimento per GR00T N1.7 su un braccio hobby a sei gradi di libertà è mantenuto dalle persone che hanno scritto il modello. Non è una demo umanoide ridimensionata, è lo stesso braccio.
La cattiva notizia è la distanza tra Ho registrato 60 episodi e il braccio esegue il compito. Ci sono circa sei punti in cui questa pipeline fallisce silenziosamente piuttosto che rumorosamente, e quattro di essi si trovano in file che la maggior parte delle persone non apre mai: meta/modality.json, la configurazione dei dati Python, meta/relative_stats.json, e la stringa di versione del dataset stesso. Questa guida illustra il percorso manuale da un capo all'altro con i comandi reali, quindi mostra lo stesso lavoro come un modulo su AY-Robots. Tutto quanto segue è stato verificato rispetto al ramo principale di Isaac-GR00T al 20 agosto 2026 (la linea di rilascio n1.7) e lerobot 0.6.1, pubblicato su PyPI il 3 agosto 2026. L'upstream si muove velocemente, e dove un flag è stato rinominato, questo articolo lo indica.
Cosa devi sapere prima di iniziare
- •Il fine-tuning di GR00T N1.7 richiede 40 GB o più di VRAM. NVIDIA raccomanda nodi H100 o L40. Una RTX 4090 da 24 GB non sarà sufficiente per questo lavoro, anche se addestrerà SmolVLA e ACT.
- •Il dataset deve essere LeRobot v2 (v2.0 o v2.1) più un meta/modality.json specifico per GR00T. Un dataset LeRobot v3.0 non si carica e deve essere convertito a una versione precedente.
- •Il punto di ingresso è gr00t/experiment/launch_finetune.py, una CLI tyro. Non ha il flag --seed, quindi le esecuzioni non sono riproducibili bit per bit.
- •Per un braccio personalizzato il tag di embodiment è NEW_EMBODIMENT, e quel tag rende --modality-config-path obbligatorio.
- •La ricetta SO-100 fornita prevede le giunture del braccio come delta RELATIVI e la pinza come target ASSOLUTO. Invertire questa coppia è un fallimento silenzioso, non un errore.
- •Su AY-Robots lo stesso lavoro è un modulo: 20000 passi, batch 32, tasso di apprendimento 1e-4, circa 4 a 12 USD sul livello A100 80 GB o H100.
Cos'è realmente GR00T N1.7
GR00T N1.7 è un modello visione-linguaggio-azione con l'architettura a doppio sistema descritta nell'articolo originale GR00T N1: un modulo visione-linguaggio che legge le telecamere e l'istruzione, e un trasformatore a diffusione che trasforma ciò in un blocco di comandi motori continui. N1.7 ha sostituito la prima metà. Il backbone Eagle di N1.6 è stato rimosso, sostituito da nvidia/Cosmos-Reason2-2B su un'architettura Qwen3-VL, e il modello è stato pre-addestrato su circa 20.000 ore di video umani egocentrici, oltre ai dati del robot. La documentazione di NVIDIA indica la cifra di 20.854 ore e riporta che passare da 1k a 20k ore più che raddoppia il completamento medio delle attività.
Anche la seconda metà è cambiata, in modi che contano per la tua esecuzione. L'action head è sceso da 32 strati di diffusione a 16, il chunk di azione previsto è cresciuto da 16 a 40 passi, e la larghezza massima di stato e azione è passata da 29 a 132. Questi tre numeri provengono dal changelog nel README del repository; il post di lancio di NVIDIA descrive ancora il Sistema 1 come un DiT a 32 strati, quindi dove i due non concordano, fidati del repository che stai per clonare. Le azioni sono espresse di default in uno spazio relativo dell'end-effector, come delta dalla posa attuale piuttosto che come target assoluti, il che permette ai priori di manipolazione appresi da video umani di trasferirsi nel controllo del robot. L'head stesso è un trasformatore a diffusione con flow-matching, della stessa famiglia di Pi0.5 ma con un backbone diverso davanti. Se sei ancora sulla generazione precedente, N1.7 contro N1.5 copre se l'aggiornamento giustifica il rifacimento della tua pipeline.
| Proprietà | Valore | Fonte |
|---|---|---|
| Parametri | 3,000,000,000 | Scheda modello Hugging Face |
| Backbone visione-linguaggio | nvidia/Cosmos-Reason2-2B (Qwen3-VL), gated su Hugging Face | README del repo |
| Action head | Trasformatore a diffusione con flow-matching, 16 strati (N1.6 ne aveva 32) | README del repo |
| Orizzonte di azione previsto | 40 passi per il checkpoint base (N1.6 ne aveva 16) | getting_started/policy.md e README del repo |
| Larghezza massima stato e azione | 132 (N1.6 ne aveva 29) | README del repo |
| Licenza codice | Apache 2.0 | Repository Isaac-GR00T |
| Licenza pesi | NVIDIA Open Model License Agreement | scheda modello |
| Latenza, H100 80 GB, PyTorch eager, 4 passi di denoising, 1 telecamera | 85.8 ms end-to-end, 11.7 Hz | tabella tempi scheda modello |
| Stesso hardware, pipeline completa TensorRT | 27.9 ms end-to-end, 35.9 Hz | tabella tempi scheda modello |
| Latenza citata da AY-Robots per il suo GR00T N1.7 servito | 152 ms per passo di azione | Catalogo policy AY-Robots |
Queste ultime tre righe spiegano la maggior parte della delusione riportata dalle persone. Il valore principale di 27.9 ms si riferisce a un motore TensorRT su una H100 con una telecamera e quattro passi di denoising. PyTorch puro sulla stessa scheda è 85.8 ms, e la scheda del modello indica un divario di 3.08x. Nessuno dei due numeri include un livello di serving, una seconda telecamera o un salto di rete. I 152 ms per passo di azione citati da AY-Robots per il suo GR00T N1.7 servito sono la cifra con il serving incluso nel ciclo, e un round trip su internet pubblico si aggiunge a questo. Maggiori informazioni su questo alla fine. Per i numeri relativi ad altri modelli, GR00T N1.7 contro Pi0.5 e GR00T N1.7 contro SmolVLA li presentano fianco a fianco.

Cosa serve all'esecuzione prima di digitare qualsiasi cosa
| Requisito | Fine-tuning | Inferenza |
|---|---|---|
| VRAM, indicazioni NVIDIA | 40 GB o più, H100 o L40 raccomandati | 16 GB o più, una RTX 4090 funziona |
| Python e CUDA su dGPU | 3.12 e CUDA 12.8 | 3.12 e CUDA 12.8 |
| Backend video | torchcodec 0.8.0, solo FFmpeg da 4 a 7 | uguale |
| Formato dataset | LeRobot v2 più meta/modality.json | non applicabile |
| Accesso Hugging Face | approvato per nvidia/Cosmos-Reason2-2B | uguale |
| Altri strumenti | git-lfs e uv | uv |
| Livello GPU AY-Robots per il trainer groot1.7 | A100 80 GB o H100 80 GB | pod fornito automaticamente |
Ogni checkpoint GR00T, inclusa la base nvidia/GR00T-N1.7-3B, carica nvidia/Cosmos-Reason2-2B al primo utilizzo, e quel repository è con accesso controllato. Il README indica esattamente l'errore: il caricamento del modello fallisce con un GatedRepoError / 401 Client Error. Ciò che non menziona è quando ciò accade, ovvero dopo aver noleggiato la scheda e l'esecuzione è iniziata. Richiedi l'accesso sulla pagina del modello, quindi esegui uv run huggingface-cli login o esporta HF_TOKEN prima di noleggiare qualsiasi cosa.
Passo 0: gli episodi stessi
Tutto ciò che segue presuppone che tu abbia già registrato degli episodi. In caso contrario, quello è il vero primo passo ed è quello che decide quanto buono possa essere il risultato, perché apprendimento per imitazione non può recuperare informazioni che non sono presenti nei dati. Calibra prima entrambi i bracci, poi guida il follower con un braccio leader mentre lerobot-record scrive i file parquet e i flussi della telecamera. Se la calibrazione è disattivata, i valori delle giunzioni nel tuo dataset descrivono un robot leggermente diverso da quello che in seguito eseguirà la policy, e nessuna quantità di addestramento può risolvere questo problema.
lerobot-record \
--robot.type=so100_follower \
--robot.port=/dev/ttyACM0 \
--robot.id=my_follower_arm \
--robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30}, wrist: {type: opencv, index_or_path: 2, width: 640, height: 480, fps: 30}}" \
--teleop.type=so100_leader \
--teleop.port=/dev/ttyACM1 \
--teleop.id=my_leader_arm \
--dataset.repo_id=${HF_USER}/cube-into-bowl \
--dataset.num_episodes=60 \
--dataset.single_task="put the cube in the yellow bowl" \
--display_data=trueIl consiglio di LeRobot è di registrare almeno 50 episodi con circa 10 per posizione dell'oggetto, mantenere le telecamere fisse e mantenere il comportamento di presa coerente. Aggiungi variazioni in seguito, non all'inizio. La regola pratica da ricordare: se non potessi eseguire il compito tu stesso solo dalle immagini della telecamera, la policy non può farlo. Per la configurazione specifica del braccio, guida introduttiva SO-100 e la pagina LeRobot SO-100 coprono porte, calibrazione e indici delle telecamere. Su AY-Robots puoi anche farlo via internet dal browser usando la teleoperazione e registrare direttamente dalla sessione.
Passo 1: il dataset deve essere LeRobot v2.1
Questo è l'ostacolo più comune. L'attuale CODEBASE_VERSION di LeRobot su main è v3.0, quindi qualsiasi cosa registri oggi con una toolchain attuale risulterà come v3.0. Il loader di GR00T si aspetta v2. Il repository è esplicito sul perché: molti dataset upstream come DROID, LIBERO e Bridge sono pubblicati in v2, e il supporto nativo per entrambi è pianificato ma non ancora rilasciato. Quindi la conversione spetta a te, e viene eseguita nella sua virtualenv per una ragione concreta: scripts/lerobot_conversion porta con sé il proprio pyproject che richiede Python 3.10 o 3.11 e blocca lerobot a un commit git specifico, mentre Isaac-GR00T stesso richiede Python 3.12. Installa il convertitore dalla root del repository e otterrai il pacchetto gr00t invece, che è l'errore di cui il suo README avverte. Se non hai familiarità con il formato, la voce del glossario dataset LeRobot spiega cosa contiene effettivamente.
# from the Isaac-GR00T repo root
cd scripts/lerobot_conversion
uv venv
source .venv/bin/activate
uv pip install -e . --verbose
# pulls the dataset from the Hub and writes a v2.1 copy into the default cache
python convert_v3_to_v2.py --repo-id <your-hf-user>/<your-dataset>
# or, back in the repo root, keep it next to the SO-100 example
uv run --project scripts/lerobot_conversion \
python scripts/lerobot_conversion/convert_v3_to_v2.py \
--repo-id <your-hf-user>/<your-dataset> \
--root examples/SO100/my_dataset_lerobotSe il dataset v3.0 esiste già localmente, lo script costruisce il layout v2.1 accanto ad esso e poi scambia: l'originale viene spostato in una cartella sorella con la versione aggiunta, <name>_v3.0, e la copia convertita prende il percorso originale. (La docstring dello script stesso chiama quella cartella _v30; il codice aggiunge la stringa della versione, quindi ciò che si ottiene effettivamente è _v3.0.) Seconda sorpresa: l'output finisce sempre sotto <root>/<repo-id>, quindi --root examples/SO100/my_dataset_lerobot ti darà examples/SO100/my_dataset_lerobot/<your-hf-user>/<your-dataset>, e quel percorso più lungo è quello che --dataset-path richiederà in seguito. Quando un job di training rifiuta il tuo dataset per motivi di versione, la pagina del dataset rifiutato come v3 elenca i sintomi esatti.
La struttura che GR00T desidera dopo la conversione è il layout classico v2: meta/info.json, meta/episodes.jsonl, meta/tasks.jsonl, file parquet sotto data/chunk-000/, file MP4 sotto videos/chunk-000/observation.images.
Passo 2: modality.json, i sei numeri che decidono tutto
In un dataset LeRobot lo stato del robot e l'azione sono memorizzati come array float32 piatti. Per un SO-100 entrambi hanno forma [6]: cinque giunti del braccio e una pinza. Il dataset demo li nomina shoulder_pan.pos, shoulder_lift.pos, elbow_flex.pos, wrist_flex.pos, wrist_roll.pos, gripper.pos, ma quei nomi risiedono in info.json e nulla nel file parquet indica quale indice corrisponde a cosa. meta/modality.json fornisce quella mappatura, e GR00T non si addestrerà senza di essa. Ecco quello che il repository fornisce per l'SO-100, testualmente.
{
"state": {
"single_arm": {
"start": 0,
"end": 5
},
"gripper": {
"start": 5,
"end": 6
}
},
"action": {
"single_arm": {
"start": 0,
"end": 5
},
"gripper": {
"start": 5,
"end": 6
}
},
"video": {
"front": {
"original_key": "observation.images.front"
},
"wrist": {
"original_key": "observation.images.wrist"
}
},
"annotation": {
"human.task_description": {
"original_key": "task_index"
}
}
}Copiato nel tuo dataset convertito in meta/modality.json e rinomina le chiavi video con i nomi effettivi delle tue telecamere. Se hai registrato con una singola telecamera dall'alto chiamata top, allora original_key è observation.images.top e il nome descrittivo è quello a cui farà riferimento la tua configurazione dei dati. I due devono concordare, e nessuno dei due controlla l'altro per te. L'annotazione del linguaggio è peggiore, perché la stessa chiave deve apparire in tre posti.
| Livello | File | Formato SO-100 utilizzato nel repository |
|---|---|---|
| Colonna Parquet | data/chunk-*/episode_*.parquet | annotation.human.task_description |
| Chiave modality.json | meta/modality.json, under "annotation", without the annotation. prefix | human.task_description |
| modality_keys nella configurazione dei dati | your so100_config.py | annotation.human.task_description |
I segmenti dopo annotation. sono scelti da chi ha creato il dataset. I dati demo SO-100 usano annotation.human.task_description; LIBERO e SimplerEnv usano annotation.human.action.task_description. Entrambi sono validi. Se hai copiato una configurazione da un esempio LIBERO e l'hai puntata alla tua registrazione SO-100, il canale del linguaggio non si risolve in nulla e il modello si addestra su un'istruzione vuota. La perdita diminuisce comunque. La policy fa comunque qualcosa. Semplicemente ignora ciò che gli hai detto di fare.
Passo 3: la configurazione dei dati, braccio relativo e pinza assoluta
La configurazione della modalità è un file Python anziché JSON, perché decide anche come viene rappresentato ogni gruppo di azioni. Questa è la parte del flusso di lavoro N1.7 che non esisteva nella stessa forma in N1.5, ed è la parte che merita di essere letta due volte. La configurazione SO-100 fornita prevede le cinque articolazioni del braccio come RELATIVE delta dallo stato attuale e la pinza come posizione target ASSOLUTA, perché un segnale binario aperto-o-chiuso si comporta meglio come target che come delta.
from gr00t.configs.data.embodiment_configs import register_modality_config
from gr00t.data.embodiment_tags import EmbodimentTag
from gr00t.data.types import (
ActionConfig, ActionFormat, ActionRepresentation, ActionType, ModalityConfig,
)
so100_config = {
"video": ModalityConfig(
delta_indices=[0], # current frame only
modality_keys=["front", "wrist"], # must match modality.json
),
"state": ModalityConfig(
delta_indices=[0],
modality_keys=["single_arm", "gripper"],
),
"action": ModalityConfig(
delta_indices=list(range(0, 16)), # predict 16 future steps
modality_keys=["single_arm", "gripper"],
action_configs=[
ActionConfig(rep=ActionRepresentation.RELATIVE, # arm joints
type=ActionType.NON_EEF,
format=ActionFormat.DEFAULT),
ActionConfig(rep=ActionRepresentation.ABSOLUTE, # gripper
type=ActionType.NON_EEF,
format=ActionFormat.DEFAULT),
],
),
"language": ModalityConfig(
delta_indices=[0],
modality_keys=["annotation.human.task_description"],
),
}
register_modality_config(so100_config, embodiment_tag=EmbodimentTag.NEW_EMBODIMENT)Due dettagli qui ti costeranno una giornata se non li conosci. Primo, action_configs è posizionale: la documentazione richiede la stessa lunghezza e lo stesso ordine di modality_keys, ed è schietta riguardo alla conseguenza di un errore, ovvero che la rappresentazione sbagliata viene applicata silenziosamente. La tua pinza viene addestrata come un delta e il tuo braccio come un target assoluto, e non c'è alcun messaggio di errore. Secondo, register_modality_config asserisce che il tag non è già registrato, quindi una seconda configurazione NEW_EMBODIMENT nello stesso processo Python si interrompe con Embodiment tag ... already registered. Non puoi importare due di queste in un unico script. Una terza regola viene applicata in seguito, al momento del deployment: l'azione delta_indices deve essere l'intervallo contiguo che inizia da zero. Una finestra sparsa come [0, 4, 8] viene rifiutata, perché tutto a valle indicizza il blocco previsto linearmente e altrimenti eseguirebbe le righe sbagliate.
Le statistiche di normalizzazione, in particolare meta/relative_stats.json, vengono calcolate per la lunghezza dell'orizzonte che avevi quando le hai generate. Accorcia l'orizzonte delle azioni da 16 a 8 senza rigenerare e l'addestramento si interrompe con IndexError: boolean index did not match indexed array ... dimension is 8 but corresponding boolean dimension is 16. La soluzione è un solo comando: python gr00t/data/stats.py --dataset-path <path> --embodiment-tag NEW_EMBODIMENT --modality-config-path examples/SO100/so100_config.py. Eseguilo dopo ogni modifica a delta_indices.
Passo 4: l'ambiente
N1.7 ha spostato il repository su e Python 3.12. Il vecchio percorso conda più pip install -e . esiste ancora in una sezione compressa del README, ma avverte che le dipendenze GPU, inclusi flash-attn e TensorRT, potrebbero richiedere un'installazione manuale. Usa uv a meno che tu non abbia una ragione specifica per non farlo. Per quanto riguarda flash-attn, un dettaglio evita confusione: vedrai Installing flash-attn stampato ad ogni uv run. Non si sta ricostruendo. uv sta ri-validando una wheel bloccata tramite URL che è già nella cache, e ci vogliono due o tre secondi.
- 1Installa git-lfs, quindi clona con i sottomoduli
git-lfs è richiesto, non opzionale. Senza di esso, i file parquet in demo_data/ vengono scaricati come stub di puntatore e l'esecuzione della demo fallisce su un dataset che sembra presente nell'elenco dei file.
bashsudo apt install git-lfs && git lfs install git clone --recurse-submodules https://github.com/NVIDIA/Isaac-GR00T cd Isaac-GR00T - 2Installa uv e sincronizza l'ambiente
L'installazione predefinita scarica le dipendenze GPU, inclusi flash-attn e TensorRT. Su un'immagine A100 o H100 nuova, questo è il singolo passo più lungo, quindi fallo prima di prestare attenzione a qualsiasi altra cosa.
bashcurl -LsSf https://astral.sh/uv/install.sh | sh sudo apt-get update && sudo apt-get install -y ffmpeg uv sync --python 3.12 uv run python -c "import gr00t; print('GR00T installed successfully')" - 3Autenticati con Hugging Face
Fallo prima del primo avvio dell'addestramento, non dopo che fallisce dopo otto minuti.
bashuv run huggingface-cli login # or: export HF_TOKEN=<your_token> - 4Verifica di integrità sui dati demo SO-100 forniti
Prima di toccare la tua registrazione, esegui 2000 passi su demo_data/cube_to_bowl_5. Sono cinque episodi, finisce velocemente e dimostra l'ambiente piuttosto che i tuoi dati. Se questa esecuzione fallisce, nulla di ciò che farai al tuo dataset aiuterà.
bashCUDA_VISIBLE_DEVICES=0 uv run python \ gr00t/experiment/launch_finetune.py \ --base-model-path nvidia/GR00T-N1.7-3B \ --dataset-path demo_data/cube_to_bowl_5 \ --embodiment-tag NEW_EMBODIMENT \ --modality-config-path examples/SO100/so100_config.py \ --num-gpus 1 \ --output-dir /tmp/test_finetune \ --max-steps 2000 \ --global-batch-size 32 \ --dataloader-num-workers 4
FFmpeg 8. torchcodec 0.8.0 supporta solo FFmpeg da 4 a 7, e Ubuntu 25.10 e versioni successive distribuiscono la versione 8. L'errore è Could not load libtorchcodec, che sembra un'installazione interrotta piuttosto che un conflitto di versione. Installa un runtime più vecchio, ad esempio conda install -c conda-forge 'ffmpeg<8', e metti le sue librerie su LD_LIBRARY_PATH. CUDA_HOME non è impostato. Il fine-tuning fallisce completamente. Esegui bash scripts/deployment/dgpu/install_deps.sh una volta, o semplicemente export CUDA_HOME=/usr/local/cuda.
Passo 5: il comando di fine-tuning e i valori predefiniti reali dei suoi flag
Sostituisci il dataset demo con il tuo e aggiungi le opzioni che desideri. Di seguito è riportata la forma completa che il repository utilizza nel suo tutorial sul nuovo embodiment, inclusi i flag di aumento e checkpointing che l'esempio breve del README omette. Questo è in senso stretto: il backbone linguistico e l'encoder visivo rimangono congelati, e ciò che viene addestrato sono il proiettore e la testa d'azione di diffusione.
export NUM_GPUS=1
CUDA_VISIBLE_DEVICES=0 uv run python \
gr00t/experiment/launch_finetune.py \
--base-model-path nvidia/GR00T-N1.7-3B \
--dataset-path ./my_dataset_lerobot \
--embodiment-tag NEW_EMBODIMENT \
--modality-config-path examples/SO100/so100_config.py \
--num-gpus $NUM_GPUS \
--output-dir /tmp/so100 \
--save-total-limit 5 \
--save-steps 2000 \
--max-steps 20000 \
--use-wandb \
--global-batch-size 32 \
--color-jitter-params brightness 0.3 contrast 0.4 saturation 0.5 hue 0.08 \
--dataloader-num-workers 4| Flag | Predefinito in FinetuneConfig | Cosa fa |
|---|---|---|
| --global-batch-size | 64 | Batch totale su tutte le GPU prima dell'accumulo del gradiente. Gli esempi forniti utilizzano 32. |
| --learning-rate | 1e-4 | Lo stesso valore che AY-Robots invia per il suo trainer groot1.7. |
| --max-steps | 10000 | Passi totali dell'ottimizzatore. Anche il wrapper examples/finetune.sh ha un valore predefinito di 10000. |
| --gradient-accumulation-steps | 1 | Moltiplica il batch effettivo. Valori superiori a 1 emettono un avviso che indica la dimensione accumulata. |
| --save-steps and --save-total-limit | 1000 and 5 | Frequenza dei checkpoint e quanti ne vengono mantenuti. Quelli più vecchi vengono eliminati. |
| --weight-decay and --warmup-ratio | 1e-5 and 0.05 | Impostato esplicitamente anche da examples/finetune.sh. |
| --state-dropout-prob | 0.2 in the CLI, 0.8 in the model config | Elimina casualmente lo stato propriocettivo durante l'addestramento. Abbassalo se il tuo compito si basa sullo stato. |
| --tune-llm and --tune-visual | False and False | Il backbone rimane congelato per impostazione predefinita. |
| --tune-projector and --tune-diffusion-model | True and True | Il proiettore e la testa d'azione di diffusione sono ciò che viene effettivamente addestrato. |
| --use-percentiles | True | Normalizza con q01 e q99 invece dei valori minimi e massimi grezzi. |
| --dataloader-num-workers | 2 | Il loader è basato su CPU per design. Gli esempi lo aumentano a 4. |
| --seed | does not exist | Non esiste un flag seed su questa CLI. |
Quell'ultima riga non è un errore di battitura. launch_finetune.py è una CLI tyro generata da una dataclass, e quella dataclass non ha un campo seed. Il README nota separatamente una varianza del 5-6 percento tra le esecuzioni causata dall'aumento non deterministico delle immagini. Due esecuzioni con flag identici non produrranno checkpoint identici, il che è molto importante quando si cerca di decidere se una modifica dell'iperparametro ha aiutato o se si è stati fortunati. Per confronto, il trainer di lerobot di default usa seed 1000, e la ricetta LeRobot GR00T passa --seed=42 esplicitamente.
Il fine-tuning viene eseguito con eval_strategy="no", quindi non c'è alcuna curva di perdita di validazione. Si ottiene solo la perdita di training e nient'altro. La guida per la nuova incarnazione ti dice di attivarla con --eval-strategy steps --eval-steps 500, ma quel flag non esiste su launch_finetune.py: la CLI è generata da tyro dalla dataclass FinetuneConfig, e eval_strategy, eval_steps e eval_batch_size sono invece campi di TrainingConfig. I loro valori predefiniti sono "no", 500 e 2. Per raggiungerli, usa il punto di ingresso più completo gr00t/experiment/launch_train.py, dove il flag annidato è --training.eval-strategy. In ogni caso, una perdita di training in calo da sola dice molto poco sulla generalizzazione, che è esattamente la situazione descritta su la perdita diminuisce ma la policy non fa nulla.
Quanto costa un'esecuzione di 20000 passi
GR00T N1.7 richiede una scheda da 80 GB, quindi la questione dei costi ha una risposta ristretta. Su AY-Robots il trainer groot1.7 viene eseguito sul livello A100 80 GB o H100 80 GB, dove un'esecuzione richiede da 3 a 6 ore a 1.20-2.00 USD all'ora sul mercato spot. Questo equivale a circa 4-12 USD per il lavoro predefinito di 20000 passi. Lo stesso compito su SmolVLA o ACT si esegue su una scheda da 24 GB a 0.30-0.60 USD all'ora e 1-3 USD per esecuzione. Questo è il vero compromesso: GR00T costa circa quattro volte di più per tentativo, e non puoi eseguirlo sulla 4090 sotto la tua scrivania.
| Modello | Livello GPU | Durata tipica | Costo tipico | Episodi minimi |
|---|---|---|---|---|
| GR00T N1.7 | A100 80 GB or H100 80 GB | 3 to 6 hours | 4 to 12 USD | 50 |
| GR00T N1.5 | A100 80 GB or H100 80 GB | 3 to 6 hours | 4 to 12 USD | 50 |
| Pi0.5 | A100 80 GB or H100 80 GB | 3 to 6 hours | 4 to 12 USD | 50 |
| SmolVLA | RTX 4090 or any 24 GB card | 2 to 5 hours | 1 to 3 USD | 30 |
| ACT | RTX 4090 or any 24 GB card | 2 to 5 hours | 1 to 3 USD | 50 |
Il minimo di 50 episodi è un limite inferiore, non un obiettivo. Le FAQ di NVIDIA sono più esigenti: circa 100 traiettorie per un semplice pick and place a posizione fissa, 500 o più per scene complesse o a più passaggi, e da 100 a 500 per manipolazioni fini. Se hai solo 20 episodi, dedica il pomeriggio alla registrazione piuttosto che la sera alla messa a punto. La guida alla raccolta dati copre ciò che distingue un episodio utile da uno sprecato, registra il tuo primo dataset è la versione breve, e raccolta dati SO-100 è quella specifica per il braccio.
Passo 6: valutazione a ciclo aperto prima di toccare il braccio
Non applicare un nuovo checkpoint su un braccio fisico per scoprire se l'addestramento ha funzionato. Esegui prima la valutazione a ciclo aperto. Riproduce un episodio registrato, chiede al modello le azioni ad ogni passo e traccia la previsione rispetto alla verità di base con MSE e MAE. Non costa nulla e rileva gli errori di mappatura dai passaggi 2 e 3.
uv run python gr00t/eval/open_loop_eval.py \
--dataset-path ./my_dataset_lerobot \
--embodiment-tag NEW_EMBODIMENT \
--model-path /tmp/so100/checkpoint-20000 \
--traj-ids 0 \
--execution-horizon 16 \
--steps 400 \
--modality-keys single_arm gripperIl repository si rifiuta deliberatamente di pubblicare un MSE target per dati personalizzati, ed è la scelta giusta: il numero dipende dalle tue unità di azione, dal tuo compito e dalla dimensione del tuo dataset, quindi una soglia copiata dal braccio di qualcun altro non significa nulla. Ciò che è significativo è la tendenza. Ecco l'esecuzione di riferimento documentata dal repository su una singola H100 con il dataset demo di cinque episodi e 2000 passi.
| Checkpoint | MSE medio su traj 0 | MAE medio su traj 0 |
|---|---|---|
| 500 | 87.5 | 5.63 |
| 1000 | 25.4 | 3.30 |
| 1500 | 13.2 | 2.18 |
| 2000 | 10.0 | 1.76 |
La forma è il segnale, non i valori assoluti. L'errore dovrebbe diminuire costantemente man mano che i passi di addestramento si accumulano. Mediato su tutti e cinque gli episodi di addestramento anziché sulla sola traiettoria 0, il checkpoint finale del repository ha ottenuto circa 7.5 MSE e 1.5 MAE, quindi anche l'esecuzione di riferimento si legge in modo diverso a seconda degli episodi che si mediano. Registra la tua baseline sul comando demo non modificato prima di cambiare qualsiasi cosa sui tuoi dati: se non riesci a riprodurre un'esecuzione nota come buona, non puoi distinguere un errore di configurazione da un problema di dati. Il repository mappa anche i sintomi comuni alle cause, e ognuno di essi è operativo piuttosto che un bug del modello.
| Sintomo | Causa probabile |
|---|---|
| MSE piatto o in aumento tra i checkpoint | Tasso di apprendimento troppo basso, o i dati non vengono caricati affatto. Controlla --dataset-path e i worker del dataloader. |
| Curva di previsione piatta o costante | Le chiavi di modality.json o --modality-config-path non corrispondono. Le chiavi di azione non sono mappate. |
| MSE enorme, o perdita NaN durante l'addestramento | Normalizzazione di azione e stato. Verifica meta/stats e che gli intervalli di azione siano fisicamente plausibili. |
| Buono su traj 0, scarso sugli episodi non inclusi nell'addestramento | Scarsità di dati, non un bug. Cinque episodi demo non possono generalizzare. |
L'altra strada: lerobot-train invece di Isaac-GR00T
L'attuale release di LeRobot, la 0.6.1 su PyPI dal 3 agosto 2026, offre un secondo e piuttosto diverso modo per ottimizzare gli stessi pesi base. LeRobot espone GR00T N1.7 come tipo di policy e lo addestra tramite il suo punto di ingresso lerobot-train. Due cose sono importanti qui. La CLI di LeRobot è un insieme di script della console, quindi qualsiasi cosa tu legga che dica python lerobot/scripts/train.py è obsoleta e non verrà eseguita. E LeRobot ha rimosso completamente il supporto per GR00T N1.5, rifiutando i checkpoint e le configurazioni N1.5 con una nota di migrazione, quindi se hai bisogno di N1.5 tramite LeRobot devi bloccare lerobot==0.5.1, l'ultima release che lo supporta, pubblicata il 7 aprile 2026.
pip install "lerobot[groot]" "lerobot[training]"
hf auth login
lerobot-train \
--dataset.repo_id=$HF_USER/$DATASET_NAME \
--dataset.image_transforms.enable=true \
--policy.type=groot \
--policy.device=cuda \
--policy.base_model_path=nvidia/GR00T-N1.7-3B \
--policy.embodiment_tag=new_embodiment \
--policy.chunk_size=16 \
--policy.n_action_steps=16 \
--policy.use_relative_actions=true \
--policy.relative_exclude_joints='["gripper"]' \
--policy.use_bf16=true \
--seed=42 \
--batch_size=64 \
--steps=20000 \
--save_freq=5000 \
--output_dir=$OUTPUT_DIR| Aspetto | Isaac-GR00T launch_finetune.py | lerobot-train --policy.type=groot |
|---|---|---|
| Versione del dataset | Solo LeRobot v2, conversione richiesta | Dataset LeRobot nativo, nessun downgrade |
| Mappatura delle modalità | meta/modality.json più una configurazione dati Python | nessun modality.json; comportamento impostato dai flag --policy.* sulla riga di comando |
| Seed | nessun flag seed | --seed, LeRobot predefinito 1000 |
| Azioni relative | ActionConfig per chiave nella configurazione dei dati | --policy.use_relative_actions più --policy.relative_exclude_joints |
| Risultati di riferimento pubblicati | Tendenza MSE a ciclo aperto SO-100 sui dati demo | Suite LIBERO, media del 96,5 percento su quattro suite |
| Percorso di deployment | run_gr00t_server.py più eval_so100.py su ZMQ | lerobot-rollout, con chunking in tempo reale (queue_threshold dovrebbe rimanere a o sotto 5) |
- Ogni flag è visibile e modificabile. Puoi sbloccare l'encoder visivo, spostare state_dropout_prob o accorciare l'orizzonte d'azione.
- I grafici a ciclo aperto sono file locali. Confrontare checkpoint-5000 con checkpoint-20000 è un comando shell.
- Non dipendi da alcuna piattaforma che rimanga online, e il checkpoint si trova sul tuo disco in un formato standard.
- Gli esempi di benchmark del repository per LIBERO, SimplerEnv e DROID ti forniscono esecuzioni note e valide da riprodurre prima di fidarti dei tuoi dati.
- L'ambiente è la maggior parte del lavoro. Versione di FFmpeg, CUDA_HOME, git-lfs, il backbone gated, torchcodec: nessuno di questi è un problema del modello e ognuno di essi ferma l'esecuzione.
- La conversione da v3.0 a v2.1 richiede un virtualenv separato con il proprio passaggio di installazione, e riscrive la directory del tuo dataset in loco.
- Il noleggio della GPU inizia a fatturare quando si inizia il debug, non quando inizia l'addestramento, e nulla ferma l'istanza quando l'esecuzione termina.
- Nessun seed significa nessuna riproducibilità bit-per-bit, oltre a una varianza del 5-6 percento tra le esecuzioni dovuta solo all'aumento dei dati.
Due modi per ottenere lo stesso checkpoint
Noleggi la GPU e gestisci ogni passaggio. Realisticamente, la prima volta richiederà un pomeriggio, e venti minuti ogni volta successiva.
- Registra episodi con lerobot-record sul SO-100. Otterrai un dataset LeRobot v3.0.
- Convertilo alla versione v2.1 con scripts/lerobot_conversion/convert_v3_to_v2.py nel suo virtualenv dedicato.
- Scrivi meta/modality.json e una configurazione di modalità Python, registrata sotto EmbodimentTag.NEW_EMBODIMENT.
- Noleggia una scheda da 80 GB, clona con i sottomoduli, sincronizza uv, autenticati con Hugging Face.
- Esegui launch_finetune.py, poi open_loop_eval.py su diversi checkpoint, e confronta l'andamento dell'MSE prima di toccare l'hardware.
- Recupera il checkpoint dalla macchina prima di distruggere l'istanza, quindi costruisci il percorso di servizio al braccio.
Copia il checkpoint dall'istanza noleggiata prima di spegnerla. --save-total-limit 5 significa anche che i checkpoint più vecchi vengono eliminati man mano che l'addestramento procede, quindi il checkpoint che volevi al passo 5000 potrebbe non esistere più al passo 20000.
Lo stesso lavoro, ma come un modulo. Scegli il modello e il dataset, il backend noleggia una GPU sul mercato spot in base alla VRAM richiesta, esegue il trainer e scrive i checkpoint nell'object storage. La guida GR00T N1.7 su SO-100 è esattamente questa combinazione; la matrice di addestramento contiene ogni altra coppia modello-braccio, inclusa GR00T N1.7 sul SO-101.
| Cosa invia il trainer groot1.7 | Valore |
|---|---|
| Dimensione del batch | 32 |
| Tasso di apprendimento | 1e-4 |
| Passi massimi | 20000 |
| Accumulo del gradiente | 1, e ha effetto per questo trainer |
| Parametro aggiuntivo esposto nel modulo | saveSteps |
| Checkpoint di base | nvidia/GR00T-N1.7-3B |
| Formato dataset accettato | LeRobot v2.0 o v2.1 |
Il dataset può provenire da un ID repository di Hugging Face, dalla tua macchina, o da una sessione registrata con il client desktop. L'inferenza è un passaggio separato: la piattaforma predispone un pod che serve la policy, e il tuo client robot locale comunica con quell'endpoint. I pod sono dotati di un watchdog per l'inattività e si distruggono dopo un periodo di inattività, quindi una scheda del browser dimenticata non genera costi durante la notte. Se preferisci non cliccare, le stesse operazioni sono disponibili su la CLI e il server MCP.
GR00T N1.7 e Pi0.5 sono qui solo cloud; solo SmolVLA e ACT funzionano anche localmente. Il requisito v2.1 non scompare, perché un dataset v3.0 deve comunque essere convertito prima che il loader GR00T lo accetti. E nulla scrive per te la semantica del tuo modality.json: se le chiavi della tua telecamera o la tua chiave linguistica sono sbagliate, sono sbagliate su entrambi i percorsi. Consulta la documentazione di addestramento per sapere cosa il backend fa e non fa per tuo conto.

Riportare il checkpoint sul braccio
Isaac-GR00T utilizza una divisione server-client tramite ZMQ. La policy viene eseguita sulla GPU, e un client leggero sulla macchina robot invia osservazioni e riceve blocchi di azioni. L'esempio SO-100 è sufficientemente completo da copiare: avvia run_gr00t_server.py con il tuo checkpoint e --embodiment-tag NEW_EMBODIMENT, quindi esegui eval_so100.py sul lato robot con la porta seriale, l'ID del robot, gli indici della telecamera e l'istruzione linguistica. I nomi delle telecamere in quel comando devono corrispondere ai nomi amichevoli del tuo modality.json, non ai numeri di dispositivo del sistema operativo.
# GPU side
uv run python gr00t/eval/run_gr00t_server.py \
--model-path /tmp/so100/checkpoint-20000 \
--embodiment-tag NEW_EMBODIMENT \
--device cuda:0 \
--host 0.0.0.0 --port 5555
# robot side, from gr00t/eval/real_robot/SO100
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="put the cube in the yellow bowl"Mentre ricolleghi il braccio: l'SO-100 utilizza servomotori bus Feetech STS3215 su una linea da 7.4 V. Alimentarli a 12 V li distrugge, ed è un errore facile se possiedi anche un LeKiwi, la cui base funziona a 12 V mentre il suo braccio no. Controlla l'alimentazione prima della prima accensione, non dopo il fumo. Vedi la pagina hardware dell'SO-100 e SO-100 contro LeKiwi. Se il braccio si accende ma nulla si muove, servo non risponde è il punto di partenza.
Ora la parte onesta su dove viene eseguita la policy, perché l'addestramento e il serving hanno storie hardware diverse. Il fine-tuning richiede 40 GB o più. L'inferenza no: il README la indica a 16 GB o più e nomina esplicitamente la RTX 4090, quindi una scheda che già possiedi può servire un checkpoint che non avrebbe mai potuto produrre. Ciò che decide se la policy risulta reattiva non è la VRAM, è dove si trova il server. Su AY-Robots GR00T N1.7 è solo cloud, quindi il ciclo di controllo paga un viaggio di andata e ritorno su internet pubblico oltre ai 152 ms per passo d'azione, e solo SmolVLA e ACT vengono eseguiti anche localmente. Per un lento pick and place un pod remoto è sostenibile. Per qualsiasi cosa reattiva non lo è: la policy diventa esitante in un modo che assomiglia esattamente a un fallimento dell'addestramento e non lo è. ACT a 20 ms per passo d'azione è il modello che tollera il ciclo più stretto, SmolVLA si attesta a 245 ms, e nessuna quantità di ottimizzazione della latenza recupera un viaggio di andata e ritorno che è già stato speso. Esegui la tua prima policy illustra il lato del serving da cima a fondo.
Cosa va effettivamente storto
- GatedRepoError alla prima esecuzione. Non ti è stato concesso l'accesso a nvidia/Cosmos-Reason2-2B, o non ti sei autenticato. Questo accade dopo che l'orologio della GPU è già partito.
- Dataset rifiutato al caricamento. Quasi sempre un dataset v3.0. Convertilo a una versione precedente. Vedi dataset rifiutato come v3.
- IndexError riguardo a dimensioni booleane non corrispondenti. Hai modificato delta_indices e non hai rigenerato le statistiche.
- Memoria esaurita al batch 32. Riduci --global-batch-size e aumenta --gradient-accumulation-steps, oppure riduci --num-shards-per-epoch, cosa che la configurazione suggerisce esplicitamente quando la VRAM è limitata. Vedi memoria esaurita durante l'addestramento.
- La perdita diminuisce, la policy non fa nulla. Non c'è una suddivisione di validazione di default, quindi una curva di addestramento pulita dimostra molto poco. Questa pagina copre la diagnosi.
- Funziona nella tua configurazione e in nessun altro luogo. Previsto con un piccolo dataset filmato in una singola condizione di illuminazione. NVIDIA raccomanda l'aumento del jitter di colore più da 20 a 50 episodi con illuminazioni diverse. Maggiori informazioni qui.
- La pinza non si chiude mai correttamente. Verifica che l'azione della pinza sia ABSOLUTA e le articolazioni del braccio RELATIVE, in quest'ordine in action_configs. La pinza non si chiude elenca le altre cause.
- Una telecamera si disattiva silenziosamente a metà registrazione. L'episodio viene comunque salvato e la chiave video esiste ancora, il che rende questo problema spiacevole. Telecamera non rilevata lo copre.
L'intero indice delle modalità di errore si trova su . Se stai scegliendo tra modelli piuttosto che debuggarne uno, e hanno numeri di benchmark con fonti allegate, e è il confronto di cui la maggior parte delle persone ha effettivamente bisogno, perché è la scelta tra un modello che puoi addestrare sulla scheda sotto la tua scrivania e uno per cui devi noleggiare un nodo da 80 GB per il fine-tuning. Per un contesto sul perché questi modelli si comportano in questo modo, la e la meritano di essere letti per primi. E se non possiedi ancora un braccio, trasmette un SO-100 fisico senza registrazione.
Quanti episodi mi servono prima che il fine-tuning di GR00T N1.7 ne valga la pena?▾
AY-Robots impone un minimo di 50 episodi per il trainer groot1.7. Le FAQ di NVIDIA sono più esigenti: circa 100 traiettorie per un semplice pick and place in una posizione fissa, 500 o più per scene complesse o a più passaggi, e da 100 a 500 per la manipolazione fine. Sotto i 50 episodi è quasi sempre meglio registrare più dati che ottimizzare gli iperparametri. Se il successo si stabilizza dopo, NVIDIA raccomanda HG-DAgger: esegui la policy, intervieni quando fallisce e aggiungi quelle correzioni al dataset.
Perché il mio dataset non si carica e come faccio a sapere di che versione è?▾
Apri meta/info.json e leggi codebase_version. L'attuale CODEBASE_VERSION di LeRobot su main è v3.0, quindi qualsiasi cosa registrata con una toolchain recente è v3.0, e il loader GR00T si aspetta v2. Converti con scripts/lerobot_conversion/convert_v3_to_v2.py dal repository Isaac-GR00T, che scrive codebase_version: v2.1 nel dataset convertito. Lo script viene eseguito nel suo virtualenv perché necessita di una versione di lerobot diversa da quella che GR00T blocca.
Posso fare il fine-tuning di GR00T N1.7 su una RTX 4090?▾
No. NVIDIA raccomanda 40 GB o più di VRAM per il fine-tuning e indica nodi H100 o L40; altre schede funzionano ma impiegano molto più tempo. Una 4090 ha 24 GB. AY-Robots offre GR00T N1.7 solo sul livello A100 80 GB e H100 80 GB per la stessa ragione. L'inferenza è un'altra storia: 16 GB sono sufficienti per servire il modello, quindi una 4090 può eseguire una policy che non può addestrare. Se vuoi un VLA che puoi addestrare su 24 GB, si tratta di SmolVLA con circa 450 M di parametri o ACT con circa 80 M.
Perché due esecuzioni con flag identici producono checkpoint diversi?▾
Perché launch_finetune.py non ha un seed. È una CLI tyro generata da una dataclass che non contiene un campo seed, quindi nulla fissa il RNG. Il repository nota separatamente una varianza del 5-6 percento tra le esecuzioni causata dall'aumento non deterministico delle immagini. Se la riproducibilità è importante, usa invece il percorso LeRobot: lerobot-train accetta --seed e la ricetta GR00T pubblicata passa --seed=42.
Dovrei usare Isaac-GR00T o lerobot-train?▾
Usa Isaac-GR00T se desideri l'implementazione di riferimento, il controllo per chiave sulla rappresentazione delle azioni, l'esportazione TensorRT o gli esempi di benchmark da riprodurre prima di fidarti dei tuoi dati. Usa lerobot-train se il tuo dataset è già LeRobot v3.0 e preferisci non convertirlo, se vuoi un seed, o se il resto del tuo stack è già LeRobot. Entrambi eseguono il fine-tuning degli stessi pesi nvidia/GR00T-N1.7-3B. Nota che LeRobot ha abbandonato completamente il supporto a GR00T N1.5: i checkpoint N1.5 vengono rifiutati con una nota di migrazione, e devi bloccare lerobot==0.5.1 per continuare a usarli.
Ho davvero bisogno di una telecamera da polso oltre a una telecamera frontale?▾
La configurazione SO-100 fornita utilizza entrambe, e modality.json mappa la telecamera frontale e quella da polso come chiavi video separate. Puoi addestrare con una telecamera, e la tabella di latenza della scheda del modello è misurata con una telecamera, ma la vista dal polso è ciò che fornisce alla policy informazioni utilizzabili sulla pinza al momento del contatto. Se la pinza si chiude nel momento sbagliato durante le tue esecuzioni, una telecamera da polso mancante o mal posizionata è una delle prime cose da controllare.
Esegui il fine-tuning di GR00T N1.7 sul tuo SO-100 senza prima costruire l'ambiente
Scegli modello, dataset e iperparametri in un modulo. Il backend noleggia una A100 80 GB o H100 sul mercato spot, esegue il trainer con batch 32, learning rate 1e-4 e 20000 passi, e scrive i checkpoint su object storage. Circa 4 a 12 USD per esecuzione.
Apri la guida all'addestramento di GR00T N1.7Sources
- NVIDIA Isaac-GR00T repository README, N1.7 main branch
- Isaac-GR00T: Fine-tune on Custom Embodiments (NEW_EMBODIMENT)
- Isaac-GR00T: Finetuning Models for the SO100/SO101 Robot
- Isaac-GR00T: Robot Data Preparation Guide, the GR00T LeRobot format
- Isaac-GR00T: modality config and ActionConfig reference
- Isaac-GR00T: Policy API Guide and the embodiment tag list
- Isaac-GR00T: FinetuneConfig dataclass with every CLI default
- Isaac-GR00T: the shared finetune launcher wrapper
- GR00T N1.7 FAQ: data volume, augmentation and deployment
- nvidia/GR00T-N1.7-3B model card, including the inference timing table
- nvidia/Cosmos-Reason2-2B, the gated VLM backbone used by N1.7
- GR00T N1: An Open Foundation Model for Generalist Humanoid Robots
- NVIDIA Isaac GR00T N1.7: Open Reasoning VLA Model for Humanoid Robots
- LeRobot: GR00T Policy, the lerobot-train recipe
- LeRobot: Imitation Learning on Real-World Robots (lerobot-record, lerobot-train)
Ready for high-quality robotics data?
AY-Robots connects your robots to skilled operators worldwide.
Get Started