Views
No views yet
| File | Format | Size | Description |
|---|---|---|---|
model.onnx + model.onnx_data | FP32 | ~3.4 GB | Full precision (dynamic-batch, attention-mask broadcast patched) |
model_int8.onnx + model_int8.onnx_data | INT8 | ~2.7 GB | Weight-only INT8 (per-tensor symmetric, batch-broadcast patched) |
model_int4_full.onnx | INT4 | ~1.4 GB | MatMulNBits INT4, block_size=32, batch-broadcast patched |
model_int4_full.unpatched.onnx | INT4 | ~1.4 GB | Archival: original INT4 export with hardcoded batch=1 (use only for batch=1 inference) |
export_zerank_v2.py (FP32 export with dynamic batch), stream_int8.py (INT8 quantization).batch=1 in the attention-mask And kernel:
ONNX Runtime would crash at any batch>1 with Shape mismatch attempting to re-use buffer. {1,1,T,T} != {B,1,T,T}. The patch inserts Expand + Shape nodes before the And so the mask broadcasts dynamically. INT4 was re-uploaded on 2026-05-03 with the patch applied; the original unpatched INT4 is preserved at model_int4_full.unpatched.onnx for archival. Validated under fastembed-rs' reranker_parity harness — Spearman 0.99 vs FP32 reference, top-1 match across the 4 reference query groups.system=query, user=document:1# using the tokenizer directly (matches training format exactly):
2messages = [
3 {"role": "system", "content": query},
4 {"role": "user", "content": document},
5]
6text = tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)<|im_start|>system
{query}
<|im_end|>
<|im_start|>user
{document}
<|im_end|>
<|im_start|>assistant1import onnxruntime as ort
2import numpy as np
3from transformers import AutoTokenizer
4
5MODEL_PATH = "model_int8.onnx" # or model.onnx, model_int4_full.onnx
6MAX_LENGTH = 512
7
8sess = ort.InferenceSession(MODEL_PATH, providers=["CPUExecutionProvider"])
9tok = AutoTokenizer.from_pretrained("cstr/zerank-1-small-ONNX")
10
11def format_pair(query: str, doc: str) -> str:
12 messages = [
13 {"role": "system", "content": query},
14 {"role": "user", "content": doc},
15 ]
16 return tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
17
18def rerank(query: str, documents: list[str]) -> list[float]:
19 scores = []
20 for doc in documents:
21 text = format_pair(query, doc)
22 enc = tok(text, return_tensors="np", truncation=True, max_length=MAX_LENGTH)
23 logit = sess.run(["logits"], {
24 "input_ids": enc["input_ids"].astype(np.int64),
25 "attention_mask": enc["attention_mask"].astype(np.int64),
26 })[0]
27 scores.append(float(logit[0, 0]))
28 return scores
29
30query = "What is a panda?"
31docs = [
32 "The giant panda is a bear species endemic to China.",
33 "The sky is blue and the grass is green.",
34 "Pandas are mammals in the family Ursidae.",
35]
36scores = rerank(query, docs)
37for s, d in sorted(zip(scores, docs), reverse=True):
38 print(f"[{s:.3f}] {d}")
39# [+6.8] The giant panda is a bear species endemic to China.
40# [+2.1] Pandas are mammals in the family Ursidae.
41# [-5.8] The sky is blue and the grass is green.Batch inference: The v2 export (model.onnx) supportsbatch_size > 1via a dynamic causal+padding mask. Pad a batch with the tokenizer and pass the full batch at once for higher throughput.
1use fastembed::{RerankInitOptions, RerankerModel, TextRerank};
2
3let mut reranker = TextRerank::try_new(
4 RerankInitOptions::new(RerankerModel::ZerankSmallInt8)
5).unwrap();
6
7// The chat template is applied automatically; batch_size > 1 is supported.
8let results = reranker.rerank(
9 "What is a panda?",
10 vec![
11 "The giant panda is a bear species endemic to China.",
12 "The sky is blue.",
13 "Pandas are mammals in the family Ursidae.",
14 ],
15 true,
16 Some(32),
17).unwrap();
18
19for r in &results {
20 println!("[{:.3}] {}", r.score, r.document.as_ref().unwrap());
21}export_zerank_v2.py wraps Qwen3ForCausalLM in a ZeRankScorerV2 that:input_ids.shape[0] — this makes the batch dimension dynamic in the ONNX graph (enabling batch_size > 1).hidden [batch, seq, hidden]attention_mask.sum - 1)lm_head, slices the "Yes" token (id 9454) → [batch, 1]logits [batch, 1] — raw Yes-token logit (higher = more relevant). FP16 weights, opset 18.stream_int8.py performs fully streaming weight-only INT8 quantization:scale = max(|w|) / 127DequantizeLinear → MatMul nodes for all MatMul B-weightstext-embedding-3-small as initial retriever (Top 100 candidates):| Task | Embedding only | cohere-rerank-v3.5 | Llama-rank-v1 | zerank-1-small | zerank-1 |
|---|---|---|---|---|---|
| Code | 0.678 | 0.724 | 0.694 | 0.730 | 0.754 |
| Finance | 0.839 | 0.824 | 0.828 | 0.861 | 0.894 |
| Legal | 0.703 | 0.804 | 0.767 | 0.817 | 0.821 |
| Medical | 0.619 | 0.750 | 0.719 | 0.773 | 0.796 |
| STEM | 0.401 | 0.510 | 0.595 | 0.680 | 0.694 |
| Conversational | 0.250 | 0.571 | 0.484 | 0.556 | 0.596 |
zeroentropy.apache-2.0. This repository redistributes under the same terms; it grants no rights the upstream licence does not.