Skip to content

[feat] Add IVF+PQ FAISS flag for compressed-memory deployments (air-gapped / Docker-free parity with Qdrant) #153

Description

@miles-on-nightshift

Motivation

The FAISS backend today uses IndexIVFFlat, which stores every vector at full float32 precision. For the default embedding model (Qwen/Qwen3-VL-Embedding-2B, dim = 2048), a 30 M-vector corpus requires roughly 245 GB of RAM just to hold the index — far outside the reach of air-gapped or Docker-free deployments that cannot run a Qdrant service.

FAISS ships IndexIVFPQ (Product Quantization) out of the box. Switching to PQ with M=64, nbits=8 compresses each vector from 8 192 bytes to 64 bytes (128× reduction, ~1.9 GB for 30 M vectors) at a typical recall-at-10 drop of ≤ 5–8% on inner-product tasks — a trade-off Qdrant users can already make today via --qdrant-quantization-config. Adding an equivalent flag to the FAISS path closes that gap for users who cannot run a Docker sidecar.

Requested change

Add opt-in --pq-m / --pq-nbits flags to the faiss build path so users can select IndexIVFPQ instead of IndexIVFFlat.

CLI — embed/src/pixelrag_embed/index.py

 # inside the 'build' subparser, faiss-only flags
+p_build.add_argument(
+    "--pq-m",
+    type=int,
+    default=0,
+    help="PQ sub-quantizers (0 = IVFFlat, no compression). "
+         "Must divide the embedding dim evenly. "
+         "Typical: dim/32 — e.g. 64 for dim 2048.",
+)
+p_build.add_argument(
+    "--pq-nbits",
+    type=int,
+    default=8,
+    choices=[4, 8],
+    help="Bits per PQ sub-quantizer (default: 8).",
+)

In build_ivf(), when pq_m > 0, replace:

index = faiss.IndexIVFFlat(quantizer, dim, nlist, metric_type)

with:

index = faiss.IndexIVFPQ(quantizer, dim, nlist, pq_m, pq_nbits)

Config — pixelrag.yaml

index:
  backend: faiss
  pq_m: 64        # sub-quantizers; must divide dim evenly
  pq_nbits: 8     # 4 or 8 (default 8)

Pipeline forwarding — index/src/pixelrag_index/pipelines.py

The faiss branch of the cmd build in build() already passes --nlist. It would additionally pass --pq-m / --pq-nbits when they appear in index_cfg.

Why not just use Qdrant?

Qdrant scalar/binary quantization via --qdrant-quantization-config is an excellent solution for connected deployments. IVF+PQ covers the complementary case: no Docker available, no network service permitted, fully air-gapped lab or edge environments. Both users deserve a compression path; this closes the gap on the FAISS side.

Memory impact at a glance

Index type M (sub-quantizers) Bytes / vector 30 M vectors Recall@10 (approx.)
IVFFlat (current) — 8 192 ~245 GB baseline
IVFPQ nbits=8 128 128 ~3.8 GB ~96%
IVFPQ nbits=8 64 64 ~1.9 GB ~92%
IVFPQ nbits=4 64 32 ~0.95 GB ~87%

Figures are illustrative for inner-product search on visual-embedding benchmarks; actual recall depends on the corpus. Even the most aggressive setting (M=64, nbits=4) makes a 30 M-vector corpus fit on a workstation with 4 GB of free RAM.

Scope

  • embed/src/pixelrag_embed/index.py: build_ivf() function + arg parser — approximately 25 lines changed/added.
  • index/src/pixelrag_index/pipelines.py: forward pq_m / pq_nbits from index_cfg — approximately 6 lines.
  • No new dependencies: faiss-cpu and faiss-gpu already include IndexIVFPQ.

Happy to open a PR if this direction looks good to the maintainers.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions