From 8a90d888d587fabf45f8959c66266be1e5d1927c Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sun, 20 Sep 2026 19:39:05 +0200 Subject: [PATCH] Probe and compile for a package's models, not only the user's `inference::init` listed the models from the user's shared directory alone, while the app loads them from there or from the package's `/usr/share/darkroom/models`. On a fresh package install the probe found "no model to probe with", stayed on the CPU, and compiled nothing. Both now resolve each file with the same search, `library::shared_model`. The PKGBUILD names the ONNX Runtime packages as optional dependencies, since the app loads one from /usr/lib if present. Co-Authored-By: Claude Opus 5 (1M context) --- packaging/PKGBUILD | 7 ++++++- ui/dr-ui/src/inference.rs | 44 +++++++++++++++++++++------------------ ui/dr-ui/src/library.rs | 17 +++++++++++---- 3 files changed, 43 insertions(+), 25 deletions(-) diff --git a/packaging/PKGBUILD b/packaging/PKGBUILD index 8f9bfe0..9a9c605 100644 --- a/packaging/PKGBUILD +++ b/packaging/PKGBUILD @@ -16,8 +16,13 @@ license=('GPL-3.0-or-later') # Nextcloud credentials (FR-NC-2) — gnome-keyring or kwallet both provide it. depends=('vulkan-icd-loader' 'fontconfig' 'libxkbcommon') makedepends=('cargo' 'git') +# ONNX Runtime is loaded from /usr/lib at launch if a package put it there +# (docs/inference.md §3): the CPU build is 8–10× the built-in tract, the +# ROCm build adds the MIGraphX rung on an AMD GPU. Neither is required. optdepends=('gnome-keyring: store Nextcloud credentials' - 'kwallet: store Nextcloud credentials') + 'kwallet: store Nextcloud credentials' + 'onnxruntime-cpu: run the neural models on every core' + 'onnxruntime-rocm: run the neural models on an AMD GPU') options=('!lto') # the workspace sets its own LTO in Cargo.toml _repo="$(cd "${startdir}/.." && pwd)" diff --git a/ui/dr-ui/src/inference.rs b/ui/dr-ui/src/inference.rs index 38b3ae3..bae0762 100644 --- a/ui/dr-ui/src/inference.rs +++ b/ui/dr-ui/src/inference.rs @@ -19,23 +19,26 @@ use dr_types::FaceDetector; /// fingerprints the model files and a probe before they land would be a /// probe of nothing. pub fn init(runtime_dirs: Vec) { - let dir = crate::library::shared_face_models_dir(); - let mut models: Vec<(Role, PathBuf)> = FaceDetector::ALL + // Each file where the app will actually load it from — the user's + // shared directory, else the package's — so a fresh install with models + // only under `/usr/share` probes and compiles for them rather than + // finding nothing and settling on the CPU. + let mut wanted: Vec<(Role, &str)> = FaceDetector::ALL .iter() - .map(|d| (Role::Detector, dir.join(d.file_name()))) + .map(|d| (Role::Detector, d.file_name())) + .collect(); + wanted.extend([ + (Role::Embedder, "arcface_mbf_b1.onnx"), + (Role::Scene, "yolo26s-sem-ade20k.onnx"), + (Role::Landmarks, crate::library::LANDMARK_MODEL), + (Role::EyeClassifier, crate::library::EYE_MODEL), + (Role::EyeClassifier, crate::library::SUNGLASSES_MODEL), + (Role::Inpainter, crate::library::INPAINT_MODEL), + ]); + let models: Vec<(Role, PathBuf)> = wanted + .into_iter() + .filter_map(|(role, name)| Some((role, crate::library::shared_model(name)?))) .collect(); - models.push((Role::Embedder, dir.join("arcface_mbf_b1.onnx"))); - models.push((Role::Scene, dir.join("yolo26s-sem-ade20k.onnx"))); - models.push((Role::Landmarks, dir.join(crate::library::LANDMARK_MODEL))); - models.push((Role::EyeClassifier, dir.join(crate::library::EYE_MODEL))); - models.push(( - Role::EyeClassifier, - dir.join(crate::library::SUNGLASSES_MODEL), - )); - if let Some(p) = crate::library::inpaint_model() { - models.push((Role::Inpainter, p)); - } - models.retain(|(_, p)| p.is_file()); dr_inference_engine::init(dr_inference_engine::Config { runtime_dirs, @@ -75,12 +78,13 @@ pub fn user_runtime_dir() -> PathBuf { /// Which form the current backend loads `detector` in, given the files on /// this device — the fact `faces.model_id` has to carry (§7). /// -/// Reads the shared directory only. An account-private model directory can -/// override the file `library::face_models` loads, but not which form the -/// backend wants, and the int8 sibling is something a packager ships, not -/// something a user drops in. +/// Reads the shared and system directories only. An account-private model +/// directory can override the file `library::face_models` loads, but not +/// which form the backend wants, and the int8 sibling is something a +/// packager ships, not something a user drops in. pub fn detector_form(detector: FaceDetector) -> Form { - let canonical = crate::library::shared_face_models_dir().join(detector.file_name()); + let canonical = crate::library::shared_model(detector.file_name()) + .unwrap_or_else(|| crate::library::shared_face_models_dir().join(detector.file_name())); dr_inference_engine::resolve_model(Role::Detector, &canonical).1 } diff --git a/ui/dr-ui/src/library.rs b/ui/dr-ui/src/library.rs index 2e31075..7a3b539 100644 --- a/ui/dr-ui/src/library.rs +++ b/ui/dr-ui/src/library.rs @@ -4463,6 +4463,18 @@ fn system_face_models_dirs() -> Vec { .collect() } +/// The file `name` in the shared user models directory, else in the first +/// system directory that has it — the search every account-independent +/// model lookup makes, and the one the inference engine is told about, so +/// that a package's models under `/usr/share` are probed and compiled for +/// exactly as a user's own would be. +pub fn shared_model(name: &str) -> Option { + std::iter::once(shared_face_models_dir()) + .chain(system_face_models_dirs()) + .map(|d| d.join(name)) + .find(|p| p.is_file()) +} + /// TRACES: FR-MRG-4 /// The panorama border filler, as shipped in `models/inpaint/`. pub const INPAINT_MODEL: &str = "migan-512.onnx"; @@ -4473,10 +4485,7 @@ pub const INPAINT_MODEL: &str = "migan-512.onnx"; /// the per-account step, because a fill is not identity-bearing and no /// library has a reason to pin its own. pub fn inpaint_model() -> Option { - std::iter::once(shared_face_models_dir()) - .chain(system_face_models_dirs()) - .map(|d| d.join(INPAINT_MODEL)) - .find(|p| p.is_file()) + shared_model(INPAINT_MODEL) } /// One grid cell's data, read from the catalog.