use std::fmt;
use std::ptr;
use std::slice;
use furiosa_mapping::M;
use crate::prelude::HostTensor;
use crate::scalar::MaterializableScalar;
use crate::storage::BufStorage;
use crate::tensor::Tensor;
use crate::tensor::memory::HbmTensor;
use super::Npu;
use super::ffi;
#[derive(Debug)]
pub struct Buffer(*mut ffi::NpuBuffer);
unsafe impl Send for Buffer {}
unsafe impl Sync for Buffer {}
impl Drop for Buffer {
fn drop(&mut self) {
if !self.0.is_null() {
unsafe { ffi::furiosa_npu_buffer_free(self.0) }
}
}
}
impl Clone for Buffer {
fn clone(&self) -> Self {
Buffer(unsafe { ffi::furiosa_npu_buffer_clone(self.0) })
}
}
impl Buffer {
pub(super) fn from_raw(ptr: *mut ffi::NpuBuffer) -> Self {
Buffer(ptr)
}
pub(super) fn as_ptr(&self) -> *const ffi::NpuBuffer {
self.0
}
pub(crate) fn npu(addr: u64, len: usize) -> Self {
Buffer::from_raw(unsafe { ffi::furiosa_npu_buffer_from(ffi::rt(), addr, len) })
}
pub(crate) fn alloc(size: usize) -> Self {
let ptr = unsafe { ffi::furiosa_npu_buffer(ffi::rt(), size) };
assert!(!ptr.is_null(), "failed to allocate buffer");
Buffer::from_raw(ptr)
}
pub(crate) fn offset(&self) -> u64 {
unsafe { ffi::furiosa_npu_buffer_offset(self.0) }
}
}
pub struct CpuBuffer {
ptr: *mut ffi::CpuBuffer,
len: usize,
}
unsafe impl Send for CpuBuffer {}
unsafe impl Sync for CpuBuffer {}
impl fmt::Debug for CpuBuffer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CpuBuffer").field("len", &self.len()).finish()
}
}
impl Drop for CpuBuffer {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe { ffi::furiosa_cpu_buffer_free(self.ptr) }
}
}
}
impl From<Vec<u8>> for CpuBuffer {
fn from(v: Vec<u8>) -> Self {
Self::from_slice(&v)
}
}
impl From<CpuBuffer> for Vec<u8> {
fn from(c: CpuBuffer) -> Self {
c.as_slice().to_vec()
}
}
impl AsRef<[u8]> for CpuBuffer {
fn as_ref(&self) -> &[u8] {
self.as_slice()
}
}
impl AsMut<[u8]> for CpuBuffer {
fn as_mut(&mut self) -> &mut [u8] {
self.as_mut_slice()
}
}
impl Clone for CpuBuffer {
fn clone(&self) -> Self {
Self::from_slice(self.as_slice())
}
}
impl CpuBuffer {
fn alloc(len: usize) -> Self {
let ptr = unsafe { ffi::furiosa_cpu_buffer(len) };
assert!(!ptr.is_null(), "failed to allocate DMA buffer");
CpuBuffer { ptr, len }
}
fn from_slice(bytes: &[u8]) -> Self {
let cpu = Self::alloc(bytes.len());
if !bytes.is_empty() {
unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), cpu.data_ptr(), bytes.len()) };
}
cpu
}
fn as_ptr(&self) -> *const ffi::CpuBuffer {
self.ptr
}
fn data_ptr(&self) -> *mut u8 {
unsafe { ffi::furiosa_cpu_buffer_addr(self.ptr) as *mut u8 }
}
fn len(&self) -> usize {
self.len
}
fn as_slice(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.data_ptr(), self.len()) }
}
fn as_mut_slice(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self.data_ptr(), self.len()) }
}
}
pub struct Kernel {
ptr: *mut ffi::Kernel,
}
unsafe impl Send for Kernel {}
unsafe impl Sync for Kernel {}
impl std::fmt::Debug for Kernel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Kernel").finish_non_exhaustive()
}
}
impl Drop for Kernel {
fn drop(&mut self) {
unsafe { ffi::furiosa_kernel_free(self.ptr) }
}
}
impl Kernel {
pub async fn load(data: &[u8]) -> Self {
assert!(!data.is_empty(), "attempted to load an uncompiled NPU kernel");
log::debug!("load: {} bytes", data.len());
let ptr = unsafe { ffi::furiosa_kernel_load(ffi::rt(), data.as_ptr(), data.len()) };
assert!(!ptr.is_null(), "failed to load kernel");
Kernel { ptr }
}
pub async fn run(&self, inputs: &[Buffer], outputs: &[Buffer]) {
log::debug!("run: inputs={}, outputs={}", inputs.len(), outputs.len());
let in_ptrs = inputs.iter().map(|b| b.as_ptr()).collect::<Vec<_>>();
let out_ptrs = outputs.iter().map(|b| b.as_ptr()).collect::<Vec<_>>();
assert!(ffi::run(self.ptr, &in_ptrs, &out_ptrs) == 0, "kernel execution failed");
}
pub fn alloc(&self, size: usize) -> Buffer {
Buffer::alloc(size)
}
pub async fn write<D: MaterializableScalar, Element: M, Chip: M, Element2: M>(
host: &HostTensor<D, Element, Npu>,
) -> HbmTensor<D, Chip, Element2, Npu> {
let src = host.storage().inner();
let len = src.len();
let dst = Buffer::alloc(len);
let addr = dst.offset();
log::debug!("write: addr=0x{addr:x}, len={len}");
assert!(
unsafe { ffi::furiosa_write(ffi::rt(), src.as_ptr(), dst.as_ptr()) } == 0,
"DMA write failed"
);
unsafe { HbmTensor::from_addr(addr) }.owns(dst)
}
pub async fn read<D: MaterializableScalar, Chip: M, Element: M, Element2: M>(
hbm: &HbmTensor<D, Chip, Element, Npu>,
) -> HostTensor<D, Element2, Npu> {
let count = furiosa_mapping::Pair::<Chip, Element>::SIZE;
let len = D::size_in_bytes_from_length(count);
let hbm_addr = hbm.address();
log::debug!("read: addr=0x{:x}, len={len}", hbm_addr);
let src = Buffer::npu(hbm_addr, len);
let cpu = CpuBuffer::alloc(len);
assert!(
unsafe { ffi::furiosa_read(ffi::rt(), src.as_ptr(), cpu.as_ptr()) } == 0,
"DMA read failed"
);
let storage = BufStorage::<D, CpuBuffer>::from(cpu);
Tensor::from_inner(storage).into()
}
}