#[expect(dead_code, missing_docs, reason = "bindgen output; bindgen does not emit docs")]
mod bindings {
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}
pub use bindings::NpuDesc;
pub(crate) use bindings::*;
use std::ffi::c_void;
use std::sync::OnceLock;
use crate::context::Device;
const TRACING_TARGET_NPU: &str = "span::npu";
const TRACING_CATEGORY_NPU: &str = "NPU";
pub(super) fn run(kernel: *mut Kernel, inputs: &[*const NpuBuffer], outputs: &[*const NpuBuffer]) -> i32 {
if !tracing::enabled!(target: TRACING_TARGET_NPU, tracing::Level::INFO) {
return unsafe {
furiosa_kernel_run(
kernel,
rt(),
inputs.as_ptr(),
inputs.len(),
outputs.as_ptr(),
outputs.len(),
)
};
}
unsafe extern "C" fn sink(_ctx: *mut c_void, name: Str, begin: u64, end: u64) {
let bytes = unsafe { std::slice::from_raw_parts(name.ptr.cast::<u8>(), name.len) };
tracing::info_span!(
target: TRACING_TARGET_NPU,
"NPU",
cat = TRACING_CATEGORY_NPU,
name = String::from_utf8_lossy(bytes).as_ref(),
begin_cycle = begin,
end_cycle = end,
);
}
unsafe extern "C" fn done(_ctx: *mut c_void) {}
unsafe {
furiosa_profiled_run(
rt(),
kernel,
inputs.as_ptr(),
inputs.len(),
outputs.as_ptr(),
outputs.len(),
Some(sink),
Some(done),
std::ptr::null_mut(),
)
}
}
pub(crate) fn rt() -> *mut Runtime {
struct Handle(*mut Runtime);
unsafe impl Send for Handle {}
unsafe impl Sync for Handle {}
static RT: OnceLock<Handle> = OnceLock::new();
RT.get_or_init(|| {
let ver = unsafe { std::ffi::CStr::from_ptr(furiosa_version()) };
log::info!("device-runtime: {}", ver.to_string_lossy());
let Device { chip: chips, pe: pes } = *DEVICE.get_or_init(|| Device { chip: 1, pe: 8 });
let available = available_chips();
for group in available.windows(chips as usize) {
let npus: Vec<NpuDesc> = group
.iter()
.flat_map(|&chip| match pes {
8 => vec![
NpuDesc {
chip,
pe_start: 0,
pe_end: 3,
},
NpuDesc {
chip,
pe_start: 4,
pe_end: 7,
},
],
n => vec![NpuDesc {
chip,
pe_start: 0,
pe_end: n - 1,
}],
})
.collect();
let ptr = unsafe { furiosa_runtime_init(npus.as_ptr(), npus.len()) };
if !ptr.is_null() {
return Handle(ptr);
}
}
panic!("no NPU available: {} chip(s) present, all in use", available.len());
})
.0
}
fn available_chips() -> Vec<u8> {
let mut chips: Vec<u8> = std::fs::read_dir("/dev/rngd")
.into_iter()
.flatten()
.flatten()
.filter_map(|entry| {
let name = entry.file_name();
name.to_str()?.strip_prefix("npu")?.strip_suffix("mgmt")?.parse().ok()
})
.collect();
chips.sort_unstable();
if let Some(visible) = visible_chips() {
chips.retain(|chip| visible.contains(chip));
}
chips
}
fn visible_chips() -> Option<Vec<u8>> {
let spec = std::env::var("FURIOSA_VISIBLE_DEVICES").ok()?;
let spec = spec.trim();
if spec.is_empty() {
return None;
}
Some(
spec.split(',')
.map(|id| {
id.trim()
.parse()
.unwrap_or_else(|_| panic!("FURIOSA_VISIBLE_DEVICES: `{id}` is not a chip index (0-255)"))
})
.collect(),
)
}
static DEVICE: OnceLock<Device> = OnceLock::new();
pub(crate) fn bind_device(device: Device) {
let cur = *DEVICE.get_or_init(|| device);
assert_eq!(
cur, device,
"conflicting NPU device in one process: {cur:?} vs {device:?}"
);
}