1use std::any::{Any, TypeId};
33use std::collections::HashMap;
34use std::marker::PhantomData;
35use std::sync::{Arc, OnceLock};
36
37use crate::array_protocol::{
38 ArrayFunction, ArrayProtocol, GPUArray, NdarrayWrapper, NotImplemented,
39};
40use crate::error::{CoreError, CoreResult, ErrorContext};
41use crate::gpu::backends::WebGPUContext;
42use crate::gpu::GpuError;
43
44mod sealed {
49 pub trait Sealed {}
50}
51
52pub trait GpuScalar: sealed::Sealed + Clone + Send + Sync + 'static {}
55
56impl sealed::Sealed for f32 {}
57impl GpuScalar for f32 {}
58
59const GPU_THRESHOLD: usize = 4096;
65
66static GPU_AVAILABLE: OnceLock<bool> = OnceLock::new();
68
69static GPU_CONTEXT: OnceLock<Option<Arc<WebGPUContext>>> = OnceLock::new();
71
72pub fn global_context() -> Option<Arc<WebGPUContext>> {
76 GPU_CONTEXT
77 .get_or_init(|| match WebGPUContext::new() {
78 Ok(ctx) => Some(Arc::new(ctx)),
79 Err(_) => None,
80 })
81 .clone()
82}
83
84pub fn is_gpu_available() -> bool {
86 *GPU_AVAILABLE.get_or_init(|| global_context().is_some())
87}
88
89pub struct GpuNdarray<T: GpuScalar> {
98 buffer: Arc<wgpu::Buffer>,
101
102 shape: Vec<usize>,
104
105 strides: Vec<usize>,
107
108 context: Arc<WebGPUContext>,
110
111 _phantom: PhantomData<T>,
112}
113
114impl<T: GpuScalar> std::fmt::Debug for GpuNdarray<T> {
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 f.debug_struct("GpuNdarray")
117 .field("shape", &self.shape)
118 .field("strides", &self.strides)
119 .finish_non_exhaustive()
120 }
121}
122
123impl<T: GpuScalar> Clone for GpuNdarray<T> {
124 fn clone(&self) -> Self {
125 Self {
126 buffer: Arc::clone(&self.buffer),
127 shape: self.shape.clone(),
128 strides: self.strides.clone(),
129 context: Arc::clone(&self.context),
130 _phantom: PhantomData,
131 }
132 }
133}
134
135impl<T: GpuScalar> GpuNdarray<T> {
136 #[must_use]
138 pub fn buffer_arc(&self) -> &Arc<wgpu::Buffer> {
139 &self.buffer
140 }
141
142 #[must_use]
144 fn numel(&self) -> usize {
145 self.shape.iter().product()
146 }
147
148 fn compute_strides(shape: &[usize]) -> Vec<usize> {
150 let mut strides = vec![1usize; shape.len()];
151 for i in (0..shape.len().saturating_sub(1)).rev() {
152 strides[i] = strides[i + 1] * shape[i + 1];
153 }
154 strides
155 }
156}
157
158fn build_pipeline(
166 ctx: &WebGPUContext,
167 wgsl: &str,
168 bgl_entries: &[wgpu::BindGroupLayoutEntry],
169 label: &str,
170) -> Result<(wgpu::ComputePipeline, wgpu::BindGroupLayout), GpuError> {
171 let device = ctx.device();
172 let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
173 label: Some(label),
174 source: wgpu::ShaderSource::Wgsl(wgsl.into()),
175 });
176 let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
177 label: Some(&format!("{label}_bgl")),
178 entries: bgl_entries,
179 });
180 let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
181 label: Some(&format!("{label}_layout")),
182 bind_group_layouts: &[Some(&bgl)],
183 ..Default::default()
184 });
185 let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
186 label: Some(&format!("{label}_pipeline")),
187 layout: Some(&pipeline_layout),
188 module: &shader_module,
189 entry_point: Some("main"),
190 compilation_options: Default::default(),
191 cache: None,
192 });
193 Ok((pipeline, bgl))
194}
195
196fn storage_ro(binding: u32) -> wgpu::BindGroupLayoutEntry {
198 wgpu::BindGroupLayoutEntry {
199 binding,
200 visibility: wgpu::ShaderStages::COMPUTE,
201 ty: wgpu::BindingType::Buffer {
202 ty: wgpu::BufferBindingType::Storage { read_only: true },
203 has_dynamic_offset: false,
204 min_binding_size: None,
205 },
206 count: None,
207 }
208}
209
210fn storage_rw(binding: u32) -> wgpu::BindGroupLayoutEntry {
212 wgpu::BindGroupLayoutEntry {
213 binding,
214 visibility: wgpu::ShaderStages::COMPUTE,
215 ty: wgpu::BindingType::Buffer {
216 ty: wgpu::BufferBindingType::Storage { read_only: false },
217 has_dynamic_offset: false,
218 min_binding_size: None,
219 },
220 count: None,
221 }
222}
223
224fn uniform_buf(binding: u32) -> wgpu::BindGroupLayoutEntry {
226 wgpu::BindGroupLayoutEntry {
227 binding,
228 visibility: wgpu::ShaderStages::COMPUTE,
229 ty: wgpu::BindingType::Buffer {
230 ty: wgpu::BufferBindingType::Uniform,
231 has_dynamic_offset: false,
232 min_binding_size: None,
233 },
234 count: None,
235 }
236}
237
238impl GpuNdarray<f32> {
243 pub fn from_ndarray_data(
247 data: &[f32],
248 shape: Vec<usize>,
249 context: Arc<WebGPUContext>,
250 ) -> Result<Self, GpuError> {
251 use wgpu::util::DeviceExt as _;
252 let bytes: Vec<u8> = data.iter().flat_map(|v| v.to_le_bytes()).collect();
253 let buffer = context
254 .device()
255 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
256 label: Some("GpuNdarray<f32>"),
257 contents: &bytes,
258 usage: wgpu::BufferUsages::STORAGE
259 | wgpu::BufferUsages::COPY_SRC
260 | wgpu::BufferUsages::COPY_DST,
261 });
262 let strides = Self::compute_strides(&shape);
263 Ok(Self {
264 buffer: Arc::new(buffer),
265 shape,
266 strides,
267 context,
268 _phantom: PhantomData,
269 })
270 }
271
272 pub fn to_vec(&self) -> Result<Vec<f32>, GpuError> {
274 let byte_size = (self.numel() * std::mem::size_of::<f32>()) as u64;
275 let staging = self
276 .context
277 .device()
278 .create_buffer(&wgpu::BufferDescriptor {
279 label: Some("GpuNdarray-readback"),
280 size: byte_size,
281 usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
282 mapped_at_creation: false,
283 });
284
285 let mut encoder =
286 self.context
287 .device()
288 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
289 label: Some("GpuNdarray-readback-encoder"),
290 });
291 encoder.copy_buffer_to_buffer(&self.buffer, 0, &staging, 0, byte_size);
292 self.context.queue().submit(Some(encoder.finish()));
293
294 self.context
295 .device()
296 .poll(wgpu::PollType::wait_indefinitely())
297 .map_err(|e| GpuError::Other(format!("poll error: {e:?}")))?;
298
299 let slice = staging.slice(0..byte_size);
300 let (tx, rx) = std::sync::mpsc::channel();
301 slice.map_async(wgpu::MapMode::Read, move |r| {
302 let _ = tx.send(r);
303 });
304
305 self.context
306 .device()
307 .poll(wgpu::PollType::wait_indefinitely())
308 .map_err(|e| GpuError::Other(format!("poll-map error: {e:?}")))?;
309
310 rx.recv()
311 .map_err(|_| GpuError::Other("channel closed".into()))?
312 .map_err(|e| GpuError::Other(format!("map_async failed: {e:?}")))?;
313
314 let mapped = slice.get_mapped_range();
315 let result: Vec<f32> = mapped
316 .chunks_exact(4)
317 .map(|b| f32::from_le_bytes([b[0], b[1], b[2], b[3]]))
318 .collect();
319 drop(mapped);
320 staging.unmap();
321 Ok(result)
322 }
323
324 pub fn to_ndarray(&self) -> Result<ndarray::ArrayD<f32>, GpuError> {
326 let flat = self.to_vec()?;
327 ndarray::ArrayD::<f32>::from_shape_vec(self.shape.clone(), flat)
328 .map_err(|e| GpuError::Other(format!("shape_vec error: {e}")))
329 }
330
331 pub fn from_data(data: &[f32], shape: Vec<usize>) -> Result<Self, GpuError> {
337 let ctx =
338 global_context().ok_or_else(|| GpuError::Other("No wgpu adapter available".into()))?;
339 Self::from_ndarray_data(data, shape, ctx)
340 }
341
342 pub fn add(&self, other: &GpuNdarray<f32>) -> Result<GpuNdarray<f32>, GpuError> {
346 self.dispatch_elementwise_binary(other, 0)
347 }
348
349 pub fn subtract(&self, other: &GpuNdarray<f32>) -> Result<GpuNdarray<f32>, GpuError> {
351 self.dispatch_elementwise_binary(other, 1)
352 }
353
354 pub fn multiply(&self, other: &GpuNdarray<f32>) -> Result<GpuNdarray<f32>, GpuError> {
356 self.dispatch_elementwise_binary(other, 2)
357 }
358
359 pub fn multiply_by_scalar_f32(&self, scalar: f32) -> Result<GpuNdarray<f32>, GpuError> {
361 self.dispatch_scalar_multiply(scalar)
362 }
363
364 pub fn sum_all(&self) -> Result<f32, GpuError> {
366 self.dispatch_sum_all()
367 }
368
369 pub fn dot_gpu(&self, other: &GpuNdarray<f32>) -> Result<f32, GpuError> {
371 let prod = self.dispatch_elementwise_binary(other, 2)?;
372 prod.dispatch_sum_all()
373 }
374
375 pub fn matmul(&self, other: &GpuNdarray<f32>) -> Result<GpuNdarray<f32>, GpuError> {
380 self.dispatch_matmul(other)
381 }
382
383 fn dispatch_elementwise_binary(
389 &self,
390 other: &GpuNdarray<f32>,
391 op_id: u32,
392 ) -> Result<GpuNdarray<f32>, GpuError> {
393 let n = self.numel();
394 if n != other.numel() {
395 return Err(GpuError::InvalidParameter(
396 "shape mismatch in elementwise binary".into(),
397 ));
398 }
399 let wgsl = match op_id {
400 0 => ELEMENTWISE_ADD_WGSL,
401 1 => ELEMENTWISE_SUB_WGSL,
402 _ => ELEMENTWISE_MUL_WGSL,
403 };
404 let byte_size = (n * 4) as u64;
405 let result_buf = self
406 .context
407 .device()
408 .create_buffer(&wgpu::BufferDescriptor {
409 label: Some("elementwise-result"),
410 size: byte_size,
411 usage: wgpu::BufferUsages::STORAGE
412 | wgpu::BufferUsages::COPY_SRC
413 | wgpu::BufferUsages::COPY_DST,
414 mapped_at_creation: false,
415 });
416
417 let bgl_entries = [storage_ro(0), storage_ro(1), storage_rw(2)];
418 let (pipeline, bgl) = build_pipeline(&self.context, wgsl, &bgl_entries, "elementwise")?;
419
420 let bind_group = self
421 .context
422 .device()
423 .create_bind_group(&wgpu::BindGroupDescriptor {
424 label: Some("elementwise-bg"),
425 layout: &bgl,
426 entries: &[
427 wgpu::BindGroupEntry {
428 binding: 0,
429 resource: self.buffer.as_entire_binding(),
430 },
431 wgpu::BindGroupEntry {
432 binding: 1,
433 resource: other.buffer.as_entire_binding(),
434 },
435 wgpu::BindGroupEntry {
436 binding: 2,
437 resource: result_buf.as_entire_binding(),
438 },
439 ],
440 });
441
442 let mut encoder =
443 self.context
444 .device()
445 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
446 label: Some("elementwise-encoder"),
447 });
448 {
449 let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
450 label: Some("elementwise-pass"),
451 timestamp_writes: None,
452 });
453 cpass.set_pipeline(&pipeline);
454 cpass.set_bind_group(0, &bind_group, &[]);
455 let workgroups = (n as u32 + 255) / 256;
456 cpass.dispatch_workgroups(workgroups, 1, 1);
457 }
458 self.context.queue().submit(Some(encoder.finish()));
459 self.context
460 .device()
461 .poll(wgpu::PollType::wait_indefinitely())
462 .map_err(|e| GpuError::Other(format!("poll error: {e:?}")))?;
463
464 Ok(GpuNdarray {
465 buffer: Arc::new(result_buf),
466 shape: self.shape.clone(),
467 strides: self.strides.clone(),
468 context: Arc::clone(&self.context),
469 _phantom: PhantomData,
470 })
471 }
472
473 fn dispatch_scalar_multiply(&self, scalar: f32) -> Result<GpuNdarray<f32>, GpuError> {
475 let n = self.numel();
476 let byte_size = (n * 4) as u64;
477 let result_buf = self
478 .context
479 .device()
480 .create_buffer(&wgpu::BufferDescriptor {
481 label: Some("scalar-mul-result"),
482 size: byte_size,
483 usage: wgpu::BufferUsages::STORAGE
484 | wgpu::BufferUsages::COPY_SRC
485 | wgpu::BufferUsages::COPY_DST,
486 mapped_at_creation: false,
487 });
488
489 let mut unif: Vec<u8> = Vec::with_capacity(16);
491 unif.extend_from_slice(&scalar.to_le_bytes());
492 unif.extend_from_slice(&(n as u32).to_le_bytes());
493 while unif.len() % 16 != 0 {
494 unif.push(0);
495 }
496 use wgpu::util::DeviceExt as _;
497 let uniform_buffer =
498 self.context
499 .device()
500 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
501 label: Some("scalar-mul-uniform"),
502 contents: &unif,
503 usage: wgpu::BufferUsages::UNIFORM,
504 });
505
506 let bgl_entries = [storage_ro(0), storage_rw(1), uniform_buf(2)];
507 let (pipeline, bgl) =
508 build_pipeline(&self.context, SCALAR_MUL_WGSL, &bgl_entries, "scalar-mul")?;
509
510 let bind_group = self
511 .context
512 .device()
513 .create_bind_group(&wgpu::BindGroupDescriptor {
514 label: Some("scalar-mul-bg"),
515 layout: &bgl,
516 entries: &[
517 wgpu::BindGroupEntry {
518 binding: 0,
519 resource: self.buffer.as_entire_binding(),
520 },
521 wgpu::BindGroupEntry {
522 binding: 1,
523 resource: result_buf.as_entire_binding(),
524 },
525 wgpu::BindGroupEntry {
526 binding: 2,
527 resource: uniform_buffer.as_entire_binding(),
528 },
529 ],
530 });
531
532 let mut encoder =
533 self.context
534 .device()
535 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
536 label: Some("scalar-mul-encoder"),
537 });
538 {
539 let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
540 label: Some("scalar-mul-pass"),
541 timestamp_writes: None,
542 });
543 cpass.set_pipeline(&pipeline);
544 cpass.set_bind_group(0, &bind_group, &[]);
545 let workgroups = (n as u32 + 255) / 256;
546 cpass.dispatch_workgroups(workgroups, 1, 1);
547 }
548 self.context.queue().submit(Some(encoder.finish()));
549 self.context
550 .device()
551 .poll(wgpu::PollType::wait_indefinitely())
552 .map_err(|e| GpuError::Other(format!("poll error: {e:?}")))?;
553
554 Ok(GpuNdarray {
555 buffer: Arc::new(result_buf),
556 shape: self.shape.clone(),
557 strides: self.strides.clone(),
558 context: Arc::clone(&self.context),
559 _phantom: PhantomData,
560 })
561 }
562
563 fn dispatch_matmul(&self, other: &GpuNdarray<f32>) -> Result<GpuNdarray<f32>, GpuError> {
565 if self.shape.len() != 2 || other.shape.len() != 2 {
566 return Err(GpuError::InvalidParameter(
567 "matmul requires 2-D arrays".into(),
568 ));
569 }
570 let (m, k) = (self.shape[0], self.shape[1]);
571 let (k2, n) = (other.shape[0], other.shape[1]);
572 if k != k2 {
573 return Err(GpuError::InvalidParameter(format!(
574 "matmul shape mismatch: [{m},{k}] x [{k2},{n}]"
575 )));
576 }
577
578 let byte_size = (m * n * 4) as u64;
579 let result_buf = self
580 .context
581 .device()
582 .create_buffer(&wgpu::BufferDescriptor {
583 label: Some("matmul-result"),
584 size: byte_size,
585 usage: wgpu::BufferUsages::STORAGE
586 | wgpu::BufferUsages::COPY_SRC
587 | wgpu::BufferUsages::COPY_DST,
588 mapped_at_creation: false,
589 });
590
591 let uniform_data: [u32; 3] = [m as u32, n as u32, k as u32];
592 let uniform_bytes: Vec<u8> = uniform_data.iter().flat_map(|v| v.to_le_bytes()).collect();
593 let mut uniform_padded = uniform_bytes;
595 while uniform_padded.len() % 16 != 0 {
596 uniform_padded.push(0);
597 }
598 use wgpu::util::DeviceExt as _;
599 let uniform_buffer =
600 self.context
601 .device()
602 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
603 label: Some("matmul-uniform"),
604 contents: &uniform_padded,
605 usage: wgpu::BufferUsages::UNIFORM,
606 });
607
608 let bgl_entries = [storage_ro(0), storage_ro(1), storage_rw(2), uniform_buf(3)];
609 let (pipeline, bgl) = build_pipeline(&self.context, MATMUL_WGSL, &bgl_entries, "matmul")?;
610 let bind_group = self
611 .context
612 .device()
613 .create_bind_group(&wgpu::BindGroupDescriptor {
614 label: Some("matmul-bg"),
615 layout: &bgl,
616 entries: &[
617 wgpu::BindGroupEntry {
618 binding: 0,
619 resource: self.buffer.as_entire_binding(),
620 },
621 wgpu::BindGroupEntry {
622 binding: 1,
623 resource: other.buffer.as_entire_binding(),
624 },
625 wgpu::BindGroupEntry {
626 binding: 2,
627 resource: result_buf.as_entire_binding(),
628 },
629 wgpu::BindGroupEntry {
630 binding: 3,
631 resource: uniform_buffer.as_entire_binding(),
632 },
633 ],
634 });
635
636 let wg_x = (n as u32 + 15) / 16;
637 let wg_y = (m as u32 + 15) / 16;
638 let mut encoder =
639 self.context
640 .device()
641 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
642 label: Some("matmul-encoder"),
643 });
644 {
645 let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
646 label: Some("matmul-pass"),
647 timestamp_writes: None,
648 });
649 cpass.set_pipeline(&pipeline);
650 cpass.set_bind_group(0, &bind_group, &[]);
651 cpass.dispatch_workgroups(wg_x, wg_y, 1);
652 }
653 self.context.queue().submit(Some(encoder.finish()));
654 self.context
655 .device()
656 .poll(wgpu::PollType::wait_indefinitely())
657 .map_err(|e| GpuError::Other(format!("poll error: {e:?}")))?;
658
659 Ok(GpuNdarray {
660 buffer: Arc::new(result_buf),
661 shape: vec![m, n],
662 strides: Self::compute_strides(&[m, n]),
663 context: Arc::clone(&self.context),
664 _phantom: PhantomData,
665 })
666 }
667
668 fn dispatch_sum_all(&self) -> Result<f32, GpuError> {
670 let n = self.numel();
671 let workgroup_count = (n as u32 + 255) / 256;
673 let partial_byte_size = (workgroup_count as usize * 4) as u64;
674 let partial_buf = self
675 .context
676 .device()
677 .create_buffer(&wgpu::BufferDescriptor {
678 label: Some("sum-partial"),
679 size: partial_byte_size,
680 usage: wgpu::BufferUsages::STORAGE
681 | wgpu::BufferUsages::COPY_SRC
682 | wgpu::BufferUsages::COPY_DST,
683 mapped_at_creation: false,
684 });
685
686 let n_bytes = (n as u32).to_le_bytes();
687 let mut uniform_bytes = n_bytes.to_vec();
688 while uniform_bytes.len() % 16 != 0 {
689 uniform_bytes.push(0);
690 }
691 use wgpu::util::DeviceExt as _;
692 let uniform_buffer =
693 self.context
694 .device()
695 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
696 label: Some("sum-uniform"),
697 contents: &uniform_bytes,
698 usage: wgpu::BufferUsages::UNIFORM,
699 });
700
701 let bgl_entries = [storage_ro(0), storage_rw(1), uniform_buf(2)];
702 let (pipeline, bgl) =
703 build_pipeline(&self.context, SUM_REDUCE_WGSL, &bgl_entries, "sum-reduce")?;
704 let bind_group = self
705 .context
706 .device()
707 .create_bind_group(&wgpu::BindGroupDescriptor {
708 label: Some("sum-bg"),
709 layout: &bgl,
710 entries: &[
711 wgpu::BindGroupEntry {
712 binding: 0,
713 resource: self.buffer.as_entire_binding(),
714 },
715 wgpu::BindGroupEntry {
716 binding: 1,
717 resource: partial_buf.as_entire_binding(),
718 },
719 wgpu::BindGroupEntry {
720 binding: 2,
721 resource: uniform_buffer.as_entire_binding(),
722 },
723 ],
724 });
725
726 let mut encoder =
727 self.context
728 .device()
729 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
730 label: Some("sum-encoder"),
731 });
732 {
733 let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
734 label: Some("sum-pass"),
735 timestamp_writes: None,
736 });
737 cpass.set_pipeline(&pipeline);
738 cpass.set_bind_group(0, &bind_group, &[]);
739 cpass.dispatch_workgroups(workgroup_count, 1, 1);
740 }
741 self.context.queue().submit(Some(encoder.finish()));
742 self.context
743 .device()
744 .poll(wgpu::PollType::wait_indefinitely())
745 .map_err(|e| GpuError::Other(format!("poll error: {e:?}")))?;
746
747 let staging = self
749 .context
750 .device()
751 .create_buffer(&wgpu::BufferDescriptor {
752 label: Some("sum-staging"),
753 size: partial_byte_size,
754 usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
755 mapped_at_creation: false,
756 });
757 let mut encoder2 =
758 self.context
759 .device()
760 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
761 label: Some("sum-copy-encoder"),
762 });
763 encoder2.copy_buffer_to_buffer(&partial_buf, 0, &staging, 0, partial_byte_size);
764 self.context.queue().submit(Some(encoder2.finish()));
765
766 self.context
767 .device()
768 .poll(wgpu::PollType::wait_indefinitely())
769 .map_err(|e| GpuError::Other(format!("poll error: {e:?}")))?;
770
771 let slice = staging.slice(0..partial_byte_size);
772 let (tx, rx) = std::sync::mpsc::channel();
773 slice.map_async(wgpu::MapMode::Read, move |r| {
774 let _ = tx.send(r);
775 });
776 self.context
777 .device()
778 .poll(wgpu::PollType::wait_indefinitely())
779 .map_err(|e| GpuError::Other(format!("map poll error: {e:?}")))?;
780 rx.recv()
781 .map_err(|_| GpuError::Other("channel closed".into()))?
782 .map_err(|e| GpuError::Other(format!("map_async: {e:?}")))?;
783
784 let mapped = slice.get_mapped_range();
785 let total: f32 = mapped
786 .chunks_exact(4)
787 .map(|b| f32::from_le_bytes([b[0], b[1], b[2], b[3]]))
788 .sum();
789 drop(mapped);
790 staging.unmap();
791 Ok(total)
792 }
793
794 fn dispatch_transpose_2d(&self) -> Result<GpuNdarray<f32>, GpuError> {
796 if self.shape.len() != 2 {
797 return Err(GpuError::InvalidParameter(
798 "transpose_2d requires a 2-D array".into(),
799 ));
800 }
801 let (rows, cols) = (self.shape[0], self.shape[1]);
802 let byte_size = (rows * cols * 4) as u64;
803
804 let result_buf = self
805 .context
806 .device()
807 .create_buffer(&wgpu::BufferDescriptor {
808 label: Some("transpose-result"),
809 size: byte_size,
810 usage: wgpu::BufferUsages::STORAGE
811 | wgpu::BufferUsages::COPY_SRC
812 | wgpu::BufferUsages::COPY_DST,
813 mapped_at_creation: false,
814 });
815
816 let uniform_data: [u32; 2] = [rows as u32, cols as u32];
818 let uniform_bytes: Vec<u8> = uniform_data.iter().flat_map(|v| v.to_le_bytes()).collect();
819 let mut uniform_padded = uniform_bytes;
820 while uniform_padded.len() % 16 != 0 {
821 uniform_padded.push(0);
822 }
823 use wgpu::util::DeviceExt as _;
824 let uniform_buffer =
825 self.context
826 .device()
827 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
828 label: Some("transpose-uniform"),
829 contents: &uniform_padded,
830 usage: wgpu::BufferUsages::UNIFORM,
831 });
832
833 let bgl_entries = [storage_ro(0), storage_rw(1), uniform_buf(2)];
835 let (pipeline, bgl) =
836 build_pipeline(&self.context, TRANSPOSE_WGSL, &bgl_entries, "transpose")?;
837 let bind_group = self
838 .context
839 .device()
840 .create_bind_group(&wgpu::BindGroupDescriptor {
841 label: Some("transpose-bg"),
842 layout: &bgl,
843 entries: &[
844 wgpu::BindGroupEntry {
845 binding: 0,
846 resource: self.buffer.as_entire_binding(),
847 },
848 wgpu::BindGroupEntry {
849 binding: 1,
850 resource: result_buf.as_entire_binding(),
851 },
852 wgpu::BindGroupEntry {
853 binding: 2,
854 resource: uniform_buffer.as_entire_binding(),
855 },
856 ],
857 });
858
859 let wg_x = (cols as u32 + 15) / 16;
861 let wg_y = (rows as u32 + 15) / 16;
862 let mut encoder =
863 self.context
864 .device()
865 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
866 label: Some("transpose-encoder"),
867 });
868 {
869 let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
870 label: Some("transpose-pass"),
871 timestamp_writes: None,
872 });
873 cpass.set_pipeline(&pipeline);
874 cpass.set_bind_group(0, &bind_group, &[]);
875 cpass.dispatch_workgroups(wg_x, wg_y, 1);
876 }
877 self.context.queue().submit(Some(encoder.finish()));
878 self.context
879 .device()
880 .poll(wgpu::PollType::wait_indefinitely())
881 .map_err(|e| GpuError::Other(format!("poll error: {e:?}")))?;
882
883 Ok(GpuNdarray {
884 buffer: Arc::new(result_buf),
885 shape: vec![cols, rows],
886 strides: Self::compute_strides(&[cols, rows]),
887 context: Arc::clone(&self.context),
888 _phantom: PhantomData,
889 })
890 }
891
892 fn dispatch_concatenate_axis0(
894 arrays: &[&GpuNdarray<f32>],
895 ) -> Result<GpuNdarray<f32>, GpuError> {
896 if arrays.is_empty() {
897 return Err(GpuError::InvalidParameter("empty array list".into()));
898 }
899 let trailing = &arrays[0].shape[1..];
901 for arr in arrays.iter().skip(1) {
902 if arr.shape[1..] != *trailing {
903 return Err(GpuError::InvalidParameter(
904 "concatenate axis=0: trailing dimensions must match".into(),
905 ));
906 }
907 }
908 let ctx = Arc::clone(&arrays[0].context);
909 let trailing_elems: usize = trailing.iter().product::<usize>().max(1);
910
911 let total_rows: usize = arrays.iter().map(|a| a.shape[0]).sum();
912 let total_elems = total_rows * trailing_elems;
913 let total_bytes = (total_elems * 4) as u64;
914
915 let result_buf = ctx.device().create_buffer(&wgpu::BufferDescriptor {
916 label: Some("concat-result"),
917 size: total_bytes,
918 usage: wgpu::BufferUsages::STORAGE
919 | wgpu::BufferUsages::COPY_SRC
920 | wgpu::BufferUsages::COPY_DST,
921 mapped_at_creation: false,
922 });
923
924 let mut encoder = ctx
925 .device()
926 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
927 label: Some("concat-encoder"),
928 });
929 let mut offset: u64 = 0;
930 for arr in arrays {
931 let arr_bytes = (arr.numel() * 4) as u64;
932 encoder.copy_buffer_to_buffer(&arr.buffer, 0, &result_buf, offset, arr_bytes);
933 offset += arr_bytes;
934 }
935 ctx.queue().submit(Some(encoder.finish()));
936 ctx.device()
937 .poll(wgpu::PollType::wait_indefinitely())
938 .map_err(|e| GpuError::Other(format!("poll error: {e:?}")))?;
939
940 let new_shape = {
941 let mut s = vec![total_rows];
942 s.extend_from_slice(trailing);
943 s
944 };
945 let new_strides = Self::compute_strides(&new_shape);
946 Ok(GpuNdarray {
947 buffer: Arc::new(result_buf),
948 shape: new_shape,
949 strides: new_strides,
950 context: ctx,
951 _phantom: PhantomData,
952 })
953 }
954
955 fn dispatch_concatenate_axisn(
960 a: &GpuNdarray<f32>,
961 b: &GpuNdarray<f32>,
962 axis: usize,
963 ) -> Result<GpuNdarray<f32>, GpuError> {
964 let ndim = a.shape.len();
965 if ndim > 8 {
966 return Err(GpuError::InvalidParameter(
967 "concat_axisn: ndim must be <= 8".into(),
968 ));
969 }
970
971 let mut out_shape = a.shape.clone();
973 out_shape[axis] += b.shape[axis];
974
975 let out_strides = GpuNdarray::<f32>::compute_strides(&out_shape);
976 let a_strides = GpuNdarray::<f32>::compute_strides(&a.shape);
977 let b_strides = GpuNdarray::<f32>::compute_strides(&b.shape);
978
979 let total_out = out_shape.iter().product::<usize>();
980 let byte_out = (total_out * 4) as u64;
981
982 let ctx = Arc::clone(&a.context);
983 let result_buf = ctx.device().create_buffer(&wgpu::BufferDescriptor {
984 label: Some("concat-axisn-result"),
985 size: byte_out,
986 usage: wgpu::BufferUsages::STORAGE
987 | wgpu::BufferUsages::COPY_SRC
988 | wgpu::BufferUsages::COPY_DST,
989 mapped_at_creation: false,
990 });
991
992 let dim_a = a.shape[axis] as u32;
994 let mut unif_bytes: Vec<u8> = Vec::with_capacity(16);
995 unif_bytes.extend_from_slice(&(axis as u32).to_le_bytes());
996 unif_bytes.extend_from_slice(&dim_a.to_le_bytes());
997 unif_bytes.extend_from_slice(&(ndim as u32).to_le_bytes());
998 unif_bytes.extend_from_slice(&0u32.to_le_bytes()); debug_assert_eq!(unif_bytes.len(), 16);
1000
1001 let pack_u32_slice = |vals: &[usize]| -> Vec<u8> {
1004 let mut out: Vec<u8> = Vec::with_capacity(vals.len() * 4);
1005 for &v in vals {
1006 out.extend_from_slice(&(v as u32).to_le_bytes());
1007 }
1008 while out.len() % 16 != 0 {
1010 out.extend_from_slice(&0u32.to_le_bytes());
1011 }
1012 out
1013 };
1014
1015 let out_shape_bytes = pack_u32_slice(&out_shape);
1016 let out_strides_bytes = pack_u32_slice(&out_strides);
1017 let a_strides_bytes = pack_u32_slice(&a_strides);
1018 let b_strides_bytes = pack_u32_slice(&b_strides);
1019
1020 use wgpu::util::DeviceExt as _;
1021 let make_storage_buf = |bytes: &[u8], label: &str| -> wgpu::Buffer {
1022 ctx.device()
1023 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
1024 label: Some(label),
1025 contents: bytes,
1026 usage: wgpu::BufferUsages::STORAGE,
1027 })
1028 };
1029 let unif_buf = ctx
1030 .device()
1031 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
1032 label: Some("concat-axisn-uniform"),
1033 contents: &unif_bytes,
1034 usage: wgpu::BufferUsages::UNIFORM,
1035 });
1036 let out_shape_buf = make_storage_buf(&out_shape_bytes, "concat-axisn-out-shape");
1037 let out_strides_buf = make_storage_buf(&out_strides_bytes, "concat-axisn-out-strides");
1038 let a_strides_buf = make_storage_buf(&a_strides_bytes, "concat-axisn-a-strides");
1039 let b_strides_buf = make_storage_buf(&b_strides_bytes, "concat-axisn-b-strides");
1040
1041 let bgl_entries = [
1046 storage_ro(0),
1047 storage_ro(1),
1048 storage_rw(2),
1049 uniform_buf(3),
1050 storage_ro(4),
1051 storage_ro(5),
1052 storage_ro(6),
1053 storage_ro(7),
1054 ];
1055 let (pipeline, bgl) =
1056 build_pipeline(&ctx, CONCAT_AXISN_WGSL, &bgl_entries, "concat-axisn")?;
1057
1058 let bind_group = ctx.device().create_bind_group(&wgpu::BindGroupDescriptor {
1059 label: Some("concat-axisn-bg"),
1060 layout: &bgl,
1061 entries: &[
1062 wgpu::BindGroupEntry {
1063 binding: 0,
1064 resource: a.buffer.as_entire_binding(),
1065 },
1066 wgpu::BindGroupEntry {
1067 binding: 1,
1068 resource: b.buffer.as_entire_binding(),
1069 },
1070 wgpu::BindGroupEntry {
1071 binding: 2,
1072 resource: result_buf.as_entire_binding(),
1073 },
1074 wgpu::BindGroupEntry {
1075 binding: 3,
1076 resource: unif_buf.as_entire_binding(),
1077 },
1078 wgpu::BindGroupEntry {
1079 binding: 4,
1080 resource: out_shape_buf.as_entire_binding(),
1081 },
1082 wgpu::BindGroupEntry {
1083 binding: 5,
1084 resource: out_strides_buf.as_entire_binding(),
1085 },
1086 wgpu::BindGroupEntry {
1087 binding: 6,
1088 resource: a_strides_buf.as_entire_binding(),
1089 },
1090 wgpu::BindGroupEntry {
1091 binding: 7,
1092 resource: b_strides_buf.as_entire_binding(),
1093 },
1094 ],
1095 });
1096
1097 let workgroups = (total_out as u32 + 255) / 256;
1098 let mut encoder = ctx
1099 .device()
1100 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1101 label: Some("concat-axisn-encoder"),
1102 });
1103 {
1104 let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
1105 label: Some("concat-axisn-pass"),
1106 timestamp_writes: None,
1107 });
1108 cpass.set_pipeline(&pipeline);
1109 cpass.set_bind_group(0, &bind_group, &[]);
1110 cpass.dispatch_workgroups(workgroups, 1, 1);
1111 }
1112 ctx.queue().submit(Some(encoder.finish()));
1113 ctx.device()
1114 .poll(wgpu::PollType::wait_indefinitely())
1115 .map_err(|e| GpuError::Other(format!("poll error: {e:?}")))?;
1116
1117 let new_strides = GpuNdarray::<f32>::compute_strides(&out_shape);
1118 Ok(GpuNdarray {
1119 buffer: Arc::new(result_buf),
1120 shape: out_shape,
1121 strides: new_strides,
1122 context: ctx,
1123 _phantom: PhantomData,
1124 })
1125 }
1126
1127 fn dispatch_sum_axis(&self, axis: usize) -> Result<GpuNdarray<f32>, GpuError> {
1132 let ndim = self.shape.len();
1133 if ndim > 8 {
1134 return Err(GpuError::InvalidParameter(
1135 "sum_axis: ndim must be <= 8".into(),
1136 ));
1137 }
1138
1139 let axis_size = self.shape[axis];
1140
1141 let out_shape: Vec<usize> = self
1143 .shape
1144 .iter()
1145 .enumerate()
1146 .filter(|&(i, _)| i != axis)
1147 .map(|(_, &d)| d)
1148 .collect();
1149 let out_strides = GpuNdarray::<f32>::compute_strides(&out_shape);
1150 let in_strides = &self.strides;
1151
1152 let total_out = out_shape.iter().product::<usize>().max(1);
1153 let byte_out = (total_out * 4) as u64;
1154
1155 let result_buf = self
1156 .context
1157 .device()
1158 .create_buffer(&wgpu::BufferDescriptor {
1159 label: Some("sum-axis-result"),
1160 size: byte_out,
1161 usage: wgpu::BufferUsages::STORAGE
1162 | wgpu::BufferUsages::COPY_SRC
1163 | wgpu::BufferUsages::COPY_DST,
1164 mapped_at_creation: false,
1165 });
1166
1167 let in_axis_stride = self.strides[axis] as u32;
1169 let mut unif_bytes: Vec<u8> = Vec::with_capacity(16);
1170 unif_bytes.extend_from_slice(&(axis as u32).to_le_bytes());
1171 unif_bytes.extend_from_slice(&(axis_size as u32).to_le_bytes());
1172 unif_bytes.extend_from_slice(&(ndim as u32).to_le_bytes());
1173 unif_bytes.extend_from_slice(&in_axis_stride.to_le_bytes());
1174 debug_assert_eq!(unif_bytes.len(), 16);
1175
1176 let pack_u32_slice = |vals: &[usize]| -> Vec<u8> {
1177 let mut out: Vec<u8> = Vec::with_capacity(vals.len() * 4);
1178 for &v in vals {
1179 out.extend_from_slice(&(v as u32).to_le_bytes());
1180 }
1181 while out.len() % 16 != 0 {
1182 out.extend_from_slice(&0u32.to_le_bytes());
1183 }
1184 out
1185 };
1186
1187 let in_shape_bytes = pack_u32_slice(&self.shape);
1188 let in_strides_bytes = pack_u32_slice(in_strides);
1189 let out_shape_bytes = pack_u32_slice(&out_shape);
1190 let out_strides_bytes = pack_u32_slice(&out_strides);
1191
1192 use wgpu::util::DeviceExt as _;
1193 let unif_buf =
1194 self.context
1195 .device()
1196 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
1197 label: Some("sum-axis-uniform"),
1198 contents: &unif_bytes,
1199 usage: wgpu::BufferUsages::UNIFORM,
1200 });
1201 let make_storage_buf = |bytes: &[u8], label: &str| -> wgpu::Buffer {
1202 self.context
1203 .device()
1204 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
1205 label: Some(label),
1206 contents: bytes,
1207 usage: wgpu::BufferUsages::STORAGE,
1208 })
1209 };
1210 let in_shape_buf = make_storage_buf(&in_shape_bytes, "sum-axis-in-shape");
1211 let in_strides_buf = make_storage_buf(&in_strides_bytes, "sum-axis-in-strides");
1212 let out_shape_buf = make_storage_buf(&out_shape_bytes, "sum-axis-out-shape");
1213 let out_strides_buf = make_storage_buf(&out_strides_bytes, "sum-axis-out-strides");
1214
1215 let bgl_entries = [
1220 storage_ro(0),
1221 storage_rw(1),
1222 uniform_buf(2),
1223 storage_ro(3),
1224 storage_ro(4),
1225 storage_ro(5),
1226 storage_ro(6),
1227 ];
1228 let (pipeline, bgl) = build_pipeline(
1229 &self.context,
1230 REDUCE_SUM_AXIS_WGSL,
1231 &bgl_entries,
1232 "sum-axis",
1233 )?;
1234
1235 let bind_group = self
1236 .context
1237 .device()
1238 .create_bind_group(&wgpu::BindGroupDescriptor {
1239 label: Some("sum-axis-bg"),
1240 layout: &bgl,
1241 entries: &[
1242 wgpu::BindGroupEntry {
1243 binding: 0,
1244 resource: self.buffer.as_entire_binding(),
1245 },
1246 wgpu::BindGroupEntry {
1247 binding: 1,
1248 resource: result_buf.as_entire_binding(),
1249 },
1250 wgpu::BindGroupEntry {
1251 binding: 2,
1252 resource: unif_buf.as_entire_binding(),
1253 },
1254 wgpu::BindGroupEntry {
1255 binding: 3,
1256 resource: in_shape_buf.as_entire_binding(),
1257 },
1258 wgpu::BindGroupEntry {
1259 binding: 4,
1260 resource: in_strides_buf.as_entire_binding(),
1261 },
1262 wgpu::BindGroupEntry {
1263 binding: 5,
1264 resource: out_shape_buf.as_entire_binding(),
1265 },
1266 wgpu::BindGroupEntry {
1267 binding: 6,
1268 resource: out_strides_buf.as_entire_binding(),
1269 },
1270 ],
1271 });
1272
1273 let workgroups = (total_out as u32 + 255) / 256;
1274 let mut encoder =
1275 self.context
1276 .device()
1277 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1278 label: Some("sum-axis-encoder"),
1279 });
1280 {
1281 let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
1282 label: Some("sum-axis-pass"),
1283 timestamp_writes: None,
1284 });
1285 cpass.set_pipeline(&pipeline);
1286 cpass.set_bind_group(0, &bind_group, &[]);
1287 cpass.dispatch_workgroups(workgroups, 1, 1);
1288 }
1289 self.context.queue().submit(Some(encoder.finish()));
1290 self.context
1291 .device()
1292 .poll(wgpu::PollType::wait_indefinitely())
1293 .map_err(|e| GpuError::Other(format!("poll error: {e:?}")))?;
1294
1295 let new_strides = GpuNdarray::<f32>::compute_strides(&out_shape);
1296 Ok(GpuNdarray {
1297 buffer: Arc::new(result_buf),
1298 shape: out_shape,
1299 strides: new_strides,
1300 context: Arc::clone(&self.context),
1301 _phantom: PhantomData,
1302 })
1303 }
1304
1305 fn dispatch_reshape(&self, new_shape: Vec<usize>) -> Result<GpuNdarray<f32>, GpuError> {
1307 let new_numel: usize = new_shape.iter().product();
1308 if new_numel != self.numel() {
1309 return Err(GpuError::InvalidParameter(format!(
1310 "reshape: element count mismatch: {} vs {}",
1311 self.numel(),
1312 new_numel
1313 )));
1314 }
1315 let new_strides = Self::compute_strides(&new_shape);
1316 Ok(GpuNdarray {
1317 buffer: Arc::clone(&self.buffer),
1318 shape: new_shape,
1319 strides: new_strides,
1320 context: Arc::clone(&self.context),
1321 _phantom: PhantomData,
1322 })
1323 }
1324
1325 fn cpu_fallback_unary<F>(&self, f: F) -> Result<GpuNdarray<f32>, GpuError>
1327 where
1328 F: FnOnce(ndarray::ArrayD<f32>) -> Result<ndarray::ArrayD<f32>, GpuError>,
1329 {
1330 let arr = self.to_ndarray()?;
1331 let result = f(arr)?;
1332 let shape = result.shape().to_vec();
1333 let flat: Vec<f32> = result.into_iter().collect();
1334 Self::from_ndarray_data(&flat, shape, Arc::clone(&self.context))
1335 }
1336}
1337
1338impl ArrayProtocol for GpuNdarray<f32> {
1343 fn array_function(
1344 &self,
1345 func: &ArrayFunction,
1346 _types: &[TypeId],
1347 args: &[Box<dyn Any>],
1348 kwargs: &HashMap<String, Box<dyn Any>>,
1349 ) -> Result<Box<dyn Any>, NotImplemented> {
1350 macro_rules! gpu_arg {
1352 ($idx:expr) => {{
1353 let boxed_ap = args[$idx]
1354 .downcast_ref::<Box<dyn ArrayProtocol>>()
1355 .ok_or(NotImplemented)?;
1356 boxed_ap
1357 .as_any()
1358 .downcast_ref::<GpuNdarray<f32>>()
1359 .ok_or(NotImplemented)?
1360 }};
1361 }
1362
1363 let n = self.numel();
1365 let use_gpu = n >= GPU_THRESHOLD && is_gpu_available();
1366
1367 match func.name {
1368 "scirs2::array_protocol::operations::add" => {
1370 let a = gpu_arg!(0);
1371 let b = gpu_arg!(1);
1372 if use_gpu {
1373 let result = a
1375 .dispatch_elementwise_binary(b, 0)
1376 .map_err(|_| NotImplemented)?;
1377 Ok(Box::new(Box::new(result) as Box<dyn ArrayProtocol>) as Box<dyn Any>)
1378 } else {
1379 let ra = a.to_ndarray().map_err(|_| NotImplemented)?;
1380 let rb = b.to_ndarray().map_err(|_| NotImplemented)?;
1381 let rc = ra + rb;
1382 let flat: Vec<f32> = rc.into_iter().collect();
1383 let result = GpuNdarray::<f32>::from_ndarray_data(
1384 &flat,
1385 a.shape.clone(),
1386 Arc::clone(&a.context),
1387 )
1388 .map_err(|_| NotImplemented)?;
1389 Ok(Box::new(Box::new(result) as Box<dyn ArrayProtocol>) as Box<dyn Any>)
1390 }
1391 }
1392
1393 "scirs2::array_protocol::operations::subtract" => {
1395 let a = gpu_arg!(0);
1396 let b = gpu_arg!(1);
1397 if use_gpu {
1398 let result = a
1400 .dispatch_elementwise_binary(b, 1)
1401 .map_err(|_| NotImplemented)?;
1402 Ok(Box::new(Box::new(result) as Box<dyn ArrayProtocol>) as Box<dyn Any>)
1403 } else {
1404 let ra = a.to_ndarray().map_err(|_| NotImplemented)?;
1405 let rb = b.to_ndarray().map_err(|_| NotImplemented)?;
1406 let rc = ra - rb;
1407 let flat: Vec<f32> = rc.into_iter().collect();
1408 let result = GpuNdarray::<f32>::from_ndarray_data(
1409 &flat,
1410 a.shape.clone(),
1411 Arc::clone(&a.context),
1412 )
1413 .map_err(|_| NotImplemented)?;
1414 Ok(Box::new(Box::new(result) as Box<dyn ArrayProtocol>) as Box<dyn Any>)
1415 }
1416 }
1417
1418 "scirs2::array_protocol::operations::multiply" => {
1420 let a = gpu_arg!(0);
1421 let b = gpu_arg!(1);
1422 if use_gpu {
1423 let result = a
1425 .dispatch_elementwise_binary(b, 2)
1426 .map_err(|_| NotImplemented)?;
1427 Ok(Box::new(Box::new(result) as Box<dyn ArrayProtocol>) as Box<dyn Any>)
1428 } else {
1429 let ra = a.to_ndarray().map_err(|_| NotImplemented)?;
1430 let rb = b.to_ndarray().map_err(|_| NotImplemented)?;
1431 let rc = ra * rb;
1432 let flat: Vec<f32> = rc.into_iter().collect();
1433 let result = GpuNdarray::<f32>::from_ndarray_data(
1434 &flat,
1435 a.shape.clone(),
1436 Arc::clone(&a.context),
1437 )
1438 .map_err(|_| NotImplemented)?;
1439 Ok(Box::new(Box::new(result) as Box<dyn ArrayProtocol>) as Box<dyn Any>)
1440 }
1441 }
1442
1443 "scirs2::array_protocol::operations::multiply_by_scalar_f32" => {
1445 let a = gpu_arg!(0);
1446 let scalar = kwargs
1448 .values()
1449 .find_map(|v| v.downcast_ref::<f32>().copied())
1450 .ok_or(NotImplemented)?;
1451 if use_gpu {
1452 let result = a
1453 .dispatch_scalar_multiply(scalar)
1454 .map_err(|_| NotImplemented)?;
1455 Ok(Box::new(Box::new(result) as Box<dyn ArrayProtocol>) as Box<dyn Any>)
1456 } else {
1457 let ra = a.to_ndarray().map_err(|_| NotImplemented)?;
1458 let rc = ra * scalar;
1459 let flat: Vec<f32> = rc.into_iter().collect();
1460 let result = GpuNdarray::<f32>::from_ndarray_data(
1461 &flat,
1462 a.shape.clone(),
1463 Arc::clone(&a.context),
1464 )
1465 .map_err(|_| NotImplemented)?;
1466 Ok(Box::new(Box::new(result) as Box<dyn ArrayProtocol>) as Box<dyn Any>)
1467 }
1468 }
1469
1470 "scirs2::array_protocol::operations::multiply_by_scalar_f64" => {
1472 let a = gpu_arg!(0);
1473 let scalar = kwargs
1474 .values()
1475 .find_map(|v| v.downcast_ref::<f64>().copied())
1476 .ok_or(NotImplemented)? as f32;
1477 if use_gpu {
1478 let result = a
1479 .dispatch_scalar_multiply(scalar)
1480 .map_err(|_| NotImplemented)?;
1481 Ok(Box::new(Box::new(result) as Box<dyn ArrayProtocol>) as Box<dyn Any>)
1482 } else {
1483 let ra = a.to_ndarray().map_err(|_| NotImplemented)?;
1484 let rc = ra * scalar;
1485 let flat: Vec<f32> = rc.into_iter().collect();
1486 let result = GpuNdarray::<f32>::from_ndarray_data(
1487 &flat,
1488 a.shape.clone(),
1489 Arc::clone(&a.context),
1490 )
1491 .map_err(|_| NotImplemented)?;
1492 Ok(Box::new(Box::new(result) as Box<dyn ArrayProtocol>) as Box<dyn Any>)
1493 }
1494 }
1495
1496 "scirs2::array_protocol::operations::divide_by_scalar_f64" => {
1498 let a = gpu_arg!(0);
1499 let scalar = kwargs
1500 .values()
1501 .find_map(|v| v.downcast_ref::<f64>().copied())
1502 .ok_or(NotImplemented)?;
1503 if scalar == 0.0 {
1504 return Err(NotImplemented);
1505 }
1506 let inv = (1.0 / scalar) as f32;
1507 if use_gpu {
1508 let result = a
1509 .dispatch_scalar_multiply(inv)
1510 .map_err(|_| NotImplemented)?;
1511 Ok(Box::new(Box::new(result) as Box<dyn ArrayProtocol>) as Box<dyn Any>)
1512 } else {
1513 let ra = a.to_ndarray().map_err(|_| NotImplemented)?;
1514 let rc = ra * inv;
1515 let flat: Vec<f32> = rc.into_iter().collect();
1516 let result = GpuNdarray::<f32>::from_ndarray_data(
1517 &flat,
1518 a.shape.clone(),
1519 Arc::clone(&a.context),
1520 )
1521 .map_err(|_| NotImplemented)?;
1522 Ok(Box::new(Box::new(result) as Box<dyn ArrayProtocol>) as Box<dyn Any>)
1523 }
1524 }
1525
1526 "scirs2::array_protocol::operations::matmul" => {
1528 let a = gpu_arg!(0);
1529 let b = gpu_arg!(1);
1530 if use_gpu {
1531 let result = a.dispatch_matmul(b).map_err(|_| NotImplemented)?;
1532 Ok(Box::new(Box::new(result) as Box<dyn ArrayProtocol>) as Box<dyn Any>)
1533 } else {
1534 let ra = a.to_ndarray().map_err(|_| NotImplemented)?;
1536 let rb = b.to_ndarray().map_err(|_| NotImplemented)?;
1537 if ra.ndim() != 2 || rb.ndim() != 2 {
1538 return Err(NotImplemented);
1539 }
1540 let ra2 = ra
1541 .into_dimensionality::<ndarray::Ix2>()
1542 .map_err(|_| NotImplemented)?;
1543 let rb2 = rb
1544 .into_dimensionality::<ndarray::Ix2>()
1545 .map_err(|_| NotImplemented)?;
1546 let rc = ra2.dot(&rb2);
1547 let new_shape = vec![rc.nrows(), rc.ncols()];
1548 let flat: Vec<f32> = rc.into_iter().collect();
1549 let result = GpuNdarray::<f32>::from_ndarray_data(
1550 &flat,
1551 new_shape,
1552 Arc::clone(&a.context),
1553 )
1554 .map_err(|_| NotImplemented)?;
1555 Ok(Box::new(Box::new(result) as Box<dyn ArrayProtocol>) as Box<dyn Any>)
1556 }
1557 }
1558
1559 "scirs2::array_protocol::operations::sum" => {
1561 let a = gpu_arg!(0);
1562 let axis = kwargs
1563 .get("axis")
1564 .and_then(|v| v.downcast_ref::<usize>().copied());
1565
1566 match axis {
1567 None => {
1568 if use_gpu {
1570 let total = a.dispatch_sum_all().map_err(|_| NotImplemented)?;
1571 Ok(Box::new(total) as Box<dyn Any>)
1572 } else {
1573 let arr = a.to_ndarray().map_err(|_| NotImplemented)?;
1574 let total: f32 = arr.sum();
1575 Ok(Box::new(total) as Box<dyn Any>)
1576 }
1577 }
1578 Some(ax) => {
1579 let try_gpu = use_gpu && ax < a.shape.len();
1581 if try_gpu {
1582 match a.dispatch_sum_axis(ax) {
1583 Ok(result) => {
1584 return Ok(
1585 Box::new(Box::new(result) as Box<dyn ArrayProtocol>)
1586 as Box<dyn Any>,
1587 );
1588 }
1589 Err(_) => {
1590 }
1592 }
1593 }
1594 let arr = a.to_ndarray().map_err(|_| NotImplemented)?;
1595 let reduced = arr.sum_axis(ndarray::Axis(ax));
1596 let new_shape = reduced.shape().to_vec();
1597 let flat: Vec<f32> = reduced.into_iter().collect();
1598 let result = GpuNdarray::<f32>::from_ndarray_data(
1599 &flat,
1600 new_shape,
1601 Arc::clone(&a.context),
1602 )
1603 .map_err(|_| NotImplemented)?;
1604 Ok(Box::new(Box::new(result) as Box<dyn ArrayProtocol>) as Box<dyn Any>)
1605 }
1606 }
1607 }
1608
1609 "scirs2::array_protocol::operations::transpose" => {
1611 let a = gpu_arg!(0);
1612 if use_gpu && a.shape.len() == 2 {
1613 let result = a.dispatch_transpose_2d().map_err(|_| NotImplemented)?;
1614 Ok(Box::new(Box::new(result) as Box<dyn ArrayProtocol>) as Box<dyn Any>)
1615 } else {
1616 let arr = a.to_ndarray().map_err(|_| NotImplemented)?;
1618 let transposed = arr.t().to_owned();
1619 let new_shape = transposed.shape().to_vec();
1620 let flat: Vec<f32> = transposed.into_iter().collect();
1621 let result = GpuNdarray::<f32>::from_ndarray_data(
1622 &flat,
1623 new_shape,
1624 Arc::clone(&a.context),
1625 )
1626 .map_err(|_| NotImplemented)?;
1627 Ok(Box::new(Box::new(result) as Box<dyn ArrayProtocol>) as Box<dyn Any>)
1628 }
1629 }
1630
1631 "scirs2::array_protocol::operations::concatenate" => {
1633 let axis = kwargs
1635 .values()
1636 .find_map(|v| v.downcast_ref::<usize>().copied())
1637 .unwrap_or(0);
1638
1639 let gpu_arrays: Vec<&GpuNdarray<f32>> = args
1640 .iter()
1641 .filter_map(|arg| {
1642 arg.downcast_ref::<Box<dyn ArrayProtocol>>()
1643 .and_then(|ap| ap.as_any().downcast_ref::<GpuNdarray<f32>>())
1644 })
1645 .collect();
1646
1647 if gpu_arrays.is_empty() {
1648 return Err(NotImplemented);
1649 }
1650
1651 let cpu_concat_fallback = |gpu_arrays: &[&GpuNdarray<f32>],
1653 axis: usize|
1654 -> Result<Box<dyn Any>, NotImplemented> {
1655 let arrs: Vec<ndarray::ArrayD<f32>> = gpu_arrays
1656 .iter()
1657 .map(|g| g.to_ndarray())
1658 .collect::<Result<Vec<_>, _>>()
1659 .map_err(|_| NotImplemented)?;
1660 let views: Vec<ndarray::ArrayViewD<f32>> =
1661 arrs.iter().map(|a| a.view()).collect();
1662 let concatenated = ndarray::concatenate(ndarray::Axis(axis), &views)
1663 .map_err(|_| NotImplemented)?;
1664 let ctx = Arc::clone(&gpu_arrays[0].context);
1665 let new_shape = concatenated.shape().to_vec();
1666 let flat: Vec<f32> = concatenated.into_iter().collect();
1667 let result = GpuNdarray::<f32>::from_ndarray_data(&flat, new_shape, ctx)
1668 .map_err(|_| NotImplemented)?;
1669 Ok(Box::new(Box::new(result) as Box<dyn ArrayProtocol>) as Box<dyn Any>)
1670 };
1671
1672 if axis == 0 && use_gpu {
1673 let result = GpuNdarray::<f32>::dispatch_concatenate_axis0(&gpu_arrays)
1674 .map_err(|_| NotImplemented)?;
1675 Ok(Box::new(Box::new(result) as Box<dyn ArrayProtocol>) as Box<dyn Any>)
1676 } else if axis > 0
1677 && use_gpu
1678 && gpu_arrays.len() >= 2
1679 && gpu_arrays[0].shape.len() <= 8
1680 && gpu_arrays[0].shape.iter().product::<usize>() >= GPU_THRESHOLD
1681 {
1682 let mut acc = gpu_arrays[0].clone();
1684 let mut gpu_failed = false;
1685 for next in gpu_arrays.iter().skip(1) {
1686 match GpuNdarray::<f32>::dispatch_concatenate_axisn(&acc, next, axis) {
1687 Ok(r) => acc = r,
1688 Err(_) => {
1689 gpu_failed = true;
1690 break;
1691 }
1692 }
1693 }
1694 if gpu_failed {
1695 cpu_concat_fallback(&gpu_arrays, axis)
1696 } else {
1697 Ok(Box::new(Box::new(acc) as Box<dyn ArrayProtocol>) as Box<dyn Any>)
1698 }
1699 } else {
1700 cpu_concat_fallback(&gpu_arrays, axis)
1702 }
1703 }
1704
1705 "scirs2::array_protocol::operations::reshape" => {
1707 let a = gpu_arg!(0);
1708 let new_shape = kwargs
1709 .get("shape")
1710 .and_then(|v| v.downcast_ref::<Vec<usize>>().cloned())
1711 .ok_or(NotImplemented)?;
1712 let result = a.dispatch_reshape(new_shape).map_err(|_| NotImplemented)?;
1713 Ok(Box::new(Box::new(result) as Box<dyn ArrayProtocol>) as Box<dyn Any>)
1714 }
1715
1716 "scirs2::array_protocol::operations::svd" => {
1719 let a = gpu_arg!(0);
1720 let arr = a.to_ndarray().map_err(|_| NotImplemented)?;
1721 if arr.ndim() != 2 {
1722 return Err(NotImplemented);
1723 }
1724 let arr2 = arr
1725 .into_dimensionality::<ndarray::Ix2>()
1726 .map_err(|_| NotImplemented)?;
1727 let ctx = Arc::clone(&a.context);
1728
1729 #[cfg(feature = "linalg")]
1730 {
1731 let svd_result =
1732 crate::linalg::svd_ndarray(&arr2).map_err(|_| NotImplemented)?;
1733 let (u_rows, u_cols) = svd_result.u.dim();
1734 let (vt_rows, vt_cols) = svd_result.vt.dim();
1735 let s_len = svd_result.s.len();
1736 let u_data: Vec<f32> = svd_result.u.into_iter().collect();
1737 let s_data: Vec<f32> = svd_result.s.into_iter().collect();
1738 let vt_data: Vec<f32> = svd_result.vt.into_iter().collect();
1739
1740 let u_gpu = GpuNdarray::<f32>::from_ndarray_data(
1741 &u_data,
1742 vec![u_rows, u_cols],
1743 Arc::clone(&ctx),
1744 )
1745 .map_err(|_| NotImplemented)?;
1746 let s_gpu = GpuNdarray::<f32>::from_ndarray_data(
1747 &s_data,
1748 vec![s_len],
1749 Arc::clone(&ctx),
1750 )
1751 .map_err(|_| NotImplemented)?;
1752 let vt_gpu =
1753 GpuNdarray::<f32>::from_ndarray_data(&vt_data, vec![vt_rows, vt_cols], ctx)
1754 .map_err(|_| NotImplemented)?;
1755
1756 Ok(Box::new((
1757 Box::new(u_gpu) as Box<dyn ArrayProtocol>,
1758 Box::new(s_gpu) as Box<dyn ArrayProtocol>,
1759 Box::new(vt_gpu) as Box<dyn ArrayProtocol>,
1760 )) as Box<dyn Any>)
1761 }
1762 #[cfg(not(feature = "linalg"))]
1763 {
1764 let _ = (arr2, ctx);
1768 Err(NotImplemented)
1769 }
1770 }
1771
1772 "scirs2::array_protocol::operations::inverse" => {
1775 let a = gpu_arg!(0);
1776 let arr = a.to_ndarray().map_err(|_| NotImplemented)?;
1777 if arr.ndim() != 2 || arr.shape()[0] != arr.shape()[1] {
1778 return Err(NotImplemented);
1779 }
1780 let m = arr.shape()[0];
1781 let arr2 = arr
1782 .into_dimensionality::<ndarray::Ix2>()
1783 .map_err(|_| NotImplemented)?;
1784 let ctx = Arc::clone(&a.context);
1785
1786 #[cfg(feature = "linalg")]
1787 {
1788 let inv = crate::linalg::inv_ndarray(&arr2).map_err(|_| NotImplemented)?;
1789 let inv_data: Vec<f32> = inv.into_iter().collect();
1790 let result = GpuNdarray::<f32>::from_ndarray_data(&inv_data, vec![m, m], ctx)
1791 .map_err(|_| NotImplemented)?;
1792 Ok(Box::new(Box::new(result) as Box<dyn ArrayProtocol>) as Box<dyn Any>)
1793 }
1794 #[cfg(not(feature = "linalg"))]
1795 {
1796 let _ = (arr2, ctx, m);
1800 Err(NotImplemented)
1801 }
1802 }
1803
1804 _ => Err(NotImplemented),
1805 }
1806 }
1807
1808 fn as_any(&self) -> &dyn Any {
1809 self
1810 }
1811
1812 fn shape(&self) -> &[usize] {
1813 &self.shape
1814 }
1815
1816 fn dtype(&self) -> TypeId {
1817 TypeId::of::<f32>()
1818 }
1819
1820 fn box_clone(&self) -> Box<dyn ArrayProtocol> {
1821 Box::new(self.clone())
1822 }
1823}
1824
1825impl GPUArray for GpuNdarray<f32> {
1830 fn to_gpu(&self) -> CoreResult<Box<dyn GPUArray>> {
1831 Ok(Box::new(self.clone()))
1833 }
1834
1835 fn to_cpu(&self) -> CoreResult<Box<dyn ArrayProtocol>> {
1836 let arr = self.to_ndarray().map_err(|e| {
1837 CoreError::ComputationError(ErrorContext::new(format!("GPU→CPU readback: {e}")))
1838 })?;
1839 Ok(Box::new(NdarrayWrapper::new(arr)))
1840 }
1841
1842 fn is_on_gpu(&self) -> bool {
1843 true
1844 }
1845
1846 fn device_info(&self) -> HashMap<String, String> {
1847 let mut info = HashMap::new();
1848 info.insert("backend".to_string(), "wgpu".to_string());
1849 info.insert("dtype".to_string(), "f32".to_string());
1850 info.insert("shape".to_string(), format!("{:?}", self.shape));
1851 info
1852 }
1853}
1854
1855use super::gpu_ndarray_shaders::{
1859 CONCAT_AXISN_WGSL, ELEMENTWISE_ADD_WGSL, ELEMENTWISE_MUL_WGSL, ELEMENTWISE_SUB_WGSL,
1860 MATMUL_WGSL, REDUCE_SUM_AXIS_WGSL, SCALAR_MUL_WGSL, SUM_REDUCE_WGSL, TRANSPOSE_WGSL,
1861};
1862
1863#[cfg(all(test, feature = "linalg"))]
1869#[path = "gpu_ndarray_dispatch_tests.rs"]
1870mod dispatch_correctness_tests;