Skip to content

Commit ee63df1

Browse files
committed
change the centroids initialization
Signed-off-by: Connor Tsui <connor.tsui20@gmail.com>
1 parent 1de73c4 commit ee63df1

2 files changed

Lines changed: 53 additions & 15 deletions

File tree

vortex-tensor/src/encodings/turboquant/centroids.rs

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,23 @@
33

44
//! Max-Lloyd centroid computation for TurboQuant scalar quantizers.
55
//!
6-
//! Pre-computes optimal scalar quantizer centroids for the marginal distribution of coordinates
7-
//! after random rotation of a unit-norm vector. In high dimensions, each coordinate of a randomly
8-
//! rotated unit vector follows a distribution proportional to `(1 - x^2)^((d-3)/2)` on `[-1, 1]`,
9-
//! which converges to `N(0, 1/d)`. The Max-Lloyd algorithm finds optimal quantization centroids
10-
//! that minimize MSE for this distribution.
6+
//! Pre-computes and caches optimal scalar quantizer centroids for the marginal distribution of
7+
//! coordinates after a random orthogonal transform of a unit-norm vector.
8+
//!
9+
//! In high dimensions, each coordinate of a randomly transformed unit vector follows a
10+
//! distribution proportional to `(1 - x^2)^((d-3)/2)` on `[-1, 1]`, which converges to
11+
//! `N(0, 1/d)`.
12+
//!
13+
//! The Max-Lloyd algorithm finds optimal quantization centroids that minimize MSE for this
14+
//! distribution.
15+
//!
16+
//! Centroids are not stored in TurboQuant arrays. They are deterministically derived from
17+
//! `(padded_dim, bit_width)` and cached process-locally.
18+
//!
19+
//! The centroid model follows the random orthogonal transform marginal used by the TurboQuant
20+
//! paper. This encoder applies a SORF-style structured transform instead of a dense random Gaussian
21+
//! or orthogonal matrix, so paper-level error bounds should not be treated as verified for this
22+
//! implementation without separate empirical validation.
1123
1224
use std::sync::LazyLock;
1325

