getporti lab · trained robot brains

The policies behind the getporti robotics videos: Unitree Go2, G1 and Go1, Open Duck Mini v2, a microduck kick and a bimanual ALOHA arm pair, all trained by us in simulation (MuJoCo MJX + brax PPO; mjlab for the kick; LeRobot ACT for the arms), plus every checkpoint along the way and the transcripts of LLM agents that use them as tools. Each experiment changes one thing in an official recipe and keeps a matched control, so most brains here come in pairs: the one that learned the trick and the one that did not.

The training code, frozen experiment designs and full result tables live in the getporti-lab GitHub repository, which is private for now; this page has everything needed to run the brains. Every number below is grade L: ours, simulated, usually one training seed per arm. None of these policies has been run on a real robot.

Brains

Go2 · go2-patrol/, go2-latency/

MuJoCo Playground's Go1 joystick task with the Go2 swapped in. Input 48 numbers, output 12 joint targets.

File What it is Result
go2-patrol/flat_201M.pkl the walking brain, 200 M steps from scratch (13.8 min on an A100) 0.75 m/s, 0/16 falls on flat
go2-patrol/bumps_finetune_41M.pkl flat brain fine-tuned 40 M steps on a 5 cm bumpy floor 0/16 falls on bumps (flat brain: 3/16)
go2-patrol/bumps_scratch_41M.pkl control: same 40 M steps on bumps, from scratch 0/16 falls, but 0.005 m/s: it learned to stand
go2-latency/delay_finetune_41M.pkl flat brain continued 40 M with random motor delay 0–60 ms 0/16 falls up to 60 ms at 0.74 m/s (flat brain breaks at 32 ms)
go2-latency/delay_scratch_201M.pkl control: random delay from scratch, 200 M never falls, never walks (0.01 m/s)

Karabaş, one Go2 brain through seven obstacles · karabas/

Each stage restores the policy and value function of an earlier one. Files 05_* and 06_* see a 55-point height scan (input 103); the blind controls get the same 103 inputs with the scan zeroed.

File Stage Result
01_slope_64M.pkl slope park, grade curriculum cobbled street climbed 16/16 (MJX)
02_melon_payload_20M.pkl random payload 0–30 kg during training 20 kg melon on the back: 0/32 falls
02_melon_control_20M.pkl control: same steps, no payload 32/32 falls with the melon
03_stairs_blind_82M.pkl blind stair curriculum limit 5–6 cm risers; 16 cm 0/32
04_bump_16M.pkl sharp-bump park 10 cm speed bump 53/64 (start brain 14/64)
05_curb_seeing_45M.pkl height scan in the policy, curb park 15 cm curb 30/32 at this checkpoint
05_curb_blind_45M.pkl control: scan zeroed learns to halt at the curb
06_stairs_seeing_78M.pkl stair rematch, gated reward relief + level curriculum 16 cm stairs, median 9 of 10 steps
06_stairs_blind_66M.pkl control: same recipe, scan zeroed last checkpoint of the blind arm

G1 humanoid · g1-push/

Playground G1JoystickFlatTerrain, 200 M steps each. Input 103, output 29.

