use std::ffi::c_void;
use std::fmt;
use cubecl::client::ComputeClient;
use cubecl::prelude::{ArrayArg, CubeCount, CubeDim, CubeElement, CubePrimitive, TensorBinding};
use cubecl_cuda::CudaRuntime as CubeclCudaRuntime;
use num_complex::{Complex32, Complex64};
use crate::{TensorRank, TensorScalar, TypedTensor};
use tenferro_tensor::{DType, TensorRead, TensorViewMut, TensorWrite, TypedTensorViewMut};
use super::error::unsupported_dtype;
use super::{dispatch, CudaRuntime};
pub struct DeviceByteBuffer {
handle: Option<cubecl_runtime::server::Handle>,
ptr: *mut c_void,
}
impl fmt::Debug for DeviceByteBuffer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DeviceByteBuffer")
.field("is_empty", &self.is_empty())
.field("ptr", &self.ptr)
.finish_non_exhaustive()
}
}
impl DeviceByteBuffer {
pub fn none() -> Self {
Self {
handle: None,
ptr: std::ptr::null_mut(),
}
}
pub fn with_ptr(&self, f: impl FnOnce(*mut c_void)) {
f(self.ptr)
}
pub fn is_empty(&self) -> bool {
self.handle.is_none()
}
}
#[derive(Debug)]
struct CudaExternalUseCore {
handle: cubecl_runtime::server::Handle,
runtime: CudaRuntime,
}
impl CudaExternalUseCore {
fn new(handle: cubecl_runtime::server::Handle, runtime: &CudaRuntime) -> Self {
Self {
handle,
runtime: runtime.clone(),
}
}
fn with_device_ptr(&self, callback: impl FnOnce(*mut c_void)) -> crate::Result<()> {
let resource = self
.runtime
.client()
.get_resource(self.handle.clone())
.map_err(|err| crate::Error::backend_source("cuda_external_use_lease", err))?;
let ptr = cuda_device_ptr_from_addr(resource.resource().ptr, "cuda_external_use_lease")?;
callback(ptr);
Ok(())
}
}
#[doc(hidden)]
#[derive(Debug)]
pub struct CudaExternalUseReadLease {
core: CudaExternalUseCore,
}
impl CudaExternalUseReadLease {
pub fn new<T, R>(
runtime: &CudaRuntime,
tensor: &TypedTensor<T, R>,
op: &'static str,
) -> crate::Result<Self>
where
T: TensorScalar + 'static,
R: TensorRank,
{
dispatch::ensure_resident_on_runtime(runtime, tensor, op)?;
let handle = dispatch::prepared_tensor_access(tensor, op)?.into_handle();
Ok(Self {
core: CudaExternalUseCore::new(handle, runtime),
})
}
pub fn with_device_ptr(&self, callback: impl FnOnce(*mut c_void)) -> crate::Result<()> {
self.core.with_device_ptr(callback)
}
}
#[doc(hidden)]
#[derive(Debug)]
pub struct CudaExternalUseWriteLease {
core: CudaExternalUseCore,
}
impl CudaExternalUseWriteLease {
pub fn new<T, R>(
runtime: &CudaRuntime,
tensor: &mut TypedTensor<T, R>,
op: &'static str,
) -> crate::Result<Self>
where
T: TensorScalar + 'static,
R: TensorRank,
{
dispatch::ensure_resident_on_runtime(runtime, tensor, op)?;
let handle = dispatch::prepared_tensor_write_access(tensor, op)?.into_handle();
Ok(Self {
core: CudaExternalUseCore::new(handle, runtime),
})
}
pub fn with_device_ptr(&self, callback: impl FnOnce(*mut c_void)) -> crate::Result<()> {
self.core.with_device_ptr(callback)
}
}
pub(crate) fn cuda_device_ptr_from_addr(addr: u64, op: &'static str) -> crate::Result<*mut c_void> {
let addr = usize::try_from(addr).map_err(|_| {
crate::Error::invalid_argument(
op,
"device_address",
format!("CUDA device address {addr} exceeds usize"),
)
})?;
Ok(std::ptr::with_exposed_provenance_mut::<c_void>(addr))
}
pub fn with_cubecl_client<R>(
rt: &CudaRuntime,
launch: impl FnOnce(&ComputeClient<CubeclCudaRuntime>) -> R,
) -> R {
launch(rt.client())
}
pub fn flush_cubecl_client(rt: &CudaRuntime, op: &'static str) -> crate::Result<()> {
rt.client()
.flush()
.map_err(|err| crate::Error::backend_source(op, err))
}
pub fn with_raw_cuda_stream(
rt: &CudaRuntime,
op: &'static str,
f: impl FnOnce(u64),
) -> crate::Result<()> {
let stream = rt
.raw_cuda_stream()
.map_err(|err| crate::Error::backend_source(op, err))?;
f(stream);
Ok(())
}
pub fn cube_count_for_len(len: usize) -> crate::Result<CubeCount> {
dispatch::cube_count_for_len(len)
}
pub fn cube_dim_1d() -> CubeDim {
dispatch::cube_dim_1d()
}
pub fn alloc_output<T: CubeElement + TensorScalar + Clone + Send + Sync + 'static>(
rt: &CudaRuntime,
shape: &[usize],
) -> crate::Result<TypedTensor<T>> {
dispatch::alloc_output(rt, shape)
}
#[doc(hidden)]
pub fn alloc_zero_output<T>(rt: &CudaRuntime, shape: &[usize]) -> crate::Result<TypedTensor<T>>
where
T: CubeElement + CubePrimitive + TensorScalar + Clone + Send + Sync + 'static,
{
let output = alloc_output::<T>(rt, shape)?;
dispatch::launch_nullary_into(
rt,
&output,
"alloc_zero_output",
dispatch::cube_count_for_len(output.n_elements())?,
dispatch::cube_dim_1d(),
|client, count, dim, out| unsafe {
crate::kernels::structural::fill_zero_kernel::launch_unchecked::<T, CubeclCudaRuntime>(
client, count, dim, out,
);
},
)?;
Ok(output)
}
pub fn ensure_typed_tensor_resident<T: 'static>(
tensor: &TypedTensor<T, impl TensorRank>,
op: &'static str,
) -> crate::Result<()> {
dispatch::cubecl_buffer(tensor, op)?;
Ok(())
}
pub fn typed_tensor_binding<T: CubeElement + TensorScalar + Clone>(
tensor: &TypedTensor<T, impl TensorRank>,
op: &'static str,
) -> crate::Result<TensorBinding<CubeclCudaRuntime>> {
dispatch::typed_tensor_binding(tensor, op)
}
pub fn typed_tensor_array_arg<T: CubeElement + TensorScalar + Clone>(
tensor: &TypedTensor<T, impl TensorRank>,
op: &'static str,
) -> crate::Result<ArrayArg<CubeclCudaRuntime>> {
dispatch::typed_tensor_array_arg(tensor, op)
}
pub fn with_typed_device_ptr<T: TensorScalar + 'static>(
rt: &CudaRuntime,
tensor: &TypedTensor<T, impl TensorRank>,
op: &'static str,
f: impl FnOnce(*mut c_void),
) -> crate::Result<()> {
dispatch::ensure_resident_on_runtime(rt, tensor, op)?;
let prepared = dispatch::prepared_tensor_access(tensor, op)?;
let resource = rt
.client()
.get_resource(prepared.into_handle())
.map_err(|err| crate::Error::backend_source(op, err))?;
let ptr = cuda_device_ptr_from_addr(resource.resource().ptr, op)?;
f(ptr);
Ok(())
}
pub fn upload_typed_tensor<T>(
rt: &CudaRuntime,
shape: Vec<usize>,
data: Vec<T>,
) -> crate::Result<TypedTensor<T>>
where
T: CubeElement + TensorScalar + Clone + Send + Sync + 'static,
{
let byte_len = T::as_bytes(&data).len();
let handle = rt.client().create_from_slice(T::as_bytes(&data));
dispatch::typed_from_cubecl(
shape,
crate::CubeclBuffer::new(
handle,
byte_len,
rt.device_ordinal(),
rt.allocation_domain_id(),
),
rt.device_ordinal(),
)
}
pub fn download_typed_tensor<T>(
rt: &CudaRuntime,
tensor: &TypedTensor<T, impl TensorRank>,
op: &'static str,
) -> crate::Result<TypedTensor<T>>
where
T: CubeElement + TensorScalar + Clone + 'static,
{
dispatch::ensure_resident_on_runtime(rt, tensor, op)?;
let prepared = dispatch::prepared_tensor_access(tensor, op)?;
if tensor.n_elements() == 0 {
return TypedTensor::from_vec_col_major(tensor.shape().to_vec(), Vec::new());
}
rt.synchronize()?;
let bytes = rt
.client()
.read_one(prepared.into_handle())
.map_err(|err| crate::Error::backend_source(op, err))?;
TypedTensor::from_vec_col_major(tensor.shape().to_vec(), T::from_bytes(&bytes).to_vec())
}
pub fn alloc_device_bytes(
rt: &CudaRuntime,
nbytes: usize,
op: &'static str,
) -> crate::Result<DeviceByteBuffer> {
if nbytes == 0 {
return Ok(DeviceByteBuffer::none());
}
let handle = rt.client().empty(nbytes);
device_bytes_from_handle(rt, handle, op)
}
pub fn upload_device_bytes(
rt: &CudaRuntime,
bytes: &[u8],
op: &'static str,
) -> crate::Result<DeviceByteBuffer> {
if bytes.is_empty() {
return Ok(DeviceByteBuffer::none());
}
let handle = rt.client().create_from_slice(bytes);
device_bytes_from_handle(rt, handle, op)
}
fn device_bytes_from_handle(
rt: &CudaRuntime,
handle: cubecl_runtime::server::Handle,
op: &'static str,
) -> crate::Result<DeviceByteBuffer> {
let resource = rt
.client()
.get_resource(handle.clone())
.map_err(|err| crate::Error::backend_source(op, err))?;
Ok(DeviceByteBuffer {
handle: Some(handle),
ptr: cuda_device_ptr_from_addr(resource.resource().ptr, op)?,
})
}
const SCALE_OP: &str = "scale_tensor_write";
#[doc(hidden)]
pub fn scale_tensor_write(
rt: &CudaRuntime,
output: TensorWrite<'_>,
factor: f64,
) -> crate::Result<()> {
ensure_tensor_write_resident(rt, &output, SCALE_OP)?;
let dtype = output.dtype();
if !matches!(dtype, DType::F32 | DType::F64 | DType::C32 | DType::C64) {
return Err(unsupported_dtype(SCALE_OP, dtype));
}
match output {
TensorWrite::Tensor(output) => match output {
crate::Tensor::F32(output) => {
scale_typed_tensor(rt, output, factor as f32, launch_scale_f32)
}
crate::Tensor::F64(output) => scale_typed_tensor(rt, output, factor, launch_scale_f64),
crate::Tensor::C32(output) => scale_typed_tensor(
rt,
output,
Complex32::new(factor as f32, 0.0),
launch_scale_c32,
),
crate::Tensor::C64(output) => {
scale_typed_tensor(rt, output, Complex64::new(factor, 0.0), launch_scale_c64)
}
_ => Err(unsupported_dtype(SCALE_OP, dtype)),
},
TensorWrite::View(mut output) => match &mut output {
TensorViewMut::F32(output) => {
scale_typed_view(rt, output, factor as f32, launch_scale_f32)
}
TensorViewMut::F64(output) => scale_typed_view(rt, output, factor, launch_scale_f64),
TensorViewMut::C32(output) => scale_typed_view(
rt,
output,
Complex32::new(factor as f32, 0.0),
launch_scale_c32,
),
TensorViewMut::C64(output) => {
scale_typed_view(rt, output, Complex64::new(factor, 0.0), launch_scale_c64)
}
_ => Err(unsupported_dtype(SCALE_OP, dtype)),
},
}
}
fn ensure_tensor_write_resident(
rt: &CudaRuntime,
output: &TensorWrite<'_>,
op: &'static str,
) -> crate::Result<()> {
let read = output.as_read();
match &read {
TensorRead::Tensor(output) => match *output {
crate::Tensor::F32(output) => dispatch::ensure_resident_on_runtime(rt, output, op),
crate::Tensor::F64(output) => dispatch::ensure_resident_on_runtime(rt, output, op),
crate::Tensor::I32(output) => dispatch::ensure_resident_on_runtime(rt, output, op),
crate::Tensor::I64(output) => dispatch::ensure_resident_on_runtime(rt, output, op),
crate::Tensor::Bool(output) => dispatch::ensure_resident_on_runtime(rt, output, op),
crate::Tensor::C32(output) => dispatch::ensure_resident_on_runtime(rt, output, op),
crate::Tensor::C64(output) => dispatch::ensure_resident_on_runtime(rt, output, op),
},
TensorRead::View(output) => match output {
crate::TensorView::F32(output) => {
dispatch::ensure_view_resident_on_runtime(rt, output, op)
}
crate::TensorView::F64(output) => {
dispatch::ensure_view_resident_on_runtime(rt, output, op)
}
crate::TensorView::I32(output) => {
dispatch::ensure_view_resident_on_runtime(rt, output, op)
}
crate::TensorView::I64(output) => {
dispatch::ensure_view_resident_on_runtime(rt, output, op)
}
crate::TensorView::Bool(output) => {
dispatch::ensure_view_resident_on_runtime(rt, output, op)
}
crate::TensorView::C32(output) => {
dispatch::ensure_view_resident_on_runtime(rt, output, op)
}
crate::TensorView::C64(output) => {
dispatch::ensure_view_resident_on_runtime(rt, output, op)
}
},
}
}
pub(crate) fn scale_typed_tensor<T, F>(
rt: &CudaRuntime,
output: &mut TypedTensor<T>,
factor: T,
launch: F,
) -> crate::Result<()>
where
T: CubeElement + TensorScalar + Clone + Send + Sync + 'static,
F: FnOnce(
&ComputeClient<CubeclCudaRuntime>,
CubeCount,
CubeDim,
ArrayArg<CubeclCudaRuntime>,
ArrayArg<CubeclCudaRuntime>,
),
{
scale_typed_tensor_for_op(rt, output, factor, SCALE_OP, launch)
}
pub(crate) fn scale_typed_tensor_for_op<T, F>(
rt: &CudaRuntime,
output: &mut TypedTensor<T>,
factor: T,
op: &'static str,
launch: F,
) -> crate::Result<()>
where
T: CubeElement + TensorScalar + Clone + Send + Sync + 'static,
F: FnOnce(
&ComputeClient<CubeclCudaRuntime>,
CubeCount,
CubeDim,
ArrayArg<CubeclCudaRuntime>,
ArrayArg<CubeclCudaRuntime>,
),
{
dispatch::ensure_resident_on_runtime(rt, output, op)?;
let len = output.n_elements();
validate_scale_buffer(op, len, output.buffer().len())?;
if len == 0 {
return Ok(());
}
let count = dispatch::cube_count_for_len(len)?;
let dim = dispatch::cube_dim_1d();
let mut output_view = output.as_view_mut();
let output_arg = dispatch::typed_view_mut_array_arg(&mut output_view, op)?;
launch_scaled(rt, output_arg, factor, count, dim, op, launch)
}
fn scale_typed_view<T, F>(
rt: &CudaRuntime,
output: &mut TypedTensorViewMut<'_, T>,
factor: T,
launch: F,
) -> crate::Result<()>
where
T: CubeElement + TensorScalar + Clone + Send + Sync + 'static,
F: FnOnce(
&ComputeClient<CubeclCudaRuntime>,
CubeCount,
CubeDim,
ArrayArg<CubeclCudaRuntime>,
ArrayArg<CubeclCudaRuntime>,
),
{
dispatch::ensure_view_mut_resident_on_runtime(rt, output, SCALE_OP)?;
if output.offset() != 0 || !output.is_col_major_contiguous()? {
return Err(crate::Error::invalid_argument(
SCALE_OP,
"layout",
"CUDA tensor scaling requires a zero-offset column-major view",
));
}
let len = output.n_elements();
let buffer_len = output
.backend_buffer()
.ok_or_else(|| crate::Error::runtime_state(SCALE_OP, "expected a CUDA backend buffer"))?
.len();
validate_scale_buffer(SCALE_OP, len, buffer_len)?;
if len == 0 {
return Ok(());
}
let count = dispatch::cube_count_for_len(len)?;
let dim = dispatch::cube_dim_1d();
let output_arg = dispatch::typed_view_mut_array_arg(output, SCALE_OP)?;
launch_scaled(rt, output_arg, factor, count, dim, SCALE_OP, launch)
}
fn validate_scale_buffer(op: &'static str, len: usize, buffer_len: usize) -> crate::Result<()> {
if len > buffer_len {
return Err(crate::Error::runtime_state(
op,
format!(
"CUDA tensor scaling output has {len} logical elements but its buffer has {buffer_len}"
),
));
}
Ok(())
}
fn launch_scaled<T, F>(
rt: &CudaRuntime,
output: ArrayArg<CubeclCudaRuntime>,
factor: T,
count: CubeCount,
dim: CubeDim,
op: &'static str,
launch: F,
) -> crate::Result<()>
where
T: CubeElement + TensorScalar + Clone + Send + Sync + 'static,
F: FnOnce(
&ComputeClient<CubeclCudaRuntime>,
CubeCount,
CubeDim,
ArrayArg<CubeclCudaRuntime>,
ArrayArg<CubeclCudaRuntime>,
),
{
let factor = upload_typed_tensor(rt, vec![1], vec![factor])?;
let factor = dispatch::typed_tensor_array_arg(&factor, op)?;
launch(rt.client(), count, dim, output, factor);
Ok(())
}
fn launch_scale_f32(
client: &ComputeClient<CubeclCudaRuntime>,
count: CubeCount,
dim: CubeDim,
output: ArrayArg<CubeclCudaRuntime>,
factor: ArrayArg<CubeclCudaRuntime>,
) {
unsafe {
crate::kernels::structural::scale_in_place_float_kernel::launch_unchecked::<
f32,
CubeclCudaRuntime,
>(client, count, dim, output, factor);
}
}
fn launch_scale_f64(
client: &ComputeClient<CubeclCudaRuntime>,
count: CubeCount,
dim: CubeDim,
output: ArrayArg<CubeclCudaRuntime>,
factor: ArrayArg<CubeclCudaRuntime>,
) {
unsafe {
crate::kernels::structural::scale_in_place_float_kernel::launch_unchecked::<
f64,
CubeclCudaRuntime,
>(client, count, dim, output, factor);
}
}
fn launch_scale_c32(
client: &ComputeClient<CubeclCudaRuntime>,
count: CubeCount,
dim: CubeDim,
output: ArrayArg<CubeclCudaRuntime>,
factor: ArrayArg<CubeclCudaRuntime>,
) {
unsafe {
crate::kernels::structural::scale_in_place_complex_kernel::launch_unchecked::<
Complex32,
CubeclCudaRuntime,
>(client, count, dim, output, factor);
}
}
fn launch_scale_c64(
client: &ComputeClient<CubeclCudaRuntime>,
count: CubeCount,
dim: CubeDim,
output: ArrayArg<CubeclCudaRuntime>,
factor: ArrayArg<CubeclCudaRuntime>,
) {
unsafe {
crate::kernels::structural::scale_in_place_complex_kernel::launch_unchecked::<
Complex64,
CubeclCudaRuntime,
>(client, count, dim, output, factor);
}
}
#[cfg(test)]
mod tests;