Skip to main content

ironaccelerator_neuron/
drv.rs

1//! `libnrt` loader. We need three symbols — `nrt_init`,
2//! `nrt_get_total_nc_count`, and `nrt_get_version` — and a best-effort
3//! read of the Neuron-specific env vars that AWS containers set.
4//!
5//! `drop(sym)` on a `libloading::Symbol` ends the borrow on its Library;
6//! Symbol doesn't impl Drop but the lifetime parameter does the work.
7
8#![allow(clippy::drop_non_drop)]
9
10use core::ffi::c_void;
11
12use libloading::{Library, Symbol};
13use once_cell::sync::OnceCell;
14
15pub type NrtStatus = u32;
16pub const NRT_SUCCESS: NrtStatus = 0;
17
18pub type NrtModelHandle = *mut c_void;
19pub type NrtTensorSetHandle = *mut c_void;
20pub type NrtTensorHandle = *mut c_void;
21
22pub type NrtLoadFn = unsafe extern "C" fn(
23    neff: *const u8,
24    neff_size: usize,
25    start_nc: i32,
26    nc_count: i32,
27    model: *mut NrtModelHandle,
28) -> NrtStatus;
29pub type NrtUnloadFn = unsafe extern "C" fn(model: NrtModelHandle) -> NrtStatus;
30pub type NrtExecuteFn = unsafe extern "C" fn(
31    model: NrtModelHandle,
32    input_set: NrtTensorSetHandle,
33    output_set: NrtTensorSetHandle,
34) -> NrtStatus;
35pub type NrtAllocTensorSetFn = unsafe extern "C" fn(out: *mut NrtTensorSetHandle) -> NrtStatus;
36pub type NrtFreeTensorSetFn = unsafe extern "C" fn(set: NrtTensorSetHandle) -> NrtStatus;
37pub type NrtAddTensorToSetFn = unsafe extern "C" fn(
38    set: NrtTensorSetHandle,
39    name: *const core::ffi::c_char,
40    tensor: NrtTensorHandle,
41) -> NrtStatus;
42
43type NrtInitFn =
44    unsafe extern "C" fn(framework: u32, framework_version: *const core::ffi::c_char) -> NrtStatus;
45type NrtGetTotalNcCountFn = unsafe extern "C" fn(count: *mut u32) -> NrtStatus;
46type NrtGetVersionFn = unsafe extern "C" fn(out: *mut core::ffi::c_char, out_len: u32) -> NrtStatus;
47
48static LIB: OnceCell<Option<Loaded>> = OnceCell::new();
49
50pub struct Loaded {
51    _lib: Library,
52    total_cores: u32,
53    version: String,
54    pub nrt_load: Option<NrtLoadFn>,
55    pub nrt_unload: Option<NrtUnloadFn>,
56    pub nrt_execute: Option<NrtExecuteFn>,
57    pub nrt_alloc_tensor_set: Option<NrtAllocTensorSetFn>,
58    pub nrt_free_tensor_set: Option<NrtFreeTensorSetFn>,
59    pub nrt_add_tensor_to_set: Option<NrtAddTensorToSetFn>,
60}
61
62unsafe impl Send for Loaded {}
63unsafe impl Sync for Loaded {}
64
65#[cfg(any(target_os = "linux", target_os = "android"))]
66const LIB_CANDIDATES: &[&str] = &[
67    "libnrt.so.1",
68    "libnrt.so",
69    "/opt/aws/neuron/lib/libnrt.so.1",
70];
71#[cfg(not(any(target_os = "linux", target_os = "android")))]
72const LIB_CANDIDATES: &[&str] = &[];
73
74pub(crate) fn loaded() -> Option<&'static Loaded> {
75    LIB.get_or_init(load).as_ref()
76}
77
78fn load() -> Option<Loaded> {
79    for name in LIB_CANDIDATES {
80        let lib = match unsafe { Library::new(*name) } {
81            Ok(l) => l,
82            Err(_) => continue,
83        };
84        unsafe {
85            let init: Symbol<NrtInitFn> = match lib.get(b"nrt_init\0") {
86                Ok(s) => s,
87                Err(_) => continue,
88            };
89            // framework=0 (NRT_FRAMEWORK_TYPE_NONE) is accepted for
90            // non-framework embedders like us.
91            let tag = b"ironaccelerator\0" as *const _ as *const core::ffi::c_char;
92            if init(0, tag) != NRT_SUCCESS {
93                continue;
94            }
95            let count_fn: Symbol<NrtGetTotalNcCountFn> = match lib.get(b"nrt_get_total_nc_count\0")
96            {
97                Ok(s) => s,
98                Err(_) => continue,
99            };
100            let mut count: u32 = 0;
101            if count_fn(&mut count) != NRT_SUCCESS {
102                continue;
103            }
104            let version = lib
105                .get::<NrtGetVersionFn>(b"nrt_get_version\0")
106                .ok()
107                .and_then(|f| read_version(*f))
108                .unwrap_or_default();
109            drop(init);
110            drop(count_fn);
111
112            // Optional symbols — present on Neuron SDK 2.x but we don't
113            // want to fail the whole backend if one is missing on an
114            // older runtime.
115            let nrt_load = lib.get::<NrtLoadFn>(b"nrt_load\0").ok().map(|s| *s);
116            let nrt_unload = lib.get::<NrtUnloadFn>(b"nrt_unload\0").ok().map(|s| *s);
117            let nrt_execute = lib.get::<NrtExecuteFn>(b"nrt_execute\0").ok().map(|s| *s);
118            let nrt_alloc_tensor_set = lib
119                .get::<NrtAllocTensorSetFn>(b"nrt_allocate_tensor_set\0")
120                .ok()
121                .map(|s| *s);
122            let nrt_free_tensor_set = lib
123                .get::<NrtFreeTensorSetFn>(b"nrt_free_tensor_set\0")
124                .ok()
125                .map(|s| *s);
126            let nrt_add_tensor_to_set = lib
127                .get::<NrtAddTensorToSetFn>(b"nrt_add_tensor_to_tensor_set\0")
128                .ok()
129                .map(|s| *s);
130
131            return Some(Loaded {
132                _lib: lib,
133                total_cores: count,
134                version,
135                nrt_load,
136                nrt_unload,
137                nrt_execute,
138                nrt_alloc_tensor_set,
139                nrt_free_tensor_set,
140                nrt_add_tensor_to_set,
141            });
142        }
143    }
144    None
145}
146
147unsafe fn read_version(f: NrtGetVersionFn) -> Option<String> {
148    let mut buf = vec![0i8; 128];
149    let status = f(buf.as_mut_ptr() as *mut _, buf.len() as u32);
150    if status != NRT_SUCCESS {
151        return None;
152    }
153    let bytes: Vec<u8> = buf
154        .iter()
155        .take_while(|&&c| c != 0)
156        .map(|&c| c as u8)
157        .collect();
158    String::from_utf8(bytes).ok()
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq)]
162pub enum NeuronGen {
163    /// inf1 — first-gen Inferentia.
164    Inf1,
165    /// trn1 / inf2 — second-gen Trainium / Inferentia.
166    Trn1,
167    /// trn2 — third-gen Trainium.
168    Trn2,
169    Unknown,
170}
171
172pub fn is_available() -> bool {
173    loaded().is_some()
174}
175
176pub fn total_cores() -> u32 {
177    loaded().map(|l| l.total_cores).unwrap_or(0)
178}
179
180pub fn version() -> Option<&'static str> {
181    loaded().map(|l| l.version.as_str())
182}
183
184/// Best-effort generation detection. Prefers `NEURON_INSTANCE_TYPE` env
185/// hint (commonly set by AWS Deep Learning AMIs + containers), falls
186/// back to the Neuron runtime major version.
187pub fn detect_generation() -> NeuronGen {
188    if let Ok(t) = std::env::var("NEURON_INSTANCE_TYPE") {
189        let t = t.to_ascii_lowercase();
190        if t.starts_with("trn2") {
191            return NeuronGen::Trn2;
192        }
193        if t.starts_with("trn1") || t.starts_with("inf2") {
194            return NeuronGen::Trn1;
195        }
196        if t.starts_with("inf1") {
197            return NeuronGen::Inf1;
198        }
199    }
200    match version()
201        .and_then(|v| v.split('.').next())
202        .and_then(|m| m.parse::<u32>().ok())
203    {
204        Some(n) if n >= 3 => NeuronGen::Trn2,
205        Some(2) => NeuronGen::Trn1,
206        Some(1) => NeuronGen::Inf1,
207        _ => NeuronGen::Unknown,
208    }
209}