Files
ruvnet--RuView/vendor/sublinear-time-solver/crates/neural-network-implementation/src/wasm.rs
T
rUv 407b46b206 feat: vendor midstream and sublinear-time-solver libraries (#109)
Add ruvnet/midstream (AIMDS real-time inference) and
ruvnet/sublinear-time-solver (sublinear optimization algorithms)
as vendored dependencies under vendor/.
2026-03-02 23:34:05 -05:00

618 lines
20 KiB
Rust

//! WASM bindings for temporal neural network
//!
//! This module provides WebAssembly bindings for the temporal neural network,
//! enabling sub-millisecond neural inference in web browsers and Node.js.
use wasm_bindgen::prelude::*;
use js_sys::{Array, Object, Reflect};
use web_sys::console;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::{
config::{Config, ModelConfig, TrainingConfig, InferenceConfig},
error::{Result, TemporalNeuralError},
};
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen(start)]
pub fn main() {
console_error_panic_hook::set_once();
console::log_1(&"Temporal Neural Solver initialized".into());
}
/// WASM wrapper for temporal neural network configuration
#[wasm_bindgen]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WasmConfig {
inner: Config,
}
#[wasm_bindgen]
impl WasmConfig {
/// Create a new configuration from JSON
#[wasm_bindgen(constructor)]
pub fn new(json_config: &str) -> Result<WasmConfig, JsValue> {
let config: Config = serde_json::from_str(json_config)
.map_err(|e| JsValue::from_str(&format!("Invalid config: {}", e)))?;
Ok(WasmConfig { inner: config })
}
/// Create default configuration for System A
#[wasm_bindgen(js_name = systemA)]
pub fn system_a() -> WasmConfig {
// Create default System A config - simplified for WASM demo
let config = Config {
common: crate::config::CommonConfig {
horizon_ms: 100,
window_ms: 256,
sample_rate_hz: 1000,
features: vec!["x".to_string(), "y".to_string(), "vx".to_string(), "vy".to_string()],
quantize: true,
random_seed: Some(42),
verbose: false,
},
model: ModelConfig {
model_type: "micro_gru".to_string(),
hidden_size: 32,
num_layers: 2,
dropout: 0.1,
residual: true,
activation: "tanh".to_string(),
layer_norm: false,
},
training: TrainingConfig {
optimizer: "adam".to_string(),
learning_rate: 0.001,
batch_size: 32,
epochs: 100,
patience: 10,
val_frequency: 5,
grad_clip: Some(1.0),
weight_decay: 0.0001,
smoothness_weight: 0.1,
checkpoint_frequency: 10,
},
inference: InferenceConfig {
target_latency_ms: 0.9,
enable_simd: false, // Disabled for WASM compatibility
num_threads: 1,
pin_memory: false,
cpu_affinity: None,
batch_size: 1,
},
system: crate::config::SystemConfig::Traditional(crate::config::TraditionalConfig {
enabled: true,
}),
};
WasmConfig { inner: config }
}
/// Create default configuration for System B (temporal solver)
#[wasm_bindgen(js_name = systemB)]
pub fn system_b() -> WasmConfig {
// Create default System B config - simplified for WASM demo
let config = Config {
common: crate::config::CommonConfig {
horizon_ms: 100,
window_ms: 256,
sample_rate_hz: 1000,
features: vec!["x".to_string(), "y".to_string(), "vx".to_string(), "vy".to_string()],
quantize: true,
random_seed: Some(42),
verbose: false,
},
model: ModelConfig {
model_type: "micro_gru".to_string(),
hidden_size: 32,
num_layers: 2,
dropout: 0.1,
residual: true,
activation: "tanh".to_string(),
layer_norm: false,
},
training: TrainingConfig {
optimizer: "adam".to_string(),
learning_rate: 0.001,
batch_size: 32,
epochs: 100,
patience: 10,
val_frequency: 5,
grad_clip: Some(1.0),
weight_decay: 0.0001,
smoothness_weight: 0.1,
checkpoint_frequency: 10,
},
inference: InferenceConfig {
target_latency_ms: 0.9,
enable_simd: false, // Disabled for WASM compatibility
num_threads: 1,
pin_memory: false,
cpu_affinity: None,
batch_size: 1,
},
system: crate::config::SystemConfig::TemporalSolver(crate::config::TemporalSolverConfig {
prior: crate::config::KalmanConfig {
process_noise: 0.01,
measurement_noise: 0.1,
initial_uncertainty: 1.0,
transition_model: "constant_velocity".to_string(),
update_frequency: 100.0,
},
solver_gate: crate::config::SolverGateConfig {
algorithm: "neumann".to_string(),
error_threshold: 0.01,
max_iterations: 100,
confidence_threshold: 0.9,
fallback_enabled: true,
},
active_selection: crate::config::ActiveSelectionConfig {
enabled: true,
selection_ratio: 0.1,
embedding_dim: 16,
pagerank_damping: 0.85,
update_frequency: 10,
},
}),
};
WasmConfig { inner: config }
}
/// Export configuration as JSON
#[wasm_bindgen(js_name = toJSON)]
pub fn to_json(&self) -> String {
serde_json::to_string_pretty(&self.inner).unwrap_or_default()
}
}
/// WASM wrapper for temporal neural network models
#[wasm_bindgen]
pub struct WasmTemporalSolver {
predictor: Option<Box<dyn PredictorTrait>>,
model_type: String,
config: Config,
is_trained: bool,
}
/// Trait for unified predictor interface in WASM
trait PredictorTrait {
fn predict(&mut self, input: &[f64]) -> Result<Prediction>;
fn predict_batch(&mut self, inputs: &[Vec<f64>]) -> Result<Vec<Prediction>>;
fn get_latency_stats(&self) -> LatencyStats;
fn warmup(&mut self, iterations: u32) -> Result<()>;
}
/// Wrapper for System A predictor
struct SystemAPredictor {
predictor: Predictor,
}
impl PredictorTrait for SystemAPredictor {
fn predict(&mut self, input: &[f64]) -> Result<Prediction> {
let matrix = nalgebra::DMatrix::from_row_slice(4, input.len() / 4, input);
self.predictor.predict(&matrix)
}
fn predict_batch(&mut self, inputs: &[Vec<f64>]) -> Result<Vec<Prediction>> {
let matrices: Result<Vec<_>> = inputs.iter()
.map(|input| {
Ok(nalgebra::DMatrix::from_row_slice(4, input.len() / 4, input))
})
.collect();
self.predictor.predict_batch(&matrices?)
}
fn get_latency_stats(&self) -> LatencyStats {
let stats = self.predictor.get_statistics();
LatencyStats {
avg_latency_us: stats.avg_latency_us,
p50_latency_us: stats.p50_latency_us,
p99_latency_us: stats.p99_latency_us,
p99_9_latency_us: stats.p99_9_latency_us,
violation_rate: stats.violation_rate,
throughput_pred_per_sec: stats.throughput_pred_per_sec,
}
}
fn warmup(&mut self, iterations: u32) -> Result<()> {
self.predictor.warmup(iterations as usize)
}
}
/// Wrapper for System B predictor
struct SystemBPredictor {
predictor: Predictor,
}
impl PredictorTrait for SystemBPredictor {
fn predict(&mut self, input: &[f64]) -> Result<Prediction> {
let matrix = nalgebra::DMatrix::from_row_slice(4, input.len() / 4, input);
self.predictor.predict(&matrix)
}
fn predict_batch(&mut self, inputs: &[Vec<f64>]) -> Result<Vec<Prediction>> {
let matrices: Result<Vec<_>> = inputs.iter()
.map(|input| {
Ok(nalgebra::DMatrix::from_row_slice(4, input.len() / 4, input))
})
.collect();
self.predictor.predict_batch(&matrices?)
}
fn get_latency_stats(&self) -> LatencyStats {
let stats = self.predictor.get_statistics();
LatencyStats {
avg_latency_us: stats.avg_latency_us,
p50_latency_us: stats.p50_latency_us,
p99_latency_us: stats.p99_latency_us,
p99_9_latency_us: stats.p99_9_latency_us,
violation_rate: stats.violation_rate,
throughput_pred_per_sec: stats.throughput_pred_per_sec,
}
}
fn warmup(&mut self, iterations: u32) -> Result<()> {
self.predictor.warmup(iterations as usize)
}
}
/// Latency statistics for WASM export
#[wasm_bindgen]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LatencyStats {
pub avg_latency_us: f64,
pub p50_latency_us: f64,
pub p99_latency_us: f64,
pub p99_9_latency_us: f64,
pub violation_rate: f64,
pub throughput_pred_per_sec: f64,
}
/// Prediction result for WASM export
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct WasmPrediction {
prediction: Prediction,
}
#[wasm_bindgen]
impl WasmPrediction {
/// Get predicted values as array
#[wasm_bindgen(getter = values)]
pub fn values(&self) -> Vec<f64> {
self.prediction.values.as_slice().to_vec()
}
/// Get confidence score (0.0 to 1.0)
#[wasm_bindgen(getter = confidence)]
pub fn confidence(&self) -> f64 {
self.prediction.confidence
}
/// Get prediction latency in microseconds
#[wasm_bindgen(getter = latency_us)]
pub fn latency_us(&self) -> f64 {
self.prediction.latency_us
}
/// Get certificate error bound (System B only)
#[wasm_bindgen(getter = certificate_error)]
pub fn certificate_error(&self) -> Option<f64> {
self.prediction.certificate.as_ref().map(|c| c.error_bound)
}
/// Check if solver gate passed (System B only)
#[wasm_bindgen(getter = gate_passed)]
pub fn gate_passed(&self) -> Option<bool> {
self.prediction.certificate.as_ref().map(|c| c.is_valid)
}
/// Get prediction metadata as JSON
#[wasm_bindgen(js_name = getMetadataJSON)]
pub fn get_metadata_json(&self) -> String {
serde_json::to_string(&self.prediction.metadata).unwrap_or_default()
}
}
#[wasm_bindgen]
impl WasmTemporalSolver {
/// Create a new temporal solver instance
#[wasm_bindgen(constructor)]
pub fn new(config: &WasmConfig) -> WasmTemporalSolver {
WasmTemporalSolver {
predictor: None,
model_type: "uninitialized".to_string(),
config: config.inner.clone(),
is_trained: false,
}
}
/// Initialize System A (traditional neural network)
#[wasm_bindgen(js_name = initSystemA)]
pub fn init_system_a(&mut self) -> Result<(), JsValue> {
let model = SystemA::new(&self.config.model)
.map_err(|e| JsValue::from_str(&format!("Failed to create System A: {}", e)))?;
let predictor = Predictor::new_system_a(model, self.config.inference.clone())
.map_err(|e| JsValue::from_str(&format!("Failed to create predictor: {}", e)))?;
self.predictor = Some(Box::new(SystemAPredictor { predictor }));
self.model_type = "SystemA".to_string();
Ok(())
}
/// Initialize System B (temporal solver neural network)
#[wasm_bindgen(js_name = initSystemB)]
pub fn init_system_b(&mut self) -> Result<(), JsValue> {
let temporal_config = match &self.config.system {
crate::config::SystemConfig::TemporalSolver(config) => config.clone(),
_ => return Err(JsValue::from_str("Config must be for temporal solver system")),
};
let model = SystemB::new(&self.config.model, &temporal_config)
.map_err(|e| JsValue::from_str(&format!("Failed to create System B: {}", e)))?;
let predictor = Predictor::new_system_b(model, self.config.inference.clone())
.map_err(|e| JsValue::from_str(&format!("Failed to create predictor: {}", e)))?;
self.predictor = Some(Box::new(SystemBPredictor { predictor }));
self.model_type = "SystemB".to_string();
Ok(())
}
/// Perform single prediction
#[wasm_bindgen]
pub fn predict(&mut self, input: &[f64]) -> Result<WasmPrediction, JsValue> {
let predictor = self.predictor.as_mut()
.ok_or_else(|| JsValue::from_str("Model not initialized"))?;
let prediction = predictor.predict(input)
.map_err(|e| JsValue::from_str(&format!("Prediction failed: {}", e)))?;
Ok(WasmPrediction { prediction })
}
/// Perform batch predictions
#[wasm_bindgen(js_name = predictBatch)]
pub fn predict_batch(&mut self, inputs: &JsValue) -> Result<Array, JsValue> {
let predictor = self.predictor.as_mut()
.ok_or_else(|| JsValue::from_str("Model not initialized"))?;
// Convert JS array to Vec<Vec<f64>>
let js_array = Array::from(inputs);
let mut input_vectors = Vec::new();
for i in 0..js_array.length() {
let js_input = js_array.get(i);
let input_array = Array::from(&js_input);
let mut input_vec = Vec::new();
for j in 0..input_array.length() {
let val = input_array.get(j).as_f64()
.ok_or_else(|| JsValue::from_str("Input must be numeric"))?;
input_vec.push(val);
}
input_vectors.push(input_vec);
}
let predictions = predictor.predict_batch(&input_vectors)
.map_err(|e| JsValue::from_str(&format!("Batch prediction failed: {}", e)))?;
let result_array = Array::new();
for prediction in predictions {
let wasm_pred = WasmPrediction { prediction };
result_array.push(&JsValue::from(wasm_pred));
}
Ok(result_array)
}
/// Get current latency statistics
#[wasm_bindgen(js_name = getStats)]
pub fn get_stats(&self) -> Result<LatencyStats, JsValue> {
let predictor = self.predictor.as_ref()
.ok_or_else(|| JsValue::from_str("Model not initialized"))?;
Ok(predictor.get_latency_stats())
}
/// Warm up the model for consistent latency
#[wasm_bindgen]
pub fn warmup(&mut self, iterations: u32) -> Result<(), JsValue> {
let predictor = self.predictor.as_mut()
.ok_or_else(|| JsValue::from_str("Model not initialized"))?;
predictor.warmup(iterations)
.map_err(|e| JsValue::from_str(&format!("Warmup failed: {}", e)))?;
Ok(())
}
/// Check if model meets performance targets
#[wasm_bindgen(js_name = meetsTargets)]
pub fn meets_targets(&self) -> bool {
if let Some(predictor) = &self.predictor {
let stats = predictor.get_latency_stats();
stats.p99_9_latency_us <= 900.0 && stats.violation_rate <= 0.001
} else {
false
}
}
/// Get model type
#[wasm_bindgen(getter = model_type)]
pub fn model_type(&self) -> String {
self.model_type.clone()
}
/// Check if model is trained
#[wasm_bindgen(getter = is_trained)]
pub fn is_trained(&self) -> bool {
self.is_trained
}
/// Get build information
#[wasm_bindgen(js_name = getBuildInfo)]
pub fn get_build_info() -> String {
let build_info = crate::build_info();
serde_json::to_string(&build_info).unwrap_or_default()
}
/// Benchmark the solver
#[wasm_bindgen]
pub fn benchmark(&mut self, num_predictions: u32) -> Result<String, JsValue> {
let predictor = self.predictor.as_mut()
.ok_or_else(|| JsValue::from_str("Model not initialized"))?;
let start_time = js_sys::Date::now();
let test_input = vec![1.0; 1024]; // 4x256 test input
for _ in 0..num_predictions {
predictor.predict(&test_input)
.map_err(|e| JsValue::from_str(&format!("Benchmark prediction failed: {}", e)))?;
}
let end_time = js_sys::Date::now();
let total_time_ms = end_time - start_time;
let avg_latency_ms = total_time_ms / num_predictions as f64;
let throughput = num_predictions as f64 / (total_time_ms / 1000.0);
let stats = predictor.get_latency_stats();
let benchmark_result = serde_json::json!({
"num_predictions": num_predictions,
"total_time_ms": total_time_ms,
"avg_latency_ms": avg_latency_ms,
"avg_latency_us": avg_latency_ms * 1000.0,
"throughput_pred_per_sec": throughput,
"p99_9_latency_us": stats.p99_9_latency_us,
"meets_target": avg_latency_ms * 1000.0 <= 900.0,
"model_type": self.model_type,
});
Ok(benchmark_result.to_string())
}
}
/// Utilities for WASM
#[wasm_bindgen]
pub struct WasmUtils;
#[wasm_bindgen]
impl WasmUtils {
/// Get version information
#[wasm_bindgen(js_name = getVersion)]
pub fn get_version() -> String {
crate::VERSION.to_string()
}
/// Check if SIMD is supported
#[wasm_bindgen(js_name = hasSIMD)]
pub fn has_simd() -> bool {
// In WASM, SIMD support would need to be detected at runtime
false // Conservative default
}
/// Log message to console
#[wasm_bindgen(js_name = log)]
pub fn log(message: &str) {
console::log_1(&message.into());
}
/// Generate sample trajectory data for testing
#[wasm_bindgen(js_name = generateSampleData)]
pub fn generate_sample_data(length: u32) -> Array {
let mut data = Array::new();
for i in 0..length {
let t = i as f64 * 0.01;
let trajectory = Array::new();
// Simple circular trajectory
trajectory.push(&JsValue::from(t.cos())); // x
trajectory.push(&JsValue::from(t.sin())); // y
trajectory.push(&JsValue::from(-t.sin())); // vx
trajectory.push(&JsValue::from(t.cos())); // vy
data.push(&trajectory);
}
data
}
/// Calculate temporal lead for given distance
#[wasm_bindgen(js_name = calculateTemporalLead)]
pub fn calculate_temporal_lead(distance_km: f64, computation_us: f64) -> f64 {
let light_speed_km_per_s = 299_792.458; // km/ms in vacuum
let light_travel_us = (distance_km / light_speed_km_per_s) * 1000.0;
light_travel_us - computation_us
}
}
/// Training interface for WASM (simplified)
#[wasm_bindgen]
pub struct WasmTrainer {
trainer: Option<Trainer>,
config: TrainingConfig,
}
#[wasm_bindgen]
impl WasmTrainer {
/// Create new trainer
#[wasm_bindgen(constructor)]
pub fn new(config_json: &str) -> Result<WasmTrainer, JsValue> {
let config: TrainingConfig = serde_json::from_str(config_json)
.map_err(|e| JsValue::from_str(&format!("Invalid training config: {}", e)))?;
let trainer = Trainer::new(config.clone())
.map_err(|e| JsValue::from_str(&format!("Failed to create trainer: {}", e)))?;
Ok(WasmTrainer {
trainer: Some(trainer),
config,
})
}
/// Train model with provided data
#[wasm_bindgen]
pub fn train(&mut self, data_json: &str) -> Result<String, JsValue> {
let _trainer = self.trainer.as_mut()
.ok_or_else(|| JsValue::from_str("Trainer not initialized"))?;
// For WASM, we'll provide a simplified training interface
// Full training would require streaming data support
let result = serde_json::json!({
"message": "Training interface available - use full Rust API for production training",
"epochs_completed": 0,
"final_loss": 0.0,
"training_time_seconds": 0.0
});
Ok(result.to_string())
}
}
// Error handling utilities
impl From<TemporalNeuralError> for JsValue {
fn from(error: TemporalNeuralError) -> Self {
JsValue::from_str(&format!("{}", error))
}
}
// Export main initialization function
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
// Macro for logging from WASM
macro_rules! console_log {
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
}
pub(crate) use console_log;