Kasper Grøntved

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.

Try

The index loads when you focus the box.

What just happened

78 passages · 30,522 dimensions

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

Pick a result.

The vector behind it

All 30,522 dimensions. Nearly every one is zero.

token 0vocabulary position30,521

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.