use crate::context::cuda_context;
use crate::kernels::launch_args::TractLaunchArgs;
use crate::kernels::utils::cuda_launch_cfg_for_cpy;
use crate::kernels::{BroadcastKind, LibraryName, get_sliced_cuda_view};
use cudarc::driver::PushKernelArg;
use tract_core::internal::*;
use tract_gpu::tensor::DeviceTensor;
pub fn cuda_copy_nd_dispatch(
input: &DeviceTensor,
input_offset: usize,
input_strides: &[isize],
output: &DeviceTensor,
output_offset: usize,
output_shape: &[usize],
output_strides: &[isize],
) -> TractResult<()> {
if output_shape.contains(&0) {
return Ok(());
}
crate::with_cuda_stream(|stream| {
let kernel_name = BroadcastKind::from_rank(output_shape.len())?
.copy_kernel_name(input.datum_type(), "")?;
let func = cuda_context().load_pipeline(LibraryName::Array, kernel_name)?;
let i_view = get_sliced_cuda_view(
input,
input_offset,
input.len() * input.datum_type().size_of() - input_offset,
)?;
let o_view = get_sliced_cuda_view(
output,
output_offset,
output.len() * output.datum_type().size_of() - output_offset,
)?;
let mut launch_args = TractLaunchArgs::new(stream, &func);
launch_args.push_view(&i_view);
launch_args.push_view(&o_view);
launch_args.push_slice_i32(input_strides);
launch_args.push_slice_i32(output_shape);
launch_args.push_slice_i32(output_strides);
let cfg = cuda_launch_cfg_for_cpy(output_shape);
launch_args.launch(cfg)
})
}
#[cfg(test)]
mod tests {
use super::*;
use tract_gpu::tensor::IntoDevice;
fn run_copy_case(src_shape: &[usize], dst_shape: &[usize], zone: &[usize]) -> TractResult<()> {
let src = Tensor::from_shape(
src_shape,
&(0..src_shape.iter().product::<usize>()).map(|i| i as f32).collect::<TVec<_>>(),
)?;
let mut expected = Tensor::zero::<f32>(dst_shape)?;
let mut dst_view = expected.to_plain_array_view_mut::<f32>()?;
let src_view = src.to_plain_array_view::<f32>()?;
let zone_ix: TVec<usize> = zone.into();
dst_view
.slice_each_axis_mut(|a| (0..zone_ix[a.axis.index()] as isize).into())
.assign(&src_view.slice_each_axis(|a| (0..zone_ix[a.axis.index()] as isize).into()));
let found = crate::with_cuda_stream(|stream| {
let src = src.into_device()?;
let dst = Tensor::zero::<f32>(dst_shape)?.into_device()?;
cuda_copy_nd_dispatch(&src, 0, src.strides(), &dst, 0, zone, dst.strides())?;
stream.synchronize()?;
Ok(dst.to_host()?.into_tensor())
})?;
found
.close_enough(&expected, false)
.with_context(|| format!("src {src_shape:?} dst {dst_shape:?} zone {zone:?}"))
}
#[test]
fn test_copy_nd() -> TractResult<()> {
run_copy_case(&[3000], &[3000], &[3000])?;
run_copy_case(&[5, 7], &[5, 9], &[5, 7])?;
run_copy_case(&[4, 1025], &[4, 1025], &[4, 1025])?;
run_copy_case(&[2, 1024], &[2, 1024], &[2, 1024])?;
run_copy_case(&[6, 33, 11], &[6, 33, 12], &[6, 33, 11])?;
run_copy_case(&[43, 1024, 11], &[43, 1024, 12], &[43, 1024, 11])?;
run_copy_case(&[3, 5, 65, 16], &[3, 5, 65, 17], &[3, 5, 65, 16])?;
run_copy_case(&[2, 3, 4, 8, 128], &[2, 3, 4, 8, 128], &[2, 3, 4, 8, 128])?;
run_copy_case(&[2, 3, 4, 5, 6, 7], &[2, 3, 4, 5, 6, 9], &[2, 3, 4, 5, 6, 7])?;
run_copy_case(&[2, 3, 4, 5, 6, 7], &[2, 3, 4, 5, 6, 7], &[1, 2, 3, 4, 5, 6])?;
run_copy_case(&[4, 5], &[4, 5], &[0, 5])?;
run_copy_case(&[70000, 300], &[70000, 300], &[70000, 300])?;
run_copy_case(&[70000, 8], &[70000, 9], &[70000, 8])?;
run_copy_case(&[70000, 3, 5], &[70000, 3, 5], &[70000, 3, 5])?;
run_copy_case(&[260, 256, 65, 17], &[260, 256, 65, 17], &[260, 256, 65, 16])?;
run_copy_case(&[70001, 2, 3, 4], &[70001, 2, 3, 5], &[70001, 2, 3, 4])?;
run_copy_case(&[70000, 2, 300], &[70000, 2, 300], &[70000, 2, 300])?;
Ok(())
}
#[test]
fn test_copy_launch_cfg_within_device_limits() -> TractResult<()> {
let props = cuda_context().properties();
let max_grid = props.maxGridSize;
let max_threads = props.maxThreadsPerBlock as usize;
const EXTENTS: [usize; 13] =
[1, 2, 3, 17, 31, 32, 255, 256, 257, 1024, 65535, 65536, 70000];
const MAX_ELEMS: usize = 1 << 33;
const MAX_OUTER: usize = 65535 * 65535;
let mut checked = 0usize;
let mut shape: TVec<usize> = tvec!();
let mut walk = |shape: &TVec<usize>, checked: &mut usize| -> TractResult<()> {
let rank = shape.len();
if rank > 2 && shape[..rank - 2].iter().product::<usize>() > MAX_OUTER {
return Ok(());
}
let cfg = cuda_launch_cfg_for_cpy(shape);
let grid = [cfg.grid_dim.0, cfg.grid_dim.1, cfg.grid_dim.2];
for (axis, (asked, allowed)) in grid.iter().zip(max_grid.iter()).enumerate() {
ensure!(
*asked >= 1 && (*asked as i64) <= *allowed as i64,
"{shape:?} asks for {asked} blocks on the grid's axis {axis}, of {allowed}"
);
}
let block =
cfg.block_dim.0 as usize * cfg.block_dim.1 as usize * cfg.block_dim.2 as usize;
ensure!(
block >= 1 && block <= max_threads,
"{shape:?} asks for {block} threads a block, of {max_threads}"
);
let covered = if rank == 1 {
cfg.grid_dim.0 as usize * cfg.block_dim.0 as usize
} else {
let inner = shape[rank - 1];
let rows = (cfg.block_dim.0 as usize / inner).max(1);
cfg.grid_dim.0 as usize * rows
};
ensure!(
covered >= if rank == 1 { shape[0] } else { shape[rank - 2] },
"{shape:?} leaves rows past the {covered} the grid covers"
);
if rank > 2 {
let outer: usize = shape[..rank - 2].iter().product();
let packed = cfg.grid_dim.1 as usize * cfg.grid_dim.2 as usize;
ensure!(packed >= outer, "{shape:?} leaves {outer} axes over {packed} blocks");
}
*checked += 1;
Ok(())
};
fn extend(
shape: &mut TVec<usize>,
elems: usize,
checked: &mut usize,
walk: &mut impl FnMut(&TVec<usize>, &mut usize) -> TractResult<()>,
) -> TractResult<()> {
walk(shape, checked)?;
if shape.len() == 6 {
return Ok(());
}
for extent in EXTENTS {
let Some(elems) = elems.checked_mul(extent).filter(|e| *e <= MAX_ELEMS) else {
continue;
};
shape.push(extent);
extend(shape, elems, checked, walk)?;
shape.pop();
}
Ok(())
}
for extent in EXTENTS {
shape.push(extent);
extend(&mut shape, extent, &mut checked, &mut walk)?;
shape.pop();
}
assert!(checked > 10_000, "only {checked} shapes walked");
Ok(())
}
}