
What a Raspberry Pi really does well in a robot: USB topology for the servo bus and cameras, power and encoder limits, and why the policy belongs on a GPU somewhere else.
A Raspberry Pi is a very good robot host and a very bad inference machine. Those two facts are not in tension, they are the whole design. The Pi sits next to the arm, owns the serial bus and the cameras, keeps a fixed-rate loop running, and talks to something else that owns the GPU. Get that split right and a Pi 5 will drive an SO-100 all day. Get it wrong and you spend a weekend discovering that a 3 B parameter model does not fit on a board with no CUDA device.
This page covers the boring half: which USB ports exist on which Pi, how much bandwidth two cameras need, where the power comes from, and what the published hardware requirements say when you read them literally. Every number here comes from a datasheet, a repo or a doc page linked at the bottom. Where the answer is do not run it here, it says so.
The short version
- •The Pi's job is I/O and timing: serial bus to the servos, USB video in, network out. A Raspberry Pi 5 does that comfortably.
- •USB topology decides whether two cameras work. On Pi 4 all four ports sit behind one VL805 controller on a single PCIe Gen 2 lane. On Pi 5 the RP1 southbridge gives every downstream port independent, uncontended bandwidth.
- •One 1080p30 uncompressed YUYV stream is about 995 Mbit/s. USB 2.0 signals at 480 Mbit/s. Force MJPEG, or the driver refuses to start the stream.
- •NVIDIA's Isaac-GR00T README lists inference hardware as one GPU with 16 GB or more of VRAM. LeRobot's docs put Pi0 at 14 GB at inference time and SmolVLA at about 2 GB. A Pi tops out at 16 GB of shared LPDDR4X and has no CUDA device.
- •An AI HAT+ is a 13 or 26 TOPS INT8 vision NPU, and Raspberry Pi's capability table marks LLM and VLM support as not supported. The AI HAT+ 2 does run VLMs on its own 8 GB, but a Hailo part is not a PyTorch device, so a fine-tuned VLA checkpoint is still not a drop-in.
- •The architecture that works is LeRobot's own: policy server on a GPU box, robot client on the Pi, action chunks over the wire. LeKiwi already does exactly this.
- •Remote inference is fine for slow pick and place and not fine for fast reactive motion. That is physics, not a product tier.
What the Pi is actually doing
Split the software into jobs and the placement decision answers itself. Some jobs need to be microseconds from a UART. Some need 40 GB of VRAM. Nothing needs both, which is lucky, because no single box at this price gives you both.
| Job | Runs well on a Pi? | Why |
|---|---|---|
| Serial bus to the Feetech servos | Yes | One USB CDC-ACM port at 1 Mbaud. The wire is the bottleneck, not the CPU. |
| Reading 2 to 3 USB cameras | Yes, with care | Bandwidth and pixel format decide this, not CPU. |
| Teleoperation | Yes | Leader-follower position streaming is a few hundred bytes per tick. |
| Recording a LeRobot dataset | Yes, with the encoder tuned | Video encoding is the cost. No hardware H.264 encoder on the Pi 5. |
| Running ACT (about 80 M params) | Unproven | No CUDA device. The 20 ms figure is a GPU number and no ARM-CPU rollout has been published. |
| Running SmolVLA, Pi0.5, GR00T | No | Vendor minimums start at a 16 GB VRAM GPU. |
| Fine-tuning a policy | No | Isaac-GR00T recommends 40 GB or more of VRAM. |
That table is the article. The rest turns each yes into a working setup and each no into an architecture instead of a disappointment. If the arm has never moved at all, start with the SO-100 getting started guide and come back when the servos respond.
USB topology is the whole design
Both boards advertise the same port count: two USB 3.0 and two USB 2.0. The topology behind those ports is completely different, and that difference decides whether your second camera streams.
| Raspberry Pi 4 Model B | Raspberry Pi 5 | |
|---|---|---|
| SoC | BCM2711, quad-core Cortex-A72 at 1.8 GHz | BCM2712, quad-core Cortex-A76 at 2.4 GHz |
| USB ports | 2 x USB 3.0, 2 x USB 2.0 | 2 x USB 3.0, 2 x USB 2.0 |
| USB controller | VL805, four ports behind one PCIe Gen 2 x1 link | RP1 southbridge, two independent xHCI controllers |
| Per-port bandwidth | Shared across all four ports | "every downstream port has independent and uncontended bandwidth" (RP1 datasheet) |
| USB 3.0 wording in the brief | "2 x USB 3.0 ports" | "2 x USB 3.0 ports, supporting simultaneous 5Gbps operation" |
| Video encode in silicon | H.264 1080p30 encode | None listed; the brief lists a 4Kp60 HEVC decoder only |
| Max RAM | 8 GB LPDDR4 | 16 GB LPDDR4X-4267 |
| Recommended PSU / max USB draw | 3.0 A / 1.2 A | 5.0 A / 1.6 A (600 mA on a 3 A supply) |
| Operating temperature | 0 to 50 C | 0 to 70 C |
Chapter 5 of the RP1 peripherals datasheet describes two identical USB 3.0 xHCI host controllers, each with two downstream ports: "The controllers are configured with two USB2.0 HS/FS/LS Bus Instances and a dedicated SuperSpeed Bus Instance, so every downstream port has independent and uncontended bandwidth." On a Raspberry Pi 4 that sentence does not apply. The CM4 datasheet says that on Raspberry Pi 4 Model B the internal PCIe 2.0 x1 host controller "has been connected to a USB 3 host controller (using the Via Labs VLI805)", and Raspberry Pi's CM4 IO USB 3.0 application note adds that this is "exactly the same host controller as used on Raspberry Pi 4 Model B". The Raspberry Pi 4 Model B brief lists two USB 3.0 and two USB 2.0 ports, and they all hang off that one chip on one PCIe Gen 2 lane. Put two cameras and a servo adapter on a Pi 4 and assume they are competing.
Before debugging anything, look at the tree. One command tells you what the kernel thinks your topology is:
# what is on which controller, at which speed
lsusb -t
# the shape to expect on a Pi 5 with one camera and the servo adapter
/: Bus 04.Port 1: Dev 1, Class=root_hub, Driver=xhci-hcd/2p, 5000M
|__ Port 1: Dev 2, If 0, Class=Video, Driver=uvcvideo, 5000M
/: Bus 03.Port 1: Dev 1, Class=root_hub, Driver=xhci-hcd/2p, 480M
/: Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci-hcd/2p, 5000M
/: Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci-hcd/2p, 480M
|__ Port 2: Dev 3, If 0, Class=Comm, Driver=cdc_acm, 12M
|__ Port 2: Dev 3, If 1, Class=CDC Data, Driver=cdc_acm, 12MCamera bandwidth: do the arithmetic before you buy
USB video class cameras reserve a fixed slice of bus bandwidth when the stream starts. If the slice is not available, the stream does not start. This is the most common way a two-camera rig fails, and it is predictable from multiplication.
| Format | Resolution | fps | Bytes per frame | MB/s | Mbit/s |
|---|---|---|---|---|---|
| YUYV (uncompressed) | 640 x 480 | 30 | 614,400 | 18.4 | 147 |
| YUYV (uncompressed) | 1280 x 720 | 30 | 1,843,200 | 55.3 | 442 |
| YUYV (uncompressed) | 1920 x 1080 | 30 | 4,147,200 | 124.4 | 995 |
| MJPEG (about 10:1) | 640 x 480 | 30 | about 61,000 | about 1.8 | about 15 |
| MJPEG (about 10:1) | 1920 x 1080 | 30 | about 415,000 | about 12.4 | about 100 |
USB 2.0 signals at 480 Mbit/s, and the periodic schedule only gives you part of that. A single uncompressed 1080p30 stream asks for roughly twice the whole bus. Two uncompressed 640x480 streams want about 294 Mbit/s on a bus that also carries servo traffic. The MJPEG rows are estimates, since the ratio depends on sensor and scene, but the order of magnitude is not in doubt: in-camera MJPEG is the difference between two cameras working and two cameras not working.
Plug in the second camera, start recording, and get VIDIOC_STREAMON: No space left on device. There is plenty of disk. ENOSPC here means USB bandwidth or usbfs buffer memory, not storage. Two fixes, in this order: force MJPEG instead of YUYV, since most UVC webcams produce MJPEG in the camera and bus traffic drops by roughly an order of magnitude; then, if your capture path goes through usbfs, raise usbcore.usbfs_memory_mb, which the Linux USB core (drivers/usb/core/devio.c) defaults to 16 MB. Check it with cat /sys/module/usbcore/parameters/usbfs_memory_mb. Moving a camera to the other port pair helps on a Pi 5 and barely helps on a Pi 4, where everything shares the same VL805 uplink.
# what formats and frame rates does this camera really support?
v4l2-ctl --device /dev/video0 --list-formats-ext
# confirm what the driver actually negotiated
v4l2-ctl -d /dev/video0 --get-fmt-video
# usbfs buffer ceiling, in MB (16 by default)
cat /sys/module/usbcore/parameters/usbfs_memory_mb
# LeRobot's own discovery helper
lerobot-find-cameras opencvWhatever you record is what the policy expects at inference time. A wrist camera at 15 fps during recording and 30 fps during a rollout is a changed input distribution. Fix format and rate once, write them down, and use the same values in the recording run and in the rollout. If a camera vanishes mid-session, the camera-not-detected page lists the usual causes.

