How to train SmolVLA on an SO-100

This is the first training run to do on an SO-100. SmolVLA is small enough that a full run costs about the price of a coffee and finishes while you are still in the room, and it is language conditioned, so one policy can answer to several phrasings of the same job. Everything below assumes a calibrated arm and nothing else.

45 min · beginner · Last updated 2026-08-09

Trainer key
smolvla
GPU tier
RTX 4090 or any card with 24 GB
Inference
about 245 ms per action step
Batch size
2, gradient accumulation 8
Learning rate
1e-4
Steps
20,000
Dataset format
LeRobot v2.1
Typical run
2 to 5 hours, about 1 to 3 USD
Servos
Feetech STS3215 bus servos
Servo voltage
7.4 V
Short answer

SmolVLA is a 450 million parameter vision-language-action model that fine-tunes on a single 24 GB GPU, which makes it the cheapest honest way to find out whether an SO-100 dataset is worth training on. Record 30 or more consistent episodes with an identical task string, then start a run with the defaults: batch size 2, learning rate 1e-4, 20 000 steps, gradient accumulation 8. Expect 2 to 5 hours and roughly 1 to 3 USD.

Why SmolVLA is the run to start with

At about 450 million parameters, SmolVLA fine-tunes inside 24 GB of VRAM. The other vision-language-action models on this platform are roughly 3 billion parameters and need an 80 GB A100, which the pool rents at 1.20 to 2.00 USD per hour against 0.30 to 0.60 USD for a 24 GB card. The interesting part is not the money, it is what the money does to your habits. At a couple of dollars per run you will try four versions of a dataset over a weekend. At ten dollars a run you will try one and then spend the weekend arguing with yourself about whether it was worth repeating.

The second reason is the data floor. SmolVLA becomes useful at around 30 episodes. GR00T N1.7 and Pi0.5 want 50 or more before a fine-tune has anything to hold on to, and so does ACT, which is far smaller than any of them. Thirty deliberate demonstrations is one focused evening. That gap decides what your first weekend with the arm looks like.

The third reason is that SmolVLA reads the task string. Every frame it trains on is paired with the sentence you typed when you recorded that episode, and at inference you pass a sentence too. One policy can therefore cover two or three related jobs in the same scene, which is something ACT cannot do at all.

Two pages worth opening alongside this one

The model page at /policies/smolvla lists the exact defaults, the GPU tier and the measured inference latency. /compare/act-vs-smolvla is the decision between the two models that share the 24 GB tier: ACT is roughly twelve times faster at inference and ignores language entirely, SmolVLA needs fewer episodes and understands what you asked for.

What you need before you start

  • A calibrated SO-100 with all six STS3215 servos responding. /learn/so-100-getting-started covers assembly, pairing and calibration from the box.
  • At least one camera, fixed so it cannot be nudged. A scene camera plus a wrist camera is noticeably better than a scene camera alone.
  • The desktop client or the CLI, paired to your account. The robot driver ships inside it, so there is no Python environment to build and no dependency resolution to lose an afternoon to.
  • Thirty or more reviewed episodes of one task, recorded as LeRobot v2.1 and uploaded.
  • Billing set up on the account, because the run rents a GPU by the hour. Current rates are on /pricing.
7.4 V, and nothing else

The STS3215 servos in an SO-100 run at 7.4 V. A 12 V supply destroys them in seconds and none of that damage is recoverable. Plenty of robot arm guides on the internet specify 12 V because they were written for hardware that wants 12 V. Check the label on the supply in your hand against the arm in front of you before it goes into the wall.

bash
curl -fsSL https://www.ay-robots.com/install.sh | sh
ay-robots --version

# Pairing code comes from the Robots page in the dashboard
ay-robots pair 4QD2-8HRM-1XV6

# Six servos and your cameras should both show up here
ay-robots devices

# Once per physical arm, before you record anything
ay-robots calibrate
Everything after this happens in the web UI. There is no train command in the CLI.

The dataset: thirty episodes and one sentence