File What it is Falls in 480 scripted pushes
g1-push/push.pkl trained with random pushes (Playground default) 130
g1-push/nopush.pkl control: pushes off 260 (Unitree's released policy: 252)

Go1 · go1/

File What it is
go1/go1_93M.{pkl,onnx} the official Go1 recipe trained on a Mac CPU (960 envs); the first checkpoint that walks, 2 h 05 min

Open Duck Mini v2 · open-duck/

Open_Duck_Playground joystick task, trained on a Mac CPU. Input 101, output 14; ONNX next to every pickle.

File What it is
walk_68M, walk_72M walking from scratch: first lifts its feet at 68.4 M (29.8 cm in 5 s)
hacked_fast, hacked_fast_no_flip_end, hacked_energy, hacked_feet_air the 68.4 M duck continued on deliberately broken rewards; together they found flipping and crawling, freezing, and lying with feet up
no_imitation_109M imitation reward removed: it stands, on every checkpoint
torque_random_{89M,138M,301M} servo torque randomised: walks at the 1.86 N·m catalogue torque at 89 M and 138 M, has forgotten by 301 M

Two-arm Turkish coffee · aloha-act/

LeRobot ACT (lerobot==0.6.1) on gym-aloha's bimanual ViperX with our cezve, cup and 60 balls. Load the folder with ACTPolicy.from_pretrained.

Folder Demos Successful pours / 32 at 20 k steps
coffee_act_d50_20k/ 50 9
coffee_act_d10_20k/ 10 4

Goal kick, our RL kick for an LLM agent · goal-kick/out/

Trained by us on Pollen's microduck BallKick task (mjlab + rsl_rl PPO, 4 096 envs, 1 000 iterations, A100), ball anywhere in a 7 × 7 cm patch beside the right foot. ONNX, Pollen's input layout; *.onnx.json holds the observation spec.

File Inputs Result
blind.onnx 61 (proprioception, no ball) 19 goals on 30 patch spots (Pollen's kick: 14); 26/81 in the strands-robots world
see.onnx 67 (+ ball position and velocity) ties Blind in training (30 vs 32 of 81), 0/81 in the strands-robots world

eval-{Blind,See}-<iteration>.npz are the 81-spot evaluations at iterations 0, 250, 500, 750 and 999.

Learning, step by step · checkpoints/

The full checkpoint series of every training run above (≈ 1 GB): each brain as it was at every evaluation during training, named by environment steps (ckpt_<steps>.pkl, ONNX next to it where we exported one). Load any of them the same way as the brains above and watch when a skill appears: when the Go2 first walks, when Karabaş first climbs a step, when the duck lifts its feet and when it forgets.

checkpoints/<experiment>/<arm>/, for example checkpoints/karabas/06_stairs_seeing/, checkpoints/open-duck/walk/, checkpoints/go2-patrol/flat/. Arms follow the tables above; _a1, _a3 are the earlier design amendments of a stage.

LLM agents driving robots · agent-runs/

Transcripts of Strands Agents runs with a local model (qwen 2.5 / 3.5 via Ollama) that controls robots through tools:

  • agent-runs/strands-agent/<run>/: an SO-100 arm asked to pick up a cube; tools.jsonl is every tool call with its arguments and result, qpos.jsonl the arm's state, summary.json the outcome.
  • agent-runs/goal-kick/: the microduck agent that places a ball and kicks: goal-*.json per run (placements, kicks, goals), *.log the agent's own output. With the original tool description it never scored in 9 runs; with one sentence saying where the ball must lie, 4 of 6 runs scored.

Use

Each .pkl is a brax PPO checkpoint (normalizer, policy, value). The policy is a small MLP, so plain numpy runs it; no JAX or brax is needed. Copy this, or download load_brain.py from this repo:

import pickle
import numpy as np
from huggingface_hub import hf_hub_download

class _Bag:                                        # stands in for brax/flax classes: no JAX needed
    def __init__(self, *a, **k): pass
    def __setstate__(self, s): self.__dict__.update(s if isinstance(s, dict) else {"state": s})

class _Unpickler(pickle.Unpickler):
    def find_class(self, module, name):
        if module.split(".")[0] in ("brax", "flax", "jax", "jaxlib", "optax", "ml_collections"):
            return _Bag
        return super().find_class(module, name)

def load_brain(path):
    """brax PPO checkpoint (normalizer, policy, value) -> numpy function: observation -> action."""
    with open(path, "rb") as f:
        norm, policy, _ = _Unpickler(f).load()
    get = lambda o, k: o[k] if isinstance(o, dict) else getattr(o, k)
    stat = lambda k: np.asarray(get(get(norm, k), "state") if not isinstance(get(norm, k), np.ndarray) else get(norm, k))
    mean, std = stat("mean"), stat("std")
    p = policy["params"]
    layers = [(np.asarray(p[k]["kernel"]), np.asarray(p[k]["bias"])) for k in sorted(p, key=lambda s: int(s.split("_")[-1]))]

    def act(obs):
        x = (np.asarray(obs, np.float64) - mean) / std
        with np.errstate(all="ignore"):            # numpy on macOS Accelerate raises spurious matmul flags
            for i, (w, b) in enumerate(layers):
                x = x @ w + b
                if i < len(layers) - 1:
                    x = x / (1 + np.exp(-x))       # swish
        return np.tanh(x[: len(x) // 2])          # deterministic action = tanh(mean)
    return act

brain = load_brain(hf_hub_download("fport/getporti-lab", "go2-patrol/flat_201M.pkl"))
action = brain(np.zeros(48))                       # the 48-number Go2 observation, layout below
print(action.shape, action.round(2))               # 12 numbers; joint target = home pose + 0.5 * action

What goes in and what comes out:

Robot Input Output Control
Go2, Go1 (48) body velocity (3), gyro (3), gravity in body frame (3), joint angles − home (12), joint velocities (12), last action (12), command vx vy yaw-rate (3) 12 target = home + 0.5 · action, 50 Hz, Kp 35, Kd 0.5
Karabaş 05_*, 06_* (103) the 48 above + a 55-point height scan (11 × 5 grid, 0.4 m behind to 0.6 m ahead of the trunk, ±0.2 m sideways; zeros for the blind controls) 12 same as Go2
G1 (103) Playground G1JoystickFlatTerrain observation 29 Playground G1 joystick task
Open Duck (101) Open_Duck_Playground joystick observation 14 also as ONNX, input obs, same interface as the official export

To roll one out in MuJoCo, use the matching MuJoCo Playground task (Go2 = the Go1 joystick task with Menagerie's unitree_go2) and feed the observation every control step. The run_config*.json next to each brain records the env, the one variable, the PPO settings, the restore point and the device, so a run can be repeated or continued (restore_params with the same pickle).

The two ACT folders load with LeRobot 0.6.1: ACTPolicy.from_pretrained("<folder>").

Pins

jax 0.6.2, brax 0.12.4, playground 0.0.5, mujoco / mujoco-mjx 3.3.3, Menagerie cda5236, Open_Duck_Playground b9be205; goal kick: microduck_rl bae39f5 (mjlab), strands-robots 0.6.0; agents: strands-agents 1.62 / 1.64. A different version can change the physics and with it every number above.

Downloads last month

-

Downloads are not tracked for this model. How to track
Video Preview
loading