Feature Extraction
sentence-transformers
Safetensors
English
bert
multi-vector
colbert
late-interaction
Generated from Trainer
dataset_size:501907
loss:MultiVectorMultipleNegativesRankingLoss
Eval Results (legacy)
text-embeddings-inference
Instructions to use multi-vector-encoder-testing/bert-tiny-multi-vector with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- sentence-transformers
How to use multi-vector-encoder-testing/bert-tiny-multi-vector with sentence-transformers:
from sentence_transformers import SentenceTransformer model = SentenceTransformer("multi-vector-encoder-testing/bert-tiny-multi-vector") sentences = [ "The weather is lovely today.", "It's so sunny outside!", "He drove to the stadium." ] embeddings = model.encode(sentences) similarities = model.similarity(embeddings, embeddings) print(similarities.shape) # [3, 3] - Notebooks
- Google Colab
- Kaggle
| """Train BERT tiny, evaluating three NanoBEIR datasets every 20%. | |
| Adapted from the multi-vector training skill template and training_contrastive.py. | |
| Run from the repository root with Python and the training dependencies installed. | |
| Use --smoke-test for one step, or --push-to-hub to upload the best checkpoint. | |
| Use --long-run to continue the initial model for 10,000 steps on the full dataset. | |
| Run without --long-run first to create the local initial model. | |
| """ | |
| import argparse | |
| import json | |
| import logging | |
| import shutil | |
| from contextlib import nullcontext | |
| from pathlib import Path | |
| import torch | |
| from datasets import load_dataset | |
| from transformers import BertConfig, BertModel, BertTokenizer, TrainerCallback, set_seed | |
| from sentence_transformers import ( | |
| MultiVectorEncoder, | |
| MultiVectorEncoderModelCardData, | |
| MultiVectorEncoderTrainer, | |
| MultiVectorEncoderTrainingArguments, | |
| ) | |
| from sentence_transformers.base.modules import Dense, Normalize, Transformer | |
| from sentence_transformers.base.sampler import BatchSamplers | |
| from sentence_transformers.multi_vector_encoder.evaluation import MultiVectorNanoBEIREvaluator | |
| from sentence_transformers.multi_vector_encoder.losses import MultiVectorMultipleNegativesRankingLoss | |
| from sentence_transformers.multi_vector_encoder.modules import MultiVectorMask | |
| RUN_NAME = "bert-tiny-msmarco" | |
| REPO_ID = "multi-vector-encoder-testing/bert-tiny-multi-vector" | |
| class LogProgress(TrainerCallback): | |
| def on_log(self, args, state, control, logs=None, **kwargs): | |
| values = { | |
| key: value | |
| for key, value in (logs or {}).items() | |
| if key in ("loss", "learning_rate", "eval_loss", "eval_NanoBEIR_mean_maxsim_ndcg@10") | |
| } | |
| if values: | |
| logging.info("Step %s/%s: %s", state.global_step, state.max_steps, values) | |
| def autocast_ctx(): | |
| if not torch.cuda.is_available(): | |
| return nullcontext() | |
| dtype = torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16 | |
| return torch.autocast("cuda", dtype=dtype) | |
| def main(): | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument("--smoke-test", action="store_true") | |
| parser.add_argument("--push-to-hub", action="store_true") | |
| parser.add_argument("--long-run", action="store_true") | |
| cli = parser.parse_args() | |
| repo_id = REPO_ID | |
| run_name = RUN_NAME + ("-long" if cli.long_run else "") + ("-smoke" if cli.smoke_test else "") | |
| output_dir = Path("models") / run_name | |
| output_dir.mkdir(parents=True, exist_ok=True) | |
| Path("logs").mkdir(exist_ok=True) | |
| logging.basicConfig( | |
| format="%(asctime)s - %(message)s", | |
| level=logging.INFO, | |
| handlers=[logging.StreamHandler(), logging.FileHandler(f"logs/{run_name}.log", mode="w")], | |
| force=True, | |
| ) | |
| for noisy in ("httpx", "httpcore", "huggingface_hub", "urllib3", "filelock", "fsspec"): | |
| logging.getLogger(noisy).setLevel(logging.WARNING) | |
| set_seed(12) | |
| if torch.cuda.is_available(): | |
| torch.set_float32_matmul_precision("high") | |
| card = MultiVectorEncoderModelCardData( | |
| language="en", | |
| license="mit", | |
| model_name="BERT tiny multi-vector encoder trained on MS MARCO", | |
| model_id=repo_id, | |
| ) | |
| if cli.long_run: | |
| initial_model = Path("models") / RUN_NAME / "final" | |
| if not initial_model.is_dir(): | |
| parser.error("Run without --long-run first to create the local initial model.") | |
| model = MultiVectorEncoder(str(initial_model), model_card_data=card) | |
| model.model_card_data.set_base_model("prajjwal1/bert-tiny") | |
| else: | |
| # The original checkpoint lacks model_type, which recent AutoConfig versions require. | |
| base_dir = output_dir / "base" | |
| base_model = BertModel.from_pretrained( | |
| "prajjwal1/bert-tiny", config=BertConfig.from_pretrained("prajjwal1/bert-tiny") | |
| ) | |
| base_model.save_pretrained(base_dir) | |
| BertTokenizer.from_pretrained("prajjwal1/bert-tiny").save_pretrained(base_dir) | |
| del base_model | |
| transformer = Transformer( | |
| str(base_dir), | |
| query_length=32, | |
| document_length=256, | |
| query_expansion={"strategy": "min", "length": 32}, | |
| ) | |
| model = MultiVectorEncoder( | |
| modules=[ | |
| transformer, | |
| Dense(128, 128, bias=False, activation_function=None, module_input_name="token_embeddings"), | |
| MultiVectorMask(), | |
| Normalize(module_input_name="token_embeddings"), | |
| ], | |
| model_card_data=card, | |
| ) | |
| model.model_card_data.set_base_model("prajjwal1/bert-tiny") | |
| batch_size = 128 if cli.long_run else 32 | |
| train_size, eval_size = (batch_size * 2, 32) if cli.smoke_test else (16_000, 128) | |
| split = "train" if cli.long_run and not cli.smoke_test else f"train[:{train_size + eval_size}]" | |
| dataset = load_dataset("sentence-transformers/msmarco-bm25", "triplet", split=split).select_columns( | |
| ["query", "positive", "negative"] | |
| ) | |
| if cli.long_run and not cli.smoke_test: | |
| eval_size = 1024 | |
| dataset = dataset.train_test_split(test_size=eval_size, seed=12) | |
| evaluator = MultiVectorNanoBEIREvaluator(dataset_names=["msmarco", "nq", "fiqa2018"], batch_size=64) | |
| logging.info("Baseline evaluation on three NanoBEIR datasets") | |
| with autocast_ctx(): | |
| baseline_metrics = evaluator(model, output_path=str(output_dir), steps=0) | |
| baseline_eval = baseline_metrics[evaluator.primary_metric] | |
| full_evaluator = None | |
| full_baseline = None | |
| if cli.long_run and not cli.smoke_test: | |
| full_evaluator = MultiVectorNanoBEIREvaluator(batch_size=64) | |
| full_output = output_dir / "full_eval" | |
| full_output.mkdir(exist_ok=True) | |
| logging.info("Baseline evaluation on all 13 NanoBEIR datasets") | |
| with autocast_ctx(): | |
| full_baseline = full_evaluator(model, output_path=str(full_output), steps=0) | |
| (output_dir / "baseline.json").write_text( | |
| json.dumps({"selection": baseline_metrics, "full": full_baseline}, indent=2), encoding="utf-8" | |
| ) | |
| args = MultiVectorEncoderTrainingArguments( | |
| output_dir=str(output_dir), | |
| max_steps=1 if cli.smoke_test else 10_000 if cli.long_run else 500, | |
| per_device_train_batch_size=batch_size, | |
| per_device_eval_batch_size=32, | |
| learning_rate=1e-5 if cli.long_run else 3e-5, | |
| weight_decay=0.01, | |
| warmup_steps=0.05, | |
| bf16=torch.cuda.is_available() and torch.cuda.is_bf16_supported(), | |
| fp16=torch.cuda.is_available() and not torch.cuda.is_bf16_supported(), | |
| batch_sampler=BatchSamplers.NO_DUPLICATES, | |
| eval_strategy="steps", | |
| eval_steps=0.2, | |
| save_strategy="steps", | |
| save_steps=0.2, | |
| save_total_limit=2, | |
| logging_steps=0.005 if cli.long_run else 0.02, | |
| logging_first_step=True, | |
| disable_tqdm=True, | |
| load_best_model_at_end=True, | |
| metric_for_best_model=f"eval_{evaluator.primary_metric}", | |
| greater_is_better=True, | |
| report_to="none", | |
| run_name=run_name, | |
| seed=12, | |
| ) | |
| trainer = MultiVectorEncoderTrainer( | |
| model=model, | |
| args=args, | |
| train_dataset=dataset["train"], | |
| eval_dataset=dataset["test"], | |
| loss=MultiVectorMultipleNegativesRankingLoss(model, scale=1.0), | |
| evaluator=evaluator, | |
| callbacks=[LogProgress()], | |
| ) | |
| logging.info("Training configuration: %s", args.to_dict()) | |
| trainer.train() | |
| logging.info("Evaluating the best checkpoint on the same three datasets") | |
| with autocast_ctx(): | |
| final_metrics = evaluator(model, output_path=str(output_dir / "eval")) | |
| score = final_metrics[evaluator.primary_metric] | |
| full_final = None | |
| if full_evaluator is not None: | |
| logging.info("Evaluating the best checkpoint on all 13 NanoBEIR datasets") | |
| with autocast_ctx(): | |
| full_final = full_evaluator(model, output_path=str(full_output)) | |
| baseline_eval = full_baseline[full_evaluator.primary_metric] | |
| score = full_final[full_evaluator.primary_metric] | |
| delta = score - baseline_eval | |
| verdict = "WIN" if delta >= 0.005 else "MARGINAL" if delta >= 0 else "REGRESSION" | |
| logging.info("VERDICT: %s | score=%.4f | baseline=%.4f | delta=%+.4f", verdict, score, baseline_eval, delta) | |
| final_dir = output_dir / "final" | |
| model.save_pretrained(str(final_dir)) | |
| shutil.copy2(__file__, final_dir / "train.py") | |
| results = { | |
| "baseline": baseline_metrics, | |
| "final": final_metrics, | |
| "best_checkpoint": trainer.state.best_model_checkpoint, | |
| "history": trainer.state.log_history, | |
| "verdict": verdict, | |
| "full_baseline": full_baseline, | |
| "full_final": full_final, | |
| "configuration": vars(cli), | |
| "training_args": args.to_dict(), | |
| } | |
| (final_dir / "results.json").write_text(json.dumps(results, indent=2), encoding="utf-8") | |
| logging.info("Saved model, training script, and metrics to %s", final_dir) | |
| if cli.push_to_hub and not cli.smoke_test: | |
| try: | |
| url = model.push_to_hub(repo_id, local_model_path=str(final_dir)) | |
| logging.info("Uploaded to %s", url) | |
| except Exception: | |
| logging.exception("Hub upload failed. The model is saved at %s", final_dir) | |
| if __name__ == "__main__": | |
| main() | |