use super::super::{AutoGpuSelector, GpuBuffer, GpuContext, GpuDeviceInfo, GpuLinalgOps};
use crate::error::{LinalgError, LinalgResult};
use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2};
use scirs2_core::numeric::{Float, NumAssign, Zero};
use std::fmt::Debug;
pub const DEFAULT_GPU_THRESHOLD: usize = 50_000;
pub struct GpuOperationDispatcher<T>
where
T: Float + NumAssign + Zero + Send + Sync + Debug + 'static,
{
gpu_threshold: usize,
_phantom: std::marker::PhantomData<T>,
}
impl<T> GpuOperationDispatcher<T>
where
T: Float + NumAssign + Zero + Send + Sync + Debug + 'static,
{
pub fn new() -> Self {
Self {
gpu_threshold: DEFAULT_GPU_THRESHOLD,
_phantom: std::marker::PhantomData,
}
}
pub fn with_threshold(threshold: usize) -> Self {
Self {
gpu_threshold: threshold,
_phantom: std::marker::PhantomData,
}
}
pub fn set_threshold(&mut self, threshold: usize) {
self.gpu_threshold = threshold;
}
pub fn threshold(&self) -> usize {
self.gpu_threshold
}
}
impl<T> Default for GpuOperationDispatcher<T>
where
T: Float + NumAssign + Zero + Send + Sync + Debug + 'static,
{
fn default() -> Self {
Self::new()
}
}
impl<T> GpuLinalgOps<T> for GpuOperationDispatcher<T>
where
T: Float + NumAssign + Zero + Send + Sync + Debug + 'static,
{
fn gpu_matvec(
&self,
ctx: &dyn GpuContext,
a: &ArrayView2<T>,
x: &ArrayView1<T>,
) -> LinalgResult<Array1<T>> {
let (m, n) = a.dim();
if n != x.len() {
return Err(LinalgError::ShapeError(format!(
"Matrix columns ({}) must match vector length ({})",
n,
x.len()
)));
}
let required_memory = (m * n + n + m) * std::mem::size_of::<T>();
let available_memory = ctx.available_memory()?;
if required_memory > available_memory {
return self.cpu_matvec(a, x);
}
let mut a_buffer = self.allocate_buffer_from_context::<T>(ctx, m * n)?;
let mut x_buffer = self.allocate_buffer_from_context::<T>(ctx, n)?;
let mut y_buffer = self.allocate_buffer_from_context::<T>(ctx, m)?;
let a_flat: Vec<T> = a.iter().cloned().collect();
let x_flat: Vec<T> = x.iter().cloned().collect();
a_buffer.copy_from_host(&a_flat)?;
x_buffer.copy_from_host(&x_flat)?;
self.execute_matvec_kernel(
ctx,
a_buffer.as_ref(),
x_buffer.as_ref(),
y_buffer.as_mut(),
m,
n,
)?;
let mut result_data = vec![T::zero(); m];
y_buffer.copy_to_host(&mut result_data)?;
Ok(Array1::from_vec(result_data))
}
fn gpu_matmul(
&self,
ctx: &dyn GpuContext,
a: &ArrayView2<T>,
b: &ArrayView2<T>,
) -> LinalgResult<Array2<T>> {
let (m, k1) = a.dim();
let (k2, n) = b.dim();
if k1 != k2 {
return Err(LinalgError::ShapeError(format!(
"Matrix dimensions mismatch: {}x{} * {}x{}",
m, k1, k2, n
)));
}
let k = k1;
let required_memory = (m * k + k * n + m * n) * std::mem::size_of::<T>();
let available_memory = ctx.available_memory()?;
if required_memory > available_memory {
return self.cpu_matmul(a, b);
}
let mut a_buffer = self.allocate_buffer_from_context::<T>(ctx, m * k)?;
let mut b_buffer = self.allocate_buffer_from_context::<T>(ctx, k * n)?;
let mut c_buffer = self.allocate_buffer_from_context::<T>(ctx, m * n)?;
let a_flat: Vec<T> = a.iter().cloned().collect();
let b_flat: Vec<T> = b.iter().cloned().collect();
a_buffer.copy_from_host(&a_flat)?;
b_buffer.copy_from_host(&b_flat)?;
self.execute_matmul_kernel(
ctx,
a_buffer.as_ref(),
b_buffer.as_ref(),
c_buffer.as_mut(),
m,
n,
k,
)?;
let mut result_data = vec![T::zero(); m * n];
c_buffer.copy_to_host(&mut result_data)?;
let result_array = Array2::from_shape_vec((m, n), result_data)
.map_err(|e| LinalgError::ComputationError(format!("Shape error: {}", e)))?;
Ok(result_array)
}
fn gpu_dot(
&self,
ctx: &dyn GpuContext,
x: &ArrayView1<T>,
y: &ArrayView1<T>,
) -> LinalgResult<T> {
if x.len() != y.len() {
return Err(LinalgError::ShapeError(format!(
"Vector lengths must match: {} != {}",
x.len(),
y.len()
)));
}
Ok(Self::cpu_dot_static(x, y))
}
fn gpu_norm(&self, ctx: &dyn GpuContext, x: &ArrayView1<T>) -> LinalgResult<T> {
Ok(Self::cpu_norm_static(x))
}
fn gpu_elementwise_add(
&self,
ctx: &dyn GpuContext,
a: &ArrayView2<T>,
b: &ArrayView2<T>,
) -> LinalgResult<Array2<T>> {
if a.shape() != b.shape() {
return Err(LinalgError::ShapeError(format!(
"Matrix shapes must match: {:?} != {:?}",
a.shape(),
b.shape()
)));
}
Self::cpu_elementwise_add_static(a, b)
}
fn gpu_elementwise_mul(
&self,
ctx: &dyn GpuContext,
a: &ArrayView2<T>,
b: &ArrayView2<T>,
) -> LinalgResult<Array2<T>> {
if a.shape() != b.shape() {
return Err(LinalgError::ShapeError(format!(
"Matrix shapes must match: {:?} != {:?}",
a.shape(),
b.shape()
)));
}
Self::cpu_elementwise_mul_static(a, b)
}
}
impl<T> GpuOperationDispatcher<T>
where
T: Float + NumAssign + Zero + Send + Sync + Debug + 'static,
{
fn execute_matvec_kernel(
&self,
_ctx: &dyn GpuContext,
a_buffer: &dyn GpuBuffer<T>,
x_buffer: &dyn GpuBuffer<T>,
y_buffer: &mut dyn GpuBuffer<T>,
m: usize,
n: usize,
) -> LinalgResult<()> {
self.cpu_fallback_matvec(a_buffer, x_buffer, y_buffer, m, n)
}
fn execute_matmul_kernel(
&self,
_ctx: &dyn GpuContext,
a_buffer: &dyn GpuBuffer<T>,
b_buffer: &dyn GpuBuffer<T>,
c_buffer: &mut dyn GpuBuffer<T>,
m: usize,
n: usize,
k: usize,
) -> LinalgResult<()> {
self.cpu_fallback_matmul(a_buffer, b_buffer, c_buffer, m, n, k)
}
fn cpu_fallback_matvec(
&self,
a_buffer: &dyn GpuBuffer<T>,
x_buffer: &dyn GpuBuffer<T>,
y_buffer: &mut dyn GpuBuffer<T>,
m: usize,
n: usize,
) -> LinalgResult<()> {
let mut a_data = vec![T::zero(); m * n];
let mut x_data = vec![T::zero(); n];
let mut y_data = vec![T::zero(); m];
a_buffer.copy_to_host(&mut a_data)?;
x_buffer.copy_to_host(&mut x_data)?;
for i in 0..m {
let mut sum = T::zero();
for j in 0..n {
sum += a_data[i * n + j] * x_data[j];
}
y_data[i] = sum;
}
y_buffer.copy_from_host(&y_data)?;
Ok(())
}
fn cpu_fallback_matmul(
&self,
a_buffer: &dyn GpuBuffer<T>,
b_buffer: &dyn GpuBuffer<T>,
c_buffer: &mut dyn GpuBuffer<T>,
m: usize,
n: usize,
k: usize,
) -> LinalgResult<()> {
let mut a_data = vec![T::zero(); m * k];
let mut b_data = vec![T::zero(); k * n];
let mut c_data = vec![T::zero(); m * n];
a_buffer.copy_to_host(&mut a_data)?;
b_buffer.copy_to_host(&mut b_data)?;
for i in 0..m {
for j in 0..n {
let mut sum = T::zero();
for l in 0..k {
sum += a_data[i * k + l] * b_data[l * n + j];
}
c_data[i * n + j] = sum;
}
}
c_buffer.copy_from_host(&c_data)?;
Ok(())
}
pub fn cpu_matvec(&self, a: &ArrayView2<T>, x: &ArrayView1<T>) -> LinalgResult<Array1<T>> {
let (m, n) = a.dim();
let mut result = Array1::zeros(m);
for i in 0..m {
let mut sum = T::zero();
for j in 0..n {
sum += a[[i, j]] * x[j];
}
result[i] = sum;
}
Ok(result)
}
pub fn cpu_matmul(&self, a: &ArrayView2<T>, b: &ArrayView2<T>) -> LinalgResult<Array2<T>> {
let (m, k) = a.dim();
let (_, n) = b.dim();
let mut result = Array2::zeros((m, n));
for i in 0..m {
for j in 0..n {
let mut sum = T::zero();
for l in 0..k {
sum += a[[i, l]] * b[[l, j]];
}
result[[i, j]] = sum;
}
}
Ok(result)
}
fn cpu_dot(&self, x: &ArrayView1<T>, y: &ArrayView1<T>) -> T {
let mut result = T::zero();
for (a, b) in x.iter().zip(y.iter()) {
result += *a * *b;
}
result
}
fn cpu_dot_static(x: &ArrayView1<T>, y: &ArrayView1<T>) -> T {
let mut result = T::zero();
for (a, b) in x.iter().zip(y.iter()) {
result += *a * *b;
}
result
}
fn cpu_norm(&self, x: &ArrayView1<T>) -> T {
let mut sum_sq = T::zero();
for &val in x.iter() {
sum_sq += val * val;
}
sum_sq.sqrt()
}
fn cpu_norm_static(x: &ArrayView1<T>) -> T {
let mut sum_sq = T::zero();
for &val in x.iter() {
sum_sq += val * val;
}
sum_sq.sqrt()
}
fn cpu_elementwise_add(&self, a: &ArrayView2<T>, b: &ArrayView2<T>) -> LinalgResult<Array2<T>> {
let mut result = Array2::zeros(a.dim());
for ((i, j), &val_a) in a.indexed_iter() {
result[[i, j]] = val_a + b[[i, j]];
}
Ok(result)
}
fn cpu_elementwise_add_static(a: &ArrayView2<T>, b: &ArrayView2<T>) -> LinalgResult<Array2<T>> {
let mut result = Array2::zeros(a.dim());
for ((i, j), &val_a) in a.indexed_iter() {
result[[i, j]] = val_a + b[[i, j]];
}
Ok(result)
}
fn cpu_elementwise_mul(&self, a: &ArrayView2<T>, b: &ArrayView2<T>) -> LinalgResult<Array2<T>> {
let mut result = Array2::zeros(a.dim());
for ((i, j), &val_a) in a.indexed_iter() {
result[[i, j]] = val_a * b[[i, j]];
}
Ok(result)
}
fn cpu_elementwise_mul_static(a: &ArrayView2<T>, b: &ArrayView2<T>) -> LinalgResult<Array2<T>> {
let mut result = Array2::zeros(a.dim());
for ((i, j), &val_a) in a.indexed_iter() {
result[[i, j]] = val_a * b[[i, j]];
}
Ok(result)
}
fn allocate_buffer_from_context<U: Clone + Send + Sync + Copy + std::fmt::Debug + 'static>(
&self,
ctx: &dyn GpuContext,
size: usize,
) -> LinalgResult<Box<dyn GpuBuffer<U>>> {
use crate::gpu::acceleration::CpuFallbackBuffer;
Ok(Box::new(CpuFallbackBuffer::new(size)))
}
}
impl<T> AutoGpuSelector<T> for GpuOperationDispatcher<T>
where
T: Float + NumAssign + Zero + Send + Sync + Debug + 'static,
{
fn auto_matvec(
&self,
a: &ArrayView2<T>,
x: &ArrayView1<T>,
gpu_context: Option<&dyn GpuContext>,
) -> LinalgResult<Array1<T>> {
let elements = a.len();
if let Some(ctx) = gpu_context {
if elements > self.gpu_threshold {
return self.gpu_matvec(ctx, a, x);
}
}
self.cpu_matvec(a, x)
}
fn auto_matmul(
&self,
a: &ArrayView2<T>,
b: &ArrayView2<T>,
gpu_context: Option<&dyn GpuContext>,
) -> LinalgResult<Array2<T>> {
let elements = a.len() + b.len();
if let Some(ctx) = gpu_context {
if elements > self.gpu_threshold {
return self.gpu_matmul(ctx, a, b);
}
}
self.cpu_matmul(a, b)
}
}