2.8 KiB
Studio RAG: fast incremental indexing
Progress log for an optimization stacked on the feature/rag branch (PR #5759). The goal is to
keep that PR's UX and retrieval accuracy while cutting per-document indexing from ~1 minute to a
few seconds, validated through the real Studio UI.
Root cause of slow indexing (measured on feature/rag)
- Embedder reload on every upload. Ingestion spawns a fresh
spawnsubprocess per job (core/rag/ingestion.py_CTX.Process(target=_subprocess_worker)), and the embedder is loaded inside that subprocess (get_embedder->from unsloth import FastSentenceTransformer). A spawned interpreter re-imports unsloth and reloads the model from scratch every time, so the ~12-30s cold load recurs on each upload, not just the first.lifespan()never warms it. - Per-document BM25 rebuild. On every document,
ingestion.pyreads all chunks in the scope (_all_scope_chunks) and rebuilds the wholebm25sindex (bm25.rebuild_index). That is O(N^2) tokenization as a knowledge base fills.
The change (all behind a single UNSLOTH_RAG_FAST=1 flag; off = byte-identical to PR)
- Warm the embedder at startup.
main.pylifespan()warmsget_embedder()in a daemon thread (mirrors the existing GGUF precache thread). - In-process ingestion. Run the existing
_subprocess_workerin a thread in the warm main process instead of a fresh subprocess, soget_embedderreturns the already-loaded singleton. The worker, queue protocol, persistence and SSE progress are otherwise unchanged. - Incremental SQLite FTS5 BM25. Reimplement
core/rag/bm25.pyon an FTS5 virtual table in the existingrag.db, with an incrementaladd_chunks(scope, chunks). Ingestion inserts only the new document's chunks (O(N) total) instead of rebuilding the scope. FTS5MATCHreturns only matching rows (no zero-score pollution) and addsporterstemming. The dense leg already uses sqlite-vec, so it is unchanged; RRF fusion inretrieval.pyis unchanged.
Validation
Two isolated Studios (separate UNSLOTH_STUDIO_HOME + port), same prebuilt frontend, same corpus,
same model. Baseline = UNSLOTH_RAG_FAST unset; improved = set. Driven through the real UI with
Playwright (studio_test_kit). Local GGUF model unsloth/Qwen3.5-9B-GGUF for the chat tool path.
Authoritative corpus (stable URLs): arXiv 1706.03762 (Attention), 1810.04805 (BERT), 2005.11401 (RAG); RFC 9110. Retrieval scored independently of generation with gold queries.
Metrics: per-document upload->ready latency (cold + warm), Recall@1/3/5 and MRR via POST /api/rag/search, scaling (per-doc index time vs document count), and end-to-end UI (upload ->
indexed -> RAG answer with citation). Robustness: delete-then-query, restart persistence, thread
isolation.
Results
Pending - populated as runs complete (see commits below).