Skip to content

feat: caller-owned serial CSR batched/buffered two-stage primitives (ordvec 0.5.0)#208

Open
Fieldnote-Echo wants to merge 12 commits into
mainfrom
feat/batched-subset-rerank
Open

feat: caller-owned serial CSR batched/buffered two-stage primitives (ordvec 0.5.0)#208
Fieldnote-Echo wants to merge 12 commits into
mainfrom
feat/batched-subset-rerank

Conversation

@Fieldnote-Echo

Copy link
Copy Markdown
Owner

ordvec 0.5.0 — Track 1: caller-owned serial CSR batched/buffered two-stage primitives

Adds the additive, caller-owned (no internal rayon) serial CSR primitives that downstream engines need to run a fully self-scheduled two-stage SignBitmap → RankQuant search, removing per-query allocation/orchestration overhead from the rerank stage.

Added (all additive / semver-minor)

  • SignBitmap::top_m_candidates_batched_serial_csr(queries, m) -> CandidateBatch — serial CSR candidate generation (stage 1). CandidateBatch { candidates: Vec<u32>, offsets: Vec<usize> } with documented CSR invariants + query_count / candidates_for_query / is_empty / has_no_candidates.
  • RankQuant::search_asymmetric_subset_batched_serial(queries, offsets, candidates, k) -> SearchResults — allocating ergonomic batched rerank (stage 2).
  • RankQuant::search_asymmetric_subset_batched_serial_into(.., &mut SubsetScratch, &mut out_scores, &mut out_indices)allocation-free after warmup; the integration contract for runtimes that own their own thread pool (call from a bounded pool, GIL released).
  • SubsetScratch (opaque reusable scratch).
  • Internal reuse plumbing: TopK::{reset_with_tie_keys, finalize_into_with_scratch}, l2_normalise_into.

Design

  • Caller owns parallelism. These primitives never enter rayon; the existing internally-parallel top_m_candidates_batched / search_asymmetric* are unchanged. No bundled two-stage helper — the two CSR primitives stay composable.
  • CSR carrier (offsets + flat candidates), not Vec<Vec<u32>>.
  • Rectangular output: out_k = k.min(len), underfull rows sentinel-padded (NEG_INFINITY / -1); duplicate candidate ids scored independently; tie policy (score desc, global id asc). Hard asserts on contract violation, validated before any output write.
  • search_asymmetric_subset refactored to share the single-row helper — behavior-preserving (full existing suite green).

Tests / gate

  • New coverage: CSR parity vs looped single-query, *_into == *_serial, full-candidate-set == search_asymmetric, duplicate [7,7,3,7] parity, malformed-CSR / OOB / non-finite panics, edges (k=0/nq=0/n=0/k>n/m>n/m=0), allocation-free (scratch capacity stable across calls), cross-tier scalar-reference parity, and a determinism_contract tie-policy case for the new path.
  • Full gate green: fmt, clippy --all-targets --all-features -D warnings, test (+ experimental, + --no-default-features), MSRV 1.89, --locked, RUSTDOCFLAGS=-D warnings doc, doc-tests, +nightly fuzz build.

Release

  • Lockstep version bump 0.4.0 → 0.5.0 (per RELEASING.md) + CHANGELOG. Publish remains gated — no cargo publish / maturin publish in this PR.

Closes #115. Addresses #116 (caller-owned candidate/filter surface) and #125 (host-controlled parallelism) — maintainer to confirm full closure.

🤖 Generated with Claude Code

Signed-off-by: Nelson Spence <nelson@projectnavi.ai>
…for buffer reuse

Signed-off-by: Nelson Spence <nelson@projectnavi.ai>
Signed-off-by: Nelson Spence <nelson@projectnavi.ai>
… stage-1)

Signed-off-by: Nelson Spence <nelson@projectnavi.ai>
…havior-preserving)

Signed-off-by: Nelson Spence <nelson@projectnavi.ai>
…ee serial batched rerank)

Signed-off-by: Nelson Spence <nelson@projectnavi.ai>
Signed-off-by: Nelson Spence <nelson@projectnavi.ai>
Signed-off-by: Nelson Spence <nelson@projectnavi.ai>
Signed-off-by: Nelson Spence <nelson@projectnavi.ai>
Signed-off-by: Nelson Spence <nelson@projectnavi.ai>
@Fieldnote-Echo

