VLM: enhance VL embedding model with video input support and revise warm-up strategy (#16635)

Co-authored-by: Mick <mickjagger19@icloud.com>
This commit is contained in:
yuhao
2026-01-08 22:12:01 +08:00
committed by GitHub
co-authored by Mick
parent 20ca2c6e1e
commit d2ea44f775
5 changed files with 18 additions and 4 deletions
+6 -1
View File
@@ -1514,8 +1514,13 @@ def _execute_server_warmup(server_args: ServerArgs):
# TODO Workaround the bug that embedding errors for list of size 1 # TODO Workaround the bug that embedding errors for list of size 1
if server_args.dp_size == 1: if server_args.dp_size == 1:
json_data["input_ids"] = json_data["input_ids"][0] json_data["input_ids"] = json_data["input_ids"][0]
elif is_vlm and server_args.disaggregation_mode == "null": elif (
is_vlm
and server_args.disaggregation_mode == "null"
and model_info["is_generation"]
):
# TODO: ChatCompletionRequest does not have bootstrap info required by disaggregation mode, disable image-warmup for now # TODO: ChatCompletionRequest does not have bootstrap info required by disaggregation mode, disable image-warmup for now
# Only use chat completions format for generation models, not embedding models
json_data = { json_data = {
"model": _global_state.tokenizer_manager.served_model_name, "model": _global_state.tokenizer_manager.served_model_name,
"messages": [ "messages": [
@@ -780,6 +780,7 @@ class ChatCompletionStreamResponse(BaseModel):
class MultimodalEmbeddingInput(BaseModel): class MultimodalEmbeddingInput(BaseModel):
text: Optional[str] = None text: Optional[str] = None
image: Optional[str] = None image: Optional[str] = None
video: Optional[str] = None
EmbeddingInput = Union[ EmbeddingInput = Union[
@@ -89,16 +89,18 @@ class OpenAIServingEmbedding(OpenAIServingBase):
# Handle multimodal embedding inputs # Handle multimodal embedding inputs
texts = [] texts = []
images = [] images = []
videos = []
for item in prompt: for item in prompt:
# Use padding for text if None - this could be improved # Use padding for text if None - this could be improved
texts.append(item.text if item.text is not None else "padding") texts.append(item.text if item.text is not None else "padding")
images.append(item.image if item.image is not None else None) images.append(item.image if item.image is not None else None)
videos.append(item.video if item.video is not None else None)
generate_prompts = [] generate_prompts = []
# Check if we have a chat template for multimodal embeddings # Check if we have a chat template for multimodal embeddings
if self.template_manager.chat_template_name is not None: if self.template_manager.chat_template_name is not None:
convs = generate_embedding_convs( convs = generate_embedding_convs(
texts, images, self.template_manager.chat_template_name texts, images, videos, self.template_manager.chat_template_name
) )
for conv in convs: for conv in convs:
generate_prompts.append(conv.get_prompt()) generate_prompts.append(conv.get_prompt())
@@ -109,11 +111,13 @@ class OpenAIServingEmbedding(OpenAIServingBase):
prompt_kwargs = { prompt_kwargs = {
"text": generate_prompts[0], "text": generate_prompts[0],
"image_data": images[0], "image_data": images[0],
"video_data": videos[0],
} }
else: else:
prompt_kwargs = { prompt_kwargs = {
"text": generate_prompts, "text": generate_prompts,
"image_data": images, "image_data": images,
"video_data": videos,
} }
else: else:
# List of integers (token IDs) or empty list # List of integers (token IDs) or empty list
@@ -683,6 +683,8 @@ class TokenizerManager(TokenizerCommunicatorMixin, TokenizerManagerMultiItemMixi
if self.mm_processor and obj.contains_mm_input(): if self.mm_processor and obj.contains_mm_input():
if obj.image_data is not None and not isinstance(obj.image_data, list): if obj.image_data is not None and not isinstance(obj.image_data, list):
obj.image_data = [obj.image_data] obj.image_data = [obj.image_data]
if obj.video_data is not None and not isinstance(obj.video_data, list):
obj.video_data = [obj.video_data]
if obj.audio_data is not None and not isinstance(obj.audio_data, list): if obj.audio_data is not None and not isinstance(obj.audio_data, list):
obj.audio_data = [obj.audio_data] obj.audio_data = [obj.audio_data]
self._validate_mm_limits(obj) self._validate_mm_limits(obj)
+4 -2
View File
@@ -510,11 +510,11 @@ def chat_template_exists(template_name: str) -> bool:
def generate_embedding_convs( def generate_embedding_convs(
texts: List[str], images: List[str], template_name: str texts: List[str], images: List[str], videos: List[str], template_name: str
) -> List[Conversation]: ) -> List[Conversation]:
conv_template = chat_templates[template_name].copy() conv_template = chat_templates[template_name].copy()
convs = [] convs = []
for text, image in zip(texts, images): for text, image, video in zip(texts, images, videos):
conv = Conversation( conv = Conversation(
name=conv_template.name, name=conv_template.name,
system_template=conv_template.system_template, system_template=conv_template.system_template,
@@ -544,6 +544,8 @@ def generate_embedding_convs(
else conv.image_token else conv.image_token
) )
real_content += image_token real_content += image_token
if video is not None:
real_content += conv.video_token
if text is not None: if text is not None:
real_content += text real_content += text
conv.append_message(conv.roles[0], real_content) conv.append_message(conv.roles[0], real_content)