
After studying this material, you should be able to:
Before pruning makes sense, you need to understand what we are pruning.
Sparse retrieval is a method of finding relevant documents by representing both queries and documents as sparse vectors — meaning most values are zero, and only a few terms carry non-zero weights.
Query: "best coffee shops"
Sparse representation:
coffee → 2.3
shop → 1.8
best → 0.4
(all other 30,000 vocabulary terms → 0)
| Encoder | Avg. Query Terms | Character |
|---|---|---|
| SPLADE | ~44 terms | Dense query expansion |
| V3-GTE | ~7 terms | Compact, focused |
💡 Why this matters: These represent opposite extremes. A finding that holds for both is truly general.
Sparse retrieval relies on an inverted index — the core data structure.
Term: "coffee"
Postings: [(doc_3, score=2.1), (doc_7, score=1.4), (doc_15, score=0.9), ...]
Term: "shop"
Postings: [(doc_1, score=1.9), (doc_3, score=1.7), (doc_22, score=0.5), ...]
To answer a query, the engine:
Real indexes have billions of postings. Traversing them all is slow. This is where pruning comes in.
Static pruning = permanently removing entries from the index before any query arrives.
Think of it like editing a book's index before the library opens — you remove entries you believe will rarely matter.
┌─────────────────────────────────────────────────────┐
│ STATIC PRUNING │
│ │
│ ┌──────────────────┐ ┌──────────────────────┐ │
│ │ INDEX-SIDE │ │ QUERY-SIDE │ │
│ │ PRUNING │ │ PRUNING │ │
│ │ │ │ │ │
│ │ • Document-level │ │ Remove low-weight │ │
│ │ (remove whole │ │ terms from query │ │
│ │ documents) │ │ before retrieval │ │
│ │ │ │ │ │
│ │ • Posting-list │ │ e.g., drop terms │ │
│ │ (remove low- │ │ with weight < 0.1 │ │
│ │ score entries) │ │ │ │
│ └──────────────────┘ └──────────────────────┘ │
└─────────────────────────────────────────────────────┘
Dynamic pruning = skipping entries at query time, based on the query itself.
Unlike static pruning, nothing is permanently deleted. The engine decides on-the-fly what to skip.
| Engine | Architecture | Dynamic Pruning Mechanism |
|---|---|---|
| Exhaustive C++ pipeline | Standard inverted index | None (baseline) |
| BMP | Block-max index | β parameter caps posting traversal |
| SEISMIC | Clustered inverted index | query_cut limits query terms processed |
💡 Key insight: BMP and SEISMIC already handle query-level skipping internally. This becomes critical in Step 6.
This is the mechanistic explanation for why index-side pruning works.
CPU wants data
↓
L1 Cache (fast, tiny) → MISS
↓
L2 Cache → MISS
↓
L3 Cache → MISS
↓
RAM (slow) ← This is where posting lists live
The paper validates this with hardware profiling:
Smaller index → Fits better in cache → Fewer cache misses → Faster retrieval
This is a hardware-level argument, which is why it holds across all three engines regardless of their software architecture.
This is the central finding of the paper.
| Metric | Improvement |
|---|---|
| Latency reduction | 1.2× – 6.6× |
| Index size reduction | 18% – 82% |
Why it transfers: The memory-bound nature of retrieval is universal. Smaller index = less memory pressure = faster on any engine.
Exhaustive pipeline: 4–11× speedup ✓ (very helpful!)
BMP: ~0× speedup ✗ (already handled by β)
SEISMIC: ~0× speedup ✗ (already handled by query_cut)
Why it breaks: Modern engines already internalize query-level pruning through their dynamic mechanisms. Adding static query pruning on top is redundant — you're pruning what the engine would have skipped anyway.
💡 Analogy: Imagine pre-sorting a deck of cards before handing it to a machine that automatically sorts cards. Your pre-sorting effort is wasted.
Even though query pruning alone doesn't help modern engines, combining index-side static pruning with dynamic pruning does.
Baseline (exact): NDCG@10 = X, latency = 1.0×
+ Document pruning only: NDCG@10 ≈ X, latency = 1.8×
+ Query pruning only: NDCG@10 ≈ X, latency = 1.1× (minimal gain)
+ Both combined: NDCG@10 within 0.003 of X, latency = 2.5×
The combination works because:
This is the actionable guideline for practitioners.
Pruning aggressiveness →
Recall@10: 100% ──────────────────╲──────── drops sharply
╲
NDCG@10: 100% ──────────────────────╲──── drops later
╲
↑
"The Knee"
Recall ≈ 85–95%
NDCG still stable
| Metric | Behavior |
|---|---|
| NDCG@10 | Saturates (stays high) even with aggressive pruning |
| Recall@10 | Degrades earlier, reaching 85–95% at the knee |
Push pruning until Recall@10 reaches ~85–95%. At this point, NDCG@10 will still be within noise of the unpruned baseline.
Why Recall@10 is the right signal:
┌─────────────────────────────────────────────────────────────┐
│ DECISION FRAMEWORK FOR PRACTITIONERS │
│ │
│ What engine are you using? │
│ │
│ ┌─────────────────┐ ┌──────────────┐ ┌───────────────┐ │
│ │ Exhaustive │ │ BMP │ │ SEISMIC │ │
│ │ (no dynamic │ │ (has β) │ │ (has query_ │ │
│ │ pruning) │ │ │ │ cut) │ │
│ └────────┬────────┘ └──────┬───────┘ └───────┬───────┘ │
│ │ │ │ │
│ Use BOTH │ Use INDEX-SIDE only │ │
│ static │ (query pruning redundant) │ │
│ prunings │ │ │ │
│ └──────────────────┴───────────────────┘ │
│ │ │
│ Stop when Recall@10 ≈ 85–95% │
│ (NDCG@10 will still be safe) │
└─────────────────────────────────────────────────────────────┘
| Concept | Key Takeaway |
|---|---|
| Static pruning | Permanently removes index entries before queries arrive |
| Index-side pruning | Portable across all engines; reduces memory pressure |
| Query pruning | Only helps exhaustive engines; redundant on BMP/SEISMIC |
| Why portable | Sparse retrieval is memory-bound (cache/TLB evidence) |
| Combining static + dynamic | Complementary; 2.5× speedup with <0.003 NDCG loss on BMP |
| Stopping criterion | Prune until Recall@10 ≈ 85–95%; NDCG@10 remains stable |
| Encoder regime | Findings hold for both dense (SPLADE) and sparse (V3-GTE) queries |