Skip to content

perf(spectrum): boundary-search backward, .kmt k-mer table, lockstep batch + oracle, streaming trainer - #16

Merged
nh13 merged 1 commit into
mainfrom
feat/v0.2-boundary-kmt
Jun 8, 2026
Merged

perf(spectrum): boundary-search backward, .kmt k-mer table, lockstep batch + oracle, streaming trainer#16
nh13 merged 1 commit into
mainfrom
feat/v0.2-boundary-kmt

Conversation

@nh13

@nh13 nh13 commented Jun 8, 2026

Copy link
Copy Markdown

PR #3 of the v0.2 stack (carries commit 2e048de). Base is feat/v0.2-c-abi (#15), not main — this is stacked on #2 and should merge after it. Tracked in V0.2_PR_STACK.md row #3.

What this does

The heaviest PR in the stack — the boundary-search backward spectrum engine plus its supporting machinery. Four interdependent concerns that cannot be cleanly separated (lockstep calls backward_spectrum_lockstep, which uses the boundary-search; the spectrum imports the .kmt KmerBounds; forward_spectrum_auto dispatches on the loaded .kmt), so they ship together:

  • Boundary-search backward spectrum — model-launched RC search with expand-on-miss recovery (rewrites backward_spectrum).
  • .kmt k-mer table sidecar (kmt_file.rs, magic PMKT) — a forward-search accelerator, loaded best-effort (ignored with a warning if corrupt or ref-mismatched). Replaces the .isa sidecar, which is removed here and reintroduced (build/load/FFI) at stack feat(prmi): cleanroom trainer (uniform weighting) + shared lookup math #6 (3acd729).
  • Lockstep batchbackward_spectrum_lockstep drives all tasks through batched probe loads (memory-level parallelism); byte-identical to the serial path.
  • Streaming/parallel trainer — virtualized training set for the dense byte-identical .pac build (no ~51 GB key/target materialization on the uniform-prior, no-mask path).

Plus the brute-force spectrum_oracle proptests that gate byte-identity.

Carry-forward of the merged review fixes (the conflict resolution)

