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 | |
| """Prepare the SO-100/101 slice of HuggingFaceVLA/community_dataset_v3. | |
| Steps: | |
| catalog — fetch all sub-dataset info.json, write catalog.json | |
| select — filter SO100/101 single-arm, rank by episodes, write selection.json | |
| convert — download each selected subdir + convert v2.1 -> v3.0 locally | |
| Usage: | |
| python scripts/prepare_community_v3.py catalog | |
| python scripts/prepare_community_v3.py select --target-episodes 12000 | |
| python scripts/prepare_community_v3.py convert [--limit N] | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import shutil | |
| from concurrent.futures import ThreadPoolExecutor, as_completed | |
| from pathlib import Path | |
| AGG_REPO = "HuggingFaceVLA/community_dataset_v3" | |
| DISK_BUDGET_GB = 400 | |
| DATA_ROOT = Path.home() / "tinyvla_data" | |
| CATALOG = DATA_ROOT / "community_v3_catalog.json" | |
| SELECTION = DATA_ROOT / "community_v3_selection.json" | |
| CONVERTED_DIR = DATA_ROOT / "so101_v3" | |
| SO_ROBOTS = {"so100", "so101", "so100_follower", "so101_follower"} | |
| def cmd_catalog(args): | |
| from huggingface_hub import HfApi, hf_hub_download | |
| api = HfApi() | |
| files = api.list_repo_files(AGG_REPO, repo_type="dataset") | |
| subs = sorted(f.rsplit("/meta/info.json", 1)[0] for f in files if f.endswith("meta/info.json")) | |
| print(f"{len(subs)} sub-datasets") | |
| def fetch(sub): | |
| try: | |
| p = hf_hub_download(AGG_REPO, f"{sub}/meta/info.json", repo_type="dataset") | |
| info = json.load(open(p)) | |
| return sub, { | |
| "robot_type": info.get("robot_type"), | |
| "episodes": info.get("total_episodes"), | |
| "frames": info.get("total_frames"), | |
| "fps": info.get("fps"), | |
| "version": info.get("codebase_version"), | |
| "action_shape": info.get("features", {}).get("action", {}).get("shape"), | |
| "cameras": [k for k in info.get("features", {}) if k.startswith("observation.images")], | |
| } | |
| except Exception as e: | |
| return sub, {"error": str(e)[:100]} | |
| catalog = {} | |
| with ThreadPoolExecutor(16) as ex: | |
| futs = [ex.submit(fetch, s) for s in subs] | |
| for i, f in enumerate(as_completed(futs)): | |
| sub, meta = f.result() | |
| catalog[sub] = meta | |
| if (i + 1) % 100 == 0: | |
| print(f"{i+1}/{len(subs)}") | |
| DATA_ROOT.mkdir(parents=True, exist_ok=True) | |
| CATALOG.write_text(json.dumps(catalog, indent=1)) | |
| print(f"wrote {CATALOG}") | |
| def cmd_select(args): | |
| catalog = json.loads(CATALOG.read_text()) | |
| rows = [ | |
| (sub, m) | |
| for sub, m in catalog.items() | |
| if m.get("robot_type") in SO_ROBOTS | |
| and m.get("action_shape") == [6] | |
| and m.get("fps") == 30 | |
| and m.get("episodes") | |
| and m.get("cameras") | |
| ] | |
| # prefer larger datasets: fewer conversions per episode | |
| rows.sort(key=lambda r: -r[1]["episodes"]) | |
| picked, total = [], 0 | |
| for sub, m in rows: | |
| if total >= args.target_episodes: | |
| break | |
| picked.append({"sub": sub, **m}) | |
| total += m["episodes"] | |
| print(f"{len(rows)} eligible; picked {len(picked)} datasets, {total} episodes") | |
| SELECTION.write_text(json.dumps(picked, indent=1)) | |
| print(f"wrote {SELECTION}") | |
| def free_gb(path: Path) -> float: | |
| return shutil.disk_usage(path).free / 1e9 | |
| def used_gb_cached(path: Path) -> float: | |
| import subprocess | |
| out = subprocess.run(["du", "-s", "--block-size=1G", str(path)], capture_output=True, text=True) | |
| return float(out.stdout.split()[0]) if out.returncode == 0 else 0.0 | |
| def cmd_convert(args): | |
| from huggingface_hub import snapshot_download | |
| from lerobot.scripts.convert_dataset_v21_to_v30 import convert_dataset | |
| picked = json.loads(SELECTION.read_text()) | |
| if args.limit: | |
| picked = picked[: args.limit] | |
| CONVERTED_DIR.mkdir(parents=True, exist_ok=True) | |
| raw_dir = DATA_ROOT / "_raw_v21" | |
| done, failed = 0, [] | |
| for item in picked: | |
| sub = item["sub"] | |
| name = sub.replace("/", "__") | |
| out = CONVERTED_DIR / name | |
| if (out / "meta" / "info.json").exists(): | |
| done += 1 | |
| continue | |
| local = raw_dir / name | |
| try: | |
| snapshot_download( | |
| AGG_REPO, | |
| repo_type="dataset", | |
| allow_patterns=[f"{sub}/*"], | |
| local_dir=raw_dir / "_dl", | |
| ) | |
| src = raw_dir / "_dl" / sub | |
| if local.exists(): | |
| shutil.rmtree(local) | |
| shutil.move(str(src), str(local)) | |
| convert_dataset(repo_id=name, root=local, push_to_hub=False, force_conversion=True) | |
| # converter writes v3.0 in place at root | |
| shutil.move(str(local), str(out)) | |
| # drop the v2.1 originals the converter stashes as <root>_old | |
| old = local.parent / (local.name + "_old") | |
| if old.exists(): | |
| shutil.rmtree(old) | |
| done += 1 | |
| print(f"[{done}/{len(picked)}] {sub}: converted -> {out} (free {free_gb(DATA_ROOT):.0f}GB)") | |
| if free_gb(DATA_ROOT) < 150 or used_gb_cached(DATA_ROOT) > DISK_BUDGET_GB: | |
| print("STOP: disk budget reached") | |
| break | |
| except Exception as e: | |
| failed.append(sub) | |
| print(f"FAIL {sub}: {type(e).__name__}: {str(e)[:200]}") | |
| print(f"done={done} failed={len(failed)}") | |
| if failed: | |
| (DATA_ROOT / "convert_failures.json").write_text(json.dumps(failed)) | |
| if __name__ == "__main__": | |
| parser = argparse.ArgumentParser() | |
| sub = parser.add_subparsers(dest="cmd", required=True) | |
| sub.add_parser("catalog") | |
| p_sel = sub.add_parser("select") | |
| p_sel.add_argument("--target-episodes", type=int, default=12_000) | |
| p_conv = sub.add_parser("convert") | |
| p_conv.add_argument("--limit", type=int, default=None) | |
| args = parser.parse_args() | |
| {"catalog": cmd_catalog, "select": cmd_select, "convert": cmd_convert}[args.cmd](args) | |