Image-Text-to-Text
Transformers
Safetensors
qwen3_5
vllm
video
multimodal
reinforcement-learning
temporal-grounding
object-tracking
video-segmentation
visual-question-answering
spatial-reasoning
qwen3.5
conversational
Instructions to use OraRL/Video-ORA-9B with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use OraRL/Video-ORA-9B with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="OraRL/Video-ORA-9B") messages = [ { "role": "user", "content": [ {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"}, {"type": "text", "text": "What animal is on the candy?"} ] }, ] pipe(text=messages)# Load model directly from transformers import AutoProcessor, AutoModelForMultimodalLM processor = AutoProcessor.from_pretrained("OraRL/Video-ORA-9B") model = AutoModelForMultimodalLM.from_pretrained("OraRL/Video-ORA-9B", device_map="auto") messages = [ { "role": "user", "content": [ {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"}, {"type": "text", "text": "What animal is on the candy?"} ] }, ] inputs = processor.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=40) print(processor.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use OraRL/Video-ORA-9B with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "OraRL/Video-ORA-9B" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "OraRL/Video-ORA-9B", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker
docker model run hf.co/OraRL/Video-ORA-9B
- SGLang
How to use OraRL/Video-ORA-9B with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "OraRL/Video-ORA-9B" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "OraRL/Video-ORA-9B", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "OraRL/Video-ORA-9B" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "OraRL/Video-ORA-9B", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }' - Docker Model Runner
How to use OraRL/Video-ORA-9B with Docker Model Runner:
docker model run hf.co/OraRL/Video-ORA-9B
| # ============================================================================= | |
| # Segmentation evaluation — vLLM inference + optional SAM2 post-processing. | |
| # | |
| # Usage: | |
| # bash eval/task/segmentation/run_eval_vllm.sh [MODEL_PATH] [OUTPUT_DIR] | |
| # | |
| # Common overrides: | |
| # BENCH_DIR=/path/to/OneThinker-eval DATASETS=reasonseg-val DATA_ROOT=/path/to/OneThinker-eval \ | |
| # RUN_SAM2=true SAM2_CKPT=/path/to/sam2.1_hiera_large.pt SAM2_CFG=/path/to/sam2.1_hiera_l.yaml \ | |
| # bash eval/task/segmentation/run_eval_vllm.sh /path/to/ckpt | |
| # ============================================================================= | |
| set -eo pipefail | |
| SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" | |
| PROJECT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)" | |
| # ---------- paths ---------- | |
| MODEL_PATH="${1:-}" | |
| PROCESSOR_PATH="${PROCESSOR_PATH:-$MODEL_PATH}" | |
| BENCH_DIR="${BENCH_DIR:-${PROJECT_DIR}/data/eval/segmentation}" | |
| DATA_ROOT="${DATA_ROOT:-$BENCH_DIR}" | |
| DATASETS="${DATASETS:-eval_seg_refcoco,eval_seg_refcocop,eval_seg_refcocog,eval_seg_mevis,eval_seg_reasonvos}" | |
| if [[ -z "$MODEL_PATH" || ! -d "$MODEL_PATH" ]]; then | |
| echo "ERROR: MODEL_PATH must name an existing model directory." >&2 | |
| exit 2 | |
| fi | |
| if [[ ! -d "$BENCH_DIR" || ! -d "$DATA_ROOT" ]]; then | |
| echo "ERROR: BENCH_DIR and DATA_ROOT must name existing directories." >&2 | |
| exit 2 | |
| fi | |
| # ---------- eval settings ---------- | |
| DATA_TYPE="${DATA_TYPE:-all}" # all | image | video | |
| PROMPT_MODE="${PROMPT_MODE:-train_seg}" # think | no_think | bare | onethink_system | train_seg | |
| ENABLE_THINKING="${ENABLE_THINKING:-false}" | |
| MAX_SAMPLES="${MAX_SAMPLES:-}" | |
| BATCH_SIZE="${BATCH_SIZE:-16}" | |
| MAX_NEW_TOKENS="${MAX_NEW_TOKENS:-1024}" | |
| MAX_PIXELS_IMAGE="${MAX_PIXELS_IMAGE:-1048576}" | |
| MIN_PIXELS_IMAGE="${MIN_PIXELS_IMAGE:-4096}" | |
| # Accept both the tracking-style VIDEO_* names and older MAX_PIXELS_VIDEO names. | |
| VIDEO_MAX_PIXELS="${VIDEO_MAX_PIXELS:-${MAX_PIXELS_VIDEO:-262144}}" | |
| VIDEO_MIN_PIXELS="${VIDEO_MIN_PIXELS:-${MIN_PIXELS_VIDEO:-4096}}" | |
| VIDEO_TOTAL_PIXELS="${VIDEO_TOTAL_PIXELS:-${TOTAL_PIXELS_VIDEO:-16777216}}" | |
| MAX_FRAMES="${MAX_FRAMES:-128}" | |
| FPS="${FPS:-2}" | |
| PATCH_SIZE="${PATCH_SIZE:-}" | |
| VIDEO_READER="${VIDEO_READER:-decord}" | |
| case "$VIDEO_READER" in | |
| decord|torchcodec|torchvision) ;; | |
| *) | |
| echo "ERROR: VIDEO_READER must be decord, torchcodec, or torchvision; got '$VIDEO_READER'" >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| export FORCE_QWENVL_VIDEO_READER="$VIDEO_READER" | |
| require_int() { | |
| local name="$1" | |
| local value="$2" | |
| if ! [[ "$value" =~ ^[0-9]+$ ]]; then | |
| echo "ERROR: $name must be a non-empty integer, got '$value'" | |
| exit 1 | |
| fi | |
| } | |
| require_int MAX_PIXELS_IMAGE "$MAX_PIXELS_IMAGE" | |
| require_int MIN_PIXELS_IMAGE "$MIN_PIXELS_IMAGE" | |
| require_int VIDEO_MAX_PIXELS "$VIDEO_MAX_PIXELS" | |
| require_int VIDEO_MIN_PIXELS "$VIDEO_MIN_PIXELS" | |
| require_int VIDEO_TOTAL_PIXELS "$VIDEO_TOTAL_PIXELS" | |
| require_int MAX_FRAMES "$MAX_FRAMES" | |
| require_int FPS "$FPS" | |
| require_int BATCH_SIZE "$BATCH_SIZE" | |
| require_int MAX_NEW_TOKENS "$MAX_NEW_TOKENS" | |
| # ---------- vLLM ---------- | |
| TP_SIZE="${TP_SIZE:-1}" | |
| GPU_MEM_UTIL="${GPU_MEM_UTIL:-0.85}" | |
| MAX_MODEL_LEN="${MAX_MODEL_LEN:-32768}" | |
| SEED="${SEED:-42}" | |
| VLLM_BASE_PORT="${VLLM_BASE_PORT:-}" | |
| RESUME_SHARDS="${RESUME_SHARDS:-false}" | |
| RETRY_FAILED_SHARDS="${RETRY_FAILED_SHARDS:-true}" | |
| # ---------- SAM2 ---------- | |
| RUN_SAM2="${RUN_SAM2:-false}" | |
| SAM2_CKPT="${SAM2_CKPT:-}" | |
| SAM2_CFG="${SAM2_CFG:-}" | |
| ONETHINKER_SEG_POST="${ONETHINKER_SEG_POST:-${PROJECT_DIR}/third_party/OneThinker/Evaluation/Eval/seg_post_sam2.py}" | |
| SAM2_NUM_GPUS="${SAM2_NUM_GPUS:-}" | |
| SAM2_WORKERS_PER_GPU="${SAM2_WORKERS_PER_GPU:-}" | |
| PRE_EXTRACT_THREADS="${PRE_EXTRACT_THREADS:-4}" | |
| # Each SAM2 epoch spawns `world_size` worker processes that EACH reload the SAM2 | |
| # model and then handle only their slice of the epoch. Total model loads = | |
| # world_size * num_epochs. A small epoch with many workers means tiny slices and | |
| # constant model reloading (the real bottleneck). Keep the epoch large so there | |
| # is effectively one epoch and each worker amortizes its model load over a big | |
| # contiguous slice. | |
| SAM2_EPOCH_SIZE="${SAM2_EPOCH_SIZE:-100000}" | |
| VIZ_RATIO="${VIZ_RATIO:-0.0}" | |
| # ---------- output ---------- | |
| MODEL_TAG=$(basename "${MODEL_PATH%/}") | |
| TIMESTAMP=$(date +%Y%m%d_%H%M%S) | |
| OUTPUT_DIR="${2:-${PROJECT_DIR}/outputs/segmentation/eval_seg_vllm-${MODEL_TAG}-${TIMESTAMP}}" | |
| # ---------- hardware ---------- | |
| NVJITLINK_LIB="$( | |
| python - <<'PY' | |
| import site | |
| from pathlib import Path | |
| roots = [*site.getsitepackages(), site.getusersitepackages()] | |
| for root in roots: | |
| candidate = Path(root) / "nvidia" / "nvjitlink" / "lib" | |
| if candidate.is_dir(): | |
| print(candidate) | |
| break | |
| PY | |
| )" | |
| if [[ -n "${NVJITLINK_LIB}" ]]; then | |
| export LD_LIBRARY_PATH="${NVJITLINK_LIB}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}" | |
| fi | |
| if ! python -c "import torch; print(f'PyTorch preflight: {torch.__version__} CUDA {torch.version.cuda}')"; then | |
| echo "ERROR: PyTorch CUDA libraries cannot be loaded in the active environment." >&2 | |
| exit 1 | |
| fi | |
| if [ -n "${CUDA_VISIBLE_DEVICES:-}" ]; then | |
| IFS="," read -ra GPULIST <<< "$CUDA_VISIBLE_DEVICES" | |
| else | |
| IFS="," read -ra GPULIST <<< "$(seq -s, 0 $(($(nvidia-smi -L | wc -l)-1)))" | |
| fi | |
| NUM_GPUS=${#GPULIST[@]} | |
| if (( NUM_GPUS % TP_SIZE != 0 )); then | |
| echo "ERROR: NUM_GPUS ($NUM_GPUS) must be divisible by TP_SIZE ($TP_SIZE)" | |
| exit 1 | |
| fi | |
| DP_SIZE=$(( NUM_GPUS / TP_SIZE )) | |
| if [[ -z "${VLLM_BASE_PORT}" ]]; then | |
| VLLM_BASE_PORT="$( | |
| python - "$DP_SIZE" <<'PY' | |
| import socket | |
| import sys | |
| count = int(sys.argv[1]) | |
| spacing = 16 | |
| for base in range(45000, 64000 - spacing * count, 128): | |
| sockets = [] | |
| try: | |
| for index in range(count): | |
| for offset in (0, 1): | |
| sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
| sock.bind(("127.0.0.1", base + index * spacing + offset)) | |
| sockets.append(sock) | |
| except OSError: | |
| for sock in sockets: | |
| sock.close() | |
| continue | |
| for sock in sockets: | |
| sock.close() | |
| print(base) | |
| break | |
| else: | |
| raise SystemExit("no free segmentation vLLM port block found") | |
| PY | |
| )" | |
| fi | |
| echo "==============================================" | |
| echo "Segmentation Evaluation (vLLM)" | |
| echo "==============================================" | |
| echo "Model: $MODEL_PATH" | |
| echo "Processor: $PROCESSOR_PATH" | |
| echo "Bench dir: $BENCH_DIR" | |
| echo "Data root: $DATA_ROOT" | |
| echo "Datasets: $DATASETS" | |
| echo "Data type: $DATA_TYPE" | |
| echo "Prompt: $PROMPT_MODE (enable_thinking=$ENABLE_THINKING)" | |
| echo "Video pix: min=$VIDEO_MIN_PIXELS max=$VIDEO_MAX_PIXELS total=$VIDEO_TOTAL_PIXELS frames=$MAX_FRAMES fps=$FPS" | |
| echo "Video reader:$VIDEO_READER" | |
| echo "GPUs: ${GPULIST[*]} (${NUM_GPUS} total, TP=${TP_SIZE}, DP=${DP_SIZE})" | |
| echo "Base port: $VLLM_BASE_PORT" | |
| echo "Output: $OUTPUT_DIR" | |
| echo "SAM2: $RUN_SAM2" | |
| echo "==============================================" | |
| mkdir -p "$OUTPUT_DIR" | |
| PY_ARGS=( | |
| --model_path "$MODEL_PATH" | |
| --processor_path "$PROCESSOR_PATH" | |
| --bench_dir "$BENCH_DIR" | |
| --datasets "$DATASETS" | |
| --output_dir "$OUTPUT_DIR" | |
| --base_prefix "$DATA_ROOT" | |
| --data_type "$DATA_TYPE" | |
| --prompt_mode "$PROMPT_MODE" | |
| --batch_size "$BATCH_SIZE" | |
| --max_new_tokens "$MAX_NEW_TOKENS" | |
| --tensor_parallel_size "$TP_SIZE" | |
| --gpu_memory_utilization "$GPU_MEM_UTIL" | |
| --max_model_len "$MAX_MODEL_LEN" | |
| --seed "$SEED" | |
| --max_pixels_image "$MAX_PIXELS_IMAGE" | |
| --min_pixels_image "$MIN_PIXELS_IMAGE" | |
| --max_pixels_video "$VIDEO_MAX_PIXELS" | |
| --min_pixels_video "$VIDEO_MIN_PIXELS" | |
| --total_pixels_video "$VIDEO_TOTAL_PIXELS" | |
| --max_frames "$MAX_FRAMES" | |
| --fps "$FPS" | |
| --skip_missing_media | |
| ) | |
| if [ -n "$MAX_SAMPLES" ]; then | |
| PY_ARGS+=(--max_samples "$MAX_SAMPLES") | |
| fi | |
| if [ -n "$PATCH_SIZE" ]; then | |
| PY_ARGS+=(--patch_size "$PATCH_SIZE") | |
| fi | |
| if [ "$ENABLE_THINKING" = "true" ]; then | |
| PY_ARGS+=(--enable_thinking) | |
| fi | |
| PIDS=() | |
| PID_SHARDS=() | |
| cleanup() { | |
| echo "" | |
| echo "Caught interrupt, killing workers ..." | |
| for pid in "${PIDS[@]}"; do | |
| kill -TERM "$pid" 2>/dev/null || true | |
| done | |
| wait 2>/dev/null || true | |
| exit 1 | |
| } | |
| trap cleanup INT TERM | |
| dataset_key() { | |
| python -c \ | |
| "from pathlib import Path; import sys; p=sys.argv[1]; print(Path(p).stem if Path(p).suffix else p)" \ | |
| "$1" | |
| } | |
| shard_is_complete() { | |
| local shard_id="$1" | |
| local dataset | |
| local key | |
| local datasets=() | |
| IFS=',' read -ra datasets <<< "$DATASETS" | |
| for dataset in "${datasets[@]}"; do | |
| key="$(dataset_key "$dataset")" | |
| if [[ ! -s "$OUTPUT_DIR/results_${key}_shard${shard_id}.json" ]]; then | |
| return 1 | |
| fi | |
| done | |
| return 0 | |
| } | |
| if [ "$DP_SIZE" -eq 1 ]; then | |
| CUDA_VISIBLE_DEVICES=$(IFS=,; echo "${GPULIST[*]}") \ | |
| VLLM_PORT="$VLLM_BASE_PORT" \ | |
| VLLM_HOST_IP=127.0.0.1 \ | |
| MASTER_PORT="$((VLLM_BASE_PORT + 1))" \ | |
| MASTER_ADDR=127.0.0.1 \ | |
| PYTHONUNBUFFERED=1 python "$SCRIPT_DIR/eval_seg_vllm.py" "${PY_ARGS[@]}" \ | |
| 2>&1 | tee "$OUTPUT_DIR/run.log" | |
| else | |
| echo "" | |
| echo ">>> Launching $DP_SIZE vLLM workers (TP=$TP_SIZE each) ..." | |
| for IDX in $(seq 0 $((DP_SIZE - 1))); do | |
| if [[ "$RESUME_SHARDS" == "true" ]] && shard_is_complete "$IDX"; then | |
| echo " Reusing completed shard $IDX" | |
| continue | |
| fi | |
| START=$(( IDX * TP_SIZE )) | |
| SHARD_PORT=$((VLLM_BASE_PORT + IDX * 16)) | |
| SHARD_MASTER_PORT=$((SHARD_PORT + 1)) | |
| SHARD_GPUS="" | |
| for j in $(seq 0 $((TP_SIZE - 1))); do | |
| g=${GPULIST[$((START + j))]} | |
| SHARD_GPUS="${SHARD_GPUS}${SHARD_GPUS:+,}${g}" | |
| done | |
| CUDA_VISIBLE_DEVICES="$SHARD_GPUS" \ | |
| VLLM_PORT="$SHARD_PORT" \ | |
| VLLM_HOST_IP=127.0.0.1 \ | |
| MASTER_PORT="$SHARD_MASTER_PORT" \ | |
| MASTER_ADDR=127.0.0.1 \ | |
| PYTHONUNBUFFERED=1 \ | |
| python "$SCRIPT_DIR/eval_seg_vllm.py" "${PY_ARGS[@]}" \ | |
| --chunk "$DP_SIZE" --index "$IDX" \ | |
| > "$OUTPUT_DIR/worker_shard${IDX}.log" 2>&1 & | |
| PIDS+=($!) | |
| PID_SHARDS+=("$IDX") | |
| echo " Launched shard $IDX on GPU $SHARD_GPUS (PID ${PIDS[-1]})" | |
| done | |
| FAILED_SHARDS=() | |
| for i in "${!PIDS[@]}"; do | |
| if ! wait "${PIDS[$i]}"; then | |
| FAILED_SHARDS+=("${PID_SHARDS[$i]}") | |
| fi | |
| done | |
| if (( ${#FAILED_SHARDS[@]} > 0 )) && [[ "$RETRY_FAILED_SHARDS" == "true" ]]; then | |
| echo "" | |
| echo ">>> Retrying failed shards sequentially with fresh ports ..." | |
| RETRY_FAILURES=() | |
| for IDX in "${FAILED_SHARDS[@]}"; do | |
| START=$(( IDX * TP_SIZE )) | |
| SHARD_GPUS="" | |
| for j in $(seq 0 $((TP_SIZE - 1))); do | |
| g=${GPULIST[$((START + j))]} | |
| SHARD_GPUS="${SHARD_GPUS}${SHARD_GPUS:+,}${g}" | |
| done | |
| RETRY_PORT="$( | |
| python - <<'PY' | |
| import socket | |
| for base in range(52000, 64000, 8): | |
| sockets = [] | |
| try: | |
| for offset in (0, 1): | |
| sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
| sock.bind(("127.0.0.1", base + offset)) | |
| sockets.append(sock) | |
| except OSError: | |
| for sock in sockets: | |
| sock.close() | |
| continue | |
| for sock in sockets: | |
| sock.close() | |
| print(base) | |
| break | |
| else: | |
| raise SystemExit("no free vLLM retry ports found") | |
| PY | |
| )" | |
| echo " Retrying shard $IDX on GPU $SHARD_GPUS (ports $RETRY_PORT/$((RETRY_PORT + 1)))" | |
| if ! CUDA_VISIBLE_DEVICES="$SHARD_GPUS" \ | |
| VLLM_PORT="$RETRY_PORT" \ | |
| VLLM_HOST_IP=127.0.0.1 \ | |
| MASTER_PORT="$((RETRY_PORT + 1))" \ | |
| MASTER_ADDR=127.0.0.1 \ | |
| PYTHONUNBUFFERED=1 \ | |
| python "$SCRIPT_DIR/eval_seg_vllm.py" "${PY_ARGS[@]}" \ | |
| --chunk "$DP_SIZE" --index "$IDX" \ | |
| > "$OUTPUT_DIR/worker_shard${IDX}_retry.log" 2>&1; then | |
| RETRY_FAILURES+=("$IDX") | |
| fi | |
| done | |
| FAILED_SHARDS=("${RETRY_FAILURES[@]}") | |
| fi | |
| if (( ${#FAILED_SHARDS[@]} > 0 )); then | |
| echo "ERROR: Failed shards: ${FAILED_SHARDS[*]}; not merging or running SAM2." | |
| exit 1 | |
| fi | |
| echo "" | |
| echo ">>> Merging shard results ..." | |
| IFS=',' read -ra DATASET_LIST <<< "$DATASETS" | |
| for DATASET in "${DATASET_LIST[@]}"; do | |
| DATASET_KEY=$(python -c "from pathlib import Path; import sys; p=sys.argv[1]; print(Path(p).stem if Path(p).suffix else p)" "$DATASET") | |
| python - "$OUTPUT_DIR" "$DATASET_KEY" "$DP_SIZE" <<'PY' | |
| import json | |
| import os | |
| import sys | |
| out_dir, dataset, num_shards = sys.argv[1], sys.argv[2], int(sys.argv[3]) | |
| all_samples = [] | |
| for sid in range(num_shards): | |
| path = os.path.join(out_dir, f"results_{dataset}_shard{sid}.json") | |
| if os.path.isfile(path): | |
| with open(path, encoding="utf-8") as f: | |
| payload = json.load(f) | |
| all_samples.extend(payload.get("results", [])) | |
| n = len(all_samples) | |
| if n == 0: | |
| print(f" No results for {dataset}") | |
| sys.exit(0) | |
| parse_ok = sum(1 for row in all_samples if row.get("parse_ok")) | |
| summary = { | |
| "num_samples": n, | |
| "parse_ok": parse_ok, | |
| "parse_rate": round(parse_ok / n * 100.0, 2), | |
| "by_data_type": {}, | |
| } | |
| for data_type in ("image", "video"): | |
| part = [r for r in all_samples if r.get("data_type") == data_type] | |
| if part: | |
| ok = sum(1 for r in part if r.get("parse_ok")) | |
| summary["by_data_type"][data_type] = { | |
| "num_samples": len(part), | |
| "parse_rate": round(ok / len(part) * 100.0, 2), | |
| } | |
| with open(os.path.join(out_dir, f"results_{dataset}.json"), "w", encoding="utf-8") as f: | |
| json.dump({"results": all_samples, "metrics": summary}, f, ensure_ascii=False, indent=2) | |
| summary_path = os.path.join(out_dir, "summary.json") | |
| full_summary = json.load(open(summary_path, encoding="utf-8")) if os.path.isfile(summary_path) else {} | |
| full_summary[dataset] = summary | |
| with open(summary_path, "w", encoding="utf-8") as f: | |
| json.dump(full_summary, f, ensure_ascii=False, indent=2) | |
| print(f" {dataset}: n={n} parse={summary['parse_rate']:.2f}%") | |
| PY | |
| done | |
| fi | |
| if [ "$RUN_SAM2" = "true" ]; then | |
| if [ -z "$SAM2_CKPT" ] || [ -z "$SAM2_CFG" ]; then | |
| echo "ERROR: RUN_SAM2=true requires SAM2_CKPT and SAM2_CFG." | |
| exit 1 | |
| fi | |
| echo "" | |
| echo ">>> Running SAM2 post-processing ..." | |
| # SAM2 needs visible GPUs. The DP>1 inference path sets CUDA_VISIBLE_DEVICES | |
| # only inside per-worker subshells, so the parent env may be empty here; an | |
| # empty CUDA_VISIBLE_DEVICES makes torch.cuda.is_available() False and SAM2 | |
| # falls back to slow CPU serial. Re-derive a non-empty device list. | |
| export CUDA_VISIBLE_DEVICES="$(IFS=,; echo "${GPULIST[*]}")" | |
| # Default SAM2 GPU count to all visible GPUs unless caller overrides. | |
| if [ -z "$SAM2_NUM_GPUS" ]; then | |
| SAM2_NUM_GPUS="$NUM_GPUS" | |
| fi | |
| echo " SAM2 GPUs: $CUDA_VISIBLE_DEVICES (num_gpus=$SAM2_NUM_GPUS)" | |
| IFS=',' read -ra DATASET_LIST <<< "$DATASETS" | |
| for DATASET in "${DATASET_LIST[@]}"; do | |
| DATASET_KEY=$(python -c "from pathlib import Path; import sys; p=sys.argv[1]; print(Path(p).stem if Path(p).suffix else p)" "$DATASET") | |
| RESULT_JSON="$OUTPUT_DIR/results_${DATASET_KEY}.json" | |
| if [ ! -f "$RESULT_JSON" ]; then | |
| echo " Skip $DATASET_KEY: missing $RESULT_JSON" | |
| continue | |
| fi | |
| SAM2_ARGS=( | |
| --input_json "$RESULT_JSON" | |
| --data_root "$DATA_ROOT" | |
| --sam2_ckpt "$SAM2_CKPT" | |
| --sam2_cfg "$SAM2_CFG" | |
| --onethinker_script "$ONETHINKER_SEG_POST" | |
| --pre_extract_threads "$PRE_EXTRACT_THREADS" | |
| --epoch_size "$SAM2_EPOCH_SIZE" | |
| --viz_ratio "$VIZ_RATIO" | |
| ) | |
| if [ -n "$SAM2_NUM_GPUS" ]; then | |
| SAM2_ARGS+=(--num_gpus "$SAM2_NUM_GPUS") | |
| fi | |
| if [ -n "$SAM2_WORKERS_PER_GPU" ]; then | |
| SAM2_ARGS+=(--workers_per_gpu "$SAM2_WORKERS_PER_GPU") | |
| fi | |
| PYTHONUNBUFFERED=1 python "$SCRIPT_DIR/post_sam2.py" "${SAM2_ARGS[@]}" \ | |
| 2>&1 | tee "$OUTPUT_DIR/sam2_${DATASET_KEY}.log" | |
| # Merge SAM2 metrics (cIoU / gIoU / J&F) back into summary.json. | |
| SAM2_JSON="$OUTPUT_DIR/results_${DATASET_KEY}_sam2.json" | |
| if [ -f "$SAM2_JSON" ]; then | |
| python - "$OUTPUT_DIR" "$DATASET_KEY" "$SAM2_JSON" <<'PY' | |
| import json | |
| import os | |
| import sys | |
| out_dir, dataset, sam2_json = sys.argv[1], sys.argv[2], sys.argv[3] | |
| with open(sam2_json, encoding="utf-8") as f: | |
| payload = json.load(f) | |
| metrics = payload.get("metrics", {}) | |
| avg_rewards = payload.get("avg_rewards", {}) | |
| summary_path = os.path.join(out_dir, "summary.json") | |
| full_summary = json.load(open(summary_path, encoding="utf-8")) if os.path.isfile(summary_path) else {} | |
| entry = full_summary.get(dataset, {}) | |
| if isinstance(metrics, dict): | |
| for k in ("num_samples", "parse_ok", "parse_rate"): | |
| if k in metrics: | |
| entry[k] = metrics[k] | |
| entry["avg_rewards"] = avg_rewards | |
| full_summary[dataset] = entry | |
| with open(summary_path, "w", encoding="utf-8") as f: | |
| json.dump(full_summary, f, ensure_ascii=False, indent=2) | |
| parts = ", ".join(f"{k}={v:.4f}" for k, v in avg_rewards.items()) | |
| print(f" {dataset}: {parts}") | |
| PY | |
| fi | |
| done | |
| fi | |
| echo "" | |
| echo "==============================================" | |
| echo "Done. Summary: $OUTPUT_DIR/summary.json" | |
| echo "==============================================" | |