Files
vllm-omni/vllm_omni/benchmarks/data_modules/random_multi_modal_dataset.py
T

149 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import base64
import io
import logging
from collections.abc import Mapping
from typing import Any
import numpy as np
import soundfile as sf
import torch
from vllm.benchmarks.datasets import RandomMultiModalDataset, process_image, process_video
logger = logging.getLogger(__name__)
def process_audio(audio: Any) -> Mapping[str, Any]:
"""
Process a single audio input and return a multimedia content dictionary.
Supports the following input types:
1. Dictionary with raw audio bytes: - Expects a dict with a 'bytes' key
containing raw audio data.
2. String input: - Treats the string as a URL or local file path. -
Prepends "file://" if the string doesn't start with "http://" or
"file://". - Returns a dictionary with the audio URL.
Raises:
ValueError: If the input is not a supported type.
"""
if isinstance(audio, dict) and "bytes" in audio:
audio_bytes = audio["bytes"]
audio_base64 = base64.b64encode(audio_bytes).decode("utf-8")
return {
"type": "audio_url",
"audio_url": {"url": f"data:audio/mpeg;base64,{audio_base64}"},
}
if isinstance(audio, str):
audio_url = audio if audio.startswith(("http://", "https://", "file://")) else f"file://{audio}"
return {"type": "audio_url", "audio_url": {"url": audio_url}}
raise ValueError(
f"Invalid audio input {audio}. Must be a string of local path/remote url, "
f"or a dictionary with raw audio bytes in the form of `{{'bytes': raw_audio_bytes}}`."
)
# -----------------------------------------------------------------------------
# MultiModalDataset Implementation
# -----------------------------------------------------------------------------
class OmniRandomMultiModalDataset(RandomMultiModalDataset):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def generate_synthetic_audio(
self,
duration: int, # seconds
num_channels: int, # 1:Mono,2:Stereo 5:5.1 surround sound
) -> dict[str, Any]:
"""Generate synthetic audio with random values.
Default use 48000Hz.
"""
sample_rate = 48000
num_samples = int(sample_rate * duration)
audio_data = self._rng.uniform(-0.5, 0.5, (num_samples, num_channels))
audio_data = np.clip(audio_data, -1.0, 1.0)
audio_tensor = torch.FloatTensor(audio_data.T)
audio_np = audio_tensor.numpy()
buffer = io.BytesIO()
sf.write(buffer, audio_np.T, sample_rate, format="wav")
buffer.seek(0)
audio_bytes = buffer.read()
buffer.close()
return {
"bytes": audio_bytes,
}
def generate_mm_item(
self,
mm_item_config: tuple[int, int, int],
) -> Mapping[str, Any]:
"""
Create synthetic images and videos and
apply process_image/process_video respectively.
This follows the OpenAI API chat completions
https://github.com/openai/openai-python
"""
if self.map_config_to_modality(mm_item_config) == "image":
return process_image(self.generate_synthetic_image(mm_item_config[1], mm_item_config[0]))
elif self.map_config_to_modality(mm_item_config) == "video":
return process_video(self.generate_synthetic_video(mm_item_config[1], mm_item_config[0], mm_item_config[2]))
elif self.map_config_to_modality(mm_item_config) == "audio":
return process_audio(self.generate_synthetic_audio(mm_item_config[1], mm_item_config[2]))
else:
raise ValueError(f"Invalid multimodal item configuration: {mm_item_config}")
def generate_synthetic_video(self, width: int, height: int, num_frames: int) -> Any:
"""Generate synthetic video with random values."""
import imageio
video_data = self._rng.integers(
0,
256,
(num_frames, height, width, 3),
dtype=np.uint8,
)
buffer = io.BytesIO()
writer_kwargs = {
"format": "mp4",
"fps": 30,
"codec": "libx264",
"quality": 7,
"pixelformat": "yuv420p",
"macro_block_size": 16,
"ffmpeg_params": [
"-preset",
"medium",
"-crf",
"23",
"-movflags",
"+faststart",
],
}
with imageio.get_writer(buffer, **writer_kwargs) as writer:
for frame_idx in range(num_frames):
writer.append_data(video_data[frame_idx])
buffer.seek(0)
video_bytes = buffer.read()
return {
"bytes": video_bytes,
}
def map_config_to_modality(self, config: tuple[int, int, int]) -> str:
"""Map the configuration to the modality."""
if config[0] == 0:
return "audio"
elif config[-1] == 1:
return "image"
elif config[-1] > 1:
return "video"
else:
raise ValueError(f"Invalid multimodal item configuration: {config}")