Skip to content

[codex] feat: add RankQuant eval scorer#97

Merged
project-navi-bot merged 6 commits into
mainfrom
codex/rankquant-eval-score
May 28, 2026
Merged

[codex] feat: add RankQuant eval scorer#97
project-navi-bot merged 6 commits into
mainfrom
codex/rankquant-eval-score

Conversation

@Fieldnote-Echo
Copy link
Copy Markdown
Owner

Summary

  • add standalone rankquant_eval_search(corpus, queries, dim, bits, k) in Rust for symmetric RankQuant-style top-k scoring at arbitrary eval widths bits in 1..=7
  • expose Python rankquant_eval_search(corpus, queries, bits, k) returning (scores, indices) arrays like RankQuant.search
  • keep RankQuant::new, packed storage, and .tvrq load/write contracts unchanged; b=3 remains unsupported for persistent packed indexes
  • test b=1/2/4 parity against RankQuant.search, and b=3 parity against scalar/NumPy references

Closes #71.

Validation

  • cargo fmt --all --check
  • cargo test -p ordvec rankquant_eval_search
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo test --all-features
  • maturin develop
  • ./.venv/bin/python -m pytest tests/test_rank_quant.py -q
  • ./.venv/bin/python -m pytest tests -q

Signed-off-by: Nelson Spence <nelson@projectnavi.ai>
@codecov
Copy link
Copy Markdown

codecov Bot commented May 28, 2026

Codecov Report

❌ Patch coverage is 92.61745% with 11 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
ordvec-python/src/lib.rs 78.84% 11 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces rankquant_eval_search, a standalone, eval-only symmetric RankQuant-style search function supporting arbitrary bit widths (1 to 7) without altering the packed storage format. This is implemented in Rust and exposed to Python via PyO3, accompanied by comprehensive unit and integration tests. The review feedback suggests two valuable performance and safety improvements in src/quant.rs: adding an early return when there are no queries (nq == 0) to prevent redundant corpus processing, and replacing manual slicing with chunks_exact(dim) to make the code more idiomatic and potentially allow better compiler optimizations.

Comment thread src/quant.rs Outdated
Comment thread src/quant.rs Outdated
@Fieldnote-Echo Fieldnote-Echo marked this pull request as ready for review May 28, 2026 15:52
@Fieldnote-Echo Fieldnote-Echo requested a review from Copilot May 28, 2026 15:52
@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Add RankQuant eval scorer for arbitrary bit widths

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add standalone rankquant_eval_search function supporting arbitrary bit widths (1-7)
• Enable eval-only symmetric RankQuant scoring without persistent storage changes
• Support non-byte-aligned widths like bits=3 for research and evaluation sweeps
• Maintain backward compatibility with existing RankQuant storage and .tvrq format
Diagram
flowchart LR
  corpus["corpus vectors"] --> rankquant_eval_search["rankquant_eval_search<br/>bits in 1..=7"]
  queries["query vectors"] --> rankquant_eval_search
  rankquant_eval_search --> rank_transform["rank_transform"]
  rank_transform --> bucket_centre["bucket_centre<br/>& normalize"]
  bucket_centre --> topk["top-k scoring"]
  topk --> scores["scores array"]
  topk --> indices["indices array"]

Loading

Grey Divider

File Changes

1. src/quant.rs ✨ Enhancement +109/-0

Core eval search implementation with norm computation

• Add rankquant_eval_norm function to compute analytical norm for arbitrary bit widths
• Add rankquant_eval_centres helper to transform and bucket rank vectors
• Implement rankquant_eval_search public function supporting bits 1-7 with parallel processing
• Use TopK data structure for efficient top-k result collection

src/quant.rs


2. src/lib.rs ✨ Enhancement +1/-1

Export eval search to public API

• Export rankquant_eval_search function from public API

src/lib.rs


3. ordvec-python/src/lib.rs ✨ Enhancement +68/-0

PyO3 bindings for eval search function

• Add check_bits_1_7 validation function for eval-only bit widths
• Add infer_float_2d_width helper to extract matrix column count
• Implement rankquant_eval_search PyO3 binding with input validation and array conversion
• Register function in Python module exports

ordvec-python/src/lib.rs


View more (3)
4. ordvec-python/python/ordvec/__init__.py ✨ Enhancement +9/-6

Export eval search in Python public API

