use std::sync::Arc;
use cuda_async::device_operation::DeviceOp;
use cutile::tensor::{IntoPartition, Tensor};
use cutile::tile_kernel::TileKernel;
use nuvec_kernels::cuda::ptx::DType;
use nuvec_kernels::cuda::tile::kernels;
use super::device::Device;
use super::error::{CudaError, Result};
use super::memory::Buffer;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum F32Mode {
Exact,
Tf32,
}
impl F32Mode {
pub fn current() -> Self {
static MODE: std::sync::OnceLock<F32Mode> = std::sync::OnceLock::new();
*MODE.get_or_init(|| match std::env::var("NUVEC_CUDA_TF32").as_deref() {
Ok("1") => F32Mode::Tf32,
_ => F32Mode::Exact,
})
}
}
pub fn gemm_tiles(dtype: DType, m: usize, n: usize, _cc: (u32, u32)) -> (usize, usize, usize) {
if let Ok(v) = std::env::var("NUVEC_CUDA_GEMM_TILES") {
let t: Vec<usize> = v.split(',').filter_map(|x| x.trim().parse().ok()).collect();
if let [bm, bn, bk] = t[..] {
return (bm, bn, bk);
}
}
let _ = (m, n);
match dtype {
DType::F32 => (64, 64, 32),
_ => (128, 128, 32),
}
}
unsafe fn borrow<T: cutile::DType>(buffer: &Buffer, rows: usize, cols: usize) -> Tensor<T> {
let shape = vec![rows as i32, cols as i32];
let strides = vec![cols as i32, 1];
unsafe { Tensor::borrow_raw_parts(buffer.device_ptr(), 0, shape, strides) }
}
fn tile_error(e: impl std::fmt::Display) -> CudaError {
CudaError::Tile(e.to_string())
}
pub fn gemm(
device: &'static Device,
dtype: DType,
out: &mut Buffer,
x: &Buffer,
y: &Buffer,
(m, n, k): (usize, usize, usize),
) -> Result<()> {
if [m, n, k].iter().any(|&d| d == 0 || d > i32::MAX as usize) {
return Err(CudaError::InvalidArgument(format!(
"gemm of {m}×{k} by {k}×{n}"
)));
}
let (bm, bn, bk) = gemm_tiles(dtype, m, n, device.info().compute_capability);
let name = match dtype {
DType::F32 => "f32",
DType::F16 => "f16",
DType::BF16 => "bf16",
};
let aligned = m.is_multiple_of(bm) && n.is_multiple_of(bn) && k.is_multiple_of(bk);
let tiles = [bm.to_string(), bn.to_string(), bk.to_string()];
let kk = k as i32;
device
.device
.bind_to_thread()
.map_err(super::error::driver("cuCtxSetCurrent"))?;
macro_rules! borrowed {
($t:ty) => {
unsafe {
(
borrow::<$t>(out, m, n),
Arc::new(borrow::<$t>(x, m, k)),
Arc::new(borrow::<$t>(y, k, n)),
)
}
};
}
macro_rules! run {
($t:ty) => {{
let (z, a, b) = borrowed!($t);
if aligned {
let mut g = vec![name.to_string()];
g.extend(tiles.clone());
let launch = unsafe { kernels::gemm_fast(z.partition([bm, bn]), a, b, kk) };
launch
.generics(g)
.sync_on(&device.stream)
.map_err(tile_error)?;
} else {
let mut g = vec![name.to_string()];
g.extend(tiles.clone());
g.push(k.to_string());
let launch = kernels::gemm(z.partition([bm, bn]), a, b).generics(g);
launch.sync_on(&device.stream).map_err(tile_error)?;
}
}};
}
match dtype {
DType::F32 if aligned => {
let (z, a, b) = borrowed!(f32);
let g = tiles.to_vec();
let z = z.partition([bm, bn]);
match F32Mode::current() {
F32Mode::Exact => unsafe { kernels::gemm_3xtf32(z, a, b, kk) }
.generics(g)
.sync_on(&device.stream)
.map(|_| ()),
F32Mode::Tf32 => unsafe { kernels::gemm_tf32(z, a, b, kk) }
.generics(g)
.sync_on(&device.stream)
.map(|_| ()),
}
.map_err(tile_error)?;
}
DType::F32 => run!(f32),
DType::F16 => run!(half::f16),
DType::BF16 => run!(half::bf16),
}
Ok(())
}