CoolFace
Modelpublic

tomaarsen/Qwen3-VL-Embedding-2B-vdr

sourceHugging Faceapache-2.0updated 5mo agoView on Hugging Face
5likes251downloads
Model Card

Qwen3-VL-Embedding-2B model trained on VDR query-document screenshot pairs

This is a sentence-transformers model finetuned from Qwen/Qwen3-VL-Embedding-2B on the llamaindex-vdr-en-train-preprocessed dataset, which is post-processed from the dataset released in Visual Document Retrieval Goes Multilingual. It maps queries and PDF document screenshots to a 1024-dimensional dense vector space and can be used for visual document retrieval and more.

Read my Training and Finetuning Multimodal Embedding & Reranker Models with Sentence Transformers blogpost to learn more about this model and how it was trained, or see the training script at training_visual_document_retrieval.py.

Model Details

Model Description

  • —Model Type: Sentence Transformer
  • —Base model: Qwen/Qwen3-VL-Embedding-2B <!-- at revision ca0487364b340f07c772ec8e46b8b206695eb7f6 of https://huggingface.co/tomaarsen/Qwen3-VL-Embedding-2B-->
  • —Maximum Sequence Length: 262144 tokens
  • —Output Dimensionality: 2048, 1536, 1024 (default), 512, 256, 128, or 64 dimensions with truncate_dim
  • —Similarity Function: Cosine Similarity
  • —Supported Modalities: Text, Image, Video, Message
  • —Training Dataset:
  • —llamaindex-vdr-en-train-preprocessed
  • —Language: en
  • —License: apache-2.0

Model Sources

Full Model Architecture

SentenceTransformer(
  (0): Transformer({'transformer_task': 'feature-extraction', 'modality_config': {'text': {'method': 'forward', 'method_output_name': 'last_hidden_state'}, 'image': {'method': 'forward', 'method_output_name': 'last_hidden_state'}, 'video': {'method': 'forward', 'method_output_name': 'last_hidden_state'}, 'message': {'method': 'forward', 'method_output_name': 'last_hidden_state'}}, 'module_output_name': 'token_embeddings', 'message_format': 'structured', 'processing_kwargs': {'chat_template': {'add_generation_prompt': True}}, 'unpad_inputs': False, 'architecture': 'Qwen3VLModel'})
  (1): Pooling({'embedding_dimension': 2048, 'pooling_mode': 'lasttoken', 'include_prompt': True})
  (2): Normalize({})
)

Usage

Direct Usage (Sentence Transformers)

First install the Sentence Transformers library:

bash
pip install -U sentence-transformers[image]

Then you can load this model and run inference.

python
from sentence_transformers import SentenceTransformer

# Download from the 🤗 Hub
model = SentenceTransformer("tomaarsen/Qwen3-VL-Embedding-2B-vdr")
# Run inference
queries = [
    'Which line appears longer in the provided Müller-Lyer illusion example, A or B?',
]
documents = [
    'https://huggingface.co/tomaarsen/Qwen3-VL-Embedding-2B-vdr/resolve/main/assets/image_0.jpg',
    'https://huggingface.co/tomaarsen/Qwen3-VL-Embedding-2B-vdr/resolve/main/assets/image_1.jpg',
    'https://huggingface.co/tomaarsen/Qwen3-VL-Embedding-2B-vdr/resolve/main/assets/image_2.jpg',
]
query_embeddings = model.encode_query(queries)
document_embeddings = model.encode_document(documents)
print(query_embeddings.shape, document_embeddings.shape)
# [1, 2048] [3, 2048]

# Get the similarity scores for the embeddings
similarities = model.similarity(query_embeddings, document_embeddings)
print(similarities)
# tensor([[ 0.5869, -0.1090,  0.1076]])

<!--

Direct Usage (Transformers)

<details><summary>Click to see the direct usage in Transformers</summary>

</details> -->

<!--

Downstream Usage (Sentence Transformers)

You can finetune this model on your own dataset.

<details><summary>Click to expand</summary>

</details> -->

<!--

Out-of-Scope Use

List how the model may foreseeably be misused and address what users ought not to do with the model. -->

Evaluation

This model was evaluated on the evaluation dataset: 300 text queries against a corpus of 1500 document screenshots (300 positives plus 4 hard negatives per query). See the training blogpost for full context.

Model Size vs NDCG@10

This model achieves an NDCG@10 of 0.947, up from the base Qwen/Qwen3-VL-Embedding-2B model's 0.888, and ahead of every other VDR model I tested:

Model size vs NDCG for VDR models

