1use crate::kernels::{MAX_DOT_I8_DIMENSION, dot_bit4_prepared, score_bit4_prepared_batch};
23
24use super::QuantError;
25
26const CODES_PER_BYTE: usize = 2;
27const MAX_MAGNITUDE_LEVEL: u8 = 7;
28
29#[derive(Clone, Copy, Debug, PartialEq)]
31#[repr(C)]
32pub struct Bit4Factors {
33 scale: f32,
34 normalized_norm: f32,
35 normalized_correction: f32,
36}
37
38impl Bit4Factors {
39 #[must_use]
41 pub const fn from_persisted(
42 scale: f32,
43 normalized_norm: f32,
44 normalized_correction: f32,
45 ) -> Self {
46 Self {
47 scale,
48 normalized_norm,
49 normalized_correction,
50 }
51 }
52
53 #[must_use]
55 pub const fn persisted_fields(self) -> [f32; 3] {
56 [self.scale, self.normalized_norm, self.normalized_correction]
57 }
58
59 #[must_use]
65 pub const fn norm(self) -> f64 {
66 self.scale as f64 * self.normalized_norm as f64
67 }
68
69 #[must_use]
74 pub const fn correction(self) -> f64 {
75 self.scale as f64 * self.normalized_correction as f64
76 }
77
78 #[must_use]
85 pub fn reconstruction_error_bound(self) -> f64 {
86 std::f64::consts::SQRT_2 * self.norm()
87 }
88
89 pub(crate) const fn scoring_parts(self) -> (f32, f32) {
90 (self.scale, self.normalized_correction)
91 }
92}
93
94#[derive(Clone, Debug, PartialEq)]
101pub struct Bit4Query {
102 codes: Vec<i8>,
104 code_sum: i32,
105 scale_half: f64,
106}
107
108impl Bit4Query {
109 #[must_use]
111 pub fn len(&self) -> usize {
112 self.codes.len()
113 }
114
115 #[must_use]
117 pub fn is_empty(&self) -> bool {
118 self.codes.is_empty()
119 }
120
121 pub(crate) fn resident_bytes(&self) -> usize {
122 self.codes.capacity()
123 }
124
125 #[cfg(any(test, feature = "test-support"))]
128 #[doc(hidden)]
129 #[must_use]
130 pub fn observation_parts(&self) -> (&[i8], i32, f64) {
131 (&self.codes, self.code_sum, self.scale_half)
132 }
133
134 pub(crate) fn kernel_parts(&self) -> (&[i8], i32, f64) {
135 (&self.codes, self.code_sum, self.scale_half)
136 }
137}
138
139#[derive(Clone, Copy, Debug)]
140struct SplitMix64 {
141 state: u64,
142}
143
144impl SplitMix64 {
145 const fn new(seed: u64) -> Self {
146 Self { state: seed }
147 }
148
149 fn next_open_unit_f64(&mut self) -> f64 {
150 self.state = self.state.wrapping_add(0x9e37_79b9_7f4a_7c15);
151 let mut value = self.state;
152 value = (value ^ (value >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
153 value = (value ^ (value >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
154 value ^= value >> 31;
155 ((value >> 11) as f64 + 0.5) * (1.0 / 9_007_199_254_740_992.0)
156 }
157}
158
159#[derive(Clone, Copy, Debug)]
160struct CriticalValue {
161 threshold: f64,
162 coordinate: usize,
163 level: u8,
164 magnitude: f64,
165}
166
167pub fn quantize_bit4(v: &[f32], out: &mut [u8]) -> Result<Bit4Factors, QuantError> {
181 validate_input(v, out.len())?;
182
183 let mut norm_squared = 0.0_f64;
184 let mut absolute_sum = 0.0_f64;
185 let mut row_scale = 0.0_f64;
186 let mut critical_values = Vec::with_capacity(v.len().saturating_mul(7));
187 for (coordinate, &value) in v.iter().enumerate() {
188 let value = f64::from(value);
189 let magnitude = value.abs();
190 norm_squared += value * value;
191 absolute_sum += magnitude;
192 row_scale = row_scale.max(magnitude);
193 if magnitude > 0.0 {
194 for level in 1..=MAX_MAGNITUDE_LEVEL {
195 critical_values.push(CriticalValue {
196 threshold: f64::from(level) / magnitude,
197 coordinate,
198 level,
199 magnitude,
200 });
201 }
202 }
203 }
204 critical_values.sort_unstable_by(|left, right| {
205 left.threshold
206 .total_cmp(&right.threshold)
207 .then_with(|| left.coordinate.cmp(&right.coordinate))
208 .then_with(|| left.level.cmp(&right.level))
209 });
210
211 let mut numerator = 0.5 * absolute_sum;
212 let mut grid_norm_squared = 0.25 * v.len() as f64;
213 let mut best_numerator = numerator;
214 let mut best_score_squared = normalized_score_squared(numerator, grid_norm_squared);
215 let mut best_event_count = 0_usize;
216 let mut event_count = 0_usize;
217 let mut events = critical_values.iter().peekable();
218 while let Some(event) = events.next() {
219 let threshold = event.threshold;
220 apply_event(event, &mut numerator, &mut grid_norm_squared);
221 event_count += 1;
222 while events
223 .peek()
224 .is_some_and(|next| next.threshold == threshold)
225 {
226 if let Some(tied) = events.next() {
227 apply_event(tied, &mut numerator, &mut grid_norm_squared);
228 event_count += 1;
229 }
230 }
231 let score_squared = normalized_score_squared(numerator, grid_norm_squared);
232 if score_squared > best_score_squared {
233 best_score_squared = score_squared;
234 best_numerator = numerator;
235 best_event_count = event_count;
236 }
237 }
238
239 let mut magnitudes = vec![0_u8; v.len()];
240 for event in critical_values.iter().take(best_event_count) {
241 if let Some(level) = magnitudes.get_mut(event.coordinate) {
242 *level = event.level;
243 }
244 }
245 pack(v, &magnitudes, out);
246
247 let (normalized_norm, normalized_correction) = if norm_squared == 0.0 {
248 (0.0, 0.0)
249 } else {
250 (
251 (norm_squared.sqrt() / row_scale) as f32,
252 (norm_squared / (row_scale * best_numerator)) as f32,
253 )
254 };
255 Ok(Bit4Factors {
256 scale: row_scale as f32,
257 normalized_norm,
258 normalized_correction,
259 })
260}
261
262pub fn dequantize_bit4(
273 codes: &[u8],
274 factors: Bit4Factors,
275 out: &mut [f32],
276) -> Result<(), QuantError> {
277 let unpacked = unpack(codes, out.len())?;
278 if factors.scale == 0.0 {
279 out.fill(0.0);
280 return Ok(());
281 }
282 let doubled_code_norm = unpacked
283 .iter()
284 .map(|&value| {
285 let value = f64::from(value);
286 value * value
287 })
288 .sum::<f64>()
289 .sqrt();
290 for (output, &value) in out.iter_mut().zip(&unpacked) {
291 *output = finite_f32(factors.norm() * f64::from(value) / doubled_code_norm);
292 }
293 Ok(())
294}
295
296pub fn prepare_bit4_query(q: &[f32], seed: u64) -> Result<Bit4Query, QuantError> {
308 validate_vector(q)?;
309 #[cfg(any(test, feature = "test-support"))]
310 super::QUERY_PREPARATIONS.with(|calls| {
311 if let Some(calls) = calls.borrow_mut().as_mut() {
312 calls.bit4.push((q.len(), seed));
313 }
314 });
315 let max_absolute = q.iter().map(|value| value.abs()).fold(0.0_f32, f32::max);
316 if max_absolute == 0.0 {
317 return Ok(Bit4Query {
318 codes: vec![0_i8; q.len()],
319 code_sum: 0,
320 scale_half: 0.0,
321 });
322 }
323
324 let scale = f64::from(max_absolute) / 127.0;
325 let mut random = SplitMix64::new(seed);
326 let mut coordinate_codes = Vec::with_capacity(q.len());
327 for &value in q {
328 let scaled = f64::from(value) / scale;
329 let lower = scaled.floor();
330 let probability_up = scaled - lower;
331 let rounded = if random.next_open_unit_f64() < probability_up {
332 lower + 1.0
333 } else {
334 lower
335 };
336 coordinate_codes.push(rounded.clamp(-127.0, 127.0) as i8);
337 }
338 let code_sum = coordinate_codes.iter().map(|&code| i32::from(code)).sum();
339 let codes = interleave_bit4_query_blocks(&coordinate_codes);
340 Ok(Bit4Query {
341 codes,
342 code_sum,
343 scale_half: scale * 0.5,
344 })
345}
346
347pub fn est_dot_bit4(
364 query: &Bit4Query,
365 codes: &[u8],
366 factors: Bit4Factors,
367) -> Result<f32, QuantError> {
368 validate_code(codes, query.codes.len())?;
369 if factors.scale == 0.0 {
370 return Ok(0.0);
371 }
372 let integer_dot = dot_bit4_prepared(&query.codes, query.code_sum, codes);
373 Ok((factors.correction() * query.scale_half * f64::from(integer_dot)) as f32)
374}
375
376pub fn est_dot_bit4_batch(
388 query: &Bit4Query,
389 codes: &[u8],
390 factors: &[Bit4Factors],
391 out: &mut [f32],
392) -> Result<(), QuantError> {
393 if out.len() != factors.len() {
394 return Err(QuantError::OutputLength {
395 expected: factors.len(),
396 actual: out.len(),
397 });
398 }
399 let row_bytes = query.codes.len().div_ceil(CODES_PER_BYTE);
400 let expected = row_bytes.saturating_mul(factors.len());
401 if codes.len() != expected {
402 return Err(QuantError::CodeLength {
403 expected,
404 actual: codes.len(),
405 });
406 }
407 if !query.codes.len().is_multiple_of(CODES_PER_BYTE) {
408 for row in codes.chunks_exact(row_bytes) {
409 validate_padding(row, query.codes.len())?;
410 }
411 }
412 score_bit4_prepared_batch(
413 &query.codes,
414 query.code_sum,
415 query.scale_half,
416 codes,
417 query.codes.len(),
418 factors,
419 out,
420 );
421 Ok(())
422}
423
424fn interleave_bit4_query_blocks(codes: &[i8]) -> Vec<i8> {
425 let mut interleaved = Vec::with_capacity(codes.len());
426 for block in codes.chunks(32) {
427 interleaved.extend(block.iter().step_by(2).copied());
428 interleaved.extend(block.iter().skip(1).step_by(2).copied());
429 }
430 interleaved
431}
432
433fn validate_input(v: &[f32], output_len: usize) -> Result<(), QuantError> {
434 validate_vector(v)?;
435 let expected = v.len().div_ceil(CODES_PER_BYTE);
436 if output_len != expected {
437 return Err(QuantError::OutputLength {
438 expected,
439 actual: output_len,
440 });
441 }
442 Ok(())
443}
444
445fn validate_vector(v: &[f32]) -> Result<(), QuantError> {
446 if v.is_empty() {
447 return Err(QuantError::EmptyVector);
448 }
449 if v.len() > MAX_DOT_I8_DIMENSION {
450 return Err(QuantError::DimensionTooLarge {
451 actual: v.len(),
452 maximum: MAX_DOT_I8_DIMENSION,
453 });
454 }
455 if let Some((index, _)) = v.iter().enumerate().find(|(_, value)| !value.is_finite()) {
456 return Err(QuantError::NonFinite { index });
457 }
458 Ok(())
459}
460
461fn unpack(codes: &[u8], dimension: usize) -> Result<Vec<i8>, QuantError> {
462 validate_code(codes, dimension)?;
463
464 let mut unpacked = Vec::with_capacity(dimension);
465 for &byte in codes {
466 for shift in [4_u32, 0] {
467 if unpacked.len() == dimension {
468 break;
469 }
470 let unsigned = (byte >> shift) & 0b1111;
471 unpacked.push((i16::from(unsigned) * 2 - 15) as i8);
472 }
473 }
474 Ok(unpacked)
475}
476
477fn validate_code(codes: &[u8], dimension: usize) -> Result<(), QuantError> {
478 let expected = dimension.div_ceil(CODES_PER_BYTE);
479 if codes.len() != expected {
480 return Err(QuantError::CodeLength {
481 expected,
482 actual: codes.len(),
483 });
484 }
485 validate_padding(codes, dimension)?;
486 Ok(())
487}
488
489fn validate_padding(codes: &[u8], dimension: usize) -> Result<(), QuantError> {
490 if dimension.is_multiple_of(CODES_PER_BYTE) {
491 return Ok(());
492 }
493 let mask = 0x0f;
494 if let Some(&byte) = codes.last()
495 && byte & mask != 0
496 {
497 return Err(QuantError::NonZeroPadding { byte, mask });
498 }
499 Ok(())
500}
501
502fn normalized_score_squared(numerator: f64, norm_squared: f64) -> f64 {
503 numerator * numerator / norm_squared
504}
505
506fn finite_f32(value: f64) -> f32 {
507 value.clamp(f64::from(f32::MIN), f64::from(f32::MAX)) as f32
508}
509
510fn apply_event(event: &CriticalValue, numerator: &mut f64, norm_squared: &mut f64) {
511 *numerator += event.magnitude;
512 *norm_squared += 2.0 * f64::from(event.level);
513}
514
515fn pack(v: &[f32], magnitudes: &[u8], out: &mut [u8]) {
516 for ((values, levels), byte) in v
517 .chunks(CODES_PER_BYTE)
518 .zip(magnitudes.chunks(CODES_PER_BYTE))
519 .zip(out.iter_mut())
520 {
521 let mut packed = 0_u8;
522 for (field, (&value, &level)) in values.iter().zip(levels).enumerate() {
523 let unsigned = if value < 0.0 {
524 MAX_MAGNITUDE_LEVEL - level
525 } else {
526 MAX_MAGNITUDE_LEVEL + 1 + level
527 };
528 let shift = 4_u32.saturating_sub((field as u32) * 4);
529 packed |= unsigned << shift;
530 }
531 *byte = packed;
532 }
533}