pub(crate) mod completion;
mod events;
mod memory_ops;
mod queued;
pub(crate) use completion::{select_uncertain_completion_error, CudaSynchronizationOutcome};
pub(crate) use events::elapsed_event_us_ceil;
#[cfg(test)]
pub(crate) use events::CudaEvent;
pub use queued::{
CudaExecutionStats, CudaKernelBatchOutput, CudaKernelContiguousBatchOutput, CudaKernelOutput,
CudaPooledKernelOutput, CudaQueuedExecution,
};
#[cfg(test)]
use crate::context::{CudaKernelModule, CudaKernelName};
use crate::{
context::CudaContext,
driver::{CuDevicePtr, CuFunction},
error::CudaError,
kernels::{self, copy_u8_launch_geometry},
memory::CudaDeviceBuffer,
};
use std::{ffi::c_void, ops::Range};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum CudaLaunchMode {
Sync,
Async,
}
impl CudaLaunchMode {
pub(crate) fn from_synchronize(synchronize: bool) -> Self {
if synchronize {
Self::Sync
} else {
Self::Async
}
}
}
pub(crate) unsafe trait CudaKernelParam {}
unsafe impl CudaKernelParam for CuDevicePtr {}
unsafe impl CudaKernelParam for u32 {}
unsafe impl CudaKernelParam for i32 {}
unsafe impl CudaKernelParam for f32 {}
pub(crate) fn cuda_kernel_param<T>(value: &mut T) -> *mut c_void
where
T: CudaKernelParam,
{
std::ptr::from_mut(value).cast::<c_void>()
}
impl CudaContext {
#[doc(hidden)]
pub fn copy_with_kernel(&self, bytes: &[u8]) -> Result<CudaKernelOutput, CudaError> {
let staging = self.upload(bytes)?;
let output = self.copy_device_to_device_with_kernel(&staging)?;
let copy_dispatches = usize::from(!bytes.is_empty());
Ok(CudaKernelOutput {
buffer: output,
execution: CudaExecutionStats {
kernel_dispatches: copy_dispatches,
copy_kernel_dispatches: copy_dispatches,
decode_kernel_dispatches: 0,
hardware_decode: false,
},
})
}
#[cfg(all(test, feature = "cuda-oxide-copy-u8", j2k_cuda_oxide_copy_u8_built))]
pub(crate) fn copy_with_cuda_oxide_kernel(
&self,
bytes: &[u8],
) -> Result<CudaKernelOutput, CudaError> {
let staging = self.upload(bytes)?;
let output = self.copy_device_to_device_with_cuda_oxide_kernel(&staging)?;
let copy_dispatches = usize::from(!bytes.is_empty());
Ok(CudaKernelOutput {
buffer: output,
execution: CudaExecutionStats {
kernel_dispatches: copy_dispatches,
copy_kernel_dispatches: copy_dispatches,
decode_kernel_dispatches: 0,
hardware_decode: false,
},
})
}
pub(crate) fn launch_kernel(
&self,
function: CuFunction,
geometry: kernels::CudaLaunchGeometry,
params: &mut [*mut c_void],
) -> Result<(), CudaError> {
self.launch_kernel_async(function, geometry, params)?;
self.synchronize()
}
pub(crate) fn launch_kernel_async(
&self,
function: CuFunction,
geometry: kernels::CudaLaunchGeometry,
params: &mut [*mut c_void],
) -> Result<(), CudaError> {
if !geometry.is_valid() {
return Err(CudaError::InvalidArgument {
message: format!(
"CUDA launch geometry exceeds static limits: grid {:?}, block {:?}",
geometry.grid(),
geometry.block()
),
});
}
let (grid_x, grid_y, grid_z) = geometry.grid();
let (block_x, block_y, block_z) = geometry.block();
self.inner.with_current_resource_operation(|| {
let launch_status = unsafe {
(self.inner.driver.cu_launch_kernel)(
function,
grid_x,
grid_y,
grid_z,
block_x,
block_y,
block_z,
0,
std::ptr::null_mut(),
params.as_mut_ptr(),
std::ptr::null_mut(),
)
};
self.inner.driver.check("cuLaunchKernel", launch_status)
})
}
pub(crate) fn copy_device_to_device_with_kernel(
&self,
src: &CudaDeviceBuffer,
) -> Result<CudaDeviceBuffer, CudaError> {
self.copy_device_range_to_device_with_kernel(src, 0..src.byte_len())
}
pub(crate) fn copy_device_range_to_device_with_kernel(
&self,
src: &CudaDeviceBuffer,
range: Range<usize>,
) -> Result<CudaDeviceBuffer, CudaError> {
self.copy_device_range_to_device_with_copy_u8_loader(src, range, |context| {
context.inner.cuda_oxide_copy_u8_kernel_function()
})
}
#[cfg(all(test, feature = "cuda-oxide-copy-u8", j2k_cuda_oxide_copy_u8_built))]
pub(crate) fn copy_device_to_device_with_cuda_oxide_kernel(
&self,
src: &CudaDeviceBuffer,
) -> Result<CudaDeviceBuffer, CudaError> {
self.copy_device_range_to_device_with_copy_u8_loader(src, 0..src.byte_len(), |context| {
context.inner.cuda_oxide_copy_u8_kernel_function()
})
}
fn copy_device_range_to_device_with_copy_u8_loader(
&self,
src: &CudaDeviceBuffer,
range: Range<usize>,
load_function: impl FnOnce(&Self) -> Result<CuFunction, CudaError>,
) -> Result<CudaDeviceBuffer, CudaError> {
if !src.is_owned_by(self) {
return Err(CudaError::InvalidArgument {
message: "CUDA copy source must belong to the launch context".to_string(),
});
}
if range.start > range.end {
return Err(CudaError::InvalidArgument {
message: "CUDA copy range start must not exceed its end".to_string(),
});
}
if range.end > src.byte_len() {
return Err(CudaError::OutputTooSmall {
required: range.end,
have: src.byte_len(),
});
}
let byte_len = range.end - range.start;
if byte_len == 0 {
self.inner.set_current()?;
return self.allocate(0);
}
let geometry =
copy_u8_launch_geometry(byte_len).ok_or(CudaError::LengthTooLarge { len: byte_len })?;
self.inner.set_current()?;
let dst = self.allocate(byte_len)?;
let source_offset = u64::try_from(range.start)
.map_err(|_| CudaError::LengthTooLarge { len: range.start })?;
let src_ptr = src
.device_ptr()
.checked_add(source_offset)
.ok_or(CudaError::LengthTooLarge { len: range.end })?;
let function = load_function(self)?;
let mut dst_ptr = dst.device_ptr();
let mut src_ptr = src_ptr;
let mut len =
u64::try_from(byte_len).map_err(|_| CudaError::LengthTooLarge { len: byte_len })?;
let mut params = cuda_kernel_params!(dst_ptr, src_ptr, len);
self.launch_kernel(function, geometry, &mut params)?;
Ok(dst)
}
pub fn synchronize(&self) -> Result<(), CudaError> {
self.synchronize_for_resource_release().into_result()
}
pub(crate) fn synchronize_for_resource_release(&self) -> CudaSynchronizationOutcome {
let result = self.inner.with_current_completion_operation(|| {
let status = unsafe { (self.inner.driver.cu_ctx_synchronize)() };
self.inner.driver.check("cuCtxSynchronize", status)
});
match result {
Ok(()) => CudaSynchronizationOutcome::Completed,
Err(error) => {
CudaSynchronizationOutcome::CompletionUncertain(error)
}
}
}
pub(crate) fn synchronize_then_error<T>(&self, error: CudaError) -> Result<T, CudaError> {
if self.inner.resource_lifetimes_poisoned() {
return Err(select_uncertain_completion_error(error, None));
}
match self.synchronize() {
Ok(()) => Err(error),
Err(completion_error) => Err(select_uncertain_completion_error(
error,
Some(completion_error),
)),
}
}
#[cfg(test)]
pub(crate) fn preload_kernel_module(
&self,
kernel: CudaKernelName,
) -> Result<CudaKernelModule, CudaError> {
let _ = self.inner.cuda_oxide_kernel_function(kernel.kernel())?;
Ok(CudaKernelModule {
entrypoint: kernel.entrypoint(),
})
}
}