Language conditioning is a real feature and a real trap in equal measure. Used deliberately, one policy answers to several phrasings of the same job. Used carelessly, you split a 30 episode dataset into three sets of ten that the model treats as three unrelated tasks, and none of the three has enough behind it to learn anything.

The rule while recording is one task, one sentence, typed identically every time. Write the sentence down once and paste it. Do not retype it, because you will not retype it the same way at eleven at night on episode 27.

SmolVLA carries a pretrained language encoder, so a policy trained on "pick up the red cube and place it in the bowl" will often still respond to "put the red cube in the bowl". Do not lean on that with 30 episodes. Tolerance to rewording is something you measure during evaluation, not something you assume during recording.

text
# meta/episodes.jsonl, one line per recorded episode
{"episode_index": 0, "tasks": ["pick up the red cube and place it in the bowl"], "length": 388}
{"episode_index": 1, "tasks": ["pick up the red cube and place it in the bowl"], "length": 402}
{"episode_index": 2, "tasks": ["pick up red cube, place in bowl"], "length": 377}
{"episode_index": 3, "tasks": ["pick up the red cube and place it in the bowl"], "length": 395}
Episode 2 is a different task as far as the model is concerned. Find this before you train, not after the policy has ignored your instruction twenty times.

Concretely: thirty episodes of putting the cube in the bowl plus thirty of putting it on the plate, same scene and same cameras, train into one policy you steer with the string you pass at inference. Plan for that during the first recording session, because retrofitting a second task into an existing scene is much harder once the tripod has moved.

Episodes of one taskWhat a SmolVLA run on an SO-100 realistically gives you
Under 30Not worth the GPU time. The run completes, the loss curve looks fine, the arm wanders.
30 to 50The working floor. Success from object positions close to the ones you demonstrated, and a trustworthy verdict on your data.
80 to 150Dependable across the region of the table you actually covered, and the point where paying for a GR00T run starts to make sense.
Two tasks at 30 or more eachLanguage conditioning begins to earn its keep, because the string now selects between behaviors instead of being decoration.

Start the run

  1. 1
    Check the dataset format

    Open Datasets and look at the format on your upload. SmolVLA reads LeRobot v2.1, and so does GR00T, which rejects v3.0 outright. Recording v2.1 costs you nothing at SO-100 dataset sizes and keeps the upgrade path open.

  2. 2
    Open Training and select the dataset

    The picker only lists uploads that finished processing. If yours is missing, ingest is still running, and the archive step on a two-camera dataset takes a few minutes.

  3. 3
    Pick SmolVLA as the model

    The form redraws for the model you choose. For SmolVLA you get batch size, learning rate, steps, gradient accumulation, seed and log frequency. Chunk size and action steps belong to ACT and are not shown here.

  4. 4
    Name the policy so you can read it later

    Arm, task, model, version. Something like so100-cube-bowl-smolvla-v1. Six weeks from now the run list is the only record of what you tried, and three entries called test will not help you.

  5. 5
    Leave the defaults alone, but fix the seed

    Batch 2, learning rate 1e-4, 20 000 steps, gradient accumulation 8. Set the seed to a number you write down. Without a fixed seed you cannot tell a genuine data improvement from run-to-run noise on your next attempt.

  6. 6
    Press Start, then watch the first few hundred steps

    The pool buys a 24 GB card on the spot market, so there is a wait before the first log line. Once loss is printing, the run page shows loss, learning rate, gradient norm and GPU telemetry. If loss is still flat after several hundred steps, stop the run rather than pay for the remaining four hours.

The defaults, and when to change them

