Repository navigation
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.
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
d69331f to
7935c84
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 27 out of 28 changed files in this pull request and generated no new comments.
Suppressed comments (2)
diskann/src/graph/pipnn/leaf_build.rs:524
build_symmetric_edge_csrusesneighbors.chunks_exact(leaf_k), which silently ignores any remainder if the slice length is not an exact multiple ofleaf_k. Adding an explicit assertion (or error) here would make it harder for future refactors/callers to accidentally drop edges without noticing.
for (source, neighbors) in neighbors.chunks_exact(leaf_k).enumerate() {
diskann/src/graph/pipnn/lsh.rs:155
buffer.resize(ndims, 0.0)does not clear existing elements whenbuffer.len() == ndims, so iffill_pointonly writes a prefix (or has an early-return bug) sketch computation can reuse stale values from a previous point. Either document thatfill_pointmust fully initialize the slice or clear it before callingfill_pointto make the API robust.
if buffer.len() < ndims {
let additional = ndims - buffer.len();
buffer
.try_reserve(additional)
.map_err(LshSketchError::Allocation)?;
}
buffer.resize(ndims, 0.0);
fill_point(i, &mut buffer[..ndims]).map_err(LshSketchError::Fill)?;
for j in 0..num_planes {
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 27 out of 28 changed files in this pull request and generated no new comments.
Suppressed comments (1)
diskann-disk/src/build/configuration/build_algorithm.rs:82
PiPNNParametersis documented as allowinghash_prune: Nonefor exact/direct candidate accumulation, but theDefaultimpl setshash_prune: Some(HashPruneParameters::default()). Because the struct is#[serde(default)], omittinghash_prunein JSON will now silently opt into HashPrune instead of keeping direct merging as the default, which contradicts the PR description and the field’s doc comment. Consider defaultinghash_prunetoNone(or updating the docs/PR description if HashPrune is intended to become the default).
fanout: vec![8, 3],
k: 2,
replicas: 1,
hash_prune: Some(HashPruneParameters::default()),
}
7935c84 to
cbbe4f5
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 27 out of 28 changed files in this pull request and generated no new comments.
Suppressed comments (3)
diskann-disk/src/build/configuration/build_algorithm.rs:157
- This serde-defaults test currently asserts that omitted
hash_prunedeserializes toSome(default), which would force-enable HashPrune by default. If direct merge is intended to remain the default, this assertion should instead verify thathash_pruneis absent unless explicitly set.
assert_eq!(config.hash_prune, Some(HashPruneParameters::default()));
diskann/src/graph/pipnn/hash_prune.rs:717
collect_sorted_neighbors/collect_neighbor_idsuseVec::reserveandVec::with_capacity, which can panic on allocation failure. Elsewhere in the PiPNN pipeline allocations are handled fallibly (try_reserve*) and surfaced asANNError; this new path introduces an OOM panic in a library build/extraction phase.
let n = hot.len as usize;
scratch.clear();
scratch.reserve(n);
for i in 0..n {
// SAFETY: guaranteed by this function's contract.
scratch.push(unsafe { (*neighbors.add(i), *distances.add(i)) });
}
scratch.sort_unstable_by_key(|&(id, distance)| (distance, id));
let out_len = n.min(cap);
let mut out = Vec::with_capacity(out_len);
for &(id, d) in &scratch[..out_len] {
out.push((id, bf16_to_f32(key_to_bf16(d))));
}
out
}
/// Collect the reservoir's neighbor ids, truncated to `cap`, WITHOUT sorting.
/// Reservoir order is intentionally not preserved. Reading only `neighbors`
/// lets the caller drop the hashes and distances slabs before extraction; any
/// ordering required by a later graph-finalization policy belongs to that caller.
///
/// SAFETY: caller holds the slot lock (or owns the reservoir); `neighbors` is
/// valid for `hot.len` elements.
#[inline]
unsafe fn collect_neighbor_ids(hot: &HotSlot, neighbors: *const u32, cap: usize) -> Vec<u32> {
let out_len = (hot.len as usize).min(cap);
let mut out = Vec::with_capacity(out_len);
for i in 0..out_len {
// SAFETY: guaranteed by this function's contract.
out.push(unsafe { *neighbors.add(i) });
}
out
}
diskann-disk/src/build/configuration/build_algorithm.rs:81
PiPNNParametersnow defaultshash_prunetoSome(...), which enables HashPrune even when callers don’t specify it in JSON. That changes the default candidate-merge behavior and contradicts the PR description that direct merging remains the default unless explicitly opted in.
This issue also appears on line 157 of the same file.
hash_prune: Some(HashPruneParameters::default()),
cbbe4f5 to
91bc385
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 27 out of 28 changed files in this pull request and generated no new comments.
Suppressed comments (1)
diskann-disk/src/build/configuration/build_algorithm.rs:83
PiPNNParameters::default()now enables HashPrune by default (hash_prune: Some(...)). That contradicts the PR description (“Direct merging remains the default”) and changes behavior for JSON configs that omithash_prune, because#[serde(default)]uses the struct Default.
Consider making the default hash_prune: None so HashPrune is only enabled when explicitly requested (e.g., via "hash_prune": { ... } in JSON).
#[cfg(feature = "pipnn")]
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()),
}
}
8225da6 to
f611b04
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## pipnn-stack/05-benchmark #1295 +/- ##
============================================================
+ Coverage 91.23% 91.29% +0.05%
============================================================
Files 621 624 +3
Lines 130255 131564 +1309
============================================================
+ Hits 118841 120109 +1268
- Misses 11414 11455 +41
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
|
Simplification follow-up in
Validation: 442 |
|
Validation count after deleting unsupported dynamic-k and unordered-leaf test paths: 440 |
|
The outdated HashPrune threads are addressed in the current layout: |
|
Latest cross-matrix Clippy exposed two boundary issues after removing dead-code suppression. |
Purpose
This PR adds optional HashPrune candidate merging to PiPNN.
Direct candidate merging remains the default.
Main changes
lsh.rscreates deterministic random-hyperplane sketches.final_prune=truesends reservoir candidates to shared RobustPrune.final_prune=falsereturns the nearest graph-degree candidates.Required invariants
l_maxdoes not exceed the structural reservoir limit.Review order
HashPruneConfigandwith_hash_pruneinmod.rs.lsh.rs.hash_prune.rs.hash_prune.rs.leaf_build.rs.Validation
diskannlibrary tests pass.-Dwarnings.Stack
Stack 6/6. Depends on #1294 and completes the PiPNN stack.