#[cfg(feature = "cuda")]
mod cuda {
use dotenv::dotenv;
use serial_test::serial;
use teeny_compiler::compiler::{driver::cuda::compile_kernel, target::cuda::Target};
use teeny_core::device::{Device, buffer::Buffer};
use teeny_cuda::{device::CudaLaunchConfig, errors::Result, testing};
const B: usize = 1;
const C_IN: usize = 256;
const C2: usize = 64; const C_BOX: usize = 4; const H: usize = 4;
const W: usize = 4;
const N_SPATIAL: usize = B * H * W;
const N_CONV1: usize = B * C2 * H * W; const N_CONV2: usize = B * C2 * H * W; const N_CONV3: usize = B * C_BOX * H * W;
const BLOCK_OW: i32 = 4; const BLOCK_BN: i32 = 128;
const BLOCK_SILU: i32 = 128;
const BLOCK_BIAS: i32 = 128;
const BN_EPS: f32 = 1e-5;
fn load(name: &str) -> Vec<f32> {
let path = format!(
"{}/tests/fixtures/detect_yolo26/{}",
env!("CARGO_MANIFEST_DIR"),
name
);
let bytes = std::fs::read(&path)
.unwrap_or_else(|e| panic!("missing fixture {path}: {e}"));
bytes
.chunks_exact(4)
.map(|b| f32::from_le_bytes([b[0], b[1], b[2], b[3]]))
.collect()
}
fn nchw_to_nc(src: &[f32], b: usize, c: usize, h: usize, w: usize) -> Vec<f32> {
let mut out = vec![0.0f32; b * h * w * c];
for bi in 0..b {
for ci in 0..c {
for hi in 0..h {
for wi in 0..w {
let ni = bi * h * w + hi * w + wi;
out[ni * c + ci] =
src[bi * c * h * w + ci * h * w + hi * w + wi];
}
}
}
}
out
}
fn nc_to_nchw(src: &[f32], b: usize, c: usize, h: usize, w: usize) -> Vec<f32> {
let mut out = vec![0.0f32; b * c * h * w];
for bi in 0..b {
for ci in 0..c {
for hi in 0..h {
for wi in 0..w {
let ni = bi * h * w + hi * w + wi;
out[bi * c * h * w + ci * h * w + hi * w + wi] =
src[ni * c + ci];
}
}
}
}
out
}
#[test]
#[serial]
fn test_detect_cv2_0_forward_cuda() -> Result<()> {
dotenv().ok();
let env = testing::setup_cuda_env()?;
let device = env.device;
let target = Target::new(env.capability);
let x_host = load("x.bin");
let conv1_w = load("conv1_w.bin");
let conv1_bn_w = load("conv1_bn_w.bin");
let conv1_bn_b = load("conv1_bn_b.bin");
let conv1_bn_rm = load("conv1_bn_rm.bin");
let conv1_bn_rv = load("conv1_bn_rv.bin");
let conv2_w = load("conv2_w.bin");
let conv2_bn_w = load("conv2_bn_w.bin");
let conv2_bn_b = load("conv2_bn_b.bin");
let conv2_bn_rm = load("conv2_bn_rm.bin");
let conv2_bn_rv = load("conv2_bn_rv.bin");
let conv3_w = load("conv3_w.bin");
let conv3_bias = load("conv3_bias.bin");
let expected = load("expected_output.bin");
assert_eq!(x_host.len(), B * C_IN * H * W);
assert_eq!(conv1_w.len(), C2 * C_IN * 9); assert_eq!(conv2_w.len(), C2 * C2 * 9); assert_eq!(conv3_w.len(), C_BOX * C2); assert_eq!(conv3_bias.len(), C_BOX); assert_eq!(expected.len(), B * C_BOX * H * W);
let conv3_kernel = teeny_kernels::nn::conv::conv2d::Conv2dForward::<f32>::new(
3, 3, 1, 1, 1, 1, 1, BLOCK_OW,
);
let conv3_ptx = std::fs::read(compile_kernel(&conv3_kernel, &target, true)?)?;
let conv3_prog = testing::load_program_from_ptx::<
teeny_kernels::nn::conv::conv2d::Conv2dForward<f32>,
>(&conv3_ptx)?;
let conv1_kernel = teeny_kernels::nn::conv::conv2d::Conv2dForward::<f32>::new(
1, 1, 1, 1, 0, 0, 1, BLOCK_OW,
);
let conv1_ptx = std::fs::read(compile_kernel(&conv1_kernel, &target, true)?)?;
let conv1_prog = testing::load_program_from_ptx::<
teeny_kernels::nn::conv::conv2d::Conv2dForward<f32>,
>(&conv1_ptx)?;
let bn_kernel = teeny_kernels::nn::norm::batchnorm::BatchNormForwardInference::<f32>::new(BLOCK_BN);
let bn_ptx = std::fs::read(compile_kernel(&bn_kernel, &target, true)?)?;
let bn_prog = testing::load_program_from_ptx::<
teeny_kernels::nn::norm::batchnorm::BatchNormForwardInference<f32>,
>(&bn_ptx)?;
let silu_kernel = teeny_kernels::nn::activation::sigmoid::SiluForward::<f32>::new(BLOCK_SILU);
let silu_ptx = std::fs::read(compile_kernel(&silu_kernel, &target, true)?)?;
let silu_prog = testing::load_program_from_ptx::<
teeny_kernels::nn::activation::sigmoid::SiluForward<f32>,
>(&silu_ptx)?;
let bias_kernel = teeny_kernels::nn::tensor::channel_bias_add::ChannelBiasAddForward::<f32>::new(BLOCK_BIAS);
let bias_ptx = std::fs::read(compile_kernel(&bias_kernel, &target, true)?)?;
let bias_prog = testing::load_program_from_ptx::<
teeny_kernels::nn::tensor::channel_bias_add::ChannelBiasAddForward<f32>,
>(&bias_ptx)?;
let ow_tiles = W.div_ceil(BLOCK_OW as usize);
let bn_cfg = |c_ch: usize| CudaLaunchConfig {
grid: [c_ch as u32, 1, 1], block: [1, 1, 1], cluster: [1, 1, 1],
};
let mut x_buf = device.buffer::<f32>(B * C_IN * H * W)?;
let mut conv1_w_buf = device.buffer::<f32>(C2 * C_IN * 9)?;
let conv1_nchw = device.buffer::<f32>(N_CONV1)?;
x_buf.to_device(&x_host)?;
conv1_w_buf.to_device(&conv1_w)?;
device.launch(&conv3_prog, &CudaLaunchConfig {
grid: [(B * C2 * H * ow_tiles) as u32, 1, 1],
block: [128, 1, 1], cluster: [1, 1, 1],
}, (
x_buf.as_device_ptr() as *mut f32,
conv1_w_buf.as_device_ptr() as *mut f32,
conv1_nchw.as_device_ptr() as *mut f32,
B as i32, C_IN as i32, C2 as i32,
H as i32, W as i32, H as i32, W as i32,
))?;
let mut tmp = vec![0.0f32; N_CONV1];
conv1_nchw.to_host(&mut tmp)?;
let conv1_nc_host = nchw_to_nc(&tmp, B, C2, H, W);
let mut conv1_nc_buf = device.buffer::<f32>(N_CONV1)?;
let conv1_bn_out = device.buffer::<f32>(N_CONV1)?;
let mut conv1_bnw_buf = device.buffer::<f32>(C2)?;
let mut conv1_bnb_buf = device.buffer::<f32>(C2)?;
let mut conv1_bnrm = device.buffer::<f32>(C2)?;
let mut conv1_bnrv = device.buffer::<f32>(C2)?;
conv1_nc_buf.to_device(&conv1_nc_host)?;
conv1_bnw_buf.to_device(&conv1_bn_w)?;
conv1_bnb_buf.to_device(&conv1_bn_b)?;
conv1_bnrm.to_device(&conv1_bn_rm)?;
conv1_bnrv.to_device(&conv1_bn_rv)?;
device.launch(&bn_prog, &bn_cfg(C2), (
conv1_nc_buf.as_device_ptr() as *mut f32,
conv1_bn_out.as_device_ptr() as *mut f32,
conv1_bnw_buf.as_device_ptr() as *mut f32,
conv1_bnb_buf.as_device_ptr() as *mut f32,
conv1_bnrm.as_device_ptr() as *mut f32,
conv1_bnrv.as_device_ptr() as *mut f32,
N_SPATIAL as i32, C2 as i32, BN_EPS,
))?;
let conv1_silu = device.buffer::<f32>(N_CONV1)?;
device.launch(&silu_prog, &testing::launch_config(N_CONV1, BLOCK_SILU), (
conv1_bn_out.as_device_ptr() as *mut f32,
conv1_silu.as_device_ptr() as *mut f32,
N_CONV1 as i32,
))?;
let mut conv1_silu_host = vec![0.0f32; N_CONV1];
conv1_silu.to_host(&mut conv1_silu_host)?;
let conv1_silu_nchw = nc_to_nchw(&conv1_silu_host, B, C2, H, W);
let mut conv1_silu_nchw_buf = device.buffer::<f32>(N_CONV1)?;
conv1_silu_nchw_buf.to_device(&conv1_silu_nchw)?;
let mut conv2_w_buf = device.buffer::<f32>(C2 * C2 * 9)?;
let conv2_nchw = device.buffer::<f32>(N_CONV2)?;
conv2_w_buf.to_device(&conv2_w)?;
device.launch(&conv3_prog, &CudaLaunchConfig {
grid: [(B * C2 * H * ow_tiles) as u32, 1, 1],
block: [128, 1, 1], cluster: [1, 1, 1],
}, (
conv1_silu_nchw_buf.as_device_ptr() as *mut f32,
conv2_w_buf.as_device_ptr() as *mut f32,
conv2_nchw.as_device_ptr() as *mut f32,
B as i32, C2 as i32, C2 as i32,
H as i32, W as i32, H as i32, W as i32,
))?;
let mut tmp = vec![0.0f32; N_CONV2];
conv2_nchw.to_host(&mut tmp)?;
let conv2_nc_host = nchw_to_nc(&tmp, B, C2, H, W);
let mut conv2_nc_buf = device.buffer::<f32>(N_CONV2)?;
let conv2_bn_out = device.buffer::<f32>(N_CONV2)?;
let mut conv2_bnw_buf = device.buffer::<f32>(C2)?;
let mut conv2_bnb_buf = device.buffer::<f32>(C2)?;
let mut conv2_bnrm = device.buffer::<f32>(C2)?;
let mut conv2_bnrv = device.buffer::<f32>(C2)?;
conv2_nc_buf.to_device(&conv2_nc_host)?;
conv2_bnw_buf.to_device(&conv2_bn_w)?;
conv2_bnb_buf.to_device(&conv2_bn_b)?;
conv2_bnrm.to_device(&conv2_bn_rm)?;
conv2_bnrv.to_device(&conv2_bn_rv)?;
device.launch(&bn_prog, &bn_cfg(C2), (
conv2_nc_buf.as_device_ptr() as *mut f32,
conv2_bn_out.as_device_ptr() as *mut f32,
conv2_bnw_buf.as_device_ptr() as *mut f32,
conv2_bnb_buf.as_device_ptr() as *mut f32,
conv2_bnrm.as_device_ptr() as *mut f32,
conv2_bnrv.as_device_ptr() as *mut f32,
N_SPATIAL as i32, C2 as i32, BN_EPS,
))?;
let conv2_silu = device.buffer::<f32>(N_CONV2)?;
device.launch(&silu_prog, &testing::launch_config(N_CONV2, BLOCK_SILU), (
conv2_bn_out.as_device_ptr() as *mut f32,
conv2_silu.as_device_ptr() as *mut f32,
N_CONV2 as i32,
))?;
let mut conv2_silu_host = vec![0.0f32; N_CONV2];
conv2_silu.to_host(&mut conv2_silu_host)?;
let conv2_silu_nchw = nc_to_nchw(&conv2_silu_host, B, C2, H, W);
let mut conv2_silu_nchw_buf = device.buffer::<f32>(N_CONV2)?;
conv2_silu_nchw_buf.to_device(&conv2_silu_nchw)?;
let mut conv3_w_buf = device.buffer::<f32>(C_BOX * C2)?;
let conv3_nchw = device.buffer::<f32>(N_CONV3)?;
conv3_w_buf.to_device(&conv3_w)?;
device.launch(&conv1_prog, &CudaLaunchConfig {
grid: [(B * C_BOX * H * ow_tiles) as u32, 1, 1],
block: [128, 1, 1], cluster: [1, 1, 1],
}, (
conv2_silu_nchw_buf.as_device_ptr() as *mut f32,
conv3_w_buf.as_device_ptr() as *mut f32,
conv3_nchw.as_device_ptr() as *mut f32,
B as i32, C2 as i32, C_BOX as i32,
H as i32, W as i32, H as i32, W as i32,
))?;
let mut tmp = vec![0.0f32; N_CONV3];
conv3_nchw.to_host(&mut tmp)?;
let conv3_nc_host = nchw_to_nc(&tmp, B, C_BOX, H, W);
let mut conv3_nc_buf = device.buffer::<f32>(N_CONV3)?;
let conv3_bias_out = device.buffer::<f32>(N_CONV3)?;
let mut bias_buf = device.buffer::<f32>(C_BOX)?;
conv3_nc_buf.to_device(&conv3_nc_host)?;
bias_buf.to_device(&conv3_bias)?;
device.launch(&bias_prog, &CudaLaunchConfig {
grid: [C_BOX as u32, 1, 1],
block: [BLOCK_BIAS as u32, 1, 1],
cluster: [1, 1, 1],
}, (
conv3_nc_buf.as_device_ptr() as *mut f32,
bias_buf.as_device_ptr() as *mut f32,
conv3_bias_out.as_device_ptr() as *mut f32,
N_SPATIAL as i32,
C_BOX as i32,
))?;
let mut y_nc = vec![0.0f32; N_CONV3];
conv3_bias_out.to_host(&mut y_nc)?;
let expected_nc = nchw_to_nc(&expected, B, C_BOX, H, W);
for i in 0..N_CONV3 {
assert!(
(y_nc[i] - expected_nc[i]).abs() < 1e-3,
"detect cv2[0] mismatch at element {i}: gpu={} expected={}",
y_nc[i], expected_nc[i],
);
}
Ok(())
}
}