How to train GR00T N1.7 on an SO-100

The full path from a calibrated SO-100 to a GR00T N1.7 policy that moves the real hardware. Ninety minutes of your attention, plus a few hours of unattended GPU time while the run finishes.

90 min · intermediate · Last updated 2026-08-09

Trainer key
groot1.7
GPU tier
A100 80 GB or H100 80 GB
Inference
about 152 ms per action step
Batch size
32, gradient accumulation 1
Learning rate
1e-4
Steps
20,000
Dataset format
LeRobot v2.1
Typical run
3 to 6 hours, about 4 to 12 USD
Servos
Feetech STS3215 bus servos
Servo voltage
7.4 V
Short answer

Record at least 50 reviewed episodes of one task as a LeRobot v2.1 dataset, open the Training page, select the dataset, choose GR00T N1.7, and start the run with the defaults of batch size 32, learning rate 1e-4 and 20 000 steps. The run rents an A100 80 GB for roughly 3 to 6 hours, then you load a checkpoint back onto the SO-100 and measure success over twenty physical attempts.

What this guide assumes

This page picks up where the arm already works. If your SO-100 is still in pieces or has never been calibrated, do /learn/so-100-getting-started first and come back. Everything below is the path from a calibrated arm and a recorded dataset to a GR00T N1.7 checkpoint that drives real hardware, including the parts where it does not work the first time.

  • An assembled SO-100 with all six STS3215 servos answering on the bus
  • The 7.4 V supply that belongs to those servos, checked against the label on the brick
  • At least one camera, rigidly mounted. A wrist camera in addition to a scene camera is worth more than any hyperparameter on this page
  • The desktop client or the CLI installed and paired to your account
  • At least 50 reviewed episodes of a single task, uploaded as a LeRobot v2.1 dataset
  • Billing set up on the account, because GR00T N1.7 trains on a rented A100 and has no local mode
7.4 V, and nothing else

STS3215 servos are 7.4 V parts. Twelve volt bricks are everywhere in hobby robotics and one of them will destroy all six servos in seconds, silently and permanently. If a Koch v1.1 or a LeKiwi base shares your bench, label every supply with the arm it belongs to before you plug anything in.

bash
ay-robots devices

# SERIAL PORTS
#   /dev/cu.usbmodem58FA0821911   SO-100  (6 servos responding)
# CAMERAS
#   0  Logitech C920        1920x1080   scene
#   1  USB 2.0 Camera       1280x720    wrist
Six servos and both cameras visible before you record anything is the cheapest check you can run.

Is GR00T N1.7 the right model for this dataset

GR00T N1.7 is NVIDIA’s vision language action foundation model with a diffusion action head. Fine-tuning does not retrain the whole thing. The backbone stays where it is and roughly 40 million parameters out of about 3 billion are adapted to your embodiment and your task. That is the reason it generalizes to object positions your demonstrations never showed, and also the reason it needs an 80 GB card to do it.

It is the fastest of the models this platform trains, at about 152 ms per action step. On an SO-100 that reads as roughly six or seven policy decisions per second: the arm moves in short committed increments rather than one continuous sweep. That is the model working normally, not a fault in your setup.

The honest caveat: if this is the first policy you have ever trained, spend a couple of dollars on SmolVLA or ACT with the same dataset before you spend ten on this one. Both fit on a 24 GB card and both will tell you within an hour whether your episodes are consistent with each other. GR00T N1.7 earns its cost once you already know the data is good and you want the success rate a pretrained backbone buys you.

The dataset, and why 50 episodes is the floor

Fifty is not a limit in the trainer. It is the point where fine-tuning this model stops being a waste of A100 time on this class of arm. Below it, the adapter has so few examples of your action space that it mostly memorizes the handful of start positions you happened to use, and you end up with a policy that succeeds when the cube sits where it sat in episode 12 and nowhere else. That failure looks exactly like a model problem and is entirely a data problem.

