#!/usr/bin/env bash set -euo pipefail PROFILE=dreamtacvla BENCH=manifeel MODE=smoke DRY_RUN=0 TRAIN=1 TMUX_SESSION="" usage() { cat >&2 <<'EOF' usage: scripts/deploy_verified.sh [--profile dreamtacvla] [--bench manifeel] [--smoke|--full|--prepare-only] [--dry-run] [--tmux SESSION] Verified deployment entrypoint. Currently verified end-to-end: dreamtacvla + manifeel Default paths: PROJECT_ROOT=/root/autodl-tmp/vla_sft Useful overrides: PROJECT_ROOT DATA_ROOT CKPT_ROOT CODE_ROOT VENV_ROOT OUTPUT_ROOT HF_USERNAME HF_TOKEN USE_NETWORK_TURBO ARIA2_CONNECTIONS CONDA_PY311_CHANNEL PIP_FALLBACK_INDEX_URL SMOKE_STEPS FULL_STEPS EOF } while [[ $# -gt 0 ]]; do case "$1" in --profile|--baseline) PROFILE="$2"; shift 2 ;; --bench|--benchmark) BENCH="$2"; shift 2 ;; --smoke) MODE=smoke; TRAIN=1; shift ;; --full) MODE=full; TRAIN=1; shift ;; --prepare-only|--no-train|--skip-train) MODE=prepare; TRAIN=0; shift ;; --dry-run) DRY_RUN=1; shift ;; --tmux) TMUX_SESSION="$2"; shift 2 ;; -h|--help) usage; exit 0 ;; *) echo "unknown arg: $1" >&2 usage exit 2 ;; esac done if [[ "$PROFILE:$BENCH" != "dreamtacvla:manifeel" ]]; then cat >&2 </dev/null 2>&1; then mode_arg="--$MODE" if [[ "$MODE" == "prepare" ]]; then mode_arg="--prepare-only" fi tmux_args=(--profile "$PROFILE" --bench "$BENCH" "$mode_arg") if [[ "$DRY_RUN" -eq 1 ]]; then tmux_args+=(--dry-run) fi exec tmux new -s "$TMUX_SESSION" "$0" "${tmux_args[@]}" else echo "[warn] --tmux requested but tmux is not installed; continuing in the current shell" >&2 fi fi PROJECT_ROOT=${PROJECT_ROOT:-/root/autodl-tmp/vla_sft} DATA_ROOT=${DATA_ROOT:-$PROJECT_ROOT/data} CKPT_ROOT=${CKPT_ROOT:-$PROJECT_ROOT/checkpoints} CODE_ROOT=${CODE_ROOT:-$PROJECT_ROOT/baselines} VENV_ROOT=${VENV_ROOT:-$PROJECT_ROOT/envs} OUTPUT_ROOT=${OUTPUT_ROOT:-$PROJECT_ROOT/outputs} ARCHIVE_ROOT=${ARCHIVE_ROOT:-$PROJECT_ROOT/.hf_archives} HF_USERNAME=${HF_USERNAME:-poet70} SMOKE_STEPS=${SMOKE_STEPS:-10} FULL_STEPS=${FULL_STEPS:-200000} ARIA2_CONNECTIONS=${ARIA2_CONNECTIONS:-8} CONDA_PY311_CHANNEL=${CONDA_PY311_CHANNEL:-https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main} PIP_FALLBACK_INDEX_URL=${PIP_FALLBACK_INDEX_URL:-https://pypi.org/simple} CODE_REPO="$HF_USERNAME/vla-sft-code-dreamtacvla" DATA_REPO="$HF_USERNAME/data-manifeel-dtvla" CKPT_REPO="$HF_USERNAME/vla-ckpt-dreamtacvla-jepa" CODE_DIR="$CODE_ROOT/dreamtacvla" DATA_DIR="$DATA_ROOT/manifeel_hdf5" CKPT_DIR="$CKPT_ROOT/dreamtacvla_jepa" DATA_ARCHIVE_DIR="$ARCHIVE_ROOT/manifeel_dtvla" CKPT_ARCHIVE_DIR="$ARCHIVE_ROOT/dreamtacvla_jepa" VENV_DIR="$VENV_ROOT/dreamtacvla" LOG="$OUTPUT_ROOT/deploy_dreamtacvla_manifeel_notes.md" run() { echo "+ $*" if [[ "$DRY_RUN" -eq 0 ]]; then "$@" fi } log_note() { local msg="$1" if [[ "$DRY_RUN" -eq 0 ]]; then mkdir -p "$OUTPUT_ROOT" printf -- "- %s: %s\n" "$(date)" "$msg" >> "$LOG" fi echo "[note] $msg" } init_notes() { run mkdir -p "$PROJECT_ROOT" "$DATA_ROOT" "$CKPT_ROOT" "$CODE_ROOT" "$VENV_ROOT" "$OUTPUT_ROOT" "$ARCHIVE_ROOT" if [[ "$DRY_RUN" -eq 0 && ! -f "$LOG" ]]; then cat > "$LOG" </dev/null || echo unknown) Project root: $PROJECT_ROOT ## Progress EOF fi } setup_shell_env() { if [[ "${USE_NETWORK_TURBO:-auto}" != "0" && -f /etc/network_turbo ]]; then # shellcheck disable=SC1091 source /etc/network_turbo >/dev/null 2>&1 || true fi export PYTHONUTF8=${PYTHONUTF8:-1} export PYTHONIOENCODING=${PYTHONIOENCODING:-utf-8} if locale -a 2>/dev/null | grep -Eqi '^(C\.utf8|C\.UTF-8)$'; then export LANG=C.UTF-8 export LC_ALL=C.UTF-8 elif locale -a 2>/dev/null | grep -Eqi '^en_US\.utf8$'; then export LANG=en_US.UTF-8 export LC_ALL=en_US.UTF-8 else export LANG=C export LC_ALL=C fi export HF_HUB_DISABLE_XET=${HF_HUB_DISABLE_XET:-1} export HF_XET_HIGH_PERFORMANCE=${HF_XET_HIGH_PERFORMANCE:-0} export HF_HUB_ENABLE_HF_TRANSFER=${HF_HUB_ENABLE_HF_TRANSFER:-0} } find_bootstrap_python() { if command -v python3 >/dev/null 2>&1; then command -v python3 elif [[ -x /root/miniconda3/bin/python ]]; then printf '%s\n' /root/miniconda3/bin/python elif command -v python >/dev/null 2>&1; then command -v python else return 1 fi } ensure_hf_cli() { if [[ -x "$PROJECT_ROOT/bootstrap_env/bin/hf" ]]; then export PATH="$PROJECT_ROOT/bootstrap_env/bin:$PATH" fi if command -v hf >/dev/null 2>&1; then hf --version || true return 0 fi local py py=$(find_bootstrap_python) || { echo "No usable Python found for HF CLI bootstrap." >&2 exit 4 } log_note "hf CLI not found; creating bootstrap_env with $py." run "$py" -m venv "$PROJECT_ROOT/bootstrap_env" # shellcheck disable=SC1091 source "$PROJECT_ROOT/bootstrap_env/bin/activate" run python -m pip install -U pip setuptools wheel run python -m pip install -U huggingface_hub deactivate || true export PATH="$PROJECT_ROOT/bootstrap_env/bin:$PATH" run hf --version } ensure_aria2() { if command -v aria2c >/dev/null 2>&1; then return 0 fi if command -v apt-get >/dev/null 2>&1 && [[ "$(id -u)" -eq 0 ]]; then log_note "aria2c not found; installing aria2 with apt-get for resumable HF archive downloads." run apt-get update run apt-get install -y aria2 fi } hf_url() { local repo="$1" local repo_type="$2" local path="$3" if [[ "$repo_type" == "dataset" ]]; then printf 'https://huggingface.co/datasets/%s/resolve/main/%s' "$repo" "$path" else printf 'https://huggingface.co/%s/resolve/main/%s' "$repo" "$path" fi } download_url() { local url="$1" local dest="$2" local dir base dir=$(dirname "$dest") base=$(basename "$dest") run mkdir -p "$dir" if [[ "$DRY_RUN" -eq 1 ]]; then echo "+ download $url -> $dest" return 0 fi if command -v aria2c >/dev/null 2>&1; then if [[ -n "${HF_TOKEN:-}" ]]; then echo "+ aria2c -c -x $ARIA2_CONNECTIONS -s $ARIA2_CONNECTIONS -o $base --header 'Authorization: Bearer '" if [[ "$DRY_RUN" -eq 0 ]]; then aria2c -c -x "$ARIA2_CONNECTIONS" -s "$ARIA2_CONNECTIONS" -k 1M \ --retry-wait=5 --max-tries=0 --summary-interval=30 --file-allocation=none \ --header "Authorization: Bearer $HF_TOKEN" -d "$dir" -o "$base" "$url" fi else run aria2c -c -x "$ARIA2_CONNECTIONS" -s "$ARIA2_CONNECTIONS" -k 1M \ --retry-wait=5 --max-tries=0 --summary-interval=30 --file-allocation=none \ -d "$dir" -o "$base" "$url" fi elif command -v wget >/dev/null 2>&1; then if [[ -n "${HF_TOKEN:-}" ]]; then echo "+ wget -c -O $dest --header 'Authorization: Bearer '" if [[ "$DRY_RUN" -eq 0 ]]; then wget -c --tries=0 --timeout=30 --read-timeout=30 --waitretry=5 \ --header "Authorization: Bearer $HF_TOKEN" -O "$dest" "$url" fi else run wget -c --tries=0 --timeout=30 --read-timeout=30 --waitretry=5 -O "$dest" "$url" fi else echo "Need aria2c or wget to download archive files." >&2 exit 4 fi } download_repo() { local repo="$1" local repo_type="$2" local dest="$3" run mkdir -p "$dest" run hf download "$repo" --repo-type "$repo_type" --local-dir "$dest" } artifact_dest_ready() { local archive_name="$1" local dest_dir="$2" case "$archive_name" in manifeel-dtvla.tar.gz) [[ -d "$dest_dir" ]] && find "$dest_dir" -maxdepth 1 -name '*.hdf5' -print -quit | grep -q . ;; dreamtacvla-jepa.tar.gz) [[ -s "$dest_dir/vitl_manifeel_base.pt" ]] ;; *) return 1 ;; esac } extract_archive() { local repo="$1" local repo_type="$2" local archive_name="$3" local archive_dir="$4" local dest_dir="$5" local archive="$archive_dir/$archive_name" local checksum="$archive_dir/$archive_name.sha256" download_url "$(hf_url "$repo" "$repo_type" "$archive_name.sha256")" "$checksum" download_url "$(hf_url "$repo" "$repo_type" "$archive_name")" "$archive" if [[ "$DRY_RUN" -eq 0 ]]; then (cd "$archive_dir" && sha256sum -c "$archive_name.sha256") tar -tzf "$archive" >/tmp/"$archive_name".list if artifact_dest_ready "$archive_name" "$dest_dir"; then echo "[extract] existing destination already looks ready: $dest_dir" return 0 fi mkdir -p "$dest_dir" find "$dest_dir" -mindepth 1 -maxdepth 1 -exec rm -rf {} + tar -xzf "$archive" -C "$dest_dir" --strip-components=1 else echo "+ sha256sum -c $checksum" echo "+ tar -tzf $archive" echo "+ extract $archive -> $dest_dir" fi } download_artifacts() { log_note "downloading code, ManiFeel-DTVLA data, and JEPA checkpoint." download_repo "$CODE_REPO" model "$CODE_DIR" extract_archive "$DATA_REPO" dataset manifeel-dtvla.tar.gz "$DATA_ARCHIVE_DIR" "$DATA_DIR" extract_archive "$CKPT_REPO" model dreamtacvla-jepa.tar.gz "$CKPT_ARCHIVE_DIR" "$CKPT_DIR" log_note "artifacts downloaded and extracted." } patch_dreamtacvla_loader() { local target="$CODE_DIR/ModelTrain/module/utils.py" if [[ "$DRY_RUN" -eq 1 ]]; then echo "+ patch DreamTacVLA loader to include tactile_camera_names" return 0 fi python - "$target" <<'PY' import pathlib import sys path = pathlib.Path(sys.argv[1]) text = path.read_text() new = """ image_dict = dict() all_camera_names = list(self.camera_names) for cam_name in self.tactile_camera_names: if cam_name not in all_camera_names: all_camera_names.append(cam_name) for cam_name in all_camera_names: """ old = """ image_dict = dict() for cam_name in self.camera_names: """ if new in text: print("loader patch already present") elif old in text: backup = path.with_suffix(path.suffix + ".bak_tactile_loader") if not backup.exists(): backup.write_text(text) path.write_text(text.replace(old, new, 1)) print(f"patched loader: {path}") else: raise SystemExit(f"cannot locate loader camera loop in {path}") PY log_note "patched DreamTacVLA loader to read tactile_camera_names in addition to RGB camera_names." } conda_create_python311() { local base_dir="$VENV_ROOT/.python311_base" if [[ -x "$base_dir/bin/python" ]]; then VENV_CREATE_PYTHON="$base_dir/bin/python" return 0 fi if ! command -v conda >/dev/null 2>&1; then return 1 fi run mkdir -p "$VENV_ROOT" if [[ "$DRY_RUN" -eq 1 ]]; then echo "+ conda create -y --override-channels -c $CONDA_PY311_CHANNEL -p $base_dir python=3.11" VENV_CREATE_PYTHON="$base_dir/bin/python" return 0 fi if ! conda create -y --override-channels -c "$CONDA_PY311_CHANNEL" -p "$base_dir" python=3.11; then log_note "conda create via $CONDA_PY311_CHANNEL failed; retrying with defaults." conda create -y --override-channels -c defaults -p "$base_dir" python=3.11 fi VENV_CREATE_PYTHON="$base_dir/bin/python" } select_dreamtacvla_python() { if [[ -n "${DREAMTACVLA_PYTHON_BIN:-}" ]]; then VENV_CREATE_PYTHON="$DREAMTACVLA_PYTHON_BIN" elif command -v python3.11 >/dev/null 2>&1; then VENV_CREATE_PYTHON=$(command -v python3.11) else conda_create_python311 || { echo "DreamTacVLA needs Python 3.11. Install python3.11 or conda, or set DREAMTACVLA_PYTHON_BIN." >&2 exit 4 } fi } pip_install() { local args=("$@") if [[ "$DRY_RUN" -eq 1 ]]; then echo "+ python -m pip install ${args[*]}" return 0 fi if python -m pip install "${args[@]}"; then return 0 fi log_note "pip install failed; retrying with fallback index $PIP_FALLBACK_INDEX_URL." python -m pip install -i "$PIP_FALLBACK_INDEX_URL" "${args[@]}" } prepare_resnet18_cache() { local cache_root dest tmp hash cache_root="${TORCH_HOME:-$HOME/.cache/torch}/hub/checkpoints" dest="$cache_root/resnet18-f37072fd.pth" tmp="$dest.tmp" if [[ "$DRY_RUN" -eq 1 ]]; then echo "+ cache ResNet18 at $dest" return 0 fi if [[ -s "$dest" ]]; then return 0 fi mkdir -p "$cache_root" curl -L -k --fail --connect-timeout 20 --retry 5 --retry-delay 2 \ -o "$tmp" https://download.pytorch.org/models/resnet18-f37072fd.pth hash=$(sha256sum "$tmp" | awk '{print $1}') if [[ "$hash" != f37072fd* ]]; then rm -f "$tmp" echo "ResNet18 checkpoint hash mismatch: $hash" >&2 exit 6 fi mv "$tmp" "$dest" } install_env() { select_dreamtacvla_python if [[ ! -d "$VENV_DIR" ]]; then run "$VENV_CREATE_PYTHON" -m venv "$VENV_DIR" fi if [[ "$DRY_RUN" -eq 0 ]]; then # shellcheck disable=SC1091 source "$VENV_DIR/bin/activate" else echo "+ source $VENV_DIR/bin/activate" fi run python --version pip_install -U pip setuptools wheel pip_install torch==2.12.1 torchvision==0.27.1 if [[ -f "$CODE_DIR/requirements.txt" ]]; then pip_install -r "$CODE_DIR/requirements.txt" fi pip_install transformers==5.12.1 open_clip_torch==3.3.0 timm==1.0.27 \ opencv-python==4.13.0.92 h5py==3.16.0 mujoco==2.3.7 \ imageio imageio-ffmpeg diffusers prepare_resnet18_cache log_note "DreamTacVLA Python environment installed at $VENV_DIR." } print_training_command() { local steps="$1" local out_dir="$2" cat <