VLA · Phase 4 · Action Representation and Policy Learning
4.3 — Action Normalization and Decoding
Scaling actions for training and turning model outputs back into robot commands.
Between the model and the motor sits a small piece of arithmetic that causes a surprising number of bugs. This lesson covers how actions are rescaled, why quantiles are popular, and how to decode safely.
Claims about papers, models and textbooks carry numbered references, listed with what each one is cited for in the References. Code marked “Toy model” is our own illustration, not the cited system.
Learning objectives
After this lesson, you should be able to:
- Compare min-max, mean-std and quantile scaling.
- Explain how an outlier harms min-max scaling.
- Decode and clip a model output.
- Explain why statistics must travel with the model.
- Compute statistics on the training actions
- Normalise actions for training
- The model predicts normalised actions
- Decode with the same statistics
- Clip to safe limits and send
The one-sentence idea
Rescale for learning, then undo exactly the same rescaling for the robot.
The statistics belong to the model.
Why numbers get rescaled
A gripper opens by centimetres, a joint turns by radians, a wrist rolls by degrees. If those raw numbers went straight into a model, the largest would dominate learning, and a token scheme would waste bins on ranges nobody uses. So training pipelines rescale each action dimension to a standard range, and at run time the model’s output is scaled back. The scaling statistics become part of the model, and OpenVLA is explicit about it: bins for each action dimension lie between the 1st and 99th quantile of the training actions, and the predicted tokens are decoded to continuous values and de-normalised to the robot’s physical scale.[1][1]
Three common recipes: min–max (squeeze the smallest and largest values to −1 and 1), mean–std (subtract the mean, divide by the standard deviation, as in lesson 3.1), and quantile (use, say, the 1st and 99th percentiles as the ends).
Why quantiles: one glitch can ruin min–max
Real recordings contain glitches: a sensor spike, an operator jerk. With min–max scaling, one extreme value stretches the range and squeezes all normal motions into a few bins. Quantile scaling ignores the extremes:
import numpy as np
rng = np.random.default_rng(0)
actions = rng.normal(0.0, 0.02, 10000) # normal motions, a few centimetres
actions[0] = 1.0 # one glitch, 100 times larger
def bins_used(x, lo, hi, bins=256):
idx = np.clip(((x - lo) / (hi - lo) * bins).astype(int), 0, bins - 1)
return len(np.unique(idx))
lo, hi = actions.min(), actions.max()
q_lo, q_hi = np.quantile(actions, [0.01, 0.99])
print("min-max range: ", round(float(lo), 3), "to", round(float(hi), 3), "-> distinct bins used:", bins_used(actions, lo, hi))
print("1%-99% range: ", round(float(q_lo), 3), "to", round(float(q_hi), 3), "-> distinct bins used:", bins_used(actions, q_lo, q_hi))Output:
min-max range: -0.078 to 1.0 -> distinct bins used: 35 1%-99% range: -0.047 to 0.046 -> distinct bins used: 256
With min–max, all the normal data is squeezed into just 35 of the 256 bins; with quantiles it uses all 256, spread over the motions that matter. The cost: genuinely extreme values (the top and bottom 1%) get clipped, so quantile scaling should be used when you trust that the extremes are rare or unimportant.
Decoding: from model output to a safe command
Decoding is the reverse trip, and it has two jobs: undo the scaling, and make sure the result is acceptable. A model can output a value slightly outside the training range, or, with text-style outputs, nothing sensible at all. Clip to the allowed range before anything reaches a motor:
import numpy as np
mean = np.array([0.0, 0.0, 0.04]) # saved with the model: dataset statistics per dimension
std = np.array([0.02, 0.02, 0.02])
limit = np.array([0.05, 0.05, 0.08]) # the largest move this robot may be commanded to make
def decode(model_output):
physical = model_output * std + mean # undo the normalisation
return np.clip(physical, -limit, limit) # never exceed the safe range
for out in (np.array([0.5, -1.0, 0.0]), np.array([4.0, 0.2, 3.0])):
print("model output", out.tolist(), "-> command", np.round(decode(out), 3).tolist())Output:
model output [0.5, -1.0, 0.0] -> command [0.01, -0.02, 0.04] model output [4.0, 0.2, 3.0] -> command [0.05, 0.004, 0.08]
Toy model. The statistics and limits are made up. In a real system the limits come from the robot’s specifications and safety review (lesson 9.4).
The second output was unreasonable (4 standard deviations on the first axis) and the clip held it at the limit instead of passing it to the robot. Clipping is a last line of defence, not a substitute for a good model.
The classic bug: the wrong statistics
Statistics are specific to a dataset, usually to a robot. Use robot A’s statistics to decode for robot B and the commands are wrong by a constant factor, silently:
import numpy as np
true_command = 0.03 # what the model meant: 3 cm
output = (true_command - 0.0) / 0.01 # the normalised value, using robot A's std of 0.01 m
for name, std in (("robot A statistics (correct)", 0.01), ("robot B statistics (wrong)", 0.03)):
print(f"{name:<30} -> {output * std * 100:.1f} cm")Output:
robot A statistics (correct) -> 3.0 cm robot B statistics (wrong) -> 9.0 cm
Three times too far, with no error message. Store the statistics next to the model, version them, and check that the model name and the statistics file match before running. Datasets with several robots, as in Open X-Embodiment with 22 embodiments,[2] make this more important, not less.
Common misconceptions
Misconception 1 “Normalisation is a preprocessing detail.”
Correction It is part of the model: wrong statistics give wrong commands without any error.
Misconception 2 “Min-max is the safest choice.”
Correction One glitch can squeeze all normal data into a few bins.
Misconception 3 “Clipping makes a bad output safe.”
Correction It caps the size of the command. It does not make the action correct.
Engineering takeaways
- Three recipes: min-max, mean-std, quantile.
- Quantiles resist outliers.
- Decode, then clip.
- Ship statistics with the model.
Whatever you divide by in training, multiply by at run time.
Knowledge check
Try each question first, then reveal the explanation.
Question 1
Normalised value 2.0, mean 0.1, std 0.05. What is the physical value?
Explanation
0.2. 2.0 × 0.05 + 0.1.
Question 2
Why can quantile scaling beat min-max scaling?
Explanation
Extreme outliers do not stretch the range.
Question 3
What is the risk of using another robot’s statistics?
Explanation
Silently wrong command sizes.
References
Numbered notes in the text, like [1], point here. Each entry says what it is cited for and how that was checked. Papers and books can be revised or misread, so follow the links before relying on a detail. Code marked “Toy model” is our own illustration of an idea, not the code of the cited system.
(2024). OpenVLA: An open-source vision-language-action model. Conference on Robot Learning (CoRL) 2024. arXiv:2406.09246
Cited for:
- Each action dimension is split into 256 bins between the 1st and 99th quantile of the training actions; the 256 bins are written over the 256 least-used tokens of the Llama tokenizer, and the model is trained with next-token prediction on the action tokens. Checked against the paper, via summaries of its text.
- At run time the predicted action tokens are decoded to continuous values and de-normalised to the robot’s physical scale. Checked against the paper, via summaries of its text.
(2023). Open X-Embodiment: Robotic learning datasets and RT-X models. arXiv:2310.08864
Cited for:
- Pools more than one million robot trajectories from 22 robot embodiments, assembled by 21 institutions and covering 527 skills. Checked against the paper’s abstract.
What’s next
Next: predicting several future actions at once with action chunking.