Skip to main content

innr/
lib.rs

1//! SIMD-accelerated vector similarity primitives.
2//!
3//! Fast building blocks for embedding similarity with automatic hardware dispatch.
4//!
5//! # Which Function Should I Use?
6//!
7//! | Task | Function | Notes |
8//! |------|----------|-------|
9//! | **Similarity (normalized)** | [`cosine`] | Most embeddings are normalized |
10//! | **Similarity (raw)** | [`dot`] | When you know norms |
11//! | **Distance (L2)** | [`l2_distance`] | For k-NN, clustering |
12//! | **Token-level matching** | `maxsim` | ColBERT-style (feature `maxsim`) |
13//! | **Sparse vectors** | `sparse_dot` | BM25 scores (feature `sparse`) |
14//!
15//! # SIMD Dispatch
16//!
17//! All functions automatically dispatch to the fastest available instruction set:
18//!
19//! | Architecture | Instructions | Detection |
20//! |--------------|--------------|-----------|
21//! | x86_64 | AVX2 + FMA | Runtime |
22//! | aarch64 | NEON | Always available |
23//! | Other | Portable | LLVM auto-vectorizes |
24//!
25//! Vectors shorter than 16 dimensions use portable code (SIMD overhead not worthwhile).
26//!
27//! # Historical Context
28//!
29//! The inner product (dot product) dates to Grassmann's 1844 "Ausdehnungslehre" and
30//! Hamilton's quaternions, formalized in Gibbs and Heaviside's vector calculus (~1880s).
31//! Modern embedding similarity (Word2Vec 2013, BERT 2018) relies on inner products
32//! in high-dimensional spaces where SIMD acceleration is essential.
33//!
34//! ColBERT's MaxSim (Khattab & Zaharia, 2020) extends this to token-level late
35//! interaction, requiring O(|Q| x |D|) inner products per query-document pair.
36//!
37//! # Example
38//!
39//! ```rust
40//! use innr::{dot, cosine, norm};
41//!
42//! let a = [1.0_f32, 0.0, 0.0];
43//! let b = [0.707, 0.707, 0.0];
44//!
45//! // Dot product
46//! let d = dot(&a, &b);
47//! assert!((d - 0.707).abs() < 0.01);
48//!
49//! // Cosine similarity (normalized dot product)
50//! let c = cosine(&a, &b);
51//! assert!((c - 0.707).abs() < 0.01);
52//!
53//! // L2 norm
54//! let n = norm(&a);
55//! assert!((n - 1.0).abs() < 1e-6);
56//! ```
57//!
58//! # References
59//!
60//! - Gibbs, J.W. (1881). "Elements of Vector Analysis"
61//! - Mikolov et al. (2013). "Efficient Estimation of Word Representations" (Word2Vec)
62//! - Khattab & Zaharia (2020). "ColBERT: Efficient and Effective Passage Search"
63
64#![cfg_attr(docsrs, feature(doc_cfg))]
65#![warn(missing_docs)]
66#![warn(clippy::all)]
67
68mod arch;
69
70/// Binary (1-bit) quantization: encode, Hamming distance, dot product, Jaccard.
71pub mod binary;
72
73/// Clifford algebra rotors for steerable embeddings (2D geometric product).
74pub mod clifford;
75
76/// Dense vector primitives: dot, cosine, norm, L2/L1 distance, matryoshka.
77pub mod dense;
78
79/// Metric and quasimetric trait interfaces (dependency-free).
80pub mod metric;
81
82/// Fast math operations using hardware-aware approximations (rsqrt, NR iteration).
83pub mod fast_math;
84
85/// Batch vector operations with columnar (PDX-style) layout.
86pub mod batch;
87
88/// Sparse vector dot product via sorted-index merge join.
89#[cfg(feature = "sparse")]
90#[cfg_attr(docsrs, doc(cfg(feature = "sparse")))]
91mod sparse;
92
93/// ColBERT MaxSim late interaction scoring for multi-vector retrieval.
94#[cfg(feature = "maxsim")]
95#[cfg_attr(docsrs, doc(cfg(feature = "maxsim")))]
96mod maxsim;
97
98// Re-export core operations
99pub use dense::{
100    angular_distance, cosine, dot, dot_portable, l1_distance, l2_distance, l2_distance_squared,
101    matryoshka_cosine, matryoshka_dot, norm, pool_mean,
102};
103
104// Re-export binary operations
105pub use binary::{binary_dot, binary_hamming, binary_jaccard, encode_binary, PackedBinary};
106
107// Re-export metric trait surfaces (interfaces only).
108pub use metric::{Quasimetric, SymmetricMetric};
109
110// Re-export fast math (rsqrt-based approximations)
111pub use fast_math::{fast_cosine, fast_cosine_dispatch, fast_rsqrt, fast_rsqrt_precise};
112
113/// Ternary quantization (1.58-bit) for ultra-compressed embeddings.
114pub mod ternary;
115
116#[cfg(feature = "sparse")]
117#[cfg_attr(docsrs, doc(cfg(feature = "sparse")))]
118pub use sparse::{sparse_dot, sparse_dot_portable, sparse_maxsim};
119
120#[cfg(feature = "maxsim")]
121#[cfg_attr(docsrs, doc(cfg(feature = "maxsim")))]
122pub use maxsim::{maxsim, maxsim_cosine};
123
124/// Minimum vector dimension for SIMD to be worthwhile.
125///
126/// Below this threshold, function call overhead outweighs SIMD benefits.
127/// Matches qdrant's MIN_DIM_SIZE_SIMD threshold.
128pub const MIN_DIM_SIMD: usize = 16;
129
130/// Threshold for treating a norm as "effectively zero".
131///
132/// Chosen to be larger than `f32::EPSILON` (~1.19e-7) to provide numerical
133/// headroom while remaining small enough to only catch degenerate cases.
134///
135/// Used by [`cosine`] to avoid division by zero.
136pub const NORM_EPSILON: f32 = 1e-9;
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141
142    #[test]
143    fn test_dot_basic() {
144        let a = [1.0_f32, 2.0, 3.0];
145        let b = [4.0_f32, 5.0, 6.0];
146        let result = dot(&a, &b);
147        assert!((result - 32.0).abs() < 1e-6);
148    }
149
150    #[test]
151    fn test_dot_empty() {
152        let a: [f32; 0] = [];
153        let b: [f32; 0] = [];
154        assert_eq!(dot(&a, &b), 0.0);
155    }
156
157    #[test]
158    fn test_norm() {
159        let v = [3.0_f32, 4.0];
160        assert!((norm(&v) - 5.0).abs() < 1e-6);
161    }
162
163    #[test]
164    fn test_cosine_orthogonal() {
165        let a = [1.0_f32, 0.0];
166        let b = [0.0_f32, 1.0];
167        assert!(cosine(&a, &b).abs() < 1e-6);
168    }
169
170    #[test]
171    fn test_cosine_parallel() {
172        let a = [1.0_f32, 0.0];
173        let b = [2.0_f32, 0.0];
174        assert!((cosine(&a, &b) - 1.0).abs() < 1e-6);
175    }
176
177    #[test]
178    fn test_cosine_zero_vector() {
179        let a = [1.0_f32, 2.0];
180        let zero = [0.0_f32, 0.0];
181        assert_eq!(cosine(&a, &zero), 0.0);
182    }
183
184    #[test]
185    fn test_l2_distance() {
186        let a = [0.0_f32, 0.0];
187        let b = [3.0_f32, 4.0];
188        assert!((l2_distance(&a, &b) - 5.0).abs() < 1e-6);
189    }
190
191    #[test]
192    fn test_l2_distance_same_point() {
193        let a = [1.0_f32, 2.0, 3.0];
194        assert!(l2_distance(&a, &a) < 1e-9);
195    }
196
197    #[test]
198    fn test_l1_distance() {
199        let a = [1.0_f32, 2.0, 3.0];
200        let b = [4.0_f32, 0.0, 1.0];
201        // |1-4| + |2-0| + |3-1| = 3 + 2 + 2 = 7
202        assert!((l1_distance(&a, &b) - 7.0).abs() < 1e-6);
203    }
204}