The servo bus: one serial port at 1 Mbaud
The arm is a daisy chain of Feetech STS3215 bus servos on a half-duplex TTL line, bridged to USB by a small adapter board. The servo spec lists a 1 Mbps bus and LeRobot's Feetech driver defaults to exactly that. At 1 Mbaud with 8N1 framing a byte is ten bit periods, so a byte costs 10 microseconds and reading six joints is a few hundred microseconds of signalling. That is not your problem. Everything around it is: USB scheduling, driver buffering, and the occasional corrupt packet.
LeRobot is explicit about the last one. The SO follower configuration carries num_read_retries: int = 2, with a comment that Feetech buses can return a corrupted status packet "especially when several joints move at once", which would otherwise abort the control loop (config_so_follower.py). Write your own loop instead and it will die on a fast move.
- 1Find the port
Run the finder with the adapter plugged in, unplug when prompted, and it tells you which device node is yours. On Linux this is normally a
/dev/ttyACM*node, because the adapter enumerates as USB CDC-ACM rather than as an FTDI part.bashlerobot-find-port # Finding all available ports for the MotorBus. # ['/dev/ttyACM0', '/dev/ttyACM1'] # Remove the usb cable from your MotorsBus and press Enter when done. # The port of this MotorsBus is /dev/ttyACM1 - 2Give yourself access, then stop doing it by hand
LeRobot's docs show
sudo chmod 666 /dev/ttyACM0. That works once and is undone by the next replug. Add yourself to thedialoutgroup instead and log out and in.bashsudo usermod -aG dialout $USER # log out and back in, then verify id -nG | tr ' ' '\n' | grep dialout - 3Pin the device name so it survives a reboot
With a leader and a follower plugged in, ACM0 and ACM1 swap depending on enumeration order. Use the stable symlink under
/dev/serial/by-id/, or write a udev rule keyed on the adapter's serial number.bashls -l /dev/serial/by-id/ # find the attributes to key a rule on udevadm info -a -n /dev/ttyACM0 | grep -E 'idVendor|idProduct|serial' | head # /etc/udev/rules.d/99-lerobot.rules (substitute your own values) # SUBSYSTEM=="tty", ATTRS{idVendor}=="XXXX", ATTRS{serial}=="YYYY", SYMLINK+="robot_follower" sudo udevadm control --reload-rules && sudo udevadm trigger - 4Calibrate, on the Pi, over SSH
Calibration writes a per-robot file that later runs load by
id. Do it on the machine that will drive the arm, and give the arm a name you will still recognise in six months.bashlerobot-calibrate \ --robot.type=so101_follower \ --robot.port=/dev/serial/by-id/usb-...-if00 \ --robot.id=pi_follower_01 - 5Prove the loop before you add cameras
Teleoperate with no cameras configured first. If that is smooth, the bus and the power are fine and any later stutter is video or network.
bashlerobot-teleoperate \ --robot.type=so101_follower \ --robot.port=/dev/serial/by-id/usb-...-if00 \ --robot.id=pi_follower_01 \ --teleop.type=so101_leader \ --teleop.port=/dev/serial/by-id/usb-...-if01 \ --teleop.id=pi_leader_01
Voltage. The SO-100 and SO-101 use the 7.4 V Feetech STS3215, and 12 V destroys them. Waveshare's own ST3215 page advertises 30 kg.cm at 12 V, which is exactly the temptation that kills arms. Check the label on the servo, not on the power brick. Jumper. LeRobot's SO-101 troubleshooting says it plainly: "If you are using a Waveshare controller board, make sure that the two jumpers are set on the B channel (USB)." Position A is UART over the GPIO header. On the wrong position the board enumerates, the port appears, and nothing answers, which looks identical to a dead servo. See servo not responding and arm not detected.
A note on serial latency, since it gets misquoted
You will find advice telling you to write 1 into latency_timer to fix a slow servo bus. The knob is real: the Linux ftdi_sio driver exposes a read-write latency_timer sysfs attribute in units of milliseconds, and the kernel header records FTDI's device default as 16 ms (ftdi_sio.h). It exists only for FTDI parts, which appear as /dev/ttyUSB*. The board LeRobot documents for the SO-100 family appears as /dev/ttyACM* under cdc_acm, where the attribute does not exist. Measure your own loop instead of copying a fix for hardware you do not have.
import time, statistics
from lerobot.robots.so_follower import SO101Follower, SO101FollowerConfig
robot = SO101Follower(SO101FollowerConfig(port="/dev/serial/by-id/usb-...-if00", id="pi_follower_01"))
robot.connect()
samples = []
for _ in range(500):
t0 = time.perf_counter()
robot.get_observation()
samples.append((time.perf_counter() - t0) * 1000)
samples.sort()
print(f"median {statistics.median(samples):.2f} ms p99 {samples[494]:.2f} ms")
robot.disconnect()Power: the Pi is not a power supply
The servo adapter board has its own DC barrel jack, and that is where servo current comes from. The USB cable carries data. People still try to skip the brick and run the arm off the Pi. The current budget says no, and it is not close.
| Rail | Number | Source |
|---|---|---|
| Pi 5 downstream USB, 5 A PSU | 1.6 A total across all ports | Raspberry Pi power supply documentation |
| Pi 5 downstream USB, 3 A PSU | 600 mA total | Raspberry Pi power supply documentation |
| Pi 4 downstream USB | 1.2 A total | Raspberry Pi power supply documentation |
| Pi 5 bare board, typical | 800 mA | Raspberry Pi power supply documentation |
| Waveshare ST3215 bus servo, no load | 200 mA | Waveshare ST3215 specification (12 V variant) |
| Waveshare ST3215 bus servo, locked rotor | 2.7 A | Waveshare ST3215 specification (12 V variant) |
Waveshare publishes those currents for the 12 V ST3215, not for the 7.4 V STS3215 that the SO-100 actually uses, so take them as the order of magnitude rather than as your servo's datasheet. The conclusion survives either way: one stalled servo draws more than a Raspberry Pi 5 will hand to every USB peripheral combined, and an arm has six. Give the bus its own supply, sized for the servo voltage. Then check the Pi is on a real 5 V 5 A USB-C power delivery supply: on anything less it clamps downstream USB to 600 mA, and a camera browning out mid-episode looks exactly like a bad cable.
Run vcgencmd get_throttled. Anything other than throttled=0x0 means the board has seen undervoltage or thermal throttling since boot, and you should fix that before trusting any timing you measured on it. Bit 0 is undervoltage now, bit 16 is undervoltage at some point since boot. Also watch vcgencmd measure_temp during a long recording: the Pi 5's rated ambient tops out at 70 C, and a passively cooled Pi in a printed enclosure with two cameras streaming gets there.
Why inference does not belong on the Pi
Here are the published requirements without commentary. NVIDIA's Isaac-GR00T README lists inference hardware as one GPU with 16 GB or more of VRAM, naming RTX 4090, L40, H100, Jetson AGX Thor and Orin, and DGX Spark; for fine-tuning it recommends 40 GB or more. LeRobot's async guide says Pi0 "occupies 14GB of memory at inference time, while SmolVLA requires only ~2GB". A Raspberry Pi 5 has at most 16 GB of LPDDR4X shared with the OS, the camera buffers and whatever is encoding video in software, and no CUDA device.
| Policy | Params | Inference per action step | GPU tier the platform uses |
|---|---|---|---|
| GR00T N1.7 | about 3 B, about 40 M trained during fine-tuning | 152 ms | A100 80 GB or H100 80 GB |
| GR00T N1.5 | about 3 B | 165 ms | A100 80 GB or H100 80 GB |
| Pi0.5 | about 3 B, PaliGemma backbone | 485 ms | A100 80 GB or H100 80 GB |
| SmolVLA | about 450 M | 245 ms | RTX 4090 or any 24 GB card |
| ACT | about 80 M | 20 ms | RTX 4090 or any 24 GB card |
Read the ACT row carefully, because it is the one that tempts people. Twenty milliseconds per action step is a 50 Hz budget, and it is a GPU number. Moving the same 80 M parameter model to four Cortex-A76 cores with no accelerator is not shaving that budget, it is a different regime. Nobody has published a credible real-time ACT rollout on Pi-class CPU, so treat it as unproven rather than as a plan. Compare the five on the policies page if you are still choosing.
One genuine caveat. The paper behind SmolVLA says the model is "designed to be trained on a single GPU and deployed on consumer-grade GPUs or even CPUs". That is about x86 desktop CPUs, not a 2.4 GHz ARM board, and deployable is not the same as deployable at your control rate. Test it and publish numbers rather than assuming them. Its benchmark results sit next to 84 other models in the arena.
LeRobot's installation guide states that TorchCodec is not available on Linux ARM (aarch64, arm64, armv7l), and that LeRobot falls back to pyav there. Fine for a robot client, which decodes nothing. It matters the moment you try anything training-shaped on the Pi. The base install also wants Python 3.12, and on Linux the guide pins torch and torchvision to the CUDA 12.8 wheel index with a 570.86 driver floor, which is guidance for a machine that has an NVIDIA GPU. You will spend the day on wheels before measuring a single forward pass.
But what about an AI HAT?
Raspberry Pi documents the answer in a capability table. AI HAT+ is a Hailo-8L at 13 TOPS or a Hailo-8 at 26 TOPS, both INT8, and both use the Pi 5's own memory rather than carrying any. That table marks both large language model and vision-language model support as not supported for AI HAT+, and gives its use cases as object detection, camera post-processing, robotics and moderate neural workloads. The newer AI HAT+ 2 is a Hailo-10H at 40 TOPS, INT4, and the real difference is not TOPS: it carries its own 8 GB, which the docs say lets it run LLMs and VLMs "up to ~6 billion parameters".
Read that ceiling carefully. It is a real capability, and it is a capability for models Hailo's toolchain can compile and quantize. A vision-language-action model is a VLM with an action head trained on your robot's joint space, served through a runtime that expects a PyTorch device. That is not a supported path on a Hailo part today, and the checkpoint you produce by fine-tuning is not a drop-in for it. Memory stopped being the obvious blocker on that board; the toolchain became the blocker instead.
- Real vision throughput for detection, segmentation and pose, next to the cameras, with no network hop.
- Native support in rpicam-apps and Picamera2, so a perception preprocessing stage is easy to add.
- Low power. The robot host stays on one 5 V supply.
- Useful as a gate: run a cheap detector locally, wake the expensive policy only when the scene changes.
- TOPS is an integer-operations rating for compiled, quantized graphs. A Hailo part is not a PyTorch device and it does not run an arbitrary checkpoint.
- Raspberry Pi's capability table marks LLM and VLM support as not supported for AI HAT+, both the 13 TOPS Hailo-8L and the 26 TOPS Hailo-8 variant.
- AI HAT+ 2 does support VLMs up to about 6 B parameters on its own 8 GB, but a VLA is a VLM plus an action head served through a robot runtime, which is a different integration problem.
- Every model has to go through the vendor's compiler and quantizer first. Your fine-tuned checkpoint is not a drop-in.
The architecture that actually works: Pi as robot client
LeRobot ships the answer, and their framing is worth reading literally. The RobotClient "streams observations to the PolicyServer, and receives action chunks obtained running inference on the server (which we assume to have better computational resources than the robot controller)". The Pi is the robot controller. Something with a GPU is the server. This is the supported path, not a workaround.
# on the GPU machine
python -m lerobot.async_inference.policy_server \
--host=0.0.0.0 \
--port=8080
# on the Raspberry Pi, next to the arm
python -m lerobot.async_inference.robot_client \
--server_address=<gpu-host>:8080 \
--robot.type=so100_follower \
--robot.port=/dev/serial/by-id/usb-...-if00 \
--robot.id=pi_follower_01 \
--robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30} }" \
--task="Pick up the cube and put it in the bin" \
--policy_type=smolvla \
--pretrained_name_or_path=<user>/<model> \
--policy_device=cuda \
--actions_per_chunk=50 \
--chunk_size_threshold=0.5 \
--aggregate_fn_name=weighted_average \
--debug_visualize_queue_size=TrueThe mechanism that makes this survivable is action chunking. The server returns a batch of future actions, the Pi steps through them locally, and a new observation goes out when the queue drops below the threshold. You pay the round trip once per chunk instead of once per step, and you pay for it in open-loop blindness for the length of the chunk. Hugging Face's async inference post reports sub-100 ms round trips on a local network with SmolVLA on an RTX 4090, and about a 2x speedup in task completion time at comparable success rates.
Note the phrase local network. A GPU box on the same switch is a different animal from a rented GPU across the public internet, and that difference in inference latency is the whole design constraint. Slow, deliberate pick and place tolerates the hop. Fast reactive motion does not. If a policy stalls part way through a motion, the freeze page walks the causes.
The LeKiwi mobile base is the reference implementation of Pi-as-host inside LeRobot. The docs say the on-robot computer "is normally a Raspberry Pi, but can be any PC that can run on 5V and has enough usb ports (2 or more) for the cameras and motor control board". The Pi runs python -m lerobot.robots.lekiwi.lekiwi_host --robot.id=my_awesome_kiwi, the laptop runs the client, control on ZeroMQ port 5555 and video on 5556. Note the split: calibration on the Pi, teleoperation and policy evaluation from the laptop. See SO-100 against LeKiwi if you are choosing between a fixed arm and a base.
Recording datasets on the Pi
Recording is where a Pi is closest to its limit, because it is the only stage that is genuinely compute-bound on the host. LeRobot encodes each camera stream to MP4, and the default encoder is libsvtav1 at preset 12, crf 30, GOP 2. Software AV1 on four ARM cores while two cameras stream and a control loop runs is asking a lot, and there is no hardware encoder to fall back on: the Pi 4 brief lists H.264 1080p30 encode, the Pi 5 brief lists a 4Kp60 HEVC decoder and no encoder at all.
lerobot-record \
--robot.type=so101_follower \
--robot.port=/dev/serial/by-id/usb-...-if00 \
--robot.id=pi_follower_01 \
--robot.cameras="{ front: {type: opencv, index_or_path: 0, width: 640, height: 480, fps: 30} }" \
--teleop.type=so101_leader \
--teleop.port=/dev/serial/by-id/usb-...-if01 \
--teleop.id=pi_leader_01 \
--dataset.repo_id=<user>/<dataset> \
--dataset.num_episodes=50 \
--dataset.single_task="Grab the cube" \
--dataset.streaming_encoding=true \
--dataset.encoder_threads=2 \
--dataset.rgb_encoder.vcodec=h264 \
--dataset.rgb_encoder.preset=fast \
--display_data=falseLeRobot writes the encoder configuration into meta/info.json once, from the first episode, and assumes every later episode matches. Record 20 episodes, notice dropped frames, change vcodec, record 30 more, and the metadata describes the first 20 and lies about the rest. Merging makes it worse: video.codec, pix_fmt, height, width and fps must match across sources or FFmpeg's concat demuxer fails outright. Dropped frames also surface later as a dataset that trains to a low loss and yields a policy that does nothing, covered on the loss-falls page.
Whatever you record ends up as a LeRobot dataset, and the format version matters downstream: v3.0 for Pi0.5, SmolVLA and ACT, v2.0 or v2.1 for GR00T, which crashes on a v3.0 dataset and needs it converted down. A training-time problem rather than a Pi problem, but worth knowing before you record fifty episodes. Public examples live in the dataset directory.

