sskj-h3/vbench-base/scripts/run_h3_vbench_base_tp2x4.sh

348 lines
15 KiB
Bash
Executable File

#!/usr/bin/env bash
# MiniMax-H3 dense baseline: 4 replicas x TP=2, VBench-v1 standard suite.
# Default paper-oriented protocol follows LoSA: full suite, one fixed seed.
set -Eeuo pipefail
ACTION=${ACTION:-all} # preflight | generate | evaluate | all
RUN_ID=${RUN_ID:-dense-tp2x4-$(date '+%Y%m%d-%H%M%S')}
RESULT_ROOT=${RESULT_ROOT:-/data/wxy/results/h3_vbench_base/$RUN_ID}
MODEL=${MODEL:-/data/hf_models/MiniMax-H3}
SGLANG_PYTHON=${SGLANG_PYTHON:-/root/.miniconda3/envs/sglang/bin/python}
SGLANG_BIN=${SGLANG_BIN:-/root/.miniconda3/envs/sglang/bin/sglang}
MEDIA_BIN_DIR=${MEDIA_BIN_DIR:-/root/.miniconda3/envs/deploy/bin}
CLIENT_SCRIPT=${CLIENT_SCRIPT:-/data/wxy/h3_vbench_generate.py}
VBENCH_REPO=${VBENCH_REPO:-/data/wxy/VBench}
VBENCH_PYTHON=${VBENCH_PYTHON:-/root/.miniconda3/envs/vbench/bin/python}
VBENCH_METADATA=${VBENCH_METADATA:-$VBENCH_REPO/vbench/VBench_full_info.json}
VBENCH_SAMPLE_DATA=${VBENCH_SAMPLE_DATA:-/data/datasets/vbench}
EVAL_GPUS=${EVAL_GPUS:-8}
RUN_STATIC_FILTER=${RUN_STATIC_FILTER:-1} # Official temporal-flickering preprocessing.
HOST=${HOST:-127.0.0.1}
PORTS=${PORTS:-30010,30020,30030,30040}
PORT_STRIDE=${PORT_STRIDE:-10}
MASTER_PORT_BASE=${MASTER_PORT_BASE:-31000}
SCHEDULER_PORT_BASE=${SCHEDULER_PORT_BASE:-32000}
SERVER_START_TIMEOUT=${SERVER_START_TIMEOUT:-1800}
GPU_MEMORY_LIMIT_MB=${GPU_MEMORY_LIMIT_MB:-1000}
NUM_INFERENCE_STEPS=${NUM_INFERENCE_STEPS:-20}
DURATION_SECONDS=${DURATION_SECONDS:-5.0}
SHORT_EDGE=${SHORT_EDGE:-768}
ASPECT_RATIO=${ASPECT_RATIO:-16:9}
SAMPLES_PER_PROMPT=${SAMPLES_PER_PROMPT:-1} # LoSA-style fixed seed; set 5 for strict VBench protocol.
MAX_PROMPTS=${MAX_PROMPTS:-0} # 0 = full standard suite; >0 = smoke only, not paper-comparable.
SEED=${SEED:-1101}
WARMUP_REQUESTS=${WARMUP_REQUESTS:-1}
WARMUP_INFERENCE_STEPS=${WARMUP_INFERENCE_STEPS:-5}
FLOW_SHIFT=${FLOW_SHIFT:-12.0}
AUDIO_FLOW_SHIFT=${AUDIO_FLOW_SHIFT:-3.0}
DIMENSIONS=(
subject_consistency background_consistency temporal_flickering motion_smoothness
dynamic_degree aesthetic_quality imaging_quality object_class multiple_objects
human_action color spatial_relationship scene temporal_style appearance_style
overall_consistency
)
DIMENSIONS_CSV=$(IFS=,; printf '%s' "${DIMENSIONS[*]}")
IFS=, read -r -a PORT_ARRAY <<< "$PORTS"
[[ "${#PORT_ARRAY[@]}" -eq 4 ]] || { printf 'PORTS must contain exactly four ports\n' >&2; exit 1; }
VIDEOS_DIR="$RESULT_ROOT/videos"
GENERATION_DIR="$RESULT_ROOT/generation"
EVAL_DIR="$RESULT_ROOT/evaluation"
SERVER_DIR="$RESULT_ROOT/servers"
MANIFEST="$GENERATION_DIR/manifest.jsonl"
RESULTS="$GENERATION_DIR/results.jsonl"
GENERATION_SUMMARY="$GENERATION_DIR/summary.json"
RUN_CONFIG="$RESULT_ROOT/run_config.env"
declare -a SERVER_PIDS=()
declare -a CLIENT_PIDS=()
log() { printf '[%s] %s\n' "$(date '+%F %T')" "$*"; }
die() { log "ERROR: $*" >&2; exit 1; }
port_is_open() {
"$SGLANG_PYTHON" - "$HOST" "$1" <<'PY'
import socket, sys
s = socket.socket(); s.settimeout(0.5)
try: s.connect((sys.argv[1], int(sys.argv[2])))
except OSError: raise SystemExit(1)
else: raise SystemExit(0)
finally: s.close()
PY
}
validate_vbench_layout() {
[[ -f "$VBENCH_METADATA" ]] || {
if [[ -d "$VBENCH_SAMPLE_DATA" ]]; then
die "$VBENCH_METADATA is missing. $VBENCH_SAMPLE_DATA is the VBench-2.0 sample-video dataset, not the VBench-v1 evaluator repository. Clone/install official VBench and set VBENCH_REPO."
fi
die "VBench-v1 metadata missing: $VBENCH_METADATA"
}
}
check_gpu_idle() {
command -v nvidia-smi >/dev/null || die "nvidia-smi not found"
local gpu_count used
gpu_count=$(nvidia-smi --query-gpu=index --format=csv,noheader | wc -l)
[[ "$gpu_count" -eq 8 ]] || die "expected 8 GPUs, found $gpu_count"
while IFS= read -r used; do
used=${used// /}
((used <= GPU_MEMORY_LIMIT_MB)) || die "GPU memory is already in use (${used} MiB > ${GPU_MEMORY_LIMIT_MB} MiB); refusing to touch unrelated processes"
done < <(nvidia-smi --query-gpu=memory.used --format=csv,noheader,nounits)
}
preflight_generation() {
[[ -x "$SGLANG_PYTHON" ]] || die "SGLang python not executable: $SGLANG_PYTHON"
[[ -x "$SGLANG_BIN" ]] || die "SGLang executable missing: $SGLANG_BIN"
[[ -x "$MEDIA_BIN_DIR/ffmpeg" ]] || die "ffmpeg missing: $MEDIA_BIN_DIR/ffmpeg"
[[ -x "$MEDIA_BIN_DIR/ffprobe" ]] || die "ffprobe missing: $MEDIA_BIN_DIR/ffprobe"
[[ -f "$CLIENT_SCRIPT" ]] || die "generation client missing: $CLIENT_SCRIPT"
[[ -d "$MODEL" ]] || die "model missing: $MODEL"
validate_vbench_layout
check_gpu_idle
local replica candidate port
for ((replica=0; replica<4; ++replica)); do
port=${PORT_ARRAY[$replica]}
for candidate in "$port" "$((port + 1))" "$((MASTER_PORT_BASE + replica * PORT_STRIDE))" "$((SCHEDULER_PORT_BASE + replica * PORT_STRIDE))"; do
port_is_open "$candidate" && die "port already in use: $candidate"
done
done
return 0
}
preflight_evaluation() {
validate_vbench_layout
[[ -f "$VBENCH_REPO/evaluate.py" ]] || die "official evaluate.py missing under VBENCH_REPO=$VBENCH_REPO"
[[ -x "$VBENCH_PYTHON" ]] || die "VBench python not executable: $VBENCH_PYTHON"
(
cd "$VBENCH_REPO"
"$VBENCH_PYTHON" -c 'import torch, vbench'
) || die "VBENCH_PYTHON cannot import torch and vbench from $VBENCH_REPO"
((EVAL_GPUS >= 1 && EVAL_GPUS <= 8)) || die "EVAL_GPUS must be in [1,8]"
if ((RUN_STATIC_FILTER)); then
[[ -f "$VBENCH_REPO/static_filter.py" ]] || die "official static_filter.py missing under VBENCH_REPO=$VBENCH_REPO"
fi
}
stop_servers() {
local pid alive deadline
((${#SERVER_PIDS[@]})) || return 0
log "stopping only the ${#SERVER_PIDS[@]} server process groups started by this run"
for pid in "${SERVER_PIDS[@]}"; do kill -INT "$pid" 2>/dev/null || true; done
deadline=$((SECONDS + 120))
while ((SECONDS < deadline)); do
alive=0
for pid in "${SERVER_PIDS[@]}"; do kill -0 "$pid" 2>/dev/null && alive=1; done
((alive == 0)) && break
sleep 2
done
for pid in "${SERVER_PIDS[@]}"; do
if kill -0 "$pid" 2>/dev/null; then
kill -TERM -- "-$pid" 2>/dev/null || kill -TERM "$pid" 2>/dev/null || true
sleep 5
kill -KILL -- "-$pid" 2>/dev/null || kill -KILL "$pid" 2>/dev/null || true
fi
wait "$pid" 2>/dev/null || true
done
SERVER_PIDS=()
}
cleanup() {
local rc=$?
trap - EXIT INT TERM
stop_servers
exit "$rc"
}
trap cleanup EXIT INT TERM
wait_healthy() {
local port=$1 pid=$2 log_file=$3 deadline=$((SECONDS + SERVER_START_TIMEOUT))
while ((SECONDS < deadline)); do
curl -fsS --max-time 5 "http://${HOST}:${port}/health" >/dev/null 2>&1 && return 0
if ! kill -0 "$pid" 2>/dev/null; then tail -100 "$log_file" >&2 || true; return 1; fi
sleep 5
done
tail -100 "$log_file" >&2 || true
return 1
}
start_servers() {
local replica port master_port scheduler_port gpu_csv server_log
mkdir -p "$SERVER_DIR"
for ((replica=0; replica<4; ++replica)); do
port=${PORT_ARRAY[$replica]}
master_port=$((MASTER_PORT_BASE + replica * PORT_STRIDE))
scheduler_port=$((SCHEDULER_PORT_BASE + replica * PORT_STRIDE))
gpu_csv="$((replica * 2)),$((replica * 2 + 1))"
mkdir -p "$SERVER_DIR/replica${replica}_port${port}/outputs"
server_log="$SERVER_DIR/replica${replica}_port${port}/server.log"
printf '%s\n' "$gpu_csv" > "$SERVER_DIR/replica${replica}_port${port}/cuda_visible_devices.txt"
log "starting FL2VA replica=$replica TP=2 GPUs=$gpu_csv port=$port"
PATH="$MEDIA_BIN_DIR:$PATH" CUDA_VISIBLE_DEVICES="$gpu_csv" PYTHONUNBUFFERED=1 TOKENIZERS_PARALLELISM=false \
SGLANG_USE_RUNAI_MODEL_STREAMER=false setsid "$SGLANG_BIN" serve \
--model-path "$MODEL" --model-variant FL2VA --backend sglang --performance-mode speed \
--num-gpus 2 --tp-size 2 --ulysses-degree 1 --use-fsdp-inference false \
--enable-torch-compile false --batching-max-size 1 --batching-delay-ms 0 \
--host 0.0.0.0 --port "$port" --master-port "$master_port" --scheduler-port "$scheduler_port" \
--output-path "$SERVER_DIR/replica${replica}_port${port}/outputs" >"$server_log" 2>&1 &
SERVER_PIDS+=("$!")
printf '%s\n' "$!" > "$SERVER_DIR/replica${replica}_port${port}/server.pid"
done
for ((replica=0; replica<4; ++replica)); do
port=${PORT_ARRAY[$replica]}
server_log="$SERVER_DIR/replica${replica}_port${port}/server.log"
wait_healthy "$port" "${SERVER_PIDS[$replica]}" "$server_log" || die "replica=$replica failed startup"
log "replica=$replica healthy port=$port"
done
return 0
}
write_config() {
mkdir -p "$RESULT_ROOT" "$GENERATION_DIR" "$VIDEOS_DIR" "$EVAL_DIR"
{
printf 'RUN_ID=%q\n' "$RUN_ID"
printf 'MODEL=%q\n' "$MODEL"
printf 'TOPOLOGY=%q\n' 'TP2x4'
printf 'PORTS=%q\n' "$PORTS"
printf 'VBENCH_REPO=%q\n' "$VBENCH_REPO"
printf 'VBENCH_METADATA=%q\n' "$VBENCH_METADATA"
printf 'NUM_INFERENCE_STEPS=%q\n' "$NUM_INFERENCE_STEPS"
printf 'DURATION_SECONDS=%q\n' "$DURATION_SECONDS"
printf 'SHORT_EDGE=%q\n' "$SHORT_EDGE"
printf 'ASPECT_RATIO=%q\n' "$ASPECT_RATIO"
printf 'SAMPLES_PER_PROMPT=%q\n' "$SAMPLES_PER_PROMPT"
printf 'MAX_PROMPTS=%q\n' "$MAX_PROMPTS"
printf 'SEED=%q\n' "$SEED"
printf 'RUN_STATIC_FILTER=%q\n' "$RUN_STATIC_FILTER"
printf 'DIMENSIONS=%q\n' "$DIMENSIONS_CSV"
} > "$RUN_CONFIG"
}
generate() {
preflight_generation
write_config
start_servers
log "generation begins: full_suite=$([[ "$MAX_PROMPTS" -eq 0 ]] && printf true || printf false) samples_per_prompt=$SAMPLES_PER_PROMPT"
"$SGLANG_PYTHON" "$CLIENT_SCRIPT" \
--metadata "$VBENCH_METADATA" --videos-dir "$VIDEOS_DIR" --results "$RESULTS" \
--manifest "$MANIFEST" --summary "$GENERATION_SUMMARY" --host "$HOST" --ports "$PORTS" \
--model "$MODEL" --dimensions "$DIMENSIONS_CSV" --samples-per-prompt "$SAMPLES_PER_PROMPT" \
--max-prompts "$MAX_PROMPTS" --num-inference-steps "$NUM_INFERENCE_STEPS" \
--short-edge "$SHORT_EDGE" --duration-seconds "$DURATION_SECONDS" --aspect-ratio "$ASPECT_RATIO" \
--seed "$SEED" --warmup-requests "$WARMUP_REQUESTS" --warmup-inference-steps "$WARMUP_INFERENCE_STEPS" \
--flow-shift "$FLOW_SHIFT" --audio-flow-shift "$AUDIO_FLOW_SHIFT" \
> "$GENERATION_DIR/client.log" 2>&1
stop_servers
log "generation complete: $GENERATION_SUMMARY"
}
evaluate() {
preflight_evaluation
[[ -f "$GENERATION_SUMMARY" ]] || die "generation summary missing: $GENERATION_SUMMARY"
"$SGLANG_PYTHON" - "$GENERATION_SUMMARY" <<'PY'
import json, sys
summary = json.load(open(sys.argv[1]))
if summary["failed_or_missing"]:
raise SystemExit(f"generation is incomplete: {summary}")
PY
if ((MAX_PROMPTS > 0)); then
die "MAX_PROMPTS=$MAX_PROMPTS is a smoke subset; VBench standard scores would be incomplete and misleading"
fi
mkdir -p "$EVAL_DIR/raw_main"
local result_json flicker_json score_zip
if ((RUN_STATIC_FILTER)); then
local main_dimensions=(
subject_consistency background_consistency motion_smoothness dynamic_degree
aesthetic_quality imaging_quality object_class multiple_objects human_action color
spatial_relationship scene temporal_style appearance_style overall_consistency
)
log "VBench-v1 evaluates 15 dimensions on $EVAL_GPUS GPUs"
(
cd "$VBENCH_REPO"
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 "$VBENCH_PYTHON" -m torch.distributed.run \
--nproc_per_node="$EVAL_GPUS" --standalone evaluate.py \
--videos_path "$VIDEOS_DIR" --output_path "$EVAL_DIR/raw_main" \
--full_json_dir "$VBENCH_METADATA" --mode vbench_standard \
--dimension "${main_dimensions[@]}"
) > "$EVAL_DIR/evaluate_main.log" 2>&1
log "running official static filter before temporal_flickering"
(
cd "$VBENCH_REPO"
CUDA_VISIBLE_DEVICES=0 "$VBENCH_PYTHON" static_filter.py \
--videos_path "$VIDEOS_DIR" --result_path "$EVAL_DIR/static_filter"
) > "$EVAL_DIR/static_filter.log" 2>&1
[[ -n "$(find "$EVAL_DIR/static_filter/filtered_videos" -maxdepth 1 -type f -name '*.mp4' -print -quit 2>/dev/null)" ]] || \
die "static filter produced no videos; inspect $EVAL_DIR/static_filter.log"
mkdir -p "$EVAL_DIR/raw_flicker"
(
cd "$VBENCH_REPO"
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 "$VBENCH_PYTHON" -m torch.distributed.run \
--nproc_per_node="$EVAL_GPUS" --standalone evaluate.py \
--videos_path "$EVAL_DIR/static_filter/filtered_videos" --output_path "$EVAL_DIR/raw_flicker" \
--full_json_dir "$VBENCH_METADATA" --mode vbench_standard --dimension temporal_flickering
) > "$EVAL_DIR/evaluate_flicker.log" 2>&1
result_json=$(find "$EVAL_DIR/raw_main" -maxdepth 1 -type f -name '*_eval_results.json' -print -quit)
flicker_json=$(find "$EVAL_DIR/raw_flicker" -maxdepth 1 -type f -name '*_eval_results.json' -print -quit)
[[ -n "$result_json" && -n "$flicker_json" ]] || die "VBench result JSON missing; inspect evaluation logs"
"$VBENCH_PYTHON" - "$result_json" "$flicker_json" "$EVAL_DIR/vbench_16_dimensions.json" <<'PY'
import json, sys
with open(sys.argv[1], encoding="utf-8") as handle:
result = json.load(handle)
with open(sys.argv[2], encoding="utf-8") as handle:
result.update(json.load(handle))
with open(sys.argv[3], "w", encoding="utf-8") as handle:
json.dump(result, handle, ensure_ascii=False, indent=2)
PY
else
log "VBench-v1 evaluates all 16 dimensions without static filtering (non-standard flicker preprocessing)"
(
cd "$VBENCH_REPO"
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7 "$VBENCH_PYTHON" -m torch.distributed.run \
--nproc_per_node="$EVAL_GPUS" --standalone evaluate.py \
--videos_path "$VIDEOS_DIR" --output_path "$EVAL_DIR/raw_main" \
--full_json_dir "$VBENCH_METADATA" --mode vbench_standard \
--dimension "${DIMENSIONS[@]}"
) > "$EVAL_DIR/evaluate_main.log" 2>&1
result_json=$(find "$EVAL_DIR/raw_main" -maxdepth 1 -type f -name '*_eval_results.json' -print -quit)
[[ -n "$result_json" ]] || die "VBench produced no *_eval_results.json; inspect $EVAL_DIR/evaluate_main.log"
cp "$result_json" "$EVAL_DIR/vbench_16_dimensions.json"
fi
score_zip="$EVAL_DIR/evaluation_results.zip"
"$VBENCH_PYTHON" - "$EVAL_DIR/vbench_16_dimensions.json" "$score_zip" <<'PY'
import os, sys, zipfile
source, destination = sys.argv[1:]
with zipfile.ZipFile(destination, "w", compression=zipfile.ZIP_DEFLATED) as archive:
archive.write(source, arcname=os.path.basename(source))
PY
(
cd "$EVAL_DIR"
"$VBENCH_PYTHON" "$VBENCH_REPO/scripts/cal_final_score.py" \
--zip_file "$score_zip" --model_name aggregate_input
) > "$EVAL_DIR/final_score.txt" 2>&1
log "evaluation complete: $EVAL_DIR/vbench_16_dimensions.json and $EVAL_DIR/final_score.txt"
}
case "$ACTION" in
preflight)
preflight_generation
log "generation preflight passed"
if [[ -x "$VBENCH_PYTHON" && -f "$VBENCH_REPO/evaluate.py" ]]; then
preflight_evaluation
log "evaluation preflight passed"
else
log "evaluation preflight skipped: VBench code/env is not ready"
fi
;;
generate) generate ;;
evaluate) write_config; evaluate ;;
all) generate; evaluate ;;
*) die "unknown ACTION=$ACTION (use preflight, generate, evaluate, or all)" ;;
esac
trap - EXIT INT TERM
log "done: $RESULT_ROOT"