fix(smolvla2): build preprocessor fresh, don't round-trip the recipe

``PolicyProcessorPipeline.from_pretrained`` reconstructs each saved
step by passing the persisted JSON config back to ``__init__``, but
``RenderMessagesStep.recipe`` (a ``TrainingRecipe``) doesn't survive
the JSON round-trip — the saved entry is ``{}`` and the reconstructor
crashes with ``missing 1 required argument: 'recipe'``.

Bypass the round-trip in the runtime CLI by passing
``pretrained_path=None`` to ``make_pre_post_processors``. That re-runs
``make_smolvla2_pre_post_processors``, which reloads the recipe YAML
referenced by ``cfg.recipe_path`` and wires it back into the step
correctly. ``NormalizerProcessorStep`` still gets stats from
``ds_meta.stats`` so normalization matches training.

Proper fix is to make ``RenderMessagesStep`` serializable (e.g. by
persisting the recipe path / contents); this commit keeps it scoped to
the runtime path so dry-run testing isn't blocked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Pepijn
2026-05-05 11:27:12 +02:00
parent 29bb8bb20e
commit 7b4d281ef5
@@ -197,9 +197,22 @@ def _load_policy_and_preprocessor(
ds_meta = LeRobotDatasetMetadata(dataset_repo_id)
policy = make_policy(cfg, ds_meta=ds_meta)
# NOTE: we deliberately pass ``pretrained_path=None`` here even
# though the checkpoint ships a ``policy_preprocessor.json``.
# ``RenderMessagesStep`` carries a ``TrainingRecipe`` field that
# isn't faithfully serialized into that JSON, so the saved
# pipeline can't currently be round-tripped via
# ``PolicyProcessorPipeline.from_pretrained`` — it crashes with
# ``RenderMessagesStep.__init__() missing 1 required argument:
# 'recipe'``. Building fresh from ``cfg`` re-runs
# ``make_smolvla2_pre_post_processors``, which loads the recipe
# YAML referenced by ``cfg.recipe_path`` and wires it back into
# ``RenderMessagesStep`` correctly. Normalization stats come
# from ``ds_meta.stats`` (the same dataset the user is feeding
# into the runtime), so no quality loss in practice.
preprocessor, _ = make_pre_post_processors(
cfg,
pretrained_path=policy_path,
pretrained_path=None,
dataset_stats=ds_meta.stats,
)
else: