hermes_core/structures/vector/scann/
geometry.rs1use super::{
8 MAX_SCANN_LEAVES, MAX_SCANN_TREE_LEVELS, MIN_PARTITION_TRAINING_POINTS_PER_LEAF,
9 MIN_POINTS_FOR_PARTITIONING, ScannFormatError, ScannResult,
10};
11
12pub const QUALITY_OPTIMIZED_POINTS_PER_LEAF: u64 = 100;
17const THREE_LEVEL_MIN_POINTS: u64 = 100_000_000;
22const FOUR_LEVEL_MIN_POINTS_EXCLUSIVE: u64 = 1_000_000_000;
23
24#[derive(Clone, Debug, Eq, PartialEq)]
25pub struct ScannGeometry {
26 pub centroid_levels: u8,
27 pub num_leaves: u32,
28 pub level_counts: Vec<u32>,
30}
31
32pub fn derive_geometry(points: u64, dimension: u32) -> ScannResult<ScannGeometry> {
34 derive_geometry_with_levels(points, dimension, None)
35}
36
37pub fn derive_geometry_with_levels(
41 points: u64,
42 dimension: u32,
43 requested_levels: Option<u8>,
44) -> ScannResult<ScannGeometry> {
45 derive_geometry_with_levels_and_sample_limit(points, dimension, requested_levels, points)
46}
47
48pub fn derive_geometry_with_levels_and_sample_limit(
54 points: u64,
55 dimension: u32,
56 requested_levels: Option<u8>,
57 sample_limit: u64,
58) -> ScannResult<ScannGeometry> {
59 if points == 0 {
60 return Err(ScannFormatError::new(
61 "ScaNN geometry requires at least one vector",
62 ));
63 }
64 if dimension == 0 {
65 return Err(ScannFormatError::new(
66 "ScaNN geometry requires a non-zero vector dimension",
67 ));
68 }
69 if let Some(levels) = requested_levels
70 && !(1..=MAX_SCANN_TREE_LEVELS).contains(&levels)
71 {
72 return Err(ScannFormatError::new(format!(
73 "ScaNN tree levels must be in 1..={MAX_SCANN_TREE_LEVELS}"
74 )));
75 }
76 if points < MIN_POINTS_FOR_PARTITIONING {
77 return Ok(ScannGeometry {
78 centroid_levels: 0,
79 num_leaves: 1,
80 level_counts: Vec::new(),
81 });
82 }
83
84 let (desired_leaves, corpus_min_levels) = automatic_leaf_target(points);
85 let leaves = desired_leaves.min(u64::from(MAX_SCANN_LEAVES)) as u32;
86 let levels = requested_levels
87 .unwrap_or_else(|| corpus_min_levels.max(width_required_levels(leaves, dimension)));
88 let geometry = geometry_for_leaves(leaves, levels)?;
89 let required_sample = u64::from(leaves)
90 .checked_mul(MIN_PARTITION_TRAINING_POINTS_PER_LEAF)
91 .ok_or_else(|| ScannFormatError::new("ScaNN minimum training sample overflows u64"))?
92 .max(MIN_POINTS_FOR_PARTITIONING);
93 let achievable_sample = points.min(sample_limit);
94 if achievable_sample < required_sample {
95 return Err(ScannFormatError::new(format!(
96 "ScaNN automatic geometry selected {leaves} leaves and needs at least {required_sample} training samples (hardcoded {MIN_PARTITION_TRAINING_POINTS_PER_LEAF} samples/leaf), but the builder can supply {achievable_sample}"
97 )));
98 }
99 Ok(geometry)
100}
101
102pub fn geometry_for_leaves(num_leaves: u32, levels: u8) -> ScannResult<ScannGeometry> {
104 if !(2..=MAX_SCANN_LEAVES).contains(&num_leaves) {
105 return Err(ScannFormatError::new(format!(
106 "ScaNN leaf count must be in 2..={MAX_SCANN_LEAVES}"
107 )));
108 }
109 if !(1..=MAX_SCANN_TREE_LEVELS).contains(&levels) {
110 return Err(ScannFormatError::new(format!(
111 "ScaNN tree levels must be in 1..={MAX_SCANN_TREE_LEVELS}"
112 )));
113 }
114 let factors = balanced_branching_factors(num_leaves, levels);
115 let mut product = 1u64;
116 let mut level_counts = Vec::with_capacity(usize::from(levels));
117 for (level, factor) in factors.into_iter().enumerate() {
118 product = product.saturating_mul(u64::from(factor));
119 level_counts.push(if level + 1 == usize::from(levels) {
120 num_leaves
121 } else {
122 product.min(u64::from(num_leaves)) as u32
123 });
124 }
125 Ok(ScannGeometry {
126 centroid_levels: levels,
127 num_leaves,
128 level_counts,
129 })
130}
131
132pub fn geometry_for_leaves_with_auto_depth(
137 num_leaves: u32,
138 dimension: u32,
139 requested_levels: Option<u8>,
140) -> ScannResult<ScannGeometry> {
141 if dimension == 0 {
142 return Err(ScannFormatError::new(
143 "ScaNN geometry requires a non-zero vector dimension",
144 ));
145 }
146 geometry_for_leaves(
147 num_leaves,
148 requested_levels.unwrap_or_else(|| width_required_levels(num_leaves, dimension)),
149 )
150}
151
152pub fn desired_training_sample(observed: u64, num_leaves: u32) -> u64 {
154 let desired = u64::from(num_leaves)
155 .saturating_mul(super::PARTITION_TRAINING_POINTS_PER_CENTROID)
156 .max(super::DEFAULT_TRAINING_SAMPLE_SIZE);
157 observed.min(desired)
158}
159
160fn automatic_leaf_target(points: u64) -> (u64, u8) {
161 if points < THREE_LEVEL_MIN_POINTS {
162 (fractional_power_ceil(points, 1, 2), 1)
163 } else if points <= FOUR_LEVEL_MIN_POINTS_EXCLUSIVE {
164 (fractional_power_ceil(points, 2, 3), 2)
165 } else {
166 (fractional_power_ceil(points, 3, 4), 3)
167 }
168}
169
170fn width_required_levels(leaves: u32, dimension: u32) -> u8 {
171 let max_branching = flat_training_width_bound(dimension);
172 for levels in 1..MAX_SCANN_TREE_LEVELS {
173 if nth_root_ceil(leaves, levels) <= max_branching {
174 return levels;
175 }
176 }
177 MAX_SCANN_TREE_LEVELS
178}
179
180fn flat_training_width_bound(dimension: u32) -> u32 {
182 let numerator = 60.0 * 32.0 * 2.0e9;
183 let denominator = f64::from(dimension) * super::PARTITION_TRAINING_POINTS_PER_CENTROID as f64;
184 (numerator / denominator).sqrt().ceil().max(1.0) as u32
185}
186
187fn balanced_branching_factors(leaves: u32, levels: u8) -> Vec<u32> {
188 let mut remaining = leaves;
189 let mut factors = Vec::with_capacity(usize::from(levels));
190 for levels_left in (1..=levels).rev() {
191 let factor = nth_root_ceil(remaining, levels_left);
192 factors.push(factor);
193 remaining = remaining.div_ceil(factor);
194 }
195 factors
196}
197
198fn nth_root_ceil(value: u32, degree: u8) -> u32 {
199 if value <= 1 || degree == 1 {
200 return value;
201 }
202 let mut low = 1u32;
203 let mut high = value;
204 while low < high {
205 let middle = low + (high - low) / 2;
206 if pow_reaches(middle, degree, value) {
207 high = middle;
208 } else {
209 low = middle + 1;
210 }
211 }
212 low
213}
214
215fn fractional_power_ceil(value: u64, numerator: u8, denominator: u8) -> u64 {
216 debug_assert!(value > 0 && numerator > 0 && numerator < denominator);
217 let target = saturating_pow_u128(u128::from(value), numerator);
218 let mut low = 1u64;
219 let mut high = value.min(u64::from(MAX_SCANN_LEAVES) + 1).max(2);
222 while low < high {
223 let middle = low + (high - low) / 2;
224 if saturating_pow_u128(u128::from(middle), denominator) >= target {
225 high = middle;
226 } else {
227 low = middle + 1;
228 }
229 }
230 low
231}
232
233fn saturating_pow_u128(base: u128, exponent: u8) -> u128 {
234 (0..exponent).fold(1u128, |product, _| product.saturating_mul(base))
235}
236
237fn pow_reaches(base: u32, degree: u8, target: u32) -> bool {
238 let mut product = 1u64;
239 for _ in 0..degree {
240 product = product.saturating_mul(u64::from(base));
241 if product >= u64::from(target) {
242 return true;
243 }
244 }
245 false
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn geometry_matches_billion_scale_balanced_width() {
254 let geometry = derive_geometry(1_000_000_000, 1_024).unwrap();
255 assert_eq!(geometry.centroid_levels, 2);
256 assert_eq!(geometry.level_counts, [1_000, 1_000_000]);
257 assert_eq!(
258 desired_training_sample(1_000_000_000, 1_000_000),
259 200_000_000
260 );
261 }
262
263 #[test]
264 fn billion_scale_float_geometry_rejects_an_inadequate_default_sample() {
265 let sample_limit = (4_u64 * 1024 * 1024 * 1024) / (1_024 * 4);
266 assert_eq!(sample_limit, 1_048_576);
267 let error =
268 derive_geometry_with_levels_and_sample_limit(1_000_000_000, 1_024, None, sample_limit)
269 .unwrap_err()
270 .to_string();
271 assert!(error.contains("1000000 leaves"));
272 assert!(error.contains("8000000 training samples"));
273 }
274
275 #[test]
276 fn billion_scale_binary_geometry_fits_default_training_budget() {
277 let sample_limit = 10_000_000_u64;
279 let geometry =
280 derive_geometry_with_levels_and_sample_limit(1_000_000_000, 2_560, None, sample_limit)
281 .unwrap();
282 assert_eq!(sample_limit, 10_000_000);
283 assert_eq!(geometry.centroid_levels, 2);
284 assert_eq!(geometry.level_counts, [1_000, 1_000_000]);
285 assert!(
286 u64::from(geometry.num_leaves) * MIN_PARTITION_TRAINING_POINTS_PER_LEAF <= sample_limit
287 );
288 }
289
290 #[test]
291 fn fifteen_million_rows_use_the_measured_balanced_geometry() {
292 let geometry = derive_geometry(15_000_000, 2_560).unwrap();
293 assert_eq!(geometry.centroid_levels, 2);
294 assert_eq!(geometry.level_counts, [63, 3_873]);
295 assert_eq!(desired_training_sample(15_000_000, 3_873), 774_600);
296 }
297
298 #[test]
299 fn automatic_geometry_follows_google_tree_depth_bands() {
300 assert_eq!(
301 derive_geometry(99_999_999, 2_560).unwrap().level_counts,
302 [100, 10_000]
303 );
304 assert_eq!(
305 derive_geometry(100_000_000, 2_560).unwrap().level_counts,
306 [465, 215_444]
307 );
308 assert_eq!(
309 derive_geometry(1_000_000_001, 2_560).unwrap().level_counts,
310 [178, 31_684, 5_623_414]
311 );
312 assert_eq!(
313 derive_geometry(10_000_000_000, 2_560).unwrap().level_counts,
314 [311, 96_721, 30_000_000]
315 );
316 }
317
318 #[test]
319 fn sample_limit_never_changes_the_selected_topology() {
320 let expected = derive_geometry(1_000_000_000, 2_560).unwrap();
321 assert!(
322 derive_geometry_with_levels_and_sample_limit(1_000_000_000, 2_560, None, 7_999_999,)
323 .is_err()
324 );
325 assert_eq!(
326 derive_geometry_with_levels_and_sample_limit(1_000_000_000, 2_560, None, 8_000_000,)
327 .unwrap(),
328 expected
329 );
330 }
331
332 #[test]
333 fn fractional_power_is_exact_at_and_between_perfect_powers() {
334 assert_eq!(fractional_power_ceil(1_000_000_000, 2, 3), 1_000_000);
335 assert_eq!(fractional_power_ceil(1_000_000_001, 2, 3), 1_000_001);
336 assert_eq!(fractional_power_ceil(1_000_000_000, 3, 4), 5_623_414);
337 assert_eq!(
338 fractional_power_ceil(u64::MAX, 3, 4),
339 u64::from(MAX_SCANN_LEAVES) + 1
340 );
341 }
342
343 #[test]
344 fn geometry_defers_partitioning_below_hardcoded_floor() {
345 let geometry = derive_geometry(99_999, 1_024).unwrap();
346 assert_eq!(geometry.centroid_levels, 0);
347 assert!(geometry.level_counts.is_empty());
348 }
349
350 #[test]
351 fn explicit_depth_is_validated_and_balanced() {
352 assert_eq!(
353 geometry_for_leaves(10_000, 3).unwrap().level_counts,
354 [22, 484, 10_000]
355 );
356 assert!(geometry_for_leaves(10_000, 4).is_err());
357 }
358
359 #[test]
360 fn explicit_leaf_depth_depends_on_width_not_synthetic_corpus_bands() {
361 assert_eq!(
362 geometry_for_leaves_with_auto_depth(31_622, 2_560, None)
363 .unwrap()
364 .level_counts,
365 [178, 31_622]
366 );
367 assert_eq!(
368 geometry_for_leaves_with_auto_depth(31_623, 2_560, None)
369 .unwrap()
370 .level_counts,
371 [178, 31_623]
372 );
373 assert_eq!(
374 geometry_for_leaves_with_auto_depth(1_000_000, 1_024, None)
375 .unwrap()
376 .level_counts,
377 [1_000, 1_000_000]
378 );
379 }
380
381 #[test]
382 fn automatic_geometry_partitions_at_the_exact_hardcoded_floor() {
383 assert_eq!(
384 derive_geometry(MIN_POINTS_FOR_PARTITIONING, 1_024)
385 .unwrap()
386 .level_counts,
387 [317]
388 );
389 }
390}