Learned sparse retrieval
Search this site
Papers, slides, projects and the CV, searched with inference-free SPLADE — no model runs in your browser. Pick a result to see the vectors behind it.
Results appear as you type. Arrow keys move through them, Enter opens one, Escape dismisses the list.
The index loads when you focus the box.
What just happened
78 passages · 30,522 dimensionsDissecting result
Your query, decomposed
Each WordPiece and its static weight. Dashed is out of vocabulary, orange matched nothing.
Type a query.
Why that result scored
Shared terms, biggest first. Teal was in the text, orange the model added.
No result selected
Open →Pick a result.
The vector behind it
All 30,522 dimensions. Nearly every one is zero.
That vector, as words
The part a dense embedding cannot show you.
in the passage added by the model matched your query
Pick a result.
How this works
SPLADE is asymmetric. Encoding a document is a 67-million-parameter masked language model's job: it reads the passage and outputs a weight for every one of BERT's 30,522 tokens, of which all but a hundred or so come out at zero. Unlike a dense embedding, every non-zero dimension has a name you can read.
Encoding a query is not a model at all — a tokenizer, and one static weight per token from a table shipped as a text file. So the expensive half runs offline, the browser downloads the table, the vocabulary and the postings, and scoring is a sparse dot product. That is why this runs on GitHub Pages with no inference server, no ONNX and no WASM.
The filters are ordinary metadata matching, and they run after scoring — the
model never sees a year or a venue. It shreds the strings a known-item lookup depends on:
type ICUAS 2024 and watch it become ic ##ua ##s and
202 ##4. Here the shards are rare enough that the right paper still comes
first, but that is luck, and when it misses it misses quietly.
The honest caveats
The query weights are static MS MARCO term weights — a learned IDF, fixed at training time. They know drone is more discriminative than system in general; they know nothing about this corpus. On 78 passages that is fine, and it is why no model is needed at query time.
The model is English-only, and it expands documents rather than reasoning
about them. It has no idea Grøntved is a name — it sees
gr ##ø ##nt ##ved and matches whatever contains those fragments. Meaning is what
it is for; finding a specific known thing is what the filters are for.
Built by
tools/build_search_index.py
from OpenSearch's
inference-free sparse encoder (Apache-2.0), rebuilt by CI when the content changes. The
client is
search/splade.js
— a WordPiece tokenizer and a dot product, no dependencies.
The adjacent idea is late interaction, which keeps one dense vector per token instead of collapsing to one per document. There is a primer on it here.