SettingDefaultChange this when
Batch size2Almost never. It is sized to fit a 24 GB card with the vision tower resident. Raising it is the quickest route to an out-of-memory failure four minutes into a paid run.
Learning rate1e-4Loss spikes and does not recover: halve it. Loss is flat from step one: the rate is not your problem, the dataset is, or it did not load the way you think it did.
Steps20 000Cut to about 5 000 when the run only has to answer whether the data is learnable at all. Extend past 20 000 only when loss is still visibly falling at the end.
Gradient accumulation8Leave it. With batch size 2 this gives an effective batch of 16, which is the tradeoff the whole default set is built around.
Seedyours to setAlways set it before a comparison run. It is the only way two runs on two dataset versions mean anything next to each other.
Log frequencyas shippedLower it on a short probe run so the loss curve has enough points to read. It costs nothing except log volume.

Seed and log frequency exist for the lerobot family, which is ACT, SmolVLA and Pi0.5. Both GR00T entry points have no seed field at all. If you plan to graduate to GR00T later, do not build a workflow that depends on bit-for-bit reproducibility, because it stops being available the moment you get there.

Cost, duration and the agent route

A SmolVLA run sits on the 24 GB tier: roughly 0.30 to 0.60 USD per hour, and a full 20 000 step run on a dataset of this size lands in the 2 to 5 hour range. Call it 1 to 3 USD start to finish. The A100 tier that GR00T and Pi0.5 require is 1.20 to 2.00 USD per hour over 3 to 6 hours, so about 4 to 12 USD for the equivalent run.

Spot pricing moves daily and the pool selects by VRAM rather than by GPU name, so the card you get is whichever 24 GB device was cheapest that minute. This changes the wall clock, not the result.

bash
curl -sS https://www.ay-robots.com/api/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "MCP-Protocol-Version: 2025-06-18" \
  -H "Authorization: Bearer $AY_ROBOTS_API_KEY" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "start_training",
      "arguments": {
        "dataset_id": "ds_8f3a91c4",
        "policy_name": "so100-cube-bowl-smolvla-v1",
        "model_id": "smolvla",
        "max_steps": 20000,
        "confirm_spend": true
      }
    }
  }'
The same run started over MCP. Streamable HTTP, JSON-RPC 2.0, protocol version 2025-06-18.

start_training refuses to do anything unless confirm_spend is true, because renting a GPU on a vague instruction is not a thing an agent should be able to do. The full tool list is on /mcp.

Run the policy on the arm

Deployment does not care that the policy is a VLA. Pick a checkpoint, load it in the client, pass a task string, and the arm moves. Two things are specific to SmolVLA.

The first is latency. SmolVLA reports about 245 ms per action step. Next to ACT at about 20 ms that is a different feel: the arm advances in visible increments rather than flowing. For pick and place it is fine. For anything that has to react to a moving object it is not, and no amount of tuning changes that.

The second is that you have to supply a task string, and on the first evaluation it should be exactly the string you recorded with. Testing a paraphrase is a good experiment, it is just a separate one.

  1. Put the cameras back in the exact positions used during recording. The policy is largely reading pixel locations, and a moved tripod is still the leading cause of confident, wrong behavior.
  2. Run twenty attempts with the object in different positions and write each outcome down as it happens, not afterwards from memory.
  3. Repeat with two or three checkpoints from the back half of the run. The last checkpoint is not automatically the best one.
  4. Then run the same twenty attempts with a paraphrased task string and note the drop. That number tells you how much language robustness your dataset actually bought.

If the whole loop is new to you, /learn/run-your-first-policy walks the deployment side in more detail, and /live lets you drive a real SO-100 in the browser without owning one.

Use the cheap run as a data-quality probe

The most useful output of a SmolVLA run is often not the policy. It is the verdict on your dataset. A run that cannot learn the task at all is nearly always telling you something about the data rather than something about the model, and it tells you for a dollar instead of twelve.

Read it like this. If loss falls normally, the policy loads, and the arm makes recognizably task-shaped attempts even at a poor success rate, the dataset has signal, and a GR00T fine-tune on the same episodes is a reasonable next purchase. If the arm does something unrelated to the task on every single attempt, six hours on an 80 GB card will buy you a more expensive version of the same disappointment.

A 5 000 step probe is a quarter of a run