Fifty means fifty kept episodes. Recorded and then discarded does not count, and a failed grasp left in the set is worse than a missing episode because it actively teaches the behavior you do not want. Review before you keep, every time.

  • Vary the object start position across the whole region you want the policy to handle, not just the middle
  • Hold everything else fixed on a first dataset: same lighting, same camera pose, same approach strategy, same task sentence word for word
  • Stop the recording when the task is done rather than idling in frame, so the policy does not learn to wait
  • Split the session. Three blocks of twenty beat one block of sixty, because demonstration quality drops when you are bored

What a clean episode looks like in detail, including the five recording habits that quietly ruin a set, is on /so-100/data-collection. Nothing on that page changes because you picked GR00T.

LeRobot v2.1 only, and what the rejection looks like

GR00T loads LeRobot v2.1 and refuses v3.0. This is the single most common way a GR00T run dies, and it has cost real users an afternoon each. The client writes v2.1 by default precisely because of this, so you normally only meet it after importing a dataset from elsewhere or after changing the setting once and forgetting.

In practice the failure is fast and unhelpful. The job sits in QUEUED while the pool finds an A100, flips to RUNNING when the container comes up, then to FAILED inside a minute or two, before a single loss value is written. The run page has no chart on it at all, because there was never a step one. The error text mentions the dataset metadata rather than saying the version is wrong, which is why people re-upload the same directory twice before they check.

json
{
  "codebase_version": "v2.1",
  "robot_type": "so100",
  "fps": 30,
  "total_episodes": 62,
  "features": {
    "observation.state":         { "dtype": "float32", "shape": [6] },
    "action":                    { "dtype": "float32", "shape": [6] },
    "observation.images.scene":  { "dtype": "video",   "shape": [480, 640, 3] },
    "observation.images.wrist":  { "dtype": "video",   "shape": [480, 640, 3] }
  }
}
meta/info.json of an SO-100 dataset GR00T will accept. The first line is the one that decides.
Check the version before the upload, not after

Open meta/info.json and read codebase_version. If it says v3.0, convert with the LeRobot tooling and upload the converted copy. Checking takes five seconds, and the alternative is finding out after you have uploaded several gigabytes and queued a GPU.

Start the training run

  1. 1
    Open the Training page and select the dataset

    The selector lists datasets that finished processing. Anything still ingesting cannot be trained on yet. Confirm the episode count and the robot type on the card before you continue, because picking the wrong revision of your own dataset is easier than it sounds once you have four of them.

  2. 2
    Choose GR00T N1.7 as the model

    The model list shows each supported trainer with the GPU tier it requests. N1.7 asks for the A100 80 GB tier. GR00T N1.5 sits next to it and exists to reproduce runs from before N1.7 was available, so unless that is exactly what you are doing, take N1.7.

  3. 3
    Name the policy so you recognize it in three weeks

    Something like so100-cube-groot17-v1. This is the name you will be reading in the checkpoint list when you are comparing two runs and cannot remember which dataset each one used.

  4. 4
    Review the hyperparameters

    The form pre-fills batch size 32, learning rate 1e-4, 20 000 max steps and gradient accumulation 1. Save steps is the one extra field GR00T exposes. There is no seed field, because the GR00T fine-tuning entry point does not take one. Leave all of it alone on a first run.

  5. 5
    Start it and let the pool find a card

    The platform rents on a spot market and selects by VRAM rather than by GPU name, so the exact card differs between runs. Queue time depends on availability at the current price, not on the size of your dataset.

    bash
    # The same start, driven from an agent over the hosted MCP server
    curl -sS https://www.ay-robots.com/api/mcp \
      -H "Authorization: Bearer ayr_live_..." \
      -H "Content-Type: application/json" \
      -d '{
        "jsonrpc": "2.0",
        "id": 1,
        "method": "tools/call",
        "params": {
          "name": "start_training",
          "arguments": {
            "dataset_id": "ds_8f21c4",
            "policy_name": "so100-cube-groot17-v1",
            "model_id": "groot1.7",
            "max_steps": 20000,
            "confirm_spend": true
          }
        }
      }'
  6. 6
    Watch the first few hundred steps before you walk away

    Loss should fall visibly early. If it is flat from step one, cancel rather than pay for six hours of it: the dataset did not load the way you assumed, and no amount of further training fixes that.

  7. 7
    Pick a checkpoint, not the last one

    Save steps controls how many candidates you end up with. Keep two or three from the second half of the run and compare them on the arm. The final checkpoint is a default, not a verdict, and it is regularly beaten by one saved a few thousand steps earlier.

