Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ rand_distr = "0.4"
sha2 = "0.10.7" # Added SHA-2 cryptographic hash functions
sha3 = { version = "0.10", optional = true }
blake3 = { version = "1", optional = true }
rand = "0.8"
serde_bytes = "0.11"
wide = "0.7"
warp = "0.3"
Expand Down
27 changes: 27 additions & 0 deletions benches/vector_operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,33 @@ fn bench_vector_operations(c: &mut Criterion) {
group.finish();
}

// Benchmark SIMD-enabled operations
{
let mut group = c.benchmark_group("vector_simd_operations");
let simd_dims = vec![8, 32, 128];
for &dim in &simd_dims {
let v1 = Vector::random(dim);
let v2 = Vector::random(dim);

group.bench_with_input(BenchmarkId::new("dot_simd", dim), &dim, |b, _| {
b.iter(|| black_box(&v1).dot(black_box(&v2)))
});

group.bench_with_input(BenchmarkId::new("euclidean_simd", dim), &dim, |b, _| {
b.iter(|| black_box(&v1).euclidean_distance(black_box(&v2)))
});

group.bench_with_input(BenchmarkId::new("manhattan_simd", dim), &dim, |b, _| {
b.iter(|| black_box(&v1).manhattan_distance(black_box(&v2)))
});

group.bench_with_input(BenchmarkId::new("hamming_simd", dim), &dim, |b, _| {
b.iter(|| black_box(&v1).hamming_distance(black_box(&v2)))
});
}
group.finish();
}