<details> <summary>Full NDCG@10 numbers by model (20 models)</summary>

ModelParametersNDCG@10
tomaarsen/Qwen3-VL-Embedding-2B-vdr2.1B0.947
Qwen/Qwen3-VL-Embedding-8B8.1B0.923
nvidia/omni-embed-nemotron-3b4.7B0.915
nvidia/llama-nemotron-embed-vl-1b-v21.7B0.912
nomic-ai/nomic-embed-multimodal-7b8.3B0.912
llamaindex/vdr-2b-multi-v12.2B0.912
llamaindex/vdr-2b-v12.2B0.911
nomic-ai/nomic-embed-multimodal-3b3.8B0.899
Qwen/Qwen3-VL-Embedding-2B2.1B0.888
LCO-Embedding/LCO-Embedding-Omni-7B8.9B0.888
LCO-Embedding/LCO-Embedding-Omni-3B4.7B0.860
BAAI/BGE-VL-v1.5-zs7.6B0.800
BAAI/BGE-VL-v1.5-mmeb7.6B0.797
BAAI/BGE-VL-MLLM-S27.6B0.792
BidirLM/BidirLM-Omni-2.5B-Embedding2.5B0.775
royokong/e5-v8.4B0.767
BAAI/BGE-VL-MLLM-S17.6B0.710
sentence-transformers/clip-ViT-L-14428M0.611
BAAI/BGE-VL-large428M0.467
BAAI/BGE-VL-base150M0.335

</details>

This 2B model outperforms even the 8B Qwen3-VL-Embedding model on this task.

Matryoshka Dimensions vs NDCG@10

The comparison above uses full-size 2048-dim embeddings. Thanks to the Matryoshka training, this model also holds up well when truncated to fewer dimensions, letting you trade off embedding size and retrieval quality at deployment time:

MRL dimensions vs NDCG@10

[!NOTE] Peak performance is at the full 2048 dimensions (0.948), but the model stays within 0.3% of peak all the way down to 512 (4x smaller), and retains over 92% of peak even at 64 (32x smaller). Matryoshka training concentrates the most important information in the earlier dimensions, so moderate truncation costs very little performance.

<details> <summary>Full NDCG@10 numbers by dimension</summary>

DimensionsBase NDCG@10Finetuned NDCG@10
2048 (full)0.8961 (100%)0.9480 (100%)
15360.8940 (99.8%)0.9439 (99.6%)
10240.8941 (99.8%)0.9464 (99.8%)
5120.8760 (97.8%)0.9451 (99.7%)
2560.8347 (93.2%)0.9372 (98.9%)
1280.7888 (88.0%)0.9058 (95.5%)
640.6852 (76.5%)0.8758 (92.4%)

</details>

The gap between 1024 and 2048 dimensions is small (0.946 vs. 0.948), so this model ships with truncate_dim=1024 set in its configuration. That means SentenceTransformer("tomaarsen/Qwen3-VL-Embedding-2B-vdr") produces 1024-dimensional embeddings by default, halving the storage footprint compared to the full 2048. Pass truncate_dim=N when loading to override it.

Metrics

Information Retrieval
Metricvdr-eval-hard
cosine_accuracy@10.8933
cosine_accuracy@30.97
cosine_accuracy@50.9833
cosine_accuracy@101.0
cosine_precision@10.8933
cosine_precision@30.3233
cosine_precision@50.1967
cosine_precision@100.1
cosine_recall@10.8933
cosine_recall@30.97
cosine_recall@50.9833
cosine_recall@101.0
cosine_ndcg@100.9485
cosine_mrr@100.9318
cosine_map@1000.9318

<!--

Bias, Risks and Limitations

What are the known or foreseeable issues stemming from this model? You could also flag here known failure cases or weaknesses of the model. -->

<!--

Recommendations

What are recommendations with respect to the foreseeable issues? For example, filtering explicit content. -->

Training Details

Training Dataset

