Conversation
…ort parity - ACT.__init__ accepts pretrained_name_or_path (local dir or HF Hub id), resolving config.json + model.safetensors via _from_hf() and remapping LeRobot checkpoint keys to physicalai's native parameter names via _remap_lerobot_act_state_dict(). - preprocessor.py: fix image_resolution unpacking order in the resize/pad path (stored as (height, width) but _resize_with_ar_pad expects width first), which silently swapped dimensions for non-square configs. - Add scripts/compare_act_backends.py: loads a real ACT checkpoint, exports it to OpenVINO, and validates parity via (1) single-step predict_action_chunk numeric diff on real gym-aloha observations and (2) matching closed-loop AlohaTransferCube episodes on both backends.
Pi05 models fine-tuned with adaRMSNorm (adaRMS) conditioning have
dense projection layers in every action-expert LayerNorm block
(`input_layernorm.dense.weight/bias`, `post_attention_layernorm.dense.weight/bias`).
These layers were absent from Pi05Config so the export pipeline silently
omitted them, producing an OpenVINO IR that was missing the entire
conditioning branch. Comparing such an export against the native
bfloat16 model yielded L2 ≈ 7.7, cosine-sim ≈ 0.51 — effectively
random output. After this fix the L2 drops to ≈ 0.047 (cosine-sim
0.9999), consistent with the expected bfloat16→float32 numerical noise.
Changes
-------
library/src/physicalai/policies/pi05/config.py
• Added two new optional fields to Pi05Config:
use_adarms: bool = False
adarms_cond_dim: int | None = None
These mirror the per-expert use_adarms / adarms_cond_dim parameters
already consumed by Pi05Model but were never persisted in the config.
library/src/physicalai/policies/pi05/pretrained_utils.py
• Added detect_adarms_from_checkpoint(weights_file, hf_config):
Opens the safetensors checkpoint, scans for any key matching
`*.input_layernorm.dense.weight`, and if found injects
use_adarms=True and adarms_cond_dim=<projection_width> into the
hf_config dict in-place before Pi05Config is constructed.
This makes adaRMS support fully automatic — no manual config edits.
library/src/physicalai/policies/pi05/policy.py
• Calls detect_adarms_from_checkpoint() inside _from_hf() immediately
after normalization_mode detection and before Pi05Config.from_dict(),
so any downstream code (training, export, inference) gets a correctly
populated config.
There was a problem hiding this comment.
Pull request overview
This PR addresses incorrect Pi05 exports when adaRMSNorm conditioning layers exist in a checkpoint but are not represented in Pi05Config, by adding config fields and checkpoint-key-based auto-detection prior to config parsing. It also includes ACT-related changes (HF/LeRobot checkpoint loading + an OpenVINO parity comparison script), which broadens the PR scope beyond the stated Pi05 focus.
Changes:
- Add
use_adarms/adarms_cond_dimtoPi05Configand auto-detect these from safetensors keys during pretrained loading. - Wire adaRMS auto-detection into Pi05
_from_hf()so downstream export/tracing uses the correct graph. - Add ACT LeRobot checkpoint loading/remapping support and a new script to compare native vs OpenVINO ACT parity (plus a small ACT preprocessor fix).
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/compare_act_backends.py | New script to compare native PyTorch ACT vs OpenVINO export numerically and in closed-loop rollouts. |
| library/src/physicalai/policies/pi05/pretrained_utils.py | Adds adaRMS detection by scanning safetensors keys and injecting config fields. |
| library/src/physicalai/policies/pi05/policy.py | Invokes adaRMS auto-detection during HF checkpoint loading before Pi05Config.from_dict(). |
| library/src/physicalai/policies/pi05/config.py | Adds adaRMS configuration fields to the Pi05 config dataclass. |
| library/src/physicalai/policies/act/preprocessor.py | Fixes argument order when resizing images (height/width vs width/height). |
| library/src/physicalai/policies/act/policy.py | Adds pretrained_name_or_path loading, LeRobot state-dict remapping, and weight loading during initialization. |
- pretrained_utils: only skip adaRMS auto-detection when both use_adarms and adarms_cond_dim are already set; log the exception when checkpoint key scan fails - Pi05Config: validate that adarms_cond_dim is set when use_adarms is True - ACT: document that pretrained_name_or_path overrides other __init__ args - remove scripts/compare_act_backends.py, mistakenly pushed in PR open-edge-platform#858
|
Nice catch, thanks Piotr! Interesting thing is that the model still demonstrate good perf numbers on libero even with incorrect weights loading |
Covers the reviewer request on PR open-edge-platform#858: mocked safetensors checkpoints exercising dense-layernorm detection, the no-op case, the already-set short-circuit, re-detection when adarms_cond_dim is missing, and the scan-error logging path.
|
@pwolnows could you share a script / snippet how pi05 was created and exported? There are some parameters that can affect exact outputs matching, but preserve results in envs, because the policy performs denoising internall |
|
adaRMS is intentionally hardcoded in Pi05Model, not a configuration option: In model.py, the adaRMS setting is hardcoded: self.paligemma_with_expert = PaliGemmaWithExpertModel(
paligemma_config,
action_expert_config,
use_adarms=[False, True], # VLM: disabled, Action expert: ENABLED
precision=dtype,
image_size=self._image_resolution[0],
freeze_vision_encoder=freeze_vision_encoder,
train_expert_only=train_expert_only,
)This design is intentional and aligned with upstream LeRobot Pi05, adaRMS is always enabled for the action expert ( Adding |
Pi05Model was hardcoding use_adarms=[False, True] regardless of the Pi05Config.use_adarms field, making the detect_adarms_from_checkpoint() auto-detection added in this PR completely ineffective. Changes: - Pi05Model.__init__: add use_adarms: bool = True parameter - Pi05Model.__init__: pass [False, use_adarms] to PaliGemmaWithExpertModel - policy._initialize_model: pass use_adarms=self.config.use_adarms This ensures checkpoints without adaRMS weights (use_adarms=False) build a model without dense layernorm layers, so load_state_dict does not silently discard keys with strict=False and the model runs correctly. Validated with test_golden_action.py against a local re-export: max L2=1.39, mean L2=0.50 (threshold=2.0) — all 3 samples PASS (vs L2≈6-7 before the fix)
…design adaRMS is always enabled for the action expert in Pi05Model (use_adarms=True is the default in Pi05Model.__init__). Pi05Config previously defaulted to False, which would silently disable adaRMS when loading checkpoints via any path that bypassed detect_adarms_from_checkpoint. Change the default to True to match the invariant enforced upstream (LeRobot Pi05 always enables adaRMS for the action expert). Remove the __post_init__ guard that required adarms_cond_dim to be set when use_adarms=True: the model derives adarms_cond_dim from action_expert_config.width directly; Pi05Config. adarms_cond_dim is only an optional detection hint in pretrained_utils.py.
Problem
OpenVINO/pi05-libero-fp16-ovon HuggingFace was exported from an older codebase that predates bf16 computation and adaRMS layer support inPaliGemmaWithExpertModel. Comparing it against the nativelerobot/pi05_libero_finetuned_v044checkpoint (which runs in bfloat16 with adaRMS conditioning) produces random-looking output.Structural evidence from the OpenVINO IR:
OpenVINO/pi05-libero-fp16-ov(broken HF)mainbranch exportThe zero bf16 count in the HF model confirms it was traced from a model without bf16 computation. The current
mainbranch (hardcodeduse_adarms=[False, True]) already produces the correct graph with 10422 bf16 tensors — identical to this PR's export.Root Cause
The HF model is stale. It was not re-exported after bf16/adaRMS support was added to
PaliGemmaWithExpertModel. The model code onmainwas already correct.What was missing was the config/detection layer:
Pi05Confighad nouse_adarmsoradarms_cond_dimfields, so:Pi05Config.use_adarmsdefaulted toFalse, meaning any code path that constructed the config without going through the hardcode inPi05Modelwould build a model without adaRMS layers.Changes
config.pyAdded
use_adarms: bool = Trueandadarms_cond_dim: int | None = NonetoPi05Config. Default isTrueto match the model-level invariant: adaRMS is always enabled for the action expert inPi05Model(matching upstream LeRobot Pi05). Removed the over-constrained__post_init__guard that blockedPi05Config()construction whenadarms_cond_dimwasNone— the model derives the conditioning dim fromaction_expert_config.width, not from config.pretrained_utils.pyAdded
detect_adarms_from_checkpoint(weights_file, hf_config):*.input_layernorm.dense.weightto detect adaRMS weightsuse_adarms=Trueandadarms_cond_dim=<dim>intohf_configfor checkpoints that predate the config fieldspolicy.pyCalls
detect_adarms_from_checkpoint()in_from_hf()beforePi05Config.from_dict(), enabling automatic detection for legacy checkpoints.model.pyPi05Model.__init__now acceptsuse_adarms: bool(defaultTrue) and passes[False, use_adarms]toPaliGemmaWithExpertModel.policy._initialize_modelwiresself.config.use_adarmsthrough.Validation
Validated with
test_golden_actionfrom openvinotoolkit/physicalai#208 — 30 samples fromlerobot/libero_10_image,use_random_input_noise=False, on Intel Arc A770 GPU (openvino==2026.2.1). Results after commit76c90d02on this branch:OpenVINO/pi05-libero-fp16-ov(stale HF)The stale HF model needs to be replaced with a re-export from current
main(or this PR).Testing
Unit tests in
library/tests/unit/policies/test_pi05.pycoveringdetect_adarms_from_checkpoint().Related