Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'

This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,309 @@
//! DagSonaEngine: Main orchestration for SONA learning
use super::{
DagReasoningBank, DagTrajectory, DagTrajectoryBuffer, EwcConfig, EwcPlusPlus, MicroLoRA,
MicroLoRAConfig, ReasoningBankConfig,
};
use crate::dag::{OperatorType, QueryDag};
use ndarray::Array1;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
pub struct DagSonaEngine {
micro_lora: MicroLoRA,
trajectory_buffer: DagTrajectoryBuffer,
reasoning_bank: DagReasoningBank,
#[allow(dead_code)]
ewc: EwcPlusPlus,
embedding_dim: usize,
}
impl DagSonaEngine {
pub fn new(embedding_dim: usize) -> Self {
Self {
micro_lora: MicroLoRA::new(MicroLoRAConfig::default(), embedding_dim),
trajectory_buffer: DagTrajectoryBuffer::new(1000),
reasoning_bank: DagReasoningBank::new(ReasoningBankConfig {
pattern_dim: embedding_dim,
..Default::default()
}),
ewc: EwcPlusPlus::new(EwcConfig::default()),
embedding_dim,
}
}
/// Pre-query instant adaptation (<100μs)
pub fn pre_query(&mut self, dag: &QueryDag) -> Vec<f32> {
let embedding = self.compute_dag_embedding(dag);
// Query similar patterns
let similar = self.reasoning_bank.query_similar(&embedding, 3);
// If we have similar patterns, adapt MicroLoRA
if !similar.is_empty() {
let adaptation_signal = self.compute_adaptation_signal(&similar, &embedding);
self.micro_lora
.adapt(&Array1::from_vec(adaptation_signal), 0.01);
}
// Return enhanced embedding
self.micro_lora
.forward(&Array1::from_vec(embedding))
.to_vec()
}
/// Post-query trajectory recording
pub fn post_query(
&mut self,
dag: &QueryDag,
execution_time_ms: f64,
baseline_time_ms: f64,
attention_mechanism: &str,
) {
let embedding = self.compute_dag_embedding(dag);
let trajectory = DagTrajectory::new(
self.hash_dag(dag),
embedding,
attention_mechanism.to_string(),
execution_time_ms,
baseline_time_ms,
);
self.trajectory_buffer.push(trajectory);
}
/// Background learning cycle (called periodically)
pub fn background_learn(&mut self) {
let trajectories = self.trajectory_buffer.drain();
if trajectories.is_empty() {
return;
}
// Store high-quality patterns
for t in &trajectories {
if t.quality() > 0.6 {
self.reasoning_bank
.store_pattern(t.dag_embedding.clone(), t.quality());
}
}
// Recompute clusters periodically (every 100 patterns)
if self.reasoning_bank.pattern_count() % 100 == 0 {
self.reasoning_bank.recompute_clusters();
}
}
fn compute_dag_embedding(&self, dag: &QueryDag) -> Vec<f32> {
// Compute embedding from DAG structure
let mut embedding = vec![0.0; self.embedding_dim];
if dag.node_count() == 0 {
return embedding;
}
// Encode operator type distribution (20 different types)
let mut type_counts = vec![0usize; 20];
for node in dag.nodes() {
let type_idx = match &node.op_type {
OperatorType::SeqScan { .. } => 0,
OperatorType::IndexScan { .. } => 1,
OperatorType::HnswScan { .. } => 2,
OperatorType::IvfFlatScan { .. } => 3,
OperatorType::NestedLoopJoin => 4,
OperatorType::HashJoin { .. } => 5,
OperatorType::MergeJoin { .. } => 6,
OperatorType::Aggregate { .. } => 7,
OperatorType::GroupBy { .. } => 8,
OperatorType::Filter { .. } => 9,
OperatorType::Project { .. } => 10,
OperatorType::Sort { .. } => 11,
OperatorType::Limit { .. } => 12,
OperatorType::VectorDistance { .. } => 13,
OperatorType::Rerank { .. } => 14,
OperatorType::Materialize => 15,
OperatorType::Result => 16,
#[allow(deprecated)]
OperatorType::Scan => 0, // Treat as SeqScan
#[allow(deprecated)]
OperatorType::Join => 4, // Treat as NestedLoopJoin
};
if type_idx < type_counts.len() {
type_counts[type_idx] += 1;
}
}
// Normalize and place in embedding
let total = dag.node_count() as f32;
for (i, count) in type_counts.iter().enumerate() {
if i < self.embedding_dim / 2 {
embedding[i] = *count as f32 / total;
}
}
// Encode structural features (depth, breadth, connectivity)
let depth = self.compute_dag_depth(dag);
let avg_fanout = dag.node_count() as f32 / (dag.leaves().len().max(1) as f32);
if self.embedding_dim > 20 {
embedding[20] = (depth as f32) / 10.0; // Normalize depth
embedding[21] = avg_fanout / 5.0; // Normalize fanout
}
// Encode cost statistics
let costs: Vec<f64> = dag.nodes().map(|n| n.estimated_cost).collect();
if !costs.is_empty() && self.embedding_dim > 22 {
let avg_cost = costs.iter().sum::<f64>() / costs.len() as f64;
let max_cost = costs.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
embedding[22] = (avg_cost / 1000.0) as f32; // Normalize
embedding[23] = (max_cost / 1000.0) as f32;
}
// Normalize entire embedding
let norm: f32 = embedding.iter().map(|x| x * x).sum::<f32>().sqrt();
if norm > 0.0 {
embedding.iter_mut().for_each(|x| *x /= norm);
}
embedding
}
fn compute_dag_depth(&self, dag: &QueryDag) -> usize {
// BFS to find maximum depth
use std::collections::VecDeque;
let mut max_depth = 0;
let mut queue = VecDeque::new();
if let Some(root) = dag.root() {
queue.push_back((root, 0));
}
while let Some((node_id, depth)) = queue.pop_front() {
max_depth = max_depth.max(depth);
for &child in dag.children(node_id) {
queue.push_back((child, depth + 1));
}
}
max_depth
}
fn compute_adaptation_signal(
&self,
_similar: &[(u64, f32)],
_current_embedding: &[f32],
) -> Vec<f32> {
// Weighted average of similar pattern embeddings
// For now, just return zeros as we'd need to store pattern vectors
vec![0.0; self.embedding_dim]
}
fn hash_dag(&self, dag: &QueryDag) -> u64 {
let mut hasher = DefaultHasher::new();
// Hash node types and edges
for node in dag.nodes() {
node.id.hash(&mut hasher);
// Hash operator type discriminant
match &node.op_type {
OperatorType::SeqScan { table } => {
0u8.hash(&mut hasher);
table.hash(&mut hasher);
}
OperatorType::IndexScan { index, table } => {
1u8.hash(&mut hasher);
index.hash(&mut hasher);
table.hash(&mut hasher);
}
OperatorType::HnswScan { index, ef_search } => {
2u8.hash(&mut hasher);
index.hash(&mut hasher);
ef_search.hash(&mut hasher);
}
OperatorType::IvfFlatScan { index, nprobe } => {
3u8.hash(&mut hasher);
index.hash(&mut hasher);
nprobe.hash(&mut hasher);
}
OperatorType::NestedLoopJoin => 4u8.hash(&mut hasher),
OperatorType::HashJoin { hash_key } => {
5u8.hash(&mut hasher);
hash_key.hash(&mut hasher);
}
OperatorType::MergeJoin { merge_key } => {
6u8.hash(&mut hasher);
merge_key.hash(&mut hasher);
}
OperatorType::Aggregate { functions } => {
7u8.hash(&mut hasher);
for func in functions {
func.hash(&mut hasher);
}
}
OperatorType::GroupBy { keys } => {
8u8.hash(&mut hasher);
for key in keys {
key.hash(&mut hasher);
}
}
OperatorType::Filter { predicate } => {
9u8.hash(&mut hasher);
predicate.hash(&mut hasher);
}
OperatorType::Project { columns } => {
10u8.hash(&mut hasher);
for col in columns {
col.hash(&mut hasher);
}
}
OperatorType::Sort { keys, descending } => {
11u8.hash(&mut hasher);
for key in keys {
key.hash(&mut hasher);
}
for &desc in descending {
desc.hash(&mut hasher);
}
}
OperatorType::Limit { count } => {
12u8.hash(&mut hasher);
count.hash(&mut hasher);
}
OperatorType::VectorDistance { metric } => {
13u8.hash(&mut hasher);
metric.hash(&mut hasher);
}
OperatorType::Rerank { model } => {
14u8.hash(&mut hasher);
model.hash(&mut hasher);
}
OperatorType::Materialize => 15u8.hash(&mut hasher),
OperatorType::Result => 16u8.hash(&mut hasher),
#[allow(deprecated)]
OperatorType::Scan => 0u8.hash(&mut hasher),
#[allow(deprecated)]
OperatorType::Join => 4u8.hash(&mut hasher),
}
}
hasher.finish()
}
pub fn pattern_count(&self) -> usize {
self.reasoning_bank.pattern_count()
}
pub fn trajectory_count(&self) -> usize {
self.trajectory_buffer.total_count()
}
pub fn cluster_count(&self) -> usize {
self.reasoning_bank.cluster_count()
}
}
impl Default for DagSonaEngine {
fn default() -> Self {
Self::new(256)
}
}