Copy link
Copy Markdown
Owner Author

/agentic_review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@qodo-code-review

qodo-code-review Bot commented Jun 13, 2026

Copy link
Copy Markdown

Code Review by Qodo

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

Context used

Grey Divider


Remediation recommended

1. Unsorted candidates behavior undocumented 📎 Requirement gap ⚙ Maintainability ⭐ New
Description
search_asymmetric_subset_batched_serial_into documents empty rows, k > len, and duplicate
candidate IDs, but does not state whether unsorted candidates rows are allowed/what effect
ordering has. This leaves an edge-case behavior unspecified for integrators providing caller-owned
candidate sets.
Code

src/quant.rs[R895-903]

+    /// (`NEG_INFINITY` / `-1`). Duplicate candidate ids are scored independently
+    /// — but each CSR row's length (duplicates included) must be `<= self.len()`;
+    /// deduplicate a duplicate-heavy row before calling if it would exceed that.
+    /// Tie policy: `(score desc, global row-id asc)`.
+    ///
+    /// Panics on any contract violation: malformed CSR (`candidate_offsets` not
+    /// `nq + 1` long, not starting at `0`, non-monotonic, or not ending at
+    /// `candidates.len()`), a row longer than `self.len()`, a candidate id
+    /// `>= self.len()`, a non-finite query value, or a wrong output-buffer length.
Evidence
PR Compliance ID 15 requires documented behavior for unsorted candidate IDs. The doc comment section
describing edge cases and panics mentions duplicates and length caps but does not mention unsorted
candidate rows at all.

Batched subset rerank documents behavior for edge cases and preserves deterministic tie-breaking
src/quant.rs[895-903]

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 batched subset rerank API doc comment does not specify behavior for unsorted candidate IDs, which is required as part of the edge-case contract.

## Issue Context
Rule PR Compliance ID 15 requires the batched subset rerank to document behavior for unsorted candidate IDs. The current docs cover duplicates, empty rows, and padding but do not explicitly say whether candidate rows must be sorted or whether ordering affects results.

## Fix Focus Areas
- src/quant.rs[895-903]

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


2. Redundant output buffer clears ✓ Resolved 🐞 Bug ➹ Performance
Description
search_asymmetric_subset_batched_serial_into clears the entire output buffers up front, but each
non-empty row is cleared again inside TopK::finalize_into_with_scratch during rerank. This adds
redundant O(nq*out_k) writes and extra memory bandwidth on the hot path.
Code

src/quant.rs[R928-935]

+        // Preclear the whole output so short / zero-candidate rows never leak
+        // stale caller buffer contents.
+        for s in out_scores.iter_mut() {
+            *s = f32::NEG_INFINITY;
+        }
+        for i in out_indices.iter_mut() {
+            *i = -1;
+        }
Evidence
The batched function explicitly preclears the full output buffers. For non-empty rows,
subset_rerank_row_into calls TopK::finalize_into_with_scratch, which clears the provided row
slice again before filling it, making the initial clear redundant for those rows.

src/quant.rs[928-947]
src/quant.rs[864-871]
src/util.rs[553-559]

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 batched `_into` API clears `out_scores`/`out_indices` for the entire batch before processing rows. For non-empty rows, `subset_rerank_row_into` ultimately calls `TopK::finalize_into_with_scratch`, which clears the per-row slices again before writing results. This duplicates work for every non-empty row.

### Issue Context
You still need to prevent stale data for empty candidate rows (because `subset_rerank_row_into` early-returns for `m == 0` without writing). A minimal fix is to delete the full-buffer preclear and instead explicitly clear only the rows with `row.is_empty()` inside the per-row loop; non-empty rows can rely on `finalize_into_with_scratch`’s existing clear.

### Fix Focus Areas
- src/quant.rs[928-947]
- src/quant.rs[761-768]
- src/util.rs[553-559]

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


