lattice_embed/lib.rs
1//! **Stability tier**: Unstable
2//!
3//! The SIMD dispatch layer (`simd/`) and model-loading API are actively evolving.
4//! Platform consumers should use this crate via `lattice-engine`, not directly.
5//!
6//! **Exception — stable khive ANN consumer contract:**
7//! `simd::{squared_euclidean_distance, euclidean_distance, dot_product, cosine_similarity}`
8//! are a stable consumer contract for khive's ANN indexes (`khive-hnsw`, `khive-vamana`;
9//! ADR-012): their `(&[f32], &[f32]) -> f32` signatures and documented degenerate-input
10//! behaviour are guaranteed across the 0.4.x line, as is the squared-L2 ordering invariant
11//! relative to this crate's Euclidean wrapper (both derive from the same accumulated squared
12//! distance; SIMD is not bit-identical to scalar and promises no exact ordering of near-ties).
13//! The rest of `simd/` remains unstable.
14//!
15//! The 21 `unsafe` blocks are gated SIMD intrinsic calls (AVX512/AVX2/NEON); the
16//! 1 `dead_code_allows` retains a superseded dot-product fallback for reference.
17//! See `foundation/STABILITY.md` for the full policy.
18//!
19//! # lattice-embed
20
21#![warn(missing_docs)]
22#![allow(clippy::clone_on_copy)]
23//!
24//! Vector embedding generation with SIMD-accelerated operations for the lattice-runtime substrate.
25//!
26//! This crate provides embedding generation services that convert text into
27//! high-dimensional vector representations suitable for semantic search and
28//! similarity matching.
29//!
30//! ## Features
31//!
32//! - **Native Embeddings**: Generate embeddings locally using pure Rust inference (default)
33//! - **BGE Models**: Support for BGE family of models (small/base/large)
34//! - **Async API**: Full async/await support with tokio
35//! - **SIMD Acceleration**: AVX2/NEON optimized vector operations
36//!
37//! ## Quick Start
38//!
39//! ```rust,no_run
40//! use lattice_embed::{EmbeddingService, EmbeddingModel, NativeEmbeddingService};
41//!
42//! #[tokio::main]
43//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
44//! let service = NativeEmbeddingService::default();
45//!
46//! let embedding = service.embed_one(
47//! "The quick brown fox jumps over the lazy dog",
48//! EmbeddingModel::default(),
49//! ).await?;
50//!
51//! println!("Embedding dimension: {}", embedding.len());
52//! // Output: Embedding dimension: 384
53//!
54//! Ok(())
55//! }
56//! ```
57//!
58//! ## Batch Processing
59//!
60//! ```rust,no_run
61//! use lattice_embed::{EmbeddingService, EmbeddingModel, NativeEmbeddingService};
62//!
63//! #[tokio::main]
64//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
65//! let service = NativeEmbeddingService::default();
66//!
67//! let texts = vec![
68//! "First document".to_string(),
69//! "Second document".to_string(),
70//! "Third document".to_string(),
71//! ];
72//!
73//! let embeddings = service.embed(&texts, EmbeddingModel::BgeSmallEnV15).await?;
74//! assert_eq!(embeddings.len(), 3);
75//!
76//! Ok(())
77//! }
78//! ```
79//!
80//! ## Available Models
81//!
82//! | Model | Dimensions | Use Case |
83//! |-------|------------|----------|
84//! | `BgeSmallEnV15` | 384 | Fast, general purpose (default) |
85//! | `BgeBaseEnV15` | 768 | Balanced quality/speed |
86//! | `BgeLargeEnV15` | 1024 | Highest quality |
87
88pub mod backfill;
89mod cache;
90mod error;
91pub mod migration;
92mod model;
93pub mod service;
94pub mod simd;
95pub mod types;
96// Gated on BOTH the `wasm` feature and the wasm32 target: `wasm-bindgen` is
97// only ever a dependency on the wasm32 target table (see Cargo.toml), so if
98// this module were feature-gated alone, enabling `--features wasm` on a
99// native build would fail to find the `wasm_bindgen` crate. Gating on both
100// makes enabling `wasm` on a non-wasm target a no-op instead of a build error.
101#[cfg(all(feature = "wasm", target_arch = "wasm32"))]
102pub mod wasm;
103
104pub use cache::{CacheStats, DEFAULT_CACHE_CAPACITY, EmbeddingCache, ShardStats};
105pub use error::{EmbedError, Result};
106pub use model::{EmbeddingModel, MIN_MRL_OUTPUT_DIM, ModelConfig, ModelProvenance};
107pub use service::{DEFAULT_MAX_BATCH_SIZE, EmbeddingRole, EmbeddingService, MAX_TEXT_CHARS};
108pub use simd::{SimdConfig, simd_config};
109
110#[cfg(feature = "native")]
111pub use service::{CachedEmbeddingService, NativeEmbeddingService};
112
113/// Utility functions for vector operations.
114///
115/// All functions in this module are SIMD-accelerated when available (AVX2 on x86_64, NEON on aarch64).
116/// Runtime feature detection ensures automatic fallback to scalar implementations
117/// on systems without SIMD support.
118pub mod utils {
119 use crate::simd;
120
121 /// **Stable**: external consumers may depend on this; breaking changes require a SemVer bump.
122 ///
123 /// Compute cosine similarity between two vectors.
124 ///
125 /// Uses SIMD acceleration (AVX2/NEON) when available, with automatic scalar fallback.
126 ///
127 /// Returns a value between -1.0 and 1.0, where 1.0 indicates
128 /// identical direction and -1.0 indicates opposite direction.
129 ///
130 /// # Performance
131 ///
132 /// | Dimension | Scalar | SIMD |
133 /// |-----------|--------|------|
134 /// | 384 | ~650ns | ~90ns |
135 /// | 768 | ~1300ns | ~180ns |
136 /// | 1024 | ~1700ns | ~240ns |
137 ///
138 /// # Example
139 ///
140 /// ```rust
141 /// use lattice_embed::utils::cosine_similarity;
142 ///
143 /// let a = vec![1.0, 0.0, 0.0];
144 /// let b = vec![1.0, 0.0, 0.0];
145 /// assert!((cosine_similarity(&a, &b) - 1.0).abs() < 0.0001);
146 ///
147 /// let c = vec![1.0, 0.0, 0.0];
148 /// let d = vec![0.0, 1.0, 0.0];
149 /// assert!((cosine_similarity(&c, &d) - 0.0).abs() < 0.0001);
150 /// ```
151 #[inline]
152 pub fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {
153 simd::cosine_similarity(a, b)
154 }
155
156 /// **Stable**: external consumers may depend on this; breaking changes require a SemVer bump.
157 ///
158 /// Compute dot product between two vectors.
159 ///
160 /// For normalized vectors (like embeddings), this equals cosine similarity.
161 /// Using `dot_product` directly on pre-normalized vectors is faster than
162 /// `cosine_similarity` since it skips norm computation.
163 ///
164 /// # Example
165 ///
166 /// ```rust
167 /// use lattice_embed::utils::dot_product;
168 ///
169 /// let a = vec![1.0, 2.0, 3.0];
170 /// let b = vec![4.0, 5.0, 6.0];
171 /// assert!((dot_product(&a, &b) - 32.0).abs() < 0.0001);
172 /// ```
173 #[inline]
174 pub fn dot_product(a: &[f32], b: &[f32]) -> f32 {
175 simd::dot_product(a, b)
176 }
177
178 /// **Stable**: external consumers may depend on this; breaking changes require a SemVer bump.
179 ///
180 /// Normalize a vector to unit length (L2 normalization).
181 ///
182 /// Modifies the vector in place. After normalization, the vector
183 /// will have magnitude 1.0.
184 ///
185 /// # Example
186 ///
187 /// ```rust
188 /// use lattice_embed::utils::normalize;
189 ///
190 /// let mut v = vec![3.0, 4.0];
191 /// normalize(&mut v);
192 ///
193 /// let magnitude: f32 = v.iter().map(|x| x * x).sum::<f32>().sqrt();
194 /// assert!((magnitude - 1.0).abs() < 0.0001);
195 /// ```
196 #[inline]
197 pub fn normalize(vector: &mut [f32]) {
198 simd::normalize(vector)
199 }
200
201 /// **Stable**: external consumers may depend on this; breaking changes require a SemVer bump.
202 ///
203 /// Compute Euclidean distance between two vectors.
204 ///
205 /// # Example
206 ///
207 /// ```rust
208 /// use lattice_embed::utils::euclidean_distance;
209 ///
210 /// let a = vec![0.0, 0.0];
211 /// let b = vec![3.0, 4.0];
212 /// assert!((euclidean_distance(&a, &b) - 5.0).abs() < 0.0001);
213 /// ```
214 #[inline]
215 pub fn euclidean_distance(a: &[f32], b: &[f32]) -> f32 {
216 simd::euclidean_distance(a, b)
217 }
218
219 /// **Stable**: external consumers may depend on this; breaking changes require a SemVer bump.
220 ///
221 /// Compute cosine similarities for many vector pairs (batch operation).
222 ///
223 /// More efficient than calling `cosine_similarity` in a loop.
224 #[inline]
225 pub fn batch_cosine_similarity(pairs: &[(&[f32], &[f32])]) -> Vec<f32> {
226 simd::batch_cosine_similarity(pairs)
227 }
228
229 /// **Stable**: external consumers may depend on this; breaking changes require a SemVer bump.
230 ///
231 /// Compute dot products for many vector pairs (batch operation).
232 #[inline]
233 pub fn batch_dot_product(pairs: &[(&[f32], &[f32])]) -> Vec<f32> {
234 simd::batch_dot_product(pairs)
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241
242 #[test]
243 fn test_cosine_similarity_identical() {
244 let a = vec![1.0, 2.0, 3.0];
245 let b = vec![1.0, 2.0, 3.0];
246 let sim = utils::cosine_similarity(&a, &b);
247 assert!((sim - 1.0).abs() < 0.0001);
248 }
249
250 #[test]
251 fn test_cosine_similarity_orthogonal() {
252 let a = vec![1.0, 0.0];
253 let b = vec![0.0, 1.0];
254 let sim = utils::cosine_similarity(&a, &b);
255 assert!(sim.abs() < 0.0001);
256 }
257
258 #[test]
259 fn test_cosine_similarity_opposite() {
260 let a = vec![1.0, 0.0];
261 let b = vec![-1.0, 0.0];
262 let sim = utils::cosine_similarity(&a, &b);
263 assert!((sim + 1.0).abs() < 0.0001);
264 }
265
266 #[test]
267 fn test_normalize() {
268 let mut v = vec![3.0, 4.0];
269 utils::normalize(&mut v);
270 let magnitude: f32 = v.iter().map(|x| x * x).sum::<f32>().sqrt();
271 assert!((magnitude - 1.0).abs() < 0.0001);
272 }
273
274 #[test]
275 fn test_euclidean_distance() {
276 let a = vec![0.0, 0.0, 0.0];
277 let b = vec![1.0, 0.0, 0.0];
278 let dist = utils::euclidean_distance(&a, &b);
279 assert!((dist - 1.0).abs() < 0.0001);
280 }
281
282 #[test]
283 fn test_model_default() {
284 let model = EmbeddingModel::default();
285 assert_eq!(model.dimensions(), 384);
286 }
287}