use anyhow::{Context, Result, bail};
use std::path::{Path, PathBuf};
pub const VERSION: &str = "0.4.0-cu12";
const LINUX: &[crate::packs::Asset] = &[crate::packs::Asset {
url: "https://files.pythonhosted.org/packages/52/a6/1405069d40e2d6d37b21031e5dc2cc8b8197fbb2e3d4e4f65b9155c57e4f/onnxruntime_ep_nv_tensorrt_rtx_cu12-0.4.0-py3-none-manylinux_2_28_x86_64.whl",
sha256: "221807b1797a6270f37dfd7981f66e25ca7483f3883bfe23ef2b6a4198fe2b10",
size: 167_721_016,
form: crate::packs::Form::Wheel {
prefix: "onnxruntime_ep_nv_tensorrt_rtx",
},
}];
const WINDOWS: &[crate::packs::Asset] = &[crate::packs::Asset {
url: "https://files.pythonhosted.org/packages/8b/e4/f19fdbffcf8faf18d9f81973f81e3d378ef371f857b049a3f355535a015c/onnxruntime_ep_nv_tensorrt_rtx_cu12-0.4.0-py3-none-win_amd64.whl",
sha256: "5d9dba6aafd8e0863f34d0e5f51ad7c7ae9a5d6a92a55fe45d9637d3a5f1e1a7",
size: 105_822_356,
form: crate::packs::Form::Wheel {
prefix: "onnxruntime_ep_nv_tensorrt_rtx",
},
}];
pub fn pack() -> crate::packs::Pack {
crate::packs::Pack {
name: "trt",
version: VERSION,
assets: if cfg!(all(target_os = "linux", target_arch = "x86_64")) {
LINUX
} else if cfg!(all(windows, target_arch = "x86_64")) {
WINDOWS
} else {
&[]
},
}
}
fn plugin_file() -> &'static str {
if cfg!(windows) {
"onnxruntime_providers_nv_tensorrt_rtx.dll"
} else {
"libonnxruntime_providers_nv_tensorrt_rtx.so"
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Card {
pub name: String,
pub capability: (i32, i32),
pub driver: String,
}
pub fn judge(card: Option<Card>) -> std::result::Result<String, String> {
let Some(Card {
name,
capability: (major, minor),
driver,
}) = card
else {
return Err("no NVIDIA card found".to_string());
};
if numbers(&driver) < numbers(crate::cuda::DRIVER_MIN) {
return Err(format!(
"{name} is on NVIDIA driver {driver}, below {}, the oldest TensorRT for RTX runs \
on — update the driver",
crate::cuda::DRIVER_MIN
));
}
let supported = matches!((major, minor), (7, 5) | (8, 6) | (8, 9)) || major >= 12;
if supported {
return Ok(name);
}
let why = if (major, minor) < (7, 5) {
"is older than Turing, the oldest generation TensorRT for RTX supports".to_string()
} else {
"is a data-centre or embedded part, which TensorRT for RTX does not support".to_string()
};
Err(format!(
"{name} (compute capability {major}.{minor}) {why}; {}",
fallback()
))
}
fn fallback() -> &'static str {
if cfg!(windows) {
"the GPU lane runs it through WebGPU"
} else {
"the CUDA lane runs it"
}
}
fn numbers(version: &str) -> Vec<u64> {
version
.split('.')
.map(|part| part.parse().unwrap_or(0))
.collect()
}
#[cfg(any(target_os = "linux", windows))]
fn nvml() -> Option<Card> {
use std::ffi::{CStr, c_char, c_int, c_uint, c_void};
type Init = unsafe extern "C" fn() -> c_int;
type Handle = unsafe extern "C" fn(c_uint, *mut *mut c_void) -> c_int;
type Name = unsafe extern "C" fn(*mut c_void, *mut c_char, c_uint) -> c_int;
type Capability = unsafe extern "C" fn(*mut c_void, *mut c_int, *mut c_int) -> c_int;
type Driver = unsafe extern "C" fn(*mut c_char, c_uint) -> c_int;
type Shutdown = unsafe extern "C" fn() -> c_int;
fn text(buffer: &[u8]) -> String {
CStr::from_bytes_until_nul(buffer)
.map(|c| c.to_string_lossy().trim().to_string())
.unwrap_or_default()
}
let lib = native::Library::open(if cfg!(windows) {
c"nvml.dll"
} else {
c"libnvidia-ml.so.1"
})?;
unsafe {
let init: Init = lib.sym(c"nvmlInit_v2")?;
let handle: Handle = lib.sym(c"nvmlDeviceGetHandleByIndex_v2")?;
let name: Name = lib.sym(c"nvmlDeviceGetName")?;
let capability: Capability = lib.sym(c"nvmlDeviceGetCudaComputeCapability")?;
let driver: Driver = lib.sym(c"nvmlSystemGetDriverVersion")?;
let shutdown: Shutdown = lib.sym(c"nvmlShutdown")?;
if init() != 0 {
return None;
}
let card = 'read: {
let mut device: *mut c_void = std::ptr::null_mut();
if handle(0, &mut device) != 0 {
break 'read None;
}
let mut name_buffer = [0u8; 96];
let (mut major, mut minor): (c_int, c_int) = (0, 0);
let mut version = [0u8; 80];
if name(device, name_buffer.as_mut_ptr().cast(), 96) != 0
|| capability(device, &mut major, &mut minor) != 0
|| driver(version.as_mut_ptr().cast(), 80) != 0
{
break 'read None;
}
Some(Card {
name: text(&name_buffer),
capability: (major, minor),
driver: text(&version),
})
};
shutdown();
card
}
}
#[cfg(not(any(target_os = "linux", windows)))]
fn nvml() -> Option<Card> {
None
}
pub struct Session {
session: ort::session::Session,
device: String,
}
impl Session {
pub fn open(pack: &Path) -> Result<Self> {
if let Some(why) = crate::accel::unavailable_here("trt") {
bail!("unavailable — {why}");
}
let card = judge(nvml()).map_err(|why| anyhow::anyhow!("unavailable — {why}"))?;
let model = fp16_model()?;
prepare(pack)?;
let env = ort::environment::Environment::current().context("starting ONNX Runtime")?;
let devices = plugin_devices(&env, "nv_tensorrt_rtx", &pack.join(plugin_file()))?;
let Some(device) = devices
.into_iter()
.find(|d| d.hardware_device().ty() == ort::memory::DeviceType::GPU)
else {
bail!("unavailable — the TensorRT for RTX plugin offered no GPU for {card}");
};
let ep = device.ep().map_err(|e| anyhow::anyhow!("{e}"))?.to_string();
let cache = pack.join("runtime-cache");
let mut options = Vec::new();
if std::fs::create_dir_all(&cache).is_ok() {
options.push((
format!("{ep}.nv_runtime_cache_path"),
crate::plain(&cache.display().to_string()),
));
}
let session = ort::session::Session::builder()
.map_err(|e| anyhow::anyhow!("{e}"))?
.with_devices([device], Some(&options))
.map_err(|e| anyhow::anyhow!("{e}"))?
.commit_from_file(&model)
.map_err(|e| anyhow::anyhow!("loading the fp16 model with TensorRT for RTX: {e}"))?;
Ok(Self {
session,
device: format!("{card} (TensorRT for RTX)"),
})
}
pub fn device(&self) -> String {
self.device.clone()
}
pub fn variant(&self) -> &'static str {
"fp16-trt"
}
pub fn embed(&mut self, batch: &[&[u32]]) -> Result<Vec<Vec<f32>>> {
crate::session::run(&mut self.session, batch)
.map_err(|e| anyhow::anyhow!("on TensorRT for RTX: {e:#}"))
}
}
#[cfg(target_os = "linux")]
fn prepare(pack: &Path) -> Result<()> {
const LINKS: &[(&str, &[&str])] = &[
("libcudart.so.12.9.79", &["libcudart.so.12"]),
(
"libtensorrt_rtx.so.1.6.1",
&["libtensorrt_rtx.so.1", "libtensorrt_rtx.so"],
),
(
"libtensorrt_onnxparser_rtx.so.1.6.1",
&[
"libtensorrt_onnxparser_rtx.so.1",
"libtensorrt_onnxparser_rtx.so",
],
),
];
for (real, aliases) in LINKS {
for alias in *aliases {
let link = pack.join(alias);
if std::fs::symlink_metadata(&link).is_err() {
let _ = std::os::unix::fs::symlink(real, &link);
}
}
}
for (real, _) in LINKS {
native::preload(&pack.join(real))?;
}
Ok(())
}
#[cfg(windows)]
fn prepare(pack: &Path) -> Result<()> {
native::search_beside(pack)
}
#[cfg(not(any(target_os = "linux", windows)))]
fn prepare(_pack: &Path) -> Result<()> {
bail!("unavailable — the TensorRT for RTX lane is built for Windows and Linux on x86_64")
}
pub(crate) fn fp16_model() -> Result<PathBuf> {
let cache = crate::model_cache_dir()?;
let model =
crate::accel::component_dir(&cache, &format!("webgpu-{}", crate::gpu::WEBGPU_VERSION))
.join("model_fp16.onnx");
if !model.exists() {
bail!(
"the fp16 model is not in {} — turning the lane on again fetches it",
crate::plain(&model.display().to_string())
);
}
Ok(model)
}
pub(crate) fn plugin_devices<'e>(
env: &'e std::sync::Arc<ort::environment::Environment>,
name: &str,
library: &Path,
) -> Result<Vec<ort::device::Device<'e>>> {
if !library.exists() {
bail!(
"{} is not in the pack — turning the lane off and on again fetches it afresh",
crate::plain(&library.display().to_string())
);
}
let before: Vec<String> = env
.devices()
.filter_map(|d| d.ep().ok().map(str::to_string))
.collect();
let registered = env
.register_ep_library(name, library)
.with_context(|| format!("registering {}", library.display()))?;
std::mem::forget(registered);
Ok(env
.devices()
.filter(|d| d.ep().is_ok_and(|ep| !before.iter().any(|b| b == ep)))
.collect())
}
pub(crate) mod native {
#[cfg(target_os = "linux")]
use anyhow::bail;
#[cfg(any(target_os = "linux", windows))]
use std::ffi::{CStr, c_void};
#[cfg(any(target_os = "linux", windows))]
pub struct Library(*mut c_void);
#[cfg(windows)]
#[link(name = "kernel32")]
unsafe extern "system" {
fn LoadLibraryA(name: *const std::ffi::c_char) -> *mut c_void;
fn GetProcAddress(module: *mut c_void, name: *const std::ffi::c_char) -> *mut c_void;
fn FreeLibrary(module: *mut c_void) -> i32;
fn SetDllDirectoryW(path: *const u16) -> i32;
}
#[cfg(any(target_os = "linux", windows))]
impl Library {
pub fn open(name: &CStr) -> Option<Self> {
#[cfg(target_os = "linux")]
let handle = unsafe { libc::dlopen(name.as_ptr(), libc::RTLD_NOW | libc::RTLD_LOCAL) };
#[cfg(windows)]
let handle = unsafe { LoadLibraryA(name.as_ptr()) };
if handle.is_null() {
None
} else {
Some(Self(handle))
}
}
pub unsafe fn sym<F: Copy>(&self, name: &CStr) -> Option<F> {
assert_eq!(size_of::<F>(), size_of::<*mut c_void>());
unsafe {
#[cfg(target_os = "linux")]
let found = libc::dlsym(self.0, name.as_ptr());
#[cfg(windows)]
let found = GetProcAddress(self.0, name.as_ptr());
(!found.is_null()).then(|| std::mem::transmute_copy(&found))
}
}
}
#[cfg(any(target_os = "linux", windows))]
impl Drop for Library {
fn drop(&mut self) {
unsafe {
#[cfg(target_os = "linux")]
libc::dlclose(self.0);
#[cfg(windows)]
FreeLibrary(self.0);
}
}
}
#[cfg(target_os = "linux")]
pub fn preload(path: &std::path::Path) -> anyhow::Result<()> {
use std::os::unix::ffi::OsStrExt;
let c_path = std::ffi::CString::new(path.as_os_str().as_bytes())?;
let why = unsafe {
let handle = libc::dlopen(c_path.as_ptr(), libc::RTLD_NOW | libc::RTLD_GLOBAL);
if !handle.is_null() {
return Ok(());
}
let text = libc::dlerror();
if text.is_null() {
"no reason given".to_string()
} else {
CStr::from_ptr(text).to_string_lossy().into_owned()
}
};
bail!(
"loading {}: {why} — turning the lane off and on again fetches the pack afresh",
path.display()
)
}
#[cfg(windows)]
pub fn search_beside(dir: &std::path::Path) -> anyhow::Result<()> {
let plain = crate::plain(&dir.display().to_string());
let wide: Vec<u16> = plain.encode_utf16().chain(Some(0)).collect();
if unsafe { SetDllDirectoryW(wide.as_ptr()) } == 0 {
anyhow::bail!("could not add {plain} to the DLL search path");
}
Ok(())
}
}