• Add rankquant_eval_search to module imports from _ordvec
• Update module docstring to document eval-only arbitrary-width scorer
• Add function to __all__ public API list

ordvec-python/python/ordvec/init.py


5. tests/index/quant.rs 🧪 Tests +103/-1

Rust integration tests for eval search

• Add ref_rankquant_eval_norm reference implementation for norm calculation
• Add ref_rankquant_eval_symmetric scalar reference for symmetric scoring
• Add test rankquant_eval_search_matches_rankquant_search_for_packed_widths verifying parity with
 RankQuant.search for bits 1/2/4
• Add test rankquant_eval_search_b3_matches_scalar_reference validating non-byte-aligned bit width
• Add test rankquant_constructor_still_rejects_b3 ensuring backward compatibility

tests/index/quant.rs


6. ordvec-python/tests/test_rank_quant.py 🧪 Tests +91/-1

Python tests for eval search function

• Add rank_transform_reference helper for rank computation
• Add rankquant_eval_reference NumPy reference implementation
• Add test test_rankquant_eval_search_matches_rankquant_search for bits 1/2/4 parity
• Add test test_rankquant_eval_search_b3_matches_numpy_reference for non-byte-aligned width
 validation
• Add test test_rankquant_eval_search_empty_corpus_shape for edge case handling
• Add validation tests for invalid bit width rejection

ordvec-python/tests/test_rank_quant.py


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented May 28, 2026

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. No NumPy parity tests ✓ Resolved 📎 Requirement gap ≡ Correctness
Description
The new parity tests validate rankquant_eval_search against RankQuant.search for bits in
{1,2,4}, but do not compare against the provided NumPy reference for those same widths as
required. This leaves the eval path’s semantics insufficiently validated against the reference
implementation at existing supported widths.
Code

ordvec-python/tests/test_rank_quant.py[R116-128]

Evidence
PR Compliance ID 3 requires parity checks against a NumPy reference for bits ∈ {1,2,4}. The PR
adds a NumPy reference function, but the {1,2,4} test compares rankquant_eval_search to
RankQuant.search instead of the NumPy reference, and only bits=3 is validated against the NumPy
reference.

Parity-check against NumPy reference at existing supported widths (bits ∈ {1,2,4})
ordvec-python/tests/test_rank_quant.py[32-67]
ordvec-python/tests/test_rank_quant.py[116-128]
ordvec-python/tests/test_rank_quant.py[130-143]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The compliance checklist requires automated parity checks against a NumPy reference for `bits ∈ {1,2,4}`. The PR adds a NumPy reference (`rankquant_eval_reference`) but only uses it for `bits=3`, while the `{1,2,4}` parity test compares against `RankQuant.search` instead.

## Issue Context
- `rankquant_eval_reference(...)` is present and can be used for `bits=1,2,4` as well.
- Current test only validates `{1,2,4}` vs packed `RankQuant.search`, which does not satisfy the explicit “NumPy reference” requirement.

## Fix Focus Areas
- ordvec-python/tests/test_rank_quant.py[32-67]
- ordvec-python/tests/test_rank_quant.py[116-128]
- ordvec-python/tests/test_rank_quant.py[130-143]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Doc centres double memory ✓ Resolved 🐞 Bug ☼ Reliability
Description
rankquant_eval_search materializes doc_centres as a full Vec<f32> of length n * dim,
duplicating corpus-sized memory and making eval sweeps prone to OOM on large corpora. Because
buckets are u8 codes in [0, 1<<bits), storing doc bucket codes (or another compact
representation) can reduce memory significantly without changing the math.
Code

src/quant.rs[R718-744]

Evidence
The eval search explicitly allocates an n * dim Vec<f32> for doc_centres and indexes it during
scoring, which duplicates corpus-scale storage. Separately, the rank bucketing primitives show
buckets are u8 in [0, 1<<bits), enabling a compact bucket-code representation with a small
centre lookup table.

src/quant.rs[718-742]
src/rank.rs[65-101]
src/rank.rs[205-230]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`rankquant_eval_search` allocates `doc_centres: Vec<f32>` of size `n * dim` and keeps it for the full scoring pass. For large corpora, this roughly doubles peak memory versus holding just the input `corpus`.

### Issue Context
- The bucket alphabet is representable as `u8` (`rank_to_bucket` returns `u8`, and `bucket_centre` is derived from a `u8` bucket code).
- You can reduce memory by storing `doc_buckets: Vec<u8>` (or `i8`-scaled centres) instead of `doc_centres: Vec<f32>`, and using a precomputed `centres: [f32; 1<<bits]` table in the inner loop.

