|
| 1 | +# Experimental vector_db_ann_disk Judge Image |
| 2 | + |
| 3 | +This task **constructs** the hidden SIFT100M benchmark data from scratch at |
| 4 | +judge-image build time and bakes it in (the duckdb-e2e / vllm bake pattern). No |
| 5 | +manual download, no data hosting, no host staging — the image build downloads the |
| 6 | +public BIGANN dataset, builds the DiskANN index, and measures the baseline. |
| 7 | + |
| 8 | +The **agent** image stays the default `ubuntu:24.04` — the agent never sees the |
| 9 | +hidden data; it only implements the `/load` + `/search` contract. |
| 10 | + |
| 11 | +## Build |
| 12 | + |
| 13 | +```bash |
| 14 | +bash 2.0/problems/vector_db_ann_disk/docker/build_images.sh |
| 15 | +``` |
| 16 | + |
| 17 | +Default tag (kept in sync with `config.yaml` `runtime.docker.judge_image`): |
| 18 | + |
| 19 | +```text |
| 20 | +JUDGE_TAG=frontiercs/vector-db-ann-disk-judge:experimental-v1 |
| 21 | +``` |
| 22 | + |
| 23 | +Full scale (N=100M) is **network / RAM / time heavy** (see Resources). Quick |
| 24 | +smoke image at small scale: |
| 25 | + |
| 26 | +```bash |
| 27 | +N=100000 Q=1000 bash docker/build_images.sh |
| 28 | +``` |
| 29 | + |
| 30 | +## Pipeline (docker/build_all.sh, run inside the image builder) |
| 31 | + |
| 32 | +1. **build_data.py** — downloads the real BIGANN data and slices it: |
| 33 | + - `100M.u8bin` — first N rows of `base.1B.u8bin` via HTTP range, header |
| 34 | + rewritten to N (`uint8`, 128-dim). |
| 35 | + - `query.bin` — `query.public.10K.u8bin` (10,000 × 128 `uint8`). |
| 36 | + - `truth.bin` — top-`TOP_K` ids per query, sliced from the **official** exact |
| 37 | + ground truth `GT_100M/bigann-100M` (so no 51 GB exact-search is needed). |
| 38 | +2. **build_index.sh** — clones + compiles |
| 39 | + [FreshDiskANN-baseline](https://github.com/g4197/FreshDiskANN-baseline) and runs |
| 40 | + `build_disk_index` → `100M_disk.index`, `100M_pq_pivots.bin`, |
| 41 | + `100M_pq_compressed.bin`. |
| 42 | +3. **build_baseline.py** — exact Faiss `IndexFlatL2` throughput → `baseline.json`. |
| 43 | + |
| 44 | +Source URLs (public, from big-ann-benchmarks): |
| 45 | + |
| 46 | +```text |
| 47 | +https://dl.fbaipublicfiles.com/billion-scale-ann-benchmarks/bigann/base.1B.u8bin |
| 48 | +https://dl.fbaipublicfiles.com/billion-scale-ann-benchmarks/bigann/query.public.10K.u8bin |
| 49 | +https://dl.fbaipublicfiles.com/billion-scale-ann-benchmarks/GT_100M/bigann-100M |
| 50 | +``` |
| 51 | + |
| 52 | +## What the judge image contains |
| 53 | + |
| 54 | +```text |
| 55 | +/data/index_100M/ # handed to the candidate via /load |
| 56 | + 100M.u8bin query.bin 100M_disk.index 100M_pq_pivots.bin 100M_pq_compressed.bin |
| 57 | +/data/private_100M/ # judge-only, NEVER referenced by /load |
| 58 | + truth.bin baseline.json |
| 59 | +``` |
| 60 | + |
| 61 | +Data paths, dtype, `N=100,000,000`, `Q=10,000` are pinned as image `ENV` |
| 62 | +(`judge/Dockerfile`). The adapter builds the final judge image on top of this one, |
| 63 | +layering `cargo`/`rustc` + `numpy`/`faiss-cpu` from `config.yaml`. |
| 64 | + |
| 65 | +## Resources (full N=100M build) |
| 66 | + |
| 67 | +- **base download**: ~12.8 GB (first 100M rows of `base.1B.u8bin` via range). |
| 68 | +- **DiskANN index build**: tens of GB output, multiple cores, ~hours; tune |
| 69 | + `R/L/B/M/T` via env in `build_index.sh` (`B` = PQ budget; keep PQ within the |
| 70 | + 8 GiB eval budget). |
| 71 | +- **baseline**: exact FlatL2 over 100M holds vectors as float32 (~**51 GB RAM**) |
| 72 | + on the build host. The baseline therefore reflects the *build* host, not the |
| 73 | + 8 GiB eval container — a deliberate bake. Adjust if you want a different |
| 74 | + reference. |
| 75 | + |
| 76 | +## Running locally on constrained Docker (rootless / vfs / limited disk) |
| 77 | + |
| 78 | +The baked judge image is **large (~58 GB)** because the SIFT100M data lives |
| 79 | +inside it. On a host with `overlay2` and ample disk this is fine. But on a |
| 80 | +**rootless daemon using the `vfs` storage driver** (no copy-on-write), every |
| 81 | +derived image layer and every container re-copies the full image, so a single |
| 82 | +trial can need **~3x** the image size in scratch space, and a near-full shared |
| 83 | +disk can be exhausted. |
| 84 | + |
| 85 | +For those hosts, use a **mount-data variant**: build a tiny base image (ubuntu + |
| 86 | +the pinned evaluator ENV + the small `private_100M` secrets) and bind-mount the |
| 87 | +54 GB `index_100M` read-only into the judge service at `/data/index_100M` |
| 88 | +instead of baking it. The data then lives once on the host. Other rootless |
| 89 | +gotchas: set `DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock`; pass |
| 90 | +`--cpus ignore` to `harbor trial start` if the `cpu` cgroup controller is not |
| 91 | +delegated (only `memory`/`pids` usually are); and inject agent credentials via |
| 92 | +**env vars** (e.g. `OPENAI_API_KEY` / `CLAUDE_CODE_OAUTH_TOKEN`), since |
| 93 | +`docker cp` of host-owned credential files fails under the user namespace. |
| 94 | + |
| 95 | +Note also that iterative `submit.sh` evaluations time only |
| 96 | +`FRONTIER_VECTOR_DB_ITER_Q` queries (default 2000) for fast feedback; the final |
| 97 | +verifier (`FRONTIER_SUBMISSION_ROLE=final`) always times the full |
| 98 | +`FRONTIER_VECTOR_DB_Q` set. |
| 99 | + |
| 100 | +## Security note (anti-cheat) |
| 101 | + |
| 102 | +The candidate service is built and run by the judge **in this same container**, |
| 103 | +so plain directory isolation is not enough on its own. Three layers protect the |
| 104 | +ground truth / baseline: |
| 105 | + |
| 106 | +1. **Out of the /load directory** — `truth.bin` / `baseline.json` live under |
| 107 | + `/data/private_100M`, never passed to `/load`, so they cannot be reached via |
| 108 | + `dirname(vector_path)`. |
| 109 | +2. **Restricted permissions** — `/data/private_100M` is `0700` root-only (the |
| 110 | + `/load` files under `/data/index_100M` stay world-readable). A non-root |
| 111 | + candidate cannot read them. |
| 112 | +3. **Removed after load** — `evaluator.py` loads the truth + baseline into memory |
| 113 | + at judge startup and then **deletes the files from disk** before any candidate |
| 114 | + runs, so even a root candidate finds nothing to read. (`_ensure_benchmark` |
| 115 | + caches in memory; later submissions never need the files. Opt out for |
| 116 | + debugging with `FRONTIER_VECTOR_DB_KEEP_TRUTH=1`.) |
| 117 | + |
| 118 | +They are also never named in the agent-facing `readme`. |
0 commit comments