use std::ffi::{c_void, CStr};
use anyhow::{bail, Result};
use crate::ffi;
pub fn check(code: i32, what: &str) -> Result<()> {
if code == 0 {
return Ok(());
}
let msg = unsafe { CStr::from_ptr(ffi::dr_error_string(code)) }.to_string_lossy();
bail!("{what}: CUDA error {code} ({msg})")
}
pub fn launched(code: i32, what: &str) -> Result<()> {
if code != 0 {
bail!("{what}: unsupported shape (launcher returned {code})");
}
if cfg!(debug_assertions) {
check(unsafe { ffi::dr_last_error() }, what)?;
}
Ok(())
}
pub fn mem_info() -> Result<(usize, usize)> {
let (mut free, mut total) = (0usize, 0usize);
check(unsafe { ffi::dr_mem_info(&mut free, &mut total) }, "cudaMemGetInfo")?;
Ok((free, total))
}
pub struct DevBuf {
ptr: *mut c_void,
bytes: usize,
}
unsafe impl Send for DevBuf {}
impl DevBuf {
pub fn alloc(bytes: usize) -> Result<Self> {
let mut ptr = std::ptr::null_mut();
check(unsafe { ffi::dr_malloc(&mut ptr, bytes.max(256)) }, &format!("cudaMalloc({bytes})"))?;
Ok(Self { ptr, bytes })
}
pub fn zeros(bytes: usize, st: &Stream) -> Result<Self> {
let b = Self::alloc(bytes)?;
b.zero(st)?;
Ok(b)
}
pub fn from_bytes(data: &[u8], st: &Stream) -> Result<Self> {
let b = Self::alloc(data.len())?;
b.upload(data, st)?;
Ok(b)
}
pub fn from_slice<T: Copy>(data: &[T], st: &Stream) -> Result<Self> {
let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, std::mem::size_of_val(data)) };
Self::from_bytes(bytes, st)
}
pub fn bytes(&self) -> usize {
self.bytes
}
pub fn upload(&self, data: &[u8], st: &Stream) -> Result<()> {
assert!(data.len() <= self.bytes);
check(unsafe { ffi::dr_h2d(self.ptr, data.as_ptr() as *const c_void, data.len(), st.raw) }, "h2d")
}
pub fn upload_at<T: Copy>(&self, offset_bytes: usize, data: &[T], st: &Stream) -> Result<()> {
let n = std::mem::size_of_val(data);
assert!(offset_bytes + n <= self.bytes);
check(
unsafe { ffi::dr_h2d((self.ptr as *mut u8).add(offset_bytes) as *mut c_void, data.as_ptr() as *const c_void, n, st.raw) },
"h2d",
)
}
pub fn download<T: Copy + Default>(&self, count: usize, st: &Stream) -> Result<Vec<T>> {
self.download_at(0, count, st)
}
pub fn download_at<T: Copy + Default>(&self, offset_bytes: usize, count: usize, st: &Stream) -> Result<Vec<T>> {
let mut v = vec![T::default(); count];
let n = count * std::mem::size_of::<T>();
assert!(offset_bytes + n <= self.bytes);
check(
unsafe { ffi::dr_d2h(v.as_mut_ptr() as *mut c_void, (self.ptr as *const u8).add(offset_bytes) as *const c_void, n, st.raw) },
"d2h",
)?;
st.sync()?;
Ok(v)
}
pub fn zero(&self, st: &Stream) -> Result<()> {
check(unsafe { ffi::dr_memset(self.ptr, 0, self.bytes, st.raw) }, "memset")
}
pub fn ptr<T>(&self) -> *mut T {
self.ptr as *mut T
}
pub fn at<T>(&self, elems: usize) -> *mut T {
assert!((elems * std::mem::size_of::<T>()) <= self.bytes);
unsafe { (self.ptr as *mut T).add(elems) }
}
}
impl Drop for DevBuf {
fn drop(&mut self) {
unsafe { ffi::dr_free(self.ptr) };
}
}
pub struct Stream {
pub raw: ffi::Stream,
}
impl Stream {
pub fn new() -> Result<Self> {
let mut raw = std::ptr::null_mut();
check(unsafe { ffi::dr_stream_create(&mut raw) }, "cudaStreamCreate")?;
Ok(Self { raw })
}
pub fn sync(&self) -> Result<()> {
check(unsafe { ffi::dr_stream_sync(self.raw) }, "cudaStreamSynchronize")
}
}
pub struct Graph {
exec: ffi::GraphExec,
}
impl Graph {
pub fn capture(st: &Stream, record: impl FnOnce() -> Result<()>) -> Result<Self> {
check(unsafe { ffi::dr_graph_begin(st.raw) }, "cudaStreamBeginCapture")?;
let recorded = record();
let mut exec = std::ptr::null_mut();
let ended = check(unsafe { ffi::dr_graph_end(st.raw, &mut exec) }, "cudaStreamEndCapture/Instantiate");
recorded?;
ended?;
Ok(Self { exec })
}
pub fn launch(&self, st: &Stream) -> Result<()> {
check(unsafe { ffi::dr_graph_launch(self.exec, st.raw) }, "cudaGraphLaunch")
}
}
impl Drop for Graph {
fn drop(&mut self) {
unsafe { ffi::dr_graph_destroy(self.exec) };
}
}
pub struct Cublas {
handle: *mut c_void,
}
impl Cublas {
pub fn new(st: &Stream) -> Result<Self> {
let mut handle = std::ptr::null_mut();
let r = unsafe { ffi::dr_cublas_create(&mut handle, st.raw) };
if r != 0 {
bail!("cublasCreate failed: {r}");
}
Ok(Self { handle })
}
pub fn gemm(&self, a: *const u16, w: *const u16, c: *mut f32, m: usize, n: usize, k: usize, beta: f32) -> Result<()> {
let r = unsafe { ffi::dr_gemm(self.handle, a, w, c, m as i32, n as i32, k as i32, beta) };
if r != 0 {
bail!("cublasGemmEx failed: {r} (m={m} n={n} k={k})");
}
Ok(())
}
pub fn gemm_f16(&self, a: *const u16, w: *const u16, c: *mut f32, m: usize, n: usize, k: usize, beta: f32) -> Result<()> {
let r = unsafe { ffi::dr_gemm_f16(self.handle, a, w, c, m as i32, n as i32, k as i32, beta) };
if r != 0 {
bail!("cublasGemmEx(f16) failed: {r} (m={m} n={n} k={k})");
}
Ok(())
}
}
pub struct Pinned<T: Copy> {
ptr: *mut T,
len: usize,
}
unsafe impl<T: Copy> Send for Pinned<T> {}
impl<T: Copy> Pinned<T> {
pub fn new(len: usize) -> Result<Self> {
let mut p = std::ptr::null_mut();
check(unsafe { ffi::dr_host_alloc(&mut p, len * std::mem::size_of::<T>()) }, "cudaMallocHost")?;
Ok(Self { ptr: p as *mut T, len })
}
pub fn as_slice(&self) -> &[T] {
unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
}
pub fn ptr(&self) -> *mut T {
self.ptr
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
}
impl<T: Copy> Drop for Pinned<T> {
fn drop(&mut self) {
unsafe { ffi::dr_host_free(self.ptr as *mut c_void) };
}
}