pi05-mem — SO-101 pick-the-remembered-cube, 64 memory tokens, TBPTT K=8
π₀.₅ with a recurrent memory mechanism, fine-tuned from
lerobot/pi05_base on a real-robot
memory task. A bank of 64 learnable memory tokens is carried across timesteps inside an
episode and updated end-to-end with truncated backpropagation through time, so the policy
can condition on an observation that is no longer visible in the current frame.
This is the port of the memory mechanism from
μVLA (a fork of OpenVLA-OFT) onto π₀.₅ as
implemented in LeRobot. Code:
avanturist322/muVLA-pi0.5.
Sibling checkpoint: the same recipe with a shorter truncation window is at
avanturist/pi05-mem-so101-pick-cube-color-m64-k2-tbptt.
Read the
Comparability section before treating the pair as an ablation over K.
Task
Remember the color of the cube and then pick the matching one
| |
|---|
| Robot | SO-101 follower arm |
| Episodes / frames | 300 / 116 016 |
| Control rate | 30 fps |
| Cameras | observation.images.top, observation.images.wrist |
| State / action dim | 6 / 6 |
| Tasks | 1 |
The cue is visible only early in the episode and is gone by the time the grasp has to be
committed, which is what makes a recurrent state useful here rather than decorative.
Training
Fine-tuned from lerobot/pi05_base on 8 GPUs (80 GB each) with DDP.
| Memory | |
|---|
use_memory | true |
num_mem_tokens | 64 |
memory_update | tbptt |
tbptt_length (K) | 8 |
attention_mask_mode | custom |
memory_write_scale | 1.0 |
memory_init_std | 0.02 |
| Optimization | |
|---|
| Batch size | 32 per rank, 256 global |
| Micro-steps | 30 000 |
| Optimizer steps | 3 750 (one per TBPTT window) |
| Frames seen | ~7.68 M (30 000 × 256) |
Action chunk (action_horizon) | 8 |
| Learning rate | 5e-5, cosine, 750 warmup steps, min ratio 0.1 |
| Optimizer | AdamW, β = (0.9, 0.95), ε = 1e-8, weight decay 0.01 |
| Gradient clipping | 1.0 |
| Precision | bfloat16, gradient checkpointing on |
| Vision encoder | frozen |
| Seed | 42 |
| Wall-clock | 17.7 h |
Final training loss 0.0312 (mean of the last 10 logged values: 0.0404); the run started
at 1.910. train_config.json and training_summary.json in this repository are the
verbatim artifacts the run wrote, including the DDP sharding check.
Files
| File | What it is |
|---|
model.safetensors | 814 tensors, the full policy including model.memory_module.initial_memory (64 × 2048) |
memory_module.pt | The memory parameters on their own, loaded with strict=True at eval |
memory_meta.json | Memory hyperparameters, auto-detected when the checkpoint is loaded |
config.json | π₀.₅ policy config |
dataset_stats.json | Copy of the training dataset's meta/stats.json — see the warning below |
train_config.json | Every training flag as passed |
training_summary.json | Step counts, losses, wall-clock, DDP check |
memory_module.pt is deliberately redundant with model.safetensors: the safetensors path
goes through π₀.₅'s key remapper with strict=False, so a silent rename would leave the
memory at its random initialization — which looks like "memory does not help" rather than
like a bug. Loading the standalone file with strict=True makes that failure loud.
Normalization statistics are not in the weights
This checkpoint does not carry normalize_inputs / unnormalize_outputs buffers. The
loader takes normalization from the dataset directory, because π₀.₅ discretizes the
normalized state into 256 bins inside the text prompt — evaluating with the wrong quantiles
feeds the model a corrupted prompt and produces plausible-looking but wrong actions, with no
error raised.
So you need the dataset alongside the weights. dataset_stats.json here is a copy of
meta/stats.json for reference and reproducibility, but the loader reads the dataset
directory, not this file.
Usage
1git clone https://github.com/avanturist322/muVLA-pi0.5.git && cd muVLA-pi0.5
2# environment setup: see the repository README
1from huggingface_hub import snapshot_download
2from pi05_mem.eval.loader import load_eval_policy
3
4ckpt = snapshot_download("avanturist/pi05-mem-so101-pick-cube-color-m64-k8-tbptt")
5data = snapshot_download(
6 "tttonyalpha/so101_pick_cube_300_task3", repo_type="dataset"
7) # supplies the normalization statistics
8
9bundle = load_eval_policy(ckpt, data, device="cuda", dtype="bfloat16")
load_eval_policy reads memory_meta.json, so memory does not have to be re-declared on
the command line: the checkpoint states whether memory is on and how it is configured.
Inference regime defaults to receding horizon for memory checkpoints (n_action_steps=1,
requery every environment step, memory advances once per step exactly as in training). Pass
receding_horizon=False for the open-loop chunked regime.
Comparability with the K=2 sibling
The two checkpoints are not batch-matched, so the difference between them is not a clean
ablation over the truncation length:
| K=8 (this) | K=2 (sibling) |
|---|
| Global batch | 256 | 512 |
| Optimizer steps | 3 750 | 15 000 |
| Frames seen | ~7.68 M | ~15.36 M |
| Final loss | 0.0312 | 0.0044 |
The K=2 run saw twice the frames and took four times as many optimizer steps. Attributing
its lower loss to the shorter TBPTT window would be wrong.
What is not here
No in-environment success rates. Both checkpoints were trained but not yet evaluated on the
physical SO-101 or in simulation, so this card reports training loss only. Training loss on a
300-episode imitation dataset is a weak proxy for task success and should not be read as one.
Citation
The memory mechanism this ports:
1@article{cherepanov2026muvla,
2 title={{$\mu$}VLA: On Recurrent Memory for Partially Observable Manipulation in VLA Models},
3 author={Cherepanov, Egor and Kachaev, Nikita and Zelezetsky, Daniil and Bulatov, Aydar and Pshenitsyn, Artem and Kuratov, Yuri and Skrynnik, Alexey and Panov, Aleksandr I and Kovalev, Alexey K},
4 journal={arXiv preprint arXiv:2606.12497},
5 year={2026}
6}
The base model is π₀.₅ (
arXiv:2504.16054) as implemented
in
LeRobot. Weights inherit the
Gemma Terms of Use from
lerobot/pi05_base.