main
py 411 lines 14.8 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import itertools
5 from collections.abc import Iterable
6
7 import torch
8 from torch import nn
9 from transformers import RobertaConfig
10
11 from vllm.config import ModelConfig, PoolerConfig, VllmConfig
12 from vllm.model_executor.layers.pooler import (
13 BgeM3Pooler,
14 BOSEOSFilter,
15 DispatchPooler,
16 Pooler,
17 )
18 from vllm.model_executor.layers.pooler.seqwise import (
19 pooler_for_embed,
20 )
21 from vllm.model_executor.layers.pooler.tokwise import (
22 AllPool,
23 pooler_for_token_classify,
24 pooler_for_token_embed,
25 )
26 from vllm.model_executor.layers.vocab_parallel_embedding import VocabParallelEmbedding
27 from vllm.model_executor.model_loader.default_loader import DefaultModelLoader
28 from vllm.model_executor.model_loader.weight_utils import default_weight_loader
29 from vllm.model_executor.models.bert import (
30 TOKEN_TYPE_SHIFT,
31 BertEmbeddingModel,
32 BertModel,
33 _decode_token_type_ids,
34 _encode_token_type_ids,
35 )
36 from vllm.model_executor.models.utils import (
37 AutoWeightsLoader,
38 WeightsMapper,
39 maybe_prefix,
40 )
41 from vllm.sequence import IntermediateTensors
42
43 from .bert_with_rope import BertWithRope, JinaRobertaModel
44 from .interfaces import SupportsCrossEncoding, SupportsLoRA
45 from .interfaces_base import attn_type, default_pooling_type
46
47
48 class RobertaEmbedding(nn.Module):
49 def __init__(self, config: RobertaConfig):
50 super().__init__()
51 self.size = config.hidden_size
52 self.word_embeddings = VocabParallelEmbedding(
53 config.vocab_size, config.hidden_size
54 )
55 self.padding_idx = config.pad_token_id
56 self.position_embeddings = VocabParallelEmbedding(
57 config.max_position_embeddings, config.hidden_size
58 )
59
60 self.token_type_embeddings = VocabParallelEmbedding(
61 config.type_vocab_size, config.hidden_size
62 )
63 self.LayerNorm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps)
64 self.register_buffer(
65 "position_ids",
66 torch.arange(config.max_position_embeddings).unsqueeze(0),
67 )
68
69 def forward(
70 self,
71 input_ids: torch.Tensor,
72 position_ids: torch.Tensor,
73 inputs_embeds: torch.Tensor | None = None,
74 ) -> torch.Tensor:
75 token_type_ids = _decode_token_type_ids(input_ids)
76
77 if inputs_embeds is None:
78 inputs_embeds = self.word_embeddings(input_ids)
79
80 # RoBERTa positions start at padding_idx + 1 instead of 0.
81 # Use non-in-place add to avoid mutating the persistent positions
82 # buffer -- in-place += would accumulate on CUDA graph padding
83 # slots that aren't refreshed between requests, eventually
84 # overflowing max_position_embeddings.
85 position_embeddings = self.position_embeddings(
86 position_ids + self.padding_idx + 1
87 )
88
89 token_type_embeddings = self.token_type_embeddings(token_type_ids)
90 embeddings = inputs_embeds + token_type_embeddings + position_embeddings
91 embeddings = self.LayerNorm(embeddings)
92 return embeddings
93
94
95 # Adapted from transformers
96 class RobertaClassificationHead(nn.Module):
97 """Head for sentence-level classification tasks."""
98
99 def __init__(self, model_config: "ModelConfig"):
100 super().__init__()
101 config = model_config.hf_config
102 head_dtype = model_config.head_dtype
103 self.dense = nn.Linear(config.hidden_size, config.hidden_size, dtype=head_dtype)
104 self.out_proj = nn.Linear(
105 config.hidden_size, config.num_labels, dtype=head_dtype
106 )
107
108 def forward(self, x: torch.Tensor) -> torch.Tensor:
109 # Token extraction has already been applied in `pooler.pooling`
110 x = self.dense(x)
111 x = torch.tanh(x)
112 x = self.out_proj(x)
113 return x
114
115
116 @default_pooling_type(seq_pooling_type="CLS")
117 class RobertaEmbeddingModel(BertEmbeddingModel):
118 """A model that uses Roberta to provide embedding functionalities."""
119
120 embedding_class: type[nn.Module] = RobertaEmbedding
121
122 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
123 super().__init__(vllm_config=vllm_config, prefix=prefix)
124 self.padding_idx: int = vllm_config.model_config.hf_config.pad_token_id
125
126 def forward(
127 self,
128 input_ids: torch.Tensor,
129 positions: torch.Tensor,
130 intermediate_tensors: IntermediateTensors | None = None,
131 inputs_embeds: torch.Tensor | None = None,
132 ) -> torch.Tensor:
133 return self.model(
134 input_ids=input_ids,
135 positions=positions,
136 inputs_embeds=inputs_embeds,
137 intermediate_tensors=intermediate_tensors,
138 )
139
140 def _build_model(
141 self, vllm_config: VllmConfig, prefix: str = ""
142 ) -> BertModel | BertWithRope:
143 hf_config = vllm_config.model_config.hf_config
144 if getattr(hf_config, "position_embedding_type", "absolute") == "absolute":
145 return BertModel(
146 vllm_config=vllm_config,
147 prefix=prefix,
148 embedding_class=self.embedding_class,
149 )
150 else:
151 return JinaRobertaModel(vllm_config=vllm_config, prefix=prefix)
152
153 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]):
154 weights_list = list(weights)
155 orig_to_new_prefix: dict[str, str | None] = {"lm_head.": None}
156 has_roberta_prefix = any(
157 name.startswith("roberta.") for name, _ in weights_list
158 )
159 if has_roberta_prefix:
160 # For models with the `roberta.` prefix e.g.
161 # `FacebookAI/roberta-base`
162 orig_to_new_prefix["roberta."] = "model."
163 else:
164 # For models without the `roberta.` prefix e.g.
165 # `sentence-transformers/stsb-roberta-base-v2`
166 orig_to_new_prefix[""] = "model."
167 mapper = WeightsMapper(orig_to_new_prefix=orig_to_new_prefix)
168
169 loader = AutoWeightsLoader(self)
170 return loader.load_weights(weights_list, mapper=mapper)
171
172
173 def filter_secondary_weights(
174 all_weights: Iterable[tuple[str, torch.Tensor]],
175 secondary_weights: list[str],
176 ) -> tuple[Iterable[tuple[str, torch.Tensor]], Iterable[tuple[str, torch.Tensor]]]:
177 all_weights1, all_weights2 = itertools.tee(all_weights)
178
179 def filtered(n):
180 return any(n.startswith(f) for f in secondary_weights)
181
182 return ((n, w) for n, w in all_weights1 if filtered(n)), (
183 (n, w) for n, w in all_weights2 if not filtered(n)
184 )
185
186
187 class BgeM3EmbeddingModel(RobertaEmbeddingModel):
188 """A model that extends RobertaEmbeddingModel with sparse embeddings.
189
190 This class supports loading an additional sparse_linear.pt file
191 to create sparse embeddings as described in https://arxiv.org/abs/2402.03216
192 """
193
194 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
195 self.hidden_size = vllm_config.model_config.hf_config.hidden_size
196
197 model_config = vllm_config.model_config
198 self.head_dtype = model_config.head_dtype
199 self.bos_token_id = model_config.hf_config.bos_token_id
200 self.eos_token_id = model_config.hf_config.eos_token_id
201
202 super().__init__(vllm_config=vllm_config, prefix=prefix)
203 self.secondary_weight_prefixes = ["sparse_linear.", "colbert_linear."]
204 self.secondary_weight_files = [
205 prefix + "pt" for prefix in self.secondary_weight_prefixes
206 ]
207
208 self.secondary_weights = [
209 DefaultModelLoader.Source(
210 model_or_path=vllm_config.model_config.model,
211 revision=vllm_config.model_config.revision,
212 prefix=prefix,
213 allow_patterns_overrides=[filename],
214 )
215 for filename, prefix in zip(
216 self.secondary_weight_files, self.secondary_weight_prefixes
217 )
218 ]
219
220 def _build_pooler(self, pooler_config: PoolerConfig) -> Pooler:
221 self.sparse_linear = nn.Linear(self.hidden_size, 1, dtype=self.head_dtype)
222 self.colbert_linear = nn.Linear(
223 self.hidden_size, self.hidden_size, dtype=self.head_dtype
224 )
225 embed_pooler = pooler_for_embed(pooler_config)
226 token_classify_pooler = BOSEOSFilter(
227 pooler_for_token_classify(
228 pooler_config,
229 pooling=AllPool(),
230 classifier=self.sparse_linear,
231 act_fn=torch.relu,
232 ),
233 self.bos_token_id,
234 self.eos_token_id,
235 )
236
237 return DispatchPooler(
238 {
239 "embed": embed_pooler,
240 "token_embed": BOSEOSFilter(
241 pooler_for_token_embed(pooler_config, self.colbert_linear),
242 self.bos_token_id,
243 # for some reason m3 only filters the bos for colbert vectors
244 ),
245 "token_classify": token_classify_pooler,
246 "embed&token_classify": BgeM3Pooler(
247 token_classify_pooler, embed_pooler
248 ),
249 }
250 )
251
252 def load_weights(self, all_weights: Iterable[tuple[str, torch.Tensor]]):
253 secondary, weights = filter_secondary_weights(
254 all_weights, self.secondary_weight_prefixes
255 )
256
257 super().load_weights(weights)
258
259 params_dict = dict(self.named_parameters())
260
261 for name, loaded_weight in secondary:
262 if any(
263 name.startswith(prefix) for prefix in self.secondary_weight_prefixes
264 ):
265 param = params_dict[name]
266 weight_loader = getattr(param, "weight_loader", default_weight_loader)
267 weight_loader(param, loaded_weight)
268
269
270 @default_pooling_type(seq_pooling_type="CLS")
271 class RobertaForSequenceClassification(nn.Module, SupportsCrossEncoding, SupportsLoRA):
272 """A model that uses Roberta to provide embedding functionalities.
273
274 This class encapsulates the BertModel and provides an interface for
275 embedding operations and customized pooling functions.
276
277 Attributes:
278 roberta: An instance of BertModel used for forward operations.
279 _pooler: An instance of Pooler used for pooling operations.
280
281 """
282
283 is_pooling_model = True
284 packed_modules_mapping = {"qkv_proj": ["query", "key", "value"]}
285 jina_to_vllm_mapper = WeightsMapper(
286 orig_to_new_substr={
287 "emb_ln": "embeddings.LayerNorm",
288 "layers": "layer",
289 "mixer.Wqkv": "attention.self.qkv_proj",
290 "mixer.out_proj": "attention.output.dense",
291 "norm1": "attention.output.LayerNorm",
292 "mlp.fc1": "intermediate.dense",
293 "mlp.fc2": "output.dense",
294 "norm2": "output.LayerNorm",
295 }
296 )
297
298 embedding_class: type[nn.Module] = RobertaEmbedding
299
300 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
301 super().__init__()
302 config = vllm_config.model_config.hf_config
303 self.config = config
304 self.padding_idx: int = config.pad_token_id
305
306 self.num_labels = config.num_labels
307 self.roberta = BertModel(
308 vllm_config=vllm_config,
309 prefix=maybe_prefix(prefix, "bert"),
310 embedding_class=self.embedding_class,
311 )
312 self.classifier = RobertaClassificationHead(vllm_config.model_config)
313
314 pooler_config = vllm_config.model_config.pooler_config
315 assert pooler_config is not None
316
317 self.pooler = DispatchPooler.for_seq_cls(
318 pooler_config,
319 classifier=self.classifier,
320 )
321
322 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]):
323 loader = AutoWeightsLoader(self)
324 return loader.load_weights(weights, mapper=self.jina_to_vllm_mapper)
325
326 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
327 return self.roberta.embed_input_ids(input_ids)
328
329 def forward(
330 self,
331 input_ids: torch.Tensor | None,
332 positions: torch.Tensor,
333 intermediate_tensors: IntermediateTensors | None = None,
334 inputs_embeds: torch.Tensor | None = None,
335 token_type_ids: torch.Tensor | None = None,
336 ) -> torch.Tensor:
337 if token_type_ids is not None:
338 assert self.roberta.config.vocab_size < (1 << TOKEN_TYPE_SHIFT)
339 assert input_ids is not None
340 _encode_token_type_ids(input_ids, token_type_ids)
341 return self.roberta(
342 input_ids=input_ids,
343 positions=positions,
344 inputs_embeds=inputs_embeds,
345 intermediate_tensors=intermediate_tensors,
346 )
347
348
349 @attn_type("encoder_only")
350 @default_pooling_type(tok_pooling_type="ALL")
351 class RobertaForTokenClassification(nn.Module):
352 """A model that uses Roberta to provide token classification.
353
354 Mirrors BertForTokenClassification, swapping in RobertaEmbedding for the
355 RoBERTa/XLM-RoBERTa position-embedding offset and weight layout.
356
357 Also registered as XLMRobertaForTokenClassification since XLM-RoBERTa
358 checkpoints share RoBERTa's architecture and `roberta.*` weight prefix.
359 """
360
361 is_pooling_model = True
362 embedding_class: type[nn.Module] = RobertaEmbedding
363
364 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
365 super().__init__()
366 config = vllm_config.model_config.hf_config
367 self.head_dtype = vllm_config.model_config.head_dtype
368 self.num_labels = config.num_labels
369 self.roberta = BertModel(
370 vllm_config=vllm_config,
371 prefix=maybe_prefix(prefix, "roberta"),
372 embedding_class=self.embedding_class,
373 )
374 self.classifier = nn.Linear(
375 config.hidden_size, config.num_labels, dtype=self.head_dtype
376 )
377
378 pooler_config = vllm_config.model_config.pooler_config
379 assert pooler_config is not None
380
381 self.pooler = pooler_for_token_classify(pooler_config)
382
383 def embed_input_ids(self, input_ids: torch.Tensor) -> torch.Tensor:
384 return self.roberta.embed_input_ids(input_ids)
385
386 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]):
387 loader = AutoWeightsLoader(self)
388 return loader.load_weights(weights)
389
390 def forward(
391 self,
392 input_ids: torch.Tensor | None,
393 positions: torch.Tensor,
394 intermediate_tensors: IntermediateTensors | None = None,
395 inputs_embeds: torch.Tensor | None = None,
396 token_type_ids: torch.Tensor | None = None,
397 ) -> torch.Tensor:
398 if token_type_ids is not None:
399 assert self.roberta.config.vocab_size < (1 << TOKEN_TYPE_SHIFT)
400 assert input_ids is not None
401 _encode_token_type_ids(input_ids, token_type_ids)
402
403 hidden_states = self.roberta(
404 input_ids=input_ids,
405 positions=positions,
406 inputs_embeds=inputs_embeds,
407 intermediate_tensors=intermediate_tensors,
408 )
409
410 hidden_states = hidden_states.to(self.head_dtype)
411 return self.classifier(hidden_states)