View File

@@ -0,0 +1,100 @@
//! EWC++: Elastic Weight Consolidation to prevent forgetting
use ndarray::Array1;
#[derive(Debug, Clone)]
pub struct EwcConfig {
pub lambda: f32, // Importance weight (2000-15000)
pub decay: f32, // Fisher decay rate
pub online: bool, // Use online EWC
}
impl Default for EwcConfig {
fn default() -> Self {
Self {
lambda: 5000.0,
decay: 0.99,
online: true,
}
}
}
pub struct EwcPlusPlus {
config: EwcConfig,
fisher_diag: Option<Array1<f32>>,
optimal_params: Option<Array1<f32>>,
task_count: usize,
}
impl EwcPlusPlus {
pub fn new(config: EwcConfig) -> Self {
Self {
config,
fisher_diag: None,
optimal_params: None,
task_count: 0,
}
}
/// Consolidate current parameters after training
pub fn consolidate(&mut self, params: &Array1<f32>, fisher: &Array1<f32>) {
if self.config.online && self.fisher_diag.is_some() {
// Online EWC: accumulate Fisher information
let current_fisher = self.fisher_diag.as_ref().unwrap();
self.fisher_diag =
Some(current_fisher * self.config.decay + fisher * (1.0 - self.config.decay));
} else {
self.fisher_diag = Some(fisher.clone());
}
self.optimal_params = Some(params.clone());
self.task_count += 1;
}
/// Compute EWC penalty for given parameters
pub fn penalty(&self, params: &Array1<f32>) -> f32 {
match (&self.fisher_diag, &self.optimal_params) {
(Some(fisher), Some(optimal)) => {
let diff = params - optimal;
let weighted = &diff * &diff * fisher;
0.5 * self.config.lambda * weighted.sum()
}
_ => 0.0,
}
}
/// Compute gradient of EWC penalty
pub fn penalty_gradient(&self, params: &Array1<f32>) -> Option<Array1<f32>> {
match (&self.fisher_diag, &self.optimal_params) {
(Some(fisher), Some(optimal)) => {
let diff = params - optimal;
Some(self.config.lambda * fisher * &diff)
}
_ => None,
}
}
/// Compute Fisher information from gradients
pub fn compute_fisher(gradients: &[Array1<f32>]) -> Array1<f32> {
if gradients.is_empty() {
return Array1::zeros(0);
}
let dim = gradients[0].len();
let mut fisher = Array1::zeros(dim);
for grad in gradients {
fisher = fisher + grad.mapv(|x| x * x);
}
fisher / gradients.len() as f32
}
pub fn has_prior(&self) -> bool {
self.fisher_diag.is_some()
}
pub fn task_count(&self) -> usize {
self.task_count
}
}

