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
wds mixture: unitree+nav converters (gnm/vamos), wds source branch in train_fast, AMP autoselect (V100 fp16), embedding expand 16->32, push/fetch/validate tooling, DATA.md
b152c62 verified | #!/usr/bin/env python | |
| """Validate a packed wds dataset dir (or several) against the loader contract. | |
| Checks, per dataset: | |
| * manifest.json lists every shard-*.tar that exists, tasks.json parses | |
| * samples decode: cam0 JPEG opens at the declared size, meta.npz has all keys | |
| * dtypes/shapes: state f32[S], action_chunk f32[chunk,A], action_is_pad bool | |
| * normalized sanity: |mean| < 5, std in (1e-6, 50) over a sample of frames | |
| * task_index maps into tasks.json | |
| Then, jointly: a few batches through wds_mix.make_mixture_loader and the batch | |
| schema the trainer expects (incl. the "task" string -> present and str). | |
| Usage: | |
| python validate_wds.py /data/wds/SetA /data/wds/SetB --batches 4 | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import io | |
| import json | |
| import sys | |
| import tarfile | |
| from pathlib import Path | |
| import numpy as np | |
| def check_dataset(d: Path, n_samples: int = 300) -> dict: | |
| errs = [] | |
| mf = json.loads((d / "manifest.json").read_text()) | |
| tasks = json.loads((d / "tasks.json").read_text()) | |
| tars = sorted(d.glob("shard-*.tar")) | |
| if sorted(mf["shards"]) != [t.name for t in tars]: | |
| errs.append(f"manifest shards != on-disk tars ({len(mf['shards'])} vs {len(tars)})") | |
| chunk = int(mf["chunk"]) | |
| seen, s_all, a_all = 0, [], [] | |
| from PIL import Image | |
| for t in tars: | |
| with tarfile.open(t) as tf: | |
| members = tf.getmembers() | |
| by_key = {} | |
| for m in members: | |
| k, _, ext = m.name.partition(".") | |
| by_key.setdefault(k, {})[ext] = m | |
| for k in by_key: | |
| if seen >= n_samples: | |
| break | |
| need = {"cam0.jpg", "cam1.jpg", "meta.npz"} | |
| raw = {m.name[len(k) + 1:]: m for m in members if m.name.startswith(k + ".")} | |
| if not need.issubset(raw): | |
| errs.append(f"{t.name}:{k}: missing {need - set(raw)}") | |
| seen += 1 | |
| continue | |
| c0 = tf.extractfile(raw["cam0.jpg"]).read() | |
| img = Image.open(io.BytesIO(c0)) | |
| img.load() | |
| meta = np.load(io.BytesIO(tf.extractfile(raw["meta.npz"]).read())) | |
| for kk, dt in (("state", np.float32), ("action_chunk", np.float32), | |
| ("action_is_pad", np.bool_)): | |
| if kk not in meta: | |
| errs.append(f"{k}: meta missing {kk}") | |
| elif meta[kk].dtype != dt: | |
| errs.append(f"{k}: {kk} dtype {meta[kk].dtype} != {dt}") | |
| ac = meta["action_chunk"] | |
| if ac.shape[0] != chunk: | |
| errs.append(f"{k}: chunk {ac.shape[0]} != manifest {chunk}") | |
| if int(meta["action_dim"]) != ac.shape[1] or int(meta["state_dim"]) != meta["state"].shape[0]: | |
| errs.append(f"{k}: dim fields disagree with arrays") | |
| if str(int(meta["task_index"])) not in tasks: | |
| errs.append(f"{k}: task_index {int(meta['task_index'])} not in tasks.json") | |
| s_all.append(meta["state"]); a_all.append(ac[0]) | |
| seen += 1 | |
| if seen >= n_samples: | |
| break | |
| if s_all: | |
| s = np.stack(s_all); a = np.stack(a_all) | |
| for nm, arr in (("state", s), ("action", a)): | |
| m, sd = np.abs(arr.mean(0)).max(), arr.std(0) | |
| if m > 5: | |
| errs.append(f"{nm}: |mean| up to {m:.2f} — normalization looks wrong") | |
| if sd.max() > 50: | |
| errs.append(f"{nm}: std up to {sd.max():.1f} — normalization looks wrong") | |
| return {"dir": str(d), "samples_checked": seen, "declared": mf.get("samples"), | |
| "episodes": mf.get("episodes"), "errors": errs} | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("dirs", nargs="+", type=Path) | |
| ap.add_argument("--batches", type=int, default=2) | |
| ap.add_argument("--batch-size", type=int, default=8) | |
| ap.add_argument("--max-state-dim", type=int, default=256) | |
| ap.add_argument("--max-action-dim", type=int, default=64) | |
| ap.add_argument("--samples", type=int, default=300) | |
| args = ap.parse_args() | |
| bad = False | |
| for d in args.dirs: | |
| r = check_dataset(d, args.samples) | |
| status = "OK " if not r["errors"] else "FAIL" | |
| print(f"[{status}] {r['dir']}: {r['samples_checked']} samples checked " | |
| f"(declared {r['declared']}, {r['episodes']} eps)") | |
| for e in r["errors"][:20]: | |
| print(" -", e) | |
| bad = bad or bool(r["errors"]) | |
| if args.batches > 0: | |
| sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) | |
| from tinyvla.data.wds_mix import make_mixture_loader | |
| loader, mfs = make_mixture_loader( | |
| [{"dir": str(d)} for d in args.dirs], | |
| batch_size=args.batch_size, num_workers=0, | |
| max_state_dim=args.max_state_dim, max_action_dim=args.max_action_dim, | |
| shuffle_buffer=64, steps_per_epoch=args.batches + 1, | |
| ) | |
| import torch | |
| want = { | |
| "observation.images.cam0": (torch.float32, (3, 256, 256)), | |
| "observation.images.cam1": (torch.float32, (3, 256, 256)), | |
| "camera_mask": (torch.bool, (2,)), | |
| "observation.state": (torch.float32, (args.max_state_dim,)), | |
| "action": (torch.float32, (None, args.max_action_dim)), | |
| "action_dim_mask": (torch.bool, (args.max_action_dim,)), | |
| "action_is_pad": (torch.bool, None), | |
| "embodiment_id": (torch.int64, ()), | |
| "task_index": (torch.int64, ()), | |
| } | |
| it = iter(loader) | |
| for b in range(args.batches): | |
| batch = next(it) | |
| for k, (dt, shp) in want.items(): | |
| assert k in batch, f"batch missing {k}" | |
| assert batch[k].dtype == dt, f"{k}: dtype {batch[k].dtype} != {dt}" | |
| if shp: | |
| tail = batch[k].shape[1:] | |
| for i, s in enumerate(shp): | |
| if s is not None: | |
| assert tail[i] == s, f"{k}: shape {tuple(batch[k].shape)}" | |
| assert "task" in batch and isinstance(batch["task"][0], str), "task strings missing" | |
| assert not any(k.startswith("__") and torch.is_tensor(v) for k, v in batch.items()) | |
| print(f"[OK ] mixture loader: {args.batches} batches, schema verified, " | |
| f"tasks e.g. {batch['task'][:2]}") | |
| sys.exit(1 if bad else 0) | |
| if __name__ == "__main__": | |
| main() | |