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
tinyvla_b200: B200 streaming training on nvidia/physical-ai (745 samples/s e2e, 7x ViT fast path, hub episode streaming, full instructions)
51ce5b3 verified | #!/usr/bin/env python | |
| """Pack one shard-builder output dir into self-contained webdataset tars. | |
| Each wds sample carries EVERYTHING training needs — no sidecar lookups in the | |
| loader, which is what makes the mixture loader trivial and robust: | |
| __key__ "{dsname}_{ep:06d}_{fr:05d}" | |
| cam0.jpg primary camera JPEG (as produced by build_shards*) | |
| cam1.jpg wrist camera JPEG, or b"" for single-camera datasets | |
| meta.npz state float32[S] normalized (dataset mean/std) | |
| action_chunk float32[50,A] normalized FUTURE actions from this | |
| step; episode tail padded by repeating the last row | |
| action_is_pad bool[50] | |
| action_dim / state_dim / task_index / embodiment_id int32 | |
| Normalization happens HERE, at pack time, with the per-dataset stats the shard | |
| builder wrote — the loader never touches stats. The stats used are copied into | |
| the output manifest so unnormalization at eval time uses the same numbers. | |
| JPEG bytes are copied verbatim (no re-encode); tars are uncompressed because | |
| the payload already is. | |
| Usage: | |
| python pack_wds.py --src /workspace/data/CanToDrawer \ | |
| --out /workspace/data/wds/CanToDrawer --chunk 50 --maxcount 3000 | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import io | |
| import json | |
| from pathlib import Path | |
| import numpy as np | |
| import pyarrow.parquet as pq | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--src", type=Path, required=True, help="shard-builder output dir") | |
| ap.add_argument("--out", type=Path, required=True) | |
| ap.add_argument("--name", default=None, help="dataset name for keys (default: src dirname)") | |
| ap.add_argument("--chunk", type=int, default=50) | |
| ap.add_argument("--maxcount", type=int, default=3000) | |
| ap.add_argument("--embodiment-id", type=int, default=0) | |
| args = ap.parse_args() | |
| name = args.name or args.src.name | |
| import webdataset as wds | |
| manifest = json.loads(next(args.src.glob("manifest-part*.json")).read_text()) | |
| stats = json.loads(next(args.src.glob("stats-part*.json")).read_text()) | |
| s_mean = np.asarray(stats["observation.state"]["mean"], np.float32) | |
| s_std = np.clip(np.asarray(stats["observation.state"]["std"], np.float32), 1e-6, None) | |
| a_mean = np.asarray(stats["action"]["mean"], np.float32) | |
| a_std = np.clip(np.asarray(stats["action"]["std"], np.float32), 1e-6, None) | |
| # frames table -> per-episode contiguous state/action, RAM-resident | |
| tabs = [pq.read_table(p) for p in sorted(args.src.glob("frames-part*.parquet"))] | |
| import pyarrow as pa | |
| t = pa.concat_tables(tabs) if len(tabs) > 1 else tabs[0] | |
| ep = np.asarray(t["episode_index"], np.int64) | |
| fr = np.asarray(t["frame_index"], np.int64) | |
| order = np.lexsort((fr, ep)) | |
| state = np.asarray(t["state"].to_numpy(zero_copy_only=False).tolist(), np.float32)[order] | |
| action = np.asarray(t["action"].to_numpy(zero_copy_only=False).tolist(), np.float32)[order] | |
| task_i = np.asarray(t["task_index"], np.int32)[order] | |
| ep, fr = ep[order], fr[order] | |
| uniq, starts = np.unique(ep, return_index=True) | |
| ep_start = dict(zip(uniq.tolist(), starts.tolist())) | |
| ep_len = dict(zip(uniq.tolist(), np.diff(np.append(starts, len(ep))).tolist())) | |
| state = (state - s_mean) / s_std | |
| action = (action - a_mean) / a_std | |
| S, A, CH = state.shape[1], action.shape[1], args.chunk | |
| args.out.mkdir(parents=True, exist_ok=True) | |
| sink = wds.ShardWriter(str(args.out / "shard-%05d.tar"), maxcount=args.maxcount, verbose=0) | |
| n = 0 | |
| for shard in sorted((args.src / "shards").glob("shard-*.parquet")): | |
| pf = pq.ParquetFile(shard) | |
| for batch in pf.iter_batches(batch_size=512): | |
| d = batch.to_pydict() | |
| for e, f, c0, c1 in zip(d["episode_index"], d["frame_index"], d["cam0"], d["cam1"]): | |
| e, f = int(e), int(f) | |
| base, ln = ep_start[e], ep_len[e] | |
| i = base + f | |
| avail = min(CH, ln - f) | |
| chunk = np.empty((CH, A), np.float32) | |
| chunk[:avail] = action[i : i + avail] | |
| if avail < CH: | |
| chunk[avail:] = action[i + avail - 1] | |
| is_pad = np.zeros(CH, bool) | |
| is_pad[avail:] = True | |
| buf = io.BytesIO() | |
| np.savez(buf, state=state[i], action_chunk=chunk, action_is_pad=is_pad, | |
| action_dim=np.int32(A), state_dim=np.int32(S), | |
| task_index=task_i[i], embodiment_id=np.int32(args.embodiment_id)) | |
| sink.write({"__key__": f"{name}_{e:06d}_{f:05d}", | |
| "cam0.jpg": c0, "cam1.jpg": bytes(c1 or b""), | |
| "meta.npz": buf.getvalue()}) | |
| n += 1 | |
| sink.close() | |
| tasks = {} | |
| for p in sorted(args.src.glob("tasks-part*.json")): | |
| tasks.update(json.loads(p.read_text())) | |
| (args.out / "tasks.json").write_text(json.dumps(tasks, ensure_ascii=False)) | |
| (args.out / "manifest.json").write_text(json.dumps({ | |
| **manifest, "name": name, "samples": n, "chunk": CH, | |
| "embodiment_id": args.embodiment_id, | |
| "norm_stats": stats, # exactly what was applied, for unnormalization | |
| "shards": sorted(p.name for p in args.out.glob("shard-*.tar")), | |
| }, indent=2)) | |
| print(f"{name}: {n:,} семплов -> {len(list(args.out.glob('shard-*.tar')))} тарболов в {args.out}") | |
| if __name__ == "__main__": | |
| main() | |