~/satyajit

Metatron v1: a 100-million-parameter humanoid policy, run on a CPU

mdjsonmcp

2026-10-31 · 16 min · robotics · humanoid · nvidia · reinforcement-learning · onnx

Why read this

Notabletop 60%

Licence and cards read; the real ONNX graph run on CPU: latency, determinism, mask gating, action probe measured. Synthetic inputs, no simulator.

  • Original analysis
  • Runs on a laptop CPU
  • A new technique

Robotics & embodiedCustom licencePractitioner model

How this was scored
Is it new?
2 of 3: A real new idea, method or capability
Can I trust it?
2 of 3: Measures key facts from files, code or configs
Can I run it?
2 of 3: Open code or weights with real limits
Will I understand it?
2 of 3: Mechanism from first principles with figures
Can I act on it?
2 of 3: A concrete recipe, numbers or comparison
Will it last?
1 of 3: Relevant for months
Does it affect many?
1 of 3: A specialist community
Only here?
2 of 3: A teardown or measurement few others did

Score 63 of 100, ranked 314 of 666 rated articles. Each question is answered 0–3 by hand, and a 3 is rare. How articles are scored

Metatron v1 is NVIDIA's controller for the Unitree G1 humanoid. The announcement (@HuggingPapers) calls it a unified policy that handles walking, scene interaction and recovery, with ONNX exports for real-time use. The weights are on Hugging Face as nvidia/Metatron-v1.

This article is a read and a run of that release. I read the licence and the model cards, inspected the ONNX graph, and ran the real policy with onnxruntime on a CPU, feeding it synthetic observations. Every claim carries one of three labels. Measured means I computed it from a file or ran it. Reported means it comes from NVIDIA's cards and I did not check it. Reasoned means arithmetic or inference from the first two.

What I did not do matters more than usual here, so first. There was no simulator and no robot. The policy is a closed loop: its output moves a body, the body's new state is its next input. I fed it made-up states and looked at single steps. That tells you what the network is made of, what it costs and how its inputs are wired. It tells you nothing about whether the G1 stays upright. I also found no paper for Metatron v1; the cards point at the earlier MaskedMimic work and at NVIDIA's ProtoMotions library, and I treat training claims as reported.

nvidia/Metatron-v1@ada8167 · snapshot 2026-10-31
repo size
2.46 GB
task
reinforcement-learning
library
onnx
license
other
largest file
459.5 MB
files
18
downloads
1.2K
likes
9
languages
en
roboticsreinforcement-learninghumanoidonnxmetatron

repo last modified 2026-10-29

NVlabs/ProtoMotions@ebb656b · snapshot 2026-10-31
tracked files
1,392
license
Apache-2.0
branch
main
tests
289 files
source
8.2 MB
commit date
2026-10-30
source by language
Python7.6 MB(703)C++441.4 kB(95)Shell57.4 kB(7)CUDA11.2 kB(1)C6.7 kB(2)Makefile0.6 kB(1)

by size of tracked source at this commit, file counts in brackets; docs, data and vendored trees excluded

local clone, 2026-10-31 at ebb656b — branch, commit, commitDate, fileCount, hasTests, languages, license, licenseFile, shallow, testFileCount

shallow clone: counts describe the pinned tree, not the history

The short version

Licence

The repo tag says license: other, the card names the NVIDIA Open Model License, and the license file is the NVIDIA Open Model License Agreement. I read the two-page PDF (measured). It opens by saying that models are commercially usable, that you may create and distribute derivative models, and that NVIDIA claims no ownership of outputs. The grant is perpetual, worldwide, non-exclusive, royalty-free and revocable under section 2.1. Two things end it: suing anyone over the model's IP, and bypassing a guardrail "without a substantially similar Guardrail appropriate for your use case". If you redistribute, you pass on the agreement and a notice file saying "Licensed by NVIDIA Corporation under the NVIDIA Open Model License". Use has to follow NVIDIA's Trustworthy AI terms.

Three limits are specific to this release. The licence covers weights, configs and exports, and the source code (ProtoMotions) stays Apache-2.0 (reported, README). Motion datasets, recovery clips, robot meshes and game assets are not included and have their own terms (reported). And the card says that permission to show a clip inside a demo does not mean you may redistribute it as a dataset. So you can ship the policy; you cannot assume you can ship what it was trained on. I am not a lawyer, and the full agreement governs.

