Files
esh-pfi-infrastructure/stacks/tei-bakeoff/README.md
T

118 lines
5.9 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.
# tei-bakeoff — TEI vs vLLM for embedding + reranking on esh-ml1
Prime, 2026-09-25: *"run the TEI bake-off."* Hugging Face **Text Embeddings
Inference 1.9.4** (`89-1.9.4`, the Ada build) serving the same two models as
[`embed-rerank`](../embed-rerank/) (vLLM v0.24.0), side by side on the same RTX
2000E Ada. Not wired into the gateway. This stack either becomes the
embed-rerank stack or is torn down.
## Results (2026-09-25 0744–0805 PT)
### Parity — TEI matches the existing vLLM vectors
Reference = vLLM on fv-ml1, the engine every existing index was built with.
306 texts (6 fixed incl. CJK/code/6k-char + 300 *The Stand* paragraphs);
retrieval = 2,000-paragraph corpus, 50 instruction-format queries.
| embedding check | result | noise floor / control |
|---|---|---|
| cosine TEI vs vLLM-FV | median 0.999925, min 0.999861 | vLLM-FV vs itself: 0.999916 / 0.999796 |
| overlap@10, TEI index + TEI queries | 0.976 | vLLM-FV rerun 0.986; vLLM-ESH 0.972 |
| overlap@10, **TEI queries vs the OLD vLLM index** (migration case) | **0.988** | positive control (MRL-256 dims) 0.648 |
| hit@1 own paragraph | 0.80 | vLLM-FV 0.80 |
| different-text negative control | cosine median 0.31 | — |
TEI is deterministic (TEI vs itself: min 0.999995). **Switching engines does not
require re-embedding existing indexes.**
| rerank check (100 queries × 20 docs, 9 same-chapter distractors) | TEI vs vLLM-FV | vLLM-FV vs itself |
|---|---|---|
| source paragraph ranked #1 | 0.95 (same as vLLM) | 0.95 |
| top-1 agreement | 1.00 | 1.00 |
| top-3 set agreement | 1.00 | 1.00 |
| top-5 exact order | 0.98 | 1.00 |
| order among docs scoring > 0.05 | 1.00 (n=37) | 1.00 |
| full 20-doc order | **0.55** | 0.99 |
| max score difference | **0.019** (p99 0.0034) | 0.0016 |
Every decision that matters agrees; the differences are shuffles among
near-zero-scoring tail documents. ⚠ A consumer that **thresholds** on the rerank
score could see a borderline document flip (scores move up to ~0.02). An
earlier 30-query run with random distractors had one top-1 disagreement (29/30);
across both runs, 129/130.
### Speed — TEI is NOT faster on this card
On-box, 2 interleaved runs × 3 reps each, medians:
| workload | vLLM | TEI |
|---|---|---|
| embed 1 short query, p50 | ~9.1 ms | **~6.9 ms** |
| embed 1 × ~512 tok, p50 | ~23 ms (bimodal 10–25) | ~28 ms |
| embed 1 × ~2k tok, p50 | **~86 ms** | ~108 ms |
| bulk embed, passages/s | **~50** | ~37 |
| whole novel (*The Stand*, 12,814 paragraphs), 64/request, 4 in flight | **31 s** | 40 s |
| rerank 20 docs, p50 | **~169 ms** | ~180 ms |
| rerank 20 docs, req/s at conc 8 | ~5.7 | ~5.6 |
TEI ran its fused `FlashQwen3` path. `--max-batch-tokens 32768` did not change
the novel time (39–40 s), so it stays at the default. **Through the gateway these
gaps mostly vanish**: LiteLLM is the bottleneck there (see
`servers/esh-ml1/README.md`).
### Footprint — TEI is much lighter
| | vLLM (both) | TEI (both) |
|---|---|---|
| VRAM (host `nvidia-smi`, per process) | 3,298 + 1,512 MiB | 1,352 + 1,256 MiB |
| image | 29.9 GB | 8.16 GB |
| warm restart to healthy (n=3) | ~24 s | ~4 s |
| container RAM just after start | 2.5 + 5.1 GiB | 0.7 + 1.7 GiB |
⚠ vLLM's VRAM figure is mostly a **setting** (`--gpu-memory-utilization 0.20`
each). A lower setting would shrink it; that has not been tested.
### External review — Dvalin (Grok research peer), 2026-09-25
**Agree with caveats: adopt TEI for these two seats; footprint is the right
reason.** His external evidence (not re-verified here): a Runpod 2026-09-14
engine comparison shows vLLM ahead of TEI on Qwen3-Embedding bulk throughput
(median ~2.6×, single unreplicated runs, near parity on smaller cards) and TEI
ahead on BERT-family models — consistent with our 1.3× on a 50 W card and the
rerank near-tie. Open TEI 1.9 issue #857 (tokio panic "No backend receiver"
under load, n=1) is a watch item, not a gate. A lower vLLM memory setting
would shrink only the embedder (the reranker at 1,512 MiB is already under
its cap); 0.12 is the only setting he'd expect to both boot and help — untested.
His caveats, and status: fail-closed truncation (**done**, above); recheck score-
threshold consumers (**done**, nevermore, above); two gateway providers must be
written down at cut-over (open).
## Gateway compatibility (LiteLLM v1.97, temp aliases, deleted after)
- Embeddings: **drop-in**. TEI's `/v1/embeddings` works with the existing
`hosted_vllm/` provider.
- Rerank: needs the **`huggingface/` provider** (scores identical to direct
TEI). `hosted_vllm/` fails with 422 (`missing field texts`); TEI's `/rerank`
body differs from vLLM's.
## Behaviour differences to carry into any cut-over
- **Truncation — now fail-closed (`--auto-truncate false`).** Measured with
TEI's default: a ~40k-token input returned **200 with a vector of its first
16,384 tokens**, silently, where vLLM returns 400. With truncation off, TEI
embed rejects > 32,768 tokens and TEI rerank rejects > 8,192 (both 422,
verified). Truncation off requires `--max-batch-tokens` at or above the model's max
input (32,768 for Qwen3-Embedding) or TEI refuses to start; VRAM unchanged.
Remaining difference: TEI embed **accepts** 8,193–32,767 tokens that vLLM
(`--max-model-len 8192`) rejects. It embeds the whole input, which is wider,
not lossy.
- **nevermore thresholds rerank scores** (`NEVERMORE_RERANK_THRESHOLD`, default
0.3, cluster-member filter in `digest.py`). Across the 2,000 bake-off scores,
**0 flips** at 0.3 or 0.5 for TEI vs vLLM — but only 1 score landed within
±0.02 of 0.3 (bge scores sit near 0 or 1), so this cannot exclude rare flips
on borderline headlines. Worst case: a same-story headline clusters
differently. Low stakes.
- TEI caps inputs per request at `--max-client-batch-size` (default 32; set to 128 here).
- TEI on CUDA runs float16 only (vLLM served Qwen3-Embedding in bf16); parity
above says it does not matter here.