Bi-encoder retrieval models learn by separating a matching text pair from the alternatives visible during training. MultipleNegativesRankingLoss turns the other positive texts in each batch into in-batch negatives, so a curated set of anchor-positive pairs can fine-tune a retriever without a separate negative column.

The active Python environment needs the current sentence-transformers[train] extra, which supplies datasets and accelerate for SentenceTransformerTrainer. The training workflow uses eight unique support query-answer pairs so every batch contains distinct candidate answers.

Batch size controls how many in-batch negatives each anchor sees. Use BatchSamplers.NO_DUPLICATES to keep repeated text from becoming a false negative, and consider CachedMultipleNegativesRankingLoss when a larger effective batch must fit within limited device memory.

Steps to train with multiple negatives ranking loss in Sentence Transformers:

  1. Create train_mnrl.py with the initial model setup.
    train_mnrl.py
    from datasets import Dataset
    from sentence_transformers import (
        SentenceTransformer,
        SentenceTransformerTrainer,
        SentenceTransformerTrainingArguments,
    )
    from sentence_transformers.sentence_transformer import losses
    from sentence_transformers.sentence_transformer.training_args import BatchSamplers
     
     
    model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
    output_dir = "support-mnrl-model"
  2. Append the unique anchor-positive pairs below the output path.
    train_dataset = Dataset.from_dict(
        {
            "anchor": [
                "reset a locked account",
                "reset a user password",
                "enable two-factor authentication",
                "rotate an API token",
                "restore a deleted project",
                "invite a new team member",
                "export audit logs",
                "change the billing contact",
            ],
            "positive": [
                "Unlock the account from the admin users page.",
                "Open the user profile and send a password reset email.",
                "Open account security and enroll an authenticator app.",
                "Revoke the old API token and create a replacement token.",
                "Open deleted projects and restore the selected project.",
                "Open team settings and send an invitation email.",
                "Open compliance reports and export the audit log CSV.",
                "Open billing settings and update the primary contact.",
            ],
        }
    )

    Each positive becomes a negative candidate for the other anchors in its batch. Duplicate or equivalent texts therefore create false negatives by presenting the same meaning as an incorrect answer.

  3. Append the MNRL training configuration below the dataset.
    loss = losses.MultipleNegativesRankingLoss(model)
    args = SentenceTransformerTrainingArguments(
        output_dir="mnrl-checkpoints",
        num_train_epochs=1,
        per_device_train_batch_size=4,
        learning_rate=2e-5,
        warmup_steps=0.1,
        batch_sampler=BatchSamplers.NO_DUPLICATES,
        save_strategy="no",
        logging_steps=1,
        disable_tqdm=True,
        report_to=[],
    )

    A batch size of four gives each anchor three in-batch alternatives in this small run. Larger batches expose more negatives but require more memory unless the cached loss variant is used.

  4. Append the trainer execution block below the training arguments.
    trainer = SentenceTransformerTrainer(
        model=model,
        args=args,
        train_dataset=train_dataset,
        loss=loss,
    )
    trainer.train()
    model.save_pretrained(output_dir)
     
    print(f"training rows: {len(train_dataset)}")
    print(f"loss: {loss.__class__.__name__}")
    print(f"saved model: {output_dir}")
  5. Run the completed training script from its working directory.
    $ python3 train_mnrl.py
    {'loss': '0.0005047', 'grad_norm': '0.04047', 'learning_rate': '0', 'epoch': '0.5'}
    {'loss': '0.006253', 'grad_norm': '0.7316', 'learning_rate': '2e-05', 'epoch': '1'}
    ##### snipped #####
    training rows: 8
    loss: MultipleNegativesRankingLoss
    saved model: support-mnrl-model
  6. Create verify_mnrl.py for a saved-model retrieval check.
    verify_mnrl.py
    from sentence_transformers import SentenceTransformer
     
     
    model = SentenceTransformer("support-mnrl-model")
    query = "How can I replace an API token?"
    documents = [
        "Revoke the old API token and create a replacement token.",
        "Open billing settings and update the primary contact.",
        "Open deleted projects and restore the selected project.",
    ]
     
    query_embedding = model.encode([query])
    document_embeddings = model.encode(documents)
    scores = model.similarity(query_embedding, document_embeddings)[0]
    best_index = scores.argmax().item()
     
    print(f"embedding width: {model.get_embedding_dimension()}")
    print(f"top document: {documents[best_index]}")
    print(f"top score: {float(scores[best_index]):.4f}")
     
    if best_index != 0:
        raise SystemExit("The matching API-token answer did not rank first.")
  7. Run the verifier to confirm the saved model ranks the matching API-token answer first.
    $ python3 verify_mnrl.py
    embedding width: 384
    top document: Revoke the old API token and create a replacement token.
    top score: 0.8125