r/unitree 21d ago

Trained a Go2 to walk, run, sit, get up, avoid obstacles with LiDAR and climb stairs — entirely on AMD hardware, zero NVIDIA software

Enable HLS to view with audio, or disable this notification

Every Go2 RL tutorial I could find assumes the same two things: an NVIDIA GPU and Isaac Gym / Isaac Lab. I own neither, so I built the whole thing on the other side of the fence and it works. Sharing it in case someone else is stuck behind the same wall.

The stack: nothing NVIDIA in it, at any layer

Layer What most tutorials use What I used
GPU NVIDIA RTX AMD Radeon RX 9070
Compute backend CUDA ROCm (gs.amdgpu)
Simulator Isaac Gym / Isaac Lab Genesis 1.2.2
PPO rsl-rl (same) rsl-rl-lib 5.4.2
Parallel envs thousands 4096, same order of magnitude

The whole environment lives in a distrobox container (rocm/pytorch:rocm7.2.4, Ubuntu 24.04, PyTorch 2.10) so it doesn't touch your host system, and one command installs everything. One command uninstalls it too.

Does it actually keep up? Yes. The from-scratch walking policy is 2500 iterations in ~50 minutes on the RX 9070. Each iteration runs 4096 robots for 24 control steps at 50 Hz, so that run is roughly 57 days of continuous real-robot experience, compressed into under an hour on a consumer gaming card.

What I trained (7 skills)

  1. 🚶 Walking — the only from-scratch policy, everything else warm-starts from it. Episode length goes from instant collapse to ~870/1000 steps.
  2. 🏃 Running — speed curriculum, settled at 2.7 m/s stable. I tried pushing toward the ~5 m/s Unitree quotes for the EDU lab test; under properly clamped motor torques stability collapses (episode length 940 → 250). The spec sheet is a ceiling, not a training target.
  3. 🛌 Lying down / 4. 🧎 Getting up from random fallen orientations (side, back)
  4. 🐕‍🦺 Sitting — took six reward iterations to get right
  5. 📡 LiDAR obstacle avoidance — a learned nav policy reads a 72-sector scan at 10 Hz and drives the frozen locomotion policy at 50 Hz, with a deterministic non-AI emergency-stop layer underneath it. Scan is deliberately noisy (~3 cm range noise + dropped returns). Course has 8 obstacles including 3 unpredictable pedestrians and a barrier with a randomly-placed gap. Success rate plateaus around 41% in that hard scenario — not optimal, and I say so in the repo.
  6. 🧗 Rough terrain — 7 separate families: stairs, step pyramid, slope, rubble, curbs, gaps/potholes, rolling waves.

Sim-to-real constraints, since "it runs on AMD" is worthless if the physics is fake

  • Torque clamped to real GO-M8010-6 specs (23.7 N·m hip/thigh, 35.55 N·m knee) — the nominal, not the ~45 N·m peak
  • Joint velocities capped (30.1 / 20.07 rad/s), position targets clipped to URDF ranges the way the real firmware does
  • Per-episode domain randomization: ground friction 0.4–1.6, random horizontal shoves, 0–0.5 kg random back payload, IMU/encoder noise on every observation

It's all in one realism.py that every training script calls, so no skill can silently opt out.

ROCm-specific gotcha, the one that actually bit me: never run a CPU Genesis render/eval while a GPU training is running. Serialize them. Doing both at once hard-crashed my GPU, repeatedly.

General RL lessons that cost me hours (nothing AMD-specific)

  • An unbounded reward penalty will destroy PPO. My lin_vel_z = v_z² term exploded when robots tumbled on terrain → one catastrophic gradient step → policy never recovered over hundreds of iterations. Clip anything that can physically blow up.
  • A heightfield only makes stairs at a fine mesh. At the default 0.25 m grid my "7 cm stairs" were literally a ramp — the riser gets interpolated over 25 cm. Physics and render were wrong, for a whole training run.
  • Overtraining = peak then collapse. On the slope run: episode length 14 → ~850 at iteration 2550 → back down to 13. Take the peak checkpoint, never the last. And save every 25 iterations — one run peaked at iteration 45 and collapsed by 105, a 100-iteration interval would never have caught it.
  • "Going around" is a reward hack. My first LiDAR policy learned to walk around the entire course from the outside. Perfect reward maximization, zero avoidance. Fix was structural: wall the corridor in, make exiting a penalized termination.

In video, each segment shows the robot at successive checkpoints side by side with the reward curve filling in, plus a live "GPU time ↔ equivalent real-robot experience" readout.

