1use crate::kernels::{MAX_DOT_I8_DIMENSION, dot_bit4_prepared, score_bit4_prepared_batch};
23
24use super::QuantError;
25
26mod controlled;
27pub(crate) use controlled::{
28 Bit4ControlError, Bit4Scratch, est_dot_bit4_controlled, prepare_bit4_query_controlled,
29 quantize_bit4_controlled,
30};
31
32const CODES_PER_BYTE: usize = 2;
33const MAX_MAGNITUDE_LEVEL: u8 = 7;
34
35#[derive(Clone, Copy, Debug, PartialEq)]
37#[repr(C)]
38pub struct Bit4Factors {
39 scale: f32,
40 normalized_norm: f32,
41 normalized_correction: f32,
42}
43
44impl Bit4Factors {
45 #[must_use]
47 pub const fn from_persisted(
48 scale: f32,
49 normalized_norm: f32,
50 normalized_correction: f32,
51 ) -> Self {
52 Self {
53 scale,
54 normalized_norm,
55 normalized_correction,
56 }
57 }
58
59 #[must_use]
61 pub const fn persisted_fields(self) -> [f32; 3] {
62 [self.scale, self.normalized_norm, self.normalized_correction]
63 }
64
65 #[must_use]
71 pub const fn norm(self) -> f64 {
72 self.scale as f64 * self.normalized_norm as f64
73 }
74
75 #[must_use]
80 pub const fn correction(self) -> f64 {
81 self.scale as f64 * self.normalized_correction as f64
82 }
83
84 #[must_use]
91 pub fn reconstruction_error_bound(self) -> f64 {
92 std::f64::consts::SQRT_2 * self.norm()
93 }
94
95 pub(crate) const fn scoring_parts(self) -> (f32, f32) {
96 (self.scale, self.normalized_correction)
97 }
98}
99
100#[derive(Clone, Debug, PartialEq)]
107pub struct Bit4Query {
108 codes: Vec<i8>,
110 code_sum: i32,
111 scale_half: f64,
112}
113
114impl Bit4Query {
115 #[must_use]
117 pub fn len(&self) -> usize {
118 self.codes.len()
119 }
120
121 #[must_use]
123 pub fn is_empty(&self) -> bool {
124 self.codes.is_empty()
125 }
126
127 pub(crate) fn resident_bytes(&self) -> usize {
128 self.codes.capacity()
129 }
130
131 #[cfg(any(test, feature = "test-seams"))]
134 #[doc(hidden)]
135 #[must_use]
136 pub fn observation_parts(&self) -> (&[i8], i32, f64) {
137 (&self.codes, self.code_sum, self.scale_half)
138 }
139
140 pub(crate) fn kernel_parts(&self) -> (&[i8], i32, f64) {
141 (&self.codes, self.code_sum, self.scale_half)
142 }
143}
144
145#[derive(Clone, Copy, Debug)]
146struct SplitMix64 {
147 state: u64,
148}
149
150impl SplitMix64 {
151 const fn new(seed: u64) -> Self {
152 Self { state: seed }
153 }
154
155 fn next_open_unit_f64(&mut self) -> f64 {
156 self.state = self.state.wrapping_add(0x9e37_79b9_7f4a_7c15);
157 let mut value = self.state;
158 value = (value ^ (value >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
159 value = (value ^ (value >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
160 value ^= value >> 31;
161 ((value >> 11) as f64 + 0.5) * (1.0 / 9_007_199_254_740_992.0)
162 }
163}
164
165#[derive(Clone, Copy, Debug)]
166struct CriticalValue {
167 threshold: f64,
168 coordinate: usize,
169 level: u8,
170 magnitude: f64,
171}
172
173pub fn quantize_bit4(v: &[f32], out: &mut [u8]) -> Result<Bit4Factors, QuantError> {
187 validate_input(v, out.len())?;
188 let mut scratch = Bit4Scratch::compatibility(v.len());
189 match quantize_bit4_controlled(v, out, &mut scratch, &mut |_| Ok::<_, ()>(())) {
190 Ok(factors) => Ok(factors),
191 Err(Bit4ControlError::Quant(error)) => Err(error),
192 Err(Bit4ControlError::Memory) => Err(QuantError::Allocation),
193 Err(Bit4ControlError::Control(())) => Err(QuantError::Allocation),
194 }
195}
196
197pub fn dequantize_bit4(
208 codes: &[u8],
209 factors: Bit4Factors,
210 out: &mut [f32],
211) -> Result<(), QuantError> {
212 let unpacked = unpack(codes, out.len())?;
213 if factors.scale == 0.0 {
214 out.fill(0.0);
215 return Ok(());
216 }
217 let doubled_code_norm = unpacked
218 .iter()
219 .map(|&value| {
220 let value = f64::from(value);
221 value * value
222 })
223 .sum::<f64>()
224 .sqrt();
225 for (output, &value) in out.iter_mut().zip(&unpacked) {
226 *output = finite_f32(factors.norm() * f64::from(value) / doubled_code_norm);
227 }
228 Ok(())
229}
230
231pub fn prepare_bit4_query(q: &[f32], seed: u64) -> Result<Bit4Query, QuantError> {
243 #[cfg(any(test, feature = "test-seams"))]
244 super::QUERY_PREPARATIONS.with(|calls| {
245 if let Some(calls) = calls.borrow_mut().as_mut() {
246 calls.bit4.push((q.len(), seed));
247 }
248 });
249 match prepare_bit4_query_controlled(q, seed, &mut |_| Ok::<_, ()>(())) {
250 Ok((query, _)) => Ok(query),
251 Err(Bit4ControlError::Quant(error)) => Err(error),
252 Err(Bit4ControlError::Memory) => Err(QuantError::Allocation),
253 Err(Bit4ControlError::Control(())) => Err(QuantError::Allocation),
254 }
255}
256
257pub fn est_dot_bit4(
274 query: &Bit4Query,
275 codes: &[u8],
276 factors: Bit4Factors,
277) -> Result<f32, QuantError> {
278 match est_dot_bit4_controlled(query, codes, factors, &mut |_| {
279 Ok::<_, std::convert::Infallible>(())
280 }) {
281 Ok(value) => Ok(value),
282 Err(Bit4ControlError::Quant(error)) => Err(error),
283 Err(Bit4ControlError::Memory) => Err(QuantError::Allocation),
284 Err(Bit4ControlError::Control(never)) => match never {},
285 }
286}
287
288pub fn est_dot_bit4_batch(
300 query: &Bit4Query,
301 codes: &[u8],
302 factors: &[Bit4Factors],
303 out: &mut [f32],
304) -> Result<(), QuantError> {
305 if out.len() != factors.len() {
306 return Err(QuantError::OutputLength {
307 expected: factors.len(),
308 actual: out.len(),
309 });
310 }
311 let row_bytes = query.codes.len().div_ceil(CODES_PER_BYTE);
312 let expected = row_bytes.saturating_mul(factors.len());
313 if codes.len() != expected {
314 return Err(QuantError::CodeLength {
315 expected,
316 actual: codes.len(),
317 });
318 }
319 if !query.codes.len().is_multiple_of(CODES_PER_BYTE) {
320 for row in codes.chunks_exact(row_bytes) {
321 validate_padding(row, query.codes.len())?;
322 }
323 }
324 score_bit4_prepared_batch(
325 &query.codes,
326 query.code_sum,
327 query.scale_half,
328 codes,
329 query.codes.len(),
330 factors,
331 out,
332 );
333 Ok(())
334}
335
336fn validate_input(v: &[f32], output_len: usize) -> Result<(), QuantError> {
337 validate_vector(v)?;
338 let expected = v.len().div_ceil(CODES_PER_BYTE);
339 if output_len != expected {
340 return Err(QuantError::OutputLength {
341 expected,
342 actual: output_len,
343 });
344 }
345 Ok(())
346}
347
348fn validate_vector_shape(v: &[f32]) -> Result<(), QuantError> {
349 if v.is_empty() {
350 return Err(QuantError::EmptyVector);
351 }
352 if v.len() > MAX_DOT_I8_DIMENSION {
353 return Err(QuantError::DimensionTooLarge {
354 actual: v.len(),
355 maximum: MAX_DOT_I8_DIMENSION,
356 });
357 }
358 Ok(())
359}
360
361fn validate_vector(v: &[f32]) -> Result<(), QuantError> {
362 validate_vector_shape(v)?;
363 if let Some((index, _)) = v.iter().enumerate().find(|(_, value)| !value.is_finite()) {
364 return Err(QuantError::NonFinite { index });
365 }
366 Ok(())
367}
368
369fn unpack(codes: &[u8], dimension: usize) -> Result<Vec<i8>, QuantError> {
370 validate_code(codes, dimension)?;
371
372 let mut unpacked = Vec::with_capacity(dimension);
373 for &byte in codes {
374 for shift in [4_u32, 0] {
375 if unpacked.len() == dimension {
376 break;
377 }
378 let unsigned = (byte >> shift) & 0b1111;
379 unpacked.push((i16::from(unsigned) * 2 - 15) as i8);
380 }
381 }
382 Ok(unpacked)
383}
384
385fn validate_code(codes: &[u8], dimension: usize) -> Result<(), QuantError> {
386 let expected = dimension.div_ceil(CODES_PER_BYTE);
387 if codes.len() != expected {
388 return Err(QuantError::CodeLength {
389 expected,
390 actual: codes.len(),
391 });
392 }
393 validate_padding(codes, dimension)?;
394 Ok(())
395}
396
397fn validate_padding(codes: &[u8], dimension: usize) -> Result<(), QuantError> {
398 if dimension.is_multiple_of(CODES_PER_BYTE) {
399 return Ok(());
400 }
401 let mask = 0x0f;
402 if let Some(&byte) = codes.last()
403 && byte & mask != 0
404 {
405 return Err(QuantError::NonZeroPadding { byte, mask });
406 }
407 Ok(())
408}
409
410fn normalized_score_squared(numerator: f64, norm_squared: f64) -> f64 {
411 numerator * numerator / norm_squared
412}
413
414fn finite_f32(value: f64) -> f32 {
415 value.clamp(f64::from(f32::MIN), f64::from(f32::MAX)) as f32
416}
417
418fn apply_event(event: &CriticalValue, numerator: &mut f64, norm_squared: &mut f64) {
419 *numerator += event.magnitude;
420 *norm_squared += 2.0 * f64::from(event.level);
421}