ROCm silently fell back to CPU: the code targeted ONNX Runtime's classic ROCMExecutionProvider, but distro ROCm-enabled ONNX Runtime builds (e.g. Arch's onnxruntime-rocm) are commonly compiled with --use_migraphx instead, and registration failures were invisible since breadmill never installed a tracing subscriber. Switches the rocm feature to target MIGraphX, adds a default tracing subscriber so EP registration success/failure is always visible, and fixes a real crash where MIGraphX's output sequence padding could index the attention mask out of bounds during mean-pooling. Also adds a CUDA backend (--cuda / backend = "cuda") mirroring the same ort execution-provider pattern, for NVIDIA hardware. Version bump: 0.1.0 -> 0.2.0.
8.2 KiB
breadsearch + breadmill — semantic system-wide search for BOS
Context
BOS/bread has no content search — only breadbox's app launcher (exact/fuzzy over .desktop files). The goal is a semantic "find anything by meaning" engine: a flagship, differentiating BOS feature that's also the right workload for the AMD XDNA NPU (small encoder model, compute-bound single forward pass, always-on background embedding — none of the bandwidth-bound problems that make LLMs a bad NPU fit).
Two components, mirroring the breadpad/breadman split and the breadbox/breadbox-sync precedent (GUI + background helper + shared lib in one repo):
- breadmill — always-on daemon: walks files → extracts text → chunks → embeds → vector index; answers queries over a Unix socket. ("mill grain into flour.")
- breadsearch — standalone GTK4 GUI, forked from breadbox's UI, that queries breadmill and shows ranked hits. ("sift the flour.")
breadbox stays a pure app launcher, unchanged.
Decisions (confirmed with user)
- Index scope (v1): curated roots —
~/Documents,~/Projects(notes/docs, not code yet),~/.config/breadpad. Extractmd, txt, org, pdf, odt, docx. Skip binaries/images/build dirs/.git. - Repo layout: one cargo workspace at
~/Projects/breadsearch/. - Embedding model:
nomic-embed-text-v1.5(768-dim ONNX, ~550MB). Requires task prefixes:search_document:for indexed chunks,search_query:for queries; mean-pool + L2-normalize. - Compute: CPU-first; NPU (XDNA via ONNX Runtime VitisAI EP) is a later backend swap, not a v1 dependency.
Workspace layout
~/Projects/breadsearch/
Cargo.toml # [workspace] members = breadsearch-shared, breadmill, breadsearch
bakery.toml # bread package manifest (binaries: breadsearch, breadmill)
config.example.toml # ~/.config/breadsearch/config.toml template
README.md
breadsearch-shared/ # lib: XDG paths, config, IPC types + socket client
breadmill/ # daemon bin
breadsearch/ # GUI bin (forked breadbox UI)
Component: breadsearch-shared (lib)
Model on breadbox/breadbox-shared/src/lib.rs (XDG helpers + serde/toml config).
- Paths:
config_dir()→~/.config/breadsearch;state_dir()→~/.local/state/breadsearch(index);cache_dir()→~/.cache/breadsearch(models);socket_path()→$XDG_RUNTIME_DIR/breadmill.sock. - Config (serde +
toml):[index] roots, extensions, max_file_mb;[search] limit, snippet_len;[model] name, dim. - IPC types (serde_json, newline-delimited JSON over the Unix socket):
- Request:
Query { query: String, limit: usize },Status,Reindex. - Response:
Hits(Vec<Hit>)whereHit { title, path, snippet, score };StatusInfo { indexed, pending, model_ready }.
- Request:
- Socket client helper used by the GUI (connect, send, read one response).
Component: breadmill (daemon)
Pipeline, isolated behind small traits so each stage is swappable:
- Walk —
ignorecrate (parallel, respects.gitignore) over configured roots; filter by extension + size. - Extract —
md/txt/org: read directly;pdf:pdf-extract;docx/odt: unzip + strip XML (zip+quick-xml), best-effort. - Chunk — ~512-token windows with overlap; keep byte offsets for snippets.
- Embed —
Embeddertrait. v1 impl:ort(ONNX Runtime 2.x, CPU EP) +tokenizers(HF) running nomic-embed-text-v1.5. Applysearch_document:/search_query:prefixes, mean-pool, normalize. - Store —
rusqlitefor metadata (path, mtime, content-hash, chunk text/offsets) keyed by rowid +usearch(HNSW, 768-dim, cosine) for vectors keyed by the same id. Both persisted understate_dir(). - Incremental — on start, diff roots against sqlite (mtime+hash): embed new/changed, drop deleted. Then live-watch with
notify(debounced) to re-embed on change. - Serve —
tokio(or std threads) Unix-socket listener:Query→ embed query → usearch top-k → join sqlite metadata →Hits. AlsoStatus/Reindex.
- Model fetch: first run downloads
model.onnx+tokenizer.jsonfrom HF intocache_dir()/models/(needs network once);breadmill --fetch-modelto pre-fetch. Log clearly if absent. - Lifecycle: systemd user service
breadmill.service(pattern frombreadbox-sync.service/ breadd),WantedBy=default.target.
Component: breadsearch (GUI) — fork of breadbox
Start from breadbox/breadbox/src/main.rs. Reuse verbatim: the gtk4-layer-shell overlay window (rename namespace/app-id to breadsearch / com.breadway.breadsearch), SearchEntry + ScrolledWindow + ListBox, ↑/↓/Enter/Esc handling, click-outside-to-close, PID-toggle (breadsearch.pid), and the theming path: bread_theme::gtk::apply_shared() + apply_app_css(|| build_css(&load_palette())) + user style.css. Pin bread-theme git tag v0.2.8, feature gtk (same as breadbox).
Swap:
- Result source: delete
load_sorted_entries/fuzzy_*/DesktopEntry. Onsearch.connect_changed, debounce ~150ms (glib::timeout_add_local) then query breadmill off the UI thread (std::thread+glib::MainContext::channel), clear theListBox, append a row perHit. - Row content: title (filename/heading) + muted path + snippet line; filetype icon via
gio::content_type_guess→Image::from_gicon. Extendbuild_csswith a.hit-snippetclass. - Action: replace
do_launchwith open-file —Enter/row-activated →xdg-open <path>;Ctrl+Enter→ open containing folder. Then close.
Key crates
ort (ONNX Runtime), tokenizers, usearch, rusqlite, ignore, notify, pdf-extract, zip+quick-xml, serde/serde_json/toml, gtk4 0.11 + gtk4-layer-shell 0.8, bread-theme (git tag v0.2.8).
Packaging & BOS integration (last phase — post-1.0, per earlier decision)
bakery.toml(model onbreadbox/bakery.toml):binaries = ["breadsearch","breadmill"], system_deps for onnxruntime/gtk;[[service]] unit="breadmill.service" enable=true;[config] dir="~/.config/breadsearch".- BOS: add
breadsearch+breadmilltobuild-local.shBREAD_BINS; autostartbreadmill.service; Hyprland keybind (e.g.SUPER+slash) →breadsearchin the skelhyprland.lua. - Release: dual remotes (origin GitHub + forgejo), bakery index regen — per the bread release train.
Phasing (de-risked: ship CPU, NPU later)
- Scaffold workspace +
breadsearch-shared(paths, config, IPC types, socket client). breadmillCPU pipeline end-to-end (walk→extract→chunk→embed→store→serve) +--reindex/--fetch-model+ systemd unit.breadsearchGUI fork (socket query + xdg-open + theme).- Packaging (bakery, config.example, README) + BOS wiring.
- Later: NPU
Embedderimpl (ort VitisAI/XDNA EP) — the go/no-go POC; pure backend swap.
Verification
breadmill --fetch-modelthen--reindexover a small test corpus; log embedded-chunk count; confirmstate_dirindex persists across restart.- Query the socket directly (a
breadmill query "..."subcommand orsocat) and confirm semantically-relevant hits with sane scores for a concept query (not keyword). - Launch
breadsearch, type a concept (e.g. "tax stuff", "that suspend bug fix"), see relevant files ranked,Enteropens via xdg-open,Ctrl+Enterreveals folder,Esccloses; theme matches breadbox; hot-reloads onbread-theme reload. - Edit/add/delete a file in a root →
notifyre-index → new content findable within seconds.
Notes / risks
- nomic prefixes + mean-pool + normalize must match between index and query or recall collapses.
ortlinking: prefer the crate's downloaded/bundled ONNX Runtime to avoid version skew with Arch'sonnxruntime.- Office formats (docx/odt) are best-effort in v1; md/txt/org/pdf are the reliable path.
- GPU EPs (ROCm/CUDA) fail to register silently at the ONNX Runtime level and fall back to CPU — always check
startup logs for
Successfully registeredbefore trusting a GPU build is actually accelerating. See README: GPU backend notes for the MIGraphX-vs-ROCMExecutionProvider distinction and the per-shape JIT-compile-and-cache behavior that matters for interactive query latency.