1use crate::dtype::Float;
69use crate::error::{FerrotorchError, FerrotorchResult};
70use crate::tensor::Tensor;
71
72use std::collections::HashMap;
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
84#[repr(u8)]
85pub enum DispatchKey {
86 Cpu = 0,
88 Cuda = 1,
90 Meta = 2,
92 Sparse = 3,
96 Quantized = 4,
100 Nested = 5,
103 Autocast = 6,
107 Autograd = 7,
111 Vmap = 8,
115 Profiler = 9,
119 Tracer = 10,
123}
124
125impl DispatchKey {
126 #[inline]
128 pub fn priority(self) -> u8 {
129 self as u8
130 }
131
132 pub const ALL: [DispatchKey; 11] = [
135 DispatchKey::Cpu,
136 DispatchKey::Cuda,
137 DispatchKey::Meta,
138 DispatchKey::Sparse,
139 DispatchKey::Quantized,
140 DispatchKey::Nested,
141 DispatchKey::Autocast,
142 DispatchKey::Autograd,
143 DispatchKey::Vmap,
144 DispatchKey::Profiler,
145 DispatchKey::Tracer,
146 ];
147}
148
149#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
152pub struct DispatchKeySet {
153 bits: u16,
154}
155
156impl DispatchKeySet {
157 #[inline]
159 pub const fn empty() -> Self {
160 Self { bits: 0 }
161 }
162
163 pub fn all() -> Self {
165 let mut set = Self::empty();
166 for &k in &DispatchKey::ALL {
167 set = set.insert(k);
168 }
169 set
170 }
171
172 pub fn from_keys<I: IntoIterator<Item = DispatchKey>>(keys: I) -> Self {
176 keys.into_iter().collect()
177 }
178
179 #[inline]
181 pub fn contains(self, key: DispatchKey) -> bool {
182 (self.bits >> key.priority()) & 1 != 0
183 }
184
185 #[inline]
187 #[must_use]
188 pub fn insert(self, key: DispatchKey) -> Self {
189 Self {
190 bits: self.bits | (1 << key.priority()),
191 }
192 }
193
194 #[inline]
196 #[must_use]
197 pub fn remove(self, key: DispatchKey) -> Self {
198 Self {
199 bits: self.bits & !(1 << key.priority()),
200 }
201 }
202
203 #[inline]
205 #[must_use]
206 pub fn union(self, other: Self) -> Self {
207 Self {
208 bits: self.bits | other.bits,
209 }
210 }
211
212 #[inline]
214 #[must_use]
215 pub fn intersection(self, other: Self) -> Self {
216 Self {
217 bits: self.bits & other.bits,
218 }
219 }
220
221 #[inline]
223 pub fn is_empty(self) -> bool {
224 self.bits == 0
225 }
226
227 #[inline]
229 pub fn len(self) -> usize {
230 self.bits.count_ones() as usize
231 }
232
233 pub fn highest(self) -> Option<DispatchKey> {
236 if self.bits == 0 {
237 return None;
238 }
239 DispatchKey::ALL
242 .iter()
243 .rev()
244 .find(|&&k| self.contains(k))
245 .copied()
246 }
247
248 pub fn iter_desc(self) -> impl Iterator<Item = DispatchKey> {
251 let mut bits = self.bits;
252 std::iter::from_fn(move || {
253 if bits == 0 {
254 return None;
255 }
256 let top = 15 - bits.leading_zeros() as u8;
258 bits &= !(1 << top);
259 DispatchKey::ALL
261 .iter()
262 .find(|k| k.priority() == top)
263 .copied()
264 })
265 }
266}
267
268impl Default for DispatchKeySet {
269 fn default() -> Self {
270 Self::empty()
271 }
272}
273
274impl FromIterator<DispatchKey> for DispatchKeySet {
275 fn from_iter<I: IntoIterator<Item = DispatchKey>>(keys: I) -> Self {
276 let mut set = Self::empty();
277 for k in keys {
278 set = set.insert(k);
279 }
280 set
281 }
282}
283
284impl<const N: usize> From<[DispatchKey; N]> for DispatchKeySet {
285 fn from(arr: [DispatchKey; N]) -> Self {
286 Self::from_keys(arr)
287 }
288}
289
290pub type Kernel<T> = Box<
303 dyn Fn(&[Tensor<T>], DispatchKeySet, &Dispatcher<T>) -> FerrotorchResult<Tensor<T>>
304 + Send
305 + Sync,
306>;
307
308pub struct Dispatcher<T: Float> {
314 kernels: HashMap<(String, DispatchKey), Kernel<T>>,
315}
316
317impl<T: Float> Dispatcher<T> {
318 pub fn new() -> Self {
320 Self {
321 kernels: HashMap::new(),
322 }
323 }
324
325 pub fn register<F>(&mut self, op_name: impl Into<String>, key: DispatchKey, kernel: F)
328 where
329 F: Fn(&[Tensor<T>], DispatchKeySet, &Dispatcher<T>) -> FerrotorchResult<Tensor<T>>
330 + Send
331 + Sync
332 + 'static,
333 {
334 self.kernels.insert((op_name.into(), key), Box::new(kernel));
335 }
336
337 pub fn has_kernel(&self, op_name: &str, key: DispatchKey) -> bool {
339 self.kernels.contains_key(&(op_name.to_string(), key))
340 }
341
342 pub fn kernel_count(&self) -> usize {
344 self.kernels.len()
345 }
346
347 pub fn call(
360 &self,
361 op_name: &str,
362 inputs: &[Tensor<T>],
363 keyset: DispatchKeySet,
364 ) -> FerrotorchResult<Tensor<T>> {
365 if keyset.is_empty() {
366 return Err(FerrotorchError::InvalidArgument {
367 message: format!(
368 "Dispatcher::call({op_name}): empty keyset — no backend to run on"
369 ),
370 });
371 }
372 for key in keyset.iter_desc() {
373 if let Some(kernel) = self.kernels.get(&(op_name.to_string(), key)) {
374 return kernel(inputs, keyset, self);
375 }
376 }
377 Err(FerrotorchError::InvalidArgument {
378 message: format!(
379 "Dispatcher::call({op_name}): no kernel registered for any key in {keyset:?}"
380 ),
381 })
382 }
383
384 pub fn call_direct(
391 &self,
392 op_name: &str,
393 inputs: &[Tensor<T>],
394 keyset: DispatchKeySet,
395 key: DispatchKey,
396 ) -> FerrotorchResult<Tensor<T>> {
397 match self.kernels.get(&(op_name.to_string(), key)) {
398 Some(kernel) => kernel(inputs, keyset, self),
399 None => Err(FerrotorchError::InvalidArgument {
400 message: format!(
401 "Dispatcher::call_direct({op_name}, {key:?}): no kernel registered"
402 ),
403 }),
404 }
405 }
406}
407
408impl<T: Float> Default for Dispatcher<T> {
409 fn default() -> Self {
410 Self::new()
411 }
412}
413
414impl<T: Float> std::fmt::Debug for Dispatcher<T> {
415 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
416 f.debug_struct("Dispatcher")
417 .field("kernel_count", &self.kernels.len())
418 .finish()
419 }
420}
421
422#[cfg(test)]
427mod tests {
428 use super::*;
429 use crate::storage::TensorStorage;
430
431 fn make_tensor(data: Vec<f32>, shape: Vec<usize>) -> Tensor<f32> {
432 Tensor::from_storage(TensorStorage::cpu(data), shape, false).unwrap()
433 }
434
435 #[test]
438 fn dispatch_key_priority_ordering() {
439 assert!(DispatchKey::Tracer.priority() > DispatchKey::Autograd.priority());
440 assert!(DispatchKey::Autograd.priority() > DispatchKey::Autocast.priority());
441 assert!(DispatchKey::Autocast.priority() > DispatchKey::Cpu.priority());
442 assert!(DispatchKey::Cuda.priority() > DispatchKey::Cpu.priority());
443 }
444
445 #[test]
446 fn dispatch_key_all_contains_every_key() {
447 assert_eq!(DispatchKey::ALL.len(), 11);
448 for k in &DispatchKey::ALL {
450 let count = DispatchKey::ALL.iter().filter(|&other| other == k).count();
451 assert_eq!(count, 1, "duplicate key {k:?}");
452 }
453 }
454
455 #[test]
458 fn dispatch_key_set_empty() {
459 let set = DispatchKeySet::empty();
460 assert!(set.is_empty());
461 assert_eq!(set.len(), 0);
462 assert_eq!(set.highest(), None);
463 assert!(!set.contains(DispatchKey::Cpu));
464 }
465
466 #[test]
467 fn dispatch_key_set_insert_and_contains() {
468 let set = DispatchKeySet::empty()
469 .insert(DispatchKey::Cpu)
470 .insert(DispatchKey::Autograd);
471 assert_eq!(set.len(), 2);
472 assert!(set.contains(DispatchKey::Cpu));
473 assert!(set.contains(DispatchKey::Autograd));
474 assert!(!set.contains(DispatchKey::Cuda));
475 }
476
477 #[test]
478 fn dispatch_key_set_remove() {
479 let set = DispatchKeySet::from([DispatchKey::Cpu, DispatchKey::Autograd]);
480 let without_autograd = set.remove(DispatchKey::Autograd);
481 assert_eq!(without_autograd.len(), 1);
482 assert!(without_autograd.contains(DispatchKey::Cpu));
483 assert!(!without_autograd.contains(DispatchKey::Autograd));
484 }
485
486 #[test]
487 fn dispatch_key_set_highest() {
488 let set = DispatchKeySet::from([
489 DispatchKey::Cpu,
490 DispatchKey::Autograd,
491 DispatchKey::Profiler,
492 ]);
493 assert_eq!(set.highest(), Some(DispatchKey::Profiler));
494 }
495
496 #[test]
497 fn dispatch_key_set_iter_desc_gives_priority_order() {
498 let set = DispatchKeySet::from([
499 DispatchKey::Cpu,
500 DispatchKey::Tracer,
501 DispatchKey::Autograd,
502 DispatchKey::Cuda,
503 ]);
504 let order: Vec<_> = set.iter_desc().collect();
505 assert_eq!(
506 order,
507 vec![
508 DispatchKey::Tracer,
509 DispatchKey::Autograd,
510 DispatchKey::Cuda,
511 DispatchKey::Cpu,
512 ]
513 );
514 }
515
516 #[test]
517 fn dispatch_key_set_union_and_intersection() {
518 let a = DispatchKeySet::from([DispatchKey::Cpu, DispatchKey::Autograd]);
519 let b = DispatchKeySet::from([DispatchKey::Autograd, DispatchKey::Quantized]);
520 let u = a.union(b);
521 assert_eq!(u.len(), 3);
522 assert!(u.contains(DispatchKey::Cpu));
523 assert!(u.contains(DispatchKey::Autograd));
524 assert!(u.contains(DispatchKey::Quantized));
525
526 let i = a.intersection(b);
527 assert_eq!(i.len(), 1);
528 assert!(i.contains(DispatchKey::Autograd));
529 }
530
531 #[test]
532 fn dispatch_key_set_all_contains_every_key() {
533 let set = DispatchKeySet::all();
534 assert_eq!(set.len(), 11);
535 for &k in &DispatchKey::ALL {
536 assert!(set.contains(k));
537 }
538 }
539
540 #[test]
541 fn dispatch_key_set_from_array_literal() {
542 let set = DispatchKeySet::from([DispatchKey::Cpu, DispatchKey::Cuda]);
543 assert_eq!(set.len(), 2);
544 }
545
546 #[test]
549 fn dispatcher_register_and_has_kernel() {
550 let mut d = Dispatcher::<f32>::new();
551 assert_eq!(d.kernel_count(), 0);
552 assert!(!d.has_kernel("add", DispatchKey::Cpu));
553
554 d.register(
555 "add",
556 DispatchKey::Cpu,
557 |inputs, _, _| Ok(inputs[0].clone()),
558 );
559 assert_eq!(d.kernel_count(), 1);
560 assert!(d.has_kernel("add", DispatchKey::Cpu));
561 assert!(!d.has_kernel("add", DispatchKey::Cuda));
562 assert!(!d.has_kernel("sub", DispatchKey::Cpu));
563 }
564
565 #[test]
566 fn dispatcher_call_empty_keyset_errors() {
567 let d = Dispatcher::<f32>::new();
568 let t = make_tensor(vec![1.0], vec![1]);
569 let result = d.call("add", &[t], DispatchKeySet::empty());
570 assert!(result.is_err());
571 assert!(format!("{}", result.unwrap_err()).contains("empty keyset"));
572 }
573
574 #[test]
575 fn dispatcher_call_no_kernel_errors() {
576 let d = Dispatcher::<f32>::new();
577 let t = make_tensor(vec![1.0], vec![1]);
578 let keyset = DispatchKeySet::from([DispatchKey::Cpu]);
579 let result = d.call("add", &[t], keyset);
580 assert!(result.is_err());
581 assert!(format!("{}", result.unwrap_err()).contains("no kernel registered"));
582 }
583
584 #[test]
585 fn dispatcher_call_picks_highest_priority_key() {
586 use std::sync::Arc;
587 use std::sync::atomic::{AtomicUsize, Ordering};
588
589 let cpu_count = Arc::new(AtomicUsize::new(0));
591 let autograd_count = Arc::new(AtomicUsize::new(0));
592
593 let mut d = Dispatcher::<f32>::new();
594 let cpu_c = Arc::clone(&cpu_count);
595 d.register("add", DispatchKey::Cpu, move |inputs, _, _| {
596 cpu_c.fetch_add(1, Ordering::Relaxed);
597 Ok(inputs[0].clone())
598 });
599 let ag_c = Arc::clone(&autograd_count);
600 d.register("add", DispatchKey::Autograd, move |inputs, _, _| {
601 ag_c.fetch_add(1, Ordering::Relaxed);
602 Ok(inputs[0].clone())
603 });
604
605 let t = make_tensor(vec![1.0], vec![1]);
606 let keyset = DispatchKeySet::from([DispatchKey::Cpu, DispatchKey::Autograd]);
607 d.call("add", &[t], keyset).unwrap();
608
609 assert_eq!(autograd_count.load(Ordering::Relaxed), 1);
611 assert_eq!(cpu_count.load(Ordering::Relaxed), 0);
612 }
613
614 #[test]
615 fn dispatcher_redispatch_chains_through_keys() {
616 use std::sync::Arc;
618 use std::sync::atomic::{AtomicUsize, Ordering};
619
620 let cpu_count = Arc::new(AtomicUsize::new(0));
621 let autograd_count = Arc::new(AtomicUsize::new(0));
622
623 let mut d = Dispatcher::<f32>::new();
624 let cpu_c = Arc::clone(&cpu_count);
625 d.register("add", DispatchKey::Cpu, move |inputs, _, _| {
626 cpu_c.fetch_add(1, Ordering::Relaxed);
627 Ok(inputs[0].clone())
628 });
629 let ag_c = Arc::clone(&autograd_count);
630 d.register("add", DispatchKey::Autograd, move |inputs, keyset, disp| {
631 ag_c.fetch_add(1, Ordering::Relaxed);
632 let rest = keyset.remove(DispatchKey::Autograd);
634 disp.call("add", inputs, rest)
635 });
636
637 let t = make_tensor(vec![1.0], vec![1]);
638 let keyset = DispatchKeySet::from([DispatchKey::Cpu, DispatchKey::Autograd]);
639 d.call("add", &[t], keyset).unwrap();
640
641 assert_eq!(autograd_count.load(Ordering::Relaxed), 1);
642 assert_eq!(cpu_count.load(Ordering::Relaxed), 1);
643 }
644
645 #[test]
646 fn dispatcher_skips_keys_without_kernel() {
647 let mut d = Dispatcher::<f32>::new();
651 d.register(
652 "add",
653 DispatchKey::Cpu,
654 |inputs, _, _| Ok(inputs[0].clone()),
655 );
656
657 let t = make_tensor(vec![1.0, 2.0], vec![2]);
658 let keyset = DispatchKeySet::from([DispatchKey::Autograd, DispatchKey::Cpu]);
659 let result = d.call("add", &[t], keyset).unwrap();
660 assert_eq!(result.shape(), &[2]);
661 }
662
663 #[test]
664 fn dispatcher_call_direct_bypasses_priority() {
665 use std::sync::Arc;
666 use std::sync::atomic::{AtomicUsize, Ordering};
667
668 let cpu_count = Arc::new(AtomicUsize::new(0));
669 let cuda_count = Arc::new(AtomicUsize::new(0));
670
671 let mut d = Dispatcher::<f32>::new();
672 let cpu_c = Arc::clone(&cpu_count);
673 d.register("add", DispatchKey::Cpu, move |inputs, _, _| {
674 cpu_c.fetch_add(1, Ordering::Relaxed);
675 Ok(inputs[0].clone())
676 });
677 let cuda_c = Arc::clone(&cuda_count);
678 d.register("add", DispatchKey::Cuda, move |inputs, _, _| {
679 cuda_c.fetch_add(1, Ordering::Relaxed);
680 Ok(inputs[0].clone())
681 });
682
683 let t = make_tensor(vec![1.0], vec![1]);
685 let keyset = DispatchKeySet::from([DispatchKey::Cpu, DispatchKey::Cuda]);
686 d.call("add", std::slice::from_ref(&t), keyset).unwrap();
687 assert_eq!(cuda_count.load(Ordering::Relaxed), 1);
688 assert_eq!(cpu_count.load(Ordering::Relaxed), 0);
689
690 d.call_direct("add", &[t], keyset, DispatchKey::Cpu)
692 .unwrap();
693 assert_eq!(cpu_count.load(Ordering::Relaxed), 1);
694 assert_eq!(cuda_count.load(Ordering::Relaxed), 1);
695 }
696
697 #[test]
698 fn dispatcher_call_direct_missing_kernel_errors() {
699 let d = Dispatcher::<f32>::new();
700 let t = make_tensor(vec![1.0], vec![1]);
701 let keyset = DispatchKeySet::from([DispatchKey::Cpu]);
702 let result = d.call_direct("add", &[t], keyset, DispatchKey::Cpu);
703 assert!(result.is_err());
704 }
705
706 #[test]
707 fn dispatcher_full_three_layer_stack() {
708 use std::sync::Arc;
713 use std::sync::Mutex;
714
715 let log: Arc<Mutex<Vec<&'static str>>> = Arc::new(Mutex::new(Vec::new()));
716
717 let mut d = Dispatcher::<f32>::new();
718
719 let log_c = Arc::clone(&log);
720 d.register("add", DispatchKey::Cpu, move |inputs, _, _| {
721 log_c.lock().unwrap().push("cpu");
722 Ok(inputs[0].clone())
723 });
724
725 let log_a = Arc::clone(&log);
726 d.register("add", DispatchKey::Autograd, move |inputs, keyset, disp| {
727 log_a.lock().unwrap().push("autograd");
728 let rest = keyset.remove(DispatchKey::Autograd);
729 disp.call("add", inputs, rest)
730 });
731
732 let log_t = Arc::clone(&log);
733 d.register("add", DispatchKey::Tracer, move |inputs, keyset, disp| {
734 log_t.lock().unwrap().push("tracer");
735 let rest = keyset.remove(DispatchKey::Tracer);
736 disp.call("add", inputs, rest)
737 });
738
739 let t = make_tensor(vec![1.0, 2.0], vec![2]);
740 let keyset =
741 DispatchKeySet::from([DispatchKey::Tracer, DispatchKey::Autograd, DispatchKey::Cpu]);
742 d.call("add", &[t], keyset).unwrap();
743
744 let final_log = log.lock().unwrap();
745 assert_eq!(*final_log, vec!["tracer", "autograd", "cpu"]);
746 }
747}