Refactor Dockerfile.ci to use precompiled VLLM installation and remove unused parse.py file. Update serving_chat.py to streamline rendering logic and enhance compatibility with new API. Modify OmniInputPreprocessor to directly handle SingletonDictPrompt, improving input processing efficiency.
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
This commit is contained in:
1 parent
31c5465e00
commit
7e9cae56fc
5 files changed
+44
-196
No files matched your search
@@ -34,7 +34,7 @@ RUN uv pip install --system --upgrade --force-reinstall --prerelease=allow vllm
|
||||
RUN uv pip uninstall --system nvidia-cublas-cu12
|
||||
# Install vllm-omni into the same uv-managed Python environment used by the base image.
|
||||
# Use bash -c so that $(python3 -c ...) is expanded inside the container.
|
||||
RUN /bin/bash -c 'uv pip install --python "$(python3 -c "import sys; print(sys.executable)")" --no-cache-dir -e ".[dev]"'
|
||||
RUN /bin/bash -c 'VLLM_USE_PRECOMPILED=1 uv pip install --python "$(python3 -c "import sys; print(sys.executable)")" --no-cache-dir -e ".[dev]"'
|
||||
|
||||
|
||||
RUN /bin/bash -c 'FLASHINFER_CUDA_TAG="$(python3 -c "import torch; print((torch.version.cuda or \"12.4\").replace(\".\", \"\"))")" && \
|
||||
|
||||
@@ -31,7 +31,6 @@ Input data structures for multi-modal inputs.
|
||||
- [vllm_omni.inputs.data.OmniTextPrompt][]
|
||||
- [vllm_omni.inputs.data.OmniTokenInputs][]
|
||||
- [vllm_omni.inputs.data.OmniTokensPrompt][]
|
||||
- [vllm_omni.inputs.parse.parse_singleton_prompt_omni][]
|
||||
- [vllm_omni.inputs.preprocess.OmniInputPreprocessor][]
|
||||
|
||||
## Engine
|
||||
|
||||
@@ -63,20 +63,12 @@ from vllm.entrypoints.openai.parser.harmony_utils import (
|
||||
from vllm.entrypoints.openai.responses.protocol import ResponsesRequest
|
||||
from vllm.entrypoints.openai.utils import maybe_filter_parallel_tool_calls
|
||||
from vllm.entrypoints.utils import should_include_usage
|
||||
from vllm.inputs.data import PromptType, TokensPrompt
|
||||
from vllm.inputs.data import PromptType
|
||||
from vllm.logger import init_logger
|
||||
from vllm.outputs import RequestOutput
|
||||
from vllm.reasoning import ReasoningParser
|
||||
from vllm.renderers.hf import (
|
||||
resolve_chat_template_content_format,
|
||||
)
|
||||
from vllm.renderers.hf import (
|
||||
safe_apply_chat_template as apply_hf_chat_template,
|
||||
)
|
||||
from vllm.renderers import merge_kwargs
|
||||
from vllm.renderers.inputs import TokPrompt
|
||||
from vllm.renderers.mistral import (
|
||||
safe_apply_chat_template as apply_mistral_chat_template,
|
||||
)
|
||||
from vllm.sampling_params import SamplingParams
|
||||
from vllm.tokenizers import TokenizerLike
|
||||
from vllm.tokenizers import TokenizerLike as AnyTokenizer
|
||||
@@ -88,9 +80,8 @@ from vllm.tokenizers.mistral import (
|
||||
)
|
||||
from vllm.tool_parsers import ToolParser
|
||||
from vllm.tool_parsers.mistral_tool_parser import MistralToolCall
|
||||
from vllm.utils.collection_utils import as_list, is_list_of
|
||||
from vllm.utils.collection_utils import as_list
|
||||
|
||||
from vllm_omni.entrypoints.chat_utils import parse_chat_messages_futures
|
||||
from vllm_omni.entrypoints.openai.audio_utils_mixin import AudioMixin
|
||||
from vllm_omni.entrypoints.openai.protocol import OmniChatCompletionStreamResponse
|
||||
from vllm_omni.entrypoints.openai.protocol.audio import AudioResponse, CreateAudio
|
||||
@@ -357,74 +348,39 @@ class OmniOpenAIServingChat(OpenAIServingChat, AudioMixin):
|
||||
documents: list[dict[str, str]] | None = None,
|
||||
add_special_tokens: bool = False,
|
||||
) -> tuple[list[ConversationMessage], list[TokPrompt]]:
|
||||
model_config = self.model_config
|
||||
# OMNI: Use self.renderer if renderer not provided (vLLM new API)
|
||||
if renderer is None:
|
||||
renderer = self.renderer
|
||||
tokenizer = renderer.get_tokenizer() if renderer is not None else None
|
||||
|
||||
# Map new parameter names to internal variables
|
||||
chat_template = default_template
|
||||
chat_template_content_format = default_template_content_format
|
||||
|
||||
if tokenizer is None or isinstance(tokenizer, MistralTokenizer):
|
||||
resolved_content_format = (
|
||||
chat_template_content_format if chat_template_content_format != "auto" else "string"
|
||||
)
|
||||
else:
|
||||
resolved_content_format = resolve_chat_template_content_format(
|
||||
chat_template,
|
||||
tool_dicts,
|
||||
chat_template_content_format,
|
||||
tokenizer,
|
||||
model_config=model_config,
|
||||
)
|
||||
# OMNI: Updated for vLLM v0.15.0 API - resolve_items() returns (mm_data, mm_uuids) tuple
|
||||
conversation, mm_future = parse_chat_messages_futures(
|
||||
messages,
|
||||
model_config,
|
||||
content_format=resolved_content_format,
|
||||
mm_processor_kwargs=getattr(request, "mm_processor_kwargs", None),
|
||||
)
|
||||
|
||||
# OMNI: Handle merged kwargs - default_template_kwargs replaces the old two-param system
|
||||
merged_kwargs = default_template_kwargs or {}
|
||||
|
||||
_chat_template_kwargs: dict[str, Any] = dict(
|
||||
chat_template=chat_template,
|
||||
add_generation_prompt=add_generation_prompt,
|
||||
continue_final_message=continue_final_message,
|
||||
tools=tool_dicts,
|
||||
documents=documents,
|
||||
)
|
||||
_chat_template_kwargs.update(merged_kwargs)
|
||||
|
||||
request_prompt: str | list[int]
|
||||
|
||||
if tokenizer is None:
|
||||
request_prompt = "placeholder"
|
||||
elif isinstance(tokenizer, MistralTokenizer):
|
||||
request_prompt = apply_mistral_chat_template(
|
||||
tokenizer,
|
||||
messages=messages,
|
||||
**_chat_template_kwargs,
|
||||
)
|
||||
else:
|
||||
hf_chat_template_kwargs = dict(_chat_template_kwargs)
|
||||
hf_chat_template_kwargs.pop("tools", None)
|
||||
hf_chat_template_kwargs.pop("chat_template", None)
|
||||
request_prompt = apply_hf_chat_template(
|
||||
model_config=model_config,
|
||||
tokenizer=tokenizer,
|
||||
conversation=conversation,
|
||||
# Keep OMNI compatibility args wired while delegating rendering
|
||||
# to the upstream async renderer pipeline.
|
||||
default_template_kwargs = merge_kwargs(
|
||||
default_template_kwargs,
|
||||
dict(
|
||||
tools=tool_dicts,
|
||||
chat_template=chat_template,
|
||||
tokenize=False,
|
||||
**hf_chat_template_kwargs,
|
||||
)
|
||||
documents=documents,
|
||||
add_generation_prompt=add_generation_prompt,
|
||||
continue_final_message=continue_final_message,
|
||||
add_special_tokens=add_special_tokens,
|
||||
tokenize=isinstance(renderer.tokenizer, MistralTokenizer),
|
||||
),
|
||||
)
|
||||
|
||||
# OMNI: Await the combined future to get both mm_data and mm_uuids
|
||||
mm_data, mm_uuids = await mm_future
|
||||
tok_params = request.build_tok_params(self.model_config)
|
||||
chat_params = request.build_chat_params(
|
||||
default_template,
|
||||
default_template_content_format,
|
||||
).with_defaults(default_template_kwargs)
|
||||
|
||||
(conversation,), (engine_prompt,) = await renderer.render_chat_async(
|
||||
[messages],
|
||||
chat_params,
|
||||
tok_params,
|
||||
prompt_extras={
|
||||
k: v for k in ("mm_processor_kwargs", "cache_salt") if (v := getattr(request, k, None)) is not None
|
||||
},
|
||||
)
|
||||
|
||||
tokenizer = renderer.get_tokenizer()
|
||||
|
||||
# tool parsing is done only if a tool_parser has been set and if
|
||||
# tool_choice is not "none" (if tool_choice is "none" but a tool_parser
|
||||
@@ -442,38 +398,6 @@ class OmniOpenAIServingChat(OpenAIServingChat, AudioMixin):
|
||||
request=request
|
||||
)
|
||||
|
||||
if tokenizer is None:
|
||||
assert isinstance(request_prompt, str), (
|
||||
"Prompt has to be a string",
|
||||
"when the tokenizer is not initialised",
|
||||
)
|
||||
prompt_inputs = TokensPrompt(prompt=request_prompt, prompt_token_ids=[1])
|
||||
elif isinstance(request_prompt, str):
|
||||
# OMNI: Direct tokenization using tokenizer (replaces removed _tokenize_prompt_input_async)
|
||||
prompt_token_ids = tokenizer.encode(request_prompt, add_special_tokens=add_special_tokens)
|
||||
prompt_inputs = TokensPrompt(prompt=request_prompt, prompt_token_ids=prompt_token_ids)
|
||||
else:
|
||||
# For MistralTokenizer
|
||||
assert is_list_of(request_prompt, int), "Prompt has to be either a string or a list of token ids"
|
||||
prompt_inputs = TokensPrompt(
|
||||
prompt=tokenizer.decode(request_prompt),
|
||||
prompt_token_ids=request_prompt,
|
||||
)
|
||||
|
||||
engine_prompt = TokensPrompt(prompt_token_ids=prompt_inputs["prompt_token_ids"])
|
||||
if mm_data is not None:
|
||||
engine_prompt["multi_modal_data"] = mm_data
|
||||
|
||||
if mm_uuids is not None:
|
||||
engine_prompt["multi_modal_uuids"] = mm_uuids
|
||||
|
||||
mm_processor_kwargs = getattr(request, "mm_processor_kwargs", None)
|
||||
if mm_processor_kwargs is not None:
|
||||
engine_prompt["mm_processor_kwargs"] = mm_processor_kwargs
|
||||
|
||||
if hasattr(request, "cache_salt") and request.cache_salt is not None:
|
||||
engine_prompt["cache_salt"] = request.cache_salt
|
||||
|
||||
return conversation, [engine_prompt]
|
||||
|
||||
def _to_sampling_params_list(self, sampling_params_list: list[dict]) -> list[SamplingParams]:
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
from typing import Literal, TypedDict
|
||||
|
||||
from vllm.inputs import EmbedsPrompt, SingletonPrompt, TextPrompt, TokensPrompt
|
||||
|
||||
|
||||
class ParsedStrPrompt(TypedDict):
|
||||
type: Literal["str"]
|
||||
content: str
|
||||
|
||||
|
||||
class ParsedTextPrompt(TypedDict):
|
||||
type: Literal["text"]
|
||||
content: TextPrompt
|
||||
|
||||
|
||||
class ParsedTokensPrompt(TypedDict):
|
||||
type: Literal["tokens"]
|
||||
content: TokensPrompt
|
||||
|
||||
|
||||
class ParsedEmbedsPrompt(TypedDict):
|
||||
type: Literal["embeds"]
|
||||
content: EmbedsPrompt
|
||||
|
||||
|
||||
ParsedSingletonPrompt = ParsedStrPrompt | ParsedTextPrompt | ParsedTokensPrompt | ParsedEmbedsPrompt
|
||||
|
||||
|
||||
def parse_singleton_prompt_omni(
|
||||
prompt: SingletonPrompt,
|
||||
) -> ParsedSingletonPrompt:
|
||||
"""Parse a singleton prompt into a typed parsed prompt.
|
||||
|
||||
Handles omni-specific prompt types including tokens prompts with
|
||||
embeddings and additional information. Supports string, text,
|
||||
tokens, and embeddings prompts.
|
||||
|
||||
Args:
|
||||
prompt: Singleton prompt to parse. Can be a string, TextPrompt,
|
||||
TokensPrompt (with optional prompt_embeds and additional_information),
|
||||
or EmbedsPrompt.
|
||||
|
||||
Returns:
|
||||
ParsedSingletonPrompt containing the parsed prompt with type information
|
||||
|
||||
Raises:
|
||||
TypeError: If the prompt type is not supported
|
||||
"""
|
||||
if isinstance(prompt, str):
|
||||
return {"type": "str", "content": prompt}
|
||||
if isinstance(prompt, list):
|
||||
if not all(isinstance(token, int) for token in prompt):
|
||||
raise TypeError("Token prompt should be a list of integers")
|
||||
return {
|
||||
"type": "tokens",
|
||||
"content": TokensPrompt(prompt_token_ids=prompt),
|
||||
}
|
||||
if isinstance(prompt, dict):
|
||||
# Priority tokens: When both tokens and embeds exist, keep both and
|
||||
# follow the tokens path.
|
||||
if "prompt_token_ids" in prompt:
|
||||
return {"type": "tokens", "content": prompt}
|
||||
if "prompt_embeds" in prompt:
|
||||
return {"type": "embeds", "content": prompt}
|
||||
if "prompt" in prompt:
|
||||
return {"type": "text", "content": prompt}
|
||||
raise TypeError("inputs must be a string, list of token IDs, TextPrompt, TokensPrompt, or EmbedsPrompt")
|
||||
@@ -1,10 +1,11 @@
|
||||
from typing import Any
|
||||
|
||||
from typing_extensions import assert_never
|
||||
from vllm.inputs.data import EmbedsInputs, SingletonInputs, SingletonPrompt, TextPrompt
|
||||
from vllm.inputs.data import EmbedsInputs, SingletonInputs
|
||||
from vllm.inputs.preprocess import InputPreprocessor
|
||||
from vllm.logger import init_logger
|
||||
from vllm.multimodal.inputs import MultiModalInputs, MultiModalUUIDDict
|
||||
from vllm.renderers.inputs import SingletonDictPrompt
|
||||
|
||||
from vllm_omni.inputs.data import (
|
||||
OmniEmbedsPrompt,
|
||||
@@ -13,7 +14,6 @@ from vllm_omni.inputs.data import (
|
||||
OmniTokensPrompt,
|
||||
token_inputs_omni,
|
||||
)
|
||||
from vllm_omni.inputs.parse import parse_singleton_prompt_omni
|
||||
|
||||
logger = init_logger(__name__)
|
||||
|
||||
@@ -123,7 +123,7 @@ class OmniInputPreprocessor(InputPreprocessor):
|
||||
|
||||
def _prompt_to_llm_inputs(
|
||||
self,
|
||||
prompt: SingletonPrompt,
|
||||
prompt: SingletonDictPrompt,
|
||||
tokenization_kwargs: dict[str, Any] | None = None,
|
||||
*,
|
||||
mm_uuids: MultiModalUUIDDict | None = None,
|
||||
@@ -139,28 +139,20 @@ class OmniInputPreprocessor(InputPreprocessor):
|
||||
|
||||
* [`SingletonInputs`][vllm.inputs.data.SingletonInputs] instance
|
||||
"""
|
||||
parsed = parse_singleton_prompt_omni(prompt)
|
||||
|
||||
# Note: omni parsing prioritizes tokens path when both tokens and embeds
|
||||
# exist, keeping both for pipeline stage transfer
|
||||
if parsed["type"] == "embeds":
|
||||
return self._process_embeds(parsed["content"])
|
||||
if parsed["type"] == "tokens":
|
||||
if "prompt_token_ids" in prompt:
|
||||
return self._process_tokens(
|
||||
parsed["content"],
|
||||
prompt, # type: ignore[arg-type]
|
||||
mm_uuids=mm_uuids,
|
||||
)
|
||||
if parsed["type"] == "text":
|
||||
|
||||
if "prompt_embeds" in prompt:
|
||||
return self._process_embeds(prompt) # type: ignore[arg-type]
|
||||
|
||||
if "prompt" in prompt:
|
||||
return self._process_text(
|
||||
parsed["content"],
|
||||
tokenization_kwargs=tokenization_kwargs,
|
||||
mm_uuids=mm_uuids,
|
||||
)
|
||||
if parsed["type"] == "str":
|
||||
return self._process_text(
|
||||
TextPrompt(prompt=parsed["content"]),
|
||||
prompt, # type: ignore[arg-type]
|
||||
tokenization_kwargs=tokenization_kwargs,
|
||||
mm_uuids=mm_uuids,
|
||||
)
|
||||
|
||||
assert_never(parsed)
|
||||
assert_never(prompt) # type: ignore[arg-type]
|
||||
Reference in new issue
Block a user