Support external/remote inference endpoints for embeddings and reranking (OpenAI-compatible), with local fallback #2
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Request: allow the engine to call an external inference server for embeddings and reranking over an OpenAI-compatible HTTP API, falling back to the current in-process models when no URL is configured.
Today both models are loaded in-process and are hard-bound to a local torch device.
_resolve_device()inengine/kb/embeddings.pyonly ever returnscudaorcpu— there is no third option and nothing in the engine takes a host or URL. Confirmed by grep: no references tobase_url,api_base, or any HTTP model client anywhere underengine/(httpxis a dev/test dependency only).This is an enhancement request — please label accordingly (the repo currently has no labels defined).
Motivation
Reranking landed in the engine but is currently unusable on an 8 GB consumer GPU, because the reranker must share VRAM with the embedding model in the same process.
Observed on a host with an 8 GB NVIDIA card (
<gpu-host>), engine v3.3.0:KB_RERANK_CANDIDATESBAAI/bge-reranker-v2-m3Tried to allocate 3.98 GiB, 1.07 GiB freetomaarsen/Qwen3-Reranker-0.6B-seq-clsTried to allocate 818.00 MiB, 681 MiB freetomaarsen/Qwen3-Reranker-0.6B-seq-clsTried to allocate 144.00 MiB, 45 MiB freeWith both models resident the GPU sat at 7127 MiB / 8188 MiB at idle. Failing to allocate 144 MiB at 8 candidates shows candidate count is not the lever — the resident model is consuming the card.
KB_RERANK_ENABLEDis currently back tofalsein that deployment so search keeps working.Being able to point the reranker at a separate inference host would sidestep this entirely, and would let a machine with a different/larger GPU serve models without moving the whole engine (and its SQLite data) to that machine.
Proposed change
Two new optional settings in
engine/kb/config.py:KB_EMBEDDINGS_URL— OpenAI-compatible base URL; when set,embed_texts()POSTs to/v1/embeddingsinstead of using the in-processSentenceTransformer.KB_RERANKER_URL— when set,rerank_scores()POSTs to/v1/rerankinstead of using the in-processCrossEncoder.Plus
KB_INFERENCE_API_KEY(optional bearer token) for servers that require auth.When unset, behaviour is unchanged — models load locally exactly as now. This matches the graceful-degradation pattern
engine/kb/reranker.pyalready documents ("Reranking is strictly optional — search degrades gracefully to plain hybrid retrieval when the model is absent")./v1/embeddingsand/v1/rerankare the de-facto standard: both llama.cpp'sllama-server(with--pooling rankfor rerank) and LocalAI expose them, as do several hosted providers. Implementing the interface rather than a specific vendor keeps this small.Suggested scope split
Reranking is the low-risk half and could ship alone. It is stateless — the model can be swapped freely with no migration.
Embeddings are not symmetric. An existing corpus (~123k chunks in one deployment) is embedded with
bge-base-en-v1.5at 768 dims. Changing the embedding model requires a fullreindex, and a model with a different dimension additionally requires asqlite-vecschema change. Worth handling as a separate follow-up, ideally with a dimension check at startup that refuses to start rather than silently corrupting search.Related bugs found while testing
These are worth fixing regardless of whether remote inference is adopted, since they are what makes local reranking fragile:
No
batch_sizeormax_lengthinrerank_scores()(engine/kb/reranker.py). All candidates are passed toCrossEncoder.predict()in a single call, so peak VRAM scales with candidate count x longest chunk.bge-reranker-v2-m3has an 8192-token limit, so a long chunk pads the whole batch — this is the direct cause of the 3.98 GiB allocation above.Model loads in fp32.
CrossEncoder(model_name, device=resolved_device)with no dtype. Passingmodel_kwargs={"torch_dtype": "float16"}would roughly halve resident VRAM and may be sufficient on its own for 8 GB cards.The default reranker is heavy, and the proposal's sizing does not apply to it.
docs/kb-enhancements-proposal.htmspecifiesqwen3-reranker-0.6b (~640MB GGUF), chosen explicitly as "small enough to sit alongside bge on the 4070 permanently". ButCrossEncodercannot load GGUF, and the default becameBAAI/bge-reranker-v2-m3— a 568M-param XLM-RoBERTa-large. The loadable HF safetensors port of qwen3-0.6b is 2.38 GB fp32, ~4x the budgeted size. Either the default should be something that fits (e.g.BAAI/bge-reranker-base, 278M, 512-token cap) or the docs should be corrected.Qwen/Qwen3-Reranker-0.6Bmust not be used directly. It isQwen3ForCausalLM— no sequence-classification head. Loading it viaCrossEncoderwould not error: transformers randomly initialises thescorehead, givingloaded: truein/statusand silently meaningless rankings. A startup guard that checksconfig.architecturesends inForSequenceClassification— or at minimum a documented warning — would prevent a silent quality regression. (tomaarsen/Qwen3-Reranker-0.6B-seq-clsis a correct port and loads cleanly with no random-init warning.)Environment
nvidiaimage variant