| 1 | # SPDX-License-Identifier: Apache-2.0 |
| 2 | # SPDX-FileCopyrightText: Copyright contributors to the vLLM project |
| 3 | """Backbone validation for Jina Embeddings V5. |
| 4 | |
| 5 | The V5 family ships two backbones under one `architectures` entry: `-small` is |
| 6 | a Qwen3 decoder, while `-nano` is a bidirectional EuroBERT encoder. Upstream |
| 7 | ships a separate `configuration_*.py` per repository, so the only signal |
| 8 | distinguishing them is `is_decoder`, which the encoder variant sets to False. |
| 9 | `JinaEmbeddingsV5ModelConfig` uses it to enable bidirectional attention for the |
| 10 | encoder variant; `JinaEmbeddingsV5Model` then dispatches to the correct backbone. |
| 11 | """ |
| 12 | |
| 13 | from types import SimpleNamespace |
| 14 | from typing import cast |
| 15 | |
| 16 | import pytest |
| 17 | from transformers import PreTrainedConfig |
| 18 | |
| 19 | from vllm.config import ModelConfig |
| 20 | from vllm.model_executor.models.config import ( |
| 21 | MODELS_CONFIG_MAP, |
| 22 | JinaEmbeddingsV5ModelConfig, |
| 23 | ) |
| 24 | |
| 25 | |
| 26 | def _model_config(hf_config: PreTrainedConfig) -> ModelConfig: |
| 27 | """Minimal stand-in for ModelConfig; only hf_config is read.""" |
| 28 | return cast(ModelConfig, SimpleNamespace(hf_config=hf_config)) |
| 29 | |
| 30 | |
| 31 | @pytest.mark.cpu_test |
| 32 | def test_registered_for_the_architecture(): |
| 33 | """The handler only runs if it is wired to the architecture name.""" |
| 34 | assert MODELS_CONFIG_MAP["JinaEmbeddingsV5Model"] is JinaEmbeddingsV5ModelConfig |
| 35 | |
| 36 | |
| 37 | @pytest.mark.cpu_test |
| 38 | def test_encoder_backbone_enables_bidirectional_attention(): |
| 39 | """An encoder checkpoint (is_decoder=False) is supported. |
| 40 | |
| 41 | The handler sets is_causal=False so the Llama backbone uses |
| 42 | EncoderOnlyAttention; JinaEmbeddingsV5Model then dispatches to the encoder |
| 43 | implementation. |
| 44 | """ |
| 45 | hf_config = PreTrainedConfig(is_decoder=False) |
| 46 | |
| 47 | JinaEmbeddingsV5ModelConfig.verify_and_update_model_config(_model_config(hf_config)) |
| 48 | |
| 49 | assert hf_config.is_causal is False |
| 50 | |
| 51 | |
| 52 | @pytest.mark.cpu_test |
| 53 | def test_supported_decoder_backbone_is_accepted(): |
| 54 | """The Qwen3-based variants must keep loading. |
| 55 | |
| 56 | `-small` omits `is_decoder` entirely, so an absent attribute has to be |
| 57 | treated as a decoder. The first assertion pins that assumption: if |
| 58 | PreTrainedConfig ever gains an `is_decoder=False` default, this fails here |
| 59 | rather than silently rejecting a supported checkpoint. |
| 60 | """ |
| 61 | absent = PreTrainedConfig() |
| 62 | assert not hasattr(absent, "is_decoder") |
| 63 | |
| 64 | JinaEmbeddingsV5ModelConfig.verify_and_update_model_config(_model_config(absent)) |
| 65 | JinaEmbeddingsV5ModelConfig.verify_and_update_model_config( |
| 66 | _model_config(PreTrainedConfig(is_decoder=True)) |
| 67 | ) |