3. Undocumented row length cap ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
RankQuant::search_asymmetric_subset_batched_serial_into enforces row_len <= n_vectors for every
CSR row, but this constraint is not documented in the public doc comment and therefore can cause
surprising panics for callers providing their own candidate sets. This is especially relevant
because the doc explicitly states duplicates are allowed but doesn’t mention the length bound that
duplicates can trigger.
Code

src/quant.rs[R886-895]

+    /// `queries` is `nq * dim`. Candidates are CSR: `candidate_offsets.len()
+    /// == nq + 1`, row `qi` is `candidates[candidate_offsets[qi]..candidate_offsets[qi+1]]`.
+    /// Output is rectangular: `out_k = k.min(self.len())`, and both output
+    /// buffers MUST have length `nq * out_k`. Underfull rows are sentinel-padded
+    /// (`NEG_INFINITY` / `-1`). Duplicate candidate ids are scored independently.
+    /// Tie policy: `(score desc, global row-id asc)`.
+    ///
+    /// Panics on any contract violation (CSR malformed, candidate id out of
+    /// range, non-finite query, wrong output-buffer length).
+    #[allow(clippy::too_many_arguments)] // arity is intrinsic to the caller-owned buffered contract (CSR inputs + scratch + two output buffers)
Evidence
The batched API docs list panic conditions but omit the per-row candidate-count requirement, while
the CSR validator enforces row_len <= self.n_vectors. The single-query subset API documents and
asserts the same bound, indicating the batched docs should mirror that public contract.

src/quant.rs[881-895]
src/quant.rs[709-734]
src/quant.rs[647-652]
src/quant.rs[677-682]

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

### Issue description
`search_asymmetric_subset_batched_serial_into` panics when any CSR row contains more than `self.n_vectors` candidates, but the public docs for the method do not mention this precondition in the contract or the panic list. This creates an API footgun for integrators supplying caller-owned candidate sets.

### Issue Context
The single-query `search_asymmetric_subset` explicitly documents and asserts the same bound (to prevent duplicate-heavy candidate lists from allocating scratch larger than a full scan). The batched CSR validator mirrors this bound via `validate_csr_batch`, but the batched API doc comment currently omits it.

### Fix Focus Areas
- src/quant.rs[881-895]
- src/quant.rs[709-734]
- src/quant.rs[647-652]

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


View more (1)
4. Batched docs use SignBitmap stage 📎 Requirement gap ⚙ Maintainability
Description
The new batched subset-rerank documentation only demonstrates a fully ordvec-owned two-stage flow
(SignBitmap candidate generation → batched subset rerank), and does not include an example showing
construction of the CSR candidate batch from an external ANN/filter stage. This misses the required
integration-oriented documentation and may mislead integrators about the intended caller-owned
candidate workflow.
Code

src/quant.rs[R955-965]

+    /// # Example
+    /// ```no_run
+    /// use ordvec::{RankQuant, SignBitmap};
+    /// # let (dim, queries, k) = (768usize, vec![0.0f32; 768 * 3], 10usize);
+    /// # let sign = SignBitmap::new(dim);
+    /// # let rq = RankQuant::new(dim, 2);
+    /// let cb = sign.top_m_candidates_batched_serial_csr(&queries, 256);
+    /// let results = rq.search_asymmetric_subset_batched_serial(
+    ///     &queries, &cb.offsets, &cb.candidates, k,
+    /// );
+    /// for qi in 0..results.nq {
Evidence
PR Compliance ID 7 requires documentation demonstrating an external ANN/filter/candidate stage
feeding a caller-constructed batched candidate representation into batched subset rerank. The added
example explicitly generates candidates via SignBitmap::top_m_candidates_batched_serial_csr and
then immediately calls the batched rerank, and the changelog similarly frames the intended usage
around pairing the two ordvec primitives rather than showing external candidate generation.

Batched subset rerank documentation shows use after external ANN/filter/candidate stage
src/quant.rs[955-965]
CHANGELOG.md[14-29]

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 docs for the new batched subset rerank show a `SignBitmap`-generated candidate batch, but the compliance requirement is to document use after an *external* ANN/filter/candidate stage, including how to build the CSR (`offsets` + flat `candidates`) representation.

## Issue Context
PR Compliance ID 7 requires documentation that demonstrates ecosystem integration where ordvec only performs scoring on caller-owned candidates produced by an external system (ANN index, metadata filter, shard planner, etc.). The current example and changelog entry demonstrate an ordvec-internal candidate stage instead.

