PiPNN 6/6: add HashPrune candidate merging - #1295
Conversation
|
Azure BigANN10M validation (
Raw runs: |
d4c2ba6 to
93cf436
Compare
Direct final-stack QCReviewed directly against current Fixed during QC
Remaining findings
Validation
Local runtime benchmark integration remains blocked by unavailable Git LFS (the fixture is a 123-byte pointer); CI's LFS checkout is the remaining runtime oracle. Full report: |
93cf436 to
efedce4
Compare
QC follow-upThe first post-rebase CI run exposed a real AArch64-only failure in The replacement run is green on:
Remote stack metadata was also rebuilt as one stack, #1301: |
efedce4 to
f8f27bf
Compare
There was a problem hiding this comment.
Pull request overview
Adds an optional HashPrune/LSH-based candidate-merging path to PiPNN builds, wiring it through disk build configuration and benchmarks, and extending SIMD/mask utilities needed by the new kernels.
Changes:
- Introduces
HashPrunereservoirs plus random-hyperplane LSH sketch computation, and integrates them into PiPNN leaf building / extraction (optionally followed by RobustPrune). - Extends disk-build and benchmark pipelines to accept and validate HashPrune parameters for PiPNN.
- Adds supporting utilities (trusted adjacency-list constructor, mask helpers, SIMD eq optimization) and CI Miri coverage for the raw-pointer kernels.
Reviewed changes
Copilot reviewed 25 out of 26 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| diskann/src/graph/adjacencylist.rs | Adds from_vec_trusted for zero-copy construction when uniqueness is guaranteed. |
| diskann-wide/src/doubled.rs | Adds first() support for doubled masks. |
| diskann-wide/src/arch/x86_64/v3/i16x16_.rs | Optimizes SIMD equality mask generation using movemask+pext. |
| diskann-vector/src/lib.rs | Makes x86_64 prefetch helpers available without requiring AVX2. |
| diskann-pipnn/tests/config.rs | Adds validation tests for HashPruneConfig parameters. |
| diskann-pipnn/tests/build_graph.rs | Adds parallel HashPrune build invariant test. |
| diskann-pipnn/src/lsh.rs | New LSH sketch computation (seeded random hyperplanes) and errors. |
| diskann-pipnn/src/lib.rs | Adds HashPrune config/types and integrates candidate-merge selection into build_graph. |
| diskann-pipnn/src/leaf_build/tests.rs | Adds CSR construction tests used by HashPrune leaf streaming. |
| diskann-pipnn/src/leaf_build.rs | Refactors leaf computation; adds CSR edge streaming into HashPrune reservoirs. |
| diskann-pipnn/src/hash_prune.rs | New HashPrune implementation (hot/cold slabs, per-row locking, SIMD hash ops, extraction). |
| diskann-pipnn/src/hash_prune/tests.rs | Adds unit and concurrency tests for HashPrune kernels and reservoir behavior. |
| diskann-pipnn/src/bf16.rs | Adds bf16 packing helpers for compact distance keys. |
| diskann-pipnn/Cargo.toml | Adds new dependencies and a HashPrune benchmark target. |
| diskann-pipnn/benches/hash_prune.rs | Adds criterion benchmark comparing direct vs HashPrune merge paths. |
| diskann-disk/src/lib.rs | Re-exports HashPruneParameters when pipnn feature is enabled. |
| diskann-disk/src/build/mod.rs | Re-exports HashPruneParameters from configuration. |
| diskann-disk/src/build/configuration/mod.rs | Exposes HashPrune parameters in configuration module exports. |
| diskann-disk/src/build/configuration/build_algorithm.rs | Extends PiPNNParameters with hash_prune and serde defaults. |
| diskann-disk/src/build/configuration/disk_index_build_parameter.rs | Switches to returning borrowed PiPNN parameters for build selection. |
| diskann-disk/src/build/builder/build/pipnn.rs | Wires optional HashPrune parameters into PiPNNBuildContext. |
| diskann-disk/src/build/builder/build/pipnn/tests.rs | Updates PiPNN disk builder tests for new parameter passing. |
| diskann-disk/src/build/builder/build.rs | Validates PiPNN + HashPrune config when pipnn is selected. |
| diskann-benchmark/src/index/build.rs | Wires optional HashPrune parameters into benchmark PiPNN builds. |
| Cargo.lock | Adds new transitive dependencies for diskann-pipnn changes. |
| .github/workflows/nightly.yml | Improves feature quoting/formatting and adds Miri strict-provenance coverage for HashPrune kernels. |
Comments suppressed due to low confidence (1)
diskann-disk/src/build/configuration/build_algorithm.rs:157
- If
PiPNNParameters::default()is changed to keephash_pruneopt-in, this serde-defaults test should be updated to expectNoneinstead ofSome(HashPruneParameters::default()).
assert_eq!(config.hash_prune, Some(HashPruneParameters::default()));
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| libc = "0.2" | ||
| parking_lot = "0.12" |
| impl Default for PiPNNParameters { | ||
| fn default() -> Self { | ||
| Self { | ||
| c_max: 256, | ||
| c_min: 16, | ||
| p_samp: 0.005, | ||
| fanout: vec![8, 3], | ||
| k: 2, | ||
| replicas: 1, | ||
| hash_prune: Some(HashPruneParameters::default()), | ||
| } | ||
| } |
f8f27bf to
661365c
Compare
661365c to
25ab02b
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 26 changed files in this pull request and generated no new comments.
Suppressed comments (1)
diskann-disk/src/build/configuration/build_algorithm.rs:82
PiPNNParametersis#[serde(default)], so deserializing PiPNN configs that omit the newhash_prunefield will inherit thisDefaultvalue. Settinghash_prune: Some(HashPruneParameters::default())therefore enables HashPrune by default and can change behavior for existing JSON configs that previously used direct candidate merging. If HashPrune is meant to be opt-in (as described), make the defaultNoneand require explicit configuration to enable it.
impl Default for PiPNNParameters {
fn default() -> Self {
Self {
c_max: 256,
c_min: 16,
p_samp: 0.005,
fanout: vec![8, 3],
k: 2,
replicas: 1,
hash_prune: Some(HashPruneParameters::default()),
}
25ab02b to
10bec9e
Compare
7591cbd to
8fafe0f
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 26 changed files in this pull request and generated no new comments.
Suppressed comments (2)
diskann-disk/src/build/configuration/build_algorithm.rs:83
PiPNNParameters::default()now enables HashPrune by default (hash_prune: Some(...)). This changes the default behavior for PiPNN builds that rely on serde defaults (e.g., JSON that omitshash_prune) from the existing direct-candidate path to HashPrune, which contradicts the PR description’s “direct candidates as the default;with_hash_pruneopts in”. It can also introduce unexpected validation failures whengraph_degree > min(l_max, 2^num_hash_planes).
Consider keeping the default as hash_prune: None and requiring explicit configuration to opt in (while still allowing { "hash_prune": {} } to pick up HashPruneParameters defaults).
impl Default for PiPNNParameters {
fn default() -> Self {
Self {
c_max: 256,
c_min: 16,
p_samp: 0.005,
fanout: vec![8, 3],
k: 2,
replicas: 1,
hash_prune: Some(HashPruneParameters::default()),
}
}
diskann-pipnn/src/hash_prune.rs:869
with_lockeduses a releaseassert!for the row bounds check. Since this is on the hot path for every insertion and can panic in production if an invariant is violated (e.g., corrupted/invalidpoint_ids), preferdebug_assert!here so debug builds still catch mistakes without turning this into a hard crash in release.
assert!(idx < self.hot.len(), "HashPrune row index out of bounds");
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 26 changed files in this pull request and generated no new comments.
Suppressed comments (2)
diskann-disk/src/build/configuration/build_algorithm.rs:81
PiPNNParameters::default()now enables HashPrune (hash_prune: Some(...)). That makes HashPrune effectively the default behavior for disk build configuration that relies on defaults / older JSON that omitted this field, which contradicts the stated "direct candidates as the default; with_hash_prune opts in" behavior and is a backwards-incompatible semantic change. Consider defaultinghash_prunetoNone(opt-in), or clearly documenting/migrating the default behavior change.
hash_prune: Some(HashPruneParameters::default()),
diskann-pipnn/src/hash_prune.rs:694
collect_sorted_neighborssorts by(distance, id), but reservoir eviction/"farthest" selection uses(distance, hash, id)(seeupdate_farthest). When bf16 quantization ties many distances, this extraction ordering can pick a different (and worse) subset when truncating tomax_degree, and it reintroduces systematic low-ID bias that the hash tie-break was meant to avoid. Consider sorting/truncating by the same total key(distance_key, hash, neighbor_id)during nearest extraction (which likely means passinghashesintocollect_sorted_neighborsand not droppingcold_hashesuntil after extraction).
scratch.sort_unstable_by_key(|&(id, distance)| (distance, id));
8fafe0f to
60d525a
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 25 out of 26 changed files in this pull request and generated no new comments.
Suppressed comments (2)
diskann-disk/src/build/configuration/build_algorithm.rs:81
PiPNNParametersnow defaultshash_prunetoSome(...). Because the struct is#[serde(default)], configs that omithash_prunewill silently enable HashPrune, changing the default build behavior vs prior releases and vs the PiPNN crate’s stated “direct is default” contract in this PR description. If the intent is that HashPrune remains opt-in, set the default toNone(and update the serde default test accordingly). If the intent is to switch the default, please update the PiPNN docs/PR description to reflect that external users will get HashPrune unless they explicitly disable it.
hash_prune: Some(HashPruneParameters::default()),
diskann-disk/src/build/configuration/build_algorithm.rs:157
- This serde-defaults test currently asserts that deserializing PiPNN parameters without a
hash_prunefield yieldsSome(HashPruneParameters::default()). If HashPrune is meant to be opt-in (per this PR’s description), this assertion should be updated to expectNoneand rely on callers explicitly settinghash_prunewhen desired.
assert_eq!(config.hash_prune, Some(HashPruneParameters::default()));
Adds optional LSH/HashPrune candidate merging to PiPNN.
Code map
lsh.rscreates seeded random hyperplanes and stores each point's projection vector. Leaf processing gathers only the sketches for that leaf.hash_prune.rsstores per-point metadata in a hot slab and hashes/distances/neighbor IDs in separate cold slabs.scan_lanespads each cold row for full SIMD loads;l_maxremains the logical capacity.leaf_build.rsconverts symmetric leaf top-k output into CSR once, gathers leaf sketches, computes relative hashes, and streams directed edges into the corresponding reservoirs. Worker sketch scratch retains its high-water capacity but all reads use the active prefix.lib.rskeeps direct candidates as the default;with_hash_pruneopts into this path. Validation requires graph degree to fit bothl_maxand the2^num_hash_planeshash space.Review path
Send/Syncsafety invariants, then reviewwith_lockedto establish the single-writer rule for each row.insert_lockedthrough empty, same-hash, not-full, early-reject, and eviction cases; verifyfarthest_idxafter every mutation.diskann-widetargets. PiPNN does not select an ISA.Stack 6/6: #1294