Two ways to get a policy running next to your arm
Same goal, two paths: a trained policy driving your SO-100 with the Pi as host. One is entirely yours, one rents the GPU parts. The Pi's job is identical in both.
- Install LeRobot on the Pi from source with the
core_scriptsandasyncextras. Python 3.12, and expect the pyav fallback rather than TorchCodec on aarch64. - Pin device names with udev, calibrate on the Pi, prove teleoperation with cameras off.
- Add cameras one at a time, forcing MJPEG, checking
lsusb -tand the timing loop after each. - Record 50 or more episodes with
--dataset.rgb_encoder.vcodec=h264and push to the Hub. - Rent or own a GPU box, install LeRobot there with the training extras and a CUDA wheel matching your driver, and train.
- Run
lerobot.async_inference.policy_serveron that box andlerobot.async_inference.robot_clienton the Pi, then tunechunk_size_thresholdwith--debug_visualize_queue_size.
Moving parts you own: two Python environments on two architectures, a udev ruleset, an encoder config, a GPU driver stack, a checkpoint store and a gRPC endpoint you keep reachable. All documented, none mysterious. It is simply a lot of surface, and the failures cluster on the ARM side where the wheels are thinnest.
- Record with the desktop client from /download, or bring a dataset from a Hugging Face repo id or your own machine.
- Pick model and dataset on /train. The backend rents a GPU on a spot market by required VRAM, runs the trainer and writes checkpoints to object storage.
- For inference,
/api/inference/podprovisions a cloud GPU pod serving the policy, and the local robot client talks to that endpoint. Pods carry an idle watchdog and destroy themselves after an idle period. - Drive it from the browser, from the CLI, or from an agent through the MCP server.
| Tier | Models | Typical run | Typical cost |
|---|---|---|---|
| A100 80 GB / H100 | GR00T N1.7, GR00T N1.5, Pi0.5 | 3 to 6 hours at 1.20 to 2.00 USD per hour | about 4 to 12 USD |
| RTX 4090 / 24 GB | SmolVLA, ACT | 2 to 5 hours at 0.30 to 0.60 USD per hour | about 1 to 3 USD |
The platform removes the GPU plumbing. It does not remove the round trip. A cloud pod sits behind the public internet, and the control loop is 20 to 485 ms per action step before the network is involved. Workable for slow pick and place, not for fast reactive motion. It also does nothing about your USB topology, camera format or servo voltage. Those stay yours, which is most of what this page is about.