// Benchmark batch operations
{
let mut group = c.benchmark_group("batch_operations");
Expand Down
85 changes: 16 additions & 69 deletions src/core/centroid_crdt.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
use crate::core::centroid::Centroid;
use crate::core::vector::Vector;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
<<<<<<< Updated upstream
use thiserror::Error;
=======
>>>>>>> Stashed changes
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::core::centroid::Centroid;
use crate::core::vector::Vector;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CentroidOperation {
Expand All @@ -23,15 +19,6 @@ pub enum OperationType {
Delete,
}

#[derive(Debug, Error)]
pub enum CentroidCRDTError {
#[error("Centroid with ID {0} not found")]
NotFound(Uuid),

#[error("Invalid distance value encountered during comparison")]
InvalidDistance,
}

#[derive(Debug, Clone)]
pub struct CentroidCRDT {
node_id: Uuid,
Expand Down Expand Up @@ -66,17 +53,9 @@ impl CentroidCRDT {
centroid_id
}

<<<<<<< Updated upstream
pub fn update_centroid(
&mut self,
centroid_id: Uuid,
vector: Vector,
) -> Result<(), CentroidCRDTError> {
=======
pub fn update_centroid(&mut self, centroid_id: Uuid, vector: Vector) -> Result<(), String> {
>>>>>>> Stashed changes
if !self.centroids.contains_key(&centroid_id) {
return Err(CentroidCRDTError::NotFound(centroid_id));
return Err(format!("Centroid with ID {} not found", centroid_id));
}

let operation = CentroidOperation {
Expand All @@ -91,13 +70,9 @@ impl CentroidCRDT {
Ok(())
}

<<<<<<< Updated upstream
pub fn delete_centroid(&mut self, centroid_id: Uuid) -> Result<(), CentroidCRDTError> {
=======
pub fn delete_centroid(&mut self, centroid_id: Uuid) -> Result<(), String> {
>>>>>>> Stashed changes
if !self.centroids.contains_key(&centroid_id) {
return Err(CentroidCRDTError::NotFound(centroid_id));
return Err(format!("Centroid with ID {} not found", centroid_id));
}

let operation = CentroidOperation {
Expand All @@ -120,12 +95,11 @@ impl CentroidCRDT {
match &operation.operation_type {
OperationType::Create(vector) => {
// Only create if it doesn't exist or if this is newer than the existing centroid
let should_create =
if let Some(existing) = self.centroids.get(&operation.centroid_id) {
operation.timestamp > existing.updated_at
} else {
true
};
let should_create = if let Some(existing) = self.centroids.get(&operation.centroid_id) {
operation.timestamp > existing.updated_at
} else {
true
};

if should_create {
let now = chrono::Utc::now();
Expand All @@ -138,22 +112,22 @@ impl CentroidCRDT {
};
self.centroids.insert(operation.centroid_id, centroid);
}
}
},
OperationType::Update(vector) => {
if let Some(centroid) = self.centroids.get_mut(&operation.centroid_id) {
if operation.timestamp > centroid.updated_at {
centroid.update(vector);
centroid.updated_at = operation.timestamp;
}
}
}
},
OperationType::Delete => {
if let Some(centroid) = self.centroids.get(&operation.centroid_id) {
if operation.timestamp > centroid.updated_at {
self.centroids.remove(&operation.centroid_id);
}
}
}
},
}

let op_id = operation.id;
Expand All @@ -177,32 +151,15 @@ impl CentroidCRDT {
self.centroids.values().collect()
}

<<<<<<< Updated upstream
pub fn find_nearest(
&self,
vector: &Vector,
limit: usize,
) -> Result<Vec<(&Centroid, f32)>, CentroidCRDTError> {
=======
pub fn find_nearest(&self, vector: &Vector, limit: usize) -> Vec<(&Centroid, f32)> {
>>>>>>> Stashed changes
let mut distances: Vec<(&Centroid, f32)> = self
.centroids
let mut distances: Vec<(&Centroid, f32)> = self.centroids
.values()
.map(|c| (c, c.distance_to(vector)))
.collect();
<<<<<<< Updated upstream
if distances.iter().any(|(_, d)| !d.is_finite()) {
return Err(CentroidCRDTError::InvalidDistance);
}

distances.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
=======

distances.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
>>>>>>> Stashed changes
distances.truncate(limit);
Ok(distances)
distances
}
}

Expand Down Expand Up @@ -235,12 +192,7 @@ mod tests {
let centroid_id = crdt.create_centroid(vector1);

let vector2 = Vector::new(vec![4.0, 5.0, 6.0]);
<<<<<<< Updated upstream
let res = crdt.update_centroid(centroid_id, vector2);
assert!(res.is_ok());
=======
crdt.update_centroid(centroid_id, vector2).unwrap();
>>>>>>> Stashed changes

assert_eq!(crdt.operations.len(), 2);

Expand All @@ -263,12 +215,7 @@ mod tests {
let vector = Vector::new(vec![1.0, 2.0, 3.0]);
let centroid_id = crdt.create_centroid(vector.clone());

<<<<<<< Updated upstream
let res = crdt.delete_centroid(centroid_id);
assert!(res.is_ok());
=======
crdt.delete_centroid(centroid_id).unwrap();
>>>>>>> Stashed changes

assert_eq!(crdt.centroids.len(), 0);
assert_eq!(crdt.operations.len(), 2);
Expand Down Expand Up @@ -354,4 +301,4 @@ mod tests {
let centroid = crdt1.get_centroid(&centroid_id).unwrap();
assert_eq!(centroid.vector.values, vector2.values);
}
}
}
124 changes: 112 additions & 12 deletions src/core/vector.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use serde::{Deserialize, Serialize};
use std::ops::{Add, Div, Mul, Sub};
use wide::f32x4;

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Vector {
Expand All @@ -8,6 +9,7 @@ pub struct Vector {
}

impl Vector {
const SIMD_WIDTH: usize = 4;
pub fn new(values: Vec<f32>) -> Self {
let dimensions = values.len();
Self { dimensions, values }
Expand Down Expand Up @@ -50,7 +52,11 @@ impl Vector {
"Vectors must have the same dimensions"
);

self.dot_scalar(other)
if self.dimensions % Self::SIMD_WIDTH == 0 {
self.dot_simd(other)
} else {
self.dot_scalar(other)
}
}

fn dot_scalar(&self, other: &Vector) -> f32 {
Expand All @@ -61,6 +67,26 @@ impl Vector {
.sum()
}

fn dot_simd(&self, other: &Vector) -> f32 {
let mut sum = f32x4::splat(0.0);
for i in (0..self.dimensions).step_by(Self::SIMD_WIDTH) {
let a = f32x4::new([
self.values[i],
self.values[i + 1],
self.values[i + 2],
self.values[i + 3],
]);
let b = f32x4::new([
other.values[i],
other.values[i + 1],
other.values[i + 2],
other.values[i + 3],
]);
sum = sum + a * b;
}
sum.reduce_add()
}

pub fn magnitude(&self) -> f32 {
self.dot(self).sqrt()
}
Expand Down Expand Up @@ -95,7 +121,11 @@ impl Vector {
"Vectors must have the same dimensions"
);

self.euclidean_distance_scalar(other)
if self.dimensions % Self::SIMD_WIDTH == 0 {
self.euclidean_distance_simd(other)
} else {
self.euclidean_distance_scalar(other)
}
}

fn euclidean_distance_scalar(&self, other: &Vector) -> f32 {
Expand All @@ -109,17 +139,83 @@ impl Vector {
sum_squared_diff.sqrt()
}

fn euclidean_distance_simd(&self, other: &Vector) -> f32 {
let mut sum = f32x4::splat(0.0);
for i in (0..self.dimensions).step_by(Self::SIMD_WIDTH) {
let a = f32x4::new([
self.values[i],
self.values[i + 1],
self.values[i + 2],
self.values[i + 3],
]);
let b = f32x4::new([
other.values[i],
other.values[i + 1],
other.values[i + 2],
other.values[i + 3],
]);
let diff = a - b;
sum = sum + diff * diff;
}
sum.reduce_add().sqrt()
}

fn manhattan_distance_simd(&self, other: &Vector) -> f32 {
let mut sum = f32x4::splat(0.0);
for i in (0..self.dimensions).step_by(Self::SIMD_WIDTH) {
let a = f32x4::new([
self.values[i],
self.values[i + 1],
self.values[i + 2],
self.values[i + 3],
]);
let b = f32x4::new([
other.values[i],
other.values[i + 1],
other.values[i + 2],
other.values[i + 3],
]);
sum = sum + (a - b).abs();
}
sum.reduce_add()
}

fn hamming_distance_simd(&self, other: &Vector) -> usize {
let mut count = 0usize;
for i in (0..self.dimensions).step_by(Self::SIMD_WIDTH) {
let a = f32x4::new([
self.values[i],
self.values[i + 1],
self.values[i + 2],
self.values[i + 3],
]);
let b = f32x4::new([
other.values[i],
other.values[i + 1],
other.values[i + 2],
other.values[i + 3],
]);
let mask = (a - b).abs().cmp_gt(f32x4::splat(f32::EPSILON));
count += mask.move_mask().count_ones() as usize;
}
count
}

pub fn manhattan_distance(&self, other: &Vector) -> f32 {
assert_eq!(
self.dimensions, other.dimensions,
"Vectors must have the same dimensions"
);

self.values
.iter()
.zip(other.values.iter())
.map(|(a, b)| (a - b).abs())
.sum()
if self.dimensions % Self::SIMD_WIDTH == 0 {
self.manhattan_distance_simd(other)
} else {
self.values
.iter()
.zip(other.values.iter())
.map(|(a, b)| (a - b).abs())
.sum()
}
}

pub fn hamming_distance(&self, other: &Vector) -> usize {
Expand All @@ -128,11 +224,15 @@ impl Vector {
"Vectors must have the same dimensions"
);

self.values
.iter()
.zip(other.values.iter())
.filter(|(&a, &b)| (a - b).abs() > f32::EPSILON)
.count()
if self.dimensions % Self::SIMD_WIDTH == 0 {
self.hamming_distance_simd(other)
} else {
self.values
.iter()
.zip(other.values.iter())
.filter(|(&a, &b)| (a - b).abs() > f32::EPSILON)
.count()
}
}

pub fn batch_process<F>(&self, others: &[Vector], f: F) -> Vec<f32>
Expand Down