1use num_traits::Float;
7use std::collections::HashMap;
8use std::sync::Arc;
9
10use crate::webgpu::{
11 backend::{BackendSelector, BackendType, ComputeBackend},
12 error::{ComputeError, ComputeResult},
13};
14use crate::{ActivationFunction, Layer, Network};
15
16#[cfg(feature = "gpu")]
17use crate::webgpu::webgpu_backend::WebGPUBackend;
18
19#[derive(Clone, Copy, Debug)]
21pub struct MatrixDims {
22 pub rows: usize,
23 pub cols: usize,
24}
25
26#[derive(Clone, Debug)]
27pub struct DeviceCapabilities {
28 pub max_buffer_size: u64,
29 pub max_workgroup_size: usize,
30 pub shared_memory_size: u64,
31}
32
33#[derive(Clone, Debug, Default)]
34pub struct PerformanceStats {
35 pub kernel_time_ms: f64,
36 pub memory_transfer_ms: f64,
37 pub total_time_ms: f64,
38}
39
40#[derive(Debug)]
45pub struct ComputeContext<T: Float + std::fmt::Debug + Send + Sync + 'static> {
46 backend_selector: BackendSelector<T>,
48 current_backend: BackendType,
50 #[cfg(feature = "gpu")]
52 webgpu_backend: Option<Arc<WebGPUBackend<T>>>,
53 #[cfg(not(feature = "gpu"))]
54 webgpu_backend: Option<()>,
55 gpu_enabled: bool,
57 performance_tracker: Arc<std::sync::Mutex<PerformanceTracker>>,
59 weight_cache: std::collections::HashMap<usize, (Vec<T>, MatrixDims)>,
61}
62
63#[derive(Debug)]
65struct PerformanceTracker {
66 operation_counts: HashMap<String, u64>,
67 execution_times: HashMap<String, Vec<f64>>,
68 backend_switches: HashMap<BackendType, u64>,
69 optimization_events: Vec<OptimizationEvent>,
70}
71
72#[derive(Debug, Clone)]
73struct OptimizationEvent {
74 timestamp: std::time::Instant,
75 event_type: String,
76 backend_from: BackendType,
77 backend_to: BackendType,
78 performance_gain: f64,
79}
80
81impl<T: Float + Send + Sync + std::fmt::Debug + 'static> ComputeContext<T> {
82 pub fn new() -> ComputeResult<Self> {
84 let backend_selector = BackendSelector::new();
85
86 #[cfg(feature = "gpu")]
88 let (webgpu_backend, gpu_enabled) = {
89 let gpu_available = cfg!(feature = "gpu");
91 if gpu_available {
92 (None, false) } else {
96 (None, false)
97 }
98 };
99
100 #[cfg(not(feature = "gpu"))]
101 let (webgpu_backend, gpu_enabled) = (None, false);
102
103 let current_backend = if gpu_enabled {
105 BackendType::WebGPU
106 } else {
107 BackendType::Simd
108 };
109
110 Ok(Self {
111 backend_selector,
112 current_backend,
113 webgpu_backend,
114 gpu_enabled,
115 performance_tracker: Arc::new(std::sync::Mutex::new(PerformanceTracker::new())),
116 weight_cache: HashMap::new(),
117 })
118 }
119
120 pub fn cpu_only() -> Self {
122 Self {
123 backend_selector: BackendSelector::new(),
124 current_backend: BackendType::Cpu,
125 webgpu_backend: None,
126 gpu_enabled: false,
127 performance_tracker: Arc::new(std::sync::Mutex::new(PerformanceTracker::new())),
128 weight_cache: HashMap::new(),
129 }
130 }
131
132 pub fn is_gpu_available(&self) -> bool {
134 self.gpu_enabled && self.webgpu_backend.is_some()
135 }
136
137 pub fn current_backend(&self) -> BackendType {
139 self.current_backend
140 }
141
142 pub fn select_backend(&mut self, problem_size: usize) -> BackendType {
144 let profile = crate::webgpu::backend::ComputeProfile {
145 matrix_size: match problem_size {
146 0..=10000 => crate::webgpu::backend::MatrixSize::Small,
147 10001..=1000000 => crate::webgpu::backend::MatrixSize::Medium,
148 _ => crate::webgpu::backend::MatrixSize::Large,
149 },
150 batch_size: 1,
151 operation_type: crate::webgpu::backend::OperationType::Inference,
152 };
153
154 let selected = self
155 .backend_selector
156 .select_backend(&profile)
157 .map(|backend| backend.backend_type())
158 .unwrap_or(BackendType::Cpu);
159
160 if selected == BackendType::WebGPU && !self.is_gpu_available() {
162 self.current_backend = BackendType::Simd;
163 } else {
164 self.current_backend = selected;
165 }
166
167 self.current_backend
168 }
169
170 fn get_layer_weights(
172 &mut self,
173 layer: &Layer<T>,
174 layer_id: usize,
175 ) -> ComputeResult<(Vec<T>, MatrixDims)> {
176 if let Some(cached) = self.weight_cache.get(&layer_id) {
178 return Ok(cached.clone());
179 }
180
181 println!("Converting layer {layer_id} to matrix format");
183 println!(" Layer has {} neurons", layer.neurons.len());
184
185 let non_bias_neurons: Vec<&crate::Neuron<T>> =
188 layer.neurons.iter().filter(|n| !n.is_bias).collect();
189
190 println!(" Layer has {} non-bias neurons", non_bias_neurons.len());
191
192 let input_size = if let Some(neuron) = non_bias_neurons.first() {
196 println!(
197 " First neuron has {} connections",
198 neuron.connections.len()
199 );
200 neuron.connections.len()
201 } else {
202 println!(" No non-bias neurons found!");
203 return Err(ComputeError::InvalidDimensions(format!(
204 "Layer {layer_id} has no non-bias neurons"
205 )));
206 };
207
208 let output_size = non_bias_neurons.len();
209
210 println!(" Matrix dimensions: {output_size}x{input_size} (output_size x input_size)");
211
212 if input_size == 0 || output_size == 0 {
213 return Err(ComputeError::InvalidDimensions(format!(
214 "Invalid layer dimensions: {output_size}x{input_size}"
215 )));
216 }
217
218 let mut weights = Vec::with_capacity(output_size * input_size);
219
220 for neuron in &non_bias_neurons {
222 if neuron.connections.len() != input_size {
224 return Err(ComputeError::InvalidDimensions(format!(
225 "Neuron has {} connections, expected {}",
226 neuron.connections.len(),
227 input_size
228 )));
229 }
230
231 for i in 0..input_size {
233 weights.push(neuron.connections[i].weight);
234 }
235 }
236
237 if weights.len() != output_size * input_size {
238 return Err(ComputeError::InvalidDimensions(format!(
239 "Weight matrix size mismatch: got {}, expected {}",
240 weights.len(),
241 output_size * input_size
242 )));
243 }
244
245 let dims = MatrixDims {
246 rows: output_size,
247 cols: input_size,
248 };
249 let result = (weights, dims);
250
251 self.weight_cache.insert(layer_id, result.clone());
253
254 Ok(result)
255 }
256
257 pub async fn compute_layer_forward(
259 &mut self,
260 layer: &Layer<T>,
261 layer_id: usize,
262 inputs: &[T],
263 ) -> ComputeResult<Vec<T>>
264 where
265 T: Clone + num_traits::ToPrimitive + 'static,
266 {
267 let start_time = std::time::Instant::now();
268
269 let (weights, dims) = self.get_layer_weights(layer, layer_id)?;
271
272 let mut input_with_bias = inputs.to_vec();
274 if dims.cols == inputs.len() + 1 {
275 println!(" Adding bias input to match expected dimensions");
277 input_with_bias.push(T::one()); } else if inputs.len() != dims.cols {
279 return Err(ComputeError::InvalidDimensions(format!(
280 "Input size {} doesn't match expected {} and doesn't match bias pattern",
281 inputs.len(),
282 dims.cols
283 )));
284 }
285
286 let problem_size = dims.rows * dims.cols;
288 let backend_type = self.select_backend(problem_size);
289
290 let result = match backend_type {
292 BackendType::WebGPU if self.is_gpu_available() => {
293 self.compute_layer_gpu(layer, &weights, &input_with_bias, dims)
294 .await
295 }
296 BackendType::Simd => {
297 self.compute_layer_simd(layer, &weights, &input_with_bias, dims)
298 .await
299 }
300 _ => {
301 self.compute_layer_cpu(layer, &weights, &input_with_bias, dims)
302 .await
303 }
304 };
305
306 let duration = start_time.elapsed().as_secs_f64();
308 if let Ok(mut tracker) = self.performance_tracker.lock() {
309 tracker.record_operation("layer_forward", duration, backend_type);
310 }
311
312 result
313 }
314
315 async fn compute_layer_gpu(
317 &self,
318 layer: &Layer<T>,
319 weights: &[T],
320 inputs: &[T],
321 dims: MatrixDims,
322 ) -> ComputeResult<Vec<T>>
323 where
324 T: Clone + num_traits::ToPrimitive + 'static,
325 {
326 #[cfg(feature = "gpu")]
327 {
328 if let Some(ref gpu_backend) = self.webgpu_backend {
329 let outputs =
331 gpu_backend.matrix_vector_multiply(weights, inputs, dims.rows, dims.cols)?;
332
333 let activation_function = layer
336 .neurons
337 .iter()
338 .find(|n| !n.is_bias)
339 .map(|n| n.activation_function)
340 .unwrap_or(ActivationFunction::Linear);
341 let steepness = T::one();
342 gpu_backend.apply_activation_function(&outputs, activation_function, steepness)
343 } else {
344 Err(ComputeError::GpuUnavailable)
345 }
346 }
347
348 #[cfg(not(feature = "gpu"))]
349 {
350 Err(ComputeError::GpuUnavailable)
351 }
352 }
353
354 async fn compute_layer_simd(
356 &self,
357 layer: &Layer<T>,
358 weights: &[T],
359 inputs: &[T],
360 dims: MatrixDims,
361 ) -> ComputeResult<Vec<T>>
362 where
363 T: Clone + 'static,
364 {
365 let profile = crate::webgpu::backend::ComputeProfile {
367 matrix_size: crate::webgpu::backend::MatrixSize::Medium,
368 batch_size: 1,
369 operation_type: crate::webgpu::backend::OperationType::Inference,
370 };
371
372 if let Some(backend) = self.backend_selector.select_backend(&profile) {
373 let outputs = backend.matrix_vector_multiply(weights, inputs, dims.rows, dims.cols)?;
374 let activation_function = layer
376 .neurons
377 .iter()
378 .find(|n| !n.is_bias)
379 .map(|n| n.activation_function)
380 .unwrap_or(ActivationFunction::Linear);
381 let steepness = T::one();
382 backend.apply_activation_function(&outputs, activation_function, steepness)
383 } else {
384 self.compute_layer_cpu(layer, weights, inputs, dims).await
385 }
386 }
387
388 async fn compute_layer_cpu(
390 &self,
391 layer: &Layer<T>,
392 weights: &[T],
393 inputs: &[T],
394 dims: MatrixDims,
395 ) -> ComputeResult<Vec<T>> {
396 let mut outputs = Vec::with_capacity(dims.rows);
397
398 for row in 0..dims.rows {
400 let mut sum = T::zero();
401 for col in 0..dims.cols {
402 sum = sum + weights[row * dims.cols + col] * inputs[col];
403 }
404 outputs.push(sum);
405 }
406
407 let activation_function = layer
410 .neurons
411 .iter()
412 .find(|n| !n.is_bias)
413 .map(|n| n.activation_function)
414 .unwrap_or(ActivationFunction::Linear);
415 let result: Vec<T> = outputs
416 .into_iter()
417 .map(|x| apply_activation_cpu(x, activation_function, T::one()))
418 .collect();
419
420 Ok(result)
421 }
422
423 pub async fn compute_network_forward(
425 &mut self,
426 network: &Network<T>,
427 inputs: &[T],
428 ) -> ComputeResult<Vec<T>>
429 where
430 T: Clone + num_traits::ToPrimitive + 'static,
431 {
432 if network.layers.is_empty() {
434 return Err(ComputeError::InvalidDimensions(
435 "Network has no layers".to_string(),
436 ));
437 }
438
439 if !network.layers.is_empty() && inputs.len() != network.num_inputs() {
441 return Err(ComputeError::InvalidDimensions(format!(
442 "Input size {} doesn't match network input size {}",
443 inputs.len(),
444 network.num_inputs()
445 )));
446 }
447
448 let mut current_inputs = inputs.to_vec();
449
450 for (layer_id, layer) in network.layers.iter().enumerate().skip(1) {
453 current_inputs = match self
455 .compute_layer_forward(layer, layer_id, ¤t_inputs)
456 .await
457 {
458 Ok(outputs) => outputs,
459 Err(e) => {
460 eprintln!("Error in layer {layer_id}: {e:?}");
461 return Err(e);
462 }
463 };
464 }
465
466 Ok(current_inputs)
467 }
468
469 pub fn clear_cache(&mut self) {
471 self.weight_cache.clear();
472 }
473
474 pub fn get_performance_stats(&self) -> ComputePerformanceStats {
476 let tracker_stats = if let Ok(tracker) = self.performance_tracker.lock() {
477 Some(tracker.get_stats())
478 } else {
479 None
480 };
481
482 #[cfg(feature = "gpu")]
483 let gpu_stats = self.webgpu_backend.as_ref().map(|_gpu_backend| {
484 PerformanceStats::default()
486 });
487
488 #[cfg(not(feature = "gpu"))]
489 let gpu_stats = None;
490
491 ComputePerformanceStats {
492 current_backend: self.current_backend,
493 gpu_available: self.is_gpu_available(),
494 cache_size: self.weight_cache.len(),
495 tracker_stats,
496 gpu_stats,
497 }
498 }
499
500 pub fn memory_manager(&self) -> GpuMemoryManager<T> {
502 GpuMemoryManager::new()
503 }
504}
505
506pub struct GpuMemoryManager<T: Float> {
508 _phantom: std::marker::PhantomData<T>,
509}
510
511impl<T: Float> GpuMemoryManager<T> {
512 pub fn new() -> Self {
514 Self {
515 _phantom: std::marker::PhantomData,
516 }
517 }
518
519 pub fn allocate_buffer(&self, size: usize) -> ComputeResult<super::memory::BufferHandle> {
521 Ok(super::memory::BufferHandle::new(size as u64))
524 }
525
526 pub fn upload_data(
528 &self,
529 _handle: super::memory::BufferHandle,
530 _data: &[T],
531 ) -> ComputeResult<()> {
532 Ok(())
534 }
535
536 pub fn download_data(&self, _handle: super::memory::BufferHandle) -> ComputeResult<Vec<T>> {
538 Ok(Vec::new())
540 }
541
542 pub fn deallocate_buffer(&self, _handle: super::memory::BufferHandle) -> ComputeResult<()> {
544 Ok(())
546 }
547}
548
549impl PerformanceTracker {
550 fn new() -> Self {
551 Self {
552 operation_counts: HashMap::new(),
553 execution_times: HashMap::new(),
554 backend_switches: HashMap::new(),
555 optimization_events: Vec::new(),
556 }
557 }
558
559 fn record_operation(&mut self, operation: &str, duration: f64, backend: BackendType) {
560 *self
561 .operation_counts
562 .entry(operation.to_string())
563 .or_insert(0) += 1;
564 self.execution_times
565 .entry(operation.to_string())
566 .or_default()
567 .push(duration);
568 *self.backend_switches.entry(backend).or_insert(0) += 1;
569 }
570
571 fn get_stats(&self) -> TrackerStats {
572 TrackerStats {
573 total_operations: self.operation_counts.values().sum(),
574 average_duration: self
575 .execution_times
576 .values()
577 .flat_map(|times| times.iter())
578 .sum::<f64>()
579 / self
580 .execution_times
581 .values()
582 .map(|times| times.len())
583 .sum::<usize>() as f64,
584 backend_distribution: self.backend_switches.clone(),
585 optimization_events: self.optimization_events.len(),
586 }
587 }
588}
589
590fn apply_activation_cpu<T: Float>(x: T, function: ActivationFunction, steepness: T) -> T {
592 match function {
593 ActivationFunction::Linear => x * steepness,
594 ActivationFunction::Sigmoid => {
595 let exp_val = (-steepness * x).exp();
596 T::one() / (T::one() + exp_val)
597 }
598 ActivationFunction::ReLU => {
599 if x > T::zero() {
600 x
601 } else {
602 T::zero()
603 }
604 }
605 ActivationFunction::ReLULeaky => {
606 let alpha = T::from(0.01).unwrap_or(T::zero());
607 if x > T::zero() {
608 x
609 } else {
610 alpha * x
611 }
612 }
613 ActivationFunction::Tanh => (steepness * x).tanh(),
614 _ => x, }
616}
617
618#[derive(Debug, Clone)]
620pub struct ComputePerformanceStats {
621 pub current_backend: BackendType,
622 pub gpu_available: bool,
623 pub cache_size: usize,
624 pub tracker_stats: Option<TrackerStats>,
625 pub gpu_stats: Option<PerformanceStats>,
626}
627
628#[derive(Debug, Clone)]
630pub struct TrackerStats {
631 pub total_operations: u64,
632 pub average_duration: f64,
633 pub backend_distribution: HashMap<BackendType, u64>,
634 pub optimization_events: usize,
635}
636
637#[cfg(test)]
638mod tests {
639 use super::*;
640 use crate::NetworkBuilder;
641
642 #[tokio::test]
643 async fn test_compute_context_creation() {
644 let context = ComputeContext::<f32>::cpu_only();
645 assert!(!context.is_gpu_available());
646 assert_eq!(context.current_backend(), BackendType::Cpu);
647 }
648
649 #[tokio::test]
650 async fn test_backend_selection() {
651 let mut context = ComputeContext::<f32>::cpu_only();
652
653 let backend = context.select_backend(100);
655 assert!(matches!(backend, BackendType::Cpu | BackendType::Simd));
656
657 let backend = context.select_backend(1000000);
659 assert!(matches!(backend, BackendType::Cpu | BackendType::Simd));
661 }
662
663 #[tokio::test]
664 async fn test_network_forward_pass() {
665 let mut context = ComputeContext::<f32>::cpu_only();
666
667 let network = NetworkBuilder::<f32>::new()
669 .input_layer(2)
670 .hidden_layer(3)
671 .output_layer(1)
672 .build();
673
674 let inputs = vec![0.5f32, 0.7f32];
675
676 println!("Network structure:");
678 println!(" Layers: {}", network.layers.len());
679 for (i, layer) in network.layers.iter().enumerate() {
680 println!(" Layer {}: {} neurons", i, layer.neurons.len());
681
682 if let Some(neuron) = layer.neurons.first() {
684 println!(
685 " First neuron has {} connections, is_bias: {}",
686 neuron.connections.len(),
687 neuron.is_bias
688 );
689 }
690 }
691
692 println!("Starting forward pass with {} inputs", inputs.len());
693 let result = context.compute_network_forward(&network, &inputs).await;
694
695 match &result {
696 Ok(outputs) => println!("Forward pass succeeded with {} outputs", outputs.len()),
697 Err(e) => println!("Forward pass failed: {e:?}"),
698 }
699
700 assert!(result.is_ok(), "Forward pass failed");
701
702 let outputs = result.unwrap();
703 assert_eq!(outputs.len(), 1, "Output should have 1 value");
704 }
705
706 #[tokio::test]
707 async fn test_performance_tracking() {
708 let context = ComputeContext::<f32>::cpu_only();
709 let stats = context.get_performance_stats();
710
711 assert_eq!(stats.current_backend, BackendType::Cpu);
712 assert!(!stats.gpu_available);
713 assert_eq!(stats.cache_size, 0);
714 }
715}