The generic runtime is opened only when the QNN build does not fit, and the fit rests on ro.soc.manufacturer. A property the app cannot read, or an Android older than 12 that has none, read as "not Qualcomm" would put a Qualcomm device on the generic rung and off its Hexagon — what 0.22.1 had just fixed. Only a device that names another vendor is now not Qualcomm's; the tablet reports QTI.
177 lines
6.7 KiB
Rust
177 lines
6.7 KiB
Rust
//! Which GPUs this device has, as far as choosing a runtime needs to know
|
|
//! (docs/dev/inference.md §3.2).
|
|
//!
|
|
//! A runtime carries one vendor's providers — Intel's build has OpenVINO,
|
|
//! the `onnxruntime-gpu` wheel CUDA and TensorRT, a ROCm build MIGraphX,
|
|
//! Microsoft's WebGPU build the generic rung — and only one runtime loads
|
|
//! per process. These checks are what lets `api` load the one that fits
|
|
//! when a device has several installed. They read files, never a driver:
|
|
//! a wrong answer costs a slower rung, which the probe still measures, and
|
|
//! a driver call at start-up could cost the launch.
|
|
|
|
/// What a runtime's providers are scored against.
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
pub struct Gpus {
|
|
pub nvidia: bool,
|
|
/// An AMD GPU with the ROCm kernel interface, which MIGraphX needs.
|
|
pub amd_rocm: bool,
|
|
pub intel: bool,
|
|
pub qualcomm: bool,
|
|
}
|
|
|
|
/// The score of a runtime whose vendor rung matches the device's GPU.
|
|
/// Nothing beats it, so the search stops there.
|
|
pub const PERFECT: u32 = 3;
|
|
|
|
impl Gpus {
|
|
/// How well a runtime offering `providers` fits this device. The vendor
|
|
/// rungs score above OpenVINO because a machine with an Intel iGPU and
|
|
/// an NVIDIA or AMD card wants the card; the generic rung scores above
|
|
/// a CPU-only build because it carries the same CPU provider and might
|
|
/// beat it.
|
|
pub fn score(&self, providers: &[String]) -> u32 {
|
|
providers
|
|
.iter()
|
|
.map(|p| match p.as_str() {
|
|
"TensorrtExecutionProvider" | "CUDAExecutionProvider" if self.nvidia => PERFECT,
|
|
"MIGraphXExecutionProvider" if self.amd_rocm => PERFECT,
|
|
"QNNExecutionProvider" if self.qualcomm => PERFECT,
|
|
"CoreMLExecutionProvider" => PERFECT,
|
|
"OpenVINOExecutionProvider" if self.intel => 2,
|
|
"WebGpuExecutionProvider" => 1,
|
|
_ => 0,
|
|
})
|
|
.max()
|
|
.unwrap_or(0)
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
pub fn detect() -> Gpus {
|
|
use std::path::Path;
|
|
// Every DRM card's PCI vendor: an Intel iGPU is `0x8086` whether or
|
|
// not its compute driver is installed, which the probe finds out.
|
|
let vendors: Vec<String> = std::fs::read_dir("/sys/class/drm")
|
|
.into_iter()
|
|
.flatten()
|
|
.filter_map(|e| e.ok())
|
|
.filter(|e| {
|
|
let name = e.file_name();
|
|
let name = name.to_string_lossy();
|
|
name.starts_with("card") && !name.contains('-')
|
|
})
|
|
.filter_map(|e| std::fs::read_to_string(e.path().join("device/vendor")).ok())
|
|
.map(|v| v.trim().to_string())
|
|
.collect();
|
|
Gpus {
|
|
nvidia: Path::new("/proc/driver/nvidia/version").exists(),
|
|
amd_rocm: Path::new("/dev/kfd").exists(),
|
|
intel: vendors.iter().any(|v| v == "0x8086"),
|
|
qualcomm: false,
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
pub fn detect() -> Gpus {
|
|
use std::path::PathBuf;
|
|
let root = std::env::var_os("SystemRoot")
|
|
.map(PathBuf::from)
|
|
.unwrap_or_else(|| PathBuf::from(r"C:\Windows"));
|
|
let system32 = root.join("System32");
|
|
// Intel's DCH graphics driver, integrated and Arc alike, installs
|
|
// from `iigd_dch.inf`; its package directory is the evidence.
|
|
let intel = std::fs::read_dir(system32.join(r"DriverStore\FileRepository"))
|
|
.into_iter()
|
|
.flatten()
|
|
.filter_map(|e| e.ok())
|
|
.any(|e| e.file_name().to_string_lossy().starts_with("iigd_dch"));
|
|
Gpus {
|
|
nvidia: system32.join("nvcuda.dll").exists(),
|
|
amd_rocm: false,
|
|
intel,
|
|
qualcomm: false,
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "android")]
|
|
pub fn detect() -> Gpus {
|
|
// Fail-safe: only a device that names another vendor is not Qualcomm.
|
|
// `ro.soc.manufacturer` exists from Android 12, and a property or file
|
|
// the app cannot read reads as nothing; nothing keeps the QNN build
|
|
// first, as 0.22 had it, where a Qualcomm device mistaken for another
|
|
// would trade its NPU for the generic rung. Qualcomm's FastRPC library,
|
|
// which the Hexagon path loads anyway, overrules a name.
|
|
let soc = crate::probe::system_property("ro.soc.manufacturer");
|
|
let fastrpc = [
|
|
"/vendor/lib64/libcdsprpc.so",
|
|
"/system/vendor/lib64/libcdsprpc.so",
|
|
]
|
|
.iter()
|
|
.any(|p| std::path::Path::new(p).exists());
|
|
Gpus {
|
|
qualcomm: qualcomm_soc(&soc) || fastrpc,
|
|
..Gpus::default()
|
|
}
|
|
}
|
|
|
|
/// Whether `ro.soc.manufacturer` leaves the device Qualcomm's: it says so,
|
|
/// or it says nothing.
|
|
#[cfg(any(target_os = "android", test))]
|
|
fn qualcomm_soc(manufacturer: &str) -> bool {
|
|
let m = manufacturer.trim();
|
|
m.is_empty() || m.eq_ignore_ascii_case("QTI") || m.eq_ignore_ascii_case("Qualcomm")
|
|
}
|
|
|
|
#[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "android")))]
|
|
pub fn detect() -> Gpus {
|
|
Gpus::default()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn offers(p: &[&str]) -> Vec<String> {
|
|
p.iter().map(|s| s.to_string()).collect()
|
|
}
|
|
|
|
#[test]
|
|
fn only_a_named_other_vendor_is_not_qualcomm() {
|
|
assert!(qualcomm_soc("QTI"));
|
|
assert!(qualcomm_soc("Qualcomm"));
|
|
// Unreadable, or older than Android 12: the QNN build stays first.
|
|
assert!(qualcomm_soc(""));
|
|
assert!(!qualcomm_soc("Mediatek"));
|
|
assert!(!qualcomm_soc("Google"));
|
|
assert!(!qualcomm_soc("Samsung"));
|
|
}
|
|
|
|
#[test]
|
|
fn the_card_beats_the_integrated_gpu_and_both_beat_the_generic_rung() {
|
|
let cpu = offers(&["CPUExecutionProvider"]);
|
|
let nvidia = offers(&[
|
|
"TensorrtExecutionProvider",
|
|
"CUDAExecutionProvider",
|
|
"CPUExecutionProvider",
|
|
]);
|
|
let intel = offers(&["OpenVINOExecutionProvider", "CPUExecutionProvider"]);
|
|
let webgpu = offers(&["WebGpuExecutionProvider", "CPUExecutionProvider"]);
|
|
let laptop = Gpus {
|
|
nvidia: true,
|
|
intel: true,
|
|
..Gpus::default()
|
|
};
|
|
assert!(laptop.score(&nvidia) > laptop.score(&intel));
|
|
assert!(laptop.score(&intel) > laptop.score(&webgpu));
|
|
assert!(laptop.score(&webgpu) > laptop.score(&cpu));
|
|
// No Intel GPU: Intel's build is worth no more than a CPU build to
|
|
// this device, and the generic rung is worth more.
|
|
let amd_on_windows = Gpus::default();
|
|
assert_eq!(amd_on_windows.score(&intel), amd_on_windows.score(&cpu));
|
|
assert!(amd_on_windows.score(&webgpu) > amd_on_windows.score(&intel));
|
|
// A ROCm build on a machine without ROCm is a CPU build.
|
|
let rocm = offers(&["MIGraphXExecutionProvider", "CPUExecutionProvider"]);
|
|
assert_eq!(amd_on_windows.score(&rocm), 0);
|
|
}
|
|
}
|