@@ -34,9 +46,9 @@ static CENTROID_CACHE: LazyLock<DashMap<(u32, u8), Buffer<f32>>> = LazyLock::new
3446
/// Get or compute cached centroids for the given dimension and bit width.
3547
///
3648
/// Returns `2^bit_width` centroids sorted in ascending order, representing optimal scalar
37-
/// quantization levels for the coordinate distribution after random rotation in
49+
/// quantization levels for the coordinate distribution after a random orthogonal transform in
3850
/// `dimension`-dimensional space.
39-
pub fn compute_or_get_centroids(dimension: u32, bit_width: u8) -> VortexResult<Buffer<f32>> {
51+
pub(crate) fn compute_or_get_centroids(dimension: u32, bit_width: u8) -> VortexResult<Buffer<f32>> {
4052
vortex_ensure!(
4153
(1..=MAX_BIT_WIDTH).contains(&bit_width),
4254
"TurboQuant bit_width must be 1-{}, got {bit_width}",
@@ -86,22 +98,35 @@ impl HalfIntExponent {
8698

8799
/// Compute optimal centroids via the Max-Lloyd (Lloyd-Max) algorithm.
88100
///
89-
/// Operates on the marginal distribution of a single coordinate of a randomly rotated unit vector
90-
/// in d dimensions.
101+
/// Operates on the marginal distribution of a single coordinate of a randomly transformed unit
102+
/// vector in d dimensions.
91103
///
92104
/// The probability distribution function is:
93105
/// `f(x) = C_d * (1 - x^2)^((d-3)/2)` on `[-1, 1]`
94106
/// where `C_d` is the normalizing constant.
107+
///
108+
/// Centroids are seeded uniformly on `[±sqrt(bit_width) * sigma]` (where `sigma` is the standard
109+
/// deviation of the normal distribution that hyphershere dimension values take, and specifically
110+
/// `sigma = 1/sqrt( dimension)`) rather than across the full `[-1, 1]`, which strands most of the
111+
/// centroids in the near-zero-mass tails.
112+
///
113+
/// Note that the `sqrt(bit_width)` is mostly empirically derived, we do not have a theoretical
114+
/// basis for choosing this other than the fact that it seems to produce good results.
95115
fn max_lloyd_centroids(dimension: u32, bit_width: u8) -> Buffer<f32> {
96116
debug_assert!((1..=MAX_BIT_WIDTH).contains(&bit_width));
97117
let num_centroids = 1usize << bit_width;
98118

99119
// For the marginal distribution on [-1, 1], we use the exponent (d-3)/2.
100120
let exponent = HalfIntExponent::from_numerator(dimension as i32 - 3);
101121

102-
// Initialize centroids uniformly on [-1, 1].
122+
// The coordinate marginal concentrates around 0 with this standard deviation.
123+
let sigma = 1.0 / f64::from(dimension).sqrt();
124+
let init_half = (f64::from(bit_width).sqrt() * sigma).min(1.0);
125+
126+
// Initialize centroids uniformly on [-init_half, init_half], where the mass lives, so no cell
127+
// starts in a zero-mass region and freezes.
103128
let mut centroids: Vec<f64> = (0..num_centroids)
104-
.map(|idx| -1.0 + (2.0 * (idx as f64) + 1.0) / (num_centroids as f64))
129+
.map(|idx| -init_half + (2.0 * (idx as f64) + 1.0) * init_half / (num_centroids as f64))
105130
.collect();
106131

107132
let mut boundaries: Vec<f64> = vec![0.0; num_centroids + 1];
@@ -193,7 +218,7 @@ fn pdf_unnormalized(x_val: f64, exponent: HalfIntExponent) -> f64 {
193218
/// For `k` centroids, returns `k-1` boundaries. A value below `boundaries[0]` maps to centroid 0, a
194219
/// value in `[boundaries[i-1], boundaries[i])` maps to centroid `i`, and a
195220
/// value `>= boundaries[k-2]` maps to centroid `k-1`.
196-
pub fn compute_centroid_boundaries(centroids: &[f32]) -> Vec<f32> {
221+
pub(crate) fn compute_centroid_boundaries(centroids: &[f32]) -> Vec<f32> {
197222
centroids.windows(2).map(|w| (w[0] + w[1]) * 0.5).collect()
198223
}
199224

@@ -203,7 +228,7 @@ pub fn compute_centroid_boundaries(centroids: &[f32]) -> Vec<f32> {
203228
/// centroids. Uses binary search on the midpoints, avoiding distance comparisons
204229
/// in the inner loop.
205230
#[inline]
206-
pub fn find_nearest_centroid(value: f32, boundaries: &[f32]) -> u8 {
231+
pub(crate) fn find_nearest_centroid(value: f32, boundaries: &[f32]) -> u8 {
207232
debug_assert!(
208233
boundaries.windows(2).all(|w| w[0] <= w[1]),
209234
"boundaries must be sorted"

vortex-turboquant/src/centroids.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,16 +106,29 @@ impl HalfIntExponent {
106106
/// The probability distribution function is:
107107
/// `f(x) = C_d * (1 - x^2)^((d-3)/2)` on `[-1, 1]`
108108
/// where `C_d` is the normalizing constant.
109+
///
110+
/// Centroids are seeded uniformly on `[±sqrt(bit_width) * sigma]` (where `sigma` is the standard
111+
/// deviation of the normal distribution that hyphershere dimension values take, and specifically
112+
/// `sigma = 1/sqrt( dimension)`) rather than across the full `[-1, 1]`, which strands most of the
113+
/// centroids in the near-zero-mass tails.
114+
///
115+
/// Note that the `sqrt(bit_width)` is mostly empirically derived, we do not have a theoretical
116+
/// basis for choosing this other than the fact that it seems to produce good results.
109117
fn max_lloyd_centroids(dimension: u32, bit_width: u8) -> Buffer<f32> {
110118
debug_assert!((1..=MAX_BIT_WIDTH).contains(&bit_width));
111119
let num_centroids = 1usize << bit_width;
112120

113121
// For the marginal distribution on [-1, 1], we use the exponent (d-3)/2.
114122
let exponent = HalfIntExponent::from_numerator(dimension as i32 - 3);
115123

116-
// Initialize centroids uniformly on [-1, 1].
124+
// The coordinate marginal concentrates around 0 with this standard deviation.
125+
let sigma = 1.0 / f64::from(dimension).sqrt();
126+
let init_half = (f64::from(bit_width).sqrt() * sigma).min(1.0);
127+
128+
// Initialize centroids uniformly on [-init_half, init_half], where the mass lives, so no cell
129+
// starts in a zero-mass region and freezes.
117130
let mut centroids: Vec<f64> = (0..num_centroids)
118-
.map(|idx| -1.0 + (2.0 * (idx as f64) + 1.0) / (num_centroids as f64))
131+
.map(|idx| -init_half + (2.0 * (idx as f64) + 1.0) * init_half / (num_centroids as f64))
119132
.collect();
120133

121134
let mut boundaries: Vec<f64> = vec![0.0; num_centroids + 1];

0 commit comments

Comments
 (0)