Instructions to use AlexWortega/tinyvla with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- LeRobot
How to use AlexWortega/tinyvla with LeRobot:
- Notebooks
- Google Colab
- Kaggle
| #!/usr/bin/env python | |
| """Offline eval for canonical-schema checkpoints (stage2+). | |
| Reports action-chunk MSE (normalized space) on held-out episodes, the | |
| per-timestep error curve, and stale-latent degradation. | |
| Usage: | |
| python scripts/eval_offline.py \ | |
| --checkpoint outputs/stage2_mixture/final \ | |
| --repo-id VoicAndrei__so100_kitchen \ | |
| --root ~/tinyvla_data/so101_v3/VoicAndrei__so100_kitchen \ | |
| --episodes 8 --stale-s 0 1 2 [--embodiment-id 0] [--no-latent] | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| from pathlib import Path | |
| import torch | |
| def main(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("--checkpoint", type=Path, required=True) | |
| parser.add_argument("--repo-id", required=True) | |
| parser.add_argument("--root", default=None) | |
| parser.add_argument("--episodes", type=int, default=8) | |
| parser.add_argument("--stride", type=int, default=30) | |
| parser.add_argument("--stale-s", type=float, nargs="*", default=[0.0, 1.0, 2.0]) | |
| parser.add_argument("--embodiment-id", type=int, default=0) | |
| parser.add_argument("--split", choices=["first", "last"], default="last") | |
| args = parser.parse_args() | |
| from lerobot.datasets.lerobot_dataset import LeRobotDataset, LeRobotDatasetMetadata | |
| from transformers import AutoTokenizer | |
| from tinyvla.data.mixture import CanonicalSource | |
| from tinyvla.modeling_tinyvla import TinyVLAPolicy | |
| policy = TinyVLAPolicy.from_pretrained(args.checkpoint).cuda().eval() | |
| cfg = policy.config | |
| chunk = cfg.chunk_size | |
| tok = AutoTokenizer.from_pretrained(cfg.lm_model_name) | |
| meta = LeRobotDatasetMetadata(args.repo_id, root=args.root) | |
| ds = LeRobotDataset( | |
| args.repo_id, | |
| root=args.root, | |
| delta_timestamps={"action": [t / meta.fps for t in range(chunk)]}, | |
| video_backend="torchcodec", | |
| ) | |
| src = CanonicalSource( | |
| ds, args.embodiment_id, cfg.image_size, cfg.max_state_dim, cfg.max_action_dim | |
| ) | |
| if args.split == "first": | |
| eps = list(range(args.episodes)) | |
| else: | |
| eps = list(range(ds.num_episodes - args.episodes, ds.num_episodes)) | |
| def to_batch(item): | |
| t = tok([item.pop("task")], padding=True, truncation=True, | |
| max_length=cfg.tokenizer_max_length, return_tensors="pt") | |
| b = {k: v[None].cuda() if torch.is_tensor(v) else v for k, v in item.items()} | |
| b["observation.language.tokens"] = t["input_ids"].cuda() | |
| b["observation.language.attention_mask"] = t["attention_mask"].bool().cuda() | |
| return b | |
| results = {s: [] for s in args.stale_s} | |
| per_t = torch.zeros(chunk) | |
| n_chunks = 0 | |
| for ep in eps: | |
| start = int(ds.meta.episodes["dataset_from_index"][ep]) | |
| end = int(ds.meta.episodes["dataset_to_index"][ep]) | |
| for idx in range(start, end - 1, args.stride): | |
| if idx >= len(src): | |
| break | |
| item = src[idx - 0] | |
| gt = item["action"].clone() # (chunk, A) normalized | |
| mask = item["action_dim_mask"].clone() | |
| pad = item.get("action_is_pad") | |
| batch = to_batch(dict(item)) | |
| for stale_s in args.stale_s: | |
| b = dict(batch) | |
| if stale_s > 0: | |
| stale_idx = max(start, idx - int(stale_s * ds.fps)) | |
| stale_item = src[stale_idx] | |
| sb = to_batch(dict(stale_item)) | |
| b["semantic_latent"] = policy._semantic_latent(sb) | |
| pred = policy.predict_action_chunk(b)[0].cpu() # (chunk, A) normalized | |
| err = (pred[:, mask] - gt[:, mask]) ** 2 | |
| if pad is not None: | |
| err = err[~pad] | |
| mse = err.mean().item() | |
| results[stale_s].append(mse) | |
| if stale_s == 0: | |
| e = ((pred - gt) ** 2)[:, mask].mean(dim=-1) | |
| if pad is not None: | |
| e = e * (~pad).float() | |
| per_t += e | |
| n_chunks += 1 | |
| print(f"\n=== {args.repo_id} | {len(eps)} {args.split} episodes | {n_chunks} chunks | ckpt {args.checkpoint} ===") | |
| for s, vals in results.items(): | |
| print(f"stale {s:.0f}s: normalized chunk MSE {sum(vals)/len(vals):.4f}") | |
| curve = (per_t / max(n_chunks, 1)).sqrt() | |
| print("per-timestep normalized RMSE (t=0,10,25,49):", | |
| [round(curve[i].item(), 3) for i in (0, 10, 25, 49)]) | |
| if __name__ == "__main__": | |
| main() | |