A 17M-parameter cross-encoder reranker for offline search on a phone. It is cross-encoder/ettin-reranker-17m-v1, fine-tuned by listwise distillation from cross-encoder/ettin-reranker-400m-v1.
I made it for Commonplace, an offline research assistant for Android. The app searches local copies of Wikipedia, Stack Exchange, Wikibooks, Wikivoyage and other sources. This model reranks the top 24 passages that keyword and vector search find for each question. The architecture and the inference cost are the same as the base model.
The test set has 161 questions: the app's seed questions and the first turns of its test conversations. Each question has a pool of 24 passages from the app's own retrieval. Claude Sonnet graded every passage (0 = not relevant, 1 = relevant, 2 = answers the question). The "explain" subset has the 46 questions that ask why or how.
NQ is the Natural Questions open-domain validation set (2,610 held-out questions). A question is a hit at k when an answer string occurs in one of the top k passages of its pool.
| Model | nDCG@8 all | nDCG@8 explain | P@8 | NQ hit@1 | NQ hit@3 | NQ hit@10 |
|---|---|---|---|---|---|---|
| ettin-reranker-17m-v1 (base) | 0.801 | 0.724 | 0.847 | 0.495 | 0.648 | 0.737 |
this model, onnx/model_sel8.onnx |
0.813 | 0.734 | 0.848 | 0.510 | 0.653 | 0.737 |
| ettin-reranker-400m-v1 (teacher) | 0.885 | 0.829 | — | — | — | — |
The gain on the 161 questions is small: +1.2 points of nDCG@8 (95% confidence interval +0.1 to +2.3). 76 questions improved and 55 got worse. Do not expect a large change on general web search benchmarks.
The ONNX files contain the transformer only. Each one outputs last_hidden_state. To get the score, take
the first token's vector and apply the head from 2_Dense, 3_LayerNorm and 4_Dense:
Dense 256 (GELU, no bias) → LayerNorm → Dense 1.
| File | Use |
|---|---|
model.safetensors and the module folders |
PyTorch, Sentence Transformers |
onnx/model.onnx |
fp32, desktop CPU |
onnx/model_sel8.onnx |
ARM64 phones. Dynamic int8 (per channel) except layers.0/mlp/Wo/MatMul, which stays fp32. Plain int8 loses about 6 points of NQ hit@1 because of large activations in that layer. |
onnx/model_nb8.onnx |
8-bit weights only (MatMulNBits, blocks of 32). Same quality as fp32, slower than model_sel8.onnx on ARM. |
onnx/model_quint8_avx2.onnx |
x86-64 with AVX2 (for example, the Android emulator) |
The model was trained with inputs of at most 256 tokens.
from sentence_transformers import CrossEncoder
model = CrossEncoder("0xPD33/ettin-reranker-17m-commonplace", max_length=256)
scores = model.predict([
("How do vaccines train the immune system?", "A vaccine contains an antigen that ..."),
("How do vaccines train the immune system?", "The first smallpox vaccine was ..."),
])
Higher scores mean more relevant. The scores are logits, not probabilities.
23,012 queries. Each query has a pool of up to 24 passages from the Commonplace search pipeline (BM25 and dense retrieval, fused):
A filter removed generated questions that were near-duplicates of the test questions or the NQ dev questions.
Apache 2.0, the same as the base model and the teacher. The training queries and passages come from sources under their own licenses (for example CC BY-SA for Wikipedia, Stack Exchange and NQ). This repository does not contain those texts.
onnx/model.onnx 67 MBmodel.safetensors 67 MBonnx/model_nb8.onnx 56 MBonnx/model_sel8.onnx 17 MBonnx/model_quint8_avx2.onnx 17 MB2_Dense/model.safetensors 262.2 kB3_LayerNorm/model.safetensors 2.2 kB4_Dense/model.safetensors 1.2 kB