use crate::kernels::conversion::convert_apply;
use crate::kernels::creation::{arange_apply, eye_apply, full_apply, linspace as linspace_kernel};
use crate::tensor::{IntoPartition, Reshape, Storage, Tensor, Unpartition};
use cuda_async::device_context::with_default_device_policy;
use cuda_async::device_future::DeviceFuture;
use cuda_async::device_operation::{
value, with_context, DeviceOp, ExecutionContext, GraphNode, Unzippable1, Unzippable2,
};
use cuda_async::error::DeviceError;
use cuda_core::curand::{RandNormal, RandUniform, RNG};
use cuda_core::sys::CUdeviceptr;
use cuda_core::DType;
use cuda_core::{memcpy_dtod_async, memcpy_dtoh_async, memcpy_htod_async};
use half::f16;
use std::future::IntoFuture;
use std::sync::Arc;
pub struct CopyDeviceToDevice<T: DType> {
_storage: Arc<Storage>, src_ptr: CUdeviceptr,
shape: Vec<i32>,
strides: Vec<i32>,
num_elements: usize,
_dtype: std::marker::PhantomData<T>,
}
impl<T: DType> DeviceOp for CopyDeviceToDevice<T> {
type Output = Tensor<T>;
unsafe fn execute(
self,
ctx: &ExecutionContext,
) -> Result<<Self as DeviceOp>::Output, DeviceError> {
let num_bytes = self.num_elements * std::mem::size_of::<T>();
let dst = ctx.alloc_async(num_bytes)?;
memcpy_dtod_async::<T>(dst, self.src_ptr, self.num_elements, ctx.get_cuda_stream())?;
Ok(Tensor::from_raw_parts(
dst,
num_bytes,
ctx.get_device_id(),
self.shape,
self.strides,
))
}
}
impl<T: DType> IntoFuture for CopyDeviceToDevice<T> {
type Output = Result<Tensor<T>, DeviceError>;
type IntoFuture = DeviceFuture<Tensor<T>, CopyDeviceToDevice<T>>;
fn into_future(self) -> Self::IntoFuture {
match with_default_device_policy(|policy| {
let stream = policy.next_stream()?;
Ok(DeviceFuture::scheduled(self, ExecutionContext::new(stream)))
}) {
Ok(Ok(future)) => future,
Ok(Err(e)) => DeviceFuture::failed(e),
Err(e) => DeviceFuture::failed(e),
}
}
}
pub fn dup<T: DType>(tensor: &Tensor<T>) -> impl DeviceOp<Output = Tensor<T>> {
CopyDeviceToDevice {
_storage: tensor.storage.clone(),
src_ptr: tensor.cu_deviceptr(),
shape: tensor.shape.clone(),
strides: tensor.strides.clone(),
num_elements: tensor.size(),
_dtype: std::marker::PhantomData,
}
}
pub fn memcpy<'a, T: DType>(dst: &'a mut Tensor<T>, src: &'a Tensor<T>) -> Memcpy<'a> {
assert_eq!(
src.size(),
dst.size(),
"memcpy: src length ({}) != dst length ({})",
src.size(),
dst.size(),
);
Memcpy {
src_ptr: src.cu_deviceptr(),
dst_ptr: dst.cu_deviceptr(),
len: dst.num_bytes(),
_borrow: std::marker::PhantomData,
}
}
pub struct Memcpy<'a> {
src_ptr: cuda_core::sys::CUdeviceptr,
dst_ptr: cuda_core::sys::CUdeviceptr,
len: usize,
_borrow: std::marker::PhantomData<&'a mut ()>,
}
impl<'a> DeviceOp for Memcpy<'a> {
type Output = ();
unsafe fn execute(self, ctx: &ExecutionContext) -> Result<(), DeviceError> {
memcpy_dtod_async::<u8>(self.dst_ptr, self.src_ptr, self.len, ctx.get_cuda_stream())?;
Ok(())
}
}
impl<'a> GraphNode for Memcpy<'a> {}
impl<'a> IntoFuture for Memcpy<'a> {
type Output = Result<(), DeviceError>;
type IntoFuture = DeviceFuture<(), Memcpy<'a>>;
fn into_future(self) -> Self::IntoFuture {
match with_default_device_policy(|policy| {
let stream = policy.next_stream()?;
Ok(DeviceFuture::scheduled(self, ExecutionContext::new(stream)))
}) {
Ok(Ok(future)) => future,
Ok(Err(e)) => DeviceFuture::failed(e),
Err(e) => DeviceFuture::failed(e),
}
}
}
struct CopyDeviceToHostVec<T: DType> {
tensor: Arc<Tensor<T>>,
}
impl<T: DType> DeviceOp for CopyDeviceToHostVec<T> {
type Output = Vec<T>;
unsafe fn execute(
self,
ctx: &ExecutionContext,
) -> Result<<Self as DeviceOp>::Output, DeviceError> {
let cu_deviceptr = self.tensor.cu_deviceptr();
let size = self.tensor.size();
let mut host = Vec::<T>::with_capacity(size);
if size > 0 {
unsafe {
memcpy_dtoh_async(host.as_mut_ptr(), cu_deviceptr, size, ctx.get_cuda_stream())
}?;
}
unsafe { host.set_len(size) };
Ok(host)
}
}
impl<T: DType> IntoFuture for CopyDeviceToHostVec<T> {
type Output = Result<Vec<T>, DeviceError>;
type IntoFuture = DeviceFuture<Vec<T>, CopyDeviceToHostVec<T>>;
fn into_future(self) -> Self::IntoFuture {
match with_default_device_policy(|policy| {
let stream = policy.next_stream()?;
Ok(DeviceFuture::scheduled(self, ExecutionContext::new(stream)))
}) {
Ok(Ok(future)) => future,
Ok(Err(e)) => DeviceFuture::failed(e),
Err(e) => DeviceFuture::failed(e),
}
}
}
pub fn copy_device_to_host_vec<T: DType>(
tensor: &Arc<Tensor<T>>,
) -> impl DeviceOp<Output = Vec<T>> {
CopyDeviceToHostVec {
tensor: tensor.clone(),
}
}
struct CopyHostVecToDevice<T: DType> {
vec: Arc<Vec<T>>,
}
impl<T: DType> DeviceOp for CopyHostVecToDevice<T> {
type Output = Tensor<T>;
unsafe fn execute(
self,
ctx: &ExecutionContext,
) -> Result<<Self as DeviceOp>::Output, DeviceError> {
let vec = self.vec;
let element_size = std::mem::size_of::<T>();
let num_elements = vec.len();
let shape = vec![num_elements as i32];
let strides = vec![1];
let dptr = ctx.alloc_async(element_size * num_elements)?;
memcpy_htod_async(dptr, vec.as_ptr(), num_elements, ctx.get_cuda_stream())?;
Ok(Tensor::from_raw_parts(
dptr,
element_size * num_elements,
ctx.get_device_id(),
shape.clone(),
strides.clone(),
))
}
}
impl<T: DType> IntoFuture for CopyHostVecToDevice<T> {
type Output = Result<Tensor<T>, DeviceError>;
type IntoFuture = DeviceFuture<Tensor<T>, CopyHostVecToDevice<T>>;
fn into_future(self) -> Self::IntoFuture {
match with_default_device_policy(|policy| {
let stream = policy.next_stream()?;
Ok(DeviceFuture::scheduled(self, ExecutionContext::new(stream)))
}) {
Ok(Ok(future)) => future,
Ok(Err(e)) => DeviceFuture::failed(e),
Err(e) => DeviceFuture::failed(e),
}
}
}
pub fn copy_host_vec_to_device<T: DType>(vec: &Arc<Vec<T>>) -> impl DeviceOp<Output = Tensor<T>> {
CopyHostVecToDevice { vec: vec.clone() }
}
pub fn meta<T: DType>(shape: &[usize]) -> impl DeviceOp<Output = Tensor<T>> {
let shape_i32: Vec<i32> = shape
.iter()
.map(|&d| {
i32::try_from(d).unwrap_or_else(|_| {
panic!("meta tensor dimension {d} exceeds i32::MAX ({})", i32::MAX)
})
})
.collect();
with_context(move |ctx| value(Tensor::<T>::from_meta(shape_i32, ctx.get_device_id())))
}
pub fn zeros<T: DType>(shape: &[usize]) -> impl DeviceOp<Output = Tensor<T>> {
full(T::zero(), shape)
}
pub fn ones<T: DType>(shape: &[usize]) -> impl DeviceOp<Output = Tensor<T>> {
full(T::one(), shape)
}
pub fn full<T: DType>(val: T, shape: &[usize]) -> impl DeviceOp<Output = Tensor<T>> {
let shape = shape.to_vec();
let len = shape.iter().product::<usize>();
Tensor::<T>::uninitialized(len).then(move |t| {
let partition_size = 128;
let result = unsafe { t.assume_init() }.partition([partition_size]);
let (_, res) = value((val, result)).then(full_apply).unzip();
res.unpartition().reshape(&shape)
})
}
pub fn fill<T: DType>(tensor: Tensor<T>, val: T) -> impl DeviceOp<Output = Tensor<T>> {
value(tensor).then(move |t| {
let partition_size = 128;
let result = t.partition([partition_size]);
let (_, res) = value((val, result)).then(full_apply).unzip();
res.unpartition()
})
}
pub fn arange<T: DType>(len: usize) -> impl DeviceOp<Output = Tensor<T>> {
Tensor::<T>::uninitialized(len).then(move |t| {
let partition_size = 128;
let result = unsafe { t.assume_init() }.partition([partition_size]);
let res = value((result,)).then(arange_apply).unzip();
res.0.unpartition()
})
}
pub fn linspace(start: f32, stop: f32, n: usize) -> impl DeviceOp<Output = Tensor<f32>> {
let step = if n > 1 {
(stop - start) / (n - 1) as f32
} else {
0.0
};
Tensor::<f32>::uninitialized(n).then(move |t| {
let partition_size = 128;
let result = unsafe { t.assume_init() }.partition([partition_size]);
linspace_kernel(result, start, step)
.then(|(tensor, _, _)| value(tensor))
.unpartition()
})
}
pub fn eye(n: usize) -> impl DeviceOp<Output = Tensor<f32>> {
eye_rect(n, n)
}
pub fn eye_rect(rows: usize, cols: usize) -> impl DeviceOp<Output = Tensor<f32>> {
let br = 16;
let bc = 16;
let Some(len) = rows.checked_mul(cols).filter(|&len| len > 0) else {
return fail::<Tensor<f32>>(format!(
"eye_rect: shape [{rows}, {cols}] is empty or its element count overflows usize"
))
.boxed();
};
Tensor::<f32>::uninitialized(len)
.then(move |t| {
let t2d = unsafe { t.assume_init() }
.reshape(&[rows, cols])
.expect("eye: reshape failed");
let result = t2d.partition([br, bc]);
let res = value((result,)).then(eye_apply).unzip();
res.0.unpartition()
})
.boxed()
}
struct Fail<T> {
message: String,
_output: std::marker::PhantomData<fn() -> T>,
}
fn fail<T: Send>(message: impl Into<String>) -> Fail<T> {
Fail {
message: message.into(),
_output: std::marker::PhantomData,
}
}
impl<T: Send> DeviceOp for Fail<T> {
type Output = T;
unsafe fn execute(self, _ctx: &ExecutionContext) -> Result<T, DeviceError> {
Err(DeviceError::Internal(self.message))
}
}
impl<T: Send> IntoFuture for Fail<T> {
type Output = Result<T, DeviceError>;
type IntoFuture = DeviceFuture<T, Fail<T>>;
fn into_future(self) -> Self::IntoFuture {
match with_default_device_policy(|policy| {
let stream = policy.next_stream()?;
Ok(DeviceFuture::scheduled(self, ExecutionContext::new(stream)))
}) {
Ok(Ok(future)) => future,
Ok(Err(e)) => DeviceFuture::failed(e),
Err(e) => DeviceFuture::failed(e),
}
}
}
pub(crate) struct AllocUninitialized<T: DType> {
len: usize,
_element: std::marker::PhantomData<fn() -> T>,
}
pub(crate) fn alloc_uninitialized<T: DType>(len: usize) -> AllocUninitialized<T> {
AllocUninitialized {
len,
_element: std::marker::PhantomData,
}
}
impl<T: DType> DeviceOp for AllocUninitialized<T> {
type Output = std::mem::MaybeUninit<Tensor<T>>;
unsafe fn execute(
self,
ctx: &ExecutionContext,
) -> Result<<Self as DeviceOp>::Output, DeviceError> {
let num_bytes = self
.len
.checked_mul(std::mem::size_of::<T>())
.ok_or_else(|| {
DeviceError::Internal(format!(
"tensor of {} elements of {} bytes overflows usize",
self.len,
std::mem::size_of::<T>()
))
})?;
let ptr = ctx.alloc_async(num_bytes)?;
Ok(std::mem::MaybeUninit::new(unsafe {
Tensor::from_raw_parts(
ptr,
num_bytes,
ctx.get_device_id(),
vec![self.len as i32],
vec![1],
)
}))
}
}
impl<T: DType> IntoFuture for AllocUninitialized<T> {
type Output = Result<std::mem::MaybeUninit<Tensor<T>>, DeviceError>;
type IntoFuture = DeviceFuture<std::mem::MaybeUninit<Tensor<T>>, AllocUninitialized<T>>;
fn into_future(self) -> Self::IntoFuture {
match with_default_device_policy(|policy| {
let stream = policy.next_stream()?;
Ok(DeviceFuture::scheduled(self, ExecutionContext::new(stream)))
}) {
Ok(Ok(future)) => future,
Ok(Err(e)) => DeviceFuture::failed(e),
Err(e) => DeviceFuture::failed(e),
}
}
}
pub fn convert<FromType: DType, ToType: DType>(
src: Arc<Tensor<FromType>>,
) -> impl DeviceOp<Output = Tensor<ToType>> {
let len = src.size();
Tensor::<ToType>::uninitialized(len).then(move |t| {
let partition_size = 128;
let dst = unsafe { t.assume_init() }.partition([partition_size]);
let res = value((src.clone(), dst)).then(convert_apply).unzip();
res.1
.unpartition()
.reshape(&src.shape.iter().map(|x| *x as usize).collect::<Vec<_>>())
})
}
pub fn randn<T: DType + RandNormal, const RANK: usize>(
mean: T,
std: T,
shape: [usize; RANK],
seed: Option<u64>,
) -> impl DeviceOp<Output = Tensor<T>> {
let len = shape.iter().product::<usize>();
Tensor::<T>::uninitialized(len).and_then_with_context(move |ctx, t| unsafe {
let t = t.assume_init();
let rng = RNG::new_on_stream(seed, ctx.get_cuda_stream());
T::generate_normal(&rng, t.cu_deviceptr(), len, mean, std);
value(t.reshape_unchecked(&shape))
})
}
pub fn randn_f16<const RANK: usize>(
mean: f16,
std: f16,
shape: [usize; RANK],
seed: Option<u64>,
) -> impl DeviceOp<Output = Tensor<f16>> {
let len = shape.clone().iter().product::<usize>();
randn(mean.to_f32(), std.to_f32(), [len], seed).then(move |src_tensor| {
let dst = Tensor::<f16>::uninitialized(len);
dst.then(move |dst_tensor| {
let partition_size = 128;
let dst = unsafe { dst_tensor.assume_init() }.partition([partition_size]);
let res = value((Arc::new(src_tensor), dst))
.then(convert_apply)
.unzip();
res.1.unpartition().reshape(shape.as_ref())
})
})
}
pub fn rand<T: DType + RandUniform, const RANK: usize>(
shape: [usize; RANK],
seed: Option<u64>,
) -> impl DeviceOp<Output = Tensor<T>> {
let len = shape.iter().product::<usize>();
Tensor::<T>::uninitialized(len).and_then_with_context(move |ctx, t| unsafe {
let t = t.assume_init();
let rng = RNG::new_on_stream(seed, ctx.get_cuda_stream());
T::generate_uniform(&rng, t.cu_deviceptr(), len);
value(t.reshape_unchecked(&shape))
})
}
pub struct ReshapeOp<O: Send, DI: DeviceOp<Output = O>> {
shape: Vec<usize>,
input: DI,
}
impl<T: DType, DI: DeviceOp<Output = Tensor<T>>> DeviceOp for ReshapeOp<Tensor<T>, DI> {
type Output = Tensor<T>;
unsafe fn execute(self, context: &ExecutionContext) -> Result<Tensor<T>, DeviceError> {
let tensor = self.input.execute(context)?;
tensor
.reshape(&self.shape)
.map_err(|e| DeviceError::Internal(e.to_string()))
}
}
impl<T: DType, DI: DeviceOp<Output = Tensor<T>>> IntoFuture for ReshapeOp<Tensor<T>, DI> {
type Output = Result<Tensor<T>, DeviceError>;
type IntoFuture = DeviceFuture<Tensor<T>, Self>;
fn into_future(self) -> Self::IntoFuture {
match with_default_device_policy(|policy| {
let stream = policy.next_stream()?;
Ok(DeviceFuture::scheduled(self, ExecutionContext::new(stream)))
}) {
Ok(Ok(future)) => future,
Ok(Err(e)) => DeviceFuture::failed(e),
Err(e) => DeviceFuture::failed(e),
}
}
}
impl<T: DType + Send, DI: DeviceOp<Output = Arc<Tensor<T>>>> DeviceOp
for ReshapeOp<Arc<Tensor<T>>, DI>
{
type Output = Arc<Tensor<T>>;
unsafe fn execute(self, context: &ExecutionContext) -> Result<Arc<Tensor<T>>, DeviceError> {
let arc_tensor = self.input.execute(context)?;
arc_tensor
.reshape_shared(&self.shape)
.map_err(|e| DeviceError::Internal(e.to_string()))
}
}
impl<T: DType + Send, DI: DeviceOp<Output = Arc<Tensor<T>>>> IntoFuture
for ReshapeOp<Arc<Tensor<T>>, DI>
{
type Output = Result<Arc<Tensor<T>>, DeviceError>;
type IntoFuture = DeviceFuture<Arc<Tensor<T>>, Self>;
fn into_future(self) -> Self::IntoFuture {
match with_default_device_policy(|policy| {
let stream = policy.next_stream()?;
Ok(DeviceFuture::scheduled(self, ExecutionContext::new(stream)))
}) {
Ok(Ok(future)) => future,
Ok(Err(e)) => DeviceFuture::failed(e),
Err(e) => DeviceFuture::failed(e),
}
}
}
pub trait DeviceOpReshape<T: DType>: DeviceOp<Output = Tensor<T>> + Sized {
fn reshape(self, shape: &[usize]) -> ReshapeOp<Tensor<T>, Self> {
ReshapeOp {
shape: shape.to_vec(),
input: self,
}
}
}
impl<T: DType, DI: DeviceOp<Output = Tensor<T>>> DeviceOpReshape<T> for DI {}
pub trait DeviceOpReshapeShared<T: DType + Send>:
DeviceOp<Output = Arc<Tensor<T>>> + Sized
{
fn reshape(self, shape: &[usize]) -> ReshapeOp<Arc<Tensor<T>>, Self> {
ReshapeOp {
shape: shape.to_vec(),
input: self,
}
}
}
impl<T: DType + Send, DI: DeviceOp<Output = Arc<Tensor<T>>>> DeviceOpReshapeShared<T> for DI {}