View File

@@ -0,0 +1,80 @@
//! MicroLoRA: Ultra-fast per-query adaptation
use ndarray::{Array1, Array2};
#[derive(Debug, Clone)]
pub struct MicroLoRAConfig {
pub rank: usize, // 1-2 for micro
pub alpha: f32, // Scaling factor
pub dropout: f32, // Dropout rate
}
impl Default for MicroLoRAConfig {
fn default() -> Self {
Self {
rank: 2,
alpha: 1.0,
dropout: 0.0,
}
}
}
pub struct MicroLoRA {
config: MicroLoRAConfig,
a_matrix: Array2<f32>, // (in_dim, rank)
b_matrix: Array2<f32>, // (rank, out_dim)
#[allow(dead_code)]
in_dim: usize,
#[allow(dead_code)]
out_dim: usize,
}
impl MicroLoRA {
pub fn new(config: MicroLoRAConfig, dim: usize) -> Self {
let rank = config.rank;
// Initialize A with small random values, B with zeros
let a_matrix = Array2::from_shape_fn((dim, rank), |_| (rand::random::<f32>() - 0.5) * 0.01);
let b_matrix = Array2::zeros((rank, dim));
Self {
config,
a_matrix,
b_matrix,
in_dim: dim,
out_dim: dim,
}
}
/// Forward pass: x + alpha * (x @ A @ B)
pub fn forward(&self, x: &Array1<f32>) -> Array1<f32> {
let low_rank = x.dot(&self.a_matrix).dot(&self.b_matrix);
x + &(low_rank * self.config.alpha)
}
/// Adapt weights based on gradient signal
pub fn adapt(&mut self, gradient: &Array1<f32>, learning_rate: f32) {
// Update B matrix based on gradient (rank-1 update)
// This is the "instant" adaptation - must be <100μs
let grad_norm = gradient.mapv(|x| x * x).sum().sqrt();
if grad_norm > 1e-8 {
let normalized = gradient / grad_norm;
// Outer product update to B
for i in 0..self.config.rank {
for j in 0..self.out_dim {
self.b_matrix[[i, j]] +=
learning_rate * self.a_matrix.column(i).sum() * normalized[j];
}
}
}
}
/// Reset to initial state
pub fn reset(&mut self) {
self.b_matrix.fill(0.0);
}
/// Get parameter count
pub fn param_count(&self) -> usize {
self.a_matrix.len() + self.b_matrix.len()
}
}