## Fix Focus Areas
- src/quant.rs[955-968]
- CHANGELOG.md[14-29]

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


Grey Divider

Previous review results

Review updated until commit 22551af

Results up to commit cdcbe43


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (1) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0)


Remediation recommended
1. Undocumented row length cap ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
RankQuant::search_asymmetric_subset_batched_serial_into enforces row_len <= n_vectors for every
CSR row, but this constraint is not documented in the public doc comment and therefore can cause
surprising panics for callers providing their own candidate sets. This is especially relevant
because the doc explicitly states duplicates are allowed but doesn’t mention the length bound that
duplicates can trigger.
Code

src/quant.rs[R886-895]

+    /// `queries` is `nq * dim`. Candidates are CSR: `candidate_offsets.len()
+    /// == nq + 1`, row `qi` is `candidates[candidate_offsets[qi]..candidate_offsets[qi+1]]`.
+    /// Output is rectangular: `out_k = k.min(self.len())`, and both output
+    /// buffers MUST have length `nq * out_k`. Underfull rows are sentinel-padded
+    /// (`NEG_INFINITY` / `-1`). Duplicate candidate ids are scored independently.
+    /// Tie policy: `(score desc, global row-id asc)`.
+    ///
+    /// Panics on any contract violation (CSR malformed, candidate id out of
+    /// range, non-finite query, wrong output-buffer length).
+    #[allow(clippy::too_many_arguments)] // arity is intrinsic to the caller-owned buffered contract (CSR inputs + scratch + two output buffers)
Evidence
The batched API docs list panic conditions but omit the per-row candidate-count requirement, while
the CSR validator enforces row_len <= self.n_vectors. The single-query subset API documents and
asserts the same bound, indicating the batched docs should mirror that public contract.

src/quant.rs[881-895]
src/quant.rs[709-734]
src/quant.rs[647-652]
src/quant.rs[677-682]

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

### Issue description
`search_asymmetric_subset_batched_serial_into` panics when any CSR row contains more than `self.n_vectors` candidates, but the public docs for the method do not mention this precondition in the contract or the panic list. This creates an API footgun for integrators supplying caller-owned candidate sets.

### Issue Context
The single-query `search_asymmetric_subset` explicitly documents and asserts the same bound (to prevent duplicate-heavy candidate lists from allocating scratch larger than a full scan). The batched CSR validator mirrors this bound via `validate_csr_batch`, but the batched API doc comment currently omits it.

### Fix Focus Areas
- src/quant.rs[881-895]
- src/quant.rs[709-734]
- src/quant.rs[647-652]

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


2. Batched docs use SignBitmap stage 📎 Requirement gap ⚙ Maintainability
Description
The new batched subset-rerank documentation only demonstrates a fully ordvec-owned two-stage flow
(SignBitmap candidate generation → batched subset rerank), and does not include an example showing
construction of the CSR candidate batch from an external ANN/filter stage. This misses the required
integration-oriented documentation and may mislead integrators about the intended caller-owned
candidate workflow.
Code

src/quant.rs[R955-965]

+    /// # Example
+    /// ```no_run
+    /// use ordvec::{RankQuant, SignBitmap};
+    /// # let (dim, queries, k) = (768usize, vec![0.0f32; 768 * 3], 10usize);
+    /// # let sign = SignBitmap::new(dim);
+    /// # let rq = RankQuant::new(dim, 2);
+    /// let cb = sign.top_m_candidates_batched_serial_csr(&queries, 256);
+    /// let results = rq.search_asymmetric_subset_batched_serial(
+    ///     &queries, &cb.offsets, &cb.candidates, k,
+    /// );
+    /// for qi in 0..results.nq {
Evidence
PR Compliance ID 7 requires documentation demonstrating an external ANN/filter/candidate stage
feeding a caller-constructed batched candidate representation into batched subset rerank. The added
example explicitly generates candidates via SignBitmap::top_m_candidates_batched_serial_csr and
then immediately calls the batched rerank, and the changelog similarly frames the intended usage
around pairing the two ordvec primitives rather than showing external candidate generation.