Cut steps to 5 000, keep every other default, and you get the verdict for well under a dollar in roughly a quarter of the time. Cheap enough to run once per dataset revision, which is exactly the habit that makes the rest of this work.

Troubleshooting

SymptomMost likely causeWhat to do about it
Loss falls cleanly but the arm does nothing usefulYour demonstrations disagree with each other. The model learned the average of two approach strategies, and the average of two strategies is not a strategy.Watch five random episodes back to back. If you came in from the left in some and from above in others, that is the answer. Re-record the minority rather than adding more of both.
The policy ignores the language instructionEvery episode carried the same string, so the text input never varied and the model learned to route around it. Or the strings varied slightly at random, which is worse, because now they carry noise instead of information.List the distinct strings in meta/episodes.jsonl. One string per task, and only introduce a second string when there are 30 or more episodes behind it.
Works in the recorded lighting, fails an hour laterThirty episodes under one lighting condition is a thin base, and a pretrained backbone only carries you so far past what you showed it.Record ten more episodes with the blinds open and ten under room light, then retrain. Two lighting conditions buy far more than twenty more episodes of the first one.
The gripper closes early, late, or on nothingAt about 245 ms per action step the close command is committed roughly a quarter of a second before the gripper acts on it. Demonstrations where you snapped the gripper shut leave too few frames around the decisive moment.Re-record the grasp deliberately: approach, settle, close, settle, lift. This is where a wrist camera pays for itself more than anywhere else in the task.
The run fails within minutes of startingAlmost always the dataset: a v3.0 export, an upload still processing, or an episode missing one of its video streams.Open the dataset preview and step through episodes. The preview reads both format versions, so it renders happily even when the trainer refuses the same data.

When to move up, and where

SmolVLA has a lower ceiling than the 3 billion parameter models on anything needing fine positioning. The signal that you have hit it is a success rate that stops responding to more data. At that point the next step is GR00T N1.7: same v2.1 dataset, an 80 GB card, batch size 32, about 152 ms per action step, and no seed field. /train/groot-n1-7-on-so-100 is that run end to end, and /policies/groot-n1-7 has the numbers.

If what you need is speed rather than generalization, the move is sideways instead of up. ACT runs at roughly 20 ms per step on the same 24 GB tier, learns your one task very well and nothing else, and needs a 100 000 step schedule to get there. /compare/act-vs-smolvla lays out that trade honestly.

Do I need my own GPU to train SmolVLA?

No. The run happens on a rented 24 GB card in the cloud pool. SmolVLA is small enough to fine-tune locally if you happen to own a 24 GB card, which is not true of the 3 billion parameter models here, but nothing in this guide requires it.

Is 30 episodes really enough?

Enough for a run that tells you something true, not enough for a policy you would leave unattended. Thirty consistent episodes of one task produce an arm that succeeds from starting positions close to the ones you demonstrated. That is a legitimate checkpoint on the way to a real dataset, and it costs an evening.

Can one SmolVLA policy do two different tasks?

Yes, provided each task has its own block of episodes and both share the scene and camera setup. Two tasks at 30 episodes each works. Two tasks where one has 40 episodes and the other has 6 does not, because the small one is indistinguishable from noise.

Why is my SmolVLA policy slower on the arm than ACT was?

Because it is more than five times larger and runs a language encoder on every action step. About 245 ms against about 20 ms. If the task needs a fast reaction, that gap is the reason to pick ACT, not something to tune away.

The loss curve looks perfect. Why evaluate on hardware at all?

Loss measures how well the model reproduces the actions you recorded, not whether those actions achieve anything from a state you never recorded. A policy that averaged two incompatible demonstrations can post a lower loss than one that committed to a single strategy, and only one of the two picks up the cube.

Should I record LeRobot v2.1 or v3.0?

v2.1. SmolVLA reads it and GR00T rejects v3.0 entirely, so v2.1 is the format that keeps every later option open. At the dataset sizes a single SO-100 produces, the scaling advantages of v3.0 never come into play. /so-100/lerobot covers the split in detail.