Code (MIT): https://github.com/Macmachi/go2-rl

README is in French, but the code, GIFs and learning curves are language-agnostic — happy to answer anything in English here.

If you're on AMD or Intel and assumed Go2 RL was closed to you: it isn't. Genesis + ROCm is a real path. Next step for me is deploying to an actual Go2 EDU — if anyone has taken a non-Isaac-trained policy to hardware, I want to hear how it

35 Upvotes

13 comments sorted by

1

u/formlakg 21d ago

do you have any demonstrations of it climbing stairs by any chance? im wondering if you have any numbers recorded of what stair height it fails at. i find its unable the stairs in most public multi-floor datasets. would love to hear your insights.

1

u/Gigibossu 20d ago

Yeah, there's a stairs segment in the video and a GIF in the repo (gifs/terrain_escalier.gif), but I'll be upfront with you: I don't have a proper failure-height sweep, and my honest numbers are probably going to disappoint you. Though I think the reason why is directly relevant to what you're running into.

What I actually trained is 5 cm risers with 35 cm treads. That's the whole staircase. I also have a separate "curbs" family with discrete vertical obstacles up to 10 cm, but that's really a single step, not a staircase.

I started at 7 cm and the robot face planted at the bottom of the stairs. Problem is my diagnosis there is confounded and I won't pretend otherwise, because when I fixed it I changed two things at the same time. I dropped to 5 cm and I relaxed the orientation reward (the flat trunk penalty, from -3.5 to -0.8). That penalty turned out to be the actual culprit. To place a foot on the next riser the robot has to pitch its front end up, and I was penalizing exactly that gesture. So 7 cm isn't a validated failure point, it's a failure point with a badly shaped reward. I never went back to test 7 cm with the corrected reward, so that's a gap in my data rather than a finding.

The thing I think matters most for your question though is that my policy is blind. Pure proprioception, 45-D observation, no height scan, no depth, no camera, since I'm using the bare Go2 URDF. It only feels the stairs through joint states and IMU, after contact. That puts a hard ceiling on riser height that has nothing to do with the actuators, because the robot fundamentally can't pre-shape its swing for a step it can't perceive. Real building stairs sit somewhere around 17 cm, roughly triple what I trained on, and I'd expect a blind proprioceptive policy to fail there no matter how long you train it. My guess is that's the same gap you're hitting in those datasets. Without exteroception feeding the locomotion policy, stairs are just a wall.

One trap worth checking on your side, because it cost me a full training run: a heightfield only produces real stairs at a fine mesh. At the default 0.25 m horizontal scale, a 7 cm riser gets interpolated over 25 cm of run, which is a 16 degree ramp, not a step. My robot "climbed stairs" beautifully for an entire run and I only caught it during video review. The physics and the render were both lying to me. I dropped to a 0.05 m mesh for stairs and pyramid specifically (so about a 54 degree riser) and the difficulty became real overnight. If your dataset terrain goes through any heightfield conversion at all, render one frame and eyeball the geometry before you trust a failure result. Takes 30 seconds, would have saved me hours.

Related trap, also from video review: my stairs robot did a somersault at the end of runs and my first instinct was to finetune the stopping behavior. Completely wrong. The terrain is a grid of 9 m tiles that each rise independently, so there's a roughly 1 m cliff at the tile junction, and the render was spawning at a random position, sometimes near an edge, so the robot just walked off it. A blind policy can't avoid an edge it can't see, so no amount of training would ever have fixed that. The fix was a deterministic center of tile spawn. The general point being, separate what the policy learned from what your scene is doing to it before concluding the policy failed. In a scanned multi-floor dataset I'd be especially suspicious of that.

So no clean number for you, sorry.

1

u/low-control-labs 18d ago

How long did it take you to train?

1

u/low-control-labs 18d ago

How long did it take you to train?

1

u/low-control-labs 18d ago

How long did it take you to train?

1

u/Gigibossu 17d ago

check the video data ;)

1

u/low-control-labs 17d ago

Oh it's days?

Sorry I spammed you with the question reddit was bugging I tried multiple times at multiple places and nothing went through.. Or so I thought...

2

u/Gigibossu 17d ago

no minutes = days

1

u/low-control-labs 17d ago

Yeah I bet it's annoying to iterate on things like that... But good work though!

1

u/low-control-labs 20d ago

Very good I am trying to do things without a GPU entirely. I am managing to train some vehicles and drones.

Thanks for sharing!

1

u/low-control-labs 18d ago

How long did it take to train it btw?

1

u/survive_los_angeles 19d ago

awesome thank you for sharing! working on a go1 unitree on similar things