Batched subset rerank documentation shows use after external ANN/filter/candidate stage
src/quant.rs[955-965]
CHANGELOG.md[14-29]

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 docs for the new batched subset rerank show a `SignBitmap`-generated candidate batch, but the compliance requirement is to document use after an *external* ANN/filter/candidate stage, including how to build the CSR (`offsets` + flat `candidates`) representation.

## Issue Context
PR Compliance ID 7 requires documentation that demonstrates ecosystem integration where ordvec only performs scoring on caller-owned candidates produced by an external system (ANN index, metadata filter, shard planner, etc.). The current example and changelog entry demonstrate an ordvec-internal candidate stage instead.

## Fix Focus Areas
- src/quant.rs[955-968]
- CHANGELOG.md[14-29]

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


3. Redundant output buffer clears ✓ Resolved 🐞 Bug ➹ Performance
Description
search_asymmetric_subset_batched_serial_into clears the entire output buffers up front, but each
non-empty row is cleared again inside TopK::finalize_into_with_scratch during rerank. This adds
redundant O(nq*out_k) writes and extra memory bandwidth on the hot path.
Code

src/quant.rs[R928-935]

+        // Preclear the whole output so short / zero-candidate rows never leak
+        // stale caller buffer contents.
+        for s in out_scores.iter_mut() {
+            *s = f32::NEG_INFINITY;
+        }
+        for i in out_indices.iter_mut() {
+            *i = -1;
+        }
Evidence
The batched function explicitly preclears the full output buffers. For non-empty rows,
subset_rerank_row_into calls TopK::finalize_into_with_scratch, which clears the provided row
slice again before filling it, making the initial clear redundant for those rows.

src/quant.rs[928-947]
src/quant.rs[864-871]
src/util.rs[553-559]

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 batched `_into` API clears `out_scores`/`out_indices` for the entire batch before processing rows. For non-empty rows, `subset_rerank_row_into` ultimately calls `TopK::finalize_into_with_scratch`, which clears the per-row slices again before writing results. This duplicates work for every non-empty row.

### Issue Context
You still need to prevent stale data for empty candidate rows (because `subset_rerank_row_into` early-returns for `m == 0` without writing). A minimal fix is to delete the full-buffer preclear and instead explicitly clear only the rows with `row.is_empty()` inside the per-row loop; non-empty rows can rely on `finalize_into_with_scratch`’s existing clear.

### Fix Focus Areas
- src/quant.rs[928-947]
- src/quant.rs[761-768]
- src/util.rs[553-559]

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


Qodo Logo

@codecov

codecov Bot commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add caller-owned serial CSR batched two-stage search primitives (ordvec 0.5.0)
✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

Walkthroughs

Description
• Add serial CSR candidate generation and buffered batched subset rerank APIs.
• Introduce reusable scratch/TopK reuse to eliminate rerank allocations after warmup.
• Expand determinism/edge-case tests and bump release/docs to ordvec 0.5.0.
Diagram
graph TD
  Caller(["Caller runtime"]) -->|"queries,m"| Sign["SignBitmap"] -->|"CSR candidates"| CSR[("CandidateBatch") ] -->|"offsets+candidates"| Rank["RankQuant"] -->|"k"| Out["Scores/indices"]
  Caller -->|"reuse"| Scratch[["SubsetScratch"]] --> Rank

  subgraph Legend
    direction LR
    _caller(["Caller"]) ~~~ _mod["Module"] ~~~ _buf[("Buffer") ] ~~~ _scratch[["Scratch"]]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Provide a bundled two-stage helper API
  • ➕ Simplifies integration for users who don't need custom scheduling
  • ➕ Can enforce the full end-to-end contract in one entry point
  • ➖ Conflicts with the stated goal of caller-owned parallelism/orchestration
  • ➖ Harder to keep composable with existing independently-parallel APIs
2. Use Vec> for candidates instead of CSR
  • ➕ More ergonomic to construct/inspect per query
  • ➕ Avoids CSR offset validation burden
  • ➖ More allocations and pointer chasing; worse for FFI/bindings handoff
  • ➖ Harder to share a single flat buffer across runtimes/pools
