mirror of
https://github.com/ruvnet/RuView
synced 2026-08-09 20:21:43 +00:00
Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
+234
@@ -0,0 +1,234 @@
|
||||
//! Loop B - Background Learning
|
||||
//!
|
||||
//! Hourly pattern extraction and base LoRA updates.
|
||||
|
||||
use crate::ewc::EwcPlusPlus;
|
||||
use crate::lora::BaseLoRA;
|
||||
use crate::reasoning_bank::ReasoningBank;
|
||||
use crate::time_compat::Instant;
|
||||
use crate::types::{LearnedPattern, QueryTrajectory, SonaConfig};
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Background loop configuration
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct BackgroundLoopConfig {
|
||||
/// Minimum trajectories to process
|
||||
pub min_trajectories: usize,
|
||||
/// Base LoRA learning rate
|
||||
pub base_lora_lr: f32,
|
||||
/// EWC lambda
|
||||
pub ewc_lambda: f32,
|
||||
/// Pattern extraction interval
|
||||
pub extraction_interval: Duration,
|
||||
}
|
||||
|
||||
impl Default for BackgroundLoopConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
min_trajectories: 100,
|
||||
base_lora_lr: 0.0001,
|
||||
ewc_lambda: 1000.0,
|
||||
extraction_interval: Duration::from_secs(3600),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SonaConfig> for BackgroundLoopConfig {
|
||||
fn from(config: &SonaConfig) -> Self {
|
||||
Self {
|
||||
min_trajectories: 100,
|
||||
base_lora_lr: config.base_lora_lr,
|
||||
ewc_lambda: config.ewc_lambda,
|
||||
extraction_interval: Duration::from_millis(config.background_interval_ms),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Background cycle result
|
||||
#[derive(Debug)]
|
||||
pub struct BackgroundResult {
|
||||
pub trajectories_processed: usize,
|
||||
pub patterns_extracted: usize,
|
||||
pub ewc_updated: bool,
|
||||
pub elapsed: Duration,
|
||||
pub status: String,
|
||||
}
|
||||
|
||||
impl BackgroundResult {
|
||||
fn skipped(reason: &str) -> Self {
|
||||
Self {
|
||||
trajectories_processed: 0,
|
||||
patterns_extracted: 0,
|
||||
ewc_updated: false,
|
||||
elapsed: Duration::ZERO,
|
||||
status: format!("skipped: {}", reason),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Background learning loop (Loop B)
|
||||
pub struct BackgroundLoop {
|
||||
/// Configuration
|
||||
config: BackgroundLoopConfig,
|
||||
/// ReasoningBank for pattern storage
|
||||
reasoning_bank: Arc<RwLock<ReasoningBank>>,
|
||||
/// EWC++ for forgetting prevention
|
||||
ewc: Arc<RwLock<EwcPlusPlus>>,
|
||||
/// Base LoRA
|
||||
base_lora: Arc<RwLock<BaseLoRA>>,
|
||||
/// Last extraction time
|
||||
last_extraction: RwLock<Instant>,
|
||||
}
|
||||
|
||||
impl BackgroundLoop {
|
||||
/// Create new background loop
|
||||
pub fn new(
|
||||
config: BackgroundLoopConfig,
|
||||
reasoning_bank: Arc<RwLock<ReasoningBank>>,
|
||||
ewc: Arc<RwLock<EwcPlusPlus>>,
|
||||
base_lora: Arc<RwLock<BaseLoRA>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
config,
|
||||
reasoning_bank,
|
||||
ewc,
|
||||
base_lora,
|
||||
last_extraction: RwLock::new(Instant::now()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if it's time for background cycle
|
||||
pub fn should_run(&self) -> bool {
|
||||
self.last_extraction.read().elapsed() >= self.config.extraction_interval
|
||||
}
|
||||
|
||||
/// Run background learning cycle
|
||||
pub fn run_cycle(&self, trajectories: Vec<QueryTrajectory>) -> BackgroundResult {
|
||||
if trajectories.len() < self.config.min_trajectories {
|
||||
return BackgroundResult::skipped("insufficient trajectories");
|
||||
}
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
// 1. Add trajectories to reasoning bank
|
||||
{
|
||||
let mut bank = self.reasoning_bank.write();
|
||||
for trajectory in &trajectories {
|
||||
bank.add_trajectory(trajectory);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Extract patterns
|
||||
let patterns = {
|
||||
let mut bank = self.reasoning_bank.write();
|
||||
bank.extract_patterns()
|
||||
};
|
||||
|
||||
// 3. Compute gradients from patterns
|
||||
let gradients = self.compute_pattern_gradients(&patterns);
|
||||
|
||||
// 4. Apply EWC++ constraints
|
||||
let constrained_gradients = {
|
||||
let ewc = self.ewc.read();
|
||||
ewc.apply_constraints(&gradients)
|
||||
};
|
||||
|
||||
// 5. Check for task boundary
|
||||
let task_boundary = {
|
||||
let ewc = self.ewc.read();
|
||||
ewc.detect_task_boundary(&gradients)
|
||||
};
|
||||
|
||||
if task_boundary {
|
||||
let mut ewc = self.ewc.write();
|
||||
ewc.start_new_task();
|
||||
}
|
||||
|
||||
// 6. Update EWC++ Fisher
|
||||
{
|
||||
let mut ewc = self.ewc.write();
|
||||
ewc.update_fisher(&constrained_gradients);
|
||||
}
|
||||
|
||||
// 7. Update base LoRA
|
||||
self.update_base_lora(&constrained_gradients);
|
||||
|
||||
// Update last extraction time
|
||||
*self.last_extraction.write() = Instant::now();
|
||||
|
||||
BackgroundResult {
|
||||
trajectories_processed: trajectories.len(),
|
||||
patterns_extracted: patterns.len(),
|
||||
ewc_updated: true,
|
||||
elapsed: start.elapsed(),
|
||||
status: "completed".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_pattern_gradients(&self, patterns: &[LearnedPattern]) -> Vec<f32> {
|
||||
if patterns.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let dim = patterns[0].centroid.len();
|
||||
let mut gradient = vec![0.0f32; dim];
|
||||
let mut total_weight = 0.0f32;
|
||||
|
||||
for pattern in patterns {
|
||||
let weight = pattern.avg_quality * pattern.cluster_size as f32;
|
||||
for (i, &v) in pattern.centroid.iter().enumerate() {
|
||||
if i < dim {
|
||||
gradient[i] += v * weight;
|
||||
}
|
||||
}
|
||||
total_weight += weight;
|
||||
}
|
||||
|
||||
if total_weight > 0.0 {
|
||||
for g in &mut gradient {
|
||||
*g /= total_weight;
|
||||
}
|
||||
}
|
||||
|
||||
gradient
|
||||
}
|
||||
|
||||
fn update_base_lora(&self, gradients: &[f32]) {
|
||||
let mut lora = self.base_lora.write();
|
||||
let num_layers = lora.num_layers();
|
||||
|
||||
if num_layers == 0 || gradients.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let per_layer = gradients.len() / num_layers;
|
||||
|
||||
for (layer_idx, layer) in lora.layers.iter_mut().enumerate() {
|
||||
let start = layer_idx * per_layer;
|
||||
let end = (start + per_layer).min(gradients.len());
|
||||
|
||||
for (i, &grad) in gradients[start..end].iter().enumerate() {
|
||||
if i < layer.up_proj.len() {
|
||||
layer.up_proj[i] += grad * self.config.base_lora_lr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get reasoning bank reference
|
||||
pub fn reasoning_bank(&self) -> &Arc<RwLock<ReasoningBank>> {
|
||||
&self.reasoning_bank
|
||||
}
|
||||
|
||||
/// Get EWC reference
|
||||
pub fn ewc(&self) -> &Arc<RwLock<EwcPlusPlus>> {
|
||||
&self.ewc
|
||||
}
|
||||
|
||||
/// Get base LoRA reference
|
||||
pub fn base_lora(&self) -> &Arc<RwLock<BaseLoRA>> {
|
||||
&self.base_lora
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
//! Loop Coordinator - Orchestrates all learning loops
|
||||
|
||||
use crate::ewc::{EwcConfig, EwcPlusPlus};
|
||||
use crate::loops::background::{BackgroundLoop, BackgroundLoopConfig, BackgroundResult};
|
||||
use crate::loops::instant::InstantLoop;
|
||||
use crate::lora::{BaseLoRA, MicroLoRA};
|
||||
use crate::reasoning_bank::{PatternConfig, ReasoningBank};
|
||||
use crate::types::{QueryTrajectory, SonaConfig};
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Loop coordinator managing all learning loops
|
||||
pub struct LoopCoordinator {
|
||||
/// Configuration
|
||||
_config: SonaConfig,
|
||||
/// Instant loop (Loop A)
|
||||
instant: InstantLoop,
|
||||
/// Background loop (Loop B)
|
||||
background: BackgroundLoop,
|
||||
/// Shared components
|
||||
reasoning_bank: Arc<RwLock<ReasoningBank>>,
|
||||
ewc: Arc<RwLock<EwcPlusPlus>>,
|
||||
base_lora: Arc<RwLock<BaseLoRA>>,
|
||||
/// Enabled flags
|
||||
instant_enabled: bool,
|
||||
background_enabled: bool,
|
||||
}
|
||||
|
||||
impl LoopCoordinator {
|
||||
/// Create new coordinator with default config
|
||||
pub fn new(hidden_dim: usize) -> Self {
|
||||
Self::with_config(SonaConfig {
|
||||
hidden_dim,
|
||||
embedding_dim: hidden_dim,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// Create with custom config
|
||||
pub fn with_config(config: SonaConfig) -> Self {
|
||||
let reasoning_bank = Arc::new(RwLock::new(ReasoningBank::new(PatternConfig {
|
||||
embedding_dim: config.embedding_dim,
|
||||
k_clusters: config.pattern_clusters,
|
||||
..Default::default()
|
||||
})));
|
||||
|
||||
let ewc = Arc::new(RwLock::new(EwcPlusPlus::new(EwcConfig {
|
||||
param_count: config.hidden_dim * config.base_lora_rank * 2,
|
||||
initial_lambda: config.ewc_lambda,
|
||||
..Default::default()
|
||||
})));
|
||||
|
||||
let base_lora = Arc::new(RwLock::new(BaseLoRA::new(
|
||||
config.hidden_dim,
|
||||
config.base_lora_rank,
|
||||
12, // Default number of layers
|
||||
)));
|
||||
|
||||
let instant = InstantLoop::from_sona_config(&config);
|
||||
let background = BackgroundLoop::new(
|
||||
BackgroundLoopConfig::from(&config),
|
||||
reasoning_bank.clone(),
|
||||
ewc.clone(),
|
||||
base_lora.clone(),
|
||||
);
|
||||
|
||||
Self {
|
||||
_config: config,
|
||||
instant,
|
||||
background,
|
||||
reasoning_bank,
|
||||
ewc,
|
||||
base_lora,
|
||||
instant_enabled: true,
|
||||
background_enabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Process inference trajectory (Loop A)
|
||||
pub fn on_inference(&self, trajectory: QueryTrajectory) {
|
||||
if self.instant_enabled {
|
||||
self.instant.on_trajectory(trajectory);
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate next trajectory ID
|
||||
pub fn next_trajectory_id(&self) -> u64 {
|
||||
self.instant.next_id()
|
||||
}
|
||||
|
||||
/// Run background cycle if needed (Loop B)
|
||||
pub fn maybe_run_background(&self) -> Option<BackgroundResult> {
|
||||
if !self.background_enabled {
|
||||
return None;
|
||||
}
|
||||
|
||||
if self.background.should_run() {
|
||||
let trajectories = self.instant.drain_trajectories();
|
||||
if !trajectories.is_empty() {
|
||||
return Some(self.background.run_cycle(trajectories));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Force background cycle
|
||||
pub fn force_background(&self) -> BackgroundResult {
|
||||
let trajectories = self.instant.drain_trajectories();
|
||||
self.background.run_cycle(trajectories)
|
||||
}
|
||||
|
||||
/// Flush instant loop updates
|
||||
pub fn flush_instant(&self) {
|
||||
self.instant.flush();
|
||||
}
|
||||
|
||||
/// Get micro-LoRA for inference
|
||||
pub fn micro_lora(&self) -> &Arc<RwLock<MicroLoRA>> {
|
||||
self.instant.micro_lora()
|
||||
}
|
||||
|
||||
/// Get base-LoRA for inference
|
||||
pub fn base_lora(&self) -> &Arc<RwLock<BaseLoRA>> {
|
||||
&self.base_lora
|
||||
}
|
||||
|
||||
/// Get reasoning bank
|
||||
pub fn reasoning_bank(&self) -> &Arc<RwLock<ReasoningBank>> {
|
||||
&self.reasoning_bank
|
||||
}
|
||||
|
||||
/// Get EWC++
|
||||
pub fn ewc(&self) -> &Arc<RwLock<EwcPlusPlus>> {
|
||||
&self.ewc
|
||||
}
|
||||
|
||||
/// Enable/disable instant loop
|
||||
pub fn set_instant_enabled(&mut self, enabled: bool) {
|
||||
self.instant_enabled = enabled;
|
||||
}
|
||||
|
||||
/// Enable/disable background loop
|
||||
pub fn set_background_enabled(&mut self, enabled: bool) {
|
||||
self.background_enabled = enabled;
|
||||
}
|
||||
|
||||
/// Get statistics
|
||||
pub fn stats(&self) -> CoordinatorStats {
|
||||
let (buffer_len, dropped, success_rate) = self.instant.buffer_stats();
|
||||
|
||||
CoordinatorStats {
|
||||
trajectories_buffered: buffer_len,
|
||||
trajectories_dropped: dropped,
|
||||
buffer_success_rate: success_rate,
|
||||
patterns_stored: self.reasoning_bank.read().pattern_count(),
|
||||
ewc_tasks: self.ewc.read().task_count(),
|
||||
instant_enabled: self.instant_enabled,
|
||||
background_enabled: self.background_enabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Coordinator statistics
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(
|
||||
feature = "serde-support",
|
||||
derive(serde::Serialize, serde::Deserialize)
|
||||
)]
|
||||
pub struct CoordinatorStats {
|
||||
pub trajectories_buffered: usize,
|
||||
pub trajectories_dropped: u64,
|
||||
pub buffer_success_rate: f64,
|
||||
pub patterns_stored: usize,
|
||||
pub ewc_tasks: usize,
|
||||
pub instant_enabled: bool,
|
||||
pub background_enabled: bool,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::types::TrajectoryStep;
|
||||
|
||||
fn make_trajectory(id: u64) -> QueryTrajectory {
|
||||
let mut t = QueryTrajectory::new(id, vec![0.1; 256]);
|
||||
t.add_step(TrajectoryStep::new(vec![0.5; 256], vec![], 0.8, 0));
|
||||
t.finalize(0.8, 1000);
|
||||
t
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_coordinator_creation() {
|
||||
let coord = LoopCoordinator::new(256);
|
||||
let stats = coord.stats();
|
||||
assert_eq!(stats.trajectories_buffered, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inference_processing() {
|
||||
let coord = LoopCoordinator::new(256);
|
||||
|
||||
for i in 0..10 {
|
||||
let t = make_trajectory(coord.next_trajectory_id());
|
||||
coord.on_inference(t);
|
||||
}
|
||||
|
||||
let stats = coord.stats();
|
||||
assert_eq!(stats.trajectories_buffered, 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_force_background() {
|
||||
let coord = LoopCoordinator::new(256);
|
||||
|
||||
for i in 0..150 {
|
||||
let t = make_trajectory(coord.next_trajectory_id());
|
||||
coord.on_inference(t);
|
||||
}
|
||||
|
||||
let result = coord.force_background();
|
||||
assert_eq!(result.trajectories_processed, 150);
|
||||
assert!(result.patterns_extracted > 0);
|
||||
}
|
||||
}
|
||||
+247
@@ -0,0 +1,247 @@
|
||||
//! Loop A - Instant Learning
|
||||
//!
|
||||
//! Per-request adaptation with <1ms overhead.
|
||||
|
||||
use crate::lora::MicroLoRA;
|
||||
use crate::trajectory::{TrajectoryBuffer, TrajectoryIdGen};
|
||||
use crate::types::{LearningSignal, QueryTrajectory, SonaConfig};
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Configuration for instant loop
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct InstantLoopConfig {
|
||||
/// Micro-LoRA rank
|
||||
pub micro_lora_rank: usize,
|
||||
/// Micro-LoRA learning rate
|
||||
pub micro_lora_lr: f32,
|
||||
/// Buffer capacity
|
||||
pub buffer_capacity: usize,
|
||||
/// Flush threshold (apply updates every N signals)
|
||||
pub flush_threshold: usize,
|
||||
}
|
||||
|
||||
impl Default for InstantLoopConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
micro_lora_rank: 1,
|
||||
micro_lora_lr: 0.001,
|
||||
buffer_capacity: 10000,
|
||||
flush_threshold: 100,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SonaConfig> for InstantLoopConfig {
|
||||
fn from(config: &SonaConfig) -> Self {
|
||||
Self {
|
||||
micro_lora_rank: config.micro_lora_rank,
|
||||
micro_lora_lr: config.micro_lora_lr,
|
||||
buffer_capacity: config.trajectory_capacity,
|
||||
flush_threshold: 100,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Instant loop metrics
|
||||
#[derive(Debug, Default)]
|
||||
pub struct InstantLoopMetrics {
|
||||
/// Total trajectories processed
|
||||
pub trajectories_processed: AtomicU64,
|
||||
/// Total signals accumulated
|
||||
pub signals_accumulated: AtomicU64,
|
||||
/// Total flushes performed
|
||||
pub flushes_performed: AtomicU64,
|
||||
/// Total updates applied
|
||||
pub updates_applied: AtomicU64,
|
||||
}
|
||||
|
||||
/// Instant learning loop (Loop A)
|
||||
pub struct InstantLoop {
|
||||
/// Configuration
|
||||
config: InstantLoopConfig,
|
||||
/// Trajectory buffer
|
||||
trajectory_buffer: Arc<TrajectoryBuffer>,
|
||||
/// Micro-LoRA adapter
|
||||
micro_lora: Arc<RwLock<MicroLoRA>>,
|
||||
/// ID generator
|
||||
id_gen: TrajectoryIdGen,
|
||||
/// Pending signal count
|
||||
pending_signals: AtomicU64,
|
||||
/// Metrics
|
||||
pub metrics: InstantLoopMetrics,
|
||||
}
|
||||
|
||||
impl InstantLoop {
|
||||
/// Create new instant loop
|
||||
pub fn new(hidden_dim: usize, config: InstantLoopConfig) -> Self {
|
||||
Self {
|
||||
trajectory_buffer: Arc::new(TrajectoryBuffer::new(config.buffer_capacity)),
|
||||
micro_lora: Arc::new(RwLock::new(MicroLoRA::new(
|
||||
hidden_dim,
|
||||
config.micro_lora_rank,
|
||||
))),
|
||||
id_gen: TrajectoryIdGen::new(),
|
||||
pending_signals: AtomicU64::new(0),
|
||||
config,
|
||||
metrics: InstantLoopMetrics::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create from SONA config
|
||||
pub fn from_sona_config(config: &SonaConfig) -> Self {
|
||||
Self::new(config.hidden_dim, InstantLoopConfig::from(config))
|
||||
}
|
||||
|
||||
/// Generate next trajectory ID
|
||||
pub fn next_id(&self) -> u64 {
|
||||
self.id_gen.next()
|
||||
}
|
||||
|
||||
/// Process completed trajectory
|
||||
pub fn on_trajectory(&self, trajectory: QueryTrajectory) {
|
||||
// Record to buffer
|
||||
self.trajectory_buffer.record(trajectory.clone());
|
||||
self.metrics
|
||||
.trajectories_processed
|
||||
.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
// Generate learning signal
|
||||
let signal = LearningSignal::from_trajectory(&trajectory);
|
||||
|
||||
// Accumulate gradient (non-blocking)
|
||||
if let Some(mut lora) = self.micro_lora.try_write() {
|
||||
lora.accumulate_gradient(&signal);
|
||||
self.metrics
|
||||
.signals_accumulated
|
||||
.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
let pending = self.pending_signals.fetch_add(1, Ordering::Relaxed) + 1;
|
||||
|
||||
// Auto-flush if threshold reached
|
||||
if pending >= self.config.flush_threshold as u64 {
|
||||
self.flush_internal(&mut lora);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Manually flush accumulated updates
|
||||
pub fn flush(&self) {
|
||||
if let Some(mut lora) = self.micro_lora.try_write() {
|
||||
self.flush_internal(&mut lora);
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_internal(&self, lora: &mut MicroLoRA) {
|
||||
let pending = lora.pending_updates();
|
||||
if pending > 0 {
|
||||
lora.apply_accumulated(self.config.micro_lora_lr);
|
||||
self.pending_signals.store(0, Ordering::Relaxed);
|
||||
self.metrics
|
||||
.flushes_performed
|
||||
.fetch_add(1, Ordering::Relaxed);
|
||||
self.metrics
|
||||
.updates_applied
|
||||
.fetch_add(pending as u64, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
/// Drain trajectories for background processing
|
||||
pub fn drain_trajectories(&self) -> Vec<QueryTrajectory> {
|
||||
self.trajectory_buffer.drain()
|
||||
}
|
||||
|
||||
/// Drain up to N trajectories
|
||||
pub fn drain_trajectories_n(&self, n: usize) -> Vec<QueryTrajectory> {
|
||||
self.trajectory_buffer.drain_n(n)
|
||||
}
|
||||
|
||||
/// Get micro-LoRA reference for inference
|
||||
pub fn micro_lora(&self) -> &Arc<RwLock<MicroLoRA>> {
|
||||
&self.micro_lora
|
||||
}
|
||||
|
||||
/// Get trajectory buffer reference
|
||||
pub fn buffer(&self) -> &Arc<TrajectoryBuffer> {
|
||||
&self.trajectory_buffer
|
||||
}
|
||||
|
||||
/// Get pending trajectory count
|
||||
pub fn pending_count(&self) -> usize {
|
||||
self.trajectory_buffer.len()
|
||||
}
|
||||
|
||||
/// Get buffer stats
|
||||
pub fn buffer_stats(&self) -> (usize, u64, f64) {
|
||||
(
|
||||
self.trajectory_buffer.len(),
|
||||
self.trajectory_buffer.dropped_count(),
|
||||
self.trajectory_buffer.success_rate(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::types::TrajectoryStep;
|
||||
|
||||
fn make_trajectory(id: u64) -> QueryTrajectory {
|
||||
let mut t = QueryTrajectory::new(id, vec![0.1; 64]);
|
||||
t.add_step(TrajectoryStep::new(vec![0.5; 64], vec![], 0.8, 0));
|
||||
t.finalize(0.8, 1000);
|
||||
t
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_instant_loop_creation() {
|
||||
let loop_a = InstantLoop::new(64, InstantLoopConfig::default());
|
||||
assert_eq!(loop_a.pending_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trajectory_processing() {
|
||||
let loop_a = InstantLoop::new(64, InstantLoopConfig::default());
|
||||
|
||||
let t = make_trajectory(loop_a.next_id());
|
||||
loop_a.on_trajectory(t);
|
||||
|
||||
assert_eq!(loop_a.pending_count(), 1);
|
||||
assert_eq!(
|
||||
loop_a
|
||||
.metrics
|
||||
.trajectories_processed
|
||||
.load(Ordering::Relaxed),
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_auto_flush() {
|
||||
let config = InstantLoopConfig {
|
||||
flush_threshold: 3,
|
||||
..Default::default()
|
||||
};
|
||||
let loop_a = InstantLoop::new(64, config);
|
||||
|
||||
for i in 0..5 {
|
||||
loop_a.on_trajectory(make_trajectory(i));
|
||||
}
|
||||
|
||||
assert!(loop_a.metrics.flushes_performed.load(Ordering::Relaxed) >= 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_drain() {
|
||||
let loop_a = InstantLoop::new(64, InstantLoopConfig::default());
|
||||
|
||||
for i in 0..10 {
|
||||
loop_a.on_trajectory(make_trajectory(i));
|
||||
}
|
||||
|
||||
let drained = loop_a.drain_trajectories();
|
||||
assert_eq!(drained.len(), 10);
|
||||
assert_eq!(loop_a.pending_count(), 0);
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
//! SONA Learning Loops
|
||||
//!
|
||||
//! Three-tier temporal learning architecture:
|
||||
//! - Loop A (Instant): Per-request trajectory recording and micro-LoRA updates
|
||||
//! - Loop B (Background): Hourly pattern extraction and base LoRA updates
|
||||
//! - Loop C (Deep): Weekly dream consolidation and full EWC++ update
|
||||
|
||||
pub mod background;
|
||||
pub mod coordinator;
|
||||
pub mod instant;
|
||||
|
||||
pub use background::BackgroundLoop;
|
||||
pub use coordinator::LoopCoordinator;
|
||||
pub use instant::InstantLoop;
|
||||
Reference in New Issue
Block a user