Stats missing with pretrained policies: the issue family, decoded
You loaded a pretrained policy, or started a training run, and the library complained about missing statistics. This family is real, old, and fixable. You are not the first. Seven threads in the LeRobot tracker describe it. Two discussions sit on the checkpoint pages. One pull request fixed a piece of it in a day. This page collects the whole family. You get the error strings exactly as they print. You get the four causes behind them. You get the fix each tracker entry proved. You get the habit that keeps the error away.
The short answer comes first. Every error in this family means one thing. The policy looked up the numbers that map your robot's units into the band it thinks in. It found nothing under the name it used. There are four ways that happens. One: the checkpoint ships without stats on purpose, because it is a base for fine-tuning. Two: your library version moved where stats live, so an older checkpoint no longer carries them where the code looks. Three: a name mismatch hides the stats you have. A camera or feature key differs between your config and your dataset. Four: the dataset lacks the stat type your policy wants, most often quantiles. Each cause has a documented fix, quoted from the thread that proved it. Prevention is one habit. Treat a policy and its stats as one object. Prove the pair found each other before the arm moves.
The errors, verbatim
Three strings carry this family. Each is quoted exactly as the tracker prints it. Each tells you where you stand.
The first is the classic. It fires when you call a pretrained policy at inference time, inside select_action. The policy tries to scale your observation and stops:
AssertionError: `mean` is infinity. You should either initialize with `stats` as an argument, or use a pretrained model.
That string is verbatim from issue #694, "stats missing when using pretrained pi0 policy", opened February 7, 2025 (LeRobot issue 694). The same string titles a discussion on the checkpoint's own page. That discussion opened February 27, 2025 and was still open when checked October 27, 2026 (pi0 checkpoint discussion).
The second and third strings arrived together, seven months later, in one training run. Issue #2105, "KeyError occurred while training the pi0.5 model", opened October 2, 2025 (LeRobot issue 2105). The reporter's first stop was this string:
ValueError: QUANTILES normalization mode requires q01 and q99 stats, please update the dataset with the correct stats using the `augment_dataset_quantile_stats.py` script
They ran the script the error names. Training restarted and stopped at once, at dataset load time, on the third string:
KeyError: 'observation.images.high'
The current training code prints the second string word for word. That one is safe to attribute to today's library. The first string belongs to an older layout of the code. The issues that quote it are the right citation for it. That distinction matters. The section on library changes explains why.
What the error actually means
The assert is a guard with a specific story. A policy is built with empty slots for the stats of every feature. The slots hold mean, spread, min, and max. The slots start filled with infinity. The source comment says it plainly. The values are set to infinity. Real numbers should overwrite them. The assert checks that the overwrite happened. The code that produced the first string worked exactly this way when issue #694 was filed. The initialization and the guard sit a few lines apart in that era's module, read at the commit current on February 4, 2025.
So "mean is infinity" never means your data contains infinity. It means the overwrite never happened. Nothing told the policy what your robot's numbers look like. The guard caught it before the arm got a single command. That is the honest version of this family. The arm was never sent scaled garbage. The run stopped first.
Why would a pretrained model arrive without stats? Because the base checkpoints are converted, not trained. The pi0 and pi0.5 bases on the Hub came from the original publisher's weights. Beside the weights ship a config and two small processor files. They say how to scale inputs and outputs, and which mode to use. They do not carry the numbers. The files on the pi0 checkpoint read exactly that way, checked October 27, 2026. The normalizer step names its modes. Mean and spread for pi0. Quantiles for the pi0.5 base. No stats block. The numbers are meant to come from somewhere else. They come from your dataset when you fine-tune. Or from a dataset that matches your robot when you borrow.
The two issues that named this family
Issue #694 is the anchor. A user loaded the published pi0 policy. They adjusted its config for a simulator's cameras. They called select_action and got the infinity assert. The thread's ending is what makes it valuable. Eight replies over seven months say "same problem". A pi0 on a SO-100 arm. An eval run. A training script. One as late as September 2025. Then two answers land. One user writes that a fine-tuned pi0 loads without this problem. The original reporter then closes with their own analysis. In their words: pi0 is not designed for zero-shot inference. The expected path is to fine-tune on a dataset for your robot and task, which fills the stats field. The checkpoint is converted rather than trained, which is why the field is empty. Limited zero-shot play is possible. Load the statistics of a dataset from the training mix, such as the bridge or fractal sets, and see how it goes.
The thread's own summary is the cause and the fix in one line. This is not a bug in your code. The base model ships without the numbers, by design. The cure is fine-tuning, or borrowed stats that match.
Issue #2105 is the dataset-side half of the family. A user trained pi0.5 on a private dataset. The dataset had been converted from the older v2.1 format to v3.0. Training stopped on the quantile error above. Their dataset carried the older statistics. Pi0.5 wants the first and ninety-ninth percentile. They did the right thing. They ran the augmentation script the error names. Then came the KeyError. The script had written statistics for the numeric columns and none for the three camera features. The reporter laid the two file listings side by side in the issue. The info file names all three image features. The stats file the script produced contains none of them.
The response was fast and exact. A maintainer offered the escape hatch that same afternoon. It is a flag that switches that run to mean and spread scaling. It is quoted in the fixes section below. The bug itself was fixed the same day in pull request #2106. The augmentation script had been looping over the table columns only, so it never saw video features. The fix keys the script off the dataset's own feature list, image and video types included (PR 2106, merged October 2, 2025). The script's current source detects image and video features by name, read October 27, 2026.
The census: seven threads, four causes
Every public report of this family sorts into four causes. The table is the core of this page. Find your row. Take the fix named there.
| Tracker entry | When | Where it fired | The real cause | The fix that closed it |
|---|---|---|---|---|
| Issue 694 | Feb 2025 | pi0, inference | Base checkpoint ships no stats, fine-tune-first design | Fine-tune, or load a matching dataset's stats |
| Issue 699 | Feb 2025 | pi0, inference | Same, opened as duplicate | Stats injection in the processors feature closed the thread |
| Issue 1078 | May 2025 | ACT, evaluation | Name mismatch, pointed at issue 1095 | Align the names |
| Issue 1095 | May 2025 | ACT, evaluation | Camera names differ between robot config and dataset stats file | Align the names, closed by the reporter |
| Issue 1459 | Jul 2025 | ACT, evaluation | Stats never loaded into the policy at eval; closed as addressed by the processor redesign | Inject stats, or migrate the checkpoint |
| Issue 2105 | Oct 2025 | pi0.5, training | Dataset lacked quantiles, then the tool missed video features | Recompute stats, or switch that run to mean/std, fixed in PR 2106 |
| Issue 3645 | May 2026 | ACT, evaluation on SO-101 | Version scatter, fixes from before mid-2025 no longer applied | Pin your version, use the current inject path |
Two discussions on the checkpoint's own page complete the census. One is titled with the error string itself. They are the first cause again, standing where the victim stands.
Cause one, the fine-tune-first base. The published pi0 and pi0.5 bases are starting points, not finished policies. Zero-shot use is the exception. It needs borrowed stats to work at all.
Cause two, the version shift. Scaling used to live inside the model weights. A 2025 redesign moved it out into separate processor steps. The project's own compatibility page states it as a law. Policies no longer have scaling layers embedded in their weights. Models trained before the change need migration (backward compatibility page). A checkpoint from one era, run under code from another, is cause two.
Cause three, the name mismatch. The stats file is a dictionary keyed by feature names. Your camera is observation.images.top in one place and observation.image.top in another. Or side in your robot config and camera_0 in the dataset. The lookup misses. Issue #1095's reporter found it in a sentence. The cameras in the robot's config were named differently than in the dataset's stats file. Issue #2105's KeyError is the same cause one layer down. The stats dictionary did not contain the feature name at all.
Cause four, the dataset gap. Older datasets stop at min, max, mean, and spread. Quantile policies want the first and ninety-ninth percentile. The gap is a date, not a mistake. The stat types joined the format later. Datasets recorded before that day never grew them.
The fixes, matched to causes
For cause one, two roads exist. The intended road is fine-tuning. Train the base on a dataset from your robot. The run computes and stores the stats with the checkpoint. The borrow road is valid for near-zero-shot tests. Load the statistics of a published dataset whose robot, cameras, and action space match yours. The bridge and fractal families work for pi0, as issue #694's closer describes. The match condition is strict. Borrowed stats describe another body's spread. If the action space differs, the borrowed map is quietly wrong.
For cause two, inject the stats yourself, by hand, through the current front door. The library's processor feature exists for this. The duplicate thread #699 was closed pointing at it. In a maintainer's words, the infinity error comes from dataset stats not being injected when the policy is made. The pattern comes from the project's own docs (processors introduction, backward compatibility page):
from lerobot.datasets import LeRobotDataset
from lerobot.policies import make_pre_post_processors
dataset = LeRobotDataset("hf_user/dataset", episodes=[0])
preprocessor, postprocessor = make_pre_post_processors(
policy_cfg=config,
pretrained_path="hf_user/model",
dataset_stats=dataset.meta.stats,
)
Older checkpoints with scaling still inside their weights have a migration script. migrate_policy_normalization.py pulls the statistics out of the weights. It builds the processor files beside them.
For cause three, align the names. Open the dataset's meta/stats.json. Read its keys. Make your robot or policy config use exactly those strings. This is a five-minute fix. It closed #1095 the day it opened. The KeyError variant is different. There the fix is in the library itself if you are current. Update. The tool that writes quantile stats has detected video features since October 2025.
For cause four, recompute, or switch modes. The official recompute recipe comes from the pi0.5 docs. The same page warns about two placement traps. Use the new repository id together with the overwrite flag. Then check where the result lands:
lerobot-edit-dataset \
--repo_id your_dataset \
--new_repo_id your_dataset \
--operation.type recompute_stats \
--operation.overwrite true
The result lands in $HF_LEROBOT_HOME/your_dataset. That is not the cache a training command reads by default. Point the run at that directory, or push the dataset to the Hub first. The alternative keeps the dataset untouched. It switches that one run to mean and spread scaling. This is the exact flag from the maintainer's reply in #2105. The pi0.5 docs teach the same escape (pi05 docs):
--policy.normalization_mapping='{"ACTION": "MEAN_STD", "STATE": "MEAN_STD", "VISUAL": "IDENTITY"}'
Your error may be the short quantile ValueError, without the script suffix. That exact crash has its own walkthrough, including what older datasets carry and why the first batch stops. It is the loud crash page. This page owns the family around it.
What changed in the library, and why old answers expire
The family changed shape under people's feet. That is why one error has era-dependent answers. Three layouts of the scaling code have existed. First it sat inside the policy module, with the infinity guard. Then it moved to a policy-level module in the new source tree. Now it is a processor step, and the guard is gone. In the current code, a missing stats key no longer asserts at all. The transform function checks whether the feature's key exists in the loaded stats. If it does not, the values pass through unchanged. Read that twice. The loud error, the one that stopped your run, became silence. The data simply moves unscaled.
That silence is why this page links its sibling. When numbers pass through unscaled, nothing crashes. The only place the failure shows is the arm. The symptoms, the decoder table, and the four silent causes live on the silent wrong-motion page. The concept background lives in the norm stats guide.
The era shift also explains the most human entry in the census. Issue #3645 came in May 2026. A student was evaluating an ACT policy on a SO-101 arm under Windows. They hit the infinity assert. They went looking for fixes and found the versions too far apart. In their words, most of the solutions on GitHub are from before mid-2025 and no longer work. They did nothing wrong. The answers were real, each for its era. A fix for this family must name its version. So does this page. The infinity assert belongs to the layouts before the processor redesign. The injection pattern and the silent pass-through belong to the current one. The quantile strings belong to the quantile era of pi0.5 and after. Pin the library version in your project. After an upgrade, re-check the pair the next section describes.
Prevention: the pair habit
Everything above folds into one habit. A policy and its statistics are one object split across two files. Guard the pair, in both directions.
Print what actually loaded, before any first run. One line at startup, in training and in evaluation. It reports the path of the stats file in use and the first value of its mean array. It costs nothing. It catches causes one, two, and three at once. You want a log line to tell you the file never loaded. The assert will not, and in the current code not even the assert exists.
Doctor the dataset before any training run. The community tool lerobot-doctor checks LeRobot datasets directly, local or on the Hub, with no training stack installed. Its statistics pass looks for infinite values, zero-variance features, and drift between the stats file and the data. Its training pass flags normalization readiness before a run starts (lerobot-doctor). It fixes nothing and touches no policies. It tells you whether the dataset side of the pair is sound before you spend an evening on it.
Keep names frozen from recording through training through evaluation. Same camera keys. Same feature strings. One name per thing for the life of the project. Run the pair check again after any library upgrade, before hardware. And on every first run of a policy on a real arm, go slow. Guard the workspace. Keep the emergency stop in reach. The loud errors in this family stop you. The quiet ones move the arm.
If you would rather not own this stack
Everything on this page is work you do on your own machine. Your checkpoint, your dataset, your loop. The work is real, and it recurs with every new policy and every upgrade. The other shape is a policy served to you over an API route. There the model side applies the checkpoint's own statistics. The response states what was applied. The numbers that shaped the actions travel with the actions. What stays yours on any such route is the same as here. Your state must arrive in the units your robot's body expects. Your cameras must feed the views the policy was trained on.
This platform's robot action lane is one such route: written into the platform's model data, not on the public list as of 27 October 2026, with no date attached to that. The response's normalization block is part of that route's contract. The robot lane pages, for example pi05 and act, describe those policy families. For what any metered route costs, the answer lives on the pricing guide, never in prose here. If this page fixed your stack and you want the full record-and-train loop for a small arm, the SO-101 cloud brain guide walks it end to end. When a first call is next, you can sign up.
Common questions
Is "mean is infinity" a bug in my code? Almost never. It means the policy's stats slots were created at infinity and nothing overwrote them. With a pretrained base checkpoint, that is by design. The base ships without dataset statistics. Fine-tune it, or inject stats by hand. The thread that settled this is issue #694. Its own reporter closed it with that analysis.
Can I run pi0 or pi0.5 zero-shot at all? Limited zero-shot works when your setup matches a dataset the base trained on. Load that dataset's statistics as your stats. The condition is strict. Same robot family. Same camera layout. Same action space. Miss any of them and the borrowed map produces quietly wrong motion, not an error.
Which file are these stats in? In the dataset, meta/stats.json. It holds one entry per feature, with the stat arrays. With a fine-tuned checkpoint, the stats travel beside the weights, as processor files the checkpoint expects to find. The two files are one object. Copy them together. Version them together.
What does the KeyError on an image feature mean? The stats dictionary has no entry under that name. Either the tool that wrote the stats never saw the feature, the bug fixed in PR #2106 for video features, or the name differs between your dataset and your config, the cause in issue #1095. Read the keys of your stats file and compare. Then update the tool, or align the names.
Is this error family gone in current versions? The infinity assert is gone. The family is not. In the current code a missing stats key passes values through unscaled, with no error at all. That is quieter and more dangerous. The loud quantile ValueError still prints, now from the processor step. Upgrade, but do not assume the upgrade made the pair problem disappear.
Does lerobot-doctor fix any of this? No. It diagnoses datasets. It looks for infinite values, zero-variance features, stats drift, and normalization readiness. It is a check before you train, not a repair after you crash. Run it when a dataset is new, converted, or grown. Read its statistics and training passes before spending GPU time.
The short version
The family has four causes. A base checkpoint that ships without stats on purpose. A version shift that moved where stats live. A name mismatch that hides the stats you have. A dataset that lacks the stat type your policy wants. The fixes match the causes. Fine-tune, or borrow stats that match. Inject stats through the processor front door. Align the names. Recompute, or switch modes. The loud errors were the kind ones. They stopped the run. In the current code the same miss is silent. So print what loaded. Doctor the dataset. Prove the pair before the arm moves. The norm stats guide and the silent wrong-motion page carry the rest of the story. Costs for hosted routes live on the pricing guide. You can sign up when a first call is next.
Last verified: 2026-10-27