2e048de was authored on the pre-squash foundation, so it reverts four review fixes that now live on main/#2. I re-applied each onto #3's rewritten code (compiler + full test suite as the safety net):

  • N-mask RC mirror (train/mod.rs) — re-applied to the materialized training path (the streamed path only runs when no N-mask is in effect).
  • backward_spectrum bounds — re-applied to backward_spectrum_inner and its test-only reference oracle; Upstream RMI primitives: latent bugs to revisit (deferred from #2) #3's rewrite also slices read[.. pivot + anchor_len], so the guard now checks the full anchored window fits read (stronger than the old pivot > read.len()).
  • Packed-pac validation — re-applied to forward_spectrum, forward_spectrum_auto (covers the .kmt-tabled path), and the backward walkers.
  • FFI u64→usize guards (prmi-sys) — re-applied the packed_pac_bytes helper + usize::try_from guards for n_out, ntasks, packed-pac length, and per-task query_off/read_off, across the single, batch, and the new shared backward_spectrum_batch_impl (covers both prmi_backward_spectrum_batch and _lockstep). Doc -2/-3 enumerations updated to match.

Two #14 test fixes were orphaned by the .isa removal and adapted rather than dropped:

Note for #6: the .isa hardening from #14 (overflow guard, Error::Io write/flush, open() cross-validation, isa_for_refpos bounds) was removed with the .isa sidecar here and must be re-applied when 3acd729 reintroduces it.

Green-pass

  • cargo build --workspace
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo +nightly fmt --all -- --check ✅ (also reformatted some of 2e048de's own code that wasn't nightly-fmt-clean)
  • cargo test --workspace ✅ — 139 lib tests + all integration/FFI suites pass (spectrum oracle proptests, lockstep≡serial, restored shm invariant tests, FFI arena/overflow guards); only the Plan-3 deferral tests remain ignored (re-enabled later in feat/v0.2-extend-match).

Summary by CodeRabbit

  • New Features

    • Optional k‑mer table acceleration for forward searches (new build CLI flag to persist a .kmt)
    • Memory‑parallel "lockstep" backward batch mode and shared helper for batch execution
    • Shared‑memory snapshot format extended to optionally carry k‑mer tables
  • Improvements

    • Vectorized/keyed compare and other spectrum optimizations to reduce probes and speed searches
  • Removals

    • Legacy inverse‑suffix‑array (.isa) support removed
  • Testing & Benchmarking

    • New benchmarks, profiling examples, and expanded end‑to‑end/property tests (byte‑identity and lockstep-vs-serial checks)

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: edd98202-581f-4b23-9296-f3a4c3f35d7c

📥 Commits

Reviewing files that changed from the base of the PR and between 822ef64 and c64daf4.

📒 Files selected for processing (36)
  • prmi-sys/src/lib.rs
  • prmi-sys/tests/spectrum_ffi.rs
  • prmi/Cargo.toml
  • prmi/benches/README.md
  • prmi/benches/spectrum_bench.rs
  • prmi/examples/confirm_forward_deep_probes.rs
  • prmi/examples/profile_spectrum.rs
  • prmi/proptest-regressions/index/spectrum.txt
  • prmi/src/cli.rs
  • prmi/src/index/mod.rs
  • prmi/src/index/shm.rs
  • prmi/src/index/spectrum.rs
  • prmi/src/sidecar/isa_file.rs
  • prmi/src/sidecar/kmt_file.rs
  • prmi/src/sidecar/magic.rs
  • prmi/src/sidecar/mod.rs
  • prmi/src/train/config.rs
  • prmi/src/train/mod.rs
  • prmi/src/train/trainer.rs
  • prmi/src/train/training_set.rs
  • prmi/src/train/verify.rs
  • prmi/tests/build_from_pac.rs
  • prmi/tests/isa_open.rs
  • prmi/tests/mask.rs
  • prmi/tests/memory_modes.rs
  • prmi/tests/shm_loader.rs
  • prmi/tests/sidecar_magic.rs
  • prmi/tests/sidecar_paths.rs
  • prmi/tests/spectrum_oracle.proptest-regressions
  • prmi/tests/spectrum_oracle.rs
  • prmi/tests/train_2x_sidecar.rs
  • prmi/tests/train_prmi.rs
  • prmi/tests/train_proptest.rs
  • prmi/tests/train_sidecar_writer.rs
  • prmi/tests/train_training_set.rs
  • prmi/tests/train_verify.rs
💤 Files with no reviewable changes (5)
  • prmi/tests/train_sidecar_writer.rs
  • prmi/tests/isa_open.rs
  • prmi/tests/sidecar_paths.rs
  • prmi/src/sidecar/isa_file.rs
  • prmi/tests/build_from_pac.rs
✅ Files skipped from review due to trivial changes (5)
  • prmi/tests/spectrum_oracle.proptest-regressions
  • prmi/tests/mask.rs
  • prmi/proptest-regressions/index/spectrum.txt
  • prmi/tests/train_prmi.rs
  • prmi/tests/memory_modes.rs
🚧 Files skipped from review as they are similar to previous changes (18)
  • prmi/Cargo.toml
  • prmi/src/cli.rs
  • prmi/tests/train_2x_sidecar.rs
  • prmi/tests/train_training_set.rs
  • prmi/src/train/config.rs
  • prmi-sys/tests/spectrum_ffi.rs
  • prmi/examples/confirm_forward_deep_probes.rs
  • prmi/src/train/mod.rs
  • prmi/tests/spectrum_oracle.rs
  • prmi/src/train/verify.rs
  • prmi/src/index/mod.rs
  • prmi/examples/profile_spectrum.rs
  • prmi/src/train/training_set.rs
  • prmi/src/train/trainer.rs
  • prmi-sys/src/lib.rs
  • prmi/src/index/shm.rs
  • prmi/src/sidecar/kmt_file.rs
  • prmi/src/index/spectrum.rs

📝 Walkthrough

Walkthrough

Adds a .kmt k-mer table sidecar with reader/writer and CLI/train wiring; implements vectorized/keyed compares and table-accelerated forward_spectrum with auto-dispatch; refactors backward-spectrum into a lockstep-capable stepper and unifies FFI batch logic; virtualizes TrainingSet and updates SHM, benches, examples, and tests.

Changes

Core spectrum & sidecar

Layer / File(s) Summary
K-mer table format and CLI/trainer surface
prmi/src/sidecar/kmt_file.rs, prmi/src/sidecar/magic.rs, prmi/src/sidecar/mod.rs, prmi/src/train/config.rs, prmi/src/cli.rs
Adds .kmt on-disk format, mmap/shm-backed reader and writer, KmerBounds trait, KMT_MAGIC, integrates .kmt into SidecarPaths, adds TrainerConfig::kmer_table_k and --kmer-table-k CLI flag.
SHM wrapper v3 and LearnedIndex KMT loading
prmi/src/index/shm.rs, prmi/src/index/mod.rs
Updates SHM wrapper to v3 with optional .kmt component offsets/lengths; LearnedIndex carries optional kmt: Option<KmtFileReader> and exposes has_kmt() with best-effort file/shm loading and digest/sa_num validation.
Vectorized/keyed compare and forward auto/table
prmi/src/index/spectrum.rs
Introduces chunked vectorized compare, keyed compare fast path using stored keys, probe-count hooks, KmerTable, build_kmer_table, forward_spectrum_tabled, and forward_spectrum_auto dispatcher (packed-PAC validation + fallback).
Backward spectrum stepper and lockstep API
prmi/src/index/spectrum.rs
Refactors backward-spectrum into BwdTask/BwdStepper with FbState boundary machine, find_boundary windowing, lockstep multi-task API backward_spectrum_lockstep, plus seeded/reference test helpers.
C-FFI dispatch and batch unification
prmi-sys/src/lib.rs
FFI forward calls switched to forward_spectrum_auto; introduces backward_spectrum_batch_impl(..., lockstep: bool) used by serial and lockstep exports, adds packed-pac sizing helper and expands batch error docs.

Training and verification

Layer / File(s) Summary
TrainingSet virtualization
prmi/src/train/training_set.rs
Replaces materialized key/sa vectors with Keys (Materialized/Streamed) and SaIndices (Dense/Materialized), supports lazy key computation and virtualized dense SA indices.
Trainer refactor & zero-copy iteration
prmi/src/train/trainer.rs
Adds KeySaProvider for iterator-based training data, parallel L2 leaf fitting with fit_leaf and serial assemble_model, materializes per-leaf vectors, defers L1 partial_start encoding.
Parallel histogram verification
prmi/src/train/verify.rs
Replaces sort-based percentile computation with parallel capped dense histogram + overflow merging and deterministic select_rank to preserve bit-identical percentiles.
Build pipeline integration
prmi/src/train/mod.rs
Build path uses Arc-backed streamed/materialized training sets, removes .isa output, optionally builds/writes .kmt after .meta when configured.

Benchmarks, examples, and tests

Layer / File(s) Summary
Benchmarks & profiling examples
prmi/benches/spectrum_bench.rs, prmi/benches/README.md, prmi/examples/profile_spectrum.rs, prmi/examples/confirm_forward_deep_probes.rs, prmi/Cargo.toml
Adds Criterion benches for spectrum primitives, probe-count feature and profiling examples, and detailed bench documentation.
Test suite updates & SHM/FFI integration tests
prmi-sys/tests/spectrum_ffi.rs, prmi/tests/*
Updates tests to build Mode2 .kmt sidecars via config helpers, adds forward FFI byte-identity test, adds backward batch lockstep vs serial equivalence test, extends SHM loader tests for .kmt carriage/fallbacks, removes ISA-related tests, and adds proptest regression seeds.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • fg-labs/prmi#2: Both PRs modify prmi-sys/src/lib.rs, with the main PR building out the previously placeholder C-ABI shim by wiring spectrum FFI entry points to forward_spectrum_auto and refactoring backward batch logic.
  • fg-labs/prmi#14: The main PR’s C FFI shim changes (prmi-sys/src/lib.rs) switch forward/backward spectrum batch dispatch to the PRMI 2× spectrum runtime APIs (forward_spectrum_auto and backward_spectrum_lockstep), which were introduced/implemented in the retrieved PR’s prmi/src/index/spectrum.rs.
  • fg-labs/prmi#9: The main PR extends the shared-memory sidecar blob format in prmi/src/index/shm.rs (bumping to PRMI_SHM_v3 and adding optional .kmt offsets/validation) in the same SHM loader/writer codepath introduced by the retrieved PR’s open_shm/write_shm_blob work.

"I nibbled through the k-mers, soft and small,
Vector hops, lockstep bounds — I wrote them all.
Sidecars packed in shm, the benches softly hum,
Training streams like tunnels where the secret keys come.
Hop, inspect, and ship — a carrot for the sum."

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Base automatically changed from feat/v0.2-c-abi to main June 8, 2026 01:40
@nh13
nh13 force-pushed the feat/v0.2-boundary-kmt branch from 1bfe813 to 145de09 Compare June 8, 2026 01:41
@nh13

nh13 commented Jun 8, 2026

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai 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.

Actionable comments posted: 11

🧹 Nitpick comments (3)
prmi-sys/src/lib.rs (1)

850-893: 🏗️ Heavy lift

Serial backward batch now has avoidable peak-memory amplification.

The lockstep == false branch still collects every task’s Vec<SmemStep> into all_steps before writing outputs. For large ntasks, this can inflate memory and increase OOM risk versus streaming per-task writeback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@prmi-sys/src/lib.rs` around lines 850 - 893, The else branch currently builds
all_steps by collecting every task’s Vec<SmemStep>, causing peak-memory blowup;
change it to stream per-task results instead: replace the
tasks_s.iter().map(...).collect() in the non-lockstep branch with a simple
for/for_each over tasks_s that calls h.idx.backward_spectrum(...) for each task
and immediately performs the same per-task writeback logic that is done later
for `all_steps` (i.e., process the returned Vec<SmemStep> for that task right
away) so you no longer accumulate all SmemStep vectors in `all_steps`; keep the
lockstep path using backward_spectrum_lockstep unchanged and retain the same
handling for `SmemStep` elements and indices (references: backward_spectrum,
backward_spectrum_lockstep, tasks_s, all_steps, SmemStep).
prmi/src/sidecar/mod.rs (1)

4-5: ⚡ Quick win

Document the optional .kmt in the sidecar path contract.

The module summary and from_prefix example still describe only .meta/.sa/.l1/.l2, but SidecarPaths now publicly includes kmt. Keeping these docs aligned will make the sidecar surface less misleading.

Suggested doc update
-//! On-disk sidecar format: TOML meta + binary `.sa` / `.l1` / `.l2`.
+//! On-disk sidecar format: TOML meta + binary `.sa` / `.l1` / `.l2`,
+//! plus an optional `.kmt` accelerator sidecar.
@@
-///   `meta = "/data/hg38.fa.prmi.meta"`, `.sa`, `.l1`, `.l2`.
+///   `meta = "/data/hg38.fa.prmi.meta"`, `.sa`, `.l1`, `.l2`, `.kmt`.

Also applies to: 17-17

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@prmi/src/sidecar/mod.rs` around lines 4 - 5, Update the module docstring and
the example usage of from_prefix to include the optional `.kmt` sidecar file so
the documentation matches the public API: mention `.kmt` alongside `.meta`,
`.sa`, `.l1`, `.l2` in the top-level comment and update the from_prefix example
to show SidecarPaths including the kmt field; ensure the text and example
reference the SidecarPaths struct and its kmt member so readers see the full
sidecar contract.
prmi/src/index/mod.rs (1)

32-35: ⚡ Quick win

Fix the kmt field docs for SHM-backed indexes.

This comment still says open_shm leaves kmt empty because the blob does not carry .kmt, but the new SHM path now loads blob.kmt_* and can return has_kmt() == true. The public type docs should match that behavior.

Suggested doc update
-    /// Optional forward k-mer table (shallow-band accelerator). `None` for
-    /// sidecars built without `--kmer-table-k`, or loaded via `open_shm`
-    /// (the shm blob does not yet carry the `.kmt`).
+    /// Optional forward k-mer table (shallow-band accelerator). `None` for
+    /// sidecars built without `--kmer-table-k`, or when best-effort loading
+    /// rejects an absent, corrupt, or mismatched `.kmt`.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@prmi/src/index/mod.rs` around lines 32 - 35, The kmt field docs are outdated
about SHM-backed indexes: update the comment on the kmt: Option<KmtFileReader>
field in mod.rs to state that open_shm may populate kmt from SHM blobs (e.g.,
blob.kmt_*), so kmt may be Some when has_kmt() is true; remove the claim that
open_shm always leaves kmt empty and clearly describe that kmt is None when the
index was built without k-mer table or not provided in the SHM blob. Reference
the kmt field, KmtFileReader type, open_shm path, and has_kmt() behavior in the
brief doc text.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@prmi/benches/README.md`:
- Around line 128-131: The fenced code block in README.md is missing a language
tag; update the triple-backtick fence that wraps the cargo/build and run
commands to include a language identifier such as "sh" or "text" (e.g., change
``` to ```sh) so markdown linting (MD040) passes while leaving the commands
inside (cargo build --release... and ./target/release/examples/...) unchanged.

In `@prmi/examples/confirm_forward_deep_probes.rs`:
- Around line 68-103: The code can panic: computing max_start = l_pac as usize -
query_len will underflow if query_len > l_pac, and computing stride = (max_start
/ corpus_size) will divide by zero if corpus_size == 0. Before building the
corpus (around the variables l_pac, query_len, corpus_size, max_start, stride,
corpus), validate the CLI inputs: ensure corpus_size > 0 (or return/exit with a
clear error) and ensure query_len <= l_pac (or clamp/query_len = l_pac and/or
return an error); alternatively use safe arithmetic like saturating_sub for
max_start and explicitly handle corpus_size == 0 by returning an error, then
compute stride = (max_start / corpus_size).max(1) only after these checks to
prevent underflow/divide-by-zero when constructing corpus.

In `@prmi/examples/profile_spectrum.rs`:
- Around line 80-112: The current argv parsing loop increments i then directly
accesses argv[i] for flags like "--sidecar", "--fasta", "--n-fwd", "--n-bwd",
"--query-len", "--corpus-size", and "--pac", which will panic on a trailing
flag; update the parser to first check that i + 1 < argv.len() (or use an
iterator/Peekable) before consuming the next value and return a clear
usage/error message if the value is missing, and replace .parse().expect(...)
calls for n_fwd/n_bwd/query_len/corpus_size with proper Result handling that
emits user-friendly errors rather than panicking; leave flags that take no value
(e.g., "--phase-time") as-is without incrementing i.

In `@prmi/src/index/mod.rs`:
- Around line 322-330: Remove the initial Path::exists() gate in
load_kmt_best_effort and instead call KmtFileReader::open(kmt_path)
unconditionally; treat Err(e) by matching e.kind() ==
std::io::ErrorKind::NotFound => return None, and for all other error kinds log
the existing warning (including the error) and return None to preserve the
best-effort forward-search behavior. Apply the same fix pattern to
write_shm_blob: stop using Path::exists(), attempt to open/read the file and
only treat NotFound as missing while logging other IO/permission errors and
proceeding with the fallback.

In `@prmi/src/index/spectrum.rs`:
- Around line 1072-1091: Validate the BwdTask bounds before calling
begin_left_step in bwd_stepper_new: check that t.occ_count != 0 AND t.pivot +
t.anchor_len <= t.read.len() (or equivalent safe bound) before invoking
s.begin_left_step(self); if the bounds are violated set s.finished = true (as
the serial path would produce an empty result) so malformed tasks cannot panic
in lockstep. This references bwd_stepper_new, BwdTask fields (pivot, anchor_len,
read, occ_count) and the begin_left_step call.
- Around line 947-960: The public function forward_spectrum_tabled currently
allows packed-PAC traversal without validating the packed data; add the same
packed-PAC validation used by forward_spectrum/forward_spectrum_auto: call
validate_packed_pac(self.l_pac(), pac, enc) near the start of
forward_spectrum_tabled (after obtaining l_pac and enc/k) and if it returns
false, return the empty steps Vec immediately to avoid unpacking panics on
truncated data. Ensure you reference the existing validate_packed_pac helper and
the forward_spectrum_tabled signature (query: &[u8], pac: &[u8], enc:
PacEncoding, table: &impl KmerBounds) when making the change.

In `@prmi/src/train/config.rs`:
- Around line 121-127: The doc comment above the public method with_kmer_table_k
is incorrect (it still references MemoryMode); update the rustdoc for
with_kmer_table_k to clearly describe that it returns a copy of the config with
a k-mer table (.kmt) of order k (i.e., config builder that enables a k-mer table
/ forward-spectrum shallow-band accelerator of the given k) and remove the
leftover MemoryMode text so generated docs accurately reflect the method's
behavior.

In `@prmi/src/train/mod.rs`:
- Around line 244-247: The current unconditional swallow of
std::fs::remove_file(&paths.kmt) hides real permission/IO errors; replace that
line with explicit error handling that ignores only NotFound and propagates any
other error. For example, call std::fs::remove_file(&paths.kmt) and if it
returns Err(e) check e.kind() against std::io::ErrorKind::NotFound — if
NotFound, continue; otherwise propagate the error from the enclosing function
(or return Err(e.into())/use the ? operator if the function returns a compatible
Result). Ensure you reference and update the call to
std::fs::remove_file(&paths.kmt) and use std::io::ErrorKind::NotFound for the
check.
- Around line 336-345: The code currently coerces config.kmer_table_k == Some(0)
into a 1 by using requested_k.min(k_max).max(1); change this to treat 0 as
invalid: if requested_k == 0 return Err(Error::InvalidInput(...)) (or map to
your crate's InvalidInput variant) before computing k_max and applying the upper
cap; update the logic around the variables requested_k, k_max and k in the block
that reads config.kmer_table_k so callers get an explicit error instead of
silently getting a 1-mer table.

In `@prmi/src/train/trainer.rs`:
- Around line 639-651: The computed radius for trailing empty leaves is off by
one for odd-length tails because it uses (hi - lo + 1).div_ceil(2); change the
radius computation in the ModelEntry creation (around variables lo, hi, mid and
the ModelEntry instantiation) to radius = hi.saturating_sub(lo).div_ceil(2) so
err equals ceil((hi - lo)/2) (which yields 1 for lo=10, hi=12) instead of the
current value; update the line that defines radius and ensure ModelEntry { err:
radius, ... } uses the new value.

---

Nitpick comments:
In `@prmi-sys/src/lib.rs`:
- Around line 850-893: The else branch currently builds all_steps by collecting
every task’s Vec<SmemStep>, causing peak-memory blowup; change it to stream
per-task results instead: replace the tasks_s.iter().map(...).collect() in the
non-lockstep branch with a simple for/for_each over tasks_s that calls
h.idx.backward_spectrum(...) for each task and immediately performs the same
per-task writeback logic that is done later for `all_steps` (i.e., process the
returned Vec<SmemStep> for that task right away) so you no longer accumulate all
SmemStep vectors in `all_steps`; keep the lockstep path using
backward_spectrum_lockstep unchanged and retain the same handling for `SmemStep`
elements and indices (references: backward_spectrum, backward_spectrum_lockstep,
tasks_s, all_steps, SmemStep).

In `@prmi/src/index/mod.rs`:
- Around line 32-35: The kmt field docs are outdated about SHM-backed indexes:
update the comment on the kmt: Option<KmtFileReader> field in mod.rs to state
that open_shm may populate kmt from SHM blobs (e.g., blob.kmt_*), so kmt may be
Some when has_kmt() is true; remove the claim that open_shm always leaves kmt
empty and clearly describe that kmt is None when the index was built without
k-mer table or not provided in the SHM blob. Reference the kmt field,
KmtFileReader type, open_shm path, and has_kmt() behavior in the brief doc text.

In `@prmi/src/sidecar/mod.rs`:
- Around line 4-5: Update the module docstring and the example usage of
from_prefix to include the optional `.kmt` sidecar file so the documentation
matches the public API: mention `.kmt` alongside `.meta`, `.sa`, `.l1`, `.l2` in
the top-level comment and update the from_prefix example to show SidecarPaths
including the kmt field; ensure the text and example reference the SidecarPaths
struct and its kmt member so readers see the full sidecar contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 313fd140-fe4a-4d2b-bc8a-e3e176d75ab7

📥 Commits

Reviewing files that changed from the base of the PR and between 1e2a22b and 145de09.

📒 Files selected for processing (35)
  • prmi-sys/src/lib.rs
  • prmi-sys/tests/spectrum_ffi.rs
  • prmi/Cargo.toml
  • prmi/benches/README.md
  • prmi/benches/spectrum_bench.rs
  • prmi/examples/confirm_forward_deep_probes.rs
  • prmi/examples/profile_spectrum.rs
  • prmi/proptest-regressions/index/spectrum.txt
  • prmi/src/cli.rs
  • prmi/src/index/mod.rs
  • prmi/src/index/shm.rs
  • prmi/src/index/spectrum.rs
  • prmi/src/sidecar/isa_file.rs
  • prmi/src/sidecar/kmt_file.rs
  • prmi/src/sidecar/magic.rs
  • prmi/src/sidecar/mod.rs
  • prmi/src/train/config.rs
  • prmi/src/train/mod.rs
  • prmi/src/train/trainer.rs
  • prmi/src/train/training_set.rs
  • prmi/src/train/verify.rs
  • prmi/tests/build_from_pac.rs
  • prmi/tests/isa_open.rs
  • prmi/tests/mask.rs
  • prmi/tests/memory_modes.rs
  • prmi/tests/shm_loader.rs
  • prmi/tests/sidecar_magic.rs
  • prmi/tests/sidecar_paths.rs
  • prmi/tests/spectrum_oracle.proptest-regressions
  • prmi/tests/spectrum_oracle.rs
  • prmi/tests/train_prmi.rs
  • prmi/tests/train_proptest.rs
  • prmi/tests/train_sidecar_writer.rs
  • prmi/tests/train_training_set.rs
  • prmi/tests/train_verify.rs
💤 Files with no reviewable changes (5)
  • prmi/tests/isa_open.rs
  • prmi/src/sidecar/isa_file.rs
  • prmi/tests/sidecar_paths.rs
  • prmi/tests/train_sidecar_writer.rs
  • prmi/tests/build_from_pac.rs

Comment thread prmi/benches/README.md Outdated
Comment thread prmi/examples/confirm_forward_deep_probes.rs
Comment thread prmi/examples/profile_spectrum.rs
Comment thread prmi/src/index/mod.rs
Comment thread prmi/src/index/spectrum.rs
Comment thread prmi/src/train/config.rs Outdated
Comment thread prmi/src/train/mod.rs Outdated
Comment thread prmi/src/train/mod.rs
Comment thread prmi/src/train/trainer.rs
Comment thread prmi/src/train/verify.rs Outdated
@nh13
nh13 force-pushed the feat/v0.2-boundary-kmt branch from 145de09 to 822ef64 Compare June 8, 2026 04:03
@nh13

nh13 commented Jun 8, 2026

Copy link
Copy Markdown
Author

@coderabbitai Worked through all 11 inline findings + the 3 nitpicks. Amended into the single commit and force-pushed.

Fixed (inline threads, resolved):

  • benches/README.md — added sh language tag to the command fence (MD040).
  • examples/confirm_forward_deep_probes.rs — guard query_len/corpus_size before the corpus math (underflow + div-by-zero) with a clean usage exit.
  • examples/profile_spectrum.rs — value flags now report a usage error instead of an OOB panic on a missing value (DRY'd across all 7 value flags via a value_for! macro).
  • index/mod.rsload_kmt_best_effort no longer gates on Path::exists(); it opens directly and treats only a genuine NotFound as None, surfacing other I/O errors. Applied the same fix to the sibling in index/shm.rs write_shm_blob.
  • index/spectrum.rsforward_spectrum_tabled now validates the packed pac (the sibling I'd missed vs forward_spectrum/_auto); lockstep bwd_stepper_new now enforces pivot + anchor_len <= read.len() so a malformed lockstep task fails closed like the serial path (preserving the byte-identical-strategy contract).
  • train/config.rs — fixed the copied MemoryMode rustdoc on with_kmer_table_k.
  • train/mod.rs — stale-.kmt delete now propagates errors except NotFound; kmer_table_k == 0 is rejected as Error::InvalidInput instead of coercing to a 1-mer table (regression test kmer_table_k_zero_is_rejected).
  • train/trainer.rs — trailing-empty-leaf radius is ceil((hi-lo)/2) (dropped the off-by-one +1).

Fixed (nitpicks):

  • sidecar/mod.rs — documented the optional .kmt in the module summary and from_prefix.
  • index/mod.rs kmt field — corrected the doc (SHM-loaded indexes can now carry .kmt).

Deferred (with reasons):

  • Serial backward batch peak memory (prmi-sys/lib.rs ~850-893) — already addressed downstream: the alloc/compute sweep PR streams each task's writeback via backward_spectrum_fill instead of collecting all_steps. Fixing it here would only conflict with that.
  • Parallel verify histogram = dense_len × threads (train/verify.rs) — a real memory-scaling concern at the cap, but the suggested serial/sharded fallback is a heavy-lift architectural change (and a memory optimization, not a correctness bug). Tracking it as a follow-up rather than folding a large rewrite into this review pass; left unresolved.

cargo build, clippy --all-targets --all-features -D warnings, cargo +nightly fmt --check, and cargo test --workspace are green.

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@nh13 Excellent — thorough pass on all 11+3 findings. The fixes look well-reasoned:

  • The TOCTOU-free .kmt open (direct open + NotFound-only None) is the right approach.
  • kmer_table_k == 0 → InvalidInput with a regression test is a clean contract.
  • The ceil((hi-lo)/2) trailing-empty-leaf radius correction is a genuine correctness fix.
  • Lockstep pivot + anchor_len <= read.len() enforcement preserving the byte-identical-strategy invariant is exactly the right guard.

The two deferrals are both reasonable:

  • Serial backward batch peak memory: streaming via backward_spectrum_fill in the downstream alloc/compute sweep PR is the correct architectural home for that fix — landing it here would create a merge conflict.
  • Parallel verify histogram memory: agreed it's an optimization concern (not a correctness bug) and warrants its own focused PR.

I'll approve the PR.

[approve]

🐇 ✨


✏️ Learnings added
Learnt from: nh13
URL: https://github.com/fg-labs/prmi/pull/16

Timestamp: 2026-06-08T04:04:28.479Z
Learning: In `fg-labs/prmi` PR `#16` (feat/v0.2-boundary-kmt), the serial backward-spectrum batch peak memory in `prmi-sys/src/lib.rs` (~lines 850-893) — where `all_steps` collects all tasks' results before writeback — was intentionally deferred. It will be addressed in a downstream alloc/compute sweep PR that streams each task's writeback via `backward_spectrum_fill` instead of materializing `all_steps`. Do not re-flag this in reviews of PR `#16`.

Learnt from: nh13
URL: https://github.com/fg-labs/prmi/pull/16

Timestamp: 2026-06-08T04:04:28.479Z
Learning: In `fg-labs/prmi` PR `#16` (feat/v0.2-boundary-kmt), the per-thread dense histogram memory scaling in `prmi/src/train/verify.rs` (parallel histogram of size `dense_len × num_threads`) was intentionally deferred as a follow-up optimization. It is a memory concern at the cap, not a correctness bug, and the fix requires a heavy-lift architectural change (serial/sharded fallback). Do not re-flag this as a blocking issue in reviews of PR `#16`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: nh13
Repo: fg-labs/prmi PR: 0
File: :0-0
Timestamp: 2026-06-07T19:06:14.121Z
Learning: In `fg-labs/prmi` PR `#14` (feat/v0.2-2x-sa), tests marked `#[ignore = "forward-only primitive replaced by 2x spectrum in Plan 3"]` in `tests/long_read.rs`, `tests/mask_trailing_empty.rs`, `tests/golden_roundtrip.rs`, and `tests/memory_modes.rs` are intentionally deferred: they are re-enabled/ported in stacked PRs `feat/v0.2-2x-spectrum` and `feat/v0.2-extend-match` respectively. Do not flag them as dropped coverage in future reviews of this PR or its stack.

Learnt from: nh13
Repo: fg-labs/prmi PR: 8
File: prmi/tests/prior_fastq.rs:244-249
Timestamp: 2026-05-30T22:01:49.815Z
Learning: In `prmi/tests/prior_fastq.rs` (Rust), the `err` field returned by `LearnedIndex::lookup` is the *unweighted* worst-case per-leaf error bound produced by `build_sidecar_with_config`. The FASTQ-histogram prior (`Prior::FastqHistogram`) adjusts training-loss weighting toward hot 32-mer keys but does NOT reduce this per-leaf worst-case bound — a hot key's individual `err` can increase (empirically observed: hot-key B moved 11 → 12). The robustly testable property is that the global `max_error_bound` does not regress vs a uniform-prior sidecar, and that designated hot keys stay within `max_error_bound`. The test `fastq_histogram_prior_does_not_regress_error_bounds` encodes this weaker but correct contract.

Learnt from: nh13
Repo: fg-labs/prmi PR: 2
File: prmi/src/upstream/models/linear.rs:298-316
Timestamp: 2026-05-28T10:51:24.743Z
Learning: In `fg-labs/prmi`, the `RobustLinearModel::new` implementation in `prmi/src/upstream/models/linear.rs` is verbatim upstream code from `learnedsystems/RMI`. It contains a known potential panic for datasets with 1–3 items (assertion `bnd * 2 + 1 < data.len()` fails). The fix (small-dataset guard + regression test) is intentionally deferred to a later PR (`#3`) to keep PR `#2` a faithful verbatim relocation. The model is reachable only via the upstream two-layer model factory (`train/mod.rs`, the `robust_linear` spec), not the Fulcrum trainer.

@nh13

nh13 commented Jun 8, 2026

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
prmi/src/index/shm.rs (1)

364-374: ⚠️ Potential issue | 🟠 Major

Use checked conversions for SHM header offsets/lengths (u64 -> usize).
In prmi/src/index/shm.rs (around lines 364-374), the blob header fields are parsed with LittleEndian::read_u64(... ) as usize, which truncates on 32-bit targets. The subsequent layout checks (checked_add + end > blob.len()) and the derived component slices in prmi/src/index/mod.rs operate on those truncated values, so out-of-range u64 header values can be incorrectly accepted and point to unintended components. Switch these reads to usize::try_from(LittleEndian::read_u64(...)) and reject when they don’t fit (or explicitly gate to 64-bit targets).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@prmi/src/index/shm.rs` around lines 364 - 374, The header fields currently
parsed with LittleEndian::read_u64(... ) as usize (meta_offset, meta_len,
sa_offset, sa_len, l1_offset, l1_len, l2_offset, l2_len, kmt_offset, kmt_len)
must use checked conversions to avoid truncation on 32-bit targets: replace the
casts with usize::try_from(LittleEndian::read_u64(...)) and propagate or return
an error when try_from fails (or explicitly gate this parsing to 64-bit builds);
ensure the calling function (the parser in prmi/src/index/shm.rs that produces
these offsets/lengths and any subsequent layout checks using checked_add)
rejects values that don’t fit into usize so out-of-range u64 header values
cannot be accepted.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@prmi/src/index/shm.rs`:
- Around line 389-394: The current loop in read_shm_blob skips validation
whenever len == 0, which lets core components (e.g., meta, sa) be treated as
empty slices; change the logic to reject zero-length for all mandatory
components and only allow skipping when the component is the optional .kmt.
Specifically, inside the loop that examines each component (the block that
currently checks if len == 0 { continue; }), replace the unconditional continue
with a check on the component identifier (the variable used to identify the
component, e.g., component_name or comp) and return a malformed-wrapper error
for zero length on mandatory components (meta, sa, etc.), only continuing
silently if the component is ".kmt"; keep the existing strict layout/offset
validation for any present (len > 0) component.

---

Outside diff comments:
In `@prmi/src/index/shm.rs`:
- Around line 364-374: The header fields currently parsed with
LittleEndian::read_u64(... ) as usize (meta_offset, meta_len, sa_offset, sa_len,
l1_offset, l1_len, l2_offset, l2_len, kmt_offset, kmt_len) must use checked
conversions to avoid truncation on 32-bit targets: replace the casts with
usize::try_from(LittleEndian::read_u64(...)) and propagate or return an error
when try_from fails (or explicitly gate this parsing to 64-bit builds); ensure
the calling function (the parser in prmi/src/index/shm.rs that produces these
offsets/lengths and any subsequent layout checks using checked_add) rejects
values that don’t fit into usize so out-of-range u64 header values cannot be
accepted.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d1187774-ab92-468c-96df-3d299c0886a6

📥 Commits

Reviewing files that changed from the base of the PR and between 145de09 and 822ef64.

📒 Files selected for processing (36)
  • prmi-sys/src/lib.rs
  • prmi-sys/tests/spectrum_ffi.rs
  • prmi/Cargo.toml
  • prmi/benches/README.md
  • prmi/benches/spectrum_bench.rs
  • prmi/examples/confirm_forward_deep_probes.rs
  • prmi/examples/profile_spectrum.rs
  • prmi/proptest-regressions/index/spectrum.txt
  • prmi/src/cli.rs
  • prmi/src/index/mod.rs
  • prmi/src/index/shm.rs
  • prmi/src/index/spectrum.rs
  • prmi/src/sidecar/isa_file.rs
  • prmi/src/sidecar/kmt_file.rs
  • prmi/src/sidecar/magic.rs
  • prmi/src/sidecar/mod.rs
  • prmi/src/train/config.rs
  • prmi/src/train/mod.rs
  • prmi/src/train/trainer.rs
  • prmi/src/train/training_set.rs
  • prmi/src/train/verify.rs
  • prmi/tests/build_from_pac.rs
  • prmi/tests/isa_open.rs
  • prmi/tests/mask.rs
  • prmi/tests/memory_modes.rs
  • prmi/tests/shm_loader.rs
  • prmi/tests/sidecar_magic.rs
  • prmi/tests/sidecar_paths.rs
  • prmi/tests/spectrum_oracle.proptest-regressions
  • prmi/tests/spectrum_oracle.rs
  • prmi/tests/train_2x_sidecar.rs
  • prmi/tests/train_prmi.rs
  • prmi/tests/train_proptest.rs
  • prmi/tests/train_sidecar_writer.rs
  • prmi/tests/train_training_set.rs
  • prmi/tests/train_verify.rs
💤 Files with no reviewable changes (5)
  • prmi/tests/sidecar_paths.rs
  • prmi/tests/train_sidecar_writer.rs
  • prmi/src/sidecar/isa_file.rs
  • prmi/tests/isa_open.rs
  • prmi/tests/build_from_pac.rs
✅ Files skipped from review due to trivial changes (3)
  • prmi/tests/spectrum_oracle.proptest-regressions
  • prmi/proptest-regressions/index/spectrum.txt
  • prmi/src/sidecar/magic.rs
🚧 Files skipped from review as they are similar to previous changes (25)
  • prmi/tests/mask.rs
  • prmi/tests/train_verify.rs
  • prmi/tests/sidecar_magic.rs
  • prmi/Cargo.toml
  • prmi/tests/train_prmi.rs
  • prmi/tests/train_proptest.rs
  • prmi/src/train/config.rs
  • prmi/tests/memory_modes.rs
  • prmi/src/cli.rs
  • prmi/tests/shm_loader.rs
  • prmi/src/sidecar/mod.rs
  • prmi/src/train/trainer.rs
  • prmi/tests/spectrum_oracle.rs
  • prmi/tests/train_training_set.rs
  • prmi/examples/confirm_forward_deep_probes.rs
  • prmi/src/train/verify.rs
  • prmi/src/sidecar/kmt_file.rs
  • prmi-sys/tests/spectrum_ffi.rs
  • prmi/src/index/mod.rs
  • prmi/benches/spectrum_bench.rs
  • prmi-sys/src/lib.rs
  • prmi/examples/profile_spectrum.rs
  • prmi/src/train/training_set.rs
  • prmi/src/index/spectrum.rs
  • prmi/src/train/mod.rs

Comment thread prmi/src/index/shm.rs Outdated
…p batch + oracle; streaming/parallel trainer
@nh13
nh13 force-pushed the feat/v0.2-boundary-kmt branch from 822ef64 to c64daf4 Compare June 8, 2026 18:37
@nh13

nh13 commented Jun 8, 2026

Copy link
Copy Markdown
Author

@coderabbitai Addressed both remaining findings (amended into the single commit).

  • verify.rs parallel histogram memory — previously deferred; now fixed. Factored the histogram into error_histogram(..., parallel) and switch to a serial single-buffer pass once dense_len > HIST_PARALLEL_DENSE_LIMIT (1 << 20 entries), so memory no longer scales with dense_len × rayon_threads. Identical counts either way — regression test error_histogram_serial_equals_parallel asserts serial ≡ parallel, and error_distribution_equals_sorted_reference still holds.
  • shm.rs zero-length core componentread_shm_blob no longer skips validation for every zero-length component: only .kmt may be absent (offset 0 / len 0, with offset==0 enforced); a zero-length meta/sa/l1/l2 is now rejected as a malformed blob. Regression test read_shm_blob_rejects_zero_length_core_component.

build / clippy -D warnings / nightly fmt / cargo test --workspace green.

@nh13

nh13 commented Jun 8, 2026

Copy link
Copy Markdown
Author

@coderabbitai review

@nh13

nh13 commented Jun 8, 2026

Copy link
Copy Markdown
Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@nh13

nh13 commented Jun 8, 2026

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

1 participant