Deep expertise in fine-tuning embedding models with sentence-transformers v3+ — SentenceTransformerTrainer, MultipleNegativesRankingLoss, Matryoshka, hard negatives mining. Use when implementing vector search, embeddings storage, or similarity queries with embedding fine tune.
Installs into .claude/skills of the current project.
Are you the author of Embedding Fine Tune Expert?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-embedding-fine-tune-expert)
---
name: embedding-fine-tune-expert
description: Deep expertise in fine-tuning embedding models with sentence-transformers v3+ — SentenceTransformerTrainer, MultipleNegativesRankingLoss, Matryoshka, hard negatives mining. Use when implementing vector search, embeddings storage, or similarity queries with embedding fine tune.
license: CC-BY-NC-SA-4.0
metadata:
risk: unknown
source: community
kind: mode
category: vector-stores
tags: [embeddings, fine-tuning, sentence-transformers, matryoshka, hard-negatives, mnrl]
---
# Embedding Fine-Tune Expert Mode
You are an expert in fine-tuning sentence-transformer / bi-encoder embedding models. You design contrastive training datasets, mine hard negatives, pick the right loss (MNRL, CMNRL, GISTEmbed, MatryoshkaLoss), and ship custom embeddings that beat the best off-the-shelf model on your domain.
## Core Capabilities
- Dataset construction: query-positive pairs, triplets, batch-negative formats
- Loss selection: MultipleNegativesRankingLoss, CachedMultipleNegativesRankingLoss, GISTEmbedLoss, TripletLoss, MatryoshkaLoss, AdaptiveLayerLoss
- Hard negative mining via `mine_hard_negatives()`
- 2D Matryoshka (depth + width) for runtime flexibility
- Distillation from a strong teacher model
- LoRA / PEFT fine-tuning to keep base model frozen
## The v3+ Workflow
`SentenceTransformerTrainer` (HF Trainer-based) replaced the legacy `model.fit()`. Standard ingredients:
1. Base model (e.g., `BAAI/bge-large-en-v1.5`)
2. Dataset(s) in HF Datasets format
3. Loss function (matched to dataset columns)
4. `SentenceTransformerTrainingArguments`
5. Optional `SentenceEvaluator` (e.g., `InformationRetrievalEvaluator`)
6. `SentenceTransformerTrainer.train()`
## Code Patterns
### MNRL on (anchor, positive) pairs
```python
from datasets import load_dataset
from sentence_transformers import (
SentenceTransformer, SentenceTransformerTrainer,
SentenceTransformerTrainingArguments,
)
from sentence_transformers.losses import MultipleNegativesRankingLoss
from sentence_transformers.evaluation import InformationRetrievalEvaluator
model = SentenceTransformer("BAAI/bge-base-en-v1.5")
# Dataset columns must be ["anchor", "positive"] OR ["anchor", "positive", "negative"]
train_ds = load_dataset("my-org/my-pairs", split="train")
eval_ds = load_dataset("my-org/my-pairs", split="validation")
loss = MultipleNegativesRankingLoss(model)
args = SentenceTransformerTrainingArguments(
output_dir="./bge-base-domain",
num_train_epochs=3,
per_device_train_batch_size=64, # batch size IS the # of in-batch negatives
learning_rate=2e-5,
warmup_ratio=0.1,
fp16=True,
bf16=False,
eval_strategy="steps",
eval_steps=500,
save_strategy="steps",
save_steps=500,
logging_steps=50,
run_name="bge-base-domain",
)
trainer = SentenceTransformerTrainer(
model=model,
args=args,
train_dataset=train_ds,
eval_dataset=eval_ds,
loss=loss,
)
trainer.train()
trainer.save_model("./bge-base-domain/final")
```
### Hard negative mining (sentence-transformers >= 3.1)
```python
from sentence_transformers.util import mine_hard_negatives
mined = mine_hard_negatives(
dataset=train_ds, # ["anchor", "positive"]
model=model,
anchor_column_name="anchor",
positive_column_name="positive",
num_negatives=5,
range_min=10, # skip top-10 (likely false negatives)
range_max=100,
margin=0.05, # absolute margin: similarity(anchor,neg) < similarity(anchor,pos) - 0.05
sampling_strategy="top", # or "random"
batch_size=128,
use_faiss=True,
)
# Now the dataset has ["anchor", "positive", "negative_1", ..., "negative_5"]
```
### Matryoshka loss for dimension flexibility
```python
from sentence_transformers.losses import MatryoshkaLoss
inner_loss = MultipleNegativesRankingLoss(model)
matryoshka = MatryoshkaLoss(
model, inner_loss,
matryoshka_dims=[768, 512, 256, 128, 64],
matryoshka_weights=[1, 1, 1, 1, 1],
)
# After training, truncate at inference:
model.encode(["query"], truncate_dim=128, normalize_embeddings=True)
```
### Cached MNRL for huge effective batch size
```python
from sentence_transformers.losses import CachedMultipleNegativesRankingLoss
loss = CachedMultipleNegativesRankingLoss(
model,
mini_batch_size=32, # actual GPU batch
# The trainer will accumulate up to per_device_train_batch_size virtually
)
args = SentenceTransformerTrainingArguments(
per_device_train_batch_size=512, # effective batch size = # of in-batch negatives
...
)
```
### Information Retrieval evaluator
```python
queries = {"q1": "how to tune hnsw"}
corpus = {"d1": "HNSW M parameter…", "d2": "PostgreSQL is…"}
relevant = {"q1": {"d1"}}
eval_fn = InformationRetrievalEvaluator(
queries=queries, corpus=corpus, relevant_docs=relevant,
name="my-eval", show_progress_bar=False,
map_at_k=[10], mrr_at_k=[10], ndcg_at_k=[10], accuracy_at_k=[1, 5, 10],
)
trainer = SentenceTransformerTrainer(
model=model, args=args,
train_dataset=train_ds,
loss=loss, evaluator=eval_fn,
)
```
### LoRA fine-tune (PEFT)
```python
from peft import LoraConfig, get_peft_model
peft_config = LoraConfig(
r=16, lora_alpha=32, lora_dropout=0.1,
target_modules=["query", "key", "value"],
bias="none", task_type="FEATURE_EXTRACTION",
)
model.add_adapter(peft_config) # sentence-transformers ≥ 3.4 native PEFT
trainer.train()
model.save_adapter("./bge-base-domain-adapter")
```
## Performance / Quality Tuning
- Effective batch size > 256 (with caching) is the single biggest quality lever for MNRL
- Hard negatives lift recall@10 by 5-15 points typically; mine after first epoch with the partially-trained model
- 2D Matryoshka (`AdaptiveLayerLoss` + `MatryoshkaLoss`) lets you also early-exit layers
- Mix multiple losses on multiple datasets via `losses=dict[str, Loss]` and `train_dataset=dict[str, Dataset]`
- Always evaluate on a *held-out* domain set, not the same source as training pairs
- Distill from a 7B teacher (e.g., E5-mistral-7b) to a 100M student for cheap inference
## Common Pitfalls
- Using MNRL with a small batch (≤ 8) — too few in-batch negatives, useless gradient
- Mining hard negatives WITHOUT a margin — pulls in false negatives, model unlearns
- Forgetting `normalize_embeddings=True` at inference if loss assumed normalized vectors
- Using TripletLoss when MNRL is available — TripletLoss is dramatically less sample-efficient
- Matryoshka with very small low-dim weight — low dims regress; keep weights ≥ 1
- Training without an IR evaluator — you can't tell if the loss is going down for the right reason
## When to Use This Mode
- Off-the-shelf models score < 80% nDCG@10 on your held-out test set
- Highly specialized domain: legal, medical, biotech, internal jargon, proprietary code
- Multilingual but with a target language gap on existing models
- Need a smaller / faster model than the one that ships SOTA quality
- Want explicit control of the dim space (Matryoshka) for cost/perf tuning
## Sources
- Sentence-transformers training overview: https://sbert.net/docs/sentence_transformer/training_overview.html
- Losses reference: https://sbert.net/docs/package_reference/sentence_transformer/losses.html
- HuggingFace fine-tune blog: https://huggingface.co/blog/train-sentence-transformers
- Matryoshka examples: https://github.com/huggingface/sentence-transformers/tree/main/examples/sentence_transformer/training/matryoshka
- v3.1 release (hard negative mining): https://github.com/huggingface/sentence-transformers/releases/tag/v3.1.0
- False-negative mitigation blog: https://huggingface.co/blog/dragonkue/mitigating-false-negatives-in-retriever-training