multi-qa-mpnet-base is a sentence-transformers model built on the MPNet base encoder (12 layers, 768 hidden) and fine-tuned on a large collection of question-answer pairs from sources such as search logs, forums and QA datasets. It produces 768-dimensional vectors and is designed for asymmetric semantic search: a short query against longer passages, the retrieval step of most RAG systems.
It sits between the compact MiniLM (384 dims) and hosted models such as ada-002 (1536 dims) on the size/quality curve.
MPNet (Song et al., 2020) unifies BERT's masked language modelling and XLNet's permuted language modelling. MLM predicts masked tokens independently of each other; PLM models dependencies between predicted tokens but each prediction lacks the position information of the full sentence. MPNet permutes the prediction order while also feeding position embeddings for all tokens, so the model sees the full sentence length during pretraining just as it will at fine-tuning time.
Symmetric similarity (paraphrase detection) compares texts of the same kind. Retrieval is asymmetric: "how do vaccines work?" should match a paragraph explaining immune response, which shares few words with the question. Training on (question, answer) pairs with an in-batch-negatives contrastive loss teaches the encoder to place questions near their answers rather than near other similar questions.
The two common checkpoints differ in pooling and normalization. Configure your vector database to match: inner product for dot-v1, cosine (or inner product on normalized vectors) for cos-v1. Using cosine on a dot-trained model discards the vector-length signal; using raw dot product on mean-pooled unnormalized vectors favours long inputs.
# pip install sentence-transformers
from sentence_transformers import SentenceTransformer, util
model = SentenceTransformer("sentence-transformers/multi-qa-mpnet-base-dot-v1")
print(model.get_sentence_embedding_dimension()) # 768
passages = [
"Vaccines train the immune system by exposing it to a harmless antigen.",
"The Eiffel Tower was completed in 1889 for the World's Fair.",
"mRNA vaccines deliver instructions for cells to make a spike protein.",
]
P = model.encode(passages, convert_to_tensor=True) # dot-v1: do NOT normalise
q = model.encode("how do vaccines work?", convert_to_tensor=True)
scores = util.dot_score(q, P)[0]
for i in scores.argsort(descending=True):
print(f"{scores[i]:.2f} {passages[i]}")
# Long documents: chunk first (stay well under the max sequence length)
def chunk(text, words=200, overlap=40):
w = text.split()
return [" ".join(w[i:i + words]) for i in range(0, max(len(w) - overlap, 1), words - overlap)]
The model accepts up to 512 wordpieces, but it was trained on shorter text, so passages of roughly 100–300 words with some overlap usually retrieve best. A GPU speeds up bulk indexing substantially; query-time encoding is fine on CPU.
The fine-tuning objective is the multiple-negatives ranking loss (an in-batch contrastive loss related to InfoNCE). A batch contains B pairs (qi, ai). Every question is encoded, every answer is encoded, and a B × B score matrix Sij = sim(qi, aj) is built. The loss is softmax cross-entropy where the correct "class" for row i is column i, so each question must score its own answer above the B − 1 other answers in the batch. Larger batches mean more negatives per step and generally better embeddings, which is why these models are trained with large batches on many accelerators.
Because questions and answers share one encoder, the model learns a single space where a short question and a long passage can be close. Pooling turns the per-token outputs into one vector: the cos variant averages token vectors and L2-normalizes them; the dot variant uses the [CLS] token and leaves length unnormalized, so the score can also reflect how much relevant content a passage carries.
A small worked example. With normalized vectors, cosine and dot product are the same number. If a query vector scores 0.62 against passage A and 0.41 against passage B, A ranks first. Those absolute values are not probabilities and are not comparable across models; a threshold such as "accept above 0.5" must be calibrated on labeled pairs from your own data.
In a retrieval-augmented generation system the model sits in two places. Offline, every document chunk is encoded once and written to a vector index such as FAISS, pgvector or a managed vector database. Online, each user question is encoded with the same model and the index returns the nearest chunks. Because the bi-encoder never sees query and passage together, it misses fine-grained interactions; a cross-encoder reranker that reads both texts jointly is commonly added on the top 50 to 100 candidates. Combining dense scores with BM25 in a hybrid search recovers exact-match terms such as product codes, names and error strings that dense vectors tend to blur.
| Concern | Guidance |
|---|---|
| Storage | 768 float32 values = 3,072 bytes per vector before index overhead. One million chunks is about 3 GB raw. float16 halves it; product quantization or int8 reduce it further with some recall loss. |
| Throughput | Batch encoding (32 to 256 texts) on a GPU for indexing; for CPU serving, ONNX export or int8 quantization of the encoder reduces latency. |
| Versioning | Vectors from different models, or the dot and cos variants, are not interchangeable. Store the model name with each index and re-embed the whole corpus on any model change. |
| Chunking | Split on headings and paragraphs, keep a little overlap, and prepend a title or section name so short chunks carry context. |
| Truncation | Text beyond the configured maximum sequence length is cut silently. Check model.max_seq_length and the token count of your longest chunks. |
Build a small labeled set: a few hundred real user questions, each with the passages that answer it. Measure recall@k (did any correct passage appear in the top k?), mean reciprocal rank, and nDCG@10. Compare against BM25 alone, MiniLM, and a hosted model on the same set; public leaderboards such as MTEB are a starting point, but domain vocabulary often reorders the ranking. Re-run the evaluation whenever chunking, the model, or the index parameters change. For end-to-end answer quality in a RAG system, see RAG evaluation with RAGAS.
If evaluation shows the model failing on your domain (legal citations, internal product names, medical terminology), fine-tuning on a few thousand in-domain (query, relevant passage) pairs with the same multiple-negatives loss often helps. Pairs can come from search click logs, FAQ pages, or questions generated by an LLM from your documents and then spot-checked. Mine hard negatives (passages that BM25 ranks highly but are wrong) to make training more informative, hold out an evaluation set before training, and confirm the tuned model beats the base model on it.
Further reading. Song et al. (2020), "MPNet: Masked and Permuted Pre-training for Language Understanding", arXiv:2004.09297; Reimers and Gurevych (2019), "Sentence-BERT", arXiv:1908.10084; Henderson et al. (2017), "Efficient Natural Language Response Suggestion for Smart Reply" (multiple-negatives loss); Karpukhin et al. (2020), "Dense Passage Retrieval for Open-Domain Question Answering"; Muennighoff et al. (2022), "MTEB: Massive Text Embedding Benchmark"; the sentence-transformers documentation at sbert.net.