Files
2026-10-15 08:26:22 +00:00

225 lines
6.6 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
import pytest
from vllm.config import ModelConfig
from vllm.renderers.chat_utils import parse_chat_messages, parse_chat_messages_async
from vllm.utils.serial_utils import tensor2base64
from .utils import (
assert_mm_data_inputs,
assert_mm_uuids,
)
QWEN2AUDIO_MODEL_ID = "Qwen/Qwen2-Audio-7B-Instruct"
@pytest.fixture(scope="function")
def qwen2_audio_model_config():
return ModelConfig(
QWEN2AUDIO_MODEL_ID,
runner="generate",
trust_remote_code=True,
limit_mm_per_prompt={
"audio": 1,
},
)
@pytest.fixture(scope="function")
def audio_embeds_model_config():
return ModelConfig(
QWEN2AUDIO_MODEL_ID,
runner="generate",
trust_remote_code=True,
limit_mm_per_prompt={
"audio": 2,
},
enable_mm_embeds=True,
)
@pytest.mark.parametrize("input_audio", [None, {}, {"data": "", "format": "wav"}])
def test_parse_chat_messages_single_empty_audio_with_uuid(
qwen2_audio_model_config,
input_audio,
):
audio_uuid = "abcd"
conversation, mm_data, mm_uuids = parse_chat_messages(
[
{
"role": "user",
"content": [
{
"type": "input_audio",
"input_audio": input_audio,
"uuid": audio_uuid,
},
{"type": "text", "text": "What does the audio say?"},
],
}
],
qwen2_audio_model_config,
content_format="string",
)
assert conversation == [
{
"role": "user",
"content": "Audio 1: <|audio_bos|><|AUDIO|><|audio_eos|>\nWhat does the "
"audio say?",
}
]
assert_mm_data_inputs(mm_data, {"audio": 1})
assert_mm_uuids(mm_uuids, 1, modality="audio", expected_uuids=[audio_uuid])
@pytest.mark.asyncio
@pytest.mark.parametrize("input_audio", [None, {}, {"data": "", "format": "wav"}])
async def test_parse_chat_messages_single_empty_audio_with_uuid_async(
qwen2_audio_model_config,
input_audio,
):
audio_uuid = "abcd"
conversation, mm_data, mm_uuids = await parse_chat_messages_async(
[
{
"role": "user",
"content": [
{
"type": "input_audio",
"input_audio": input_audio,
"uuid": audio_uuid,
},
{"type": "text", "text": "What does the audio say?"},
],
}
],
qwen2_audio_model_config,
content_format="string",
)
assert conversation == [
{
"role": "user",
"content": "Audio 1: <|audio_bos|><|AUDIO|><|audio_eos|>\nWhat does the "
"audio say?",
}
]
assert_mm_data_inputs(mm_data, {"audio": 1})
assert_mm_uuids(mm_uuids, 1, modality="audio", expected_uuids=[audio_uuid])
def test_parse_chat_messages_empty_audio_embeds_with_uuid(
audio_embeds_model_config,
):
"""Test audio_embeds with UUID (no actual embeds data)."""
uuid = "test-audio-uuid-123"
conversation, mm_data, mm_uuids = parse_chat_messages(
[
{
"role": "user",
"content": [
{"type": "text", "text": "Describe this audio"},
{"type": "audio_embeds", "audio_embeds": None, "uuid": uuid},
],
}
],
audio_embeds_model_config,
content_format="string",
)
# Should have audio in mm_data as None (UUID provided)
assert mm_data is not None
assert "audio" in mm_data
assert isinstance(mm_data["audio"], list)
assert len(mm_data["audio"]) == 1
assert mm_data["audio"][0] is None
# UUID should be recorded
assert_mm_uuids(mm_uuids, 1, modality="audio", expected_uuids=[uuid])
def test_parse_chat_messages_audio_embeds_with_string(
audio_embeds_model_config,
):
"""Test audio_embeds with base64 string embedding data."""
import torch
# Create a sample audio embedding tensor
hidden_size = audio_embeds_model_config.get_inputs_embeds_size()
audio_embedding = torch.randn(1, 128, hidden_size)
# Encode it as base64
base64_audio_embedding = tensor2base64(audio_embedding)
conversation, mm_data, mm_uuids = parse_chat_messages(
[
{
"role": "user",
"content": [
{"type": "text", "text": "Describe this audio"},
{
"type": "audio_embeds",
"audio_embeds": base64_audio_embedding,
},
],
}
],
audio_embeds_model_config,
content_format="string",
)
# Should have audio embedding in mm_data (single tensor, not a list)
assert mm_data is not None
assert "audio" in mm_data
assert isinstance(mm_data["audio"], torch.Tensor)
assert mm_data["audio"].shape == audio_embedding.shape
# No UUID provided
assert mm_uuids is not None
assert "audio" in mm_uuids
assert_mm_uuids(mm_uuids, 1, modality="audio", expected_uuids=[None])
@pytest.mark.asyncio
async def test_parse_chat_messages_audio_embeds_async(
audio_embeds_model_config,
):
"""Test audio_embeds with async futures."""
import torch
# Create a sample audio embedding tensor
hidden_size = audio_embeds_model_config.get_inputs_embeds_size()
audio_embedding = torch.randn(1, 128, hidden_size)
# Encode it as base64
base64_audio_embedding = tensor2base64(audio_embedding)
conversation, mm_data, mm_uuids = await parse_chat_messages_async(
[
{
"role": "user",
"content": [
{"type": "text", "text": "Describe this audio"},
{
"type": "audio_embeds",
"audio_embeds": base64_audio_embedding,
},
],
}
],
audio_embeds_model_config,
content_format="string",
)
# Should have audio embedding in mm_data (single tensor, not a list)
assert mm_data is not None
assert "audio" in mm_data
assert isinstance(mm_data["audio"], torch.Tensor)
assert mm_data["audio"].shape == audio_embedding.shape
# No UUID provided
assert mm_uuids is not None
assert "audio" in mm_uuids
assert_mm_uuids(mm_uuids, 1, modality="audio", expected_uuids=[None])