mirror of
https://github.com/qdrant/qdrant.git
synced 2026-09-21 13:37:46 -05:00
Fix TQ quantized vector layout alignment (#10005)
`EncodedVectorsTQ::layout()` declared `align_of::<f32>()`, but the size it reports is the packed dimensions plus a 4-byte-multiple extras trailer, which is not a multiple of 4 for three quarters of all dimensions (e.g. dim=756, Bits4: 378 + 4 = 382 bytes). The claim was never true — the encoded storage packs vectors at `id * quantized_vector_size` with no per-vector padding — and nothing relies on it: packed dimensions are read through unaligned SIMD loads (`loadu` / `vld1`) and the extras trailer through `f32::from_le_bytes` on a byte slice. It is also actively harmful. Inline HNSW storage packs link vectors back-to-back using this layout and rejects one whose size is not a multiple of its alignment, so building an index with `inline_storage` enabled fails for those dimensions — and retries forever as an optimization crashloop. Use `align_of::<u8>()`, matching scalar and product quantization. Old links files stay readable: both layouts are persisted in the file header and the reader takes size and alignment from there, never from the live quantizer. Add a test covering the `size % align == 0` invariant across awkward dimensions, bit widths, distances and modes — `layout()` had no coverage, which is why the mismatch went unnoticed on the multiple-of-32 dimensions everyone uses in practice. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
81a0e43c73
commit
8b0d092c01
@@ -342,7 +342,7 @@ impl<TStorage: EncodedStorage> EncodedVectorsTQ<TStorage> {
|
||||
}
|
||||
|
||||
pub fn layout(&self) -> Layout {
|
||||
Layout::from_size_align(self.quantized_vector_size(), align_of::<f32>()).unwrap()
|
||||
Layout::from_size_align(self.quantized_vector_size(), align_of::<u8>()).unwrap()
|
||||
}
|
||||
|
||||
pub fn get_metadata(&self) -> &Metadata {
|
||||
|
||||
@@ -1064,4 +1064,72 @@ mod tests {
|
||||
"bits={bits:?}: Plus recall regressed (Normal={normal:.3}, Plus={plus:.3})"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tq_layout_size_is_multiple_of_alignment() {
|
||||
const LAYOUT_DIMS: &[usize] = &[1, 7, 33, 65, 100, 756, 768];
|
||||
let vectors_count = 8;
|
||||
|
||||
for &dim in LAYOUT_DIMS {
|
||||
for &bits in BITS {
|
||||
for &distance in &[
|
||||
DistanceType::Dot,
|
||||
DistanceType::Cosine,
|
||||
DistanceType::L1,
|
||||
DistanceType::L2,
|
||||
] {
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(42);
|
||||
let vector_data: Vec<Vec<f32>> = (0..vectors_count)
|
||||
.map(|_| {
|
||||
let vector: Vec<f32> =
|
||||
(0..dim).map(|_| rng.random_range(-1.0..1.0)).collect();
|
||||
match distance {
|
||||
DistanceType::Cosine => normalize(&vector),
|
||||
DistanceType::Dot | DistanceType::L1 | DistanceType::L2 => vector,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let vector_parameters = VectorParameters {
|
||||
dim,
|
||||
deprecated_count: None,
|
||||
distance_type: distance,
|
||||
invert: false,
|
||||
};
|
||||
|
||||
for &mode in &[TQMode::Normal, TQMode::Plus] {
|
||||
let quantized_vector_size = encoded_vectors_tq::get_quantized_vector_size(
|
||||
&vector_parameters,
|
||||
bits,
|
||||
mode,
|
||||
);
|
||||
let encoded = EncodedVectorsTQ::encode(
|
||||
vector_data.iter(),
|
||||
TestEncodedStorageBuilder::new(None, quantized_vector_size),
|
||||
&vector_parameters,
|
||||
vectors_count,
|
||||
bits,
|
||||
mode,
|
||||
TQRotation::Padded,
|
||||
false,
|
||||
1,
|
||||
None,
|
||||
&AtomicBool::new(false),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let layout = encoded.layout();
|
||||
assert_eq!(layout.size(), quantized_vector_size);
|
||||
assert_eq!(
|
||||
layout.size() % layout.align(),
|
||||
0,
|
||||
"dim={dim} bits={bits:?} distance={distance:?} mode={mode:?}: \
|
||||
layout size {} is not a multiple of alignment {}",
|
||||
layout.size(),
|
||||
layout.align(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user