Files
wehub-resource-sync 542cfa195c
CI / Frontend build (push) Failing after 9m6s
CI / Plugin validate (push) Failing after 9m27s
CI / Python lint (push) Failing after 16m1s
CI / Tests (push) Successful in 18m0s
Deploy / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:27 +08:00

119 lines
6.6 KiB
Bash
Executable File

#!/bin/bash
# PixelRAG paper Table 1 reproduction — one cell at a time.
# Self-contained: uses this repo's eval/run_bench.py + eval/lib (no old Vis-RAG repo).
#
# bash reproduce.sh <bench> <retrieval>
# bench = nq | nqt | sqa | mms | evqa | livevqa
# retrieval = naive | traf | base | lora
#
# Runs the full pipeline (retrieve -> read -> grade) and prints the score.
# It does NOT compare to the paper and does NOT detect the GPU: run the reader on an
# H100 (see REPRODUCE.md) and the numbers naturally land within ~1pp of the paper.
#
# Env (defaults in [] — see REPRODUCE.md for the serve topology):
# READER_URL reader (Qwen3.5-4B, vLLM 0.19.0) OpenAI API base [http://localhost:8010/v1]
# BASE_PORT base pixel search serve [30088]
# LORA_PORT lora pixel search serve [30096]
# TEXT_PORT trafilatura text serve [30097]
# NEWS_PORT news pixel serve (livevqa)[30095]
# TILES_DIR local wiki kiwix tiles [/mnt/data/yichuan/kiwix_tiles]
# OPENAI_API_KEY / OPENAI_BASE_URL for the LLM-judge grader (auto-loaded from ../.env)
set -euo pipefail
cd "$(dirname "$0")"
BENCH="${1:?Usage: reproduce.sh <nq|nqt|sqa|mms|evqa|livevqa> <naive|traf|base|lora>}"
RETR="${2:?Usage: reproduce.sh <bench> <naive|traf|base|lora>}"
READER_URL="${READER_URL:-http://localhost:8010/v1}"
BASE_PORT="${BASE_PORT:-30088}"; LORA_PORT="${LORA_PORT:-30096}"
TEXT_PORT="${TEXT_PORT:-30097}"; NEWS_PORT="${NEWS_PORT:-30095}"
TILES_DIR="${TILES_DIR:-/mnt/data/yichuan/kiwix_tiles}"
PY="$(pwd)/.venv/bin/python"
PIXEL_INSTR="Retrieve images or text relevant to the user's query."
TEXT_INSTR="Retrieve text relevant to the user's query."
mkdir -p eval_output
[ -f ../.env ] && { export OPENAI_API_KEY="$(grep '^OPENAI_API_KEY=' ../.env | cut -d= -f2-)"; \
export OPENAI_BASE_URL="$(grep '^OPENAI_BASE_URL=' ../.env | cut -d= -f2-)"; }
# --- LiveVQA: separate news pipeline (run_livevqa.py) ---------------------
if [ "$BENCH" = livevqa ]; then
OUT="eval_output/repro_livevqa_${RETR}.jsonl"
COMMON=(--api-base "$READER_URL" --model Qwen/Qwen3.5-4B --no-think --max-tokens 16
--livevqa-images /mnt/data/yichuan/livevqa --output "$OUT")
case "$RETR" in
naive) "$PY" run_livevqa.py --mode naive "${COMMON[@]}" ;;
base) "$PY" run_livevqa.py --mode pixel --pixel-api "http://localhost:${NEWS_PORT}/search" \
--pages-db /mnt/data/yichuan/news_state.db --tiles-dir /mnt/data/yichuan/news_tiles "${COMMON[@]}" ;;
*) echo "livevqa supports: naive | base (MCQ exact-match, scored by run_livevqa.py)" >&2; exit 1 ;;
esac
exit 0
fi
# --- per-benchmark config (Qwen3.5-4B, rtk=5, rk=3) -----------------------
case "$BENCH" in
nq) TASK=nq; GRADE=nq; THINK=off; MAXTOK=200; N=1000; EXTRA="" ;;
nqt) TASK=nq_tables; GRADE=nq_tables; THINK=off; MAXTOK=200; N=1068; EXTRA="" ;;
sqa) TASK=simpleqa; GRADE=simpleqa; THINK=off; MAXTOK=200; N=1000; EXTRA="--nprobe 2000" ;;
mms) TASK=mmsearch; GRADE=mmsearch; THINK=on; MAXTOK=16384; N=300; EXTRA="" ;;
evqa) TASK=encyclopedic_vqa; GRADE=encyclopedic_vqa; THINK=off; MAXTOK=16384; N=1000;
EXTRA="--evqa-dataset-filter landmarks --evqa-question-type-filter automatic" ;;
*) echo "unknown bench: $BENCH" >&2; exit 1 ;;
esac
# MMS naive is the one MMS cell the paper ran no-think / max_tokens=200.
[ "$BENCH" = mms ] && [ "$RETR" = naive ] && { THINK=off; MAXTOK=200; }
N="${NUM:-$N}" # NUM env overrides example count (handy for a quick smoke test)
THINKFLAG=""; [ "$THINK" = off ] && THINKFLAG="--no-think"
# --- retrieval condition --------------------------------------------------
case "$RETR" in
naive) RFLAGS=() ;;
base) RFLAGS=(--local-api --local-api-url "http://localhost:${BASE_PORT}/search" --query-instruction "$PIXEL_INSTR") ;;
lora) RFLAGS=(--local-api --local-api-url "http://localhost:${LORA_PORT}/search" --query-instruction "$PIXEL_INSTR") ;;
# --no-query-image: paper kept text retrieval TEXT-ONLY (the "send query image to text
# serve" fix was NOT applied in the paper). Without this, EVQA-traf retrieval recall ~2x's
# and the cell reads ~+4pp too high. See REPRODUCE.md.
traf) RFLAGS=(--text-api --text-api-url "http://localhost:${TEXT_PORT}/search" --query-instruction "$TEXT_INSTR" --no-query-image) ;;
*) echo "unknown retrieval: $RETR" >&2; exit 1 ;;
esac
# --- PREFLIGHT: verify required serves are up AND the right index is loaded ----------
# (catches "serve down / wrong port / wrong index" before a silent empty run.)
preflight_fail=0
check_reader() {
local m; m=$(curl -s --max-time 5 "${READER_URL%/v1}/v1/models" 2>/dev/null | grep -o "Qwen/Qwen3.5-4B" | head -1 || true)
if [ "$m" = "Qwen/Qwen3.5-4B" ]; then echo " ok reader $READER_URL (Qwen/Qwen3.5-4B)"; else
echo " FAIL reader $READER_URL not serving Qwen/Qwen3.5-4B"
echo " launch on an H100: CUDA_VISIBLE_DEVICES=0 vllm serve Qwen/Qwen3.5-4B --port 8010 (vLLM 0.19.0)"
preflight_fail=1; fi
}
# check_search <role> <port> <min_vectors> <launch-index-dir>
check_search() {
local role=$1 port=$2 minv=$3 idx=$4
local st; st=$(curl -s --max-time 5 "http://localhost:${port}/status" 2>/dev/null || true)
local nv; nv=$(echo "$st" | grep -o '"total_vectors":[0-9]*' | grep -o '[0-9]*' || true)
if [ -n "$nv" ] && [ "$nv" -ge "$minv" ]; then echo " ok $role :$port ($nv vectors)"; else
echo " FAIL $role :$port down or wrong index (got vectors='${nv:-none}', need >=$minv)"
echo " launch: pixelrag serve --index-dir $idx --port $port (+ tunnel if remote)"
preflight_fail=1; fi
}
echo ">>> [$BENCH/$RETR] preflight:"
check_reader
case "$RETR" in
base) check_search base-pixel "$BASE_PORT" 28000000 "search_index_normed_v2" ;;
lora) check_search lora-pixel "$LORA_PORT" 26000000 "search_index_lora_vit_ckpt200_v2" ;;
traf) check_search traf-text "$TEXT_PORT" 15000000 "text_search_index_1024_normed" ;;
esac
if [ "$preflight_fail" = 1 ]; then echo ">>> preflight FAILED — bring the serve(s) up (commands above), then re-run." >&2; exit 2; fi
OUT="eval_output/repro_${BENCH}_${RETR}.jsonl"
echo ">>> [$BENCH/$RETR] run_bench: reader=$READER_URL task=$TASK think=$THINK max_tokens=$MAXTOK n=$N"
# shellcheck disable=SC2086
"$PY" run_bench.py --task "$TASK" --model Qwen/Qwen3.5-4B \
--api-base "$READER_URL" --api-key dummy $THINKFLAG \
--retrieval-top-k 5 --reader-top-k 3 --num-examples "$N" --max-tokens "$MAXTOK" \
--tiles-dir "$TILES_DIR" --output "$OUT" --force --max-concurrent 24 \
$EXTRA "${RFLAGS[@]}"
echo ">>> [$BENCH/$RETR] grading ($GRADE)"
PYTHONPATH=. "$PY" -m lib.grader "$GRADE" "$OUT"