main
py 284 lines 8.08 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 from typing import (
4 TYPE_CHECKING,
5 Any,
6 ClassVar,
7 Literal,
8 Protocol,
9 overload,
10 runtime_checkable,
11 )
12
13 import torch
14 import torch.nn as nn
15 from typing_extensions import TypeIs, TypeVar
16
17 from vllm.logger import init_logger
18 from vllm.tasks import ScoreType
19 from vllm.utils.func_utils import supports_kw
20
21 if TYPE_CHECKING:
22 from vllm.config import VllmConfig
23 from vllm.config.model import AttnTypeStr
24 from vllm.config.pooler import SequencePoolingType, TokenPoolingType
25 from vllm.model_executor.layers.pooler import Pooler
26 else:
27 VllmConfig = Any
28 Pooler = Any
29 SequencePoolingType = Any
30 TokenPoolingType = Any
31 AttnTypeStr = Any
32
33 logger = init_logger(__name__)
34
35 # The type of hidden states
36 # Currently, T = torch.Tensor for all models except for Medusa
37 # which has T = list[torch.Tensor]
38 T = TypeVar("T", default=torch.Tensor)
39 T_co = TypeVar("T_co", default=torch.Tensor, covariant=True)
40
41 # NOTE: Unlike those in `interfaces.py`, we don't define `ClassVar` tags
42 # for the base interfaces to avoid breaking OOT registration for existing models
43 # that don't inherit from the base interface classes
44
45
46 @runtime_checkable
47 class VllmModel(Protocol[T_co]):
48 """The interface required for all models in vLLM."""
49
50 def __init__(self, vllm_config: VllmConfig, prefix: str = "") -> None: ...
51
52 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
53 """Apply token embeddings to `input_ids`."""
54 ...
55
56 def forward(self, input_ids: torch.Tensor, positions: torch.Tensor) -> T_co: ...
57
58
59 def _check_vllm_model_init(model: type[object] | object) -> bool:
60 model_init = getattr(model, "__init__") # noqa: B009
61 return supports_kw(model_init, "vllm_config")
62
63
64 def _check_vllm_model_embed_input_ids(model: type[object] | object) -> bool:
65 model_embed_input_ids = getattr(model, "embed_input_ids", None)
66 if not callable(model_embed_input_ids):
67 logger.warning(
68 "The model (%s) is missing the `embed_input_ids` method.",
69 model,
70 )
71 return False
72
73 return True
74
75
76 def _check_vllm_model_forward(model: type[object] | object) -> bool:
77 model_forward = getattr(model, "forward", None)
78 if not callable(model_forward):
79 return False
80
81 vllm_kws = ("input_ids", "positions")
82 missing_kws = tuple(kw for kw in vllm_kws if not supports_kw(model_forward, kw))
83
84 if missing_kws and (isinstance(model, type) and issubclass(model, nn.Module)):
85 logger.warning(
86 "The model (%s) is missing "
87 "vLLM-specific keywords from its `forward` method: %s",
88 model,
89 missing_kws,
90 )
91
92 return len(missing_kws) == 0
93
94
95 @overload
96 def is_vllm_model(model: type[object]) -> TypeIs[type[VllmModel]]: ...
97
98
99 @overload
100 def is_vllm_model(model: object) -> TypeIs[VllmModel]: ...
101
102
103 def is_vllm_model(
104 model: type[object] | object,
105 ) -> TypeIs[type[VllmModel]] | TypeIs[VllmModel]:
106 return (
107 _check_vllm_model_init(model)
108 and _check_vllm_model_embed_input_ids(model)
109 and _check_vllm_model_forward(model)
110 )
111
112
113 @runtime_checkable
114 class VllmModelForTextGeneration(VllmModel[T], Protocol[T]):
115 """The interface required for all generative models in vLLM."""
116
117 def compute_logits(
118 self,
119 hidden_states: T,
120 ) -> T | None:
121 """Return `None` if TP rank > 0."""
122 ...
123
124
125 @overload
126 def is_text_generation_model(
127 model: type[object],
128 ) -> TypeIs[type[VllmModelForTextGeneration]]: ...
129
130
131 @overload
132 def is_text_generation_model(model: object) -> TypeIs[VllmModelForTextGeneration]: ...
133
134
135 def is_text_generation_model(
136 model: type[object] | object,
137 ) -> TypeIs[type[VllmModelForTextGeneration]] | TypeIs[VllmModelForTextGeneration]:
138 if not is_vllm_model(model):
139 return False
140
141 if isinstance(model, type):
142 return isinstance(model, VllmModelForTextGeneration)
143
144 return isinstance(model, VllmModelForTextGeneration)
145
146
147 @runtime_checkable
148 class VllmModelForPooling(VllmModel[T_co], Protocol[T_co]):
149 """The interface required for all pooling models in vLLM."""
150
151 is_pooling_model: ClassVar[Literal[True]] = True
152 """
153 A flag that indicates this model supports pooling.
154
155 Note:
156 There is no need to redefine this flag if this class is in the
157 MRO of your model class.
158 """
159
160 default_seq_pooling_type: ClassVar[SequencePoolingType] = "LAST"
161 """
162 Indicates the [vllm.config.pooler.PoolerConfig.seq_pooling_type][]
163 to use by default.
164
165 You can use the
166 [vllm.model_executor.models.interfaces_base.default_pooling_type][]
167 decorator to conveniently set this field.
168 """
169
170 default_tok_pooling_type: ClassVar[TokenPoolingType] = "ALL"
171 """
172 Indicates the [vllm.config.pooler.PoolerConfig.tok_pooling_type][]
173 to use by default.
174
175 You can use the
176 [vllm.model_executor.models.interfaces_base.default_pooling_type][]
177 decorator to conveniently set this field.
178 """
179
180 attn_type: ClassVar[AttnTypeStr] = "decoder"
181 """
182 Indicates the
183 [vllm.config.model.ModelConfig.attn_type][]
184 to use by default.
185
186 You can use the
187 [vllm.model_executor.models.interfaces_base.attn_type][]
188 decorator to conveniently set this field.
189 """
190
191 score_type: ClassVar[ScoreType] = "bi-encoder"
192 """
193 Indicates the
194 [vllm.config.model.ModelConfig.score_type][]
195 to use by default.
196
197 Scoring API handles score/rerank for:\n
198 - "classify" task (score_type: cross-encoder models)\n
199 - "embed" task (score_type: bi-encoder models)\n
200 - "token_embed" task (score_type: late interaction models)\n
201
202 score_type defaults to bi-encoder, then the Score API uses the "embed" task.\n
203 If you set score_type to cross-encoder via
204 [vllm.model_executor.models.interfaces.SupportsCrossEncoding][],
205 then the Score API uses the "score" task.\n
206 If you set score_type to late-interaction via
207 [vllm.model_executor.models.interfaces.SupportsLateInteraction][],
208 then the Score API uses the "token_embed" task.\n
209 """
210
211 pooler: Pooler
212 """The pooler is only called on TP rank 0."""
213
214
215 @overload
216 def is_pooling_model(model: type[object]) -> TypeIs[type[VllmModelForPooling]]: ...
217
218
219 @overload
220 def is_pooling_model(model: object) -> TypeIs[VllmModelForPooling]: ...
221
222
223 def is_pooling_model(
224 model: type[object] | object,
225 ) -> TypeIs[type[VllmModelForPooling]] | TypeIs[VllmModelForPooling]:
226 if not is_vllm_model(model):
227 return False
228
229 return getattr(model, "is_pooling_model", False)
230
231
232 _T = TypeVar("_T", bound=type[nn.Module])
233
234
235 def default_pooling_type(
236 *,
237 seq_pooling_type: SequencePoolingType = "LAST",
238 tok_pooling_type: TokenPoolingType = "ALL",
239 ):
240 """Decorator to set `VllmModelForPooling.default_*_pooling_type`."""
241
242 def func(model: _T) -> _T:
243 model.default_seq_pooling_type = seq_pooling_type # type: ignore
244 model.default_tok_pooling_type = tok_pooling_type # type: ignore
245 return model
246
247 return func
248
249
250 def get_default_seq_pooling_type(
251 model: type[object] | object,
252 ) -> SequencePoolingType:
253 return getattr(model, "default_seq_pooling_type", "LAST")
254
255
256 def get_default_tok_pooling_type(
257 model: type[object] | object,
258 ) -> TokenPoolingType:
259 return getattr(model, "default_tok_pooling_type", "ALL")
260
261
262 def attn_type(attn_type: AttnTypeStr):
263 """Decorator to set `VllmModelForPooling.attn_type`."""
264
265 def func(model: _T) -> _T:
266 model.attn_type = attn_type # type: ignore
267 return model
268
269 return func
270
271
272 def get_attn_type(model: type[object] | object) -> AttnTypeStr:
273 return getattr(model, "attn_type", "decoder")
274
275
276 def get_score_type(model: type[object] | object) -> ScoreType:
277 score_types: set[ScoreType] = set()
278 model_cls = model if isinstance(model, type) else type(model)
279 for m in model_cls.__mro__:
280 score_type: ScoreType = getattr(m, "score_type", "bi-encoder")
281 if score_type != "bi-encoder":
282 score_types.add(score_type)
283 assert len(score_types) < 2
284 return "bi-encoder" if not score_types else next(iter(score_types))