Add Support for Qwen2-VL Multi-modal Embedding Models (#3694)
This commit is contained in:
@@ -293,6 +293,8 @@ class TokenizedGenerateReqInput:
|
||||
class EmbeddingReqInput:
|
||||
# The input prompt. It can be a single prompt or a batch of prompts.
|
||||
text: Optional[Union[List[str], str]] = None
|
||||
# The image input. It can be a file name, a url, or base64 encoded string.
|
||||
image_data: Optional[Union[List[str], str]] = None
|
||||
# The token ids for text; one can either specify text or input_ids.
|
||||
input_ids: Optional[Union[List[List[int]], List[int]]] = None
|
||||
# The request id.
|
||||
@@ -303,28 +305,40 @@ class EmbeddingReqInput:
|
||||
input_embeds: Optional[Union[List[List[List[float]]], List[List[float]]]] = None
|
||||
# Whether to log metrics for this request (e.g. health_generate calls do not log metrics)
|
||||
log_metrics: bool = True
|
||||
# The modalities of the image data [image, multi-images, video]
|
||||
modalities: Optional[List[str]] = None
|
||||
|
||||
def normalize_batch_and_arguments(self):
|
||||
if (self.text is None and self.input_ids is None) or (
|
||||
self.text is not None and self.input_ids is not None
|
||||
):
|
||||
raise ValueError("Either text or input_ids should be provided.")
|
||||
# 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:
|
||||
raise ValueError(
|
||||
"At least one of text, input_ids, or image should be provided"
|
||||
)
|
||||
|
||||
# text and input_ids cannot be provided at the same time
|
||||
if self.text is not None and self.input_ids is not None:
|
||||
raise ValueError("text and input_ids cannot be provided at the same time")
|
||||
|
||||
# Derive the batch size
|
||||
self.batch_size = 0
|
||||
self.is_single = True
|
||||
|
||||
# check the batch size of text
|
||||
if self.text is not None:
|
||||
if isinstance(self.text, str):
|
||||
self.is_single = True
|
||||
self.batch_size = 1
|
||||
if isinstance(self.text, list):
|
||||
self.batch_size += len(self.text)
|
||||
else:
|
||||
self.is_single = False
|
||||
self.batch_size = len(self.text)
|
||||
else:
|
||||
if isinstance(self.input_ids[0], int):
|
||||
self.is_single = True
|
||||
self.batch_size = 1
|
||||
self.batch_size += 1
|
||||
|
||||
# check the batch size of input_ids
|
||||
if self.input_ids is not None:
|
||||
if isinstance(self.input_ids[0], list):
|
||||
self.batch_size += len(self.input_ids)
|
||||
else:
|
||||
self.is_single = False
|
||||
self.batch_size = len(self.input_ids)
|
||||
self.batch_size += 1
|
||||
|
||||
if self.batch_size > 1:
|
||||
self.is_single = False
|
||||
|
||||
# Fill in default arguments
|
||||
if self.is_single:
|
||||
@@ -352,6 +366,7 @@ class EmbeddingReqInput:
|
||||
return EmbeddingReqInput(
|
||||
text=self.text[i] if self.text is not None else None,
|
||||
input_ids=self.input_ids[i] if self.input_ids is not None else None,
|
||||
image_data=self.image_data[i] if self.image_data is not None else None,
|
||||
sampling_params=self.sampling_params[i],
|
||||
rid=self.rid[i],
|
||||
)
|
||||
@@ -365,6 +380,8 @@ class TokenizedEmbeddingReqInput:
|
||||
input_text: str
|
||||
# The input token ids
|
||||
input_ids: List[int]
|
||||
# The image inputs
|
||||
image_inputs: dict
|
||||
# Dummy sampling params for compatibility
|
||||
sampling_params: SamplingParams
|
||||
|
||||
|
||||
@@ -767,6 +767,30 @@ class Scheduler:
|
||||
)
|
||||
req.tokenizer = self.tokenizer
|
||||
|
||||
# Handle multimodal inputs
|
||||
if recv_req.image_inputs is not None:
|
||||
image_inputs = ImageInputs.from_dict(recv_req.image_inputs)
|
||||
# Expand a single image token into multiple dummy tokens for receiving image embeddings
|
||||
req.origin_input_ids = self.pad_input_ids_func(
|
||||
req.origin_input_ids, image_inputs
|
||||
)
|
||||
req.extend_image_inputs(image_inputs)
|
||||
|
||||
if len(req.origin_input_ids) >= self.max_req_input_len:
|
||||
error_msg = (
|
||||
"Multimodal prompt is too long after expanding multimodal tokens. "
|
||||
f"After expanding {len(req.origin_input_ids_unpadded)=} => {len(req.origin_input_ids)} >= {self.max_req_input_len}."
|
||||
)
|
||||
logger.error(error_msg)
|
||||
req.origin_input_ids = [0]
|
||||
req.image_inputs = None
|
||||
req.sampling_params.max_new_tokens = 0
|
||||
req.finished_reason = FINISH_ABORT(
|
||||
error_msg, HTTPStatus.BAD_REQUEST, "BadRequestError"
|
||||
)
|
||||
self.waiting_queue.append(req)
|
||||
return
|
||||
|
||||
# Validate prompts length
|
||||
error_msg = validate_input_length(
|
||||
req,
|
||||
|
||||
@@ -372,13 +372,12 @@ class TokenizerManager:
|
||||
)
|
||||
input_ids = self.tokenizer.encode(input_text)
|
||||
|
||||
image_inputs: Dict = await self.image_processor.process_images_async(
|
||||
obj.image_data, input_text or input_ids, obj, self.max_req_input_len
|
||||
)
|
||||
if image_inputs and "input_ids" in image_inputs:
|
||||
input_ids = image_inputs["input_ids"]
|
||||
if self.is_generation:
|
||||
# TODO: also support getting embeddings for multimodal models
|
||||
image_inputs: Dict = await self.image_processor.process_images_async(
|
||||
obj.image_data, input_text or input_ids, obj, self.max_req_input_len
|
||||
)
|
||||
if image_inputs and "input_ids" in image_inputs:
|
||||
input_ids = image_inputs["input_ids"]
|
||||
return_logprob = obj.return_logprob
|
||||
logprob_start_len = obj.logprob_start_len
|
||||
top_logprobs_num = obj.top_logprobs_num
|
||||
@@ -438,6 +437,7 @@ class TokenizerManager:
|
||||
obj.rid,
|
||||
input_text,
|
||||
input_ids,
|
||||
image_inputs,
|
||||
sampling_params,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user