Instructions to use n-bor1sov/RuModernBERT-base-reranker-ru with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- sentence-transformers
How to use n-bor1sov/RuModernBERT-base-reranker-ru with sentence-transformers:
from sentence_transformers import CrossEncoder model = CrossEncoder("n-bor1sov/RuModernBERT-base-reranker-ru") query = "Which planet is known as the Red Planet?" passages = [ "Venus is often called Earth's twin because of its similar size and proximity.", "Mars, known for its reddish appearance, is often referred to as the Red Planet.", "Jupiter, the largest planet in our solar system, has a prominent red spot.", "Saturn, famous for its rings, is sometimes mistaken for the Red Planet." ] scores = model.predict([(query, passage) for passage in passages]) print(scores) - Notebooks
- Google Colab
- Kaggle
RuModernBERT-base Reranker (Russian)
Cross-encoder reranker for high-precision Russian text ranking in two-stage retrieval pipelines. Fine-tuned on RuModernBERT-base (150M parameters) as part of a bachelor thesis on contextual cross-encoders for Russian IR.
Use case: rerank BM25 (or other first-stage) top-k candidates by scoring query–passage pairs.
Model summary
| Architecture | Cross-encoder (ModernBertForSequenceClassification) |
| Base model | deepvk/RuModernBERT-base |
| Parameters | 150M |
| Max input length | 512 tokens |
| Languages | Russian (primary); some English training data |
| Checkpoint | Step 4914 (best validation MAP) |
Training
- Corpus: multi-source Russian retrieval data (11 sources + selected English domain data), ~6.57M pairs after consistency filtering (k=30) with Qwen3-Embedding-0.6B
- Hard negatives: 8 mined + 2 random in-batch per query (8HN+2RN)
- Loss: Multiple Negatives Ranking Loss (scale 10.0),
sentence-transformers5.2.2 - Optimizer: AdamW, lr
1e-5, weight decay0.01, cosine schedule, 5% warmup - Batch size: 256 per device (128 with gradient caching), bf16 + TF32
- Selection: early stopping on validation MAP (~1.3 epochs)
Evaluation (rusBEIR)
Macro-average over 9 out-of-distribution rusBEIR datasets (BM25 top-100 → cross-encoder rerank):
| Metric | Score |
|---|---|
| NDCG@10 | 0.560 |
| MAP@10 | 0.432 |
| MRR@10 | 0.597 |
| P@3 | 0.311 |
Compared to off-the-shelf baselines on the same benchmark: beats Qwen3-Reranker-0.6B (596M) on all reported metrics; within ~3 pp NDCG@10 of bge-reranker-v2-m3 (568M) at ~4× fewer parameters.
Usage
pip install -U sentence-transformers
from sentence_transformers import CrossEncoder
model = CrossEncoder("n-bor1sov/RuModernBERT-base-reranker-ru")
query = "Куда подавать апелляцию, если дело рассматривал Головинский районный суд?"
candidates = [
"Жалобу подают в Мосгорсуд через Головинский районный суд.",
"Апелляционную жалобу на заочное решение подают в районный суд.",
]
scores = model.predict([[query, doc] for doc in candidates])
ranked = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)
For a single query against many passages, use model.rank(query, candidates).
Inference with vLLM
vLLM serves the model as a scoring (cross-encoder) model with a
Jina/Cohere-compatible rerank API. Scores already include the sigmoid and
match sentence-transformers output.
pip install -U vllm
# vLLM < 0.10: replace `--runner pooling` with `--task score`
vllm serve n-bor1sov/RuModernBERT-base-reranker-ru \
--runner pooling --dtype bfloat16 --max-model-len 512
curl http://localhost:8000/v1/rerank -H "Content-Type: application/json" -d '{
"model": "n-bor1sov/RuModernBERT-base-reranker-ru",
"query": "Куда подавать апелляцию, если дело рассматривал Головинский районный суд?",
"documents": [
"Жалобу подают в Мосгорсуд через Головинский районный суд.",
"Апелляционную жалобу на заочное решение подают в районный суд."
]
}'
# → {"results": [{"index": 0, "relevance_score": 0.84, ...}, ...]}
For offline batch scoring in Python:
from vllm import LLM
llm = LLM(model="n-bor1sov/RuModernBERT-base-reranker-ru",
runner="pooling", dtype="bfloat16", max_model_len=512)
outputs = llm.score(query, candidates)
scores = [o.outputs.score for o in outputs]
The model was trained with 512-token inputs. Requests longer than
--max-model-len are rejected, so truncate long passages on the client side.
Inference with Triton
Export the model to ONNX and serve it with Triton's onnxruntime backend.
Tokenization runs on the client.
1. Export (pip install torch transformers onnx, then
mkdir -p model_repository/ru_reranker/1):
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification
name = "n-bor1sov/RuModernBERT-base-reranker-ru"
tok = AutoTokenizer.from_pretrained(name)
model = AutoModelForSequenceClassification.from_pretrained(name, attn_implementation="eager").eval()
enc = tok(["запрос"], ["документ"], return_tensors="pt")
torch.onnx.export(
model, (enc["input_ids"], enc["attention_mask"]),
"model_repository/ru_reranker/1/model.onnx",
input_names=["input_ids", "attention_mask"], output_names=["logits"],
dynamic_axes={"input_ids": {0: "batch", 1: "seq"},
"attention_mask": {0: "batch", 1: "seq"},
"logits": {0: "batch"}},
opset_version=17, dynamo=False,
)
2. Model repository:
model_repository/
└── ru_reranker/
├── config.pbtxt
└── 1/model.onnx
config.pbtxt:
name: "ru_reranker"
backend: "onnxruntime"
max_batch_size: 64
input [
{ name: "input_ids", data_type: TYPE_INT64, dims: [ -1 ] },
{ name: "attention_mask", data_type: TYPE_INT64, dims: [ -1 ] }
]
output [
{ name: "logits", data_type: TYPE_FP32, dims: [ 1 ] }
]
instance_group [ { kind: KIND_GPU, count: 1 } ]
dynamic_batching { max_queue_delay_microseconds: 1000 }
3. Run the server:
docker run --gpus all --rm -p 8000:8000 -p 8001:8001 \
-v $PWD/model_repository:/models \
nvcr.io/nvidia/tritonserver:24.12-py3 \
tritonserver --model-repository=/models
4. Client (pip install tritonclient[http] transformers numpy):
import numpy as np
import tritonclient.http as httpclient
from transformers import AutoTokenizer
tok = AutoTokenizer.from_pretrained("n-bor1sov/RuModernBERT-base-reranker-ru")
client = httpclient.InferenceServerClient("localhost:8000")
def rerank(query, docs):
enc = tok([query] * len(docs), docs, padding=True, truncation=True,
max_length=512, return_tensors="np")
inputs = []
for name in ("input_ids", "attention_mask"):
arr = enc[name].astype(np.int64)
inp = httpclient.InferInput(name, arr.shape, "INT64")
inp.set_data_from_numpy(arr)
inputs.append(inp)
logits = client.infer("ru_reranker", inputs).as_numpy("logits")[:, 0]
return 1 / (1 + np.exp(-logits)) # same scores as CrossEncoder.predict
Send at most max_batch_size (64) pairs per request. Dynamic batching
only merges requests whose padded lengths match. Padding to a fixed
max_length=512 lets it merge every request, but makes short inputs
slower.
Citation
If you use this model, please cite the thesis:
@thesis{dydalin2026russianreranker,
author = {Dydalin, Dmitrii Sergeevich},
title = {A Contextual Cross-Encoder for High-Precision Textual Reranking in Russian Language Information Retrieval},
school = {Innopolis University},
year = {2026},
type = {Bachelor's thesis},
}
Base model: RuModernBERT-base. Training library: Sentence Transformers.
- Downloads last month
- 13
Model tree for n-bor1sov/RuModernBERT-base-reranker-ru
Base model
deepvk/RuModernBERT-base