### Fix Focus Areas
- Replace `doc_centres: Vec<f32>` with `doc_buckets: Vec<u8>` (or similarly compact storage), and adjust the scoring loop to map bucket codes to centres via a small lookup table.
- file/path[start_line-end_line]
 - src/quant.rs[718-744]
 - src/rank.rs[65-101]
 - src/rank.rs[205-230]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Empty queries scan corpus ✓ Resolved 🐞 Bug ➹ Performance
Description
rankquant_eval_search still computes norm and allocates/fills doc_centres even when nq == 0
(empty queries), despite the scoring loop doing no work. This can trigger large, surprising
CPU+memory cost (and potential OOM) when a caller passes an empty (0, dim) query matrix.
Code

src/quant.rs[R691-745]

Evidence
The function derives nq from queries.len() / dim, sizes output buffers as nq * k, but only
early-returns when k_eff == 0; therefore when nq == 0 and k_eff > 0 it still allocates and
fills doc_centres before reaching a query loop that has no chunks.

src/quant.rs[691-745]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`rankquant_eval_search` does substantial precomputation (`rankquant_eval_norm`, `doc_centres` allocation + fill) even when there are zero queries (`nq == 0`). Since `scores_flat`/`indices_flat` will be empty (because `buf_len = nq * k`), the subsequent parallel query loop is a no-op, making the precompute pure overhead.

### Issue Context
- The existing early-return only checks `k_eff == 0` (i.e., `k==0` or `n==0`), not `nq==0`.
- With `nq==0`, returning `SearchResults { scores: vec![], indices: vec![], nq: 0, k }` satisfies the `nq * k` buffer-length contract.

### Fix Focus Areas
- Add a fast-path `if nq == 0 { ... }` before `rankquant_eval_norm` / `doc_centres` allocation.
- file/path[start_line-end_line]
 - src/quant.rs[691-725]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an eval-only RankQuant scorer for arbitrary bit widths, enabling symmetric RankQuant-style top-k search for research sweeps including non-packed widths like bits=3 while preserving the existing packed RankQuant storage contract.

Changes:

  • Adds Rust rankquant_eval_search with arbitrary bits in 1..=7.
  • Exposes the scorer through the Python package API.
  • Adds Rust and Python parity tests against packed RankQuant.search and scalar/NumPy references.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/quant.rs Implements eval-only symmetric RankQuant scoring.
src/lib.rs Re-exports the new Rust scorer.
ordvec-python/src/lib.rs Adds Python binding and validation for the scorer.
ordvec-python/python/ordvec/__init__.py Exposes rankquant_eval_search in the Python public API.
tests/index/quant.rs Adds Rust parity and b=3 reference tests.
ordvec-python/tests/test_rank_quant.py Adds Python API, parity, invalid-bits, and empty-corpus tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ordvec-python/tests/test_rank_quant.py
Signed-off-by: Nelson Spence <nelson@projectnavi.ai>
Signed-off-by: Nelson Spence <nelson@projectnavi.ai>
Copy link
Copy Markdown
Owner Author

Bot review disposition after follow-up fixes:

  • Gemini empty-query fast path: fixed in dc24254 with Rust and Python empty-query coverage.
  • Gemini chunks_exact(dim) loop: fixed in dc24254.
  • Qodo NumPy parity gap: fixed in 2ba013a; rankquant_eval_reference now checks bits 1, 2, 3, and 4.
  • Qodo top-level doc-centre memory concern: fixed in 2ba013a; eval search now stores corpus bucket codes as u8 and uses a small centre lookup table during scoring instead of materializing an n * dim f32 centre matrix.
  • Codecov patch report: added focused Rust/Python tests for the empty-query path and expanded Python NumPy parity coverage.

Validation run locally:

  • cargo fmt --all --check
  • cargo test --all-features rankquant_eval
  • maturin develop
  • uv run pytest tests/test_rank_quant.py -k rankquant_eval

@project-navi-bot project-navi-bot merged commit 63a28fc into main May 28, 2026
32 checks passed
@project-navi-bot project-navi-bot deleted the codex/rankquant-eval-score branch May 28, 2026 17:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RankQuant: support odd-byte (non-{1,2,4}) bit widths for research coarsening sweeps

3 participants