GEO /

Dynamic Hybrid Beats Dense and Fixed Hybrid

Parsec

TLDR: Pinecone, Weaviate, Qdrant, Elastic, Vespa — every shipped hybrid stack picks one fusion weight and lives with it. On FEVER the best fixed weight buys 1.3 percentage points R@10 at a cost of 41 percentage points R@1. Dasein predicts α per query in 0.40 ms, keeps the R@10 lift, and pays no R@1 tax. Portable predictor drops in front of any of those stacks. Conceptual illustration: a query branching into a dial that selects an α weight per call. Hybrid retrieval fuses dense and BM25 with a scalar weight α. Pinecone, Weaviate, Qdrant, Elastic with RRF, Vespa — they all pick α once at indexing time and use it on every query forever. That's the wrong default, and the cost lands on R@1, which is the metric every product showing a single answer or single citation actually cares about. The 42-percentage-point R@1 tax: We measured this on FEVER, sweeping α across 10 encoders on a 21-point grid. The best static α by R@10 is α = 0.50. At that fusion weight, R@10 lifts from 0.7628 dense to 0.7754, an increase of 1.3 percentage points. R@1 collapses from 0.6680 to 0.2623, a decrease of 41 percentage points. A single scalar can't reorder top-1 cleanly when BM25 and dense disagree. On a dense-friendly query, BM25 noise drags the right answer down a rank. On a lexical query, dense pulls a near-miss to the top. The α you'd want is different per query, and a fixed value is right for neither. What we built: A small predictor that reads the query embedding and emits α. 0.40 ms in the portable variant, 4.17 ms in the Dasein-native variant. Frozen — no training on your data. The bet was that the right α for a query is mostly a function of cheap features of the query, and a small model trained on retrieval outcomes could learn that mapping orders of magnitude cheaper than an LLM scoring top-1 candidates. It worked. The SDK exposes it as a per-query toggle on hybrid indexes. Set dynamic_hybrid=True and the predictor picks α for you. Python example: from dasein import Client. client = Client(api_key="dsk_..."). index = client.create_index("my-docs", index_type="hybrid", model="bge-large-en-v1.5"). results = index.query("AAPL Q3 2025 earnings call", top_k=10, mode="hybrid", dynamic_hybrid=True). Dasein picks α per query and fixed alpha is ignored. If you run your own dense plus BM25 pipeline, hit the predictor as a primitive. client.predict_dynamic returns α plus the matched K cutoffs as three scalars in one HTTP call. Python example: qvec = my_encoder.encode("who founded apple?"). p = client.predict_dynamic("who founded apple?", query_vector=qvec). p.alpha is a float in [0.0, 1.0], where 1.0 means all dense under the Pinecone and Weaviate convention. p.top_k_dense is an integer in [1, 10] for pure-dense ranking. p.top_k_hybrid is an integer in [1, 10] for hybrid ranking. fused = rrf_fuse(my_dense_hits, my_bm25_hits, alpha=p.alpha). kept = fused[:p.top_k_hybrid]. Pass your own query_vector — otherwise the prediction is tied to Dasein's encoder geometry instead of yours. Free tier is 1,000 calls per month. Paid hybrid plans are unlimited. The receipts: From dynamic_hybrid_summary.md in the repo, pooled across 10 encoders from MiniLM through E5-Mistral-7B and GTE-Qwen2-7B, per BEIR corpus. A note on convention: α values here follow the SDK: 1.0 = all dense, 0.0 = all BM25, default 0.5. The benchmark file uses the inverse, score = (1−α)·dense + α·BM25, so a best static α = 0.05 BM25-weight in the source maps to alpha = 0.95 in the SDK. Portable on FEVER, n=160,559: Dense — R@1 0.6680, R@10 0.7628, MRR 0.7084, mean rank 33.4. Best static α = 0.50 — R@1 0.2623, R@10 0.7754, MRR 0.4217, mean rank 12.8. Dynamic Hybrid — R@1 0.6830, R@10 0.8548, MRR 0.7494, mean rank 12.6. Per-query α captures the mean-rank improvement that fixed α gets, 33 to 13, without the 41-percentage-point R@1 collapse. R@10 lifts 9.2 percentage points over dense. Portable on NQ, n=4,723: Dense — R@1 0.5122, R@10 0.6943, MRR 0.5823, mean rank 40.0. Best static α = 0.95 — R@1 0.4120, R@10 0.6957, MRR 0.5109, mean rank 32.8. Dynamic Hybrid — R@1 0.5079, R@10 0.7629, MRR 0.6003, mean rank 21.6. Same shape — R@10 lifts, R@1 doesn't get taxed. Portable on SciFact, n=2,934, the dense-dominant corpus where BM25 has little to add: Dense — R@1 0.6118, R@10 0.8770, MRR 0.7042, mean rank 9.3. Best static α = 0.95 — R@1 0.6138, R@10 0.8817, MRR 0.7073, mean rank 8.1. Dynamic Hybrid — R@1 0.6125, R@10 0.8821, MRR 0.7054, mean rank 8.1. Flat, which is right. The predictor sees SciFact queries don't benefit from BM25 and keeps α near 1.0. FiQA, the other dense-dominant corpus, behaves the same way, with full tables in the source. If your corpus is dense-dominant, Dynamic Hybrid degrades gracefully — you don't pay a tax to turn it on. Fixed-α vs Dynamic Hybrid on FEVER, R@1 and R@10 side by side, showing the 42-percentage-point R@1 gap. Dasein-native variant: End-to-end on the Dasein index, with per-query α composed with the compressed index and the per-query K predictor, the numbers get bigger. FEVER, n=144,918: R@1 lifts from 0.7401 dense to 0.8603, mean rank from 16.3 to 1.9 — the right answer is on average the second result returned. NQ, n=4,728: R@10 from 0.6937 to 0.9425 (+24.9 percentage points), R@1 from 0.5116 to 0.6997 (+18.8 percentage points). The composition is where the larger lifts come from. RRF composes with per-query α: RRF fixes the fusion arithmetic — the score-incompatibility between cosine and BM25 — by going rank-only. It doesn't give you per-query adaptation; the same policy still applies to every query. Per-query α and RRF compose. Predict α with the portable predictor and feed it into weighted fusion. Use RRF and ignore the α. Use the predictor's confidence to pick which fusion to run per query. The numbers above are weighted fusion because the BEIR baselines are. The SDK ships both. What to do: A question to ask any retrieval vendor: is your hybrid α per query? Right now nobody in production except Dasein answers yes. The portable SDK is free. Drop it on FEVER or your own production traces, measure R@1 and R@10 before and after. We closed the 42-percentage-point gap on FEVER and NQ. Close it on your data.