Files
yvgude--lean-ctx/docs/guides/custom-embeddings.md
T
wehub-resource-sync 26382a7ac6
CI / Clippy (push) Failing after 15m13s
CI / Test (ubuntu-latest) (push) Failing after 16m1s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (no embeddings / no ORT) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Cookbook (Node) (push) Has been cancelled
CI / Pi Extension (Node) (push) Has been cancelled
CI / Rust SDK (lean-ctx-client) (push) Has been cancelled
CI / Embed SDK (lean-ctx-sdk) (push) Has been cancelled
CI / Python SDK (leanctx) (push) Has been cancelled
CI / Hermes Plugin (Python) (push) Has been cancelled
CI / SDK Conformance Matrix (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / cargo-deny (push) Has been cancelled
CI / Adversarial Safety (push) Has been cancelled
CI / Benchmarks (push) Has been cancelled
CI / Output-Quality Gate (eval A/B) (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / CI Green (push) Has been cancelled
JetBrains Plugin / Actionlint (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (rust) (push) Has been cancelled
JetBrains Plugin / Validation (push) Has been cancelled
JetBrains Plugin / Build (push) Has been cancelled
JetBrains Plugin / Test (push) Has been cancelled
Security Check / Security Scan (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:30 +08:00

122 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Custom Embedding Models (`hf:org/repo`)
`ctx_semantic_search` ships three built-in ONNX models — but every team's corpus is
different. Since GL #397 you can point lean-ctx at **any HuggingFace repo with an ONNX
export**, fully local, no API keys:
```toml
# config.toml
[embedding]
model = "hf:intfloat/multilingual-e5-small@ffdcc22a9a5c973258343b0001a4d483cbd45be9"
```
Or per-shell:
```bash
export LEAN_CTX_EMBEDDING_MODEL="hf:intfloat/multilingual-e5-small@ffdcc22..."
```
The env var always wins over `config.toml`. The first semantic search after a model
switch triggers a **one-shot re-index** (the vector index stores the model id and
detects the mismatch automatically — no manual steps).
## What the repo must contain
| File | Purpose |
|---|---|
| `onnx/model.onnx` | The ONNX export of the encoder |
| `tokenizer.json` | HuggingFace fast-tokenizer config (WordPiece/BPE) |
Most `sentence-transformers`-compatible repos already publish both (look for the
`onnx/` folder on the model page). If yours doesn't, export it once:
```bash
pip install optimum[onnxruntime]
optimum-cli export onnx --model org/your-model --task feature-extraction ./out
# upload ./out/model.onnx as onnx/model.onnx + the generated tokenizer.json
```
## Syntax
```text
hf:<org>/<repo>[@<revision>]
```
- `revision` may be a tag, branch, or commit SHA. **Pin a revision.** Without a pin
lean-ctx resolves `main` and logs a warning — an upstream force-push could otherwise
change your embeddings silently.
- Optional `[embedding].dimensions` declares the vector width as a fallback. You can
usually omit it: lean-ctx probes the real width from the ONNX graph at load time.
```toml
[embedding]
model = "hf:org/repo@v1.2"
dimensions = 1024 # optional fallback, probed value wins
```
## Static embeddings: model2vec (GL #452)
Besides classic transformer encoders, lean-ctx drives **model2vec** static-embedding
exports — ONNX graphs with an EmbeddingBag topology (`input_ids` + `offsets`, output
already pooled to `[batch, dim]`). Topology is detected from the graph's input
signature at load time; no configuration needed:
```toml
[embedding]
model = "hf:minishlab/potion-base-8M@main" # pin a commit SHA in production
```
Why you would want this:
| Metric | Transformer (minilm) | model2vec (potion-base-8M) |
|---|---|---|
| Inference | ~520 ms/text | ~0.05 ms/text (**~500x**) |
| Model size | 91 MB | ~30 MB |
| Dimensions | 384 | 256 (probed from the graph) |
| Quality | baseline | ~9295 % of MiniLM on MTEB retrieval |
The trade-off is deliberate: static embeddings skip the attention pass entirely, so
initial indexing of large repos and search on weak hardware (CI runners, laptops on
battery) get a massive throughput win for a moderate quality loss. Everything else —
`hf:` download, SHA-256 lockfile, re-index-on-switch, BM25 fallback — behaves exactly
like any other custom model.
## Supply-chain integrity
Downloads are cached under `~/.lean-ctx/models/hf-<org>-<repo>[-<rev>]/`. After the
first successful download lean-ctx writes a `model.lock.json` with the SHA-256 of every
artifact (trust-on-first-use). Any later re-download that doesn't reproduce the pinned
hash **fails hard** — a repo that swaps bytes under the same revision is rejected.
To intentionally accept new upstream content: delete the model directory (or just
`model.lock.json`) and re-run.
## Operational notes
- **Storage isolation**: every repo+revision combination gets its own directory, so
switching back and forth never re-downloads.
- **Re-index semantics**: the index records the canonical model id
(`hf:org/repo@rev`). Changing repo *or* revision re-indexes once; vectors from
different models never mix.
- **Offline**: once downloaded, no network access is needed (or attempted).
- **Fallback**: if the model fails to load (bad export, unsupported tokenizer),
semantic search degrades gracefully to BM25 — search keeps working.
## Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| `Unknown embedding model 'hf:…'` | Typo in repo id (must be exactly `org/repo`) | Check the repo URL on huggingface.co |
| `Download … returned HTTP 404` | Repo has no `onnx/model.onnx` at that revision | Export with `optimum-cli` (see above) or pick a repo with an ONNX export |
| `Failed to load tokenizer.json` | Repo ships no fast-tokenizer config or uses an unsupported model type | Re-export the tokenizer (`AutoTokenizer.from_pretrained(...).save_pretrained()` writes `tokenizer.json`) |
| `SHA-256 mismatch` | Upstream changed bytes under the same revision | Verify upstream intent, then delete `model.lock.json` to re-pin |
| Search results look wrong after switch | Old index still loading in a long-running daemon | `lean-ctx restart` — the re-index happens on the next search |
## Built-ins (no setup required)
| Alias | Model | Dims | Best for |
|---|---|---|---|
| `minilm` (default) | all-MiniLM-L6-v2 | 384 | Fast general-purpose |
| `jina-code-v2` | jina-embeddings-v2-base-code | 768 | Code + natural language |
| `nomic` | nomic-embed-text-v1.5 | 768 | Long-form text, MTEB-strong |