A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 15, 01:00 UTC
[Model] Re-add the implicit conversion feature for as_seq_cls_model (#21103)
Signed-off-by: wang.yuqi <noooop@126.com>
wang.yuqi committed
Jul 18, 2025 at 15:15 UTC
ca4eb82bcba97f4fb0a377287ff4d36e19c3d33e
11 files changed
+165
-75
tests/models/registry.py
+20
-12
@@ -265,7 +265,6 @@ _TEXT_GENERATION_EXAMPLE_MODELS = {
265
"Qwen2MoeForCausalLM": _HfExamplesInfo("Qwen/Qwen1.5-MoE-A2.7B-Chat"),
266
"Qwen3ForCausalLM": _HfExamplesInfo("Qwen/Qwen3-8B"),
267
"Qwen3MoeForCausalLM": _HfExamplesInfo("Qwen/Qwen3-30B-A3B"),
268
- "Qwen3ForSequenceClassification": _HfExamplesInfo("tomaarsen/Qwen3-Reranker-0.6B-seq-cls"), # noqa: E501
268
"RWForCausalLM": _HfExamplesInfo("tiiuae/falcon-40b"),
269
"StableLMEpochForCausalLM": _HfExamplesInfo("stabilityai/stablelm-zephyr-3b"), # noqa: E501
270
"StableLmForCausalLM": _HfExamplesInfo("stabilityai/stablelm-3b-4e1t"),
@@ -292,7 +291,6 @@ _EMBEDDING_EXAMPLE_MODELS = {
291
# [Text-only]
292
"BertModel": _HfExamplesInfo("BAAI/bge-base-en-v1.5", v0_only=True),
293
"Gemma2Model": _HfExamplesInfo("BAAI/bge-multilingual-gemma2", v0_only=True), # noqa: E501
295
- "GPT2ForSequenceClassification": _HfExamplesInfo("nie3e/sentiment-polish-gpt2-small"), # noqa: E501
294
"GritLM": _HfExamplesInfo("parasail-ai/GritLM-7B-vllm"),
295
"GteModel": _HfExamplesInfo("Snowflake/snowflake-arctic-embed-m-v2.0",
296
trust_remote_code=True),
@@ -311,7 +309,6 @@ _EMBEDDING_EXAMPLE_MODELS = {
309
"Qwen2Model": _HfExamplesInfo("ssmits/Qwen2-7B-Instruct-embed-base"),
310
"Qwen2ForRewardModel": _HfExamplesInfo("Qwen/Qwen2.5-Math-RM-72B"),
311
"Qwen2ForProcessRewardModel": _HfExamplesInfo("Qwen/Qwen2.5-Math-PRM-7B"),
314
- "Qwen2ForSequenceClassification": _HfExamplesInfo("jason9693/Qwen2.5-1.5B-apeach"), # noqa: E501
312
"RobertaModel": _HfExamplesInfo("sentence-transformers/stsb-roberta-base-v2", v0_only=True), # noqa: E501
313
"RobertaForMaskedLM": _HfExamplesInfo("sentence-transformers/all-roberta-large-v1", v0_only=True), # noqa: E501
314
"XLMRobertaModel": _HfExamplesInfo("intfloat/multilingual-e5-small", v0_only=True), # noqa: E501
@@ -324,20 +321,29 @@ _EMBEDDING_EXAMPLE_MODELS = {
321
is_available_online=False), # noqa: E501
322
}
323
327
-_CROSS_ENCODER_EXAMPLE_MODELS = {
328
- # [Text-only]
324
+_SEQUENCE_CLASSIFICATION_EXAMPLE_MODELS = {
325
+ # [Decoder-only]
326
+ "GPT2ForSequenceClassification": _HfExamplesInfo("nie3e/sentiment-polish-gpt2-small"), # noqa: E501
327
+
328
+ # [Cross-encoder]
329
"BertForSequenceClassification": _HfExamplesInfo("cross-encoder/ms-marco-MiniLM-L-6-v2", v0_only=True), # noqa: E501
330
- "GemmaForSequenceClassification": _HfExamplesInfo("BAAI/bge-reranker-v2-gemma", # noqa: E501
331
- v0_only=True,
332
- hf_overrides={"architectures": ["GemmaForSequenceClassification"], # noqa: E501
333
- "classifier_from_token": ["Yes"], # noqa: E501
334
- "method": "no_post_processing"}), # noqa: E501
335
- "LlamaForSequenceClassification": _HfExamplesInfo("Skywork/Skywork-Reward-V2-Llama-3.2-1B"), # noqa: E501
330
"ModernBertForSequenceClassification": _HfExamplesInfo("Alibaba-NLP/gte-reranker-modernbert-base", v0_only=True), # noqa: E501
331
"RobertaForSequenceClassification": _HfExamplesInfo("cross-encoder/quora-roberta-base", v0_only=True), # noqa: E501
332
"XLMRobertaForSequenceClassification": _HfExamplesInfo("BAAI/bge-reranker-v2-m3", v0_only=True), # noqa: E501
333
}
334
335
+_AUTOMATIC_CONVERTED_MODELS = {
336
+ # Use as_seq_cls_model for automatic conversion
337
+ "GemmaForSequenceClassification": _HfExamplesInfo("BAAI/bge-reranker-v2-gemma", # noqa: E501
338
+ v0_only=True,
339
+ hf_overrides={"architectures": ["GemmaForSequenceClassification"], # noqa: E501
340
+ "classifier_from_token": ["Yes"], # noqa: E501
341
+ "method": "no_post_processing"}), # noqa: E501
342
+ "LlamaForSequenceClassification": _HfExamplesInfo("Skywork/Skywork-Reward-V2-Llama-3.2-1B"), # noqa: E501
343
+ "Qwen2ForSequenceClassification": _HfExamplesInfo("jason9693/Qwen2.5-1.5B-apeach"), # noqa: E501
344
+ "Qwen3ForSequenceClassification": _HfExamplesInfo("tomaarsen/Qwen3-Reranker-0.6B-seq-cls"), # noqa: E501
345
+}
346
+
347
_MULTIMODAL_EXAMPLE_MODELS = {
348
# [Decoder-only]
349
"AriaForConditionalGeneration": _HfExamplesInfo("rhymes-ai/Aria"),
@@ -449,6 +455,7 @@ _MULTIMODAL_EXAMPLE_MODELS = {
455
"JinaVLForRanking": _HfExamplesInfo("jinaai/jina-reranker-m0"), # noqa: E501
456
}
457
458
+
459
_SPECULATIVE_DECODING_EXAMPLE_MODELS = {
460
"EAGLEModel": _HfExamplesInfo("JackFram/llama-68m",
461
speculative_model="abhigoyal/vllm-eagle-llama-68m-random"), # noqa: E501
@@ -489,7 +496,7 @@ _TRANSFORMERS_MODELS = {
496
_EXAMPLE_MODELS = {
497
**_TEXT_GENERATION_EXAMPLE_MODELS,
498
**_EMBEDDING_EXAMPLE_MODELS,
492
- **_CROSS_ENCODER_EXAMPLE_MODELS,
499
+ **_SEQUENCE_CLASSIFICATION_EXAMPLE_MODELS,
500
**_MULTIMODAL_EXAMPLE_MODELS,
501
**_SPECULATIVE_DECODING_EXAMPLE_MODELS,
502
**_TRANSFORMERS_MODELS,
@@ -522,3 +529,4 @@ class HfExampleModels:
529
530
531
HF_EXAMPLE_MODELS = HfExampleModels(_EXAMPLE_MODELS)
532
+AUTO_EXAMPLE_MODELS = HfExampleModels(_AUTOMATIC_CONVERTED_MODELS)
tests/models/test_initialization.py
+21
-8
@@ -13,20 +13,21 @@ from vllm.v1.core.kv_cache_utils import get_kv_cache_config
13
from vllm.v1.engine.core import EngineCore as V1EngineCore
14
15
from ..utils import create_new_process_for_each_test
16
-from .registry import HF_EXAMPLE_MODELS
16
+from .registry import AUTO_EXAMPLE_MODELS, HF_EXAMPLE_MODELS, HfExampleModels
17
18
19
-@pytest.mark.parametrize("model_arch", HF_EXAMPLE_MODELS.get_supported_archs())
19
@create_new_process_for_each_test()
21
-def test_can_initialize(model_arch: str, monkeypatch: pytest.MonkeyPatch):
22
- """The reason for using create_new_process_for_each_test is to avoid
23
- the WARNING:
24
- "We must use the 'spawn' multiprocessing start method. Overriding
20
+def can_initialize(model_arch: str, monkeypatch: pytest.MonkeyPatch,
21
+ EXAMPLE_MODELS: HfExampleModels):
22
+ """The reason for using create_new_process_for_each_test is to avoid
23
+ the WARNING:
24
+ "We must use the 'spawn' multiprocessing start method. Overriding
25
VLLM_WORKER_MULTIPROC_METHOD to 'spawn'."
26
- The spawn process causes the _initialize_kv_caches_v1 function below to
26
+ The spawn process causes the _initialize_kv_caches_v1 function below to
27
become ineffective.
28
"""
29
- model_info = HF_EXAMPLE_MODELS.get_hf_info(model_arch)
29
+
30
+ model_info = EXAMPLE_MODELS.get_hf_info(model_arch)
31
model_info.check_available_online(on_fail="skip")
32
model_info.check_transformers_version(on_fail="skip")
33
@@ -127,3 +128,15 @@ def test_can_initialize(model_arch: str, monkeypatch: pytest.MonkeyPatch):
128
load_format="dummy",
129
hf_overrides=hf_overrides,
130
)
131
+
132
+
133
+@pytest.mark.parametrize("model_arch", HF_EXAMPLE_MODELS.get_supported_archs())
134
+def test_can_initialize(model_arch: str, monkeypatch: pytest.MonkeyPatch):
135
+ can_initialize(model_arch, monkeypatch, HF_EXAMPLE_MODELS)
136
+
137
+
138
+@pytest.mark.parametrize("model_arch",
139
+ AUTO_EXAMPLE_MODELS.get_supported_archs())
140
+def test_implicit_converted_models(model_arch: str,
141
+ monkeypatch: pytest.MonkeyPatch):
142
+ can_initialize(model_arch, monkeypatch, AUTO_EXAMPLE_MODELS)
tests/models/test_transformers.py
+35
@@ -138,3 +138,38 @@ def test_quantization(
138
name_0="transformers",
139
name_1="vllm",
140
)
141
+
142
+
143
+@pytest.mark.parametrize(
144
+ "model",
145
+ ["jason9693/Qwen2.5-1.5B-apeach"],
146
+)
147
+@pytest.mark.parametrize("dtype", ["half"])
148
+def test_classify(
149
+ hf_runner,
150
+ vllm_runner,
151
+ example_prompts,
152
+ model: str,
153
+ dtype: str,
154
+ monkeypatch,
155
+) -> None:
156
+ import torch
157
+ from transformers import AutoModelForSequenceClassification
158
+
159
+ with vllm_runner(model,
160
+ max_model_len=512,
161
+ dtype=dtype,
162
+ model_impl="transformers") as vllm_model:
163
+ vllm_outputs = vllm_model.classify(example_prompts)
164
+
165
+ with hf_runner(model,
166
+ dtype=dtype,
167
+ auto_cls=AutoModelForSequenceClassification) as hf_model:
168
+ hf_outputs = hf_model.classify(example_prompts)
169
+
170
+ for hf_output, vllm_output in zip(hf_outputs, vllm_outputs):
171
+ hf_output = torch.tensor(hf_output)
172
+ vllm_output = torch.tensor(vllm_output)
173
+
174
+ assert torch.allclose(hf_output, vllm_output,
175
+ 1e-3 if dtype == "float" else 1e-2)
vllm/config.py
+25
-21
@@ -551,7 +551,7 @@ class ModelConfig:
551
# For pooling models, self.task is used to indicate the
552
# user-selected task
553
if self.task == "score":
554
- if self.registry.is_cross_encoder_model(self.architectures):
554
+ if self._is_classify_task(self.architectures):
555
self.task = "classify"
556
else:
557
self.task = "embed"
@@ -806,6 +806,12 @@ class ModelConfig:
806
f"one of {get_args(TokenizerMode)}.")
807
self.tokenizer_mode = tokenizer_mode
808
809
+ def _is_classify_task(self, architectures: list[str]):
810
+ for arch in architectures:
811
+ if arch.endswith("ForSequenceClassification"):
812
+ return True
813
+ return self.registry.is_cross_encoder_model(architectures)
814
+
815
def _get_preferred_pooling_task(
816
self,
817
architectures: list[str],
@@ -813,14 +819,11 @@ class ModelConfig:
819
model_id = self.model
820
if get_pooling_config(model_id, self.revision):
821
return "embed"
816
- if self.registry.is_cross_encoder_model(architectures):
817
- return "classify"
822
if self.registry.is_transcription_model(architectures):
823
return "transcription"
824
825
suffix_to_preferred_task: list[tuple[str, _ResolvedTask]] = [
826
# Other models follow this pattern
823
- ("ForSequenceClassification", "classify"),
827
("EmbeddingModel", "embed"),
828
("RewardModel", "reward"),
829
]
@@ -878,11 +881,14 @@ class ModelConfig:
881
self,
882
task_option: TaskOption,
883
) -> dict[RunnerType, list[_ResolvedTask]]:
881
- return {
882
- "generate": self._get_supported_generation_tasks(task_option),
883
- "pooling": self._get_supported_pooling_tasks(task_option),
884
- "draft": ["draft"]
885
- }
884
+ if self._is_classify_task(self.architectures):
885
+ return {"generate": [], "pooling": ["classify"], "draft": []}
886
+ else:
887
+ return {
888
+ "generate": self._get_supported_generation_tasks(task_option),
889
+ "pooling": self._get_supported_pooling_tasks(task_option),
890
+ "draft": ["draft"]
891
+ }
892
893
def _get_supported_runner_types(
894
self,
@@ -925,12 +931,16 @@ class ModelConfig:
931
f"Available tasks for runner={task_runner!r}: "
932
f"{supported_tasks[task_runner]}")
933
934
+ if "classify" in supported_tasks.get("pooling", []):
935
+ # When multiple pooling tasks are present, default to
936
+ # pooling (eg cross-encoder) for non-standard architectures.
937
+ return "pooling"
938
+
939
suffix_to_preferred_runner: list[tuple[str, RunnerType]] = [
940
("ForCausalLM", "generate"),
941
("ForConditionalGeneration", "generate"),
942
("ChatModel", "generate"),
943
("LMHeadModel", "generate"),
933
- ("ForSequenceClassification", "pooling"),
944
("EmbeddingModel", "pooling"),
945
("RewardModel", "pooling"),
946
]
@@ -940,10 +950,6 @@ class ModelConfig:
950
if arch.endswith(suffix) and pref_runner in supported_runner_types:
951
return pref_runner
952
943
- if "classify" in supported_tasks.get("pooling", []):
944
- # When multiple pooling tasks are present, default to
945
- # pooling (eg cross-encoder) for non-standard architectures.
946
- return "pooling"
953
if "generate" in supported_runner_types:
954
return "generate"
955
if "pooling" in supported_runner_types:
@@ -1525,7 +1531,7 @@ class ModelConfig:
1531
1532
@property
1533
def is_matryoshka(self) -> bool:
1528
- return (hasattr(self.hf_config, "matryoshka_dimensions")
1534
+ return (bool(getattr(self.hf_config, "matryoshka_dimensions", None))
1535
or getattr(self.hf_config, "is_matryoshka", False))
1536
1537
@property
@@ -1539,13 +1545,11 @@ class ModelConfig:
1545
return getattr(self.hf_config, "use_pad_token", True)
1546
1547
def get_and_verify_max_len(self, max_model_len: int):
1542
- # For pooling models, the tokenizer's `model_max_length` is often a
1543
- # reliable source for the maximum sequence length. However, for
1544
- # generative models, this can be incorrect and unduly limit the
1545
- # context window (e.g., DeepSeek-R1). Therefore, we only consider
1546
- # tokenizer_config for pooling models.
1548
+ # Consider max_model_len in tokenizer_config only when
1549
+ # pooling models use absolute position_embedding.
1550
tokenizer_config = None
1548
- if self.runner_type == "pooling":
1551
+ if (self.runner_type == "pooling" and getattr(
1552
+ self.hf_config, "position_embedding_type", "") == "absolute"):
1553
tokenizer_config = try_get_tokenizer_config(
1554
self.tokenizer,
1555
trust_remote_code=self.trust_remote_code,
vllm/model_executor/model_loader/utils.py
+26
-4
@@ -22,7 +22,8 @@ from vllm.model_executor.layers.quantization.base_config import (
22
QuantizationConfig, QuantizeMethodBase)
23
from vllm.model_executor.models import ModelRegistry
24
from vllm.model_executor.models.adapters import (as_embedding_model,
25
- as_reward_model)
25
+ as_reward_model,
26
+ as_seq_cls_model)
27
from vllm.model_executor.models.interfaces import SupportsQuant
28
from vllm.utils import is_pin_memory_available
29
@@ -238,9 +239,29 @@ def get_model_architecture(
239
vllm_supported_archs = ModelRegistry.get_supported_archs()
240
vllm_not_supported = not any(arch in vllm_supported_archs
241
for arch in architectures)
242
+
243
+ if vllm_not_supported:
244
+ # try automatic conversion in adapters.py
245
+ for arch in architectures:
246
+ if not arch.endswith("ForSequenceClassification"):
247
+ continue
248
+
249
+ assert model_config.task == "classify"
250
+ causal_lm_arch = arch.replace("ForSequenceClassification",
251
+ "ForCausalLM")
252
+ causal_lm_arch_vllm_supported = (causal_lm_arch
253
+ in vllm_supported_archs)
254
+ if not causal_lm_arch_vllm_supported:
255
+ continue
256
+
257
+ architectures = [causal_lm_arch]
258
+ vllm_not_supported = False
259
+ break
260
+
261
if (model_config.model_impl == ModelImpl.TRANSFORMERS or
262
model_config.model_impl != ModelImpl.VLLM and vllm_not_supported):
263
architectures = resolve_transformers_arch(model_config, architectures)
264
+ logger.debug_once("Resolve transformers arch %s", str(architectures))
265
elif (model_config.quantization is not None
266
and model_config.quantization not in mixtral_supported
267
and "MixtralForCausalLM" in architectures):
@@ -248,12 +269,13 @@ def get_model_architecture(
269
270
model_cls, arch = ModelRegistry.resolve_model_cls(architectures)
271
if model_config.task == "embed":
272
+ logger.debug_once("Automatic conversion using `as_embedding_model`.")
273
model_cls = as_embedding_model(model_cls)
274
elif model_config.task == "classify":
253
- # Cannot automatically run as_seq_cls_model,
254
- # otherwise it will cause a circular reference on is_cross_encoder_model
255
- pass
275
+ logger.debug_once("Automatic conversion using `as_seq_cls_model`.")
276
+ model_cls = as_seq_cls_model(model_cls)
277
elif model_config.task == "reward":
278
+ logger.debug_once("Automatic conversion using `as_reward_model`.")
279
model_cls = as_reward_model(model_cls)
280
281
return model_cls, arch
vllm/model_executor/models/adapters.py
+9
-6
@@ -331,13 +331,13 @@ def load_weights_using_from_2_way_softmax(
331
332
false_id = tokenizer.convert_tokens_to_ids(tokens[0])
333
true_id = tokenizer.convert_tokens_to_ids(tokens[1])
334
- weight = model.lm_head.weight.data[[true_id]].to(
334
+ score_weight = model.lm_head.weight.data[[true_id]].to(
335
torch.float32) - model.lm_head.weight.data[[false_id]].to(
336
torch.float32)
337
338
param = model.score.weight
339
weight_loader = getattr(param, "weight_loader", default_weight_loader)
340
- weight_loader(param, weight)
340
+ weight_loader(param, score_weight)
341
342
del model.lm_head
343
loaded_weights.add("score.weight")
@@ -350,6 +350,8 @@ def load_weights_no_post_processing(model,
350
torch.Tensor]]):
351
from vllm.model_executor.layers.vocab_parallel_embedding import (
352
ParallelLMHead)
353
+ from vllm.model_executor.model_loader.weight_utils import (
354
+ default_weight_loader)
355
from vllm.model_executor.models.utils import AutoWeightsLoader
356
357
model_config = model.vllm_config.model_config
@@ -357,8 +359,6 @@ def load_weights_no_post_processing(model,
359
tokens = cast(list[int], tokens)
360
assert len(tokens) > 0
361
360
- device = model.score.weight.device
361
-
362
if model.config.tie_word_embeddings:
363
model.lm_head = model.model.embed_tokens
364
else:
@@ -376,8 +376,11 @@ def load_weights_no_post_processing(model,
376
trust_remote_code=model_config.trust_remote_code)
377
378
token_ids = [tokenizer.convert_tokens_to_ids(t) for t in tokens]
379
- score_weight = model.lm_head.weight.data[token_ids].to(device)
380
- model.score.weight.data.copy_(score_weight)
379
+ score_weight = model.lm_head.weight.data[token_ids]
380
+
381
+ param = model.score.weight
382
+ weight_loader = getattr(param, "weight_loader", default_weight_loader)
383
+ weight_loader(param, score_weight)
384
385
del model.lm_head
386
loaded_weights.add("score.weight")
vllm/model_executor/models/gemma.py
-4
@@ -43,7 +43,6 @@ from vllm.model_executor.model_loader.weight_utils import default_weight_loader
43
from vllm.model_executor.sampling_metadata import SamplingMetadata
44
from vllm.sequence import IntermediateTensors
45
46
-from .adapters import as_seq_cls_model
46
from .interfaces import SupportsLoRA, SupportsPP
47
from .utils import (AutoWeightsLoader, is_pp_missing_parameter,
48
make_empty_intermediate_tensors_factory, make_layers,
@@ -426,6 +425,3 @@ class GemmaForCausalLM(nn.Module, SupportsLoRA, SupportsPP):
425
if self.config.tie_word_embeddings else None),
426
)
427
return loader.load_weights(weights)
429
-
430
-
431
-GemmaForSequenceClassification = as_seq_cls_model(GemmaForCausalLM)
vllm/model_executor/models/llama.py
-4
@@ -49,7 +49,6 @@ from vllm.model_executor.model_loader.weight_utils import (
49
from vllm.model_executor.sampling_metadata import SamplingMetadata
50
from vllm.sequence import IntermediateTensors
51
52
-from .adapters import as_seq_cls_model
52
from .interfaces import SupportsLoRA, SupportsPP
53
from .utils import (AutoWeightsLoader, PPMissingLayer, extract_layer_index,
54
is_pp_missing_parameter,
@@ -646,6 +645,3 @@ class LlamaForCausalLM(nn.Module, SupportsLoRA, SupportsPP):
645
name = name.replace(item, mapping[item])
646
647
return name, loaded_weight
649
-
650
-
651
-LlamaForSequenceClassification = as_seq_cls_model(LlamaForCausalLM)
vllm/model_executor/models/qwen2.py
-4
@@ -50,7 +50,6 @@ from vllm.model_executor.model_loader.weight_utils import (
50
from vllm.model_executor.sampling_metadata import SamplingMetadata
51
from vllm.sequence import IntermediateTensors
52
53
-from .adapters import as_seq_cls_model
53
from .interfaces import SupportsLoRA, SupportsPP
54
from .utils import (AutoWeightsLoader, PPMissingLayer, extract_layer_index,
55
is_pp_missing_parameter,
@@ -496,6 +495,3 @@ class Qwen2ForCausalLM(nn.Module, SupportsLoRA, SupportsPP):
495
if self.config.tie_word_embeddings else None),
496
)
497
return loader.load_weights(weights)
499
-
500
-
501
-Qwen2ForSequenceClassification = as_seq_cls_model(Qwen2ForCausalLM)
vllm/model_executor/models/qwen3.py
-4
@@ -44,7 +44,6 @@ from vllm.model_executor.layers.vocab_parallel_embedding import ParallelLMHead
44
from vllm.model_executor.sampling_metadata import SamplingMetadata
45
from vllm.sequence import IntermediateTensors
46
47
-from .adapters import as_seq_cls_model
47
from .interfaces import SupportsLoRA, SupportsPP
48
from .qwen2 import Qwen2MLP as Qwen3MLP
49
from .qwen2 import Qwen2Model
@@ -320,6 +319,3 @@ class Qwen3ForCausalLM(nn.Module, SupportsLoRA, SupportsPP):
319
if self.config.tie_word_embeddings else None),
320
)
321
return loader.load_weights(weights)
323
-
324
-
325
-Qwen3ForSequenceClassification = as_seq_cls_model(Qwen3ForCausalLM)
vllm/model_executor/models/registry.py
+29
-8
@@ -12,7 +12,7 @@ import sys
12
import tempfile
13
from abc import ABC, abstractmethod
14
from collections.abc import Set
15
-from dataclasses import dataclass, field
15
+from dataclasses import asdict, dataclass, field
16
from functools import lru_cache
17
from typing import Callable, Optional, TypeVar, Union
18
@@ -181,10 +181,6 @@ _CROSS_ENCODER_MODELS = {
181
"ModernBertForSequenceClassification": ("modernbert",
182
"ModernBertForSequenceClassification"),
183
# [Auto-converted (see adapters.py)]
184
- "GemmaForSequenceClassification": ("gemma", "GemmaForSequenceClassification"), # noqa: E501
185
- "Qwen2ForSequenceClassification": ("qwen2", "Qwen2ForSequenceClassification"), # noqa: E501
186
- "Qwen3ForSequenceClassification": ("qwen3", "Qwen3ForSequenceClassification"), # noqa: E501
187
- "LlamaForSequenceClassification": ("llama", "LlamaForSequenceClassification"), # noqa: E501
184
"JinaVLForRanking": ("jina_vl", "JinaVLForSequenceClassification"), # noqa: E501,
185
}
186
@@ -462,10 +458,26 @@ class _ModelRegistry:
458
return _try_load_model_cls(model_arch, self.models[model_arch])
459
460
def _try_inspect_model_cls(self, model_arch: str) -> Optional[_ModelInfo]:
465
- if model_arch not in self.models:
466
- return None
461
+ if model_arch in self.models:
462
+ return _try_inspect_model_cls(model_arch, self.models[model_arch])
463
+
464
+ if model_arch.endswith("ForSequenceClassification"):
465
+ causal_lm_arch = model_arch.replace("ForSequenceClassification",
466
+ "ForCausalLM")
467
+ if causal_lm_arch not in self.models:
468
+ return None
469
+
470
+ info = _try_inspect_model_cls(causal_lm_arch,
471
+ self.models[causal_lm_arch])
472
468
- return _try_inspect_model_cls(model_arch, self.models[model_arch])
473
+ info = _ModelInfo(**dict(
474
+ asdict(info), **{
475
+ "architecture": model_arch,
476
+ "supports_cross_encoding": True
477
+ }))
478
+ return info
479
+
480
+ return None
481
482
def _normalize_archs(
483
self,
@@ -480,6 +492,15 @@ class _ModelRegistry:
492
normalized_arch = list(
493
filter(lambda model: model in self.models, architectures))
494
495
+ # try automatic conversion in adapters.py
496
+ for arch in architectures:
497
+ if not arch.endswith("ForSequenceClassification"):
498
+ continue
499
+ causal_lm_arch = arch.replace("ForSequenceClassification",
500
+ "ForCausalLM")
501
+ if causal_lm_arch in self.models:
502
+ normalized_arch.append(arch)
503
+
504
# make sure Transformers backend is put at the last as a fallback
505
if len(normalized_arch) != len(architectures):
506
normalized_arch.append("TransformersForCausalLM")