FastH3-Omni-MLX / bench /prepare_model_dir.sh
aryan5v's picture
Build + benchmark kit for FastH3 Omni on MLX
0ac7396 verified
Raw History Blame Contribute Delete
19.6 kB
#!/usr/bin/env bash
# Build the FastH3 Omni MLX benchmark model directory from PUBLIC Hugging Face
# sources. Resumable, idempotent, self-verifying. Run `./prepare_model_dir.sh --help`.
set -euo pipefail
KIT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
HELPERS="$KIT_DIR/prepare_helpers.py"
# ---- Pinned source revisions (HF `main` commit SHAs fetched 2026-10-20) ------
REV_OMNI="53e0d26a1691aa473b6262d8196a8feb2c297df0" # FastVideo/FastVideo-FastH3-Omni-8-Step-V1
REV_QAD="f01e6a23ac6066aa9eb622cf94b6259442e70ec2" # FastVideo/FastH3-Omni-NVFP4-QAD
REV_DEC="bd0c248f2a0f57ca643b3df2ac1f375fa55101fb" # FastVideo/FastH3-NVFP4-VAE-Decoder
REV_LVAE="4925ea978147b9de492d7a5fd4f9c6d1854ca1cc" # lightx2v/LightVAE
REPO_OMNI="FastVideo/FastVideo-FastH3-Omni-8-Step-V1"
REPO_QAD="FastVideo/FastH3-Omni-NVFP4-QAD"
REPO_DEC="FastVideo/FastH3-NVFP4-VAE-Decoder"
REPO_LVAE="lightx2v/LightVAE"
ENCODER_SOURCE_FIELD="hf://FastVideo/FastH3-Omni-NVFP4-QAD/text_encoder"
# Reference SHA-256 of our build (from the validated model dir). Downloaded/copied
# files must match exactly (failure). dit/* and root/text_encoder/{model.safetensors,
# mlx_h3_nvfp4_encoder.json} are converter outputs: a mismatch is only a WARNING
# (MLX version / hardware may change the quantized bytes).
read -r -d '' EXPECTED_SHA256 <<'EXPECTED_EOF' || true
59b99642b95ea21630e311198ddbfffbfe05aadba0c2f5d884cbdf4efcc90f44 LICENSE
c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4 LICENSE-Qwen3-VL
6f63c0101bdd0c7188ba540277ddd619952e2874437f9702bcdfa644b355dbc4 NOTICE
fcc29480ea8247d75b5bdb86805c03044083af614244600d13467814acd4eb9b dit/int6/mlx_h3_dit.json
9ea28f8944686dbfecd684664ea5574b90994e3f232c4552b5c15dfc1faeb446 dit/int6/mlx_h3_dit.safetensors
804780f7133477067bd6bbfbc02dc8b3cf9feeb400f97c08f5b1d5f6cbab3840 root/audio_scheduler/scheduler_config.json
9a3c645ff892b376c6f5f4c8685964cd75474731af594ff058492a0000caabb6 root/audio_vae/config.json
52c59e67ba8de5477c81bfbced0327aabf500f1bfdeefd5ee754529241cb26cb root/audio_vae/diffusion_pytorch_model.safetensors
68577f23f0c866e329aa8a578b2455e4ca9a26a3ca1d15ddd6d0e99091007e18 root/fastvideo_inference.json
5c72a170d2a4a1a3bc5adad2e689ae28138a9700e5b8c96c0266331e86c0acce root/processor/chat_template.json
599bab54075088774b1733fde865d5bd747cbcc7a547c5bc12610e874e26f5e3 root/processor/merges.txt
27225450ac9c6529872ee1924fcb0962ff5634834f817040f444118116f4e516 root/processor/preprocessor_config.json
a5d85b6dcc535e6b93115a9ef287e6132fdbf30270da6218194ba742261173c7 root/processor/tokenizer.json
a07e942ac874baa13758de8d1fbdb186683cc03416b5589e1b6671c6b3057c68 root/processor/tokenizer_config.json
7768af27c1fafa9cc9011c1dc20067e03f8915e03b63504550e11d5066986d13 root/processor/video_preprocessor_config.json
ca10d7e9fb3ed18575dd1e277a2579c16d108e32f27439684afa0e10b1440910 root/processor/vocab.json
8fa6c3aa70dc9e691e1a6df899fd1b6f75f70481a27cee6e18a303817075c304 root/scheduler/scheduler_config.json
29d4a1e1db0e112612bf1e7c457a42ce9fe13fd0c69a0c3a64c1d291325bd9eb root/text_encoder/config.json
1d50c336c0e34645699039d78e7532fe2e7c21b2ed71f98d4252b682d53e075e root/text_encoder/mlx_h3_nvfp4_encoder.json
f91405a223c95ed013267a6274dbee40d832a28cf89184ab86f84214b1882809 root/text_encoder/model.safetensors
599bab54075088774b1733fde865d5bd747cbcc7a547c5bc12610e874e26f5e3 root/tokenizer/merges.txt
a5d85b6dcc535e6b93115a9ef287e6132fdbf30270da6218194ba742261173c7 root/tokenizer/tokenizer.json
a07e942ac874baa13758de8d1fbdb186683cc03416b5589e1b6671c6b3057c68 root/tokenizer/tokenizer_config.json
ca10d7e9fb3ed18575dd1e277a2579c16d108e32f27439684afa0e10b1440910 root/tokenizer/vocab.json
658c1f6278262db3072d9a6f4832c7c49280f91c94a8455414525cd6648de677 root/transformer_ref/config.json
ffa6d5e7a95dc57ae0494a8c0caf588e2c555252c159104d310ffcc450e1c914 root/vae/config.json
c0d0f1beb88f0579d2a9fb249850419dbf3baa05d86ce37d3135c8c8873b4752 vae/fasth3-fast-8L-decoder-nvfp4.safetensors
84a92170f1cf6737f0509f8350cf642832afe3c0c0b1766bf4a0e6e820b62729 vae/fasth3-quality-36L-decoder-nvfp4.safetensors
4b0e5af102da19324c64df40abbd95008d8acb0b9cf76e4704d74c8ac20cefac vae/lightvae-lite-h3-encoder.safetensors
EXPECTED_EOF
# Approximate sizes in GB.
SRC_GB=95; ENC_GB=16; OUT_FIXED_GB=7 # decoders+encoder(vae)+misc
fmt_gb() { case "$1" in int6) echo 17;; int8) echo 20;; bf16) echo 38;; *) echo 0;; esac; }
# ---- Options -----------------------------------------------------------------
FASTVIDEO=""; MODEL_DIR="$PWD/fasth3-omni-mlx-model"; SOURCES=""; FORMATS="int6"
PYTHON="python3"; SKIP_DOWNLOAD=0; KEEP_SOURCES=0; LOCK_DIR=""; DRY_RUN=0
RESERVE_GB=5 # free space that must remain after each big stage
usage() {
cat <<EOF
Usage: $0 --fastvideo <FastVideo checkout> [options]
--fastvideo DIR FastVideo checkout (branch aryan/omniref-mlx-speed). Required.
--model-dir DIR output model directory (default: $MODEL_DIR)
--sources DIR download directory (default: <model-dir>/../fasth3-sources); may be deleted afterwards
--formats "int6" DiT formats, space/comma separated: int6 (default), int8, bf16
--python PATH python of the FastVideo venv (needs mlx, fastvideo); default python3
--skip-download use existing --sources (layout: omni-bf16/ nvfp4-qad/ vae-decoders/ lightvae/)
--keep-sources do not delete downloaded sources after a successful verified build
--reserve-gb N free GB that must remain after each big stage (default $RESERVE_GB)
--lock-dir DIR opt-in: serialize memory-heavy stages via mkdir DIR/gpu.lock
--dry-run print the hf/conversion commands without running them
Safe to interrupt (Ctrl-C) and re-run: finished stages are verified and skipped.
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--fastvideo) FASTVIDEO="$2"; shift 2;;
--model-dir) MODEL_DIR="$2"; shift 2;;
--sources) SOURCES="$2"; shift 2;;
--formats) FORMATS="$2"; shift 2;;
--python) PYTHON="$2"; shift 2;;
--skip-download) SKIP_DOWNLOAD=1; shift;;
--keep-sources) KEEP_SOURCES=1; shift;;
--reserve-gb) RESERVE_GB="$2"; shift 2;;
--lock-dir) LOCK_DIR="$2"; shift 2;;
--dry-run) DRY_RUN=1; shift;;
-h|--help) usage; exit 0;;
*) echo "unknown option: $1" >&2; usage >&2; exit 2;;
esac
done
[[ -n "$FASTVIDEO" ]] || { echo "error: --fastvideo is required" >&2; usage >&2; exit 2; }
FORMATS="${FORMATS//,/ }"
for f in $FORMATS; do [[ "$(fmt_gb "$f")" != 0 ]] || { echo "error: unknown format '$f' (int6|int8|bf16)" >&2; exit 2; }; done
abspath() { case "$1" in /*) echo "${1%/}";; *) echo "$PWD/${1%/}";; esac; }
MODEL_DIR="$(abspath "$MODEL_DIR")"; FASTVIDEO="$(abspath "$FASTVIDEO")"
[[ -n "$SOURCES" ]] || SOURCES="$(dirname "$MODEL_DIR")/fasth3-sources"
SOURCES="$(abspath "$SOURCES")"
S_OMNI="$SOURCES/omni-bf16"; S_QAD="$SOURCES/nvfp4-qad"; S_DEC="$SOURCES/vae-decoders"; S_LVAE="$SOURCES/lightvae"
log() { printf '[%s] %s\n' "$(date +%H:%M:%S)" "$*"; }
warn() { printf '[%s] WARNING: %s\n' "$(date +%H:%M:%S)" "$*" >&2; WARNINGS+=("$*"); }
die() { printf '[%s] ERROR: %s\n' "$(date +%H:%M:%S)" "$*" >&2; exit 1; }
WARNINGS=(); declare -a TIMINGS=()
run() { if [[ $DRY_RUN -eq 1 ]]; then echo " (dry-run) $*"; else "$@"; fi; }
helper() { "$PYTHON" -I "$HELPERS" "$@"; }
timed() { # timed <label> <command...>; records elapsed time, preserves exit status
local label="$1" t0=$SECONDS rc=0; shift
"$@" || rc=$?
TIMINGS+=("$label: $((SECONDS - t0))s"); return $rc
}
# ---- Lock (opt-in) -------------------------------------------------------------
LOCK_HELD=""
lock_release() { if [[ -n "$LOCK_HELD" ]]; then rm -rf "$LOCK_HELD"; LOCK_HELD=""; fi; }
lock_acquire() {
[[ -n "$LOCK_DIR" && $DRY_RUN -eq 0 ]] || return 0
mkdir -p "$LOCK_DIR"
until mkdir "$LOCK_DIR/gpu.lock" 2>/dev/null; do
log "waiting for $LOCK_DIR/gpu.lock (owner pid $(cat "$LOCK_DIR/gpu.lock/owner" 2>/dev/null || echo ?)); retry in 30 s"
sleep 30
done
LOCK_HELD="$LOCK_DIR/gpu.lock"; echo $$ > "$LOCK_HELD/owner"; log "acquired $LOCK_HELD"
}
trap 'lock_release' EXIT
trap 'lock_release; exit 130' INT TERM
# ---- Preflight -------------------------------------------------------------------
free_gb() { df -Pk "$1" | awk 'NR==2 {printf "%d", $4/1048576}'; }
existing_parent() { local p="$1"; while [[ ! -d "$p" ]]; do p="$(dirname "$p")"; done; echo "$p"; }
need_space() { # need_space <dir> <GB needed> <stage label>
local dir; dir="$(existing_parent "$1")"; local free; free="$(free_gb "$dir")"
if (( free < $2 + RESERVE_GB )); then
local msg="not enough disk for $3: need ~$2 GB + $RESERVE_GB GB reserve, only $free GB free on $dir"
if [[ $DRY_RUN -eq 1 ]]; then warn "$msg"; else die "$msg (partial work is kept; free space and re-run)"; fi
fi
}
preflight() {
[[ "$(uname -s)" == Darwin && "$(uname -m)" == arm64 ]] || warn "not Apple Silicon macOS; MLX conversion will not run"
[[ -f "$FASTVIDEO/scripts/checkpoint_conversion/convert_minimax_h3_mlx.py" ]] \
|| die "$FASTVIDEO is not a FastVideo checkout with convert_minimax_h3_mlx.py (need branch aryan/omniref-mlx-speed)"
command -v "$PYTHON" >/dev/null || die "python not found: $PYTHON"
if [[ $DRY_RUN -eq 0 ]]; then
PYTHONPATH="$FASTVIDEO" "$PYTHON" -I -c 'import sys; sys.path.insert(0, sys.argv[1]); import mlx.core, fastvideo.mlx_runtime.minimax_h3_conditioner' "$FASTVIDEO" \
|| die "$PYTHON cannot import mlx and fastvideo; pass --python of the FastVideo venv"
fi
if [[ $SKIP_DOWNLOAD -eq 0 ]]; then
command -v hf >/dev/null || die "'hf' CLI not found; run: $PYTHON -m pip install -U huggingface_hub"
fi
local mem_gb; mem_gb=$(( $(sysctl -n hw.memsize 2>/dev/null || echo 0) / 1073741824 ))
log "total RAM: ${mem_gb} GB"
(( mem_gb >= 24 )) || warn "RAM below 24 GB; int6/int8 conversion (peak ~21 GB) may fail"
local f out_gb=$((OUT_FIXED_GB + ENC_GB)) fmts=""
for f in $FORMATS; do
if [[ $f == bf16 ]] && (( mem_gb < 64 )); then
warn "skipping bf16: conversion needs ~40 GB free RAM and this Mac has ${mem_gb} GB (<64)"; continue
fi
out_gb=$((out_gb + $(fmt_gb "$f"))); fmts+="$f "
done
FORMATS="${fmts% }"
local src_gb=0; [[ $SKIP_DOWNLOAD -eq 0 ]] && src_gb=$SRC_GB
if [[ "$(df -Pk "$(existing_parent "$SOURCES")" | awk 'NR==2{print $1}')" == "$(df -Pk "$(existing_parent "$MODEL_DIR")" | awk 'NR==2{print $1}')" ]]; then
log "sources and outputs share a disk: need ~$((src_gb + out_gb)) GB total"
need_space "$MODEL_DIR" $((src_gb + out_gb)) "whole build"
else
need_space "$MODEL_DIR" "$out_gb" "outputs"
[[ $src_gb -eq 0 ]] || need_space "$SOURCES" "$src_gb" "downloads"
fi
log "formats to build: ${FORMATS:-none}"
}
# ---- Stage: download ---------------------------------------------------------------
sources_ok() { # all required source files present and well-formed (quiet)
helper shards-check "$S_OMNI/transformer_ref" diffusion_pytorch_model >/dev/null 2>&1 || return 1
local f
for f in transformer_ref/config.json scheduler/scheduler_config.json audio_scheduler/scheduler_config.json fastvideo_inference.json; do
[[ -s "$S_OMNI/$f" ]] || return 1; done
helper shards-check "$S_QAD/text_encoder" model >/dev/null 2>&1 || return 1
for f in text_encoder/config.json text_encoder/model.safetensors.index.json audio_vae/config.json audio_vae/diffusion_pytorch_model.safetensors \
processor/tokenizer.json tokenizer/tokenizer.json; do [[ -s "$S_QAD/$f" ]] || return 1; done
helper st-check "$S_QAD/audio_vae/diffusion_pytorch_model.safetensors" "$S_DEC/quality-36L/decoder-nvfp4.safetensors" \
"$S_DEC/fast-8L/decoder-nvfp4.safetensors" "$S_LVAE/lightvae-lite-h3-encoder.safetensors" >/dev/null 2>&1 || return 1
}
hf_get() { # hf_get <repo> <rev> <dir> <include...>
local repo="$1" rev="$2" dir="$3"; shift 3
local inc=(); local p; for p in "$@"; do inc+=(--include "$p"); done
mkdir -p "$dir"
log "hf download $repo @ ${rev:0:10}"
run hf download "$repo" --revision "$rev" "${inc[@]}" --local-dir "$dir"
}
stage_download() {
if [[ $SKIP_DOWNLOAD -eq 1 ]]; then
log "download: skipped (--skip-download); checking $SOURCES"
[[ $DRY_RUN -eq 1 ]] || sources_ok || die "sources under $SOURCES are incomplete (need omni-bf16/ nvfp4-qad/ vae-decoders/ lightvae/)"
return
fi
if [[ $DRY_RUN -eq 0 ]] && sources_ok; then log "download: all sources present and valid, skipping"; return; fi
need_space "$SOURCES" "$SRC_GB" "downloads"
hf_get "$REPO_OMNI" "$REV_OMNI" "$S_OMNI" 'transformer_ref/*' 'scheduler/*' 'audio_scheduler/*' fastvideo_inference.json
hf_get "$REPO_QAD" "$REV_QAD" "$S_QAD" 'text_encoder/*' 'processor/*' 'tokenizer/*' 'audio_vae/*'
hf_get "$REPO_DEC" "$REV_DEC" "$S_DEC" quality-36L/decoder-nvfp4.safetensors fast-8L/decoder-nvfp4.safetensors
hf_get "$REPO_LVAE" "$REV_LVAE" "$S_LVAE" lightvae-lite-h3-encoder.safetensors
[[ $DRY_RUN -eq 1 ]] || sources_ok || die "downloads finished but source validation failed; re-run to resume"
}
# ---- Stage: assemble root/, vae/, licenses ---------------------------------------------
copy_file() { # atomic, skip when identical
local src="$1" dst="$2"
[[ -f "$src" ]] || die "missing source file: $src"
if [[ -f "$dst" ]] && cmp -s "$src" "$dst"; then return 0; fi
mkdir -p "$(dirname "$dst")"
local tmp="$dst.partial.$$"; cp "$src" "$tmp" && mv -f "$tmp" "$dst"
}
copy_dir() { # copy_dir <src dir> <dst dir>: regular files only (skip AppleDouble / .cache)
local src="$1" dst="$2" f
[[ -d "$src" ]] || die "missing source dir: $src"
while IFS= read -r f; do copy_file "$src/$f" "$dst/$f"; done \
< <(cd "$src" && find -L . -type f ! -name '._*' ! -name '.DS_Store' ! -path './.cache/*' ! -name '*.metadata' | sed 's#^\./##' | sort)
}
stage_assemble() {
[[ $DRY_RUN -eq 0 ]] || { log "assemble: would copy root/ configs, vae/ weights, licenses and derive root/vae/config.json"; return 0; }
local r="$MODEL_DIR/root"
log "assembling root/, vae/, licenses"
copy_file "$S_OMNI/fastvideo_inference.json" "$r/fastvideo_inference.json"
copy_dir "$S_OMNI/scheduler" "$r/scheduler"
copy_dir "$S_OMNI/audio_scheduler" "$r/audio_scheduler"
copy_file "$S_OMNI/transformer_ref/config.json" "$r/transformer_ref/config.json"
copy_dir "$S_QAD/audio_vae" "$r/audio_vae"
copy_dir "$S_QAD/processor" "$r/processor"
copy_dir "$S_QAD/tokenizer" "$r/tokenizer"
copy_file "$S_DEC/quality-36L/decoder-nvfp4.safetensors" "$MODEL_DIR/vae/fasth3-quality-36L-decoder-nvfp4.safetensors"
copy_file "$S_DEC/fast-8L/decoder-nvfp4.safetensors" "$MODEL_DIR/vae/fasth3-fast-8L-decoder-nvfp4.safetensors"
copy_file "$S_LVAE/lightvae-lite-h3-encoder.safetensors" "$MODEL_DIR/vae/lightvae-lite-h3-encoder.safetensors"
helper vae-config "$S_LVAE/lightvae-lite-h3-encoder.safetensors" "$r/vae/config.json"
local l; for l in LICENSE LICENSE-Qwen3-VL NOTICE; do copy_file "$KIT_DIR/licenses/$l" "$MODEL_DIR/$l"; done
}
# ---- Stage: DiT conversion (sequential) -------------------------------------------------
convert_one() { # convert_one <fmt>
local fmt="$1" out="$MODEL_DIR/dit/$1" stage="$MODEL_DIR/dit/.partial-$1"
if [[ $DRY_RUN -eq 1 ]]; then
echo " (dry-run) (cd $FASTVIDEO && PYTHONPATH=$FASTVIDEO $PYTHON scripts/checkpoint_conversion/convert_minimax_h3_mlx.py --model-root $S_OMNI/transformer_ref --out $stage --formats $fmt --include-vsa) # then mv to $out"
return 0
fi
if [[ -s "$out/mlx_h3_dit.json" ]] && helper st-check "$out/mlx_h3_dit.safetensors" >/dev/null 2>&1; then
log "dit/$fmt: already converted and well-formed, skipping"; return 0
fi
need_space "$MODEL_DIR" "$(fmt_gb "$fmt")" "dit/$fmt conversion"
rm -rf "$out" "$stage" # incomplete or corrupt earlier attempt
lock_acquire
log "converting dit/$fmt (this takes a while)"
(cd "$FASTVIDEO" && run env PYTHONPATH="$FASTVIDEO" "$PYTHON" scripts/checkpoint_conversion/convert_minimax_h3_mlx.py \
--model-root "$S_OMNI/transformer_ref" --out "$stage" --formats "$fmt" --include-vsa)
if [[ $DRY_RUN -eq 0 ]]; then
helper st-check "$stage/$fmt/mlx_h3_dit.safetensors" || die "dit/$fmt conversion produced an invalid file"
mkdir -p "$MODEL_DIR/dit"; mv "$stage/$fmt" "$out"; rm -rf "$stage"
fi
lock_release
}
stage_dit() { local f; for f in $FORMATS; do timed "dit/$f" convert_one "$f"; done; }
# ---- Stage: NVFP4 encoder cache -----------------------------------------------------------
stage_encoder() {
local out="$MODEL_DIR/root/text_encoder"
if [[ $DRY_RUN -eq 1 ]]; then
echo " (dry-run) $PYTHON -c 'export_mlx_h3_nvfp4_encoder($S_QAD/text_encoder, $out)'; then set source_dir=$ENCODER_SOURCE_FIELD"
return 0
fi
if [[ -s "$out/model.safetensors" && -s "$out/mlx_h3_nvfp4_encoder.json" ]] \
&& helper st-check "$out/model.safetensors" >/dev/null 2>&1; then
log "text_encoder cache: present and well-formed, skipping export"
else
need_space "$MODEL_DIR" "$ENC_GB" "encoder export"
rm -rf "$out" "$MODEL_DIR"/root/.text_encoder.partial-* # export refuses a non-empty target
lock_acquire
log "exporting NVFP4 encoder cache (format v2)"
run env PYTHONPATH="$FASTVIDEO" "$PYTHON" -c '
import sys
from fastvideo.mlx_runtime.minimax_h3_conditioner import export_mlx_h3_nvfp4_encoder
export_mlx_h3_nvfp4_encoder(sys.argv[1], sys.argv[2])' "$S_QAD/text_encoder" "$out"
lock_release
fi
# Make the manifest independent of where sources lived (same edit as our reference build).
[[ $DRY_RUN -eq 1 ]] || helper rewrite-source "$out/mlx_h3_nvfp4_encoder.json" "$ENCODER_SOURCE_FIELD"
}
# ---- Stage: verify ----------------------------------------------------------------------------
stage_verify() {
[[ $DRY_RUN -eq 0 ]] || { log "verify: skipped in dry-run"; return 0; }
local exp; exp="$(mktemp)"; printf '%s\n' "$EXPECTED_SHA256" > "$exp"
log "verifying SHA-256 against the reference build"
local rc=0; helper verify "$MODEL_DIR" "$exp" $FORMATS || rc=$?
rm -f "$exp"; return $rc
}
# ---- Main -------------------------------------------------------------------------------------------
log "model dir: $MODEL_DIR | sources: $SOURCES | python: $PYTHON | formats: $FORMATS"
preflight
timed download stage_download
timed assemble stage_assemble
stage_dit
timed encoder stage_encoder
VERIFY_RC=0; timed verify stage_verify || VERIFY_RC=$?
if [[ $VERIFY_RC -eq 0 && $SKIP_DOWNLOAD -eq 0 && $KEEP_SOURCES -eq 0 && $DRY_RUN -eq 0 ]]; then
log "removing downloaded sources $SOURCES (use --keep-sources to retain)"; rm -rf "$SOURCES"
fi
echo; echo "================ SUMMARY ================"
printf 'timings:\n'; printf ' %s\n' ${TIMINGS[@]+"${TIMINGS[@]}"}
echo "model dir: $MODEL_DIR"
(( ${#WARNINGS[@]} == 0 )) || { echo "warnings:"; printf ' - %s\n' "${WARNINGS[@]}"; }
if [[ $DRY_RUN -eq 1 ]]; then echo "dry-run: nothing was downloaded or converted"; exit 0; fi
if [[ $VERIFY_RC -ne 0 ]]; then
echo "RESULT: VERIFICATION FAILED. Downloaded-file hash mismatches mean a corrupt/changed download:"
echo "delete the named files under $SOURCES and $MODEL_DIR and re-run."; exit 1
fi
echo "RESULT: OK"
FIRST_FMT="${FORMATS%% *}"
cat <<EOF
Next, from $KIT_DIR (use the FastVideo venv python):
$PYTHON run_matrix.py --self-test --data-root ~/omniref-handoff
$PYTHON run_matrix.py --smoke --model-dir "$MODEL_DIR" --fastvideo-root "$FASTVIDEO" --data-root ~/omniref-handoff
$PYTHON run_matrix.py --model-dir "$MODEL_DIR" --fastvideo-root "$FASTVIDEO" --data-root ~/omniref-handoff --out ./results-mystudio
(DiT format used by default: ${FIRST_FMT:-int6}; see run_matrix.py --help for selecting int8/bf16.)
EOF