| #!/usr/bin/env bash |
| set -euo pipefail |
| IFS=$'\n\t' |
|
|
| export PATH="$HOME/.local/bin:$PATH" |
|
|
| SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" |
| OPENPI_ROOT="${OPENPI_ROOT:-$(cd -- "$SCRIPT_DIR/.." && pwd)}" |
|
|
| log() { |
| printf '%s %s\n' "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" "$*" |
| } |
|
|
| on_exit() { |
| local rc=$? |
| if [[ $rc -eq 0 ]]; then |
| log "Run exited successfully" |
| else |
| log "Run exited with status $rc" |
| fi |
| } |
|
|
| trap on_exit EXIT |
|
|
| require_env() { |
| local name="$1" |
| if [[ -z "${!name:-}" ]]; then |
| echo "Required environment variable is not set: $name" >&2 |
| exit 1 |
| fi |
| } |
|
|
| project_python_path() { |
| echo "$OPENPI_ROOT/.venv/bin/python" |
| } |
|
|
| hf_cli() { |
| local venv_python |
| venv_python="$(project_python_path)" |
| if [[ -x "$venv_python" ]]; then |
| "$venv_python" -m huggingface_hub.commands.huggingface_cli "$@" |
| return |
| fi |
| if command -v huggingface-cli >/dev/null 2>&1; then |
| huggingface-cli "$@" |
| return |
| fi |
| if command -v hf >/dev/null 2>&1; then |
| hf "$@" |
| return |
| fi |
| echo "No Hugging Face CLI found in PATH or project venv." >&2 |
| exit 1 |
| } |
|
|
| project_python() { |
| local venv_python |
| venv_python="$(project_python_path)" |
| if [[ -x "$venv_python" ]]; then |
| "$venv_python" "$@" |
| return |
| fi |
| python3 "$@" |
| } |
|
|
| ensure_hf_auth() { |
| if hf_cli whoami >/dev/null 2>&1; then |
| return 0 |
| fi |
| if [[ -n "${HF_TOKEN:-}" ]]; then |
| hf_cli login --token "$HF_TOKEN" >/dev/null |
| hf_cli whoami >/dev/null |
| return 0 |
| fi |
| echo "Hugging Face auth is not available. Set HF_TOKEN or login first." >&2 |
| exit 1 |
| } |
|
|
| export HF_HUB_ENABLE_HF_TRANSFER=0 |
|
|
| export ROOT="${ROOT:-$HOME/pi05prep-work}" |
| export LOGDIR="${LOGDIR:-$ROOT/logs}" |
| export SOURCE_CACHE="${SOURCE_CACHE:-$ROOT/aloha-source}" |
| export MODEL_REPO="${MODEL_REPO:-lsnu/pi05tests-openpi-multiarm}" |
| export HF_HOME="${HF_HOME:-$ROOT/hf-home}" |
| export HF_HUB_CACHE="${HF_HUB_CACHE:-$HF_HOME/hub}" |
| export HF_DATASETS_CACHE="${HF_DATASETS_CACHE:-$HF_HOME/datasets}" |
| export HF_LEROBOT_HOME="${HF_LEROBOT_HOME:-$HF_HOME/lerobot}" |
| export STATS_BATCH_SIZE="${STATS_BATCH_SIZE:-64}" |
| export STATS_NUM_WORKERS="${STATS_NUM_WORKERS:-0}" |
| export STATS_MAX_FRAMES="${STATS_MAX_FRAMES:-}" |
| export TARGET_PRIVATE="${TARGET_PRIVATE:-true}" |
| export ALOHA_FILTER="${ALOHA_FILTER:-}" |
|
|
| unset LEROBOT_HOME || true |
|
|
| mkdir -p "$LOGDIR" "$SOURCE_CACHE" "$HF_HOME" "$HF_HUB_CACHE" "$HF_DATASETS_CACHE" "$HF_LEROBOT_HOME" |
|
|
| prepare_openpi_env() { |
| require_env HF_TOKEN |
| cd "$OPENPI_ROOT" |
|
|
| if grep -q 'jax\[cuda12\]==0\.5\.3' pyproject.toml; then |
| echo "pyproject.toml still points at jax[cuda12]; patch it before running." >&2 |
| exit 1 |
| fi |
|
|
| log "Authenticating Hugging Face CLI" |
| hf_cli login --token "$HF_TOKEN" |
| hf_cli whoami |
|
|
| log "Syncing uv environment" |
| GIT_LFS_SKIP_SMUDGE=1 uv sync --python 3.11 |
| GIT_LFS_SKIP_SMUDGE=1 uv pip install -e . |
| } |
|
|
| dataset_specs() { |
| cat <<'EOF' |
| training|pen_uncap_diverse|physical-intelligence/aloha_pen_uncap_diverse|lsnu/pi05tests-openpi-multiarm-aloha-training-pen-uncap-diverse|pi0_aloha_pen_uncap,pi05_aloha_pen_uncap |
| training|transfer_cube_human|lerobot/aloha_sim_transfer_cube_human|lsnu/pi05tests-openpi-multiarm-aloha-training-transfer-cube-human|pi0_aloha_sim |
| benchmark|transfer_cube_scripted|lerobot/aloha_sim_transfer_cube_scripted|lsnu/pi05tests-openpi-multiarm-aloha-benchmark-transfer-cube-scripted| |
| benchmark|insertion_human|lerobot/aloha_sim_insertion_human|lsnu/pi05tests-openpi-multiarm-aloha-benchmark-insertion-human| |
| benchmark|insertion_scripted|lerobot/aloha_sim_insertion_scripted|lsnu/pi05tests-openpi-multiarm-aloha-benchmark-insertion-scripted| |
| EOF |
| } |
|
|
| matches_filter() { |
| local label="$1" |
| local source_repo="$2" |
| local target_repo="$3" |
| if [[ -z "$ALOHA_FILTER" ]]; then |
| return 0 |
| fi |
| [[ "$label" == *"$ALOHA_FILTER"* || "$source_repo" == *"$ALOHA_FILTER"* || "$target_repo" == *"$ALOHA_FILTER"* ]] |
| } |
|
|
| create_target_repo() { |
| local repo_id="$1" |
| local private_flag="$2" |
| project_python - "$repo_id" "$private_flag" <<'PY' |
| import sys |
|
|
| from huggingface_hub import HfApi |
|
|
| repo_id, private_flag = sys.argv[1:3] |
| private = private_flag.lower() in {"1", "true", "yes", "y"} |
| HfApi().create_repo(repo_id, repo_type="dataset", exist_ok=True, private=private) |
| print(repo_id) |
| PY |
| } |
|
|
| download_source_dataset() { |
| local source_repo="$1" |
| local local_dir="$2" |
| project_python - "$source_repo" "$local_dir" <<'PY' |
| from pathlib import Path |
| import sys |
|
|
| from huggingface_hub import snapshot_download |
|
|
| source_repo, local_dir = sys.argv[1:3] |
| Path(local_dir).mkdir(parents=True, exist_ok=True) |
| snapshot_download( |
| repo_id=source_repo, |
| repo_type="dataset", |
| local_dir=local_dir, |
| allow_patterns=[".gitattributes", "README.md", "meta/**", "data/**", "videos/**"], |
| ) |
| print(local_dir) |
| PY |
| } |
|
|
| verify_local_dataset() { |
| local dataset_root="$1" |
| project_python - "$dataset_root" <<'PY' |
| import json |
| import sys |
| from pathlib import Path |
|
|
| root = Path(sys.argv[1]) |
| if not root.exists(): |
| raise SystemExit(f"Local dataset directory not found: {root}") |
|
|
| paths = [p.relative_to(root).as_posix() for p in root.rglob("*") if p.is_file()] |
| if "meta/info.json" not in paths: |
| raise SystemExit(f"Missing meta/info.json in {root}") |
|
|
| has_tasks = "meta/tasks.parquet" in paths or "meta/tasks.jsonl" in paths |
| if not has_tasks: |
| raise SystemExit(f"Missing task metadata in {root}") |
|
|
| has_episodes = "meta/episodes.jsonl" in paths or any( |
| p.startswith("meta/episodes/") and p.endswith(".parquet") for p in paths |
| ) |
| if not has_episodes: |
| raise SystemExit(f"Missing episode metadata in {root}") |
|
|
| data_parquet = [p for p in paths if p.startswith("data/") and p.endswith(".parquet")] |
| if not data_parquet: |
| raise SystemExit(f"No parquet data files found under {root}") |
|
|
| video_files = [p for p in paths if p.startswith("videos/") and p.endswith(".mp4")] |
| info = json.loads((root / "meta" / "info.json").read_text()) |
| features = info.get("features", {}) |
| if "action" not in features: |
| raise SystemExit(f"Dataset is missing 'action' feature in {root / 'meta' / 'info.json'}") |
| if "observation.state" not in features: |
| raise SystemExit(f"Dataset is missing 'observation.state' feature in {root / 'meta' / 'info.json'}") |
|
|
| print( |
| json.dumps( |
| { |
| "root": str(root), |
| "files": len(paths), |
| "data_parquet": len(data_parquet), |
| "videos": len(video_files), |
| "features": sorted(features), |
| } |
| ) |
| ) |
| PY |
| } |
|
|
| verify_remote_dataset() { |
| local repo_id="$1" |
| project_python - "$repo_id" <<'PY' |
| import json |
| import sys |
|
|
| from huggingface_hub import HfApi |
|
|
| repo_id = sys.argv[1] |
| api = HfApi() |
| paths = [item.path for item in api.list_repo_tree(repo_id, repo_type="dataset", recursive=True, expand=True)] |
|
|
| if "meta/info.json" not in paths: |
| raise SystemExit(f"Remote dataset is missing meta/info.json: {repo_id}") |
| if "meta/tasks.parquet" not in paths and "meta/tasks.jsonl" not in paths: |
| raise SystemExit(f"Remote dataset is missing task metadata: {repo_id}") |
| if "meta/episodes.jsonl" not in paths and not any( |
| p.startswith("meta/episodes/") and p.endswith(".parquet") for p in paths |
| ): |
| raise SystemExit(f"Remote dataset is missing episode metadata: {repo_id}") |
|
|
| data_parquet = [p for p in paths if p.startswith("data/") and p.endswith(".parquet")] |
| if not data_parquet: |
| raise SystemExit(f"Remote dataset {repo_id} has no parquet data files") |
|
|
| video_files = [p for p in paths if p.startswith("videos/") and p.endswith(".mp4")] |
| print( |
| json.dumps( |
| { |
| "repo_id": repo_id, |
| "files": len(paths), |
| "data_parquet": len(data_parquet), |
| "video_files": len(video_files), |
| } |
| ) |
| ) |
| PY |
| } |
|
|
| upload_dataset() { |
| local local_dir="$1" |
| local repo_id="$2" |
| log "Uploading dataset repo: $repo_id" |
| hf_cli upload-large-folder "$repo_id" "$local_dir" --repo-type dataset --num-workers 16 |
| } |
|
|
| stats_one() { |
| local config_name="$1" |
| local repo_id="$2" |
| local log_file="$LOGDIR/${config_name//\//_}__${repo_id//\//_}.stats.log" |
| local -a cmd=( |
| .venv/bin/python -u scripts/compute_norm_stats_repo.py |
| --config-name "$config_name" |
| --repo-id "$repo_id" |
| --batch-size "$STATS_BATCH_SIZE" |
| --num-workers "$STATS_NUM_WORKERS" |
| --assets-base-dir ./assets |
| ) |
| if [[ -n "$STATS_MAX_FRAMES" ]]; then |
| cmd+=(--max-frames "$STATS_MAX_FRAMES") |
| fi |
|
|
| log "Computing norm stats: $config_name / $repo_id" |
| ( |
| cd "$OPENPI_ROOT" |
| PYTHONUNBUFFERED=1 "${cmd[@]}" |
| ) >"$log_file" 2>&1 |
| tail -n 40 "$log_file" || true |
| } |
|
|
| upload_stats() { |
| local config_name="$1" |
| local repo_id="$2" |
| local src_dir="$OPENPI_ROOT/assets/$config_name/$repo_id" |
| local dst_dir="openpi/assets/$config_name/$repo_id" |
| log "Uploading norm stats: $MODEL_REPO::$dst_dir" |
| hf_cli upload "$MODEL_REPO" "$src_dir" "$dst_dir" |
| } |
|
|
| verify_remote_stats() { |
| local config_name="$1" |
| local repo_id="$2" |
| project_python - "$MODEL_REPO" "$config_name" "$repo_id" <<'PY' |
| import sys |
|
|
| from huggingface_hub import HfApi |
|
|
| model_repo, config_name, repo_id = sys.argv[1:4] |
| target = f"openpi/assets/{config_name}/{repo_id}/norm_stats.json" |
| paths = [item.path for item in HfApi().list_repo_tree(model_repo, repo_type="model", recursive=True, expand=True)] |
| if target not in paths: |
| raise SystemExit(f"Missing remote norm stats file: {target}") |
| print(target) |
| PY |
| } |
|
|
| cleanup_local_source() { |
| local source_dir="$1" |
| rm -rf "$source_dir" |
| } |
|
|
| sync_dataset_if_needed() { |
| local label="$1" |
| local source_repo="$2" |
| local target_repo="$3" |
| local source_dir="$SOURCE_CACHE/${target_repo//\//__}" |
|
|
| if verify_remote_dataset "$target_repo" >"$LOGDIR/${label}_verify_remote.json" 2>/dev/null; then |
| log "Remote dataset already verified; skipping sync for $target_repo" |
| return 0 |
| fi |
|
|
| log "Ensuring target dataset repo exists: $target_repo" |
| create_target_repo "$target_repo" "$TARGET_PRIVATE" >"$LOGDIR/${label}_create_repo.txt" |
|
|
| log "Downloading source dataset: $source_repo" |
| download_source_dataset "$source_repo" "$source_dir" >"$LOGDIR/${label}_download.txt" |
| verify_local_dataset "$source_dir" | tee "$LOGDIR/${label}_verify_local.json" |
| upload_dataset "$source_dir" "$target_repo" |
| verify_remote_dataset "$target_repo" | tee "$LOGDIR/${label}_verify_remote.json" |
| cleanup_local_source "$source_dir" |
| } |
|
|
| process_training_dataset() { |
| local label="$1" |
| local source_repo="$2" |
| local target_repo="$3" |
| local config_names="$4" |
|
|
| log "=== Training dataset: $label ===" |
| sync_dataset_if_needed "$label" "$source_repo" "$target_repo" |
|
|
| local cfg |
| IFS=',' read -r -a cfgs <<<"$config_names" |
| for cfg in "${cfgs[@]}"; do |
| if verify_remote_stats "$cfg" "$target_repo" >"$LOGDIR/${label}_${cfg}_stats_remote.txt" 2>/dev/null; then |
| log "Norm stats already verified remotely; skipping $cfg / $target_repo" |
| continue |
| fi |
| stats_one "$cfg" "$target_repo" |
| upload_stats "$cfg" "$target_repo" |
| verify_remote_stats "$cfg" "$target_repo" | tee "$LOGDIR/${label}_${cfg}_stats_remote.txt" |
| done |
| } |
|
|
| process_benchmark_dataset() { |
| local label="$1" |
| local source_repo="$2" |
| local target_repo="$3" |
|
|
| log "=== Benchmark dataset: $label ===" |
| sync_dataset_if_needed "$label" "$source_repo" "$target_repo" |
| } |
|
|
| main() { |
| if [[ "${SKIP_PREPARE:-0}" == "1" ]]; then |
| ensure_hf_auth |
| log "Skipping environment bootstrap; using HF_HOME=$HF_HOME" |
| else |
| prepare_openpi_env |
| fi |
|
|
| while IFS='|' read -r category label source_repo target_repo config_names; do |
| [[ -z "$category" ]] && continue |
| if ! matches_filter "$label" "$source_repo" "$target_repo"; then |
| continue |
| fi |
|
|
| if [[ "$category" == "training" ]]; then |
| process_training_dataset "$label" "$source_repo" "$target_repo" "$config_names" |
| else |
| process_benchmark_dataset "$label" "$source_repo" "$target_repo" |
| fi |
| done < <(dataset_specs) |
|
|
| log "All ALOHA tasks completed" |
| } |
|
|
| if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then |
| main "$@" |
| fi |
|
|