Which Pi, and when to stop using a Pi
Buying today for one SO-100 with two cameras: a Raspberry Pi 5 with 8 GB, a 5 V 5 A USB-C power delivery supply and active cooling. The independent USB controllers are the reason, not the CPU. A Pi 4 works with one camera and is painful with two.
- Pi 4, one camera, MJPEG. Fine for teleoperation and a first dataset. Everything shares one VL805 uplink, so expect a fight for the second stream.
- Pi 5, two or three cameras. The sensible default. Put each camera on a different root hub and confirm with
lsusb -t. - Pi 5 plus AI HAT+. Worth it for local detection or segmentation as a preprocessing stage. Not a way to run a VLA.
- Small x86 box. Higher resolution, more than three cameras, or a fully local policy: an x86 mini PC removes the ARM wheel problem and the encoder problem at once.
- Jetson-class module. The only edge option with a CUDA device, and the one NVIDIA names in the Isaac-GR00T inference list. A different price bracket.
Whatever you pick, the split stays the same. The host owns time and I/O, the GPU owns the policy, and the wire between them is the thing you measure. To feel the loop before buying anything, the live arm is a physical SO-100 you can drive from a browser with no signup, and the try page lays out the three starting points. The SO-100 complete guide covers the assembly and calibration this page assumes you have done.
Can a Raspberry Pi 5 run a VLA policy locally?▾
Not at a useful control rate. NVIDIA's Isaac-GR00T README lists inference hardware as one GPU with 16 GB or more of VRAM. LeRobot's async guide puts Pi0 at 14 GB at inference time and SmolVLA at about 2 GB. A Pi 5 has at most 16 GB of LPDDR4X shared with everything else and no CUDA device. SmolVLA's paper does claim CPU deployment, but that is about desktop CPUs, and no real-time ARM rollout has been published. Run the policy on a GPU and the robot client on the Pi.
Why do two USB cameras fail on the Pi when each works alone?▾
Because USB video class cameras reserve bandwidth when the stream starts, and uncompressed YUYV is enormous: a 1920x1080 30 fps YUYV stream is about 995 Mbit/s against a 480 Mbit/s USB 2.0 bus. Force MJPEG, which most webcams encode in hardware, and confirm with v4l2-ctl --list-formats-ext that the camera offers it. On a Pi 4 all four ports share one VL805 controller behind a single PCIe Gen 2 lane; on a Pi 5 the RP1 datasheet says every downstream port has independent and uncontended bandwidth, which is why the Pi 5 is the right board for two cameras.
Should I power the servos from the Raspberry Pi?▾
No. The servo adapter board has its own DC input for exactly this reason. A Pi 5 on a 5 A supply provides 1.6 A total to all downstream USB peripherals, and 600 mA on a 3 A supply. Waveshare quotes 2.7 A locked-rotor current for one ST3215 bus servo, and that is the 12 V variant rather than the 7.4 V STS3215 the arm uses, so treat it as the order of magnitude. Watch the voltage too: the SO-100 and SO-101 use the 7.4 V STS3215, and 12 V destroys them.
Which device path should I use, /dev/ttyACM0 or a udev symlink?▾
A symlink. With a leader and a follower plugged in, ACM0 and ACM1 swap depending on enumeration order, so a hard-coded path works until the first reboot. Use /dev/serial/by-id/ or a udev rule keyed on the adapter's serial number, then point --robot.port at the stable name.
Is recording a LeRobot dataset on a Pi realistic?▾
Yes, if you change the encoder. LeRobot defaults to libsvtav1 at preset 12, which is software AV1, and the Pi 5 has no hardware encoder to fall back on: its brief lists a 4Kp60 HEVC decoder and no encoder, while the Pi 4 brief lists H.264 1080p30 encode. Pass --dataset.rgb_encoder.vcodec=h264 and --dataset.rgb_encoder.preset=fast, turn the live viewer off, and watch for dropped frames. Decide before episode one, because LeRobot writes the encoder config into meta/info.json from the first episode and assumes the rest match.
Does an AI HAT+ change the answer on local inference?▾
Not today. Raspberry Pi's capability table lists AI HAT+ as a Hailo-8L at 13 TOPS or Hailo-8 at 26 TOPS, both INT8, using the Pi 5's memory, and marks LLM and VLM support as not supported. The AI HAT+ 2 is a Hailo-10H at 40 TOPS with its own 8 GB and does run LLMs and VLMs up to about 6 billion parameters. That is a real change, but a VLA is a VLM with an action head served through a runtime that expects a PyTorch device, and every model must pass through Hailo's compiler. Treat an AI HAT as a local perception stage, not a way to serve your fine-tuned policy.
Record the dataset, not the yak shave
The AY-Robots desktop client records LeRobot-format datasets straight from a teleoperation session: episodes, camera streams and joint states, in the format the trainers expect. Check the supported platforms and get it set up before you spend a weekend on encoder flags.
Get the desktop clientSources
- Raspberry Pi 5 Product Brief (published April 2026)
- Raspberry Pi 4 Model B Product Brief (published April 2026)
- Raspberry Pi RP1 Peripherals datasheet, chapter 5 (USB)
- Raspberry Pi documentation source: power supplies and downstream USB current
- Raspberry Pi Compute Module 4 datasheet (Pi 4 Model B routes its PCIe 2.0 x1 to the VL805 USB controller)
- Raspberry Pi documentation source: AI HATs (AI HAT+ vs AI HAT+ 2 capability table)
- LeRobot installation guide (Python 3.12, TorchCodec unavailable on Linux ARM)
- LeRobot: Asynchronous Inference (PolicyServer and RobotClient)
- LeRobot: LeKiwi, a Raspberry Pi as the on-robot host
- LeRobot: SO-101 setup, lerobot-find-port and the Waveshare jumper note
- LeRobot: Video encoding parameters (libsvtav1 default, info.json persistence)
- Hugging Face: Asynchronous robot inference, measured round trips and speedup
- NVIDIA Isaac-GR00T: inference and fine-tuning hardware requirements
- SmolVLA: A Vision-Language-Action Model for Affordable and Efficient Robotics
- Waveshare ST3215 bus servo specification (200 mA no load, 2.7 A locked rotor)
Sources
- Raspberry Pi 5 Product Brief (published April 2026)
- Raspberry Pi 4 Model B Product Brief (published April 2026)
- Raspberry Pi RP1 Peripherals datasheet, chapter 5 (USB)
- Raspberry Pi documentation source: power supplies and downstream USB current
- Raspberry Pi Compute Module 4 datasheet (Pi 4 Model B routes its PCIe 2.0 x1 to the VL805 USB controller)
- Raspberry Pi documentation source: AI HATs (AI HAT+ vs AI HAT+ 2 capability table)
- LeRobot installation guide (Python 3.12, TorchCodec unavailable on Linux ARM)
- LeRobot: Asynchronous Inference (PolicyServer and RobotClient)
- LeRobot: LeKiwi, a Raspberry Pi as the on-robot host
- LeRobot: SO-101 setup, lerobot-find-port and the Waveshare jumper note
- LeRobot: Video encoding parameters (libsvtav1 default, info.json persistence)
- Hugging Face: Asynchronous robot inference, measured round trips and speedup
- NVIDIA Isaac-GR00T: inference and fine-tuning hardware requirements
- SmolVLA: A Vision-Language-Action Model for Affordable and Efficient Robotics
- Waveshare ST3215 bus servo specification (200 mA no load, 2.7 A locked rotor)
Ready for high-quality robotics data?
AY-Robots connects your robots to skilled operators worldwide.
Get Started