mlboydaisuke's picture
Reference hero in card
880b615 verified
|
Raw
History Blame Contribute Delete
3.7 kB
---
license: apache-2.0
library_name: litert
pipeline_tag: text-ranking
tags: [qwen3, qwen3-reranker, reranker, reranking, cross-encoder, rag, retrieval, litert, tflite, on-device, gpu, android]
base_model: Qwen/Qwen3-Reranker-0.6B
---
# Qwen3-Reranker-0.6B β€” LiteRT on-device RAG reranker (fully GPU)
[Qwen3-Reranker-0.6B](https://huggingface.co/Qwen/Qwen3-Reranker-0.6B) (Apache-2.0), the 2025 SOTA
small reranker, re-authored to run **entirely on the LiteRT `CompiledModel` GPU** (ML Drift). Given a
query and candidate documents, it scores each by relevance (`P("yes")`) and reorders them β€” the
reranking half of an on-device RAG pipeline.
Pairs with [`litert-community/Qwen3-Embedding-0.6B-LiteRT`](https://huggingface.co/litert-community/Qwen3-Embedding-0.6B-LiteRT):
**embed β†’ retrieve top-k β†’ rerank**, all on-device, no server.
![On-device RAG reranking on a Pixel 8a](hero.png)
Like the embedder it is a **single forward pass** (no generation, no KV cache) β†’ a plain `.tflite`, not
a `.litertlm`. Verified on a **Pixel 8a / Tensor G3**: all nodes on the GPU delegate, `P(yes)` parity
**ref 0.9995 / dev 0.9994** vs the HF fp32 reference.
## Files
| file | purpose | runs on |
|---|---|---|
| `qwen3rerank_gpu_fp16.tflite` | 28-layer Qwen3 decoder + baked 2-logit head, `inputs_embeds[1,256,1024] β†’ logits[1,256,2]` | **GPU** |
| `embeddings_fp16.bin` | tied token-embedding table `[151669,1024]` fp16, for the host-side lookup | host |
| `vocab.json`, `merges.txt` | Qwen byte-level BPE tokenizer | host |
## How it scores
```
prompt = PREFIX + "<Instruct>:… <Query>:… <Document>:…" + SUFFIX (Qwen3-Reranker template)
β†’[host embed lookup]β†’ inputs_embeds[1,256,1024]
β†’[GPU: 28-layer decoder + 2-logit head]β†’ logits[1,256,2]
β†’[softmax over (no,yes) at the last token]β†’ P(yes) = relevance
```
The 2-logit head bakes the tied-embedding rows for `"no"` (2152) and `"yes"` (9693), so the graph
emits `[no,yes]` directly. The host **right-pads and pools the last real token** (causal β‡’ it never
sees the trailing pad, identical to the official left-pad + attention-mask). Token embedding is a
GATHER (GPU-banned) so it is done host-side.
The GPU-clean re-authoring is the same as the embedder (host-embed, GQA `cat`-repeat to avoid
`BROADCAST_TO`, **max-normalized RMSNorm** for the deep-stack fp16 overflow, baked RoPE / causal mask).
## Minimal usage
**Python (reference score with the original model):**
```python
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
tok = AutoTokenizer.from_pretrained("Qwen/Qwen3-Reranker-0.6B")
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-Reranker-0.6B").eval()
yes, no = tok.convert_tokens_to_ids("yes"), tok.convert_tokens_to_ids("no")
# … build the PREFIX/SUFFIX prompt, then:
logits = model(**inputs).logits[:, -1, :]
score = torch.softmax(torch.stack([logits[:, no], logits[:, yes]], 1), 1)[:, 1] # P(yes)
```
**Kotlin (on-device, LiteRT `CompiledModel` GPU):**
```kotlin
val model = CompiledModel.create("qwen3rerank_gpu_fp16.tflite",
CompiledModel.Options(Accelerator.GPU), null)
// host: build prompt ids -> lookup embeddings_fp16.bin -> inputs_embeds[1,256,1024]
inputs[0].writeFloat(embedLookup(promptIds(query, doc)))
model.run(inputs, outputs)
val logits = outputs[0].readFloat() // [256,2] = [no,yes] per position
val score = softmaxYes(logits, poolPos) // P(yes) relevance
```
Full tokenizer + prompt template + reranking app: see the official LiteRT sample.
## Conversion
Reproducible in the official sample's `conversion/` (`build_qwen3rerank.py`, `export_embeddings.py`,
device-parity harness).