What is in the repo

The repo has three packages (measured, file listing and sizes).

PackageWhatSize
G1/PyTorch checkpoint inference_last.ckpt, resolved config, experiment code459,455,983 bytes
demo/model.onnxsingle-robot browser pipeline, with observation processing and action conversion inside403,375,789 bytes
racer/network.onnxsix-robot batched network for a browser game, observations computed outside403,036,698 bytes

The card says the demo and Racer exports carry the same learned weights in different graph layouts, and are not interchangeable (reported). It says the six-robot path matched the single-robot export to a maximum absolute action error of 5.55e-6 on 60 test states (reported). I downloaded only demo/model.onnx and the 439 KB graph skeleton, and did not open the Racer file or the PyTorch checkpoint.

One more reported detail changes how to read the release: the native PyTorch preset masks the scene inputs, while the 39-input ONNX keeps them. The card says weights retain scene conditioning, but "a scene-capable host must supply the matching object measurements", and that no scene provider ships with ProtoMotions.

What the model is, as reported

The card describes a MaskedMimic-style student: a spherical conditional VAE with a 64-dimensional latent, and a six-layer, four-head, 1024-wide transformer for the prior and the conditioned action decoder (reported, G1 card and resolved_configs.json). It was trained in IsaacLab by supervised imitation from frozen experts for motion tracking, recovery and scene interaction, on BONES, AMASS/LAFAN, fall-recovery and SAMP interaction data (reported). Inference uses the learned prior with no teacher, and the browser graphs export that deterministic path only.

The config also fixes the control contract: 50 Hz control with a 1000 Hz MuJoCo timestep, a 29-joint G1 and use_implicit_pd: true (measured, from resolved_configs.json). The policy is told five future frames, the history is eight past states, and the robot's own body poses are not inputs; they are recomputed inside the policy from joint readings and the torso pose with a forward-kinematics kernel (reported).

The graph

I inspected the ONNX with the onnx package (1.23.2). The graph was exported by PyTorch 2.11.0 at opset 17 (measured).

The 39 inputs are 4,002 numbers: 3,808 floats and 194 booleans (measured). Those numbers are not spread evenly:

So 61% of the input is a goal you may leave empty. A walking command uses only the velocity pair and its mask.

The operator histogram of the 2,914 nodes is mostly plumbing (measured): Mul 397, Unsqueeze 339, Add 321, Reshape 303, Gather 232, MatMul 223, Sub 213, Slice 162, Concat 141, Squeeze 127, Gemm 70. Twelve LayerNormalization nodes and six Softmax nodes are the transformer: one six-layer stack, each layer with a 1024-by-3072 attention input, a 1024-by-1024 output and a 1024-by-4096 feed-forward pair (measured). That stack alone is about 75.5 million of the 100.7 million parameters (reasoned, 6 layers of 12.6 million). The rest is the observation processing, sines, cosines and quaternion arithmetic that the export bakes in (Cos 10, Sin 10, Atan 7, ReduceL2 8), which is why the graph has so many small ops. The card says the demo export includes observation processing and action conversion, and the node list agrees.

For scale, the MotionBricks article counted 194.5 million parameters for NVIDIA's motion generator for the same robot, in three networks. Metatron is about half that size, and the two do different jobs (reasoned). MotionBricks writes a short clip of poses from recent and target poses, with replanning at 10 Hz in the paper. Metatron takes poses, speed commands and the robot's own state, and writes joint targets at the control rate. One is a planner of motion, the other is a tracker that turns motion into torque-level commands. They could be stacked, but I found no card that says NVIDIA does so, so that is my reading, not a claim about the release.

Reading the action

Inputs are half the contract. The outputs are action (29 joint position targets), stiffness and damping (29 each), and raw_action, which is fed back as history (measured).

I checked how they relate. With the config's pd_action_offset (the default standing pose: hips at -0.312, knees at 0.669, ankles at -0.363 rad and so on) and action_scale, the relation is exact:

action = pd_action_offset + action_scale * raw_action

