NeighborPriorityQueue::insert() panics on aarch64 (emulated SIMD)
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 1.9k
- Forks
- 454
- Avg merge
- 3d 22h
- Merged PRs (30d)
- 35
Description
Bug
NeighborPriorityQueue::insert() panics during index build on aarch64 (Apple Silicon M3 Pro):
thread '<unnamed>' panicked at diskann-0.45.0/src/neighbor/queue.rs:156:26:
insertion index (is 129) should be <= len (is 128)
Versions: diskann = "0.45.0", also verified 0.46.0 has identical queue.rs.
Reproduction
- Platform: macOS 15.4, Apple M3 Pro (aarch64)
- Dataset: 563 f32 vectors, 384 dimensions (MiniLM embeddings)
- Config:
max_degree=64, MaxDegree::default_slack() (1.3), build_complexity=128, alpha=1.2, metric=L2 - Happens during
DiskANNIndex::insert()after inserting ~100-200 vectors (non-deterministic)
Minimal caller code:
let prune_kind = PruneKind::from_metric(DistanceMetric::L2);
let mut builder = Builder::new(64, MaxDegree::default_slack(), 128, prune_kind);
builder.alpha(1.2);
let config = builder.build()?;
let index = DiskANNIndex::new(config, provider, None);
// Insert 563 vectors sequentially
for (label, vector) in vectors {
runtime::block_on(index.insert(FullPrecisionStrategy::new(), &DefaultContext, &label, &vector))?;
}
Analysis
In queue.rs:133-167, the insert() method for a fixed-size queue:
- Guard (line 140):
self.get_unchecked(self.size - 1) < nbr→ returns false (last element ≥ nbr by distance) - Lower bound (line 145):
get_lower_bound(&nbr)→ returnsself.size(129), meaning "all elements compare less-than via SIMD" - Truncate (line 150-153): removes last element,
self.sizebecomes 128, vec length becomes 128 - Insert (line 156):
self.id_visiteds.insert(129, ...)→ panics because 129 > vec.len() (128)
Steps 1 and 2 are contradictory: the guard says last element has distance >= nbr.distance, but get_lower_bound says all distances are < nbr.distance. On aarch64 there's no native diskann-wide SIMD backend — it falls back to emulated.rs which does scalar f32 >= f32 comparisons. I confirmed the vectors contain no NaN/Inf values, so the comparisons should agree.
I haven't been able to pinpoint exactly why the guard and get_lower_bound disagree. It may be related to the queue capacity (129 = build_complexity + 1 frozen point) and how load_simd_first handles the tail.
Workaround
Clamping insert_idx after truncation fixes the panic and preserves sort order:
if self.size == self.capacity {
self.id_visiteds.truncate(self.size - 1);
self.distances.truncate(self.size - 1);
self.size -= 1;
}
let insert_idx = insert_idx.min(self.size); // <-- fix
self.id_visiteds.insert(insert_idx, (nbr.id, false));
self.distances.insert(insert_idx, nbr.distance);
This is safe because if insert_idx == old_size, the element has the largest distance and should go at the end (replacing the truncated element).
Question
Am I configuring something wrong, or is this a bug in the queue logic? Happy to provide more details or a standalone repro.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in queue.rs:133-167, especially the fixed-size insert path, and compare its guard and get_lower_bound behavior with the emulated SIMD implementation in emulated.rs. Run the supplied aarch64 reproduction using the stated queue configuration; done means insertion no longer panics and the queue remains sorted when the insertion index reaches the truncated length.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- search
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100