diff --git a/Cargo.lock b/Cargo.lock index 3e8ac3d..01a39f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,7 +76,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -87,7 +87,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -302,6 +302,20 @@ dependencies = [ "piper", ] +[[package]] +name = "bread-onnx" +version = "0.2.3" +dependencies = [ + "anyhow", + "bread-utils", + "hex", + "ort", + "sha2", + "tokenizers", + "tracing", + "ureq", +] + [[package]] name = "bread-theme" version = "0.2.3" @@ -313,6 +327,15 @@ dependencies = [ "serde_json", ] +[[package]] +name = "bread-utils" +version = "0.2.3" +dependencies = [ + "dirs 5.0.1", + "serde", + "serde_json", +] + [[package]] name = "breadman" version = "0.4.1" @@ -335,6 +358,7 @@ name = "breadpad" version = "0.4.1" dependencies = [ "anyhow", + "bread-utils", "breadpad-shared", "chrono", "dirs 5.0.1", @@ -354,7 +378,9 @@ name = "breadpad-shared" version = "0.4.1" dependencies = [ "anyhow", + "bread-onnx", "bread-theme", + "bread-utils", "chrono", "dirs 5.0.1", "ical", @@ -613,6 +639,18 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "console" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fe5f465a4f6fee88fad41b85d990f84c835335e85b5d9e6e63e0d06d28cba7c" +dependencies = [ + "encode_unicode", + "libc", + "unicode-width", + "windows-sys 0.61.2", +] + [[package]] name = "convert_case" version = "0.10.0" @@ -704,6 +742,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "daachorse" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f55d7153ba3b507595872a3874803f07a8a81d1e888abed8e5db7da0597d6e2" + [[package]] name = "darling" version = "0.20.11" @@ -851,7 +895,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -926,7 +970,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -934,6 +978,9 @@ name = "esaxx-rs" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d817e038c30374a4bcb22f94d0a8a0e216958d4c3dcde369b1439fec4bdda6e6" +dependencies = [ + "cc", +] [[package]] name = "event-listener" @@ -958,9 +1005,9 @@ dependencies = [ [[package]] name = "fancy-regex" -version = "0.14.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" +checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8" dependencies = [ "bit-set", "regex-automata", @@ -1237,7 +1284,7 @@ dependencies = [ "gobject-sys", "libc", "system-deps", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -1489,7 +1536,7 @@ checksum = "629d8f3bbeda9d148036d6b0de0a3ab947abd08ce90626327fc3547a49d59d97" dependencies = [ "dirs 6.0.0", "http", - "indicatif", + "indicatif 0.17.11", "libc", "log", "rand 0.9.4", @@ -1793,13 +1840,26 @@ version = "0.17.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" dependencies = [ - "console", + "console 0.15.11", "number_prefix", "portable-atomic", "unicode-width", "web-time", ] +[[package]] +name = "indicatif" +version = "0.18.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9433806cd6b4ec1aba79c021c7e4c58fb4c3b9977c085062e611ac929998fb0c" +dependencies = [ + "console 0.16.4", + "portable-atomic", + "unicode-width", + "unit-prefix", + "web-time", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -2079,7 +2139,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2127,6 +2187,28 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "onig" +version = "6.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc3cbf698f9438986c11a880c90a6d04b9de27575afd28bbf45b154b6c709e2" +dependencies = [ + "bitflags", + "libc", + "once_cell", + "onig_sys", +] + +[[package]] +name = "onig_sys" +version = "69.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e68317604e77e53b85896388e1a803c1d21b74c899ec9e5e1112db90735edd7" +dependencies = [ + "cc", + "pkg-config", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -2701,7 +2783,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2859,6 +2941,17 @@ dependencies = [ "digest", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -2915,7 +3008,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3025,7 +3118,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -3104,23 +3197,25 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokenizers" -version = "0.21.4" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a620b996116a59e184c2fa2dfd8251ea34a36d0a514758c6f966386bd2e03476" +checksum = "44e5bea67576e04b6ff8564c5d9e09c2ef0cf476502245f2f120e497769d3112" dependencies = [ "ahash", - "aho-corasick", "compact_str", + "daachorse", "dary_heap", "derive_builder", "esaxx-rs", "fancy-regex", "getrandom 0.3.4", "hf-hub", + "indicatif 0.18.6", "itertools", "log", "macro_rules_attribute", "monostate", + "onig", "paste", "rand 0.9.4", "rayon", @@ -3393,7 +3488,7 @@ checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" dependencies = [ "memoffset", "tempfile", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3435,6 +3530,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "unit-prefix" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" + [[package]] name = "untrusted" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index b287686..53daebd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,11 @@ tokio = { version = "1", features = ["full"] } zbus = { version = "4", default-features = false, features = ["tokio"] } ort = { version = "2.0.0-rc.12", default-features = false, features = ["std", "ndarray", "tracing", "api-24", "rocm", "load-dynamic"] } ndarray = "0.16" -tokenizers = { version = "0.21", default-features = false, features = ["http", "fancy-regex"] } +# Bumped 0.21 -> 0.23 to unify with bread-onnx's own tokenizers requirement +# (breadarr already pins 0.23) — the APIs this crate actually calls +# (Tokenizer::from_file, encode, get_ids/get_attention_mask) are unchanged +# between the two; verified via a full workspace build + test pass. +tokenizers = { version = "0.23", default-features = false, features = ["http", "fancy-regex"] } gtk4 = { version = "0.11", features = ["v4_12"] } gtk4-layer-shell = "0.8" hyprland = "0.4.0-beta.3" diff --git a/breadpad-shared/Cargo.toml b/breadpad-shared/Cargo.toml index 7af8279..cc2e2f5 100644 --- a/breadpad-shared/Cargo.toml +++ b/breadpad-shared/Cargo.toml @@ -26,6 +26,9 @@ regex.workspace = true ureq.workspace = true reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json"] } ical = "0.11" +# TODO(owner): switch to tag-pinned git dependency once bread-onnx/bread-utils are merged and tagged, matching the bread-theme pattern +bread-onnx = { path = "../../bread-ecosystem-fix-worktree/bread-onnx" } +bread-utils = { path = "../../bread-ecosystem-fix-worktree/bread-utils" } [dev-dependencies] tempfile = "3" diff --git a/breadpad-shared/src/classifier.rs b/breadpad-shared/src/classifier.rs index e752a31..18efb83 100644 --- a/breadpad-shared/src/classifier.rs +++ b/breadpad-shared/src/classifier.rs @@ -16,7 +16,7 @@ pub enum ExecutionProvider { impl ExecutionProvider { pub fn as_str(&self) -> &str { match self { - ExecutionProvider::Gpu => "ROCm (iGPU)", + ExecutionProvider::Gpu => "MIGraphX (iGPU)", ExecutionProvider::Cpu => "CPU", } } @@ -32,10 +32,13 @@ pub struct Classifier { } fn model_dir() -> PathBuf { - dirs::data_local_dir() - .unwrap_or_else(|| PathBuf::from("~/.local/share")) - .join("breadpad") - .join("model") + // Was `dirs::data_local_dir().unwrap_or_else(|| PathBuf::from("~/.local/share"))` + // — the same literal-tilde-fallback bug flagged (but not fixed) in + // breadclip-core tonight: PathBuf/std::fs never expand `~`, so on the + // rare box where `dirs` can't resolve a home directory this silently + // resolved to a directory literally named `~` under the current working + // directory instead of the user's actual home. + bread_utils::xdg::data_dir("breadpad").join("model") } impl Classifier { @@ -246,21 +249,46 @@ fn softmax_single(logits: &[f32], idx: usize) -> f32 { fn try_load_session( path: &std::path::Path, ) -> (Option, ExecutionProvider) { - // Try ROCm (iGPU) first, fall back to CPU. - let rocm_available = { + // AMD iGPU via MIGraphX, falling back to CPU. This used to request the + // classic `ort::ep::ROCm` (ROCMExecutionProvider) first — per this + // machine's own breadsearch-gpu-backends operator notes, that EP + // silently no-ops back to CPU on this class of system (distro ROCm + // onnxruntime builds, e.g. Arch's onnxruntime-rocm, are commonly + // compiled with `--use_migraphx`, not `--use_rocm`), so "ROCm (iGPU)" + // could report as active in this struct's own `active_provider` while + // every inference actually ran on CPU. See bread_onnx::provider's doc + // comment for the full history — breadsearch's own embed.rs already + // got this right. + // + // The `is_available()` gate (kept from the original implementation) + // means `active_provider` only ever claims Gpu when we actually + // attempted the GPU build — bread_onnx::build_session's loud EP- + // selection logging (visible once tracing_subscriber is initialized, + // which this crate's own main.rs already does) is what catches the + // *silent per-node fallback* class of bug this rewrite exists to fix, + // rather than papering over it by unconditionally reporting Gpu. + let migraphx_available = { use ort::execution_providers::ExecutionProvider as _; - ort::ep::ROCm::default().is_available().unwrap_or(false) + ort::ep::MIGraphX::default().is_available().unwrap_or(false) }; - if rocm_available { - match build_onnx_session(path, ort::ep::ROCm::default().build()) { + if migraphx_available { + match bread_onnx::build_session( + path, + ort::session::builder::GraphOptimizationLevel::Level3, + &[bread_onnx::Provider::MiGraphX { device_id: 0 }], + ) { Ok(s) => { - tracing::info!("ONNX session loaded (ROCm iGPU)"); + tracing::info!("ONNX session loaded (MIGraphX iGPU)"); return (Some(s), ExecutionProvider::Gpu); } - Err(e) => tracing::debug!("ROCm EP unavailable: {}; trying CPU", e), + Err(e) => tracing::debug!("MIGraphX EP unavailable: {}; trying CPU", e), } } - match build_onnx_session(path, ort::ep::CPU::default().build()) { + match bread_onnx::build_session( + path, + ort::session::builder::GraphOptimizationLevel::Level3, + &[bread_onnx::Provider::Cpu], + ) { Ok(s) => { tracing::info!("ONNX session loaded (CPU)"); (Some(s), ExecutionProvider::Cpu) @@ -271,14 +299,3 @@ fn try_load_session( } } } - -fn build_onnx_session( - path: &std::path::Path, - ep: ort::ep::ExecutionProviderDispatch, -) -> anyhow::Result { - let mut builder = ort::session::Session::builder() - .map_err(|e| anyhow::anyhow!("builder: {}", e))? - .with_execution_providers([ep]) - .map_err(|e| anyhow::anyhow!("ep: {}", e))?; - builder.commit_from_file(path).map_err(|e| anyhow::anyhow!("load: {}", e)) -} diff --git a/breadpad-shared/src/config.rs b/breadpad-shared/src/config.rs index 2eeb3d5..12702ec 100644 --- a/breadpad-shared/src/config.rs +++ b/breadpad-shared/src/config.rs @@ -212,15 +212,13 @@ impl Config { } pub fn config_path() -> PathBuf { - dirs::config_dir() - .unwrap_or_else(|| PathBuf::from("~/.config")) - .join("breadpad") - .join("breadpad.toml") + // Was `dirs::config_dir().unwrap_or_else(|| PathBuf::from("~/.config"))` + // — same literal-tilde-fallback bug as `classifier.rs::model_dir` (see + // its doc comment) and breadclip-core's `data_dir`; PathBuf/std::fs + // never expand `~`. + bread_utils::xdg::config_dir("breadpad").join("breadpad.toml") } pub fn style_css_path() -> PathBuf { - dirs::config_dir() - .unwrap_or_else(|| PathBuf::from("~/.config")) - .join("breadpad") - .join("style.css") + bread_utils::xdg::config_dir("breadpad").join("style.css") } diff --git a/breadpad/Cargo.toml b/breadpad/Cargo.toml index c835bb9..4bf9b0b 100644 --- a/breadpad/Cargo.toml +++ b/breadpad/Cargo.toml @@ -23,3 +23,5 @@ gtk4-layer-shell.workspace = true hyprland.workspace = true dirs.workspace = true tokio.workspace = true +# TODO(owner): switch to tag-pinned git dependency once bread-utils is merged and tagged, matching the bread-theme pattern +bread-utils = { path = "../../bread-ecosystem-fix-worktree/bread-utils" } diff --git a/breadpad/src/main.rs b/breadpad/src/main.rs index e33087c..3d946f2 100644 --- a/breadpad/src/main.rs +++ b/breadpad/src/main.rs @@ -536,13 +536,14 @@ fn run_popup(preset_type: Option, no_classify: bool, cfg: Config) -> Res } fn get_active_workspace() -> Option { - // Use hyprctl via CLI since the async API would require a runtime here - let out = std::process::Command::new("hyprctl") - .args(["activeworkspace", "-j"]) - .output() - .ok()?; - let val: serde_json::Value = serde_json::from_slice(&out.stdout).ok()?; - val.get("id").and_then(|v| v.as_i64()).map(|id| id.to_string()) + // Was a `Command::new("hyprctl").output()` call with no timeout (the + // `hyprland` crate's async API would require a runtime here, which this + // call site doesn't have) — bread_utils::hypr's socket1 client is + // synchronous and needs neither a subprocess nor a runtime. + bread_utils::hypr::request_json("j/activeworkspace")? + .get("id") + .and_then(|v| v.as_i64()) + .map(|id| id.to_string()) } fn build_window(