3. Expose an iterator/streaming rerank interface
  • ➕ Could avoid materializing candidate sets for some pipelines
  • ➕ Naturally supports early stopping and backpressure
  • ➖ More complex API surface and harder to bind across languages
  • ➖ Less compatible with SIMD gather/pack patterns used by subset rerank

Recommendation: Keep the PR’s CSR + allocation-free _into design: it matches the caller-owned scheduling goal, minimizes allocations/FFI friction, and stays composable (stage-1 and stage-2 remain independently usable). A bundled helper could be added later as a thin convenience wrapper without changing these primitives.

Grey Divider

File Changes

Enhancement (4)
lib.rs Re-export CandidateBatch and SubsetScratch in public API +2/-0

Re-export CandidateBatch and SubsetScratch in public API

• Exposes the new CandidateBatch and SubsetScratch types at the crate root for downstream callers and bindings.

src/lib.rs


quant.rs Add serial batched subset rerank APIs with reusable SubsetScratch +315/-55

Add serial batched subset rerank APIs with reusable SubsetScratch

• Introduces SubsetScratch for allocation-free reuse, refactors subset rerank into a shared per-row helper, adds CSR validation, and implements 'search_asymmetric_subset_batched_serial_into' plus an allocating wrapper returning SearchResults.

src/quant.rs


sign_bitmap.rs Add CandidateBatch CSR type and serial batched candidate generation +115/-0

Add CandidateBatch CSR type and serial batched candidate generation

• Adds CandidateBatch (CSR: candidates + offsets) with helper methods, and implements 'top_m_candidates_batched_serial_csr' to generate per-query top-m candidates without entering rayon.

src/sign_bitmap.rs


util.rs Add allocation-free normalization and TopK reuse/finalize scratch +136/-14

Add allocation-free normalization and TopK reuse/finalize scratch

• Adds 'l2_normalise_into' for reuse-friendly query normalization, extends TopK with 'reset_with_tie_keys' and 'finalize_into_with_scratch', and adds tests to ensure behavior parity and capacity reuse.

src/util.rs


Tests (2)
determinism_contract.rs Add determinism/duplicate-candidate contract test for batched subset rerank +39/-0

Add determinism/duplicate-candidate contract test for batched subset rerank

• Verifies that the batched '_into' subset rerank preserves duplicates and applies the tie policy '(score desc, global id asc)', and matches the single-query subset rerank on the same candidate row.

tests/determinism_contract.rs


two_stage.rs Add CSR parity, edge-case, allocation-free, and cross-tier tests +400/-0

Add CSR parity, edge-case, allocation-free, and cross-tier tests

• Adds extensive integration coverage for serial CSR candidate generation and serial batched subset rerank, including CSR invariants, malformed-input panics, sentinel padding behavior, scratch capacity stability, wrapper parity, and scalar-reference parity across SIMD tiers.

tests/index/two_stage.rs


Documentation (3)
CHANGELOG.md Document ordvec 0.5.0 caller-owned two-stage primitives +26/-0

Document ordvec 0.5.0 caller-owned two-stage primitives

• Adds a 0.5.0 release section describing the new serial CSR candidate generation and serial batched subset rerank APIs, including the allocation-free '_into' contract and design notes.

CHANGELOG.md


README.md Update dependency snippet to ordvec 0.5 +1/-1

Update dependency snippet to ordvec 0.5

• Changes the README dependency example from ordvec 0.4 to 0.5 to match the new release line.

README.md


THREAT_MODEL.md Refresh threat model version/status to v0.5.0 +1/-1

Refresh threat model version/status to v0.5.0

• Updates the maintained threat model status line to reflect the 0.5.0 release date/version.

THREAT_MODEL.md


Other (9)
Cargo.toml Bump ordvec crate version to 0.5.0 +1/-1

Bump ordvec crate version to 0.5.0

• Updates the crate version from 0.4.0 to 0.5.0 for the release.

Cargo.toml


Cargo.toml Bump ordvec-ffi version to 0.5.0 +1/-1

Bump ordvec-ffi version to 0.5.0

• Updates the FFI crate version to remain lockstep with the root ordvec version.

ordvec-ffi/Cargo.toml