View File

@@ -0,0 +1,13 @@
//! SONA: Self-Optimizing Neural Architecture for DAG Learning
mod engine;
mod ewc;
mod micro_lora;
mod reasoning_bank;
mod trajectory;
pub use engine::DagSonaEngine;
pub use ewc::{EwcConfig, EwcPlusPlus};
pub use micro_lora::{MicroLoRA, MicroLoRAConfig};
pub use reasoning_bank::{DagPattern, DagReasoningBank, ReasoningBankConfig};
pub use trajectory::{DagTrajectory, DagTrajectoryBuffer};

View File

@@ -0,0 +1,257 @@
//! Reasoning Bank: K-means++ clustering for pattern storage
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct DagPattern {
pub id: u64,
pub vector: Vec<f32>,
pub quality_score: f32,
pub usage_count: usize,
pub metadata: HashMap<String, String>,
}
#[derive(Debug, Clone)]
pub struct ReasoningBankConfig {
pub num_clusters: usize,
pub pattern_dim: usize,
pub max_patterns: usize,
pub similarity_threshold: f32,
}
impl Default for ReasoningBankConfig {
fn default() -> Self {
Self {
num_clusters: 100,
pattern_dim: 256,
max_patterns: 10000,
similarity_threshold: 0.7,
}
}
}
pub struct DagReasoningBank {
config: ReasoningBankConfig,
patterns: Vec<DagPattern>,
centroids: Vec<Vec<f32>>,
cluster_assignments: Vec<usize>,
next_id: u64,
}
impl DagReasoningBank {
pub fn new(config: ReasoningBankConfig) -> Self {
Self {
config,
patterns: Vec::new(),
centroids: Vec::new(),
cluster_assignments: Vec::new(),
next_id: 0,
}
}
/// Store a new pattern
pub fn store_pattern(&mut self, vector: Vec<f32>, quality: f32) -> u64 {
let id = self.next_id;
self.next_id += 1;
let pattern = DagPattern {
id,
vector,
quality_score: quality,
usage_count: 0,
metadata: HashMap::new(),
};
self.patterns.push(pattern);
// Evict if over capacity
if self.patterns.len() > self.config.max_patterns {
self.evict_lowest_quality();
}
id
}
/// Query similar patterns using cosine similarity
pub fn query_similar(&self, query: &[f32], k: usize) -> Vec<(u64, f32)> {
let mut similarities: Vec<(u64, f32)> = self
.patterns
.iter()
.map(|p| (p.id, cosine_similarity(&p.vector, query)))
.filter(|(_, sim)| *sim >= self.config.similarity_threshold)
.collect();
similarities.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
similarities.truncate(k);
similarities
}
/// Run K-means++ clustering
pub fn recompute_clusters(&mut self) {
if self.patterns.is_empty() {
return;
}
let k = self.config.num_clusters.min(self.patterns.len());
// K-means++ initialization
self.centroids = kmeans_pp_init(&self.patterns, k);
// K-means iterations
for _ in 0..10 {
// Assign points to clusters
self.cluster_assignments = self
.patterns
.iter()
.map(|p| self.nearest_centroid(&p.vector))
.collect();
// Update centroids
self.update_centroids();
}
}
fn nearest_centroid(&self, point: &[f32]) -> usize {
self.centroids
.iter()
.enumerate()
.map(|(i, c)| (i, euclidean_distance(point, c)))
.min_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
.map(|(i, _)| i)
.unwrap_or(0)
}
fn update_centroids(&mut self) {
let k = self.centroids.len();
let dim = if !self.centroids.is_empty() {
self.centroids[0].len()
} else {
return;
};
// Initialize new centroids
let mut new_centroids = vec![vec![0.0; dim]; k];
let mut counts = vec![0usize; k];
// Sum points in each cluster
for (pattern, &cluster) in self.patterns.iter().zip(self.cluster_assignments.iter()) {
if cluster < k {
for (i, &val) in pattern.vector.iter().enumerate() {
new_centroids[cluster][i] += val;
}
counts[cluster] += 1;
}
}
// Average to get centroids
for (centroid, count) in new_centroids.iter_mut().zip(counts.iter()) {
if *count > 0 {
for val in centroid.iter_mut() {
*val /= *count as f32;
}
}
}
self.centroids = new_centroids;
}
fn evict_lowest_quality(&mut self) {
// Remove pattern with lowest quality * usage score
if let Some(min_idx) = self
.patterns
.iter()
.enumerate()
.min_by(|(_, a), (_, b)| {
let score_a = a.quality_score * (a.usage_count as f32 + 1.0).ln();
let score_b = b.quality_score * (b.usage_count as f32 + 1.0).ln();
score_a.partial_cmp(&score_b).unwrap()
})
.map(|(i, _)| i)
{
self.patterns.remove(min_idx);
}
}
pub fn pattern_count(&self) -> usize {
self.patterns.len()
}
pub fn cluster_count(&self) -> usize {
self.centroids.len()
}
}
fn cosine_similarity(a: &[f32], b: &[f32]) -> f32 {
let dot: f32 = a.iter().zip(b.iter()).map(|(x, y)| x * y).sum();
let norm_a: f32 = a.iter().map(|x| x * x).sum::<f32>().sqrt();
let norm_b: f32 = b.iter().map(|x| x * x).sum::<f32>().sqrt();
if norm_a > 0.0 && norm_b > 0.0 {
dot / (norm_a * norm_b)
} else {
0.0
}
}
fn euclidean_distance(a: &[f32], b: &[f32]) -> f32 {
a.iter()
.zip(b.iter())
.map(|(x, y)| (x - y).powi(2))
.sum::<f32>()
.sqrt()
}
fn kmeans_pp_init(patterns: &[DagPattern], k: usize) -> Vec<Vec<f32>> {
use rand::Rng;
if patterns.is_empty() || k == 0 {
return Vec::new();
}
let mut rng = rand::thread_rng();
let mut centroids = Vec::with_capacity(k);
let _dim = patterns[0].vector.len();
// Choose first centroid randomly
let first_idx = rng.gen_range(0..patterns.len());
centroids.push(patterns[first_idx].vector.clone());
// Choose remaining centroids using D^2 weighting
for _ in 1..k {
let mut distances = Vec::with_capacity(patterns.len());
let mut total_distance = 0.0f32;
// Compute minimum distance to existing centroids for each point
for pattern in patterns {
let min_dist = centroids
.iter()
.map(|c| euclidean_distance(&pattern.vector, c))
.min_by(|a, b| a.partial_cmp(b).unwrap())
.unwrap_or(0.0);
let squared = min_dist * min_dist;
distances.push(squared);
total_distance += squared;
}
// Select next centroid with probability proportional to D^2
if total_distance > 0.0 {
let mut threshold = rng.gen::<f32>() * total_distance;
for (idx, &dist) in distances.iter().enumerate() {
threshold -= dist;
if threshold <= 0.0 {
centroids.push(patterns[idx].vector.clone());
break;
}
}
} else {
// Fallback: choose random point
let idx = rng.gen_range(0..patterns.len());
centroids.push(patterns[idx].vector.clone());
}
if centroids.len() >= k {
break;
}
}
centroids
}