llamaindex-vdr-en-train-preprocessed
  • —Dataset: llamaindex-vdr-en-train-preprocessed using the train subset.
  • —Size: 10,000 training samples
  • —Columns: <code>query</code>, <code>image</code>, and <code>negative_0</code>
  • —Approximate statistics based on the first 1000 samples: | | query | image | negative_0 | |:--------|:-----------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------| | type | string | image | image | | details | <ul><li>min: 26 tokens</li><li>mean: 36.31 tokens</li><li>max: 62 tokens</li></ul> | <ul><li>min: 700x709 px</li><li>mean: 1416x1648 px</li><li>max: 2100x2064 px</li></ul> | <ul><li>min: 827x709 px</li><li>mean: 1438x1633 px</li><li>max: 2583x1897 px</li></ul> |
  • —Samples: | query | image | negative0 | |:-----------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------|:---------------------------------------------------| | <code>What are the new anthropological perspectives on development as discussed by Quarles Van Ufford and Giri in 2003?</code> | <img src="https://huggingface.co/tomaarsen/Qwen3-VL-Embedding-2B-vdr/resolve/main/assets/exampleimage0.jpg" width="200"> | <img src="https://huggingface.co/tomaarsen/Qwen3-VL-Embedding-2B-vdr/resolve/main/assets/exampleimage1.jpg" width="200"> | | <code>What are the three main positions anthropologists have taken in relation to development, as discussed by David Lewis?</code> | <img src="https://huggingface.co/tomaarsen/Qwen3-VL-Embedding-2B-vdr/resolve/main/assets/exampleimage2.jpg" width="200"> | <img src="https://huggingface.co/tomaarsen/Qwen3-VL-Embedding-2B-vdr/resolve/main/assets/exampleimage1.jpg" width="200"> | | <code>Who are the three sisters known as the Fates in Greek mythology?</code> | <img src="https://huggingface.co/tomaarsen/Qwen3-VL-Embedding-2B-vdr/resolve/main/assets/exampleimage3.jpg" width="200"> | <img src="https://huggingface.co/tomaarsen/Qwen3-VL-Embedding-2B-vdr/resolve/main/assets/exampleimage_4.jpg" width="200"> |
  • —Loss: <code>MatryoshkaLoss</code> with these parameters:
json
  {
      "loss": "CachedMultipleNegativesRankingLoss",
      "matryoshka_dims": [
          2048,
          1536,
          1024,
          512,
          256,
          128,
          64
      ],
      "matryoshka_weights": [
          1,
          1,
          1,
          1,
          1,
          1,
          1
      ],
      "n_dims_per_step": -1
  }

Evaluation Dataset

llamaindex-vdr-en-train-preprocessed
  • —Dataset: llamaindex-vdr-en-train-preprocessed using the eval subset.
  • —Size: 300 evaluation samples
  • —Columns: <code>query</code>, <code>image</code>, <code>negative0</code>, <code>negative1</code>, <code>negative2</code>, and <code>negative3</code>
  • —Approximate statistics based on the first 300 samples: | | query | image | negative0 | negative1 | negative2 | negative3 | |:--------|:-----------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------| | type | string | image | image | image | image | image | | details | <ul><li>min: 27 tokens</li><li>mean: 36.48 tokens</li><li>max: 66 tokens</li></ul> | <ul><li>min: 334x481 px</li><li>mean: 1425x1636 px</li><li>max: 2229x1890 px</li></ul> | <ul><li>min: 992x709 px</li><li>mean: 1444x1635 px</li><li>max: 2051x1866 px</li></ul> | <ul><li>min: 937x709 px</li><li>mean: 1437x1642 px</li><li>max: 2044x1939 px</li></ul> | <ul><li>min: 872x709 px</li><li>mean: 1441x1642 px</li><li>max: 2044x2696 px</li></ul> | <ul><li>min: 1008x756 px</li><li>mean: 1423x1654 px</li><li>max: 2044x1866 px</li></ul> |
  • —Samples: | query | image | negative0 | negative1 | negative2 | negative3 | |:-------------------------------------------------------------------------------------------------------|:-------------------------------------------|:----------------------------------------------------|:----------------------------------------------------|:----------------------------------------------------|:----------------------------------------------------| | <code>Which line appears longer in the provided Müller-Lyer illusion example, A or B?</code> | <img src="https://huggingface.co/tomaarsen/Qwen3-VL-Embedding-2B-vdr/resolve/main/assets/image0.jpg" width="200"> | <img src="https://huggingface.co/tomaarsen/Qwen3-VL-Embedding-2B-vdr/resolve/main/assets/exampleimage5.jpg" width="200"> | <img src="https://huggingface.co/tomaarsen/Qwen3-VL-Embedding-2B-vdr/resolve/main/assets/exampleimage6.jpg" width="200"> | <img src="https://huggingface.co/tomaarsen/Qwen3-VL-Embedding-2B-vdr/resolve/main/assets/exampleimage7.jpg" width="200"> | <img src="https://huggingface.co/tomaarsen/Qwen3-VL-Embedding-2B-vdr/resolve/main/assets/exampleimage8.jpg" width="200"> | | <code>When did Hyundai begin its initial rural car-sharing program in Spain?</code> | <img src="https://huggingface.co/tomaarsen/Qwen3-VL-Embedding-2B-vdr/resolve/main/assets/image1.jpg" width="200"> | <img src="https://huggingface.co/tomaarsen/Qwen3-VL-Embedding-2B-vdr/resolve/main/assets/exampleimage9.jpg" width="200"> | <img src="https://huggingface.co/tomaarsen/Qwen3-VL-Embedding-2B-vdr/resolve/main/assets/exampleimage10.jpg" width="200"> | <img src="https://huggingface.co/tomaarsen/Qwen3-VL-Embedding-2B-vdr/resolve/main/assets/exampleimage11.jpg" width="200"> | <img src="https://huggingface.co/tomaarsen/Qwen3-VL-Embedding-2B-vdr/resolve/main/assets/exampleimage12.jpg" width="200"> | | <code>What is the formula for calculating the time to move to a target according to Fitts' Law?</code> | <img src="https://huggingface.co/tomaarsen/Qwen3-VL-Embedding-2B-vdr/resolve/main/assets/image2.jpg" width="200"> | <img src="https://huggingface.co/tomaarsen/Qwen3-VL-Embedding-2B-vdr/resolve/main/assets/exampleimage13.jpg" width="200"> | <img src="https://huggingface.co/tomaarsen/Qwen3-VL-Embedding-2B-vdr/resolve/main/assets/exampleimage14.jpg" width="200"> | <img src="https://huggingface.co/tomaarsen/Qwen3-VL-Embedding-2B-vdr/resolve/main/assets/exampleimage15.jpg" width="200"> | <img src="https://huggingface.co/tomaarsen/Qwen3-VL-Embedding-2B-vdr/resolve/main/assets/exampleimage_16.jpg" width="200"> |
  • —Loss: <code>MatryoshkaLoss</code> with these parameters:
json
  {
      "loss": "CachedMultipleNegativesRankingLoss",
      "matryoshka_dims": [
          2048,
          1536,
          1024,
          512,
          256,
          128,
          64
      ],
      "matryoshka_weights": [
          1,
          1,
          1,
          1,
          1,
          1,
          1
      ],
      "n_dims_per_step": -1
  }

Training Hyperparameters

Non-Default Hyperparameters
  • —per_device_train_batch_size: 64
  • —num_train_epochs: 1
  • —learning_rate: 2e-05
  • —warmup_steps: 0.1
  • —bf16: True
  • —per_device_eval_batch_size: 64
  • —batch_sampler: no_duplicates
All Hyperparameters

<details><summary>Click to expand</summary>

  • —per_device_train_batch_size: 64
  • —num_train_epochs: 1
  • —max_steps: -1
  • —learning_rate: 2e-05
  • —lr_scheduler_type: linear
  • —lr_scheduler_kwargs: None
  • —warmup_steps: 0.1
  • —optim: adamwtorchfused
  • —optim_args: None
  • —weight_decay: 0.0
  • —adam_beta1: 0.9
  • —adam_beta2: 0.999
  • —adam_epsilon: 1e-08
  • —optim_target_modules: None
  • —gradient_accumulation_steps: 1
  • —average_tokens_across_devices: True
  • —max_grad_norm: 1.0
  • —label_smoothing_factor: 0.0
  • —bf16: True
  • —fp16: False
  • —bf16_full_eval: False
  • —fp16_full_eval: False
  • —tf32: None
  • —gradient_checkpointing: False
  • —gradient_checkpointing_kwargs: None
  • —torch_compile: False
  • —torch_compile_backend: None
  • —torch_compile_mode: None
  • —use_liger_kernel: False
  • —liger_kernel_config: None
  • —use_cache: False
  • —neftune_noise_alpha: None
  • —torch_empty_cache_steps: None
  • —auto_find_batch_size: False
  • —log_on_each_node: True
  • —logging_nan_inf_filter: True
  • —include_num_input_tokens_seen: no
  • —log_level: passive
  • —log_level_replica: warning
  • —disable_tqdm: False
  • —project: huggingface
  • —trackio_space_id: trackio
  • —eval_strategy: steps
  • —per_device_eval_batch_size: 64
  • —prediction_loss_only: True
  • —eval_on_start: False
  • —eval_do_concat_batches: True
  • —eval_use_gather_object: False
  • —eval_accumulation_steps: None
  • —include_for_metrics: []
  • —batch_eval_metrics: False
  • —save_only_model: False
  • —save_on_each_node: False
  • —enable_jit_checkpoint: False
  • —push_to_hub: False
  • —hub_private_repo: None
  • —hub_model_id: None
  • —hub_strategy: every_save
  • —hub_always_push: False
  • —hub_revision: None
  • —load_best_model_at_end: False
  • —ignore_data_skip: False
  • —restore_callback_states_from_checkpoint: False
  • —full_determinism: False
  • —seed: 42
  • —data_seed: None
  • —use_cpu: False
  • —accelerator_config: {'splitbatches': False, 'dispatchbatches': None, 'evenbatches': True, 'useseedablesampler': True, 'nonblocking': False, 'gradientaccumulationkwargs': None}
  • —parallelism_config: None
  • —dataloader_drop_last: False
  • —dataloader_num_workers: 0
  • —dataloader_pin_memory: True
  • —dataloader_persistent_workers: False
  • —dataloader_prefetch_factor: None
  • —remove_unused_columns: True
  • —label_names: None
  • —train_sampling_strategy: random
  • —length_column_name: length
  • —ddp_find_unused_parameters: None
  • —ddp_bucket_cap_mb: None
  • —ddp_broadcast_buffers: False
  • —ddp_backend: None
  • —ddp_timeout: 1800
  • —fsdp: []
  • —fsdp_config: {'minnumparams': 0, 'xla': False, 'xlafsdpv2': False, 'xlafsdpgrad_ckpt': False}
  • —deepspeed: None
  • —debug: []
  • —skip_memory_metrics: True
  • —do_predict: False
  • —resume_from_checkpoint: None
  • —warmup_ratio: None
  • —local_rank: -1
  • —prompts: None
  • —batch_sampler: no_duplicates
  • —multi_dataset_batch_sampler: proportional
  • —router_mapping: {}
  • —learning_rate_mapping: {}