The defaults, and when to touch them

ParameterDefault for GR00T N1.7What it controlsChange this when
Batch size32Samples per optimizer stepOnly if the run fails on memory. Halve it and raise gradient accumulation by the same factor so the effective batch stays where it was.
Learning rate1e-4Step size of the adapter updateHalve it if the loss spikes and never comes back down. Raising it is almost never the fix for a policy that underperforms.
Max steps20 000Length of the run. This trainer counts steps, not epochsRaise it when the dataset runs to several hundred episodes and loss is still falling at the end. Lower it to a few hundred when you only want to prove the pipeline runs.
Gradient accumulation1Batches accumulated before an optimizer stepRaise it together with a reduced batch size when VRAM is the binding constraint, never on its own.
Save stepsthe only GR00T-specific field in the formInterval between checkpointsLower it when you want more candidates to compare on the hardware. Raise it if you genuinely only care about the end of the run.
Seednot offered for GR00TNothing, the field does not existNever, because you cannot. Two identical GR00T runs will differ slightly. If you need bit-for-bit reproducibility, Pi0.5 and the lerobot family expose a seed and GR00T does not.

What an A100 hour actually costs

GR00T N1.7 trains on the A100 80 GB tier. On an SO-100 dataset of 50 to 150 episodes at the default 20 000 steps, expect 3 to 6 hours of GPU time at 1.20 to 2.00 USD per hour, so about 4 to 12 USD for the run. Those are spot prices and they move day to day, which is why the range is a range.

The scheduling consequence matters more than the money. A GR00T run is an overnight or a working-afternoon commitment, not something you iterate on between coffees. Make each run count: change one thing about the dataset, run it, evaluate it against a fixed protocol, then decide. Two carelessly started runs cost more than one careful one and tell you less.

A job that dies in the first minutes, for example on a v3.0 dataset, costs only the minutes the card was held. A job you cancel at step 18 000 costs almost the whole run. Cancel early or commit.

Reading the run page while it trains

The run page carries the loss curve, the learning rate schedule, the gradient norm, and GPU telemetry: utilization, VRAM, temperature and power. They answer two different questions and it pays to keep them apart. Loss tells you whether the model is fitting your data. Telemetry tells you whether you are getting what you are renting.

  • Loss falls, then flattens: the normal shape. There is nothing further to read into it, and in particular it is not evidence the policy works
  • Loss flat from step one: the data did not load as intended. Stop the run
  • Loss near zero within the first thousand steps on fifty episodes: your episodes are near duplicates of one another and the model memorized them
  • Gradient norm climbing steadily instead of settling: the learning rate is too high for this dataset
  • GPU utilization well below full while VRAM sits high: data loading is the bottleneck, not the model. The run will finish, just slower and at the top of the cost range
  • Temperature and power both moving: the card is real and busy. The cheapest possible check that a run has not silently stalled

Load the policy back onto the SO-100

When the run finishes the checkpoints are attached to the policy in your account. Deployment means selecting one for the paired robot and starting inference from the client. Nothing about the arm changes: same calibration, same serial port, same cameras. That last one is not optional.

  • Cameras in the exact positions they occupied during recording, down to the tripod leg marks
  • The same lighting you recorded under, which usually means the same time of day or the blinds closed
  • The scene reset the way you reset it while recording
  • The task sentence typed exactly as it appears in the dataset, since the model is language conditioned
  • Nothing on the table you would mind having swept off it
