1use crate::{Device, Result, Shape, Tensor, TensorError};
7use std::collections::HashMap;
8use std::sync::{Arc, RwLock};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub enum BackendType {
13 Cpu,
15 #[cfg(feature = "simd")]
17 SimdCpu,
18 #[cfg(feature = "blas")]
20 Blas,
21 #[cfg(feature = "gpu")]
23 Gpu,
24 #[cfg(all(feature = "cuda", any(target_os = "linux", target_os = "windows")))]
26 Cuda,
27 #[cfg(feature = "rocm")]
29 Rocm,
30 #[cfg(all(feature = "metal", target_os = "macos"))]
32 Metal,
33}
34
35impl BackendType {
36 pub fn is_available(&self) -> bool {
38 match self {
39 BackendType::Cpu => true,
40 #[cfg(feature = "simd")]
41 BackendType::SimdCpu => true,
42 #[cfg(feature = "blas")]
43 BackendType::Blas => crate::ops::lapack::is_lapack_available(),
44 #[cfg(feature = "gpu")]
45 BackendType::Gpu => true, #[cfg(all(feature = "cuda", any(target_os = "linux", target_os = "windows")))]
47 BackendType::Cuda => crate::gpu::cuda_kernels::is_cuda_available(),
48 #[cfg(feature = "rocm")]
49 BackendType::Rocm => false, #[cfg(all(feature = "metal", target_os = "macos"))]
51 BackendType::Metal => true,
52 }
53 }
54
55 pub fn priority(&self) -> u8 {
57 match self {
58 BackendType::Cpu => 0,
59 #[cfg(feature = "simd")]
60 BackendType::SimdCpu => 10,
61 #[cfg(feature = "blas")]
62 BackendType::Blas => 20,
63 #[cfg(feature = "gpu")]
64 BackendType::Gpu => 30,
65 #[cfg(all(feature = "cuda", any(target_os = "linux", target_os = "windows")))]
66 BackendType::Cuda => 40,
67 #[cfg(feature = "rocm")]
68 BackendType::Rocm => 40,
69 #[cfg(all(feature = "metal", target_os = "macos"))]
70 BackendType::Metal => 50,
71 }
72 }
73
74 pub fn from_device(device: &Device) -> Self {
76 match device {
77 Device::Cpu => BackendType::Cpu,
78 #[cfg(feature = "gpu")]
79 Device::Gpu(_) => BackendType::Gpu,
80 #[cfg(feature = "rocm")]
81 Device::Rocm(_) => BackendType::Rocm,
82 }
83 }
84}
85
86#[derive(Debug, Clone)]
88pub struct OperationDescriptor {
89 pub name: String,
91 pub category: String,
93 pub version: String,
95 pub supported_dtypes: Vec<crate::DType>,
97 pub min_rank: Option<usize>,
99 pub max_rank: Option<usize>,
101 pub supports_broadcast: bool,
103 pub supports_inplace: bool,
105}
106
107impl OperationDescriptor {
108 pub fn new(name: impl Into<String>, category: impl Into<String>) -> Self {
110 Self {
111 name: name.into(),
112 category: category.into(),
113 version: "1.0.0".to_string(),
114 supported_dtypes: vec![crate::DType::Float32, crate::DType::Float64],
115 min_rank: None,
116 max_rank: None,
117 supports_broadcast: false,
118 supports_inplace: false,
119 }
120 }
121
122 pub fn with_dtypes(mut self, dtypes: Vec<crate::DType>) -> Self {
124 self.supported_dtypes = dtypes;
125 self
126 }
127
128 pub fn with_rank_range(mut self, min: Option<usize>, max: Option<usize>) -> Self {
130 self.min_rank = min;
131 self.max_rank = max;
132 self
133 }
134
135 pub fn with_broadcast(mut self) -> Self {
137 self.supports_broadcast = true;
138 self
139 }
140
141 pub fn with_inplace(mut self) -> Self {
143 self.supports_inplace = true;
144 self
145 }
146}
147
148pub type UnaryKernelFn<T> = fn(&Tensor<T>) -> Result<Tensor<T>>;
150
151pub type BinaryKernelFn<T> = fn(&Tensor<T>, &Tensor<T>) -> Result<Tensor<T>>;
153
154#[derive(Clone)]
156pub struct KernelImplementation<T> {
157 pub backend: BackendType,
158 pub unary_fn: Option<UnaryKernelFn<T>>,
159 pub binary_fn: Option<BinaryKernelFn<T>>,
160}
161
162impl<T> KernelImplementation<T> {
163 pub fn unary(backend: BackendType, func: UnaryKernelFn<T>) -> Self {
165 Self {
166 backend,
167 unary_fn: Some(func),
168 binary_fn: None,
169 }
170 }
171
172 pub fn binary(backend: BackendType, func: BinaryKernelFn<T>) -> Self {
174 Self {
175 backend,
176 unary_fn: None,
177 binary_fn: Some(func),
178 }
179 }
180}
181
182struct RegisteredOperation<T> {
184 descriptor: OperationDescriptor,
185 kernels: Vec<KernelImplementation<T>>,
186}
187
188impl<T> RegisteredOperation<T> {
189 fn new(descriptor: OperationDescriptor) -> Self {
190 Self {
191 descriptor,
192 kernels: Vec::new(),
193 }
194 }
195
196 fn add_kernel(&mut self, kernel: KernelImplementation<T>) {
197 self.kernels.push(kernel);
198 }
199
200 fn select_kernel(&self, device: &Device) -> Option<&KernelImplementation<T>> {
202 let preferred_backend = BackendType::from_device(device);
203
204 if let Some(kernel) = self
206 .kernels
207 .iter()
208 .find(|k| k.backend == preferred_backend && k.backend.is_available())
209 {
210 return Some(kernel);
211 }
212
213 self.kernels
215 .iter()
216 .filter(|k| k.backend.is_available())
217 .max_by_key(|k| k.backend.priority())
218 }
219}
220
221pub struct DispatchRegistry<T> {
223 operations: Arc<RwLock<HashMap<String, RegisteredOperation<T>>>>,
224}
225
226impl<T> Default for DispatchRegistry<T> {
227 fn default() -> Self {
228 Self::new()
229 }
230}
231
232impl<T> DispatchRegistry<T> {
233 pub fn new() -> Self {
235 Self {
236 operations: Arc::new(RwLock::new(HashMap::new())),
237 }
238 }
239
240 pub fn register_operation(&self, descriptor: OperationDescriptor) -> Result<()> {
242 let mut ops = self.operations.write().map_err(|_| {
243 TensorError::invalid_operation_simple(
244 "dispatch registry write lock poisoned".to_string(),
245 )
246 })?;
247
248 if ops.contains_key(&descriptor.name) {
249 return Err(TensorError::invalid_argument(format!(
250 "Operation '{}' is already registered",
251 descriptor.name
252 )));
253 }
254
255 ops.insert(
256 descriptor.name.clone(),
257 RegisteredOperation::new(descriptor),
258 );
259 Ok(())
260 }
261
262 pub fn register_kernel(
264 &self,
265 operation_name: &str,
266 kernel: KernelImplementation<T>,
267 ) -> Result<()> {
268 let mut ops = self.operations.write().map_err(|_| {
269 TensorError::invalid_operation_simple(
270 "dispatch registry write lock poisoned".to_string(),
271 )
272 })?;
273
274 let op = ops.get_mut(operation_name).ok_or_else(|| {
275 TensorError::invalid_argument(format!(
276 "Operation '{}' not found. Register the operation first.",
277 operation_name
278 ))
279 })?;
280
281 op.add_kernel(kernel);
282 Ok(())
283 }
284
285 pub fn dispatch_unary(&self, operation_name: &str, input: &Tensor<T>) -> Result<Tensor<T>> {
287 let ops = self.operations.read().map_err(|_| {
288 TensorError::invalid_operation_simple(
289 "dispatch registry read lock poisoned".to_string(),
290 )
291 })?;
292
293 let op = ops.get(operation_name).ok_or_else(|| {
294 TensorError::invalid_argument(format!(
295 "Operation '{}' not found in registry",
296 operation_name
297 ))
298 })?;
299
300 let kernel = op.select_kernel(input.device()).ok_or_else(|| {
301 TensorError::invalid_argument(format!(
302 "No available kernel for operation '{}' on device {:?}",
303 operation_name,
304 input.device()
305 ))
306 })?;
307
308 let kernel_fn = kernel.unary_fn.ok_or_else(|| {
309 TensorError::invalid_argument(format!(
310 "Operation '{}' does not support unary execution",
311 operation_name
312 ))
313 })?;
314
315 kernel_fn(input)
316 }
317
318 pub fn dispatch_binary(
320 &self,
321 operation_name: &str,
322 lhs: &Tensor<T>,
323 rhs: &Tensor<T>,
324 ) -> Result<Tensor<T>> {
325 if lhs.device() != rhs.device() {
327 return Err(TensorError::device_mismatch(
328 operation_name,
329 &format!("{:?}", lhs.device()),
330 &format!("{:?}", rhs.device()),
331 ));
332 }
333
334 let ops = self.operations.read().map_err(|_| {
335 TensorError::invalid_operation_simple(
336 "dispatch registry read lock poisoned".to_string(),
337 )
338 })?;
339
340 let op = ops.get(operation_name).ok_or_else(|| {
341 TensorError::invalid_argument(format!(
342 "Operation '{}' not found in registry",
343 operation_name
344 ))
345 })?;
346
347 let kernel = op.select_kernel(lhs.device()).ok_or_else(|| {
348 TensorError::invalid_argument(format!(
349 "No available kernel for operation '{}' on device {:?}",
350 operation_name,
351 lhs.device()
352 ))
353 })?;
354
355 let kernel_fn = kernel.binary_fn.ok_or_else(|| {
356 TensorError::invalid_argument(format!(
357 "Operation '{}' does not support binary execution",
358 operation_name
359 ))
360 })?;
361
362 kernel_fn(lhs, rhs)
363 }
364
365 pub fn get_operation(&self, name: &str) -> Option<OperationDescriptor> {
367 self.operations
368 .read()
369 .ok()?
370 .get(name)
371 .map(|op| op.descriptor.clone())
372 }
373
374 pub fn list_operations(&self) -> Vec<String> {
376 match self.operations.read() {
377 Ok(ops) => ops.keys().cloned().collect(),
378 Err(_) => Vec::new(),
379 }
380 }
381
382 pub fn available_backends(&self, operation_name: &str) -> Vec<BackendType> {
384 match self.operations.read() {
385 Ok(ops) => {
386 if let Some(op) = ops.get(operation_name) {
387 op.kernels
388 .iter()
389 .filter(|k| k.backend.is_available())
390 .map(|k| k.backend)
391 .collect()
392 } else {
393 Vec::new()
394 }
395 }
396 Err(_) => Vec::new(),
397 }
398 }
399}
400
401#[macro_export]
403macro_rules! register_operation {
404 ($registry:expr, $name:expr, $category:expr) => {
405 $registry.register_operation(
406 $crate::OperationDescriptor::new($name, $category)
407 ).expect("operation registration should succeed");
408 };
409 ($registry:expr, $name:expr, $category:expr, dtypes: [$($dtype:expr),*]) => {
410 $registry.register_operation(
411 $crate::OperationDescriptor::new($name, $category)
412 .with_dtypes(vec![$($dtype),*])
413 ).expect("operation registration with dtypes should succeed");
414 };
415 ($registry:expr, $name:expr, $category:expr, rank: $min:expr, $max:expr) => {
416 $registry.register_operation(
417 $crate::OperationDescriptor::new($name, $category)
418 .with_rank_range(Some($min), Some($max))
419 ).expect("operation registration with rank range should succeed");
420 };
421}
422
423#[macro_export]
425macro_rules! register_unary_kernel {
426 ($registry:expr, $op_name:expr, $backend:expr, $func:expr) => {
427 $registry
428 .register_kernel(
429 $op_name,
430 $crate::KernelImplementation::unary($backend, $func),
431 )
432 .expect("unary kernel registration should succeed");
433 };
434}
435
436#[macro_export]
438macro_rules! register_binary_kernel {
439 ($registry:expr, $op_name:expr, $backend:expr, $func:expr) => {
440 $registry
441 .register_kernel(
442 $op_name,
443 $crate::KernelImplementation::binary($backend, $func),
444 )
445 .expect("binary kernel registration should succeed");
446 };
447}
448
449#[derive(Debug, Clone)]
454pub struct DispatchBenchmarkResult {
455 pub min_ns: u64,
457 pub max_ns: u64,
459 pub avg_ns: u64,
461 pub p95_ns: u64,
463 pub sample_count: usize,
465}
466
467impl DispatchBenchmarkResult {
468 pub fn from_sorted_samples(samples: &[u64]) -> Option<Self> {
472 if samples.is_empty() {
473 return None;
474 }
475
476 let min_ns = *samples.first().unwrap_or(&0);
477 let max_ns = *samples.last().unwrap_or(&0);
478
479 let sum: u64 = samples.iter().sum();
480 let avg_ns = sum / samples.len() as u64;
481
482 let p95_idx = ((samples.len() as f64 * 0.95) as usize).min(samples.len() - 1);
484 let p95_ns = samples[p95_idx];
485
486 Some(Self {
487 min_ns,
488 max_ns,
489 avg_ns,
490 p95_ns,
491 sample_count: samples.len(),
492 })
493 }
494}
495
496impl<T> DispatchRegistry<T> {
497 pub fn benchmark_overhead(&self) -> DispatchBenchmarkResult {
506 const SAMPLE_COUNT: usize = 1_000;
507 const PROBE_NAME: &str = "__overhead_probe__";
508
509 let mut samples: Vec<u64> = Vec::with_capacity(SAMPLE_COUNT);
510
511 for _ in 0..SAMPLE_COUNT {
512 let start = std::time::Instant::now();
513 let _ = self.get_operation(PROBE_NAME);
515 let elapsed_ns = start.elapsed().as_nanos() as u64;
516 samples.push(elapsed_ns);
517 }
518
519 samples.sort_unstable();
520
521 DispatchBenchmarkResult::from_sorted_samples(&samples).unwrap_or(DispatchBenchmarkResult {
523 min_ns: 0,
524 max_ns: 0,
525 avg_ns: 0,
526 p95_ns: 0,
527 sample_count: 0,
528 })
529 }
530}
531
532use lazy_static::lazy_static;
534
535lazy_static! {
536 pub static ref F32_REGISTRY: DispatchRegistry<f32> = DispatchRegistry::new();
538
539 pub static ref F64_REGISTRY: DispatchRegistry<f64> = DispatchRegistry::new();
541
542 pub static ref I32_REGISTRY: DispatchRegistry<i32> = DispatchRegistry::new();
544}
545
546pub fn get_registry<T: 'static>() -> Option<&'static DispatchRegistry<T>> {
548 use std::any::TypeId;
549
550 let type_id = TypeId::of::<T>();
551
552 if type_id == TypeId::of::<f32>() {
553 Some(unsafe {
555 &*(&*F32_REGISTRY as *const DispatchRegistry<f32> as *const DispatchRegistry<T>)
556 })
557 } else if type_id == TypeId::of::<f64>() {
558 Some(unsafe {
560 &*(&*F64_REGISTRY as *const DispatchRegistry<f64> as *const DispatchRegistry<T>)
561 })
562 } else if type_id == TypeId::of::<i32>() {
563 Some(unsafe {
565 &*(&*I32_REGISTRY as *const DispatchRegistry<i32> as *const DispatchRegistry<T>)
566 })
567 } else {
568 None
569 }
570}
571
572#[cfg(test)]
573mod tests {
574 use super::*;
575 use scirs2_core::ndarray::array;
576
577 #[test]
578 fn test_backend_type_priority() {
579 assert!(BackendType::Cpu.priority() < BackendType::Cpu.priority() + 1);
580
581 #[cfg(feature = "simd")]
582 assert!(BackendType::SimdCpu.priority() > BackendType::Cpu.priority());
583 }
584
585 #[test]
586 fn test_operation_descriptor() {
587 let desc = OperationDescriptor::new("test_op", "binary")
588 .with_dtypes(vec![crate::DType::Float32])
589 .with_broadcast()
590 .with_rank_range(Some(1), Some(4));
591
592 assert_eq!(desc.name, "test_op");
593 assert_eq!(desc.category, "binary");
594 assert!(desc.supports_broadcast);
595 assert_eq!(desc.min_rank, Some(1));
596 assert_eq!(desc.max_rank, Some(4));
597 }
598
599 #[test]
600 fn test_registry_creation() {
601 let registry: DispatchRegistry<f32> = DispatchRegistry::new();
602 assert_eq!(registry.list_operations().len(), 0);
603 }
604
605 #[test]
606 fn test_operation_registration() {
607 let registry: DispatchRegistry<f32> = DispatchRegistry::new();
608
609 let desc = OperationDescriptor::new("add", "binary");
610 registry
611 .register_operation(desc)
612 .expect("test: register_operation should succeed");
613
614 assert_eq!(registry.list_operations().len(), 1);
615 assert!(registry.get_operation("add").is_some());
616 }
617
618 #[test]
619 fn test_duplicate_registration_fails() {
620 let registry: DispatchRegistry<f32> = DispatchRegistry::new();
621
622 let desc1 = OperationDescriptor::new("add", "binary");
623 let desc2 = OperationDescriptor::new("add", "binary");
624
625 registry
626 .register_operation(desc1)
627 .expect("test: register_operation should succeed");
628 assert!(registry.register_operation(desc2).is_err());
629 }
630
631 #[test]
632 fn test_kernel_registration() {
633 let registry: DispatchRegistry<f32> = DispatchRegistry::new();
634
635 let desc = OperationDescriptor::new("abs", "unary");
637 registry
638 .register_operation(desc)
639 .expect("test: register_operation should succeed");
640
641 fn abs_cpu(x: &Tensor<f32>) -> Result<Tensor<f32>> {
643 let data = x.data();
644 let abs_data: Vec<f32> = data.iter().map(|v| v.abs()).collect();
645 let array = scirs2_core::ndarray::ArrayD::from_shape_vec(x.shape().dims(), abs_data)
646 .expect("test: operation should succeed");
647 Ok(Tensor::from_array(array))
648 }
649
650 let kernel = KernelImplementation::unary(BackendType::Cpu, abs_cpu);
651 registry
652 .register_kernel("abs", kernel)
653 .expect("test: register_kernel should succeed");
654
655 assert_eq!(registry.available_backends("abs").len(), 1);
656 }
657
658 #[test]
659 fn test_unary_dispatch() {
660 let registry: DispatchRegistry<f32> = DispatchRegistry::new();
661
662 let desc = OperationDescriptor::new("negate", "unary");
664 registry
665 .register_operation(desc)
666 .expect("test: register_operation should succeed");
667
668 fn negate_cpu(x: &Tensor<f32>) -> Result<Tensor<f32>> {
670 let data = x.data();
671 let neg_data: Vec<f32> = data.iter().map(|v| -v).collect();
672 let array = scirs2_core::ndarray::ArrayD::from_shape_vec(x.shape().dims(), neg_data)
673 .expect("test: operation should succeed");
674 Ok(Tensor::from_array(array))
675 }
676
677 let kernel = KernelImplementation::unary(BackendType::Cpu, negate_cpu);
678 registry
679 .register_kernel("negate", kernel)
680 .expect("test: register_kernel should succeed");
681
682 let input = Tensor::from_array(array![1.0f32, 2.0, 3.0].into_dyn());
684 let result = registry
685 .dispatch_unary("negate", &input)
686 .expect("test: dispatch_unary should succeed");
687
688 assert_eq!(result.data(), &[-1.0f32, -2.0, -3.0]);
689 }
690
691 #[test]
692 fn test_binary_dispatch() {
693 let registry: DispatchRegistry<f32> = DispatchRegistry::new();
694
695 let desc = OperationDescriptor::new("add", "binary");
697 registry
698 .register_operation(desc)
699 .expect("test: register_operation should succeed");
700
701 fn add_cpu(a: &Tensor<f32>, b: &Tensor<f32>) -> Result<Tensor<f32>> {
703 let a_data = a.data();
704 let b_data = b.data();
705 let sum_data: Vec<f32> = a_data
706 .iter()
707 .zip(b_data.iter())
708 .map(|(x, y)| x + y)
709 .collect();
710 let array = scirs2_core::ndarray::ArrayD::from_shape_vec(a.shape().dims(), sum_data)
711 .expect("test: operation should succeed");
712 Ok(Tensor::from_array(array))
713 }
714
715 let kernel = KernelImplementation::binary(BackendType::Cpu, add_cpu);
716 registry
717 .register_kernel("add", kernel)
718 .expect("test: register_kernel should succeed");
719
720 let a = Tensor::from_array(array![1.0f32, 2.0, 3.0].into_dyn());
722 let b = Tensor::from_array(array![4.0f32, 5.0, 6.0].into_dyn());
723 let result = registry
724 .dispatch_binary("add", &a, &b)
725 .expect("test: dispatch_binary should succeed");
726
727 assert_eq!(result.data(), &[5.0f32, 7.0, 9.0]);
728 }
729
730 #[test]
731 fn test_device_mismatch_error() {
732 let registry: DispatchRegistry<f32> = DispatchRegistry::new();
733
734 let desc = OperationDescriptor::new("add", "binary");
735 registry
736 .register_operation(desc)
737 .expect("test: register_operation should succeed");
738
739 fn add_cpu(a: &Tensor<f32>, b: &Tensor<f32>) -> Result<Tensor<f32>> {
740 Ok(a.clone())
741 }
742
743 let kernel = KernelImplementation::binary(BackendType::Cpu, add_cpu);
744 registry
745 .register_kernel("add", kernel)
746 .expect("test: register_kernel should succeed");
747
748 let a = Tensor::from_array(array![1.0f32].into_dyn());
749 let b = Tensor::from_array(array![2.0f32].into_dyn());
750
751 let result = registry.dispatch_binary("add", &a, &b);
753 assert!(result.is_ok());
754 }
755
756 #[test]
757 fn test_global_registry_access() {
758 let registry = get_registry::<f32>();
759 assert!(registry.is_some());
760
761 let registry = get_registry::<f64>();
762 assert!(registry.is_some());
763
764 let registry = get_registry::<i32>();
765 assert!(registry.is_some());
766 }
767}