</details>

Training Logs

EpochStepTraining LossValidation Lossvdr-eval-hard_cosine_ndcg@10
-1-1--0.8629
0.051089.0764--
0.1019166.74458.32410.8987
0.1529246.4662--
0.2038326.72898.31680.9090
0.2548406.8125--
0.3057486.57808.06690.9261
0.3567566.6617--
0.4076646.50157.96640.9232
0.4586726.4136--
0.5096806.33918.01280.9381
0.5605886.5283--
0.6115966.33567.87040.9443
0.66241046.1200--
0.71341126.30237.57710.9481
0.76431206.4604--
0.81531286.16598.10320.9508
0.86621366.1308--
0.91721446.25767.59170.9454
0.96821526.4182--
1.0157-7.12060.9485
-1-1--0.9485

Framework Versions

  • —Python: 3.11.6
  • —Sentence Transformers: 5.4.0.dev0
  • —Transformers: 5.5.0.dev0
  • —PyTorch: 2.10.0+cu128
  • —Accelerate: 1.13.0.dev0
  • —Datasets: 4.3.0
  • —Tokenizers: 0.22.2

Citation

BibTeX

Sentence Transformers
bibtex
@inproceedings{reimers-2019-sentence-bert,
    title = "Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks",
    author = "Reimers, Nils and Gurevych, Iryna",
    booktitle = "Proceedings of the 2019 Conference on Empirical Methods in Natural Language Processing",
    month = "11",
    year = "2019",
    publisher = "Association for Computational Linguistics",
    url = "https://arxiv.org/abs/1908.10084",
}
MatryoshkaLoss
bibtex
@misc{kusupati2024matryoshka,
    title={Matryoshka Representation Learning},
    author={Aditya Kusupati and Gantavya Bhatt and Aniket Rege and Matthew Wallingford and Aditya Sinha and Vivek Ramanujan and William Howard-Snyder and Kaifeng Chen and Sham Kakade and Prateek Jain and Ali Farhadi},
    year={2024},
    eprint={2205.13147},
    archivePrefix={arXiv},
    primaryClass={cs.LG}
}
CachedMultipleNegativesRankingLoss
bibtex
@misc{gao2021scaling,
    title={Scaling Deep Contrastive Learning Batch Size under Memory Limited Setup},
    author={Luyu Gao and Yunyi Zhang and Jiawei Han and Jamie Callan},
    year={2021},
    eprint={2101.06983},
    archivePrefix={arXiv},
    primaryClass={cs.LG}
}

<!--

Glossary

Clearly define terms in order to be accessible across audiences. -->

<!--

Model Card Authors

Lists the people who create the model card, providing recognition and accountability for the detailed work that goes into its construction. -->

<!--

Model Card Contact

Provides a way for people who have updates to the Model Card, suggestions, or questions, to contact the Model Card authors. -->