Cargo.toml Bump ordvec-manifest-python version to 0.5.0 +1/-1

Bump ordvec-manifest-python version to 0.5.0

• Updates the Rust-side Python bindings crate version for the 0.5.0 lockstep release.

ordvec-manifest-python/Cargo.toml


pyproject.toml Bump ordvec-manifest Python package version to 0.5.0 +1/-1

Bump ordvec-manifest Python package version to 0.5.0

• Updates the Python packaging metadata version to 0.5.0.

ordvec-manifest-python/pyproject.toml


__init__.py Update ordvec_manifest __version__ to 0.5.0 +1/-1

Update ordvec_manifest version to 0.5.0

• Bumps the Python module version constant to 0.5.0.

ordvec-manifest-python/python/ordvec_manifest/init.py


Cargo.toml Bump ordvec-manifest version and dependency to 0.5.0 +2/-2

Bump ordvec-manifest version and dependency to 0.5.0

• Updates ordvec-manifest's package version and the path dependency on ordvec to 0.5.0.

ordvec-manifest/Cargo.toml


Cargo.toml Bump ordvec-python bindings crate version to 0.5.0 +1/-1

Bump ordvec-python bindings crate version to 0.5.0

• Updates the Rust crate version for the Python bindings package in lockstep with ordvec 0.5.0.

ordvec-python/Cargo.toml


pyproject.toml Bump ordvec Python package version to 0.5.0 +1/-1

Bump ordvec Python package version to 0.5.0

• Updates the Python packaging metadata version to 0.5.0.

ordvec-python/pyproject.toml


__init__.py Update ordvec Python __version__ to 0.5.0 +1/-1

Update ordvec Python version to 0.5.0

• Bumps the Python module version constant to 0.5.0.

ordvec-python/python/ordvec/init.py


Grey Divider

Qodo Logo

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

Copy link
Copy Markdown

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 bumps the version of the ordvec crate and its bindings to 0.5.0 and introduces caller-owned serial batched/buffered two-stage primitives. These include SignBitmap::top_m_candidates_batched_serial_csr for serial CSR candidate generation, and RankQuant::search_asymmetric_subset_batched_serial (along with its allocation-free _into variant) for serial batched subset reranking, supported by the new CandidateBatch and SubsetScratch types. The review feedback focuses on two performance optimizations in src/quant.rs: first, avoiding redundant zero-initialization of the sub_packed scratch buffer by using reserve and extend_from_slice instead of resize and copy_from_slice; second, eliminating the redundant full-buffer preclear of output scores and indices by clearing them only for empty rows within the query loop.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/quant.rs
Comment thread src/quant.rs Outdated
Address gemini review on #208 (two medium findings):
- subset_rerank_row_into gather: clear + reserve + extend_from_slice instead of
  resize(_, 0) + indexed copy, avoiding zero-init of bytes immediately overwritten
  (still allocation-free after warmup).
- search_asymmetric_subset_batched_serial_into: drop the redundant whole-buffer
  preclear; each row is written once (non-empty via finalize_into_with_scratch,
  empty rows sentinel-padded in-loop). Validation still precedes all writes, so
  the validation-panic-leaves-buffers-intact guarantee holds.

No behavior change; full suite + cross-tier parity + allocation-free tests green.

Signed-off-by: Nelson Spence <nelson@projectnavi.ai>
@Fieldnote-Echo

Copy link
Copy Markdown
Owner Author

/agentic_review

@qodo-code-review

qodo-code-review Bot commented Jun 13, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 5ea2804

Address qodo review on #208 (requirement gap): the CSR per-row length bound
(each row, duplicates included, must be <= self.len()) and the full panic
contract were undocumented on search_asymmetric_subset_batched_serial_into —
surprising for callers supplying their own candidate sets. Document the cap and
expand the panic list; cross-reference from the allocating wrapper. Docs-only.

Signed-off-by: Nelson Spence <nelson@projectnavi.ai>
@Fieldnote-Echo

Copy link
Copy Markdown
Owner Author

/agentic_review

@qodo-code-review

qodo-code-review Bot commented Jun 13, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 22551af

@Fieldnote-Echo

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

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.

api: batched RankQuant subset rerank for host-owned candidate sets

1 participant