From 77bd05f9ccbab3426614aed38d36f3391f3074db Mon Sep 17 00:00:00 2001 From: Junkui Chen Date: Tue, 21 Jul 2026 15:58:29 +0800 Subject: [PATCH 1/4] Make BlockTransposed cloneable Implement cloning for the block-transposed representation by copying its complete backing allocation, including padding, and verify cloned matrices own independent storage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: e50cc67e-4fa3-4f9c-ac82-89c85f4182f3 --- .../src/multi_vector/block_transposed.rs | 41 +++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/diskann-quantization/src/multi_vector/block_transposed.rs b/diskann-quantization/src/multi_vector/block_transposed.rs index 6fe9a1315..71e17c38c 100644 --- a/diskann-quantization/src/multi_vector/block_transposed.rs +++ b/diskann-quantization/src/multi_vector/block_transposed.rs @@ -86,8 +86,8 @@ use diskann_utils::{ }; use super::matrix::{ - Defaulted, LayoutError, Mat, MatMut, MatRef, NewMut, NewOwned, NewRef, Overflow, Repr, ReprMut, - ReprOwned, SliceError, + Defaulted, LayoutError, Mat, MatMut, MatRef, NewCloned, NewMut, NewOwned, NewRef, Overflow, + Repr, ReprMut, ReprOwned, SliceError, }; use crate::bits::{AsMutPtr, AsPtr, MutSlicePtr, SlicePtr}; use crate::utils; @@ -602,6 +602,23 @@ unsafe impl NewOwned NewCloned + for BlockTransposedRepr +{ + fn new_cloned(v: MatRef<'_, Self>) -> Mat { + let repr = *v.repr(); + // SAFETY: `v` points to an allocation described by `repr`, which contains + // exactly `storage_len` initialized elements. + let data = + unsafe { std::slice::from_raw_parts(v.as_raw_ptr().cast::(), repr.storage_len()) }; + let b = data.to_vec().into_boxed_slice(); + + // SAFETY: `b` was copied from the complete backing allocation and therefore + // has exactly `repr.storage_len()` elements. + unsafe { repr.box_to_mat(b) } + } +} + // SAFETY: This checks slice length against storage_len. unsafe impl NewRef for BlockTransposedRepr @@ -677,7 +694,7 @@ macro_rules! delegate_to_ref { /// /// - [`Row`] — a `Copy` handle supporting `Index` and `.iter()`. /// - [`RowMut`] — a mutable handle supporting `IndexMut`. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct BlockTransposed { data: Mat>, } @@ -1256,6 +1273,24 @@ mod tests { ((i % 255) + 1) as u8 } + #[test] + fn clone_has_independent_backing_allocation() { + let mut data = Matrix::new(0, 5, 3); + data.as_mut_slice() + .iter_mut() + .enumerate() + .for_each(|(i, value)| *value = (i + 1) as i32); + let original = BlockTransposed::::from_matrix_view(data.as_view()); + let mut cloned = original.clone(); + + assert_eq!(cloned.as_slice(), original.as_slice()); + assert_ne!(cloned.as_ptr(), original.as_ptr()); + + cloned.get_row_mut(0).unwrap()[0] = -1; + assert_eq!(original[(0, 0)], 1); + assert_eq!(cloned[(0, 0)], -1); + } + // ── Unified parameterized test ────────────────────────────────── /// Exhaustive test for the full `BlockTransposed` / `BlockTransposedRef` / From 07f497248ad739e9bfa87ebfb712ecbc675e2417 Mon Sep 17 00:00:00 2001 From: Junkui Chen Date: Tue, 21 Jul 2026 16:33:29 +0800 Subject: [PATCH 2/4] Test cloning non-default padding Write sentinels into column padding, remainder-row padding, and their intersection before cloning so the regression test distinguishes a full backing-allocation copy from logical reconstruction. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: e50cc67e-4fa3-4f9c-ac82-89c85f4182f3 --- .../src/multi_vector/block_transposed.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/diskann-quantization/src/multi_vector/block_transposed.rs b/diskann-quantization/src/multi_vector/block_transposed.rs index 71e17c38c..f8fc054f2 100644 --- a/diskann-quantization/src/multi_vector/block_transposed.rs +++ b/diskann-quantization/src/multi_vector/block_transposed.rs @@ -1280,10 +1280,20 @@ mod tests { .iter_mut() .enumerate() .for_each(|(i, value)| *value = (i + 1) as i32); - let original = BlockTransposed::::from_matrix_view(data.as_view()); + let mut original = BlockTransposed::::from_matrix_view(data.as_view()); + let column_padding = linear_index::<4, 2>(0, 3, original.ncols()); + let row_padding = linear_index::<4, 2>(5, 0, original.ncols()); + let row_and_column_padding = linear_index::<4, 2>(5, 3, original.ncols()); + original.as_mut_slice()[column_padding] = -10; + original.as_mut_slice()[row_padding] = -11; + original.as_mut_slice()[row_and_column_padding] = -12; + let mut cloned = original.clone(); assert_eq!(cloned.as_slice(), original.as_slice()); + assert_eq!(cloned.as_slice()[column_padding], -10); + assert_eq!(cloned.as_slice()[row_padding], -11); + assert_eq!(cloned.as_slice()[row_and_column_padding], -12); assert_ne!(cloned.as_ptr(), original.as_ptr()); cloned.get_row_mut(0).unwrap()[0] = -1; From 3e33f52853b7d4f3e782b53853568de7ba39b2b2 Mon Sep 17 00:00:00 2001 From: Junkui Chen Date: Thu, 23 Jul 2026 10:04:08 +0800 Subject: [PATCH 3/4] Refine BlockTransposed clone allocation Clone through BlockTransposedRef's safe slice API and convert the slice directly into a precisely sized Box allocation, avoiding both local unsafe code and Vec over-allocation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: e50cc67e-4fa3-4f9c-ac82-89c85f4182f3 --- .../src/multi_vector/block_transposed.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/diskann-quantization/src/multi_vector/block_transposed.rs b/diskann-quantization/src/multi_vector/block_transposed.rs index f8fc054f2..f9b07fd2c 100644 --- a/diskann-quantization/src/multi_vector/block_transposed.rs +++ b/diskann-quantization/src/multi_vector/block_transposed.rs @@ -607,11 +607,8 @@ impl NewCloned { fn new_cloned(v: MatRef<'_, Self>) -> Mat { let repr = *v.repr(); - // SAFETY: `v` points to an allocation described by `repr`, which contains - // exactly `storage_len` initialized elements. - let data = - unsafe { std::slice::from_raw_parts(v.as_raw_ptr().cast::(), repr.storage_len()) }; - let b = data.to_vec().into_boxed_slice(); + let data = BlockTransposedRef::new(v).as_slice(); + let b: Box<[T]> = data.into(); // SAFETY: `b` was copied from the complete backing allocation and therefore // has exactly `repr.storage_len()` elements. @@ -717,6 +714,10 @@ pub struct BlockTransposedMut<'a, T: Copy, const GROUP: usize, const PACK: usize // ── BlockTransposedRef (core read implementations) ─────────────── impl<'a, T: Copy, const GROUP: usize, const PACK: usize> BlockTransposedRef<'a, T, GROUP, PACK> { + fn new(data: MatRef<'a, BlockTransposedRepr>) -> Self { + Self { data } + } + /// Returns the number of logical rows. #[inline] pub fn nrows(&self) -> usize { @@ -1033,9 +1034,7 @@ impl<'a, T: Copy, const GROUP: usize, const PACK: usize> BlockTransposedMut<'a, impl BlockTransposed { /// Borrow as an immutable [`BlockTransposedRef`]. pub fn as_view(&self) -> BlockTransposedRef<'_, T, GROUP, PACK> { - BlockTransposedRef { - data: self.data.as_view(), - } + BlockTransposedRef::new(self.data.as_view()) } /// Borrow as a mutable [`BlockTransposedMut`]. From f18874e279d2e1d3e63a175f7b5ee3e355bdf675 Mon Sep 17 00:00:00 2001 From: Junkui Chen Date: Thu, 23 Jul 2026 10:10:27 +0800 Subject: [PATCH 4/4] Inline repr binding in new_cloned Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: e50cc67e-4fa3-4f9c-ac82-89c85f4182f3 --- diskann-quantization/src/multi_vector/block_transposed.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/diskann-quantization/src/multi_vector/block_transposed.rs b/diskann-quantization/src/multi_vector/block_transposed.rs index f9b07fd2c..b8414b6bd 100644 --- a/diskann-quantization/src/multi_vector/block_transposed.rs +++ b/diskann-quantization/src/multi_vector/block_transposed.rs @@ -606,13 +606,11 @@ impl NewCloned for BlockTransposedRepr { fn new_cloned(v: MatRef<'_, Self>) -> Mat { - let repr = *v.repr(); - let data = BlockTransposedRef::new(v).as_slice(); - let b: Box<[T]> = data.into(); + let b: Box<[T]> = BlockTransposedRef::new(v).as_slice().into(); // SAFETY: `b` was copied from the complete backing allocation and therefore - // has exactly `repr.storage_len()` elements. - unsafe { repr.box_to_mat(b) } + // has exactly `v.repr().storage_len()` elements. + unsafe { v.repr().box_to_mat(b) } } }