View File

@@ -0,0 +1,97 @@
//! Trajectory Buffer: Lock-free buffer for learning trajectories
use crossbeam::queue::ArrayQueue;
use std::sync::atomic::{AtomicUsize, Ordering};
/// A single learning trajectory
#[derive(Debug, Clone)]
pub struct DagTrajectory {
pub query_hash: u64,
pub dag_embedding: Vec<f32>,
pub attention_mechanism: String,
pub execution_time_ms: f64,
pub improvement_ratio: f32,
pub timestamp: std::time::Instant,
}
impl DagTrajectory {
pub fn new(
query_hash: u64,
dag_embedding: Vec<f32>,
attention_mechanism: String,
execution_time_ms: f64,
baseline_time_ms: f64,
) -> Self {
let improvement_ratio = if baseline_time_ms > 0.0 {
(baseline_time_ms - execution_time_ms) as f32 / baseline_time_ms as f32
} else {
0.0
};
Self {
query_hash,
dag_embedding,
attention_mechanism,
execution_time_ms,
improvement_ratio,
timestamp: std::time::Instant::now(),
}
}
/// Compute quality score (0-1)
pub fn quality(&self) -> f32 {
// Quality based on improvement and execution time
let time_score = 1.0 / (1.0 + self.execution_time_ms as f32 / 1000.0);
let improvement_score = (self.improvement_ratio + 1.0) / 2.0;
0.5 * time_score + 0.5 * improvement_score
}
}
/// Lock-free trajectory buffer
pub struct DagTrajectoryBuffer {
queue: ArrayQueue<DagTrajectory>,
count: AtomicUsize,
#[allow(dead_code)]
capacity: usize,
}
impl DagTrajectoryBuffer {
pub fn new(capacity: usize) -> Self {
Self {
queue: ArrayQueue::new(capacity),
count: AtomicUsize::new(0),
capacity,
}
}
/// Push trajectory, dropping oldest if full
pub fn push(&self, trajectory: DagTrajectory) {
if self.queue.push(trajectory.clone()).is_err() {
// Queue full, pop oldest and retry
let _ = self.queue.pop();
let _ = self.queue.push(trajectory);
}
self.count.fetch_add(1, Ordering::Relaxed);
}
/// Drain all trajectories for processing
pub fn drain(&self) -> Vec<DagTrajectory> {
let mut result = Vec::with_capacity(self.queue.len());
while let Some(t) = self.queue.pop() {
result.push(t);
}
result
}
pub fn len(&self) -> usize {
self.queue.len()
}
pub fn is_empty(&self) -> bool {
self.queue.is_empty()
}
pub fn total_count(&self) -> usize {
self.count.load(Ordering::Relaxed)
}
}