Standard dense retrieval models score query–passage pairs using a single semantic similarity signal, giving users no control over
what "relevant" means beyond keyword choice.
Promptriever (
Weller et al., 2024) introduced per-instance natural language instructions that dynamically redefine relevance — a capability previously limited to generative LLMs.
This is the
Russian-only continued training variant. Starting from
ru-Promptriever-4B-pretrained (trained on synthetic data only), it was further fine-tuned on a mix of:
Adding real retrieval data (not just synthetic) improved both nDCG and p-MRR compared to the pretrained model. However, the
final model with additional English instruction-following data achieves even higher p-MRR.
Russian split of
mFollowIR — multilingual instruction-following retrieval using TREC NeuCLIR narratives as instructions.
1import torch
2from transformers import AutoTokenizer, AutoModelForCausalLM
3import torch.nn.functional as F
4
5model_name = "Vladimirlv/ru-promptriever-qwen3-4b-ru-only"
6tokenizer = AutoTokenizer.from_pretrained(model_name)
7model = AutoModelForCausalLM.from_pretrained(
8 model_name,
9 torch_dtype=torch.bfloat16,
10 device_map="auto",
11)
12model.eval()
13
14def encode(texts: list[str], max_length: int = 512) -> torch.Tensor:
15 """Encode texts using last-token (EOS) pooling."""
16 inputs = tokenizer(
17 texts,
18 padding=True,
19 truncation=True,
20 max_length=max_length,
21 return_tensors="pt",
22 ).to(model.device)
23
24 with torch.no_grad():
25 # Bypass lm_head to get post-norm hidden states
26 original_lm_head = model.lm_head
27 model.lm_head = torch.nn.Identity()
28 outputs = model(**inputs, use_cache=False, return_dict=True)
29 model.lm_head = original_lm_head
30
31 # EOS pooling: take embedding at last non-padding token
32 seq_len = inputs["attention_mask"].sum(dim=1) - 1
33 embeddings = outputs.logits[torch.arange(len(texts)), seq_len]
34 return F.normalize(embeddings, p=2, dim=1)
35
36
37query = "Когда была основана Москва?"
38passages = [
39 "Москва была основана в 1147 году князем Юрием Долгоруким.",
40 "Санкт-Петербург был основан Петром I в 1703 году.",
41]
42
43q_emb = encode([query])
44p_emb = encode(passages)
45scores = (q_emb @ p_emb.T).squeeze()
46print(scores) # tensor([0.82, 0.61])
1# Append the instruction directly to the query (same format as training)
2instruction = "Найди документ, в котором упоминается конкретная дата основания города."
3instructed_query = f"{query} {instruction}"
4
5q_emb = encode([instructed_query])
6p_emb = encode(passages)
7scores = (q_emb @ p_emb.T).squeeze()
8# The model adjusts rankings based on the instruction
The non-commercial restriction is inherited from the upstream
MS MARCO license (Microsoft Research License — non-commercial use only), which governs the training corpus.
1@article{weller2024promptriever,
2 title = {Promptriever: Instruction-Trained Retrievers Can Be Prompted Like Language Models},
3 author = {Weller, Orion and Van Durme, Benjamin and Lawrie, Dawn and
4 Paranjape, Ashwin and Zhang, Yuhao and Hessel, Jack},
5 journal = {arXiv preprint arXiv:2409.11136},
6 year = {2024}
7}