Co-authored-by: Satyam Kumar <satyamk@linkedin.com>
This commit is contained in:
co-authored by
Satyam Kumar
parent
c11b34d599
commit
9fc3e8aac7
@@ -205,6 +205,14 @@ class ModelConfig:
|
||||
self.hf_config, "image_token_id", None
|
||||
) or getattr(self.hf_config, "image_token_index", None)
|
||||
|
||||
# matryoshka embeddings
|
||||
self.matryoshka_dimensions = getattr(
|
||||
self.hf_config, "matryoshka_dimensions", None
|
||||
)
|
||||
self.is_matryoshka = self.matryoshka_dimensions or getattr(
|
||||
self.hf_config, "is_matryoshka", False
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def from_server_args(
|
||||
server_args: ServerArgs,
|
||||
|
||||
@@ -312,6 +312,7 @@ class Engine(EngineBase):
|
||||
image_data: Optional[MultimodalDataInputFormat] = None,
|
||||
audio_data: Optional[MultimodalDataInputFormat] = None,
|
||||
video_data: Optional[MultimodalDataInputFormat] = None,
|
||||
dimensions: Optional[int] = None,
|
||||
) -> Dict:
|
||||
"""
|
||||
The arguments of this function is the same as `sglang/srt/managers/io_struct.py::EmbeddingReqInput`.
|
||||
@@ -322,6 +323,7 @@ class Engine(EngineBase):
|
||||
image_data=image_data,
|
||||
audio_data=audio_data,
|
||||
video_data=video_data,
|
||||
dimensions=dimensions,
|
||||
)
|
||||
generator = self.tokenizer_manager.generate_request(obj, None)
|
||||
ret = self.loop.run_until_complete(generator.__anext__())
|
||||
@@ -333,6 +335,7 @@ class Engine(EngineBase):
|
||||
image_data: Optional[MultimodalDataInputFormat] = None,
|
||||
audio_data: Optional[MultimodalDataInputFormat] = None,
|
||||
video_data: Optional[MultimodalDataInputFormat] = None,
|
||||
dimensions: Optional[int] = None,
|
||||
) -> Dict:
|
||||
"""
|
||||
Asynchronous version of encode method.
|
||||
@@ -345,6 +348,7 @@ class Engine(EngineBase):
|
||||
image_data=image_data,
|
||||
audio_data=audio_data,
|
||||
video_data=video_data,
|
||||
dimensions=dimensions,
|
||||
)
|
||||
generator = self.tokenizer_manager.generate_request(obj, None)
|
||||
return await generator.__anext__()
|
||||
|
||||
@@ -126,6 +126,7 @@ class OpenAIServingEmbedding(OpenAIServingBase):
|
||||
**prompt_kwargs,
|
||||
rid=request.rid,
|
||||
priority=request.priority,
|
||||
dimensions=request.dimensions,
|
||||
)
|
||||
|
||||
return adapted_request, request
|
||||
|
||||
@@ -20,7 +20,9 @@ class PoolingType(IntEnum):
|
||||
|
||||
@dataclass
|
||||
class EmbeddingPoolerOutput:
|
||||
embeddings: torch.Tensor
|
||||
# Pooler can return list[tensor] instead of tensor if the dimension of each tensor in the batch is different
|
||||
# due to different per-request matryoshka dim truncation
|
||||
embeddings: torch.Tensor | list[torch.Tensor]
|
||||
|
||||
|
||||
class Pooler(nn.Module):
|
||||
@@ -42,6 +44,7 @@ class Pooler(nn.Module):
|
||||
def forward(
|
||||
self, hidden_states: torch.Tensor, forward_batch: ForwardBatch
|
||||
) -> EmbeddingPoolerOutput:
|
||||
|
||||
if self.pooling_type == PoolingType.LAST:
|
||||
last_token_indices = torch.cumsum(forward_batch.extend_seq_lens, dim=0) - 1
|
||||
pooled_data = hidden_states[last_token_indices]
|
||||
@@ -53,8 +56,24 @@ class Pooler(nn.Module):
|
||||
else:
|
||||
raise ValueError(f"Invalid pooling type: {self.pooling_type}")
|
||||
|
||||
if forward_batch.dimensions is not None:
|
||||
all_same_dimensions = len(set(forward_batch.dimensions)) == 1
|
||||
if all_same_dimensions:
|
||||
pooled_data = pooled_data[..., : forward_batch.dimensions[0]]
|
||||
else:
|
||||
pooled_data = [
|
||||
tensor[..., :dim]
|
||||
for tensor, dim in zip(pooled_data, forward_batch.dimensions)
|
||||
]
|
||||
|
||||
if self.normalize:
|
||||
pooled_data = nn.functional.normalize(pooled_data, p=2, dim=1)
|
||||
if isinstance(pooled_data, list):
|
||||
pooled_data = [
|
||||
nn.functional.normalize(tensor, p=2, dim=-1)
|
||||
for tensor in pooled_data
|
||||
]
|
||||
else:
|
||||
pooled_data = nn.functional.normalize(pooled_data, p=2, dim=-1)
|
||||
|
||||
return EmbeddingPoolerOutput(embeddings=pooled_data)
|
||||
|
||||
|
||||
@@ -695,6 +695,9 @@ class EmbeddingReqInput(BaseReq):
|
||||
# tracing context
|
||||
trace_context: Optional[Dict] = None
|
||||
|
||||
# The number of dimensions the resulting output embeddings should have. It is applicable for Matryoshka Embeddings.
|
||||
dimensions: Optional[int] = None
|
||||
|
||||
def normalize_batch_and_arguments(self):
|
||||
# at least one of text, input_ids, or image should be provided
|
||||
if self.text is None and self.input_ids is None and self.image_data is None:
|
||||
@@ -771,6 +774,7 @@ class EmbeddingReqInput(BaseReq):
|
||||
video_data=self.video_data[i] if self.video_data is not None else None,
|
||||
sampling_params=self.sampling_params[i],
|
||||
rid=self.rid[i],
|
||||
dimensions=self.dimensions,
|
||||
http_worker_ipc=self.http_worker_ipc,
|
||||
)
|
||||
|
||||
@@ -791,6 +795,8 @@ class TokenizedEmbeddingReqInput(BaseReq):
|
||||
data_parallel_rank: Optional[int] = None
|
||||
# Priority for the request
|
||||
priority: Optional[int] = None
|
||||
# The number of dimensions the resulting output embeddings should have. It is applicable for Matryoshka Embeddings.
|
||||
dimensions: Optional[int] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
@@ -442,6 +442,7 @@ class Req:
|
||||
priority: Optional[int] = None,
|
||||
metrics_collector: Optional[SchedulerMetricsCollector] = None,
|
||||
extra_key: Optional[str] = None,
|
||||
dimensions: Optional[int] = None,
|
||||
http_worker_ipc: Optional[str] = None,
|
||||
):
|
||||
# Input and output info
|
||||
@@ -650,6 +651,9 @@ class Req:
|
||||
self.tmp_end_idx: int = -1
|
||||
self.metadata_buffer_index: int = -1
|
||||
|
||||
# For Matryoshka embeddings
|
||||
self.dimensions = dimensions
|
||||
|
||||
@property
|
||||
def seqlen(self):
|
||||
return len(self.origin_input_ids) + len(self.output_ids)
|
||||
@@ -1014,6 +1018,9 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
|
||||
encoder_lens_cpu: Optional[List[int]] = None
|
||||
encoder_out_cache_loc: Optional[torch.Tensor] = None
|
||||
|
||||
# For matryoshka embeddings
|
||||
dimensions: Optional[list[int]] = None
|
||||
|
||||
# For split prefill
|
||||
split_index: int = 0
|
||||
split_prefill_finished: bool = False
|
||||
@@ -1177,6 +1184,15 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
|
||||
prefix_lens = [len(r.prefix_indices) for r in reqs]
|
||||
extend_lens = [r.extend_input_len for r in reqs]
|
||||
|
||||
# For matryoshka embeddings
|
||||
if self.model_config.is_matryoshka and any(
|
||||
r.dimensions is not None for r in reqs
|
||||
):
|
||||
self.dimensions = [
|
||||
r.dimensions if r.dimensions else self.model_config.hidden_size
|
||||
for r in reqs
|
||||
]
|
||||
|
||||
token_type_ids = [
|
||||
r.token_type_ids for r in reqs if r.token_type_ids is not None
|
||||
]
|
||||
@@ -1765,6 +1781,7 @@ class ScheduleBatch(ScheduleBatchDisaggregationDecodeMixin):
|
||||
),
|
||||
extend_input_logprob_token_ids=self.extend_input_logprob_token_ids,
|
||||
is_prefill_only=self.is_prefill_only,
|
||||
dimensions=self.dimensions,
|
||||
)
|
||||
|
||||
def copy(self):
|
||||
@@ -1873,5 +1890,8 @@ class ModelWorkerBatch:
|
||||
capture_hidden_mode: CaptureHiddenMode = None
|
||||
hicache_consumer_index: int = -1
|
||||
|
||||
# For matryoshka embeddings
|
||||
dimensions: Optional[list[int]] = None
|
||||
|
||||
# Whether this batch is prefill-only (no token generation needed)
|
||||
is_prefill_only: bool = False
|
||||
|
||||
@@ -1475,6 +1475,7 @@ class Scheduler(
|
||||
recv_req.sampling_params,
|
||||
token_type_ids=recv_req.token_type_ids,
|
||||
priority=recv_req.priority,
|
||||
dimensions=recv_req.dimensions,
|
||||
http_worker_ipc=recv_req.http_worker_ipc,
|
||||
)
|
||||
req.tokenizer = self.tokenizer
|
||||
|
||||
@@ -203,7 +203,10 @@ class SchedulerOutputProcessorMixin:
|
||||
i
|
||||
].item()
|
||||
else:
|
||||
embeddings = embeddings.tolist()
|
||||
if isinstance(embeddings, torch.Tensor):
|
||||
embeddings = embeddings.tolist()
|
||||
else:
|
||||
embeddings = [tensor.tolist() for tensor in embeddings]
|
||||
|
||||
# Check finish conditions
|
||||
for i, req in enumerate(batch.reqs):
|
||||
|
||||
@@ -666,6 +666,10 @@ class TokenizerManager(TokenizerCommunicatorMixin):
|
||||
)
|
||||
raise ValueError(error_msg)
|
||||
|
||||
# Matryoshka embeddings validations
|
||||
if isinstance(obj, EmbeddingReqInput):
|
||||
self._validate_for_matryoshka_dim(obj)
|
||||
|
||||
if isinstance(obj, GenerateReqInput):
|
||||
if (
|
||||
obj.return_hidden_states
|
||||
@@ -684,6 +688,34 @@ class TokenizerManager(TokenizerCommunicatorMixin):
|
||||
"Please set `--enable-custom-logit-processor` to enable this feature."
|
||||
)
|
||||
|
||||
def _validate_for_matryoshka_dim(self, obj: EmbeddingReqInput) -> None:
|
||||
"""Validate the request for Matryoshka dim if it has the field set."""
|
||||
if obj.dimensions is None:
|
||||
return
|
||||
|
||||
if not self.model_config.is_matryoshka:
|
||||
raise ValueError(
|
||||
f"Model '{self.model_config.model_path}' does not support matryoshka representation, "
|
||||
f"changing output dimensions will lead to poor results."
|
||||
)
|
||||
|
||||
if obj.dimensions < 1:
|
||||
raise ValueError("Requested dimensions must be greater than 0")
|
||||
|
||||
if (
|
||||
self.model_config.matryoshka_dimensions
|
||||
and obj.dimensions not in self.model_config.matryoshka_dimensions
|
||||
):
|
||||
raise ValueError(
|
||||
f"Model '{self.model_config.model_path}' only supports {self.model_config.matryoshka_dimensions} matryoshka dimensions, "
|
||||
f"using other output dimensions will lead to poor results."
|
||||
)
|
||||
|
||||
if obj.dimensions > self.model_config.hidden_size:
|
||||
raise ValueError(
|
||||
f"Provided dimensions are greater than max embedding dimension: {self.model_config.hidden_size}"
|
||||
)
|
||||
|
||||
def _validate_input_ids_in_vocab(
|
||||
self, input_ids: List[int], vocab_size: int
|
||||
) -> None:
|
||||
@@ -752,6 +784,7 @@ class TokenizerManager(TokenizerCommunicatorMixin):
|
||||
sampling_params,
|
||||
rid=obj.rid,
|
||||
priority=obj.priority,
|
||||
dimensions=obj.dimensions,
|
||||
http_worker_ipc=obj.http_worker_ipc,
|
||||
)
|
||||
|
||||
|
||||
@@ -320,6 +320,9 @@ class ForwardBatch:
|
||||
tbo_parent_token_range: Optional[Tuple[int, int]] = None
|
||||
tbo_children: Optional[List[ForwardBatch]] = None
|
||||
|
||||
# For matryoshka embeddings
|
||||
dimensions: Optional[list[int]] = None
|
||||
|
||||
@classmethod
|
||||
def init_new(
|
||||
cls,
|
||||
@@ -361,6 +364,7 @@ class ForwardBatch:
|
||||
input_embeds=batch.input_embeds,
|
||||
token_type_ids=batch.token_type_ids,
|
||||
tbo_split_seq_index=batch.tbo_split_seq_index,
|
||||
dimensions=batch.dimensions,
|
||||
)
|
||||
device = model_runner.device
|
||||
|
||||
|
||||
@@ -12,10 +12,11 @@
|
||||
# limitations under the License.
|
||||
# ==============================================================================
|
||||
|
||||
import json
|
||||
import multiprocessing as mp
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional, Tuple, Union
|
||||
from typing import Any, List, Optional, Tuple, Union
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
@@ -89,7 +90,9 @@ def get_token_ids_logprobs(logits, token_ids):
|
||||
return logprobs
|
||||
|
||||
|
||||
def _get_sentence_transformer_embedding_model(model_path, torch_dtype):
|
||||
def _get_sentence_transformer_embedding_model(
|
||||
model_path, torch_dtype, matryoshka_dim: Optional[int] = None
|
||||
):
|
||||
from sentence_transformers import SentenceTransformer
|
||||
from sentence_transformers.util import is_sentence_transformer_model
|
||||
|
||||
@@ -97,6 +100,7 @@ def _get_sentence_transformer_embedding_model(model_path, torch_dtype):
|
||||
model = SentenceTransformer(
|
||||
model_path,
|
||||
model_kwargs={"torch_dtype": torch_dtype},
|
||||
truncate_dim=matryoshka_dim,
|
||||
)
|
||||
else: # if no pre-trained sentence-transformers model
|
||||
from sentence_transformers import models
|
||||
@@ -106,7 +110,9 @@ def _get_sentence_transformer_embedding_model(model_path, torch_dtype):
|
||||
word_embedding_model.get_word_embedding_dimension(),
|
||||
pooling_mode="lasttoken",
|
||||
)
|
||||
model = SentenceTransformer(modules=[word_embedding_model, pooling_model])
|
||||
model = SentenceTransformer(
|
||||
modules=[word_embedding_model, pooling_model], truncate_dim=matryoshka_dim
|
||||
)
|
||||
|
||||
return model.cuda()
|
||||
|
||||
@@ -135,6 +141,7 @@ class HFRunner:
|
||||
output_str_only: bool = False,
|
||||
trust_remote_code: bool = False,
|
||||
patch_model_do_sample_false: bool = False,
|
||||
matryoshka_dim: Optional[int] = None,
|
||||
):
|
||||
self.model_type = model_type
|
||||
self.output_str_only = output_str_only
|
||||
@@ -151,6 +158,7 @@ class HFRunner:
|
||||
self.out_queue,
|
||||
model_path,
|
||||
torch_dtype,
|
||||
matryoshka_dim,
|
||||
),
|
||||
)
|
||||
self.model_proc.start()
|
||||
@@ -225,7 +233,14 @@ class HFRunner:
|
||||
embeddings = torch.nn.functional.normalize(embeddings, p=2, dim=1)
|
||||
return embeddings.contiguous()
|
||||
|
||||
def start_model_process(self, in_queue, out_queue, model_path, torch_dtype):
|
||||
def start_model_process(
|
||||
self,
|
||||
in_queue,
|
||||
out_queue,
|
||||
model_path,
|
||||
torch_dtype,
|
||||
matryoshka_dim: Optional[int] = None,
|
||||
):
|
||||
# Apply model-specific patches
|
||||
monkey_patch_gemma2_sdpa()
|
||||
|
||||
@@ -259,7 +274,7 @@ class HFRunner:
|
||||
self.processor = AutoProcessor.from_pretrained(model_path)
|
||||
else:
|
||||
self.model = _get_sentence_transformer_embedding_model(
|
||||
model_path, torch_dtype
|
||||
model_path, torch_dtype, matryoshka_dim=matryoshka_dim
|
||||
)
|
||||
elif self.model_type == "reward" or self.model_type == "cross_encoder":
|
||||
from transformers import AutoModelForSequenceClassification
|
||||
@@ -519,6 +534,7 @@ class SRTRunner:
|
||||
lora_target_modules: Optional[List[str]] = None,
|
||||
enable_lora: Optional[bool] = None,
|
||||
max_loaded_loras: Optional[int] = None,
|
||||
json_model_override_args: Optional[dict[str, Any]] = None,
|
||||
lora_eviction_policy: str = "lru",
|
||||
):
|
||||
self.model_type = model_type
|
||||
@@ -566,6 +582,11 @@ class SRTRunner:
|
||||
lora_target_modules=lora_target_modules,
|
||||
enable_lora=enable_lora,
|
||||
max_loaded_loras=max_loaded_loras,
|
||||
json_model_override_args=(
|
||||
json.dumps(json_model_override_args)
|
||||
if json_model_override_args
|
||||
else "{}"
|
||||
),
|
||||
lora_eviction_policy=lora_eviction_policy,
|
||||
**spec_kwargs,
|
||||
)
|
||||
@@ -594,6 +615,7 @@ class SRTRunner:
|
||||
logprob_start_len: int = 0,
|
||||
top_k: Optional[int] = None,
|
||||
token_ids_logprob: Optional[List[int]] = None,
|
||||
dimensions: Optional[int] = None,
|
||||
):
|
||||
if self.is_generation:
|
||||
return self.forward_generation_raw(
|
||||
@@ -607,7 +629,9 @@ class SRTRunner:
|
||||
)
|
||||
else:
|
||||
if self.model_type == "embedding":
|
||||
response = self.engine.encode(prompt=prompts, image_data=image_data)
|
||||
response = self.engine.encode(
|
||||
prompt=prompts, image_data=image_data, dimensions=dimensions
|
||||
)
|
||||
if isinstance(response, list):
|
||||
logits = [x["embedding"] for x in response]
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user