The maximum difference over the run was 0.0 in fp32 (measured). The scales are not arbitrary either. For all 29 joints, action_scale * stiffness / effort_limit equals 0.25 to four decimals (measured, from the config). A raw action of 1.0 therefore asks the PD controller for a torque equal to a quarter of the joint's effort limit at zero velocity, in every joint (reasoned). The effort limits range from 5 Nm for the wrist pitch and yaw to 139 Nm for the hip roll and knee, and the gains scale with them: hip roll and knee at stiffness 99.1 and damping 6.31, shoulders at 14.25 and 0.907.

Stiffness and damping come out of the network as outputs, but in my runs they were the same constants as the config for the same joint (99.098 for the knee, 14.251 for the shoulder). I did not test whether they ever vary. Reading the graph, they look like per-joint constants emitted so that a browser host does not need the config (reasoned).

I did not get joint position limits. The only copy I could try was the robot description inside ProtoMotions, and the session could not reach that repository, so I do not compare targets to angle limits and I won't quote limits from memory. The torque view above is what the files give: over 400 random synthetic states the largest raw action seen was 1.13 in magnitude (the 99th percentile of all outputs was 0.70), which is at most 28% of an effort limit at zero velocity (measured, reasoned). The caveat is large. My synthetic histories were random noise, which feeds the network inputs it never sees in training, so these ranges describe my probe, not the robot in motion.

Running it

Setup: Python 3.13, onnxruntime 1.31.1, the CPU provider, on a shared 4 vCPU Intel Xeon at 2.10 GHz, which other jobs were using at the same time (load average above 4). Memory peak was about 700 MB. The inputs were synthetic: the default pose, identity-ish rotations, a velocity command, goals and scene masked off unless I say otherwise. I ran with nice -n 19.

Latency (measured). 20 warm-up calls, then 300 timed calls, batch 1, fp32:

ThreadsMedian95th pct99th pctBestOver 20 ms
178.1 ms93.4 ms99.0 ms69.3 ms300 of 300
252.6 ms92.3 ms115.6 ms40.1 ms300 of 300

At the 50 Hz the config specifies, a step has 20 ms. No call met it, not even the best at 40 ms. The numbers are noisy because the box was shared, so read them as "tens of milliseconds, not single digits". I take the second thread's gain as modest and the tail as worse, which is the usual sign of a shared machine.

Why so slow (reasoned). A single step reads all 403 MB of weights once, since every matrix is used. At 78 ms that is 5.2 GB/s, and at 53 ms it is 7.7 GB/s, which I expect is low for this hardware, so I suspect the pass is limited by how the box and runtime move weights rather than by arithmetic. To fit 50 Hz the same weights would need 20.2 GB/s. I did not profile it to confirm. The card suggests a way out that I did not use: WebGPU is recommended, and "CPU/WASM is a fallback and does not guarantee real-time six-robot operation" (reported). That sentence matches what I saw.

int8 (measured). I applied onnxruntime's dynamic quantization to the MatMul and Gemm nodes, which took 8 seconds and made a 101.8 MB file (a quarter of the size). On the same CPU with 2 threads the median fell from 53.1 to 22.1 ms, with a 95th percentile of 45.2 ms, over 100 random synthetic states. It still misses 20 ms at the tail. And accuracy is not free: the largest raw-action difference from fp32 was 0.53, the mean 0.092 and the 99th percentile 0.31, which is a joint-target difference of up to 0.138 rad (measured). The inputs were random noise, so a flat softmax would flip easily and a real state might fare better, but I cannot say that. For a robot I would treat this as a warning that a quantized policy needs its own closed-loop testing, not as a recipe.

Determinism (measured). The same input run 20 times gave bit-identical outputs, in both thread settings, and one thread and two threads gave identical raw_action (maximum difference 0.0). This fits the card's "deterministic learned prior": there is no sampling at inference, so the same state gives the same action. It is a property of this graph on this CPU provider, not a promise about WebGPU or other hardware.

Poking the inputs

Because the network is a function, you can test it like one. I wrote a probe (probe.py, probe2.py) that builds a random but plausible state: joints near the default pose plus noise of 0.08 rad, speeds of about 0.5, a slightly tilted torso, random action histories, and a velocity command in a box of plus or minus 1 m/s. Then it perturbs one input and records how the 29 raw actions move. All of this is measured on synthetic inputs.

