Skip to content
Merged
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
2 changes: 1 addition & 1 deletion src/bin/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ use arrow_ipc::writer::StreamWriter;
use arrow_schema::{DataType, Field, Schema, SchemaRef};

use ladybug::core::Fingerprint;
use ladybug::core::simd::{self, hamming_distance};
use ladybug::core::rustynum_accel::{self as simd, hamming_distance};
use ladybug::nars::TruthValue;
use ladybug::storage::service::{CognitiveService, CpuFeatures, ServiceConfig};
use ladybug::storage::{Addr, BindSpace, CogRedis, FINGERPRINT_WORDS, RedisResult};
Expand Down
2 changes: 1 addition & 1 deletion src/core/fingerprint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ impl Fingerprint {
/// Hamming distance to another fingerprint
#[inline]
pub fn hamming(&self, other: &Fingerprint) -> u32 {
super::simd::hamming_distance(self, other)
super::rustynum_accel::hamming_distance(self, other)
}

/// Similarity (0.0 - 1.0)
Expand Down
3 changes: 1 addition & 2 deletions src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
mod buffer;
mod fingerprint;
mod scent;
pub mod simd;
pub mod vsa;

pub mod rustynum_accel;

pub use buffer::BufferPool;
pub use fingerprint::Fingerprint;
pub use scent::*;
pub use simd::{HammingEngine, batch_hamming, hamming_distance};
pub use rustynum_accel::{HammingEngine, batch_hamming, hamming_distance, simd_level};
pub use vsa::VsaOps;

/// Dense embedding vector
Expand Down
110 changes: 110 additions & 0 deletions src/core/rustynum_accel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,116 @@ pub fn slice_dot_i8(a: &[u64], b: &[u64]) -> i64 {
rustynum_core::simd::dot_i8(view_u64_as_bytes(a), view_u64_as_bytes(b))
}

// ────────────────────────────────────────────────────────────────
// Fingerprint-level convenience functions (formerly core::simd)
// ────────────────────────────────────────────────────────────────

/// Compute Hamming distance between two fingerprints.
///
/// Delegates to rustynum's runtime-dispatched SIMD (AVX-512 → AVX2 → scalar).
#[inline]
pub fn hamming_distance(a: &Fingerprint, b: &Fingerprint) -> u32 {
fingerprint_hamming(a, b)
}

/// Batch Hamming distance computation (parallel when `parallel` feature is on)
#[cfg(feature = "parallel")]
pub fn batch_hamming(query: &Fingerprint, corpus: &[Fingerprint]) -> Vec<u32> {
use rayon::prelude::*;
corpus
.par_iter()
.map(|fp| hamming_distance(query, fp))
.collect()
}

/// Non-parallel batch Hamming
#[cfg(not(feature = "parallel"))]
pub fn batch_hamming(query: &Fingerprint, corpus: &[Fingerprint]) -> Vec<u32> {
corpus
.iter()
.map(|fp| hamming_distance(query, fp))
.collect()
}

/// Hamming search engine with pre-allocated buffers
pub struct HammingEngine {
corpus: Vec<Fingerprint>,
#[cfg(feature = "parallel")]
thread_pool: rayon::ThreadPool,
}

impl HammingEngine {
/// Create new engine
pub fn new() -> Self {
Self {
corpus: Vec::new(),
#[cfg(feature = "parallel")]
thread_pool: rayon::ThreadPoolBuilder::new()
.num_threads(num_cpus::get())
.build()
.unwrap(),
}
}

/// Index corpus
pub fn index(&mut self, corpus: Vec<Fingerprint>) {
self.corpus = corpus;
}

/// Search for k nearest neighbors
pub fn search(&self, query: &Fingerprint, k: usize) -> Vec<(usize, u32, f32)> {
let distances = batch_hamming(query, &self.corpus);

let mut indexed: Vec<(usize, u32)> = distances.into_iter().enumerate().collect();

let k = k.min(indexed.len());
indexed.select_nth_unstable_by_key(k.saturating_sub(1), |&(_, d)| d);
indexed.truncate(k);
indexed.sort_by_key(|&(_, d)| d);

indexed
.into_iter()
.map(|(idx, dist)| {
let similarity = 1.0 - (dist as f32 / crate::FINGERPRINT_BITS as f32);
(idx, dist, similarity)
})
.collect()
}

/// Search with threshold
pub fn search_threshold(
&self,
query: &Fingerprint,
threshold: f32,
limit: usize,
) -> Vec<(usize, u32, f32)> {
let max_distance = ((1.0 - threshold) * crate::FINGERPRINT_BITS as f32) as u32;
let mut results = self.search(query, limit);
results.retain(|&(_, dist, _)| dist <= max_distance);
results
}

/// Corpus size
pub fn len(&self) -> usize {
self.corpus.len()
}

pub fn is_empty(&self) -> bool {
self.corpus.is_empty()
}
}

impl Default for HammingEngine {
fn default() -> Self {
Self::new()
}
}

/// Detect SIMD capability at runtime
pub fn simd_level() -> &'static str {
"rustynum-runtime-dispatch"
}

// ────────────────────────────────────────────────────────────────
// Tests
// ────────────────────────────────────────────────────────────────
Expand Down
173 changes: 0 additions & 173 deletions src/core/simd.rs

This file was deleted.

4 changes: 2 additions & 2 deletions src/python/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyList};

use crate::core::Fingerprint;
use crate::core::simd::{batch_hamming as rust_batch_hamming, hamming_distance};
use crate::core::rustynum_accel::{batch_hamming as rust_batch_hamming, hamming_distance};
use crate::nars::TruthValue;
use crate::storage::Database;
use crate::{FINGERPRINT_BITS, FINGERPRINT_BYTES};
Expand Down Expand Up @@ -382,7 +382,7 @@ fn open(path: &str) -> PyResult<PyDatabase> {
/// Get SIMD level
#[pyfunction]
fn simd_level() -> &'static str {
crate::core::simd::simd_level()
crate::core::rustynum_accel::simd_level()
}

// =============================================================================
Expand Down
Loading