pub mod activation;
pub mod arith;
pub mod conv;
pub mod index;
pub mod matmul;
pub mod norm;
pub mod pool;
pub mod reduce;
pub mod unary;
pub mod view;
use rayon::prelude::*;
use crate::cuda::{ffi, kernels};
use super::Tensor;
use super::shape;
use super::storage::Storage;
const PARALLEL_THRESHOLD: usize = 1 << 15;
pub(crate) fn unary_op(
x: &Tensor,
cpu: impl Fn(f32) -> f32 + Send + Sync,
gpu: ffi::UnaryKernel,
) -> Tensor {
match x.storage() {
Storage::Cpu(data) => Tensor::from_vec(map(data, cpu), x.shape()),
Storage::Cuda(buf) => {
let out = kernels::unary(buf, x.numel(), gpu).expect("cuda unary op");
Tensor::raw(Storage::Cuda(out), x.shape().to_vec(), x.device())
}
}
}
pub(crate) fn scalar_op(
x: &Tensor,
s: f32,
cpu: impl Fn(f32, f32) -> f32 + Send + Sync,
gpu: ffi::ScalarKernel,
) -> Tensor {
match x.storage() {
Storage::Cpu(data) => Tensor::from_vec(map(data, |v| cpu(v, s)), x.shape()),
Storage::Cuda(buf) => {
let out = kernels::scalar(buf, s, x.numel(), gpu).expect("cuda scalar op");
Tensor::raw(Storage::Cuda(out), x.shape().to_vec(), x.device())
}
}
}
pub(crate) fn binary_op(
a: &Tensor,
b: &Tensor,
cpu: impl Fn(f32, f32) -> f32 + Send + Sync,
gpu: ffi::BinaryKernel,
) -> Tensor {
assert_eq!(
a.device(),
b.device(),
"device mismatch: {} and {}; move one with .to()",
a.device(),
b.device()
);
if a.shape() != b.shape() {
return broadcast_binary_op(a, b, cpu, gpu);
}
match (a.storage(), b.storage()) {
(Storage::Cpu(x), Storage::Cpu(y)) => Tensor::from_vec(zip(x, y, cpu), a.shape()),
(Storage::Cuda(x), Storage::Cuda(y)) => {
let out = kernels::binary(x, y, a.numel(), gpu).expect("cuda binary op");
Tensor::raw(Storage::Cuda(out), a.shape().to_vec(), a.device())
}
_ => unreachable!("devices already checked equal"),
}
}
fn broadcast_binary_op(
a: &Tensor,
b: &Tensor,
cpu: impl Fn(f32, f32) -> f32 + Send + Sync,
gpu: ffi::BinaryKernel,
) -> Tensor {
let out_shape = shape::broadcast(a.shape(), b.shape());
if a.is_cuda() {
let a_full = view::broadcast_to(a, &out_shape);
let b_full = view::broadcast_to(b, &out_shape);
return binary_op(&a_full, &b_full, cpu, gpu);
}
let ndim = out_shape.len();
let out_strides = shape::strides_for(&out_shape);
let a_strides = shape::broadcast_strides(&shape::pad_left(a.shape(), ndim), &out_shape);
let b_strides = shape::broadcast_strides(&shape::pad_left(b.shape(), ndim), &out_shape);
let (a_data, b_data) = (a.to_vec(), b.to_vec());
let data: Vec<f32> = (0..shape::numel(&out_shape))
.map(|flat| {
let (mut rest, mut ai, mut bi) = (flat, 0usize, 0usize);
for d in 0..ndim {
let coord = rest / out_strides[d];
rest %= out_strides[d];
ai += coord * a_strides[d];
bi += coord * b_strides[d];
}
cpu(a_data[ai], b_data[bi])
})
.collect();
Tensor::from_vec(data, &out_shape)
}
pub(crate) fn map(data: &[f32], f: impl Fn(f32) -> f32 + Send + Sync) -> Vec<f32> {
if data.len() >= PARALLEL_THRESHOLD {
data.par_iter().map(|&v| f(v)).collect()
} else {
data.iter().map(|&v| f(v)).collect()
}
}
pub(crate) fn zip(a: &[f32], b: &[f32], f: impl Fn(f32, f32) -> f32 + Send + Sync) -> Vec<f32> {
debug_assert_eq!(a.len(), b.len());
if a.len() >= PARALLEL_THRESHOLD {
a.par_iter()
.zip(b.par_iter())
.map(|(&x, &y)| f(x, y))
.collect()
} else {
a.iter().zip(b.iter()).map(|(&x, &y)| f(x, y)).collect()
}
}