Masks gate exactly. With goals, style and scene masked off, I added Gaussian noise of standard deviation 1 to the global and local reference positions and rotations, the style pose and velocity samples, the time offsets, and the scene centres and extents, one tensor at a time. The largest change in any action was 0.0, every time. With the first goal frame's masks switched on and a goal 0.5 m up, noise of 0.3 in the reference positions moved an action by up to 0.76, in the reference rotations by up to 0.29, and noise of 0.5 in the style pose by up to 0.52. A scene box that was valid and visible, against an empty scene, changed an action by up to 1.33. A box that was visible but not marked valid changed nothing (0.0). So an empty scene is truly empty, and "valid" is the switch that counts. This is how the card's claim that the browser demo feeds the empty scene when there is no scene works out in the graph.

The velocity command is strong. Turning the velocity mask off changed an action by up to 1.02, and setting a command of 1.5 m/s forward moved an action by up to 1.04. Gain, the L2 change in the 29 raw actions per unit change of the input, is 0.72 for the average velocity (measured).

What the state does. With a random-direction perturbation of norm 0.05 in each tensor, the gain was 5.75 for the torso quaternion, 1.07 for joint angles, 1.10 for the historical torso rotation, 0.37 for the nine-step action history, 0.20 for the eight-step historical raw actions, 0.16 for historical joint angles and only 0.037 for joint speeds (0.012 for the history of speeds). The torso orientation is by far the most sensitive input, which is what you would want for balance, and speeds are surprisingly quiet at this operating point. My perturbation of the quaternion leaves it non-unit, so that number is partly a statement about what the network does with an unnormalized rotation; read it as a ranking, not a Lipschitz constant. current_root_pos, current_root_rot and noisy_anchor_pos had zero gain with goals off, which fits them being used only to move goal frames into the body frame (reasoned).

Joints are coupled. The heat map above is the mean absolute central-difference Jacobian of the raw action with respect to the measured joint angle, over six states (step 0.01 rad). The mean diagonal entry is 0.242 and the mean off-diagonal 0.106; the largest single entry is 0.64. A joint's reading moves every other joint's action, which a hand-tuned per-joint controller would not do, and is the plain consequence of one transformer reading everything at once. The column with the biggest norm was the waist roll (1.40), followed by the right hip roll (1.28) and the left hip roll (1.15). I do not read much into the ordering with six states.

What this does and does not show

It shows that the release is real and checkable: the hash matches, the graph loads in a stock runtime, the outputs follow the documented mapping and the mask logic does what the card says. The sizes are modest enough to run on a laptop at a few steps a second.

It does not show that the policy walks, recovers or interacts well. For that you need rollouts, and NVIDIA's own evaluation is, in the card's words, "integration checks, not a comprehensive task-success or safety benchmark". The card also warns that this is "not a safety controller" and that hardware use needs independent validation, joint and torque limits, supervision and an emergency stop. Nothing I measured contradicts that, and a clean run of single steps would not tell me if it did.

Three practical things follow from the numbers.

  1. Budget your runtime before your robot. A 50 Hz loop needs the whole step in 20 ms, and on a plain CPU this graph took 53 to 78 ms. The G1 has an onboard computer I know nothing about; the card names WebGPU for browsers and the PyTorch package for simulator and hardware use, and I tested neither.
  2. Feed masks and values together. The goal tensors are inert when masked off and decisive when on, so a host that leaves stale numbers in a tensor with its mask on will steer the robot with them. Test the mask logic on your side.
  3. Pin the interface. The card says to preserve joint order, quaternion convention, history order, normalization and control rate. My probe depends on all of them, and I only checked the ones the config makes explicit.

What I did not run

The probe scripts, the graph inspection and the raw numbers behind the tables are described above in enough detail to repeat on your own machine; the model is 403 MB and loads in under 2 seconds.

Cite this article

For attribution, please use the following reference or BibTeX:

Satyajit Ghana, "Metatron v1: a 100-million-parameter humanoid policy, run on a CPU", ai.thesatyajit.com, October 2026.

bibtex
@misc{ghana2026nvidiametatronv1,
  author = {Satyajit Ghana},
  title  = {Metatron v1: a 100-million-parameter humanoid policy, run on a CPU},
  url    = {https://ai.thesatyajit.com/articles/nvidia-metatron-v1},
  year   = {2026}
}
share