use core::ffi::c_void;
use core::marker::PhantomData;
use baracuda_cutlass::{Error, Result};
use baracuda_driver::{DeviceBuffer, Stream};
use baracuda_kernels_types::{
contiguous_stride, Element, ElementKind, KernelSku, PlanPreference, PrecisionGuarantee,
SortKind, TensorMut, TensorRef, Workspace,
};
use super::sort::build_sku;
use super::topk::{TopkArgs, TopkDescriptor, TopkPlan};
use super::TOPK_MAX_K;
#[derive(Copy, Clone, Debug)]
pub struct KthvalueDescriptor {
pub batch: i32,
pub row_len: i32,
pub k: i32,
pub element: ElementKind,
}
pub struct KthvalueArgs<'a, T: Element> {
pub input: TensorRef<'a, T, 2>,
pub values: TensorMut<'a, T, 1>,
pub indices: TensorMut<'a, i32, 1>,
}
pub struct KthvaluePlan<T: Element> {
desc: KthvalueDescriptor,
sku: KernelSku,
_marker: PhantomData<T>,
}
impl<T: Element> KthvaluePlan<T> {
pub fn select(
_stream: &Stream,
desc: &KthvalueDescriptor,
_pref: PlanPreference,
) -> Result<Self> {
if desc.element != T::KIND {
return Err(Error::Unsupported(
"baracuda-kernels::KthvaluePlan: descriptor element != type parameter T",
));
}
if desc.batch < 0 || desc.row_len < 0 || desc.k < 0 {
return Err(Error::InvalidProblem(
"baracuda-kernels::KthvaluePlan: batch / row_len / k must be non-negative",
));
}
if desc.k >= desc.row_len {
return Err(Error::InvalidProblem(
"baracuda-kernels::KthvaluePlan: k must be < row_len (0-indexed)",
));
}
if desc.k + 1 > TOPK_MAX_K {
return Err(Error::Unsupported(
"baracuda-kernels::KthvaluePlan: k+1 > 64 not supported (composes topk)",
));
}
if !matches!(T::KIND, ElementKind::F32 | ElementKind::F64) {
return Err(Error::Unsupported(
"baracuda-kernels::KthvaluePlan: today only f32 / f64 wired (TopkPlan limit)",
));
}
let sku = build_sku::<T>(SortKind::Kthvalue);
Ok(Self {
desc: *desc,
sku,
_marker: PhantomData,
})
}
pub fn can_implement(&self, args: &KthvalueArgs<'_, T>) -> Result<()> {
if args.input.shape != [self.desc.batch, self.desc.row_len] {
return Err(Error::InvalidProblem(
"baracuda-kernels::KthvaluePlan: input shape != [batch, row_len]",
));
}
if args.values.shape != [self.desc.batch] {
return Err(Error::InvalidProblem(
"baracuda-kernels::KthvaluePlan: values shape != [batch]",
));
}
if args.indices.shape != [self.desc.batch] {
return Err(Error::InvalidProblem(
"baracuda-kernels::KthvaluePlan: indices shape != [batch]",
));
}
Ok(())
}
#[inline]
pub fn workspace_size(&self) -> usize {
0
}
#[inline]
pub fn sku(&self) -> KernelSku {
self.sku
}
#[inline]
pub fn precision_guarantee(&self) -> PrecisionGuarantee {
self.sku.precision_guarantee
}
pub fn run(
&self,
stream: &Stream,
_workspace: Workspace<'_>,
args: KthvalueArgs<'_, T>,
) -> Result<()> {
self.can_implement(&args)?;
if self.desc.batch == 0 {
return Ok(());
}
let kp1 = self.desc.k + 1;
let topk_desc = TopkDescriptor {
batch: self.desc.batch,
row_len: self.desc.row_len,
k: kp1,
largest: false,
element: T::KIND,
};
let topk_plan = TopkPlan::<T>::select(stream, &topk_desc, PlanPreference::default())?;
let ctx = stream.context();
let total = (self.desc.batch as usize) * (kp1 as usize);
let mut topk_vals: DeviceBuffer<T> = DeviceBuffer::zeros(ctx, total).map_err(|_| {
Error::InvalidProblem(
"baracuda-kernels::KthvaluePlan: failed to allocate topk values buffer",
)
})?;
let mut topk_idx: DeviceBuffer<i32> = DeviceBuffer::zeros(ctx, total).map_err(|_| {
Error::InvalidProblem(
"baracuda-kernels::KthvaluePlan: failed to allocate topk indices buffer",
)
})?;
let topk_args = TopkArgs::<T> {
input: args.input,
values: TensorMut {
data: topk_vals.as_slice_mut(),
shape: [self.desc.batch, kp1],
stride: contiguous_stride([self.desc.batch, kp1]),
},
indices: TensorMut {
data: topk_idx.as_slice_mut(),
shape: [self.desc.batch, kp1],
stride: contiguous_stride([self.desc.batch, kp1]),
},
};
topk_plan.run(stream, Workspace::None, topk_args)?;
stream
.synchronize()
.map_err(|_| Error::CutlassInternal(-1))?;
let val_bytes = total * core::mem::size_of::<T>();
let idx_bytes_total = total * core::mem::size_of::<i32>();
let mut host_vals: Vec<u8> = vec![0u8; val_bytes];
let mut host_idx_bytes: Vec<u8> = vec![0u8; idx_bytes_total];
unsafe {
copy_d2h_async(
host_vals.as_mut_ptr() as *mut c_void,
topk_vals.as_raw().0,
val_bytes,
stream,
)?;
copy_d2h_async(
host_idx_bytes.as_mut_ptr() as *mut c_void,
topk_idx.as_raw().0,
idx_bytes_total,
stream,
)?;
}
stream
.synchronize()
.map_err(|_| Error::CutlassInternal(-1))?;
let out_val_bytes = (self.desc.batch as usize) * core::mem::size_of::<T>();
let out_idx_bytes = (self.desc.batch as usize) * core::mem::size_of::<i32>();
let mut out_vals: Vec<u8> = vec![0u8; out_val_bytes];
let mut out_idx: Vec<u8> = vec![0u8; out_idx_bytes];
let stride_v = core::mem::size_of::<T>();
let stride_i = core::mem::size_of::<i32>();
for row in 0..self.desc.batch as usize {
let src_v_off = (row * (kp1 as usize) + self.desc.k as usize) * stride_v;
let src_i_off = (row * (kp1 as usize) + self.desc.k as usize) * stride_i;
let dst_v_off = row * stride_v;
let dst_i_off = row * stride_i;
out_vals[dst_v_off..dst_v_off + stride_v]
.copy_from_slice(&host_vals[src_v_off..src_v_off + stride_v]);
out_idx[dst_i_off..dst_i_off + stride_i]
.copy_from_slice(&host_idx_bytes[src_i_off..src_i_off + stride_i]);
}
unsafe {
copy_h2d_async(
args.values.data.as_raw().0 as *mut c_void,
out_vals.as_ptr() as *const c_void,
out_val_bytes,
stream,
)?;
copy_h2d_async(
args.indices.data.as_raw().0 as *mut c_void,
out_idx.as_ptr() as *const c_void,
out_idx_bytes,
stream,
)?;
}
stream
.synchronize()
.map_err(|_| Error::CutlassInternal(-1))?;
drop(out_vals);
drop(out_idx);
drop(host_vals);
drop(host_idx_bytes);
Ok(())
}
}
unsafe fn copy_h2d_async(
dst: *mut c_void,
src: *const c_void,
bytes: usize,
stream: &Stream,
) -> Result<()> {
if bytes == 0 {
return Ok(());
}
#[allow(non_camel_case_types)]
type CUresult = i32;
unsafe extern "system" {
fn cuMemcpyHtoDAsync_v2(
dst_device: u64,
src_host: *const c_void,
byte_count: usize,
h_stream: *mut c_void,
) -> CUresult;
}
let status =
unsafe { cuMemcpyHtoDAsync_v2(dst as u64, src, bytes, stream.as_raw() as *mut c_void) };
if status != 0 {
return Err(Error::CutlassInternal(-status));
}
Ok(())
}
unsafe fn copy_d2h_async(
dst: *mut c_void,
src: u64,
bytes: usize,
stream: &Stream,
) -> Result<()> {
if bytes == 0 {
return Ok(());
}
#[allow(non_camel_case_types)]
type CUresult = i32;
unsafe extern "system" {
fn cuMemcpyDtoHAsync_v2(
dst_host: *mut c_void,
src_device: u64,
byte_count: usize,
h_stream: *mut c_void,
) -> CUresult;
}
let status =
unsafe { cuMemcpyDtoHAsync_v2(dst, src, bytes, stream.as_raw() as *mut c_void) };
if status != 0 {
return Err(Error::CutlassInternal(-status));
}
Ok(())
}