1use crate::ops::binary::{AddOp, BinaryOp, DivOp, MulOp, PReLUOp, PowOp, SubOp};
2#[cfg(feature = "gpu")]
3use crate::ops::hybrid_scheduler::{HybridWorkScheduler, WorkItem, WorkType};
4
5#[cfg(feature = "gpu")]
7pub use crate::ops::hybrid_scheduler::WorkPriority;
8#[cfg(test)]
9use crate::tensor::TensorStorage;
10use crate::{Device, Result, Tensor, TensorError};
12
13#[cfg(not(feature = "gpu"))]
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
15pub enum WorkPriority {
16 Low = 0,
17 Normal = 1,
18 High = 2,
19 Critical = 3,
20}
21
22use crate::device::async_execution::AsyncExecutor;
23use std::future::Future;
24use std::pin::Pin;
25use std::sync::Arc;
26use std::task::{Context, Poll};
27
28#[cfg(feature = "gpu")]
29use crate::gpu::multi_stream_executor::MultiStreamGpuExecutor;
30
31pub struct AsyncBinaryOpFuture<T> {
33 inner: Pin<Box<dyn Future<Output = Result<Tensor<T>>> + Send>>,
34}
35
36impl<T> Future for AsyncBinaryOpFuture<T> {
37 type Output = Result<Tensor<T>>;
38
39 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
40 self.inner.as_mut().poll(cx)
41 }
42}
43
44pub struct AsyncBinaryOperationExecutor {
46 #[cfg(feature = "gpu")]
47 hybrid_scheduler: Option<Arc<HybridWorkScheduler>>,
48 #[cfg(not(feature = "gpu"))]
49 cpu_executor: Arc<AsyncExecutor>,
50}
51
52impl AsyncBinaryOperationExecutor {
53 #[cfg(feature = "gpu")]
62 pub fn new(device_id: usize) -> Result<Self> {
63 match crate::device::context::get_gpu_context(device_id) {
64 Ok(gpu_ctx) => {
65 let cpu_executor = Arc::new(AsyncExecutor::new(Device::Cpu));
67
68 let gpu_executor = Arc::new(MultiStreamGpuExecutor::new(
70 gpu_ctx.device.clone(),
71 gpu_ctx.queue.clone(),
72 ));
73
74 let hybrid_scheduler =
76 Arc::new(HybridWorkScheduler::new(cpu_executor, gpu_executor));
77
78 Ok(Self {
79 hybrid_scheduler: Some(hybrid_scheduler),
80 })
81 }
82 Err(_) => Ok(Self {
84 hybrid_scheduler: None,
85 }),
86 }
87 }
88
89 #[cfg(not(feature = "gpu"))]
91 pub fn new(_device_id: usize) -> Result<Self> {
92 let cpu_executor = Arc::new(AsyncExecutor::new(Device::Cpu));
93 Ok(Self { cpu_executor })
94 }
95
96 pub fn execute_async<T, Op>(
98 &self,
99 a: &Tensor<T>,
100 b: &Tensor<T>,
101 op: Op,
102 ) -> AsyncBinaryOpFuture<T>
103 where
104 T: Clone
105 + Default
106 + scirs2_core::num_traits::Zero
107 + Send
108 + Sync
109 + 'static
110 + bytemuck::Pod
111 + bytemuck::Zeroable,
112 Op: BinaryOp<T> + Send + Sync + 'static,
113 {
114 #[cfg(feature = "gpu")]
115 {
116 if let Some(ref scheduler) = self.hybrid_scheduler {
117 let input_size = a.shape().elements() + b.shape().elements();
119 let dtype = std::any::type_name::<T>();
120
121 let work_item = scheduler.create_binary_op_work(
122 op.name(),
123 input_size,
124 dtype,
125 crate::ops::hybrid_scheduler::WorkPriority::Normal,
126 );
127
128 let a_clone = a.clone();
130 let b_clone = b.clone();
131 let scheduler_clone = Arc::clone(scheduler);
132
133 let future = async move {
134 let work_future = scheduler_clone.submit_work(work_item);
136
137 let result = std::thread::spawn(move || {
139 crate::ops::binary::binary_op(&a_clone, &b_clone, op)
140 })
141 .join();
142
143 work_future.await?;
145
146 result.map_err(|e| {
148 TensorError::compute_error_simple(format!(
149 "Async execution failed: {:?}",
150 e
151 ))
152 })?
153 };
154
155 return AsyncBinaryOpFuture {
156 inner: Box::pin(future),
157 };
158 }
159 }
160
161 let a_clone = a.clone();
163 let b_clone = b.clone();
164 let future = async move {
165 std::thread::spawn(move || crate::ops::binary::binary_op(&a_clone, &b_clone, op))
166 .join()
167 .map_err(|e| {
168 TensorError::compute_error_simple(format!("Async execution failed: {e:?}"))
169 })?
170 };
171
172 AsyncBinaryOpFuture {
173 inner: Box::pin(future),
174 }
175 }
176
177 pub fn execute_async_with_priority<T, Op>(
179 &self,
180 a: &Tensor<T>,
181 b: &Tensor<T>,
182 op: Op,
183 _priority: WorkPriority,
184 ) -> AsyncBinaryOpFuture<T>
185 where
186 T: Clone
187 + Default
188 + scirs2_core::num_traits::Zero
189 + Send
190 + Sync
191 + 'static
192 + bytemuck::Pod
193 + bytemuck::Zeroable,
194 Op: BinaryOp<T> + Send + Sync + 'static,
195 {
196 let _input_size = a.shape().elements() + b.shape().elements();
198 let _dtype = std::any::type_name::<T>();
199
200 #[cfg(feature = "gpu")]
201 {
202 if let Some(ref scheduler) = self.hybrid_scheduler {
203 let work_item =
204 scheduler.create_binary_op_work(op.name(), _input_size, _dtype, _priority);
205
206 let a_clone = a.clone();
208 let b_clone = b.clone();
209 let scheduler_clone = Arc::clone(scheduler);
210
211 let future = async move {
212 let work_future = scheduler_clone.submit_work(work_item);
214
215 let result = std::thread::spawn(move || {
217 crate::ops::binary::binary_op(&a_clone, &b_clone, op)
218 })
219 .join();
220
221 work_future.await?;
223
224 result.map_err(|e| {
226 TensorError::compute_error_simple(format!(
227 "Async execution failed: {:?}",
228 e
229 ))
230 })?
231 };
232
233 return AsyncBinaryOpFuture {
234 inner: Box::pin(future),
235 };
236 }
237 }
238
239 let a_clone = a.clone();
241 let b_clone = b.clone();
242 let future = async move {
243 std::thread::spawn(move || crate::ops::binary::binary_op(&a_clone, &b_clone, op))
244 .join()
245 .map_err(|e| {
246 TensorError::compute_error_simple(format!("Async execution failed: {e:?}"))
247 })?
248 };
249
250 AsyncBinaryOpFuture {
251 inner: Box::pin(future),
252 }
253 }
254
255 pub async fn execute_batch_async<T, Op>(
257 &self,
258 operations: Vec<(&Tensor<T>, &Tensor<T>, Op)>,
259 ) -> Result<Vec<Tensor<T>>>
260 where
261 T: Clone
262 + Default
263 + scirs2_core::num_traits::Zero
264 + Send
265 + Sync
266 + 'static
267 + bytemuck::Pod
268 + bytemuck::Zeroable,
269 Op: BinaryOp<T> + Send + Sync + Clone + 'static,
270 {
271 let mut futures = Vec::new();
272
273 for (a, b, op) in operations {
274 let future = self.execute_async(a, b, op);
275 futures.push(future);
276 }
277
278 let mut results = Vec::new();
280 for future in futures {
281 results.push(future.await?);
282 }
283
284 Ok(results)
285 }
286
287 pub fn synchronize(&self) {
289 #[cfg(feature = "gpu")]
290 {
291 if let Some(ref scheduler) = self.hybrid_scheduler {
292 scheduler.synchronize_all();
293 }
294 }
295 }
296
297 pub fn is_idle(&self) -> bool {
299 #[cfg(feature = "gpu")]
300 {
301 if let Some(ref scheduler) = self.hybrid_scheduler {
302 return scheduler.is_idle();
303 }
304 }
305
306 #[cfg(not(feature = "gpu"))]
307 {
308 self.cpu_executor.queue_length() == 0
309 }
310
311 #[cfg(feature = "gpu")]
312 true
313 }
314}
315
316static GLOBAL_ASYNC_EXECUTOR: std::sync::OnceLock<AsyncBinaryOperationExecutor> =
318 std::sync::OnceLock::new();
319
320pub fn global_async_executor() -> &'static AsyncBinaryOperationExecutor {
322 GLOBAL_ASYNC_EXECUTOR.get_or_init(|| {
323 AsyncBinaryOperationExecutor::new(0).unwrap_or_else(|_| {
324 #[cfg(not(feature = "gpu"))]
326 {
327 AsyncBinaryOperationExecutor::new(0)
328 .expect("CPU-only async binary executor initialization must succeed")
329 }
330 #[cfg(feature = "gpu")]
331 {
332 AsyncBinaryOperationExecutor {
334 hybrid_scheduler: None,
335 }
336 }
337 })
338 })
339}
340
341pub async fn add_async<T>(a: &Tensor<T>, b: &Tensor<T>) -> Result<Tensor<T>>
344where
345 T: Clone
346 + Default
347 + scirs2_core::num_traits::Zero
348 + std::ops::Add<Output = T>
349 + Send
350 + Sync
351 + 'static
352 + bytemuck::Pod
353 + bytemuck::Zeroable,
354{
355 global_async_executor().execute_async(a, b, AddOp).await
356}
357
358pub async fn sub_async<T>(a: &Tensor<T>, b: &Tensor<T>) -> Result<Tensor<T>>
360where
361 T: Clone
362 + Default
363 + scirs2_core::num_traits::Zero
364 + std::ops::Sub<Output = T>
365 + Send
366 + Sync
367 + 'static
368 + bytemuck::Pod
369 + bytemuck::Zeroable,
370{
371 global_async_executor().execute_async(a, b, SubOp).await
372}
373
374pub async fn mul_async<T>(a: &Tensor<T>, b: &Tensor<T>) -> Result<Tensor<T>>
376where
377 T: Clone
378 + Default
379 + scirs2_core::num_traits::Zero
380 + std::ops::Mul<Output = T>
381 + Send
382 + Sync
383 + 'static
384 + bytemuck::Pod
385 + bytemuck::Zeroable,
386{
387 global_async_executor().execute_async(a, b, MulOp).await
388}
389
390pub async fn div_async<T>(a: &Tensor<T>, b: &Tensor<T>) -> Result<Tensor<T>>
392where
393 T: Clone
394 + Default
395 + scirs2_core::num_traits::Zero
396 + std::ops::Div<Output = T>
397 + Send
398 + Sync
399 + 'static
400 + bytemuck::Pod
401 + bytemuck::Zeroable,
402{
403 global_async_executor().execute_async(a, b, DivOp).await
404}
405
406pub async fn pow_async<T>(a: &Tensor<T>, b: &Tensor<T>) -> Result<Tensor<T>>
408where
409 T: Clone
410 + Default
411 + scirs2_core::num_traits::Zero
412 + scirs2_core::num_traits::Float
413 + Send
414 + Sync
415 + 'static
416 + bytemuck::Pod
417 + bytemuck::Zeroable,
418{
419 global_async_executor().execute_async(a, b, PowOp).await
420}
421
422pub async fn prelu_async<T>(a: &Tensor<T>, b: &Tensor<T>) -> Result<Tensor<T>>
424where
425 T: Clone
426 + Default
427 + scirs2_core::num_traits::Zero
428 + scirs2_core::num_traits::Float
429 + PartialOrd
430 + std::ops::Mul<Output = T>
431 + Send
432 + Sync
433 + 'static
434 + bytemuck::Pod
435 + bytemuck::Zeroable,
436{
437 global_async_executor().execute_async(a, b, PReLUOp).await
438}
439
440pub async fn add_async_priority<T>(
442 a: &Tensor<T>,
443 b: &Tensor<T>,
444 priority: WorkPriority,
445) -> Result<Tensor<T>>
446where
447 T: Clone
448 + Default
449 + scirs2_core::num_traits::Zero
450 + std::ops::Add<Output = T>
451 + Send
452 + Sync
453 + 'static
454 + bytemuck::Pod
455 + bytemuck::Zeroable,
456{
457 global_async_executor()
458 .execute_async_with_priority(a, b, AddOp, priority)
459 .await
460}
461
462pub async fn mul_async_priority<T>(
464 a: &Tensor<T>,
465 b: &Tensor<T>,
466 priority: WorkPriority,
467) -> Result<Tensor<T>>
468where
469 T: Clone
470 + Default
471 + scirs2_core::num_traits::Zero
472 + std::ops::Mul<Output = T>
473 + Send
474 + Sync
475 + 'static
476 + bytemuck::Pod
477 + bytemuck::Zeroable,
478{
479 global_async_executor()
480 .execute_async_with_priority(a, b, MulOp, priority)
481 .await
482}
483
484pub async fn batch_add_async<T>(operations: Vec<(&Tensor<T>, &Tensor<T>)>) -> Result<Vec<Tensor<T>>>
486where
487 T: Clone
488 + Default
489 + scirs2_core::num_traits::Zero
490 + std::ops::Add<Output = T>
491 + Send
492 + Sync
493 + 'static
494 + bytemuck::Pod
495 + bytemuck::Zeroable,
496{
497 let ops: Vec<_> = operations.into_iter().map(|(a, b)| (a, b, AddOp)).collect();
498 global_async_executor().execute_batch_async(ops).await
499}
500
501pub async fn batch_mul_async<T>(operations: Vec<(&Tensor<T>, &Tensor<T>)>) -> Result<Vec<Tensor<T>>>
503where
504 T: Clone
505 + Default
506 + scirs2_core::num_traits::Zero
507 + std::ops::Mul<Output = T>
508 + Send
509 + Sync
510 + 'static
511 + bytemuck::Pod
512 + bytemuck::Zeroable,
513{
514 let ops: Vec<_> = operations.into_iter().map(|(a, b)| (a, b, MulOp)).collect();
515 global_async_executor().execute_batch_async(ops).await
516}
517
518pub fn synchronize_async_operations() {
520 global_async_executor().synchronize();
521}
522
523pub fn is_async_operations_idle() -> bool {
525 global_async_executor().is_idle()
526}
527
528#[cfg(test)]
529#[allow(irrefutable_let_patterns)] mod tests {
531 use super::*;
532 use crate::Tensor;
533
534 #[test]
535 fn test_async_executor_creation() {
536 let executor = AsyncBinaryOperationExecutor::new(0).expect("test: new should succeed");
537
538 assert!(executor.is_idle());
540 }
541
542 #[test]
543 fn test_sync_fallback() {
544 let a = Tensor::<f32>::from_vec(vec![1.0, 2.0, 3.0], &[3])
545 .expect("test: from_vec should succeed");
546 let b = Tensor::<f32>::from_vec(vec![4.0, 5.0, 6.0], &[3])
547 .expect("test: from_vec should succeed");
548
549 let result = crate::ops::add(&a, &b).expect("test: add should succeed");
551 let expected = vec![5.0, 7.0, 9.0];
552
553 if let TensorStorage::Cpu(arr) = &result.storage {
554 assert_eq!(
555 arr.as_slice().expect("tensor should be contiguous"),
556 &expected
557 );
558 }
559 }
560
561 #[test]
562 fn test_work_priority_ordering() {
563 let high = self::WorkPriority::High;
564 let normal = self::WorkPriority::Normal;
565 let low = self::WorkPriority::Low;
566
567 assert!(high > normal);
568 assert!(normal > low);
569 }
570}