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, 14:45 UTC
[New Model]: jinaai/jina-embeddings-v3 (#16120)
wang.yuqi committed
Apr 8, 2025 at 23:39 UTC
1f5d13ab9f1232751a09cdb6b1cdbb4687393833
6 files changed
+297
-86
examples/offline_inference/embed_jina_embeddings_v3.py
new
+50
@@ -0,0 +1,50 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+
3
+from argparse import Namespace
4
+
5
+from vllm import LLM, EngineArgs
6
+from vllm.utils import FlexibleArgumentParser
7
+
8
+
9
+def main(args: Namespace):
10
+ # Sample prompts.
11
+ prompts = [
12
+ "Follow the white rabbit.", # English
13
+ "Sigue al conejo blanco.", # Spanish
14
+ "Suis le lapin blanc.", # French
15
+ "跟着白兔走。", # Chinese
16
+ "اتبع الأرنب الأبيض.", # Arabic
17
+ "Folge dem weißen Kaninchen.", # German
18
+ ]
19
+
20
+ # Create an LLM.
21
+ # You should pass task="embed" for embedding models
22
+ model = LLM(**vars(args))
23
+
24
+ # Generate embedding. The output is a list of EmbeddingRequestOutputs.
25
+ # Only text matching task is supported for now. See #16120
26
+ outputs = model.embed(prompts)
27
+
28
+ # Print the outputs.
29
+ print("\nGenerated Outputs:")
30
+ print("Only text matching task is supported for now. See #16120")
31
+ print("-" * 60)
32
+ for prompt, output in zip(prompts, outputs):
33
+ embeds = output.outputs.embedding
34
+ embeds_trimmed = ((str(embeds[:16])[:-1] +
35
+ ", ...]") if len(embeds) > 16 else embeds)
36
+ print(f"Prompt: {prompt!r} \n"
37
+ f"Embeddings for text matching: {embeds_trimmed} "
38
+ f"(size={len(embeds)})")
39
+ print("-" * 60)
40
+
41
+
42
+if __name__ == "__main__":
43
+ parser = FlexibleArgumentParser()
44
+ parser = EngineArgs.add_cli_args(parser)
45
+ # Set example specific arguments
46
+ parser.set_defaults(model="jinaai/jina-embeddings-v3",
47
+ task="embed",
48
+ trust_remote_code=True)
49
+ args = parser.parse_args()
50
+ main(args)
tests/conftest.py
+3
-2
@@ -671,8 +671,9 @@ class HfRunner:
671
return [(output_ids, output_str, output_logprobs)
672
for output_ids, output_str, output_logprobs in outputs]
673
674
- def encode(self, prompts: list[str]) -> list[list[torch.Tensor]]:
675
- return self.model.encode(prompts)
674
+ def encode(self, prompts: list[str], *args,
675
+ **kwargs) -> list[list[torch.Tensor]]:
676
+ return self.model.encode(prompts, *args, **kwargs)
677
678
def predict(self, prompts: list[list[str]]) -> torch.Tensor:
679
return self.model.predict(prompts, convert_to_tensor=True)
tests/models/embedding/language/test_jina.py
renamed
+61
-3
@@ -2,13 +2,15 @@
2
# ruff: noqa: E501
3
"""Compare the scoring outputs of HF and vLLM models.
4
5
-Run `pytest tests/models/embedding/language/test_jina_reranker_v2.py`.
5
+Run `pytest tests/models/embedding/language/test_jina.py`.
6
"""
7
import math
8
9
import pytest
10
11
-MODELS = [
11
+from tests.models.embedding.utils import check_embeddings_close
12
+
13
+SCORING_MODELS = [
14
"jinaai/jina-reranker-v2-base-multilingual", # Roberta
15
]
16
@@ -27,8 +29,21 @@ TEXTS_2 = [
29
"新しいメイクのトレンドは鮮やかな色と革新的な技術に焦点を当てています",
30
]
31
32
+EMBEDDING_MODELS = [
33
+ "jinaai/jina-embeddings-v3",
34
+]
35
+
36
+EMBEDDING_PROMPTS = [
37
+ "Follow the white rabbit.", # English
38
+ "Sigue al conejo blanco.", # Spanish
39
+ "Suis le lapin blanc.", # French
40
+ "跟着白兔走。", # Chinese
41
+ "اتبع الأرنب الأبيض.", # Arabic
42
+ "Folge dem weißen Kaninchen.", # German
43
+]
44
+
45
31
-@pytest.fixture(scope="module", params=MODELS)
46
+@pytest.fixture(scope="module", params=SCORING_MODELS)
47
def model_name(request):
48
yield request.param
49
@@ -68,3 +83,46 @@ def test_llm_1_to_N(vllm_runner, hf_runner, model_name, dtype: str):
83
84
assert math.isclose(hf_outputs[0], vllm_outputs[0], rel_tol=0.01)
85
assert math.isclose(hf_outputs[1], vllm_outputs[1], rel_tol=0.01)
86
+
87
+
88
+@pytest.fixture(scope="module", params=EMBEDDING_MODELS)
89
+def emb_model_name(request):
90
+ yield request.param
91
+
92
+
93
+def test_is_matryoshka(vllm_runner, emb_model_name):
94
+ with vllm_runner(emb_model_name, task="embed",
95
+ max_model_len=None) as vllm_model:
96
+ assert vllm_model.model.llm_engine.model_config.is_matryoshka
97
+
98
+
99
+@pytest.mark.parametrize("model", EMBEDDING_MODELS)
100
+@pytest.mark.parametrize("dtype", ["half"])
101
+def test_embeddings(
102
+ hf_runner,
103
+ vllm_runner,
104
+ model,
105
+ dtype: str,
106
+ monkeypatch,
107
+) -> None:
108
+
109
+ example_prompts = EMBEDDING_PROMPTS
110
+
111
+ with hf_runner(
112
+ model,
113
+ dtype=dtype,
114
+ is_sentence_transformer=True,
115
+ ) as hf_model:
116
+ hf_outputs = hf_model.encode(example_prompts, task="text-matching")
117
+
118
+ with vllm_runner(model, task="embed", dtype=dtype,
119
+ max_model_len=None) as vllm_model:
120
+ vllm_outputs = vllm_model.encode(example_prompts)
121
+
122
+ check_embeddings_close(
123
+ embeddings_0_lst=hf_outputs,
124
+ embeddings_1_lst=vllm_outputs,
125
+ name_0="hf",
126
+ name_1="vllm",
127
+ tol=1e-2,
128
+ )
vllm/config.py
+5
@@ -1130,6 +1130,11 @@ class ModelConfig:
1130
architectures = getattr(self.hf_config, "architectures", [])
1131
return ModelRegistry.is_v1_compatible(architectures)
1132
1133
+ @property
1134
+ def is_matryoshka(self) -> bool:
1135
+ return (hasattr(self.hf_config, "matryoshka_dimensions")
1136
+ or getattr(self.hf_config, "is_matryoshka", False))
1137
+
1138
1139
class CacheConfig:
1140
"""Configuration for the KV cache.
vllm/model_executor/models/bert.py
+49
-17
@@ -18,6 +18,7 @@ from vllm.model_executor.layers.linear import (ColumnParallelLinear,
18
from vllm.model_executor.layers.pooler import (CrossEncodingPooler, Pooler,
19
PoolingType)
20
from vllm.model_executor.layers.quantization import QuantizationConfig
21
+from vllm.model_executor.layers.rotary_embedding import get_rope
22
from vllm.model_executor.layers.vocab_parallel_embedding import (
23
VocabParallelEmbedding)
24
from vllm.model_executor.model_loader.weight_utils import default_weight_loader
@@ -38,19 +39,24 @@ class BertEmbedding(nn.Module):
39
self.size = config.hidden_size
40
self.word_embeddings = VocabParallelEmbedding(config.vocab_size,
41
config.hidden_size)
41
- self.position_embeddings = VocabParallelEmbedding(
42
- config.max_position_embeddings, config.hidden_size)
42
+
43
self.token_type_embeddings = VocabParallelEmbedding(
44
config.type_vocab_size, config.hidden_size)
45
self.LayerNorm = nn.LayerNorm(config.hidden_size,
46
eps=config.layer_norm_eps)
47
- self.position_ids = nn.Parameter(
48
- torch.empty((1, config.max_position_embeddings)), )
47
48
self.position_embedding_type = config.position_embedding_type
51
- if self.position_embedding_type != "absolute":
52
- raise ValueError("Only 'absolute' position_embedding_type" +
53
- " is supported")
49
+ if self.position_embedding_type == "absolute":
50
+ self.position_embeddings = VocabParallelEmbedding(
51
+ config.max_position_embeddings, config.hidden_size)
52
+ self.position_ids = nn.Parameter(
53
+ torch.empty((1, config.max_position_embeddings)), )
54
+ elif self.position_embedding_type == "rotary":
55
+ self.position_embeddings = None
56
+ self.position_ids = None
57
+ else:
58
+ raise ValueError("Only 'absolute' and 'rotary' " +
59
+ "position_embedding_type is supported")
60
61
def forward(
62
self,
@@ -64,9 +70,6 @@ class BertEmbedding(nn.Module):
70
# Input embeddings.
71
inputs_embeds = self.word_embeddings(input_ids)
72
67
- # Position embeddings.
68
- position_embeddings = self.position_embeddings(position_ids)
69
-
73
if token_type_ids is None:
74
token_type_ids = torch.zeros(input_shape,
75
dtype=torch.long,
@@ -74,7 +77,12 @@ class BertEmbedding(nn.Module):
77
78
token_type_embeddings = self.token_type_embeddings(token_type_ids)
79
77
- embeddings = inputs_embeds + token_type_embeddings + position_embeddings
80
+ embeddings = inputs_embeds + token_type_embeddings
81
+
82
+ if self.position_embedding_type == "absolute":
83
+ position_embeddings = self.position_embeddings(position_ids)
84
+ embeddings += position_embeddings
85
+
86
embeddings = self.LayerNorm(embeddings)
87
return embeddings
88
@@ -98,7 +106,10 @@ class BertPooler(nn.Module):
106
@support_torch_compile
107
class BertEncoder(nn.Module):
108
101
- def __init__(self, vllm_config: VllmConfig, prefix: str = ""):
109
+ def __init__(self,
110
+ vllm_config: VllmConfig,
111
+ rotary_kwargs: Optional[dict] = None,
112
+ prefix: str = ""):
113
super().__init__()
114
config = vllm_config.model_config.hf_config
115
cache_config = vllm_config.cache_config
@@ -107,16 +118,18 @@ class BertEncoder(nn.Module):
118
BertLayer(config=config,
119
cache_config=cache_config,
120
quant_config=quant_config,
121
+ rotary_kwargs=rotary_kwargs,
122
prefix=f"{prefix}.layer.{layer_idx}")
123
for layer_idx in range(config.num_hidden_layers)
124
])
125
126
def forward(
127
self,
128
+ positions: torch.Tensor,
129
hidden_states: torch.Tensor,
130
) -> torch.Tensor:
131
for layer in self.layer:
119
- hidden_states = layer(hidden_states)
132
+ hidden_states = layer(positions, hidden_states)
133
return hidden_states
134
135
@@ -126,6 +139,7 @@ class BertLayer(nn.Module):
139
config: BertConfig,
140
cache_config: Optional[CacheConfig] = None,
141
quant_config: Optional[QuantizationConfig] = None,
142
+ rotary_kwargs: Optional[dict] = None,
143
prefix: str = ""):
144
super().__init__()
145
@@ -135,6 +149,7 @@ class BertLayer(nn.Module):
149
layer_norm_eps=config.layer_norm_eps,
150
cache_config=cache_config,
151
quant_config=quant_config,
152
+ rotary_kwargs=rotary_kwargs,
153
prefix=f"{prefix}.attention")
154
155
self.intermediate = BertIntermediate(
@@ -150,8 +165,8 @@ class BertLayer(nn.Module):
165
quant_config=quant_config,
166
prefix=f"{prefix}.output")
167
153
- def forward(self, hidden_states: torch.Tensor):
154
- attn_output = self.attention(hidden_states)
168
+ def forward(self, positions: torch.Tensor, hidden_states: torch.Tensor):
169
+ attn_output = self.attention(positions, hidden_states)
170
intermediate_output = self.intermediate(attn_output)
171
output = self.output(intermediate_output, attn_output)
172
return output
@@ -166,6 +181,7 @@ class BertAttention(nn.Module):
181
layer_norm_eps: float,
182
cache_config: Optional[CacheConfig] = None,
183
quant_config: Optional[QuantizationConfig] = None,
184
+ rotary_kwargs: Optional[dict] = None,
185
prefix: str = "",
186
):
187
super().__init__()
@@ -174,6 +190,7 @@ class BertAttention(nn.Module):
190
num_attention_heads=num_attention_heads,
191
cache_config=cache_config,
192
quant_config=quant_config,
193
+ rotary_kwargs=rotary_kwargs,
194
prefix=f"{prefix}.output")
195
196
self.output = BertSelfOutput(hidden_size=hidden_size,
@@ -183,9 +200,10 @@ class BertAttention(nn.Module):
200
201
def forward(
202
self,
203
+ positions: torch.Tensor,
204
hidden_states: torch.Tensor,
205
) -> torch.Tensor:
188
- self_output = self.self(hidden_states)
206
+ self_output = self.self(positions, hidden_states)
207
return self.output(self_output, hidden_states)
208
209
@@ -197,6 +215,7 @@ class BertSelfAttention(nn.Module):
215
num_attention_heads: int,
216
cache_config: Optional[CacheConfig] = None,
217
quant_config: Optional[QuantizationConfig] = None,
218
+ rotary_kwargs: Optional[dict] = None,
219
prefix: str = "",
220
):
221
super().__init__()
@@ -225,6 +244,11 @@ class BertSelfAttention(nn.Module):
244
quant_config=quant_config,
245
prefix=f"{prefix}.qkv_proj")
246
247
+ if rotary_kwargs:
248
+ self.rotary_emb = get_rope(**rotary_kwargs)
249
+ else:
250
+ self.rotary_emb = None
251
+
252
self.attn = Attention(num_heads=self.num_heads,
253
head_size=self.head_dim,
254
scale=self.scaling,
@@ -236,10 +260,15 @@ class BertSelfAttention(nn.Module):
260
261
def forward(
262
self,
263
+ positions: torch.Tensor,
264
hidden_states: torch.Tensor,
265
) -> torch.Tensor:
266
qkv, _ = self.qkv_proj(hidden_states)
267
q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
268
+
269
+ if self.rotary_emb:
270
+ q, k = self.rotary_emb(positions, q, k)
271
+
272
output = self.attn(q, k, v)
273
return output
274
@@ -321,11 +350,13 @@ class BertModel(nn.Module, SupportsQuant):
350
vllm_config: VllmConfig,
351
prefix: str = "",
352
embedding_class: type = BertEmbedding,
353
+ rotary_kwargs: Optional[dict] = None,
354
add_pooling_layer: bool = False):
355
super().__init__()
356
config = vllm_config.model_config.hf_config
357
self.embeddings = embedding_class(config)
358
self.encoder = BertEncoder(vllm_config=vllm_config,
359
+ rotary_kwargs=rotary_kwargs,
360
prefix=f"{prefix}.encoder")
361
self.pooler = BertPooler(config) if add_pooling_layer else None
362
@@ -347,7 +378,7 @@ class BertModel(nn.Module, SupportsQuant):
378
seq_lens=attn_metadata.seq_lens_tensor,
379
position_ids=position_ids,
380
token_type_ids=token_type_ids)
350
- return self.encoder(hidden_states)
381
+ return self.encoder(position_ids, hidden_states)
382
383
def load_weights(self, weights: Iterable[Tuple[str,
384
torch.Tensor]]) -> Set[str]:
@@ -401,6 +432,7 @@ class BertEmbeddingModel(nn.Module, SupportsV0Only, SupportsQuant):
432
def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
433
super().__init__()
434
pooler_config = vllm_config.model_config.pooler_config
435
+ self.config = vllm_config.model_config.hf_config
436
self.model = self._build_model(vllm_config=vllm_config,
437
prefix=maybe_prefix(prefix, "model"))
438
self._pooler = self._build_pooler(pooler_config)
vllm/model_executor/models/roberta.py
+129
-64
@@ -22,30 +22,6 @@ from vllm.transformers_utils.config import (
22
from .interfaces import SupportsCrossEncoding, SupportsV0Only
23
24
25
-def roberta_task_weights_filter(
26
- all_weights: Iterable[Tuple[str, torch.Tensor]]
27
-) -> Tuple[Iterable[Tuple[str, torch.Tensor]], Iterable[Tuple[str,
28
- torch.Tensor]]]:
29
- """
30
- Separate task-specific weights that are applied on top
31
- of the encoder-decoder bert base.
32
- To do so, return two generators over the original iterator.
33
- Also, remove the "roberta." prefix to make it loadable
34
- from vanilla BertModel.
35
- """
36
- # Copy of a lazy iterator without in-memory overhead so both
37
- # iterators can be iterated upon independently.
38
- all_weights1, all_weights2 = itertools.tee(all_weights)
39
-
40
- def encoder_decoder_weights():
41
- for name, weight in all_weights1:
42
- if name.startswith("roberta."):
43
- yield (name[len("roberta."):], weight)
44
-
45
- return encoder_decoder_weights(), ((n, w) for n, w in all_weights2
46
- if not n.startswith("roberta."))
47
-
48
-
25
class RobertaEmbedding(nn.Module):
26
27
def __init__(self, config: RobertaConfig):
@@ -119,30 +95,6 @@ class RobertaEmbedding(nn.Module):
95
return embeddings
96
97
122
-# Adapted from transformers
123
-def create_position_ids_from_input_ids(input_ids,
124
- padding_idx,
125
- past_key_values_length=0):
126
- """
127
- Replace non-padding symbols with their position numbers.
128
- Position numbers begin at padding_idx+1. Padding symbols
129
- are ignored. This is modified from fairseq's `utils.make_positions`.
130
-
131
- Args:
132
- x: torch.Tensor x:
133
-
134
- Returns: torch.Tensor
135
- """
136
- # The series of casts and type-conversions here are carefully
137
- # balanced to both work with ONNX export and XLA.
138
- mask = input_ids.ne(padding_idx).int()
139
-
140
- incremental_indices = (torch.cumsum(mask, dim=0).type_as(mask) +
141
- past_key_values_length) * mask
142
-
143
- return incremental_indices.long() + padding_idx
144
-
145
-
98
# Adapted from transformers
99
class RobertaClassificationHead(nn.Module):
100
"""Head for sentence-level classification tasks."""
@@ -174,15 +126,38 @@ class RobertaEmbeddingModel(BertEmbeddingModel):
126
def _build_model(self,
127
vllm_config: VllmConfig,
128
prefix: str = "") -> BertModel:
177
- return BertModel(vllm_config=vllm_config,
178
- prefix=prefix,
179
- embedding_class=RobertaEmbedding)
129
+ if (vllm_config.model_config.hf_config.position_embedding_type ==
130
+ "rotary"):
131
+ config = vllm_config.model_config.hf_config
132
+ head_dim = config.hidden_size // config.num_attention_heads
133
+
134
+ rotary_kwargs = {
135
+ "head_size": head_dim,
136
+ "rotary_dim": getattr(config, "rotary_emb_dim", head_dim),
137
+ "max_position": config.max_position_embeddings,
138
+ "base": config.rotary_emb_base,
139
+ "rope_scaling": getattr(config, "rope_scaling", None)
140
+ }
141
+
142
+ return BertModel(vllm_config=vllm_config,
143
+ rotary_kwargs=rotary_kwargs,
144
+ prefix=prefix)
145
+ else:
146
+ return BertModel(vllm_config=vllm_config,
147
+ prefix=prefix,
148
+ embedding_class=RobertaEmbedding)
149
150
def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]]):
151
+ if getattr(self.config, "lora_rank", 0) > 0:
152
+ scaling = self.config.lora_alpha / self.config.lora_rank
153
+ weights = jina_merge_lora_weights(weights, scaling)
154
+
155
weights = self.hf_to_vllm_mapper.apply(weights)
156
# Separate weights in "roberta"-prefixed and all else (not in memory).
157
# For use with models like FacebookAI/roberta-base.
158
bert_weights, task_weights = roberta_task_weights_filter(weights)
159
+ bert_weights = jina_to_vllm_mapper.apply(bert_weights)
160
+
161
loaded = self.model.load_weights(bert_weights)
162
if not len(loaded):
163
# Fix for models like `sentence-transformers/stsb-roberta-base-v2`
@@ -203,18 +178,6 @@ class RobertaForSequenceClassification(nn.Module, SupportsCrossEncoding,
178
_pooler: An instance of Pooler used for pooling operations.
179
"""
180
206
- jina_to_vllm_mapper = WeightsMapper(
207
- orig_to_new_substr={
208
- 'emb_ln': "embeddings.LayerNorm",
209
- 'layers': "layer",
210
- 'mixer.Wqkv': "attention.self.qkv_proj",
211
- 'mixer.out_proj': "attention.output.dense",
212
- 'norm1': "attention.output.LayerNorm",
213
- 'mlp.fc1': "intermediate.dense",
214
- 'mlp.fc2': "output.dense",
215
- 'norm2': "output.LayerNorm",
216
- })
217
-
181
def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
182
super().__init__()
183
config = vllm_config.model_config.hf_config
@@ -232,7 +195,7 @@ class RobertaForSequenceClassification(nn.Module, SupportsCrossEncoding,
195
196
def load_weights(self, weights: Iterable[Tuple[str, torch.Tensor]]):
197
bert_weights, task_weights = roberta_task_weights_filter(weights)
235
- bert_weights = self.jina_to_vllm_mapper.apply(bert_weights)
198
+ bert_weights = jina_to_vllm_mapper.apply(bert_weights)
199
200
self.roberta.load_weights(bert_weights)
201
@@ -265,3 +228,105 @@ class RobertaForSequenceClassification(nn.Module, SupportsCrossEncoding,
228
inputs_embeds=inputs_embeds,
229
intermediate_tensors=intermediate_tensors,
230
token_type_ids=token_type_ids)
231
+
232
+
233
+# Adapted from transformers
234
+def create_position_ids_from_input_ids(input_ids,
235
+ padding_idx,
236
+ past_key_values_length=0):
237
+ """
238
+ Replace non-padding symbols with their position numbers.
239
+ Position numbers begin at padding_idx+1. Padding symbols
240
+ are ignored. This is modified from fairseq's `utils.make_positions`.
241
+
242
+ Args:
243
+ x: torch.Tensor x:
244
+
245
+ Returns: torch.Tensor
246
+ """
247
+ # The series of casts and type-conversions here are carefully
248
+ # balanced to both work with ONNX export and XLA.
249
+ mask = input_ids.ne(padding_idx).int()
250
+
251
+ incremental_indices = (torch.cumsum(mask, dim=0).type_as(mask) +
252
+ past_key_values_length) * mask
253
+
254
+ return incremental_indices.long() + padding_idx
255
+
256
+
257
+def roberta_task_weights_filter(
258
+ all_weights: Iterable[Tuple[str, torch.Tensor]]
259
+) -> Tuple[Iterable[Tuple[str, torch.Tensor]], Iterable[Tuple[str,
260
+ torch.Tensor]]]:
261
+ """
262
+ Separate task-specific weights that are applied on top
263
+ of the encoder-decoder bert base.
264
+ To do so, return two generators over the original iterator.
265
+ Also, remove the "roberta." prefix to make it loadable
266
+ from vanilla BertModel.
267
+ """
268
+ # Copy of a lazy iterator without in-memory overhead so both
269
+ # iterators can be iterated upon independently.
270
+ all_weights1, all_weights2 = itertools.tee(all_weights)
271
+
272
+ def encoder_decoder_weights():
273
+ for name, weight in all_weights1:
274
+ if name.startswith("roberta."):
275
+ yield (name[len("roberta."):], weight)
276
+
277
+ return encoder_decoder_weights(), ((n, w) for n, w in all_weights2
278
+ if not n.startswith("roberta."))
279
+
280
+
281
+jina_to_vllm_mapper = WeightsMapper(
282
+ orig_to_new_substr={
283
+ 'emb_ln': "embeddings.LayerNorm",
284
+ 'layers': "layer",
285
+ 'mixer.Wqkv': "attention.self.qkv_proj",
286
+ 'mixer.out_proj': "attention.output.dense",
287
+ 'norm1': "attention.output.LayerNorm",
288
+ 'mlp.fc1': "intermediate.dense",
289
+ 'mlp.fc2': "output.dense",
290
+ 'norm2': "output.LayerNorm",
291
+ })
292
+
293
+
294
+@torch.inference_mode()
295
+def jina_merge_lora_weights(weights: Iterable[Tuple[str, torch.Tensor]],
296
+ scaling: float = 1.0):
297
+ # use for jina-embeddings-v3
298
+ # Merge Lora weights into a single weight tensor.
299
+ # This is a temporary solution until we have a better way to handle
300
+
301
+ weights = {name: weight for name, weight in weights}
302
+
303
+ o = ".original"
304
+ a = ".0.lora_A"
305
+ b = ".0.lora_B"
306
+
307
+ # text-matching
308
+ i = -1
309
+
310
+ for name in list(weights.keys()):
311
+ if o in name:
312
+ dtype = weights[name].dtype
313
+ shape = weights[name].shape
314
+ weight_name = name[:-len(o)]
315
+
316
+ if "embeddings" in weight_name:
317
+ B = weights[weight_name + a][i].cuda().float()
318
+ A = weights[weight_name + b][i].cuda().float()
319
+ else:
320
+ B = weights[weight_name + b][i].cuda().float()
321
+ A = weights[weight_name + a][i].cuda().float()
322
+
323
+ weight = (weights[weight_name + o].cuda() +
324
+ torch.matmul(B, A).view(shape) * scaling)
325
+ weight = weight.cpu().to(dtype)
326
+
327
+ weights[weight_name.replace(".parametrizations", "")] = weight
328
+
329
+ del weights[weight_name + o], weights[weight_name +
330
+ a], weights[weight_name + b]
331
+
332
+ return [(name, weight) for name, weight in weights.items()]