Hand on the power for the first attempt

A fresh policy will do something you did not predict, and the first thing it does is the least predictable of all. Clear the workspace, keep your hand near the switch, and do not leave the arm running unattended until it has earned it over a couple of dozen attempts.

Evaluate on hardware, because nothing else counts

Twenty attempts, the object in a different position each time, results written down as they happen. Not the six attempts that went well, and not a number you reconstruct from memory afterwards. This is the only measurement in the entire pipeline that is about the thing you actually wanted.

A policy that averaged two incompatible grasp strategies out of your demonstrations produces a textbook loss curve and fails every physical attempt. There is no chart on the run page that can distinguish that case from a good one. Success rate over a fixed protocol can, which is why the protocol has to stay fixed between runs. Change the number of attempts or the object positions and your second run looks better than your first for reasons that have nothing to do with the data.

When you have the number, look at where the failures cluster rather than at the average. Twenty targeted episodes of the situation that fails will beat two hundred more of what you already have, and on a 4 to 12 USD run that difference is most of your budget. The evaluation loop in full is on /learn/run-your-first-policy.

Troubleshooting

SymptomMost likely causeWhat to do
Loss plateaus after a few thousand steps and the policy is weakThe dataset does not contain the variation you are asking the policy to haveTreat it as a data problem. Adding steps to a plateaued run rarely changes the outcome. Record targeted episodes of what it gets wrong and retrain.
Policy freezes partway through the motion and stays thereThe current observation falls outside anything in the training setWrite down the pose where it stops. That pose is a hole in the data, and ten to twenty episodes that pass through it usually close it.
Works under the lighting you recorded in and nowhere elseThe policy reads pixels, and every episode you own shares one illuminationEither fix the lighting permanently, or record a second block under the other condition and retrain on the union of both.
Approach is correct, the gripper never closesThe grasp moment is underrepresented, or the wrist view is occluded exactly when it mattersStep through three episodes frame by frame at the moment of closure. If the gripper is hidden behind the object, move the wrist camera before you record anything else.
Job fails within minutes with no loss values at allLeRobot v3.0 dataset, or the dataset never finished processingRead codebase_version in meta/info.json. Convert to v2.1, re-upload, start again.
Job sits in QUEUED for a long timeThe pool has not found an 80 GB card at the current spot priceThis is market availability, not your configuration. It clears on its own, and nothing is billed while you wait.

Frequently asked questions

Can I fine-tune GR00T N1.7 on my own GPU?

Not realistically. It requests an 80 GB card, and a 24 GB consumer GPU is not near that. SmolVLA and ACT are the two models on this platform that fine-tune on a 4090, and both are reasonable ways to validate an SO-100 dataset before paying for A100 time.

At what point does GR00T N1.7 beat ACT on the same task?

There is no universal crossover. ACT is strong on one fixed repetitive task and it runs at a fraction of the latency. GR00T shows its advantage on object positions and phrasings you never demonstrated. The only way to answer it for your task is to train both on the same dataset and evaluate them with the same twenty attempt protocol.

Do I really need a wrist camera?

A single scene camera trains a working policy. The wrist camera pays for itself at the grasp, which is the moment the scene view is blocked by the gripper and the object at the same time. If your failures cluster at closure, that is the missing view.

Why is there no seed field?

The GR00T fine-tuning entry point does not accept one, so the training form does not pretend to offer it. Two runs on the same dataset will differ slightly. If reproducibility matters more than the last few points of success rate, Pi0.5 and the lerobot family do expose a seed.

Can one policy handle two tasks?

Yes, GR00T is language conditioned, so a dataset can hold several task strings and the policy picks between them from the sentence you give it at inference. Budget the fifty episode floor per task rather than per dataset, and phrase each task the same way every single time.

Will my checkpoint work on someone else’s SO-100?

Partly, and better if both arms are calibrated. Servo zero points differ between physical units, so the same joint value is a slightly different pose on each. In practice camera placement causes more transfer loss than the arm does.