1use crate::{Device, Result, TensorError};
2use std::collections::VecDeque;
3use std::future::Future;
4use std::pin::Pin;
5use std::sync::{Arc, Mutex};
6use std::task::{Context, Poll, Waker};
7
8pub struct AsyncOperation {
10 id: u64,
11 device: Device,
12 state: Arc<Mutex<OperationState>>,
13}
14
15#[derive(Debug)]
17enum OperationState {
18 Pending,
19 Running,
20 Completed,
21 Failed(String),
22}
23
24impl AsyncOperation {
25 pub fn new(id: u64, device: Device) -> Self {
26 Self {
27 id,
28 device,
29 state: Arc::new(Mutex::new(OperationState::Pending)),
30 }
31 }
32
33 pub fn id(&self) -> u64 {
35 self.id
36 }
37
38 pub fn device(&self) -> Device {
40 self.device
41 }
42
43 pub fn is_complete(&self) -> bool {
45 matches!(
46 *self
47 .state
48 .lock()
49 .unwrap_or_else(|poisoned| poisoned.into_inner()),
50 OperationState::Completed
51 )
52 }
53
54 pub fn is_failed(&self) -> bool {
56 matches!(
57 *self
58 .state
59 .lock()
60 .unwrap_or_else(|poisoned| poisoned.into_inner()),
61 OperationState::Failed(_)
62 )
63 }
64
65 pub async fn wait(&self) -> Result<()> {
67 AsyncWaitFuture::new(Arc::clone(&self.state)).await
68 }
69
70 pub(crate) fn complete(&self) {
72 let mut state = self
73 .state
74 .lock()
75 .unwrap_or_else(|poisoned| poisoned.into_inner());
76 if matches!(*state, OperationState::Running) {
77 *state = OperationState::Completed;
78 }
79 }
80
81 pub(crate) fn fail(&self, error: String) {
83 let mut state = self
84 .state
85 .lock()
86 .unwrap_or_else(|poisoned| poisoned.into_inner());
87 if matches!(*state, OperationState::Running) {
88 *state = OperationState::Failed(error);
89 }
90 }
91
92 pub(crate) fn start(&self) {
94 let mut state = self
95 .state
96 .lock()
97 .unwrap_or_else(|poisoned| poisoned.into_inner());
98 if matches!(*state, OperationState::Pending) {
99 *state = OperationState::Running;
100 }
101 }
102}
103
104struct AsyncWaitFuture {
106 state: Arc<Mutex<OperationState>>,
107 waker: Option<Waker>,
108}
109
110impl AsyncWaitFuture {
111 fn new(state: Arc<Mutex<OperationState>>) -> Self {
112 Self { state, waker: None }
113 }
114}
115
116impl Future for AsyncWaitFuture {
117 type Output = Result<()>;
118
119 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
120 let state = self
121 .state
122 .lock()
123 .unwrap_or_else(|poisoned| poisoned.into_inner());
124 match *state {
125 OperationState::Completed => Poll::Ready(Ok(())),
126 OperationState::Failed(ref err) => {
127 Poll::Ready(Err(TensorError::compute_error_simple(err.clone())))
128 }
129 OperationState::Pending | OperationState::Running => {
130 drop(state); self.waker = Some(cx.waker().clone());
132 Poll::Pending
133 }
134 }
135 }
136}
137
138pub struct AsyncExecutor {
140 device: Device,
141 queue: Arc<Mutex<VecDeque<PendingOperation>>>,
142 high_priority_queue: Arc<Mutex<VecDeque<PendingOperation>>>,
143 next_id: Arc<Mutex<u64>>,
144 batch_size: usize,
145 is_processing: Arc<Mutex<bool>>,
146 stats: Arc<Mutex<ExecutorStats>>,
147}
148
149#[derive(Debug, Default, Clone)]
151pub struct ExecutorStats {
152 pub operations_completed: u64,
153 pub operations_failed: u64,
154 pub total_execution_time_us: u64,
155 pub average_queue_length: f64,
156 pub batch_efficiency: f64,
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161pub enum Priority {
162 Low,
163 Normal,
164 High,
165 Critical,
166}
167
168#[allow(dead_code)]
170struct PendingOperation {
171 operation: Box<dyn AsyncKernel>,
172 handle: AsyncOperation,
173 priority: Priority,
174 submitted_at: std::time::Instant,
175}
176
177pub trait AsyncKernel: Send + Sync {
179 fn execute(&self) -> Pin<Box<dyn Future<Output = Result<()>> + Send>>;
181
182 fn name(&self) -> &str;
184
185 fn estimated_time_us(&self) -> u64 {
187 1000 }
189
190 fn can_batch_with(&self, _other: &dyn AsyncKernel) -> bool {
192 false }
194
195 fn priority(&self) -> Priority {
197 Priority::Normal
198 }
199
200 fn memory_requirements(&self) -> usize {
202 0 }
204
205 fn supports_concurrency(&self) -> bool {
207 false }
209}
210
211impl AsyncExecutor {
212 pub fn new(device: Device) -> Self {
214 Self::with_batch_size(device, 8) }
216
217 pub fn with_batch_size(device: Device, batch_size: usize) -> Self {
219 Self {
220 device,
221 queue: Arc::new(Mutex::new(VecDeque::new())),
222 high_priority_queue: Arc::new(Mutex::new(VecDeque::new())),
223 next_id: Arc::new(Mutex::new(0)),
224 batch_size,
225 is_processing: Arc::new(Mutex::new(false)),
226 stats: Arc::new(Mutex::new(ExecutorStats::default())),
227 }
228 }
229
230 pub fn submit(&self, kernel: Box<dyn AsyncKernel>) -> AsyncOperation {
232 self.submit_with_priority(kernel, Priority::Normal)
233 }
234
235 pub fn submit_with_priority(
237 &self,
238 kernel: Box<dyn AsyncKernel>,
239 priority: Priority,
240 ) -> AsyncOperation {
241 let id = {
242 let mut next_id = self
243 .next_id
244 .lock()
245 .unwrap_or_else(|poisoned| poisoned.into_inner());
246 let id = *next_id;
247 *next_id += 1;
248 id
249 };
250
251 let handle = AsyncOperation::new(id, self.device);
252 let pending = PendingOperation {
253 operation: kernel,
254 handle: AsyncOperation::new(id, self.device),
255 priority,
256 submitted_at: std::time::Instant::now(),
257 };
258
259 match priority {
261 Priority::Critical | Priority::High => {
262 let mut queue = self
263 .high_priority_queue
264 .lock()
265 .unwrap_or_else(|poisoned| poisoned.into_inner());
266 queue.push_back(pending);
267 }
268 Priority::Normal | Priority::Low => {
269 let mut queue = self
270 .queue
271 .lock()
272 .unwrap_or_else(|poisoned| poisoned.into_inner());
273 queue.push_back(pending);
274 }
275 }
276
277 handle
278 }
279
280 pub async fn process_queue(&self) -> Result<()> {
282 {
284 let mut is_processing = self.is_processing.lock().map_err(|_| {
285 TensorError::invalid_operation_simple("is_processing lock poisoned".to_string())
286 })?;
287 if *is_processing {
288 return Ok(());
289 }
290 *is_processing = true;
291 }
292
293 let result = self.process_queue_internal().await;
294
295 {
297 let mut is_processing = self.is_processing.lock().map_err(|_| {
298 TensorError::invalid_operation_simple("is_processing lock poisoned".to_string())
299 })?;
300 *is_processing = false;
301 }
302
303 result
304 }
305
306 async fn process_queue_internal(&self) -> Result<()> {
308 if let Some(batch) = self.get_next_batch(true) {
310 self.execute_batch(batch).await?;
311 } else if let Some(batch) = self.get_next_batch(false) {
312 self.execute_batch(batch).await?;
313 }
314
315 Ok(())
316 }
317
318 fn get_next_batch(&self, high_priority: bool) -> Option<Vec<PendingOperation>> {
320 let queue = if high_priority {
321 &self.high_priority_queue
322 } else {
323 &self.queue
324 };
325
326 let mut queue_guard = queue
327 .lock()
328 .unwrap_or_else(|poisoned| poisoned.into_inner());
329 if queue_guard.is_empty() {
330 return None;
331 }
332
333 let mut batch = Vec::new();
334 let first_op = queue_guard
335 .pop_front()
336 .expect("queue should not be empty after is_empty check");
337 batch.push(first_op);
338
339 while batch.len() < self.batch_size && !queue_guard.is_empty() {
341 let next_op = queue_guard
342 .front()
343 .expect("queue should not be empty in while loop");
344
345 if batch[0]
347 .operation
348 .can_batch_with(next_op.operation.as_ref())
349 {
350 batch.push(
351 queue_guard
352 .pop_front()
353 .expect("queue should have front element we just checked"),
354 );
355 } else {
356 break;
357 }
358 }
359
360 Some(batch)
361 }
362
363 async fn execute_batch(&self, batch: Vec<PendingOperation>) -> Result<()> {
365 let start_time = std::time::Instant::now();
366 let batch_size = batch.len();
367
368 for op in &batch {
370 op.handle.start();
371 }
372
373 let concurrent_ops: Vec<_> = batch
375 .iter()
376 .filter(|op| op.operation.supports_concurrency())
377 .collect();
378
379 let sequential_ops: Vec<_> = batch
380 .iter()
381 .filter(|op| !op.operation.supports_concurrency())
382 .collect();
383
384 if !concurrent_ops.is_empty() {
386 let futures: Vec<_> = concurrent_ops
387 .iter()
388 .map(|op| op.operation.execute())
389 .collect();
390
391 for (i, future) in futures.into_iter().enumerate() {
393 match future.await {
394 Ok(()) => concurrent_ops[i].handle.complete(),
395 Err(e) => concurrent_ops[i].handle.fail(e.to_string()),
396 }
397 }
398 }
399
400 for op in sequential_ops {
402 match op.operation.execute().await {
403 Ok(()) => op.handle.complete(),
404 Err(e) => op.handle.fail(e.to_string()),
405 }
406 }
407
408 let execution_time = start_time.elapsed();
410 self.update_stats(batch_size, execution_time, true);
411
412 Ok(())
413 }
414
415 fn update_stats(&self, batch_size: usize, execution_time: std::time::Duration, success: bool) {
417 let mut stats = self
418 .stats
419 .lock()
420 .unwrap_or_else(|poisoned| poisoned.into_inner());
421
422 if success {
423 stats.operations_completed += batch_size as u64;
424 } else {
425 stats.operations_failed += batch_size as u64;
426 }
427
428 stats.total_execution_time_us += execution_time.as_micros() as u64;
429
430 stats.batch_efficiency = batch_size as f64;
432
433 let current_queue_len = self.queue_length() as f64;
435 stats.average_queue_length = (stats.average_queue_length * 0.9) + (current_queue_len * 0.1);
436 }
437
438 pub fn queue_length(&self) -> usize {
440 let normal_len = self
441 .queue
442 .lock()
443 .unwrap_or_else(|poisoned| poisoned.into_inner())
444 .len();
445 let high_len = self
446 .high_priority_queue
447 .lock()
448 .unwrap_or_else(|poisoned| poisoned.into_inner())
449 .len();
450 normal_len + high_len
451 }
452
453 pub fn queue_length_by_priority(&self, high_priority: bool) -> usize {
455 if high_priority {
456 self.high_priority_queue
457 .lock()
458 .unwrap_or_else(|poisoned| poisoned.into_inner())
459 .len()
460 } else {
461 self.queue
462 .lock()
463 .unwrap_or_else(|poisoned| poisoned.into_inner())
464 .len()
465 }
466 }
467
468 pub fn clear_queue(&self) {
470 {
472 let mut queue = self
473 .queue
474 .lock()
475 .unwrap_or_else(|poisoned| poisoned.into_inner());
476 for op in queue.drain(..) {
477 op.handle.fail("Operation cancelled".to_string());
478 }
479 }
480
481 {
483 let mut queue = self
484 .high_priority_queue
485 .lock()
486 .unwrap_or_else(|poisoned| poisoned.into_inner());
487 for op in queue.drain(..) {
488 op.handle.fail("Operation cancelled".to_string());
489 }
490 }
491 }
492
493 pub fn get_stats(&self) -> ExecutorStats {
495 self.stats
496 .lock()
497 .unwrap_or_else(|poisoned| poisoned.into_inner())
498 .clone()
499 }
500
501 pub fn reset_stats(&self) {
503 let mut stats = self
504 .stats
505 .lock()
506 .unwrap_or_else(|poisoned| poisoned.into_inner());
507 *stats = ExecutorStats::default();
508 }
509
510 pub fn device(&self) -> Device {
512 self.device
513 }
514
515 pub fn is_processing(&self) -> bool {
517 *self
518 .is_processing
519 .lock()
520 .unwrap_or_else(|poisoned| poisoned.into_inner())
521 }
522}
523
524pub struct AsyncExecutorManager {
526 executors: Arc<Mutex<std::collections::HashMap<Device, Arc<AsyncExecutor>>>>,
527}
528
529impl AsyncExecutorManager {
530 pub fn new() -> Self {
532 Self {
533 executors: Arc::new(Mutex::new(std::collections::HashMap::new())),
534 }
535 }
536
537 pub fn get_executor(&self, device: Device) -> Arc<AsyncExecutor> {
539 let mut executors = self
540 .executors
541 .lock()
542 .unwrap_or_else(|poisoned| poisoned.into_inner());
543
544 if let Some(executor) = executors.get(&device) {
545 Arc::clone(executor)
546 } else {
547 let executor = Arc::new(AsyncExecutor::new(device));
548 executors.insert(device, Arc::clone(&executor));
549 executor
550 }
551 }
552
553 pub async fn process_all(&self) -> Result<()> {
555 let executors = {
556 let executors = self.executors.lock().map_err(|_| {
557 TensorError::invalid_operation_simple("executors lock poisoned".to_string())
558 })?;
559 executors.values().cloned().collect::<Vec<_>>()
560 };
561
562 for executor in executors {
563 executor.process_queue().await?;
564 }
565
566 Ok(())
567 }
568}
569
570impl Default for AsyncExecutorManager {
571 fn default() -> Self {
572 Self::new()
573 }
574}
575
576lazy_static::lazy_static! {
577 pub static ref ASYNC_EXECUTOR_MANAGER: AsyncExecutorManager = AsyncExecutorManager::new();
579}
580
581pub fn submit_async_operation(device: Device, kernel: Box<dyn AsyncKernel>) -> AsyncOperation {
583 let executor = ASYNC_EXECUTOR_MANAGER.get_executor(device);
584 executor.submit(kernel)
585}
586
587pub mod kernels {
589 use super::*;
590 use std::time::Duration;
591
592 pub struct DummyKernel {
594 name: String,
595 delay_ms: u64,
596 }
597
598 impl DummyKernel {
599 pub fn new(name: String, delay_ms: u64) -> Self {
600 Self { name, delay_ms }
601 }
602 }
603
604 impl AsyncKernel for DummyKernel {
605 fn execute(&self) -> Pin<Box<dyn Future<Output = Result<()>> + Send>> {
606 let delay = Duration::from_millis(self.delay_ms);
607 Box::pin(async move {
608 std::thread::sleep(delay);
610 Ok(())
611 })
612 }
613
614 fn name(&self) -> &str {
615 &self.name
616 }
617
618 fn estimated_time_us(&self) -> u64 {
619 self.delay_ms * 1000
620 }
621 }
622
623 pub struct MatMulKernel {
625 pub m: usize,
626 pub k: usize,
627 pub n: usize,
628 pub batch_size: usize,
629 }
630
631 impl AsyncKernel for MatMulKernel {
632 fn execute(&self) -> Pin<Box<dyn Future<Output = Result<()>> + Send>> {
633 let ops = self.batch_size * self.m * self.k * self.n * 2;
635 let estimated_time_us = (ops / 1_000_000).max(100); Box::pin(async move {
638 std::thread::sleep(Duration::from_micros(estimated_time_us as u64));
640 Ok(())
641 })
642 }
643
644 fn name(&self) -> &str {
645 "matmul"
646 }
647
648 fn estimated_time_us(&self) -> u64 {
649 let ops = self.batch_size * self.m * self.k * self.n * 2;
650 (ops / 1_000_000).max(100) as u64
651 }
652 }
653
654 pub struct ConvKernel {
656 pub batch_size: usize,
657 pub in_channels: usize,
658 pub out_channels: usize,
659 pub input_size: (usize, usize),
660 pub kernel_size: (usize, usize),
661 }
662
663 impl AsyncKernel for ConvKernel {
664 fn execute(&self) -> Pin<Box<dyn Future<Output = Result<()>> + Send>> {
665 let ops = self.batch_size
667 * self.out_channels
668 * self.input_size.0
669 * self.input_size.1
670 * self.in_channels
671 * self.kernel_size.0
672 * self.kernel_size.1
673 * 2;
674 let estimated_time_us = (ops / 10_000_000).max(50); Box::pin(async move {
677 std::thread::sleep(Duration::from_micros(estimated_time_us as u64));
678 Ok(())
679 })
680 }
681
682 fn name(&self) -> &str {
683 "conv2d"
684 }
685
686 fn estimated_time_us(&self) -> u64 {
687 let ops = self.batch_size
688 * self.out_channels
689 * self.input_size.0
690 * self.input_size.1
691 * self.in_channels
692 * self.kernel_size.0
693 * self.kernel_size.1
694 * 2;
695 (ops / 10_000_000).max(50) as u64
696 }
697
698 fn supports_concurrency(&self) -> bool {
699 true }
701 }
702
703 pub struct TensorManipulationKernel {
705 pub operation: String,
706 pub input_size: usize,
707 pub output_size: usize,
708 pub complexity_factor: f64, }
710
711 impl TensorManipulationKernel {
712 pub fn new_gather(input_size: usize, indices_size: usize) -> Self {
713 Self {
714 operation: "gather".to_string(),
715 input_size,
716 output_size: indices_size,
717 complexity_factor: 1.2, }
719 }
720
721 pub fn new_scatter(tensor_size: usize, updates_size: usize) -> Self {
722 Self {
723 operation: "scatter".to_string(),
724 input_size: tensor_size + updates_size,
725 output_size: tensor_size,
726 complexity_factor: 1.5, }
728 }
729
730 pub fn new_roll(size: usize) -> Self {
731 Self {
732 operation: "roll".to_string(),
733 input_size: size,
734 output_size: size,
735 complexity_factor: 1.0, }
737 }
738
739 pub fn new_where(_condition_size: usize, total_size: usize) -> Self {
740 Self {
741 operation: "where".to_string(),
742 input_size: total_size,
743 output_size: total_size,
744 complexity_factor: 0.8, }
746 }
747 }
748
749 impl AsyncKernel for TensorManipulationKernel {
750 fn execute(&self) -> Pin<Box<dyn Future<Output = Result<()>> + Send>> {
751 let base_time =
752 (self.input_size as f64 * self.complexity_factor / 1_000_000.0).max(10.0);
753 let estimated_time_us = base_time as u64;
754
755 Box::pin(async move {
756 std::thread::sleep(Duration::from_micros(estimated_time_us));
758 Ok(())
759 })
760 }
761
762 fn name(&self) -> &str {
763 &self.operation
764 }
765
766 fn estimated_time_us(&self) -> u64 {
767 (self.input_size as f64 * self.complexity_factor / 1_000_000.0).max(10.0) as u64
768 }
769
770 fn can_batch_with(&self, other: &dyn AsyncKernel) -> bool {
771 other.name() == self.name()
773 }
774
775 fn priority(&self) -> Priority {
776 match self.operation.as_str() {
777 "gather" | "scatter" => Priority::High, "where" => Priority::Normal,
779 _ => Priority::Low,
780 }
781 }
782
783 fn supports_concurrency(&self) -> bool {
784 match self.operation.as_str() {
785 "roll" | "where" => true, "gather" | "scatter" => false, _ => false,
788 }
789 }
790
791 fn memory_requirements(&self) -> usize {
792 self.input_size + self.output_size
793 }
794 }
795
796 pub struct ReductionKernel {
798 pub operation: String,
799 pub input_size: usize,
800 pub output_size: usize,
801 pub num_axes: usize,
802 }
803
804 impl ReductionKernel {
805 pub fn new_sum(input_size: usize, output_size: usize, num_axes: usize) -> Self {
806 Self {
807 operation: "sum".to_string(),
808 input_size,
809 output_size,
810 num_axes,
811 }
812 }
813
814 pub fn new_mean(input_size: usize, output_size: usize, num_axes: usize) -> Self {
815 Self {
816 operation: "mean".to_string(),
817 input_size,
818 output_size,
819 num_axes,
820 }
821 }
822
823 pub fn new_max(input_size: usize, output_size: usize, num_axes: usize) -> Self {
824 Self {
825 operation: "max".to_string(),
826 input_size,
827 output_size,
828 num_axes,
829 }
830 }
831 }
832
833 impl AsyncKernel for ReductionKernel {
834 fn execute(&self) -> Pin<Box<dyn Future<Output = Result<()>> + Send>> {
835 let complexity = self.input_size as f64 * (self.num_axes as f64).log2();
837 let estimated_time_us = (complexity / 5_000_000.0).max(5.0) as u64;
838
839 Box::pin(async move {
840 std::thread::sleep(Duration::from_micros(estimated_time_us));
841 Ok(())
842 })
843 }
844
845 fn name(&self) -> &str {
846 &self.operation
847 }
848
849 fn estimated_time_us(&self) -> u64 {
850 let complexity = self.input_size as f64 * (self.num_axes as f64).log2();
851 (complexity / 5_000_000.0).max(5.0) as u64
852 }
853
854 fn can_batch_with(&self, other: &dyn AsyncKernel) -> bool {
855 other.name().starts_with("sum")
857 || other.name().starts_with("mean")
858 || other.name().starts_with("max")
859 }
860
861 fn priority(&self) -> Priority {
862 Priority::Normal
863 }
864
865 fn supports_concurrency(&self) -> bool {
866 true }
868
869 fn memory_requirements(&self) -> usize {
870 self.input_size + self.output_size
871 }
872 }
873}
874
875#[cfg(test)]
876mod tests {
877 use super::kernels::*;
878 use super::*;
879
880 #[test]
881 fn test_async_operation() {
882 let executor = AsyncExecutor::new(Device::Cpu);
885 let kernel = Box::new(DummyKernel::new("test".to_string(), 10));
886
887 let handle = executor.submit(kernel);
888 assert!(!handle.is_complete());
889
890 assert_eq!(handle.id(), 0);
892 assert_eq!(handle.device(), Device::Cpu);
893 }
895
896 #[test]
897 fn test_multiple_operations() {
898 let executor = AsyncExecutor::new(Device::Cpu);
899
900 let handles = (0..5)
902 .map(|i| {
903 let kernel = Box::new(DummyKernel::new(format!("test_{i}"), 5));
904 executor.submit(kernel)
905 })
906 .collect::<Vec<_>>();
907
908 for (i, handle) in handles.iter().enumerate() {
910 assert_eq!(handle.id(), i as u64);
911 }
912
913 assert_eq!(executor.queue_length(), 5);
914 }
915
916 #[test]
917 fn test_matmul_kernel_estimation() {
918 let kernel = MatMulKernel {
919 m: 1024,
920 k: 1024,
921 n: 1024,
922 batch_size: 1,
923 };
924
925 let estimated_time = kernel.estimated_time_us();
926 assert!(estimated_time > 0);
927
928 let large_kernel = MatMulKernel {
930 m: 2048,
931 k: 2048,
932 n: 2048,
933 batch_size: 1,
934 };
935
936 assert!(large_kernel.estimated_time_us() > estimated_time);
937 }
938
939 #[test]
940 fn test_tensor_manipulation_kernels() {
941 let gather_kernel = TensorManipulationKernel::new_gather(10000, 1000);
943 assert_eq!(gather_kernel.name(), "gather");
944 assert_eq!(gather_kernel.priority(), Priority::High);
945 assert!(!gather_kernel.supports_concurrency());
946
947 let scatter_kernel = TensorManipulationKernel::new_scatter(10000, 2000);
949 assert_eq!(scatter_kernel.name(), "scatter");
950 assert_eq!(scatter_kernel.priority(), Priority::High);
951 assert!(!scatter_kernel.supports_concurrency());
952
953 let roll_kernel = TensorManipulationKernel::new_roll(5000);
955 assert_eq!(roll_kernel.name(), "roll");
956 assert_eq!(roll_kernel.priority(), Priority::Low);
957 assert!(roll_kernel.supports_concurrency());
958
959 let where_kernel = TensorManipulationKernel::new_where(1000, 5000);
961 assert_eq!(where_kernel.name(), "where");
962 assert_eq!(where_kernel.priority(), Priority::Normal);
963 assert!(where_kernel.supports_concurrency());
964 }
965
966 #[test]
967 fn test_reduction_kernels() {
968 let sum_kernel = ReductionKernel::new_sum(10000, 100, 2);
970 assert_eq!(sum_kernel.name(), "sum");
971 assert_eq!(sum_kernel.priority(), Priority::Normal);
972 assert!(sum_kernel.supports_concurrency());
973
974 let mean_kernel = ReductionKernel::new_mean(20000, 200, 3);
976 assert_eq!(mean_kernel.name(), "mean");
977 assert!(mean_kernel.estimated_time_us() > 0);
978
979 let max_kernel = ReductionKernel::new_max(15000, 150, 2);
981 assert_eq!(max_kernel.name(), "max");
982
983 assert!(sum_kernel.can_batch_with(&mean_kernel));
985 assert!(mean_kernel.can_batch_with(&max_kernel));
986 }
987
988 #[test]
989 fn test_kernel_batching() {
990 let gather1 = TensorManipulationKernel::new_gather(1000, 100);
991 let gather2 = TensorManipulationKernel::new_gather(2000, 200);
992 let roll = TensorManipulationKernel::new_roll(1000);
993
994 assert!(gather1.can_batch_with(&gather2));
996
997 assert!(!gather1.can_batch_with(&roll));
999 }
1000
1001 #[test]
1002 fn test_conv_kernel_concurrency() {
1003 let conv_kernel = ConvKernel {
1004 batch_size: 8,
1005 in_channels: 64,
1006 out_channels: 128,
1007 input_size: (224, 224),
1008 kernel_size: (3, 3),
1009 };
1010
1011 assert!(conv_kernel.supports_concurrency());
1012 assert_eq!(conv_kernel.name(), "conv2d");
1013 assert!(conv_kernel.estimated_time_us() > 0);
1014 }
1015}