1use half::{bf16, f16};
32use serde::{Deserialize, Serialize};
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
36#[non_exhaustive]
37pub enum VectorPrecision {
38 #[default]
40 F32,
41 F16,
43 BF16,
45}
46
47impl VectorPrecision {
48 #[must_use]
50 pub const fn bytes_per_element(&self) -> usize {
51 match self {
52 Self::F32 => 4,
53 Self::F16 | Self::BF16 => 2,
54 }
55 }
56
57 #[must_use]
59 pub const fn memory_size(&self, dimension: usize) -> usize {
60 self.bytes_per_element() * dimension
61 }
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
69#[non_exhaustive]
70pub enum VectorData {
71 F32(Vec<f32>),
73 F16(Vec<f16>),
75 BF16(Vec<bf16>),
77}
78
79impl VectorData {
80 #[must_use]
96 pub fn from_f32_slice(data: &[f32], precision: VectorPrecision) -> Self {
97 match precision {
98 VectorPrecision::F32 => Self::F32(data.to_vec()),
99 VectorPrecision::F16 => Self::F16(data.iter().map(|&x| f16::from_f32(x)).collect()),
100 VectorPrecision::BF16 => Self::BF16(data.iter().map(|&x| bf16::from_f32(x)).collect()),
101 }
102 }
103
104 #[must_use]
109 pub fn from_f32_vec(data: Vec<f32>, precision: VectorPrecision) -> Self {
110 if precision == VectorPrecision::F32 {
111 Self::F32(data)
112 } else {
113 Self::from_f32_slice(&data, precision)
115 }
116 }
117
118 #[must_use]
120 pub const fn precision(&self) -> VectorPrecision {
121 match self {
122 Self::F32(_) => VectorPrecision::F32,
123 Self::F16(_) => VectorPrecision::F16,
124 Self::BF16(_) => VectorPrecision::BF16,
125 }
126 }
127
128 #[must_use]
130 pub fn len(&self) -> usize {
131 match self {
132 Self::F32(v) => v.len(),
133 Self::F16(v) => v.len(),
134 Self::BF16(v) => v.len(),
135 }
136 }
137
138 #[must_use]
140 pub fn is_empty(&self) -> bool {
141 self.len() == 0
142 }
143
144 #[must_use]
146 pub fn memory_size(&self) -> usize {
147 self.precision().memory_size(self.len())
148 }
149
150 #[must_use]
155 pub fn to_f32_vec(&self) -> Vec<f32> {
156 match self {
157 Self::F32(v) => v.clone(),
158 Self::F16(v) => v.iter().map(|x| x.to_f32()).collect(),
159 Self::BF16(v) => v.iter().map(|x| x.to_f32()).collect(),
160 }
161 }
162
163 #[must_use]
167 pub fn as_f32_slice(&self) -> Option<&[f32]> {
168 match self {
169 Self::F32(v) => Some(v.as_slice()),
170 Self::F16(_) | Self::BF16(_) => None,
171 }
172 }
173
174 #[must_use]
176 pub fn convert(&self, target: VectorPrecision) -> Self {
177 if self.precision() == target {
178 return self.clone();
179 }
180 Self::from_f32_slice(&self.to_f32_vec(), target)
181 }
182}
183
184impl From<Vec<f32>> for VectorData {
185 fn from(data: Vec<f32>) -> Self {
186 Self::F32(data)
187 }
188}
189
190impl From<&[f32]> for VectorData {
191 fn from(data: &[f32]) -> Self {
192 Self::F32(data.to_vec())
193 }
194}
195
196fn with_f32_simd(a: &VectorData, b: &VectorData, simd_fn: fn(&[f32], &[f32]) -> f32) -> f32 {
209 match (a, b) {
210 (VectorData::F32(va), VectorData::F32(vb)) => simd_fn(va, vb),
211 _ => simd_fn(&a.to_f32_vec(), &b.to_f32_vec()),
212 }
213}
214
215#[must_use]
220pub fn dot_product(a: &VectorData, b: &VectorData) -> f32 {
221 with_f32_simd(a, b, crate::simd_native::dot_product_native)
222}
223
224#[must_use]
226pub fn cosine_similarity(a: &VectorData, b: &VectorData) -> f32 {
227 if let (VectorData::F32(va), VectorData::F32(vb)) = (a, b) {
228 crate::simd_native::cosine_similarity_native(va, vb)
229 } else {
230 let dot = dot_product(a, b);
231 let norm_a = norm_squared(a).sqrt();
232 let norm_b = norm_squared(b).sqrt();
233
234 if norm_a < f32::EPSILON || norm_b < f32::EPSILON {
235 0.0
236 } else {
237 (dot / (norm_a * norm_b)).clamp(-1.0, 1.0)
238 }
239 }
240}
241
242#[must_use]
244pub fn euclidean_distance(a: &VectorData, b: &VectorData) -> f32 {
245 with_f32_simd(a, b, crate::simd_native::euclidean_native)
246}
247
248fn norm_squared(v: &VectorData) -> f32 {
251 if let VectorData::F32(data) = v {
252 let n = crate::simd_native::norm_native(data);
253 n * n
254 } else {
255 let f32_vec = v.to_f32_vec();
256 let n = crate::simd_native::norm_native(&f32_vec);
257 n * n
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 #[test]
266 fn test_vector_data_f16_roundtrip() {
267 let data = vec![1.0, 2.0, 3.0];
268 let v = VectorData::from_f32_slice(&data, VectorPrecision::F16);
269 let result = v.to_f32_vec();
270 for (a, b) in data.iter().zip(result.iter()) {
271 assert!((a - b).abs() < 0.01);
272 }
273 }
274
275 #[test]
276 fn test_cosine_similarity_identical() {
277 let v1 = VectorData::from_f32_slice(&[1.0, 0.0, 0.0], VectorPrecision::F32);
278 let v2 = VectorData::from_f32_slice(&[1.0, 0.0, 0.0], VectorPrecision::F32);
279 let sim = cosine_similarity(&v1, &v2);
280 assert!((sim - 1.0).abs() < 1e-5);
281 }
282
283 #[test]
284 fn test_cosine_similarity_orthogonal() {
285 let v1 = VectorData::from_f32_slice(&[1.0, 0.0, 0.0], VectorPrecision::F32);
286 let v2 = VectorData::from_f32_slice(&[0.0, 1.0, 0.0], VectorPrecision::F32);
287 let sim = cosine_similarity(&v1, &v2);
288 assert!(sim.abs() < 1e-5);
289 }
290
291 #[test]
292 fn test_euclidean_distance_identical() {
293 let v1 = VectorData::from_f32_slice(&[1.0, 2.0, 3.0], VectorPrecision::F32);
294 let v2 = VectorData::from_f32_slice(&[1.0, 2.0, 3.0], VectorPrecision::F32);
295 let dist = euclidean_distance(&v1, &v2);
296 assert!(dist.abs() < 1e-5);
297 }
298
299 #[test]
300 fn test_euclidean_distance_345() {
301 let v1 = VectorData::from_f32_slice(&[0.0, 0.0], VectorPrecision::F32);
302 let v2 = VectorData::from_f32_slice(&[3.0, 4.0], VectorPrecision::F32);
303 let dist = euclidean_distance(&v1, &v2);
304 assert!((dist - 5.0).abs() < 1e-5);
305 }
306
307 #[test]
308 fn test_norm_squared_f32() {
309 let v = VectorData::from_f32_slice(&[3.0, 4.0], VectorPrecision::F32);
310 let norm = norm_squared(&v);
311 assert!((norm - 25.0).abs() < 1e-5);
312 }
313
314 #[test]
315 fn test_cosine_similarity_f16_vs_f32() {
316 let v1 = VectorData::from_f32_slice(&[1.0, 2.0, 3.0], VectorPrecision::F16);
317 let v2 = VectorData::from_f32_slice(&[1.0, 2.0, 3.0], VectorPrecision::F32);
318 let sim = cosine_similarity(&v1, &v2);
319 assert!((sim - 1.0).abs() < 0.01);
320 }
321
322 #[test]
323 fn test_cosine_similarity_is_clamped_to_unit_interval() {
324 let v1 = VectorData::from_f32_slice(&[1.0, 1.0, 1.0, 1.0], VectorPrecision::F16);
326 let v2 = VectorData::from_f32_slice(&[1.0, 1.0, 1.0, 1.0], VectorPrecision::BF16);
327 let sim = cosine_similarity(&v1, &v2);
328 assert!(
329 (-1.0..=1.0).contains(&sim),
330 "cosine similarity must be clamped to [-1, 1], got {sim}"
331 );
332 }
333}