Signed-off-by: Boao Shi <aoibosh@connect.hku.hk> Signed-off-by: bowieshi <aoibosh@connect.hku.hk> Signed-off-by: princepride <wangzhipeng628@gmail.com> Signed-off-by: R2-Y <ruiruyang2@gmail.com> Signed-off-by: amy-why-3459 <wuhaiyan17@huawei.com> Signed-off-by: Shi Boao Bowie <aoibosh@connect.hku.hk> Co-authored-by: 汪志鹏 <wangzhipeng628@gmail.com> Co-authored-by: ruirui(rein) yang <73573651+R2-Y@users.noreply.github.com> Co-authored-by: amy-why-3459 <wuhaiyan17@huawei.com> Co-authored-by: Gao Han <hgaoaf@connect.ust.hk> Co-authored-by: Hongsheng Liu <liuhongsheng4@huawei.com>
1449 lines
50 KiB
Python
1449 lines
50 KiB
Python
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
import time
|
|
import types
|
|
from importlib.machinery import ModuleSpec
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import numpy as np
|
|
import torch
|
|
from PIL import Image
|
|
|
|
TASK_CHOICES = ("t2t", "t2i", "t2s", "i2i", "i2t", "s2t", "v2t")
|
|
|
|
TASK_DEFAULT_RUNTIME = {
|
|
"t2t": ("mmu", "mmu", 0, "text"),
|
|
"t2i": ("t2i", "t2i_gen", 2, "image"),
|
|
"t2s": ("t2s_mmu_like", "t2s_gen", 1, "audio"),
|
|
"i2i": ("i2i", "i2i", 2, "image"),
|
|
"i2t": ("mmu", "mmu", 0, "text"),
|
|
"s2t": ("s2t", "s2t", 0, "text"),
|
|
"v2t": ("v2t", "v2t", 0, "text"),
|
|
}
|
|
|
|
TASK_RUNTIME_FALLBACKS: dict[str, dict[str, Any]] = {
|
|
"t2t": {
|
|
"output_dir": "/tmp/dynin_end2end_outputs",
|
|
"prompt_max_text_len": 1024,
|
|
"max_new_tokens": 1024,
|
|
"steps": 1024,
|
|
"block_length": 16,
|
|
"temperature": 0.0,
|
|
"cfg_scale": 0.0,
|
|
},
|
|
"t2i": {
|
|
"output_dir": "/tmp/dynin_t2i_outputs",
|
|
"prompt_max_text_len": 128,
|
|
"image_token_count": 1024,
|
|
"mask_token_id": 126336,
|
|
"codebook_size": 8192,
|
|
"timesteps": 20,
|
|
"guidance_scale": 3.5,
|
|
"temperature": 1.0,
|
|
},
|
|
"i2i": {
|
|
"output_dir": "/tmp/dynin_i2i_outputs",
|
|
"prompt_max_text_len": 128,
|
|
"mask_token_id": 126336,
|
|
"codebook_size": 8192,
|
|
"timesteps": 64,
|
|
"guidance_scale": 3.5,
|
|
"temperature": 1.0,
|
|
"image_resolution": 336,
|
|
"use_train_i2i_prompt": True,
|
|
},
|
|
"i2t": {
|
|
"output_dir": "/tmp/dynin_i2t_outputs",
|
|
"prompt_max_text_len": 128,
|
|
"max_new_tokens": 128,
|
|
"steps": 128,
|
|
"block_length": 2,
|
|
"temperature": 0.0,
|
|
"cfg_scale": 0.0,
|
|
"mask_token_id": 126336,
|
|
"codebook_size": 8192,
|
|
"image_resolution": 480,
|
|
"remasking": "low_confidence",
|
|
},
|
|
"s2t": {
|
|
"output_dir": "/tmp/dynin_s2t_outputs",
|
|
"prompt_max_text_len": 1024,
|
|
"max_new_tokens": 128,
|
|
"steps": 128,
|
|
"block_length": 2,
|
|
"temperature": 0.0,
|
|
"cfg_scale": 0.0,
|
|
"mask_token_id": 126336,
|
|
"codebook_size": 8192,
|
|
"remasking": "low_confidence",
|
|
},
|
|
"t2s": {
|
|
"output_dir": "/tmp/dynin_t2s_outputs",
|
|
"runtime_task": "t2s_mmu_like",
|
|
"prompting_task": "t2s_gen",
|
|
"prompt_max_text_len": 1024,
|
|
"t2s_token_length": 512,
|
|
"mask_token_id": 126336,
|
|
"codebook_size": 8192,
|
|
"audio_codebook_size": 4096,
|
|
"steps": 512,
|
|
"block_length": 128,
|
|
"temperature": 1.0,
|
|
"cfg_scale": 2.5,
|
|
"t2s_condition": "gender-female_emotion-neutral_speed-normal_pitch-normal",
|
|
},
|
|
"v2t": {
|
|
"output_dir": "/tmp/dynin_v2t_outputs",
|
|
"prompt_max_text_len": 1024,
|
|
"max_new_tokens": 128,
|
|
"steps": 128,
|
|
"block_length": 2,
|
|
"temperature": 0.0,
|
|
"cfg_scale": 0.0,
|
|
"mask_token_id": 126336,
|
|
"codebook_size": 8192,
|
|
"image_resolution": 224,
|
|
"num_frames": 5,
|
|
"remasking": "low_confidence",
|
|
},
|
|
}
|
|
|
|
DEFAULT_I2T_QUESTION = "Please describe this image in detail."
|
|
DEFAULT_S2T_INSTRUCTION = "Transcribe the given audio."
|
|
DEFAULT_V2T_QUESTION = "Please provide a detailed description of the video."
|
|
DEFAULT_T2T_PROMPT = "Explain multimodal LLM inference in 3 sentences."
|
|
DEFAULT_T2S_INSTRUCTION = "Convert the given text into spoken audio."
|
|
DEFAULT_T2S_PROMPT = "Hello. This is a default text-to-speech sample."
|
|
|
|
DYNIN_SPECIAL_TOKENS = (
|
|
"<|soi|>",
|
|
"<|eoi|>",
|
|
"<|sov|>",
|
|
"<|eov|>",
|
|
"<|t2i|>",
|
|
"<|mmu|>",
|
|
"<|t2v|>",
|
|
"<|v2v|>",
|
|
"<|lvg|>",
|
|
"<|i2i|>",
|
|
"<|ti2ti|>",
|
|
"<|v2t|>",
|
|
"<|v2s|>",
|
|
"<|s2t|>",
|
|
"<|t2s|>",
|
|
"<|s2s|>",
|
|
"<|soa|>",
|
|
"<|eoa|>",
|
|
)
|
|
|
|
|
|
def bootstrap_repo_path() -> Path:
|
|
repo_root = Path(__file__).resolve().parents[3]
|
|
repo_root_str = str(repo_root)
|
|
if repo_root_str not in sys.path:
|
|
sys.path.insert(0, repo_root_str)
|
|
return repo_root
|
|
|
|
|
|
def ensure_safe_import_for_vllm() -> None:
|
|
os.environ.setdefault("TRANSFORMERS_NO_TORCHVISION", "1")
|
|
try:
|
|
import torchvision # noqa: F401
|
|
|
|
return
|
|
except Exception:
|
|
pass
|
|
|
|
import enum
|
|
|
|
class _InterpolationMode(enum.Enum):
|
|
NEAREST = 0
|
|
BILINEAR = 2
|
|
BICUBIC = 3
|
|
LANCZOS = 1
|
|
HAMMING = 4
|
|
BOX = 5
|
|
|
|
tv_mod = types.ModuleType("torchvision")
|
|
tv_mod.__dict__["__version__"] = "0.0-stub"
|
|
tv_mod.__spec__ = ModuleSpec(name="torchvision", loader=None)
|
|
transforms_mod = types.ModuleType("torchvision.transforms")
|
|
transforms_mod.__spec__ = ModuleSpec(name="torchvision.transforms", loader=None)
|
|
transforms_mod.InterpolationMode = _InterpolationMode
|
|
tv_mod.transforms = transforms_mod
|
|
sys.modules["torchvision"] = tv_mod
|
|
sys.modules["torchvision.transforms"] = transforms_mod
|
|
|
|
|
|
def sanitize_repo_id(repo_id: str) -> str:
|
|
return re.sub(r"[^a-zA-Z0-9._-]+", "_", repo_id)
|
|
|
|
|
|
def is_hf_repo_id(value: str) -> bool:
|
|
return isinstance(value, str) and value.count("/") == 1 and all(value.split("/", 1))
|
|
|
|
|
|
def ensure_local_model_dir(model: str, cache_dir: Path, localize: bool) -> Path:
|
|
model_path = Path(model).expanduser()
|
|
if model_path.is_dir():
|
|
return model_path.resolve()
|
|
if not localize:
|
|
return Path(model)
|
|
|
|
from huggingface_hub import snapshot_download
|
|
|
|
cache_dir.mkdir(parents=True, exist_ok=True)
|
|
os.environ.setdefault("HF_HOME", str(cache_dir / ".hf_home"))
|
|
local_dir = cache_dir / sanitize_repo_id(model)
|
|
if not local_dir.exists():
|
|
print(f"[end2end] Downloading model into local cache: {local_dir}")
|
|
snapshot_download(
|
|
repo_id=model,
|
|
local_dir=str(local_dir),
|
|
local_dir_use_symlinks=True,
|
|
resume_download=True,
|
|
)
|
|
return local_dir.resolve()
|
|
|
|
|
|
def resolve_local_only(
|
|
override: bool | None,
|
|
source: str,
|
|
default: bool,
|
|
) -> bool:
|
|
if override is not None:
|
|
return bool(override)
|
|
return default or Path(source).expanduser().is_dir()
|
|
|
|
|
|
def load_text_tokenizer(tokenizer_source: str, local_files_only: bool):
|
|
from transformers import AutoTokenizer
|
|
|
|
kwargs = {
|
|
"trust_remote_code": True,
|
|
"padding_side": "left",
|
|
"local_files_only": bool(local_files_only),
|
|
}
|
|
try:
|
|
tokenizer = AutoTokenizer.from_pretrained(tokenizer_source, **kwargs)
|
|
except TypeError:
|
|
kwargs.pop("local_files_only", None)
|
|
tokenizer = AutoTokenizer.from_pretrained(tokenizer_source, **kwargs)
|
|
return tokenizer
|
|
|
|
|
|
def preprocess_image(image: Image.Image, resolution: int) -> torch.Tensor:
|
|
w, h = image.size
|
|
short_side = min(w, h)
|
|
scale = resolution / short_side
|
|
new_w, new_h = round(w * scale), round(h * scale)
|
|
image = image.resize((new_w, new_h), Image.BICUBIC)
|
|
left = (new_w - resolution) // 2
|
|
top = (new_h - resolution) // 2
|
|
image = image.crop((left, top, left + resolution, top + resolution))
|
|
arr = np.array(image, dtype=np.float32) / 255.0
|
|
tensor = torch.from_numpy(arr).permute(2, 0, 1)
|
|
return (tensor - 0.5) / 0.5
|
|
|
|
|
|
def load_vq_image_encoder(source: str, local_files_only: bool, device: torch.device) -> Any:
|
|
from vllm_omni.model_executor.models.dynin_omni.dynin_omni_common import get_dynin_magvit_attr
|
|
|
|
MAGVITv2 = get_dynin_magvit_attr("MAGVITv2", source=source, local_files_only=local_files_only)
|
|
vq_model = MAGVITv2.from_pretrained(source, local_files_only=local_files_only).to(device)
|
|
vq_model.requires_grad_(False)
|
|
vq_model.eval()
|
|
return vq_model
|
|
|
|
|
|
def encode_image_tokens(
|
|
image_path: Path,
|
|
vq_model: Any,
|
|
device: torch.device,
|
|
resolution: int,
|
|
) -> torch.Tensor:
|
|
image = Image.open(image_path).convert("RGB")
|
|
image_tensor = preprocess_image(image, resolution=resolution).unsqueeze(0).to(device)
|
|
with torch.no_grad():
|
|
token_ids = vq_model.get_code(image_tensor)
|
|
token_ids = torch.as_tensor(token_ids, dtype=torch.long).detach().cpu()
|
|
if token_ids.ndim == 2 and token_ids.shape[0] == 1:
|
|
token_ids = token_ids[0]
|
|
return token_ids.contiguous()
|
|
|
|
|
|
def encode_video_tokens(
|
|
video_path: Path,
|
|
vq_model: Any,
|
|
device: torch.device,
|
|
resolution: int,
|
|
num_frames: int,
|
|
) -> torch.Tensor:
|
|
import cv2
|
|
|
|
cap = cv2.VideoCapture(str(video_path))
|
|
frames: list[np.ndarray] = []
|
|
while True:
|
|
ok, frame = cap.read()
|
|
if not ok:
|
|
break
|
|
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
|
frames.append(frame)
|
|
cap.release()
|
|
if not frames:
|
|
raise ValueError(f"Video has no readable frames: {video_path}")
|
|
if len(frames) < num_frames:
|
|
raise ValueError(f"Video has {len(frames)} frames, requires >= {num_frames}: {video_path}")
|
|
|
|
indices = np.linspace(0, len(frames) - 1, num_frames).astype(int)
|
|
token_list: list[torch.Tensor] = []
|
|
for idx in indices:
|
|
pil = Image.fromarray(frames[int(idx)])
|
|
frame_tensor = preprocess_image(pil, resolution=resolution).unsqueeze(0).to(device)
|
|
with torch.no_grad():
|
|
token_list.append(torch.as_tensor(vq_model.get_code(frame_tensor), dtype=torch.long))
|
|
merged = torch.cat(token_list, dim=1).detach().cpu()
|
|
if merged.ndim == 2 and merged.shape[0] == 1:
|
|
merged = merged[0]
|
|
return merged.contiguous()
|
|
|
|
|
|
def load_vq_audio_encoder(source: str, local_files_only: bool, device: torch.device) -> Any:
|
|
from transformers import AutoModel
|
|
|
|
kwargs = {
|
|
"trust_remote_code": True,
|
|
"local_files_only": bool(local_files_only),
|
|
"low_cpu_mem_usage": False,
|
|
}
|
|
try:
|
|
model = AutoModel.from_pretrained(source, **kwargs)
|
|
except TypeError:
|
|
kwargs.pop("low_cpu_mem_usage", None)
|
|
try:
|
|
model = AutoModel.from_pretrained(source, **kwargs)
|
|
except TypeError:
|
|
kwargs.pop("local_files_only", None)
|
|
model = AutoModel.from_pretrained(source, **kwargs)
|
|
model.requires_grad_(False)
|
|
model.eval()
|
|
if hasattr(model, "to"):
|
|
model = model.to(device)
|
|
return model
|
|
|
|
|
|
def encode_audio_tokens(audio_path: Path, vq_audio_model: Any) -> torch.Tensor:
|
|
encoded = vq_audio_model.encode(str(audio_path))
|
|
if isinstance(encoded, dict):
|
|
for key in ("input_ids", "token_ids", "codes", "tokens"):
|
|
if key in encoded:
|
|
encoded = encoded[key]
|
|
break
|
|
encoded = torch.as_tensor(encoded, dtype=torch.long).detach().cpu()
|
|
if encoded.ndim == 1:
|
|
encoded = encoded.unsqueeze(0)
|
|
elif encoded.ndim > 2:
|
|
encoded = encoded.view(encoded.shape[0], -1)
|
|
return encoded.contiguous()
|
|
|
|
|
|
def build_chat_prompt(content: str) -> str:
|
|
return (
|
|
f"<|start_header_id|>user<|end_header_id|>\n{content}<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n"
|
|
)
|
|
|
|
|
|
def resolve_task_text(
|
|
*,
|
|
task_name: str,
|
|
text: str,
|
|
instruction: str = "",
|
|
raw_prompt: bool = False,
|
|
) -> str:
|
|
text = str(text or "").strip()
|
|
|
|
if task_name == "t2t" and not text:
|
|
return DEFAULT_T2T_PROMPT
|
|
if task_name == "i2t" and not text:
|
|
return DEFAULT_I2T_QUESTION
|
|
if task_name == "s2t" and not text:
|
|
return DEFAULT_S2T_INSTRUCTION
|
|
if task_name == "v2t" and not text:
|
|
return DEFAULT_V2T_QUESTION
|
|
if task_name in {"t2i", "i2i"} and not text:
|
|
return "A high quality detailed image."
|
|
|
|
if task_name != "t2s":
|
|
return text
|
|
|
|
if not text:
|
|
text = DEFAULT_T2S_PROMPT
|
|
|
|
if raw_prompt:
|
|
return text
|
|
|
|
instruction = str(instruction or "").strip() or DEFAULT_T2S_INSTRUCTION
|
|
return build_chat_prompt(f"{instruction}\n{text}")
|
|
|
|
|
|
def load_universal_prompting(
|
|
*,
|
|
tokenizer: Any,
|
|
tokenizer_source: str,
|
|
max_text_len: int,
|
|
cond_dropout_prob: float,
|
|
local_files_only: bool,
|
|
max_audio_len: int = 512,
|
|
max_audio_len_short: int = 256,
|
|
) -> Any:
|
|
from vllm_omni.model_executor.models.dynin_omni.dynin_omni_common import (
|
|
DYNIN_REMOTE_SETTINGS,
|
|
resolve_remote_attr,
|
|
)
|
|
|
|
UniversalPrompting = resolve_remote_attr(
|
|
"UniversalPrompting",
|
|
module_name="prompting_utils",
|
|
settings=DYNIN_REMOTE_SETTINGS,
|
|
source=tokenizer_source,
|
|
local_files_only=bool(local_files_only),
|
|
fallback_module_names=("modeling_dynin_omni",),
|
|
)
|
|
init_kwargs: dict[str, Any] = {
|
|
"max_text_len": int(max_text_len),
|
|
"special_tokens": DYNIN_SPECIAL_TOKENS,
|
|
"ignore_id": -100,
|
|
"cond_dropout_prob": float(cond_dropout_prob),
|
|
"use_reserved_token": True,
|
|
"max_audio_len": int(max_audio_len),
|
|
"max_audio_len_short": int(max_audio_len_short),
|
|
}
|
|
try:
|
|
return UniversalPrompting(tokenizer, **init_kwargs)
|
|
except TypeError:
|
|
init_kwargs.pop("max_audio_len", None)
|
|
init_kwargs.pop("max_audio_len_short", None)
|
|
return UniversalPrompting(tokenizer, **init_kwargs)
|
|
|
|
|
|
def _runtime_fallback(task: str, key: str, value: Any) -> Any:
|
|
if isinstance(value, str):
|
|
if value.strip() != "":
|
|
return value
|
|
elif value is not None:
|
|
return value
|
|
return TASK_RUNTIME_FALLBACKS.get(task, {}).get(key)
|
|
|
|
|
|
def _validate_generation_args(*, task: str, max_new_tokens: int, steps: int, block_length: int) -> None:
|
|
# Keep i2t/v2t generation constraints aligned with i2t.py/v2t.py.
|
|
if task not in {"i2t", "v2t"}:
|
|
return
|
|
if max_new_tokens <= 0:
|
|
raise ValueError(f"{task} requires max_new_tokens > 0.")
|
|
if block_length <= 0:
|
|
raise ValueError(f"{task} requires block_length > 0.")
|
|
if steps <= 0:
|
|
raise ValueError(f"{task} requires steps > 0.")
|
|
if max_new_tokens % block_length != 0:
|
|
raise ValueError(f"{task} requires max_new_tokens % block_length == 0, got {max_new_tokens} % {block_length}")
|
|
num_blocks = max_new_tokens // block_length
|
|
if num_blocks <= 0:
|
|
raise ValueError(f"{task} has invalid num_blocks.")
|
|
if steps % num_blocks != 0:
|
|
raise ValueError(
|
|
f"{task} requires steps % (max_new_tokens // block_length) == 0, "
|
|
f"got steps={steps}, max_new_tokens={max_new_tokens}, block_length={block_length}"
|
|
)
|
|
|
|
|
|
def make_prompt_payload(
|
|
*,
|
|
task: str,
|
|
text: str,
|
|
image_tokens: torch.Tensor | None,
|
|
audio_tokens: torch.Tensor | None,
|
|
video_tokens: torch.Tensor | None,
|
|
image_placeholder_tokens: int,
|
|
audio_placeholder_tokens: int,
|
|
image_token_offset: int,
|
|
speech_token_offset: int,
|
|
mask_token_id: int,
|
|
use_train_i2i_prompt: bool,
|
|
) -> tuple[Any, str]:
|
|
runtime_task, prompting_task, _, _ = TASK_DEFAULT_RUNTIME[task]
|
|
del runtime_task
|
|
|
|
if task == "t2t":
|
|
payload = ([[]], [build_chat_prompt(text)])
|
|
return payload, prompting_task
|
|
|
|
if task == "i2t":
|
|
if image_tokens is None:
|
|
raise ValueError("i2t requires image tokens")
|
|
img = image_tokens.view(-1).long() + int(image_token_offset)
|
|
payload = ([[img]], [build_chat_prompt(text)])
|
|
return payload, prompting_task
|
|
|
|
if task == "s2t":
|
|
if audio_tokens is None:
|
|
raise ValueError("s2t requires audio tokens")
|
|
aud = audio_tokens.long() + int(speech_token_offset)
|
|
if aud.ndim == 1:
|
|
aud = aud.unsqueeze(0)
|
|
payload = ([aud], [build_chat_prompt(text)])
|
|
return payload, prompting_task
|
|
|
|
if task == "v2t":
|
|
if video_tokens is None:
|
|
raise ValueError("v2t requires video tokens")
|
|
vid = video_tokens.view(-1).long() + int(image_token_offset)
|
|
payload = (vid.unsqueeze(0), [build_chat_prompt(text)])
|
|
return payload, prompting_task
|
|
|
|
if task == "t2i":
|
|
image_placeholder = torch.full(
|
|
(1, int(image_placeholder_tokens)),
|
|
fill_value=int(mask_token_id),
|
|
dtype=torch.long,
|
|
)
|
|
payload = ([text], image_placeholder)
|
|
return payload, prompting_task
|
|
|
|
if task == "i2i":
|
|
if image_tokens is None:
|
|
raise ValueError("i2i requires image tokens")
|
|
src = image_tokens.view(1, -1).long() + int(image_token_offset)
|
|
target_len = int(image_placeholder_tokens) if image_placeholder_tokens > 0 else int(src.shape[1])
|
|
image_placeholder = torch.full(
|
|
(1, target_len),
|
|
fill_value=int(mask_token_id),
|
|
dtype=torch.long,
|
|
)
|
|
if use_train_i2i_prompt:
|
|
labels_placeholder = torch.full(
|
|
(1, target_len),
|
|
fill_value=-100,
|
|
dtype=torch.long,
|
|
)
|
|
payload = ([text], src, image_placeholder, labels_placeholder)
|
|
return payload, "i2i"
|
|
payload = ([text], src, image_placeholder)
|
|
return payload, "i2i_gen"
|
|
|
|
if task == "t2s":
|
|
audio_placeholder = torch.full(
|
|
(1, int(audio_placeholder_tokens)),
|
|
fill_value=int(mask_token_id),
|
|
dtype=torch.long,
|
|
)
|
|
payload = ([text], audio_placeholder)
|
|
return payload, prompting_task
|
|
|
|
raise ValueError(f"Unsupported task: {task}")
|
|
|
|
|
|
def _to_1d_int_list(value: Any) -> list[int]:
|
|
if value is None:
|
|
return []
|
|
if isinstance(value, torch.Tensor):
|
|
tensor = value.detach().to(device="cpu", dtype=torch.long)
|
|
else:
|
|
tensor = torch.as_tensor(value, dtype=torch.long)
|
|
if tensor.ndim == 0:
|
|
tensor = tensor.view(1)
|
|
elif tensor.ndim >= 2:
|
|
tensor = tensor.view(tensor.shape[0], -1)[0]
|
|
return [int(v) for v in tensor.tolist()]
|
|
|
|
|
|
def _run_uni_prompting(uni_prompting: Any, payload: Any, prompting_task: str) -> tuple[list[int], list[int]]:
|
|
prepared = uni_prompting(payload, prompting_task)
|
|
if isinstance(prepared, tuple):
|
|
prepared_input_ids = prepared[0] if len(prepared) > 0 else None
|
|
prepared_attention_mask = prepared[1] if len(prepared) > 1 else None
|
|
else:
|
|
prepared_input_ids = prepared
|
|
prepared_attention_mask = None
|
|
|
|
input_ids = _to_1d_int_list(prepared_input_ids)
|
|
attention_mask = _to_1d_int_list(prepared_attention_mask)
|
|
if not input_ids:
|
|
raise RuntimeError(f"UniversalPrompting returned empty input_ids for task={prompting_task}")
|
|
return input_ids, attention_mask
|
|
|
|
|
|
def _get_special_token_id(uni_prompting: Any, token: str) -> int:
|
|
sptids = getattr(uni_prompting, "sptids_dict", None) or {}
|
|
if token not in sptids:
|
|
raise KeyError(f"Special token not found in UniversalPrompting.sptids_dict: {token}")
|
|
token_ids = _to_1d_int_list(sptids[token])
|
|
if not token_ids:
|
|
raise ValueError(f"Special token id is empty for token: {token}")
|
|
return int(token_ids[0])
|
|
|
|
|
|
def _tokenize_chat_query(tokenizer: Any, text: str) -> list[int]:
|
|
encoded = tokenizer(build_chat_prompt(text), return_tensors="pt").input_ids[0]
|
|
token_ids = _to_1d_int_list(encoded)
|
|
if not token_ids:
|
|
raise RuntimeError("Failed to tokenize chat query text.")
|
|
return token_ids
|
|
|
|
|
|
def _flatten_media_token_ids_with_offset(token_ids: Any, token_offset: int) -> list[int]:
|
|
media_ids = token_ids
|
|
if isinstance(media_ids, torch.Tensor):
|
|
media_ids = media_ids.detach().cpu().reshape(-1).tolist()
|
|
else:
|
|
media_ids = np.asarray(media_ids).reshape(-1).tolist()
|
|
return [int(x) + int(token_offset) for x in media_ids]
|
|
|
|
|
|
def _scalar_token_id(value: Any) -> int:
|
|
if isinstance(value, torch.Tensor):
|
|
if value.numel() == 0:
|
|
raise ValueError("Empty special-token tensor.")
|
|
return int(value.view(-1)[0].item())
|
|
if isinstance(value, (list, tuple)):
|
|
if not value:
|
|
raise ValueError("Empty special-token list.")
|
|
return int(value[0])
|
|
return int(value)
|
|
|
|
|
|
def build_v2t_input_ids(
|
|
*,
|
|
video_token_ids: Any,
|
|
tokenizer: Any,
|
|
uni_prompting: Any,
|
|
question: str,
|
|
image_token_offset: int,
|
|
) -> tuple[list[int], str]:
|
|
media_ids = video_token_ids
|
|
if isinstance(media_ids, torch.Tensor):
|
|
media_ids = media_ids.detach().cpu().reshape(-1).tolist()
|
|
else:
|
|
media_ids = np.asarray(media_ids).reshape(-1).tolist()
|
|
media_ids = [int(x) + int(image_token_offset) for x in media_ids]
|
|
|
|
sptids = uni_prompting.sptids_dict
|
|
task_id = _scalar_token_id(sptids["<|v2t|>"])
|
|
soi_id = _scalar_token_id(sptids["<|soi|>"])
|
|
eoi_id = _scalar_token_id(sptids["<|eoi|>"])
|
|
sot_id = _scalar_token_id(sptids["<|sot|>"])
|
|
|
|
prompt_text = build_v2t_chat_prompt(question)
|
|
query_ids = tokenizer(prompt_text, return_tensors="pt").input_ids[0].detach().cpu().tolist()
|
|
input_ids = [task_id, soi_id] + media_ids + [eoi_id, sot_id] + [int(v) for v in query_ids]
|
|
return input_ids, prompt_text
|
|
|
|
|
|
def build_i2t_input_ids(
|
|
*,
|
|
image_token_ids: Any,
|
|
tokenizer: Any,
|
|
uni_prompting: Any,
|
|
question: str,
|
|
image_token_offset: int,
|
|
) -> tuple[list[int], str]:
|
|
image_ids = image_token_ids
|
|
if isinstance(image_ids, torch.Tensor):
|
|
image_ids = image_ids.detach().cpu().reshape(-1).tolist()
|
|
else:
|
|
image_ids = np.asarray(image_ids).reshape(-1).tolist()
|
|
image_ids = [int(x) + int(image_token_offset) for x in image_ids]
|
|
|
|
sptids = uni_prompting.sptids_dict
|
|
task_id = _scalar_token_id(sptids["<|mmu|>"])
|
|
soi_id = _scalar_token_id(sptids["<|soi|>"])
|
|
eoi_id = _scalar_token_id(sptids["<|eoi|>"])
|
|
sot_id = _scalar_token_id(sptids["<|sot|>"])
|
|
|
|
prompt_text = build_i2t_chat_prompt(question)
|
|
query_ids = tokenizer(prompt_text, return_tensors="pt").input_ids[0].detach().cpu().tolist()
|
|
input_ids = [task_id, soi_id] + image_ids + [eoi_id, sot_id] + [int(v) for v in query_ids]
|
|
return input_ids, prompt_text
|
|
|
|
|
|
def build_v2t_chat_prompt(question: str) -> str:
|
|
return (
|
|
f"<|start_header_id|>user<|end_header_id|>\n{question}<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n"
|
|
)
|
|
|
|
|
|
def build_i2t_chat_prompt(question: str) -> str:
|
|
return (
|
|
f"<|start_header_id|>user<|end_header_id|>\n{question}<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n"
|
|
)
|
|
|
|
|
|
def make_mmu_prompt(
|
|
*,
|
|
task: str,
|
|
text: str,
|
|
tokenizer: Any,
|
|
uni_prompting: Any,
|
|
image_tokens: torch.Tensor | None,
|
|
audio_tokens: torch.Tensor | None,
|
|
video_tokens: torch.Tensor | None,
|
|
image_token_offset: int,
|
|
speech_token_offset: int,
|
|
) -> tuple[list[int], list[int]]:
|
|
query_ids = _tokenize_chat_query(tokenizer, text)
|
|
|
|
if task == "i2t":
|
|
token_ids, _ = build_i2t_input_ids(
|
|
image_token_ids=image_tokens,
|
|
tokenizer=tokenizer,
|
|
uni_prompting=uni_prompting,
|
|
question=text,
|
|
image_token_offset=int(image_token_offset),
|
|
)
|
|
token_ids = [int(v) for v in token_ids]
|
|
return token_ids, [1] * len(token_ids)
|
|
|
|
if task == "v2t":
|
|
token_ids, _ = build_v2t_input_ids(
|
|
video_token_ids=video_tokens,
|
|
tokenizer=tokenizer,
|
|
uni_prompting=uni_prompting,
|
|
question=text,
|
|
image_token_offset=int(image_token_offset),
|
|
)
|
|
token_ids = [int(v) for v in token_ids]
|
|
return token_ids, [1] * len(token_ids)
|
|
|
|
if task == "s2t":
|
|
if audio_tokens is None:
|
|
raise ValueError("s2t requires audio tokens")
|
|
audio_ids = _to_1d_int_list(audio_tokens.long() + int(speech_token_offset))
|
|
token_ids = [
|
|
_get_special_token_id(uni_prompting, "<|s2t|>"),
|
|
_get_special_token_id(uni_prompting, "<|soa|>"),
|
|
*audio_ids,
|
|
_get_special_token_id(uni_prompting, "<|eoa|>"),
|
|
*query_ids,
|
|
]
|
|
return token_ids, [1] * len(token_ids)
|
|
|
|
raise ValueError(f"Unsupported task for validation-style MMU prompt: {task}")
|
|
|
|
|
|
def iter_mm_outputs(outputs: list[Any]):
|
|
for omni_out in outputs:
|
|
req_out = omni_out
|
|
req_list = req_out if isinstance(req_out, list) else [req_out]
|
|
for item in req_list:
|
|
if item is None:
|
|
continue
|
|
mm_out = getattr(item, "multimodal_output", None) or {}
|
|
if mm_out:
|
|
yield mm_out
|
|
completions = getattr(item, "outputs", None) or []
|
|
for completion in completions:
|
|
c_mm_out = getattr(completion, "multimodal_output", None) or {}
|
|
if c_mm_out:
|
|
yield c_mm_out
|
|
omni_mm = getattr(omni_out, "multimodal_output", None) or {}
|
|
if omni_mm:
|
|
yield omni_mm
|
|
|
|
|
|
def _to_token_list(value: Any) -> list[int]:
|
|
if value is None:
|
|
return []
|
|
if hasattr(value, "detach"):
|
|
value = value.detach()
|
|
if hasattr(value, "cpu"):
|
|
value = value.cpu()
|
|
if hasattr(value, "flatten"):
|
|
value = value.flatten().tolist()
|
|
if isinstance(value, tuple):
|
|
value = list(value)
|
|
if not isinstance(value, list):
|
|
return []
|
|
out: list[int] = []
|
|
for token in value:
|
|
if isinstance(token, bool):
|
|
continue
|
|
try:
|
|
out.append(int(token))
|
|
except Exception:
|
|
continue
|
|
return out
|
|
|
|
|
|
def extract_text_output(outputs: list[Any], tokenizer: Any) -> str:
|
|
for mm_out in iter_mm_outputs(outputs):
|
|
text = mm_out.get("text")
|
|
if isinstance(text, list) and text:
|
|
text = text[-1]
|
|
if isinstance(text, str) and text.strip():
|
|
return text.strip()
|
|
for key in ("text_tokens", "token_ids"):
|
|
token_ids = _to_token_list(mm_out.get(key))
|
|
if not token_ids:
|
|
continue
|
|
decoded = tokenizer.decode(token_ids, skip_special_tokens=True)
|
|
if isinstance(decoded, str) and decoded.strip():
|
|
return decoded.strip()
|
|
return ""
|
|
|
|
|
|
def extract_image_output(outputs: list[Any]) -> torch.Tensor | None:
|
|
for mm_out in iter_mm_outputs(outputs):
|
|
image = mm_out.get("image")
|
|
if isinstance(image, list) and image:
|
|
image = image[-1]
|
|
if isinstance(image, torch.Tensor):
|
|
return image
|
|
return None
|
|
|
|
|
|
def tensor_to_pil_image(image: torch.Tensor) -> Image.Image:
|
|
arr = image.detach().cpu().numpy()
|
|
if arr.ndim == 4:
|
|
arr = arr[0]
|
|
if arr.ndim == 3 and arr.shape[0] in (1, 3, 4):
|
|
arr = np.transpose(arr, (1, 2, 0))
|
|
if arr.dtype != np.uint8:
|
|
arr = arr.astype(np.float32)
|
|
if arr.max() <= 1.0:
|
|
arr = arr * 255.0
|
|
arr = np.clip(arr, 0.0, 255.0).astype(np.uint8)
|
|
if arr.ndim == 3 and arr.shape[-1] == 1:
|
|
arr = arr[..., 0]
|
|
return Image.fromarray(arr)
|
|
|
|
|
|
def extract_audio_output(outputs: list[Any]) -> tuple[np.ndarray, int] | None:
|
|
for mm_out in iter_mm_outputs(outputs):
|
|
audio = mm_out.get("audio")
|
|
if audio is None:
|
|
audio = mm_out.get("speech")
|
|
if audio is None:
|
|
continue
|
|
|
|
def _to_wav_array(value: Any) -> np.ndarray:
|
|
if isinstance(value, torch.Tensor):
|
|
return value.detach().cpu().numpy().reshape(-1).astype(np.float32)
|
|
return np.asarray(value).reshape(-1).astype(np.float32)
|
|
|
|
if isinstance(audio, list):
|
|
chunks = [_to_wav_array(chunk) for chunk in audio]
|
|
wav = np.concatenate(chunks, axis=0) if chunks else np.zeros((0,), dtype=np.float32)
|
|
else:
|
|
wav = _to_wav_array(audio)
|
|
sr = mm_out.get("sr", 24000)
|
|
if hasattr(sr, "item"):
|
|
try:
|
|
sr = int(sr.item())
|
|
except Exception:
|
|
sr = 24000
|
|
elif isinstance(sr, list):
|
|
sr = int(sr[0]) if sr else 24000
|
|
else:
|
|
sr = int(sr)
|
|
return wav, sr
|
|
return None
|
|
|
|
|
|
def save_audio_wav(path: Path, wav: np.ndarray, sr: int) -> None:
|
|
try:
|
|
import soundfile as sf
|
|
|
|
sf.write(str(path), wav, int(sr), format="WAV")
|
|
except Exception:
|
|
from scipy.io import wavfile
|
|
|
|
wav_i16 = np.clip(wav, -1.0, 1.0)
|
|
wav_i16 = (wav_i16 * 32767.0).astype(np.int16)
|
|
wavfile.write(str(path), int(sr), wav_i16)
|
|
|
|
|
|
def parse_args(repo_root: Path) -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(description="Dynin-Omni unified offline end2end example.")
|
|
parser.add_argument("--task", type=str, required=True, choices=TASK_CHOICES)
|
|
parser.add_argument("--model", type=str, required=True, help="HF repo id or local model directory.")
|
|
parser.add_argument(
|
|
"--stage-config-path",
|
|
type=str,
|
|
default=str(repo_root / "vllm_omni/deploy/dynin_omni.yaml"),
|
|
help="Path to deploy config yaml.",
|
|
)
|
|
parser.add_argument(
|
|
"--dynin-config-path",
|
|
type=str,
|
|
default="",
|
|
help="Path to DYNIN config yaml (passed through additional_information).",
|
|
)
|
|
parser.add_argument(
|
|
"--model-cache-dir",
|
|
type=str,
|
|
default="/tmp/dynin_localized_models",
|
|
help="Cache directory used when --model is HF repo id.",
|
|
)
|
|
parser.add_argument(
|
|
"--localize-model",
|
|
action=argparse.BooleanOptionalAction,
|
|
default=True,
|
|
help="If true and --model is HF repo id, snapshot it under --model-cache-dir.",
|
|
)
|
|
parser.add_argument("--text", type=str, default="", help="Prompt/edit/question text.")
|
|
parser.add_argument("--instruction", type=str, default="", help="Optional extra instruction.")
|
|
parser.add_argument("--raw-prompt", action=argparse.BooleanOptionalAction, default=False)
|
|
parser.add_argument("--image", type=str, default="", help="Input image path for i2i/i2t.")
|
|
parser.add_argument("--audio", type=str, default="", help="Input audio path for s2t.")
|
|
parser.add_argument("--video", type=str, default="", help="Input video path for v2t.")
|
|
parser.add_argument("--image-resolution", type=int, default=None)
|
|
parser.add_argument("--num-frames", type=int, default=None)
|
|
parser.add_argument(
|
|
"--output-dir",
|
|
type=str,
|
|
default="",
|
|
help="Directory for generated outputs.",
|
|
)
|
|
parser.add_argument("--output-prefix", type=str, default="")
|
|
parser.add_argument("--seed", type=int, default=0)
|
|
parser.add_argument("--dtype", type=str, default="auto")
|
|
parser.add_argument("--max-tokens-per-stage", type=int, default=1)
|
|
|
|
parser.add_argument("--runtime-task", type=str, default="", help="Override runtime task key.")
|
|
parser.add_argument("--prompting-task", type=str, default="", help="Override prompting task key.")
|
|
parser.add_argument("--detok-id", type=int, default=None, help="Override detok id.")
|
|
|
|
parser.add_argument("--prompt-max-text-len", type=int, default=None)
|
|
parser.add_argument("--cond-dropout-prob", type=float, default=0.0)
|
|
parser.add_argument("--max-new-tokens", type=int, default=None)
|
|
parser.add_argument("--steps", type=int, default=None)
|
|
parser.add_argument("--block-length", type=int, default=None)
|
|
parser.add_argument("--temperature", type=float, default=None)
|
|
parser.add_argument("--cfg-scale", type=float, default=None)
|
|
parser.add_argument("--remasking", type=str, default="low_confidence")
|
|
|
|
parser.add_argument("--timesteps", type=int, default=None)
|
|
parser.add_argument("--guidance-scale", type=float, default=None)
|
|
parser.add_argument("--noise-type", type=str, default="mask")
|
|
parser.add_argument("--noise-schedule-name", type=str, default="cosine")
|
|
parser.add_argument("--noise-schedule-params", type=str, default="{}")
|
|
|
|
parser.add_argument("--mask-token-id", type=int, default=None)
|
|
parser.add_argument("--codebook-size", type=int, default=None)
|
|
parser.add_argument("--audio-codebook-size", type=int, default=None)
|
|
parser.add_argument("--image-token-count", type=int, default=None)
|
|
parser.add_argument("--t2s-token-length", type=int, default=None)
|
|
parser.add_argument(
|
|
"--t2s-condition",
|
|
type=str,
|
|
default="",
|
|
)
|
|
parser.add_argument(
|
|
"--use-train-i2i-prompt",
|
|
action="store_true",
|
|
help="Use i2i training prompt template (default behavior of i2i.py).",
|
|
)
|
|
parser.add_argument(
|
|
"--no-use-train-i2i-prompt",
|
|
dest="use_train_i2i_prompt",
|
|
action="store_false",
|
|
help="Use i2i_gen prompt template.",
|
|
)
|
|
parser.set_defaults(use_train_i2i_prompt=None)
|
|
|
|
parser.add_argument("--tokenizer-path", type=str, default="")
|
|
parser.add_argument("--model-local-files-only", action=argparse.BooleanOptionalAction, default=None)
|
|
parser.add_argument("--tokenizer-local-files-only", action=argparse.BooleanOptionalAction, default=None)
|
|
|
|
parser.add_argument("--vq-model-image-path", type=str, default="")
|
|
parser.add_argument("--vq-model-image-local-files-only", action=argparse.BooleanOptionalAction, default=None)
|
|
parser.add_argument("--vq-model-audio-path", type=str, default="")
|
|
parser.add_argument("--vq-model-audio-local-files-only", action=argparse.BooleanOptionalAction, default=None)
|
|
|
|
parser.add_argument("--disable-hf-xet", action=argparse.BooleanOptionalAction, default=True)
|
|
return parser.parse_args()
|
|
|
|
|
|
def main() -> None:
|
|
repo_root = bootstrap_repo_path()
|
|
ensure_safe_import_for_vllm()
|
|
from vllm_omni.model_executor.models.dynin_omni.dynin_omni_common import (
|
|
DYNIN_PROMPT_SOURCE_KEY,
|
|
DYNIN_PROMPT_SOURCE_OFFLINE_PREBUILT,
|
|
)
|
|
|
|
args = parse_args(repo_root)
|
|
|
|
if args.disable_hf_xet:
|
|
os.environ.setdefault("HF_HUB_DISABLE_XET", "1")
|
|
|
|
np.random.seed(args.seed)
|
|
torch.manual_seed(args.seed)
|
|
|
|
model_dir = ensure_local_model_dir(
|
|
model=args.model,
|
|
cache_dir=Path(args.model_cache_dir).expanduser(),
|
|
localize=bool(args.localize_model),
|
|
)
|
|
model_source = str(model_dir)
|
|
|
|
task_name = str(args.task)
|
|
dynin_config_path = str(Path(args.dynin_config_path).expanduser())
|
|
os.environ["DYNIN_CONFIG_PATH"] = dynin_config_path
|
|
default_runtime_task, default_prompting_task, default_detok_id, final_modality = TASK_DEFAULT_RUNTIME[task_name]
|
|
runtime_task = args.runtime_task.strip() or str(
|
|
_runtime_fallback(task_name, "runtime_task", None) or default_runtime_task
|
|
)
|
|
prompting_task = args.prompting_task.strip() or str(
|
|
_runtime_fallback(task_name, "prompting_task", None) or default_prompting_task
|
|
)
|
|
detok_id_default = _runtime_fallback(task_name, "detok_id", None)
|
|
if detok_id_default is None:
|
|
detok_id_default = default_detok_id
|
|
detok_id = int(detok_id_default if args.detok_id is None else args.detok_id)
|
|
|
|
output_dir_default = _runtime_fallback(task_name, "output_dir", args.output_dir)
|
|
resolved_output_dir = str(output_dir_default or "/tmp/dynin_end2end_outputs")
|
|
|
|
image_resolution_value = _runtime_fallback(
|
|
task_name,
|
|
"image_resolution",
|
|
args.image_resolution,
|
|
)
|
|
if image_resolution_value is None:
|
|
image_resolution_value = 336
|
|
image_resolution = int(image_resolution_value)
|
|
|
|
num_frames_value = _runtime_fallback(
|
|
task_name,
|
|
"num_frames",
|
|
args.num_frames,
|
|
)
|
|
if num_frames_value is None:
|
|
num_frames_value = 8
|
|
num_frames = int(num_frames_value)
|
|
|
|
prompt_max_text_len_value = _runtime_fallback(
|
|
task_name,
|
|
"prompt_max_text_len",
|
|
args.prompt_max_text_len,
|
|
)
|
|
if prompt_max_text_len_value is None:
|
|
prompt_max_text_len_value = 1024
|
|
prompt_max_text_len = int(prompt_max_text_len_value)
|
|
|
|
max_new_tokens_value = _runtime_fallback(
|
|
task_name,
|
|
"max_new_tokens",
|
|
args.max_new_tokens,
|
|
)
|
|
if max_new_tokens_value is None:
|
|
max_new_tokens_value = 256
|
|
max_new_tokens = int(max_new_tokens_value)
|
|
|
|
steps_value = _runtime_fallback(
|
|
task_name,
|
|
"steps",
|
|
args.steps,
|
|
)
|
|
if steps_value is None:
|
|
steps_value = 256
|
|
steps = int(steps_value)
|
|
|
|
block_length_value = _runtime_fallback(
|
|
task_name,
|
|
"block_length",
|
|
args.block_length,
|
|
)
|
|
if block_length_value is None:
|
|
block_length_value = 2
|
|
block_length = int(block_length_value)
|
|
|
|
temperature_value = _runtime_fallback(
|
|
task_name,
|
|
"temperature",
|
|
args.temperature,
|
|
)
|
|
if temperature_value is None:
|
|
temperature_value = 0.0
|
|
temperature = float(temperature_value)
|
|
|
|
cfg_scale_value = _runtime_fallback(
|
|
task_name,
|
|
"cfg_scale",
|
|
args.cfg_scale,
|
|
)
|
|
if cfg_scale_value is None:
|
|
cfg_scale_value = 0.0
|
|
cfg_scale = float(cfg_scale_value)
|
|
|
|
remasking = str(_runtime_fallback(task_name, "remasking", args.remasking) or "low_confidence")
|
|
|
|
timesteps_value = _runtime_fallback(
|
|
task_name,
|
|
"timesteps",
|
|
args.timesteps,
|
|
)
|
|
if timesteps_value is None:
|
|
timesteps_value = 20
|
|
timesteps = int(timesteps_value)
|
|
|
|
guidance_scale_value = _runtime_fallback(
|
|
task_name,
|
|
"guidance_scale",
|
|
args.guidance_scale,
|
|
)
|
|
if guidance_scale_value is None:
|
|
guidance_scale_value = 0.0
|
|
guidance_scale = float(guidance_scale_value)
|
|
|
|
mask_token_id_value = _runtime_fallback(
|
|
task_name,
|
|
"mask_token_id",
|
|
args.mask_token_id,
|
|
)
|
|
if mask_token_id_value is None:
|
|
mask_token_id_value = 126336
|
|
mask_token_id = int(mask_token_id_value)
|
|
|
|
codebook_size_value = _runtime_fallback(
|
|
task_name,
|
|
"codebook_size",
|
|
args.codebook_size,
|
|
)
|
|
if codebook_size_value is None:
|
|
codebook_size_value = 8192
|
|
codebook_size = int(codebook_size_value)
|
|
|
|
audio_codebook_size_value = _runtime_fallback(
|
|
task_name,
|
|
"audio_codebook_size",
|
|
args.audio_codebook_size,
|
|
)
|
|
if audio_codebook_size_value is None:
|
|
audio_codebook_size_value = 4096
|
|
audio_codebook_size = int(audio_codebook_size_value)
|
|
|
|
image_token_count_value = _runtime_fallback(
|
|
task_name,
|
|
"image_token_count",
|
|
args.image_token_count,
|
|
)
|
|
image_token_count = int(image_token_count_value) if image_token_count_value is not None else 0
|
|
|
|
t2s_token_length_value = _runtime_fallback(
|
|
task_name,
|
|
"t2s_token_length",
|
|
args.t2s_token_length,
|
|
)
|
|
if t2s_token_length_value is None:
|
|
t2s_token_length_value = 383
|
|
t2s_token_length = int(t2s_token_length_value)
|
|
|
|
t2s_condition = str(
|
|
_runtime_fallback(task_name, "t2s_condition", args.t2s_condition)
|
|
or "gender-female_emotion-neutral_speed-normal_pitch-normal"
|
|
)
|
|
|
|
_validate_generation_args(
|
|
task=task_name,
|
|
max_new_tokens=max_new_tokens,
|
|
steps=steps,
|
|
block_length=block_length,
|
|
)
|
|
|
|
use_train_i2i_prompt = _runtime_fallback(task_name, "use_train_i2i_prompt", args.use_train_i2i_prompt)
|
|
if use_train_i2i_prompt is None:
|
|
use_train_i2i_prompt = bool(task_name == "i2i")
|
|
use_train_i2i_prompt = bool(use_train_i2i_prompt)
|
|
|
|
if task_name in {"i2i", "i2t"} and not args.image:
|
|
raise ValueError(f"--task {task_name} requires --image")
|
|
if task_name == "s2t" and not args.audio:
|
|
raise ValueError("--task s2t requires --audio")
|
|
if task_name == "v2t" and not args.video:
|
|
raise ValueError("--task v2t requires --video")
|
|
|
|
text = resolve_task_text(
|
|
task_name=task_name,
|
|
text=args.text,
|
|
instruction=args.instruction,
|
|
raw_prompt=bool(args.raw_prompt),
|
|
)
|
|
|
|
tokenizer_source = args.tokenizer_path.strip() or model_source
|
|
model_local_only = resolve_local_only(
|
|
args.model_local_files_only, model_source, default=Path(model_source).is_dir()
|
|
)
|
|
tokenizer_local_only = resolve_local_only(
|
|
args.tokenizer_local_files_only,
|
|
tokenizer_source,
|
|
default=model_local_only,
|
|
)
|
|
tokenizer = load_text_tokenizer(tokenizer_source, local_files_only=tokenizer_local_only)
|
|
text_vocab_size = int(len(tokenizer))
|
|
|
|
image_tokens: torch.Tensor | None = None
|
|
audio_tokens: torch.Tensor | None = None
|
|
video_tokens: torch.Tensor | None = None
|
|
|
|
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
|
|
|
vq_image_source = args.vq_model_image_path.strip() or "snu-aidas/magvitv2"
|
|
vq_audio_source = args.vq_model_audio_path.strip() or "snu-aidas/emova_speech_tokenizer_vllm"
|
|
vq_image_local_only = resolve_local_only(args.vq_model_image_local_files_only, vq_image_source, default=False)
|
|
vq_audio_local_only = resolve_local_only(args.vq_model_audio_local_files_only, vq_audio_source, default=False)
|
|
|
|
if task_name in {"i2i", "i2t", "v2t"}:
|
|
vq_image = load_vq_image_encoder(vq_image_source, vq_image_local_only, device)
|
|
if task_name in {"i2i", "i2t"}:
|
|
image_tokens = encode_image_tokens(
|
|
Path(args.image).expanduser().resolve(),
|
|
vq_model=vq_image,
|
|
device=device,
|
|
resolution=int(image_resolution),
|
|
)
|
|
if task_name == "v2t":
|
|
video_tokens = encode_video_tokens(
|
|
Path(args.video).expanduser().resolve(),
|
|
vq_model=vq_image,
|
|
device=device,
|
|
resolution=int(image_resolution),
|
|
num_frames=int(num_frames),
|
|
)
|
|
if hasattr(vq_image, "cpu"):
|
|
vq_image = vq_image.cpu()
|
|
|
|
if task_name == "s2t":
|
|
vq_audio = load_vq_audio_encoder(vq_audio_source, vq_audio_local_only, device)
|
|
audio_tokens = encode_audio_tokens(Path(args.audio).expanduser().resolve(), vq_audio)
|
|
if hasattr(vq_audio, "cpu"):
|
|
vq_audio = vq_audio.cpu()
|
|
|
|
noise_schedule_params: dict[str, Any] = {}
|
|
try:
|
|
parsed = json.loads(args.noise_schedule_params)
|
|
if isinstance(parsed, dict):
|
|
noise_schedule_params = {str(k): v for k, v in parsed.items()}
|
|
except Exception:
|
|
noise_schedule_params = {}
|
|
|
|
image_token_count = int(image_token_count)
|
|
if image_token_count <= 0:
|
|
if image_tokens is not None:
|
|
image_token_count = int(image_tokens.numel())
|
|
else:
|
|
base_res = int(image_resolution)
|
|
image_token_count = max(1, (base_res // 16) ** 2)
|
|
|
|
uncond_input_ids: list[int] | None = None
|
|
uncond_attention_mask: list[int] | None = None
|
|
if task_name == "t2t":
|
|
messages = [{"role": "user", "content": text}]
|
|
if getattr(tokenizer, "chat_template", None):
|
|
prompt_text = tokenizer.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
|
|
encoded = tokenizer(prompt_text, return_tensors="pt", add_special_tokens=False)
|
|
else:
|
|
encoded = tokenizer(text, return_tensors="pt", add_special_tokens=True)
|
|
prompt_token_ids = _to_1d_int_list(encoded["input_ids"])
|
|
prompt_attention_mask = _to_1d_int_list(encoded.get("attention_mask"))
|
|
if not prompt_attention_mask:
|
|
prompt_attention_mask = [1] * len(prompt_token_ids)
|
|
else:
|
|
max_audio_len_for_prompt = int(max(t2s_token_length, 512))
|
|
if audio_tokens is not None:
|
|
max_audio_len_for_prompt = max(max_audio_len_for_prompt, int(audio_tokens.numel()))
|
|
max_audio_len_short_for_prompt = max(256, max_audio_len_for_prompt // 2)
|
|
|
|
uni_prompting = load_universal_prompting(
|
|
tokenizer=tokenizer,
|
|
tokenizer_source=tokenizer_source,
|
|
max_text_len=int(prompt_max_text_len),
|
|
cond_dropout_prob=float(args.cond_dropout_prob),
|
|
local_files_only=bool(tokenizer_local_only),
|
|
max_audio_len=int(max_audio_len_for_prompt),
|
|
max_audio_len_short=int(max_audio_len_short_for_prompt),
|
|
)
|
|
prompting_text_vocab_size = int(len(uni_prompting.text_tokenizer))
|
|
|
|
is_mmu_task = task_name in {"i2t", "s2t", "v2t"} and not args.prompting_task.strip()
|
|
if is_mmu_task:
|
|
prompt_token_ids, prompt_attention_mask = make_mmu_prompt(
|
|
task=task_name,
|
|
text=text,
|
|
tokenizer=uni_prompting.text_tokenizer,
|
|
uni_prompting=uni_prompting,
|
|
image_tokens=image_tokens,
|
|
audio_tokens=audio_tokens,
|
|
video_tokens=video_tokens,
|
|
image_token_offset=prompting_text_vocab_size,
|
|
speech_token_offset=prompting_text_vocab_size + int(codebook_size),
|
|
)
|
|
else:
|
|
prompt_payload, prompting_task = make_prompt_payload(
|
|
task=task_name,
|
|
text=text,
|
|
image_tokens=image_tokens,
|
|
audio_tokens=audio_tokens,
|
|
video_tokens=video_tokens,
|
|
image_placeholder_tokens=image_token_count,
|
|
audio_placeholder_tokens=int(t2s_token_length),
|
|
image_token_offset=text_vocab_size,
|
|
speech_token_offset=text_vocab_size + int(codebook_size),
|
|
mask_token_id=int(mask_token_id),
|
|
use_train_i2i_prompt=use_train_i2i_prompt,
|
|
)
|
|
if args.prompting_task.strip():
|
|
prompting_task = args.prompting_task.strip()
|
|
|
|
prompt_token_ids, prompt_attention_mask = _run_uni_prompting(
|
|
uni_prompting,
|
|
prompt_payload,
|
|
prompting_task,
|
|
)
|
|
|
|
if task_name in {"i2t", "s2t", "v2t"}:
|
|
prompt_attention_mask = [1] * len(prompt_token_ids)
|
|
if not prompt_attention_mask:
|
|
prompt_attention_mask = [1] * len(prompt_token_ids)
|
|
|
|
if task_name in {"t2i", "i2i"} and guidance_scale > 0:
|
|
uncond_payload, uncond_prompting_task = make_prompt_payload(
|
|
task=task_name,
|
|
text="",
|
|
image_tokens=image_tokens,
|
|
audio_tokens=audio_tokens,
|
|
video_tokens=video_tokens,
|
|
image_placeholder_tokens=image_token_count,
|
|
audio_placeholder_tokens=int(t2s_token_length),
|
|
image_token_offset=text_vocab_size,
|
|
speech_token_offset=text_vocab_size + int(codebook_size),
|
|
mask_token_id=int(mask_token_id),
|
|
use_train_i2i_prompt=use_train_i2i_prompt,
|
|
)
|
|
uncond_input_ids, uncond_attention_mask = _run_uni_prompting(
|
|
uni_prompting,
|
|
uncond_payload,
|
|
args.prompting_task.strip() or uncond_prompting_task,
|
|
)
|
|
if not uncond_attention_mask:
|
|
uncond_attention_mask = [1] * len(uncond_input_ids)
|
|
|
|
runtime_info: dict[str, Any] = {
|
|
"task": [runtime_task],
|
|
"detok_id": [int(detok_id)],
|
|
DYNIN_PROMPT_SOURCE_KEY: [DYNIN_PROMPT_SOURCE_OFFLINE_PREBUILT],
|
|
"dynin_config_path": [str(dynin_config_path)],
|
|
"attention_mask": [prompt_attention_mask],
|
|
"prompt_max_text_len": [int(prompt_max_text_len)],
|
|
"prompting_max_text_len": [int(prompt_max_text_len)],
|
|
"cond_dropout_prob": [float(args.cond_dropout_prob)],
|
|
"prompting_cond_dropout_prob": [float(args.cond_dropout_prob)],
|
|
"tokenizer_path": [str(tokenizer_source)],
|
|
"text_vocab_size": [int(text_vocab_size)],
|
|
"model_local_files_only": [bool(model_local_only)],
|
|
"max_new_tokens": [int(max_new_tokens)],
|
|
"steps": [int(steps)],
|
|
"block_length": [int(block_length)],
|
|
"temperature": [float(temperature)],
|
|
"cfg_scale": [float(cfg_scale)],
|
|
"remasking": [str(remasking)],
|
|
"mask_id": [int(mask_token_id)],
|
|
"mask_token_id": [int(mask_token_id)],
|
|
"codebook_size": [int(codebook_size)],
|
|
"audio_codebook_size": [int(audio_codebook_size)],
|
|
"timesteps": [int(timesteps)],
|
|
"guidance_scale": [float(guidance_scale)],
|
|
"noise_type": [str(args.noise_type)],
|
|
"noise_schedule_name": [str(args.noise_schedule_name)],
|
|
"noise_schedule_params": [noise_schedule_params],
|
|
"seq_len": [int(image_token_count)],
|
|
"condition": [str(t2s_condition)],
|
|
"vq_model_image_path": [str(vq_image_source)],
|
|
"vq_model_image_local_files_only": [bool(vq_image_local_only)],
|
|
"vq_model_audio_path": [str(vq_audio_source)],
|
|
"vq_model_audio_local_files_only": [bool(vq_audio_local_only)],
|
|
}
|
|
|
|
if task_name in {"t2t", "i2t", "s2t", "v2t"}:
|
|
runtime_info["prompt_length"] = [int(len(prompt_token_ids))]
|
|
if uncond_input_ids is not None:
|
|
runtime_info["uncond_input_ids"] = [uncond_input_ids]
|
|
if uncond_attention_mask is not None:
|
|
runtime_info["uncond_attention_mask"] = [uncond_attention_mask]
|
|
|
|
if task_name == "t2s":
|
|
runtime_info["max_new_tokens"] = [int(t2s_token_length)]
|
|
|
|
prompt = {
|
|
"prompt_token_ids": [int(v) for v in prompt_token_ids],
|
|
"additional_information": runtime_info,
|
|
"modalities": [final_modality],
|
|
}
|
|
|
|
from vllm import SamplingParams
|
|
|
|
from vllm_omni.entrypoints.omni import Omni
|
|
|
|
stage_config_path = str(Path(args.stage_config_path).expanduser())
|
|
omni = Omni(model=model_source, stage_configs_path=stage_config_path, dtype=args.dtype)
|
|
sampling_params_list = [
|
|
SamplingParams(max_tokens=int(args.max_tokens_per_stage), temperature=0.0, top_p=1.0, detokenize=False)
|
|
for _ in range(omni.num_stages)
|
|
]
|
|
|
|
try:
|
|
outputs = list(omni.generate(prompt, sampling_params_list))
|
|
finally:
|
|
omni.close()
|
|
|
|
out_dir = Path(resolved_output_dir).expanduser()
|
|
out_dir.mkdir(parents=True, exist_ok=True)
|
|
stamp = time.strftime("%Y%m%d_%H%M%S")
|
|
prefix = args.output_prefix.strip() or f"{task_name}_{stamp}"
|
|
|
|
if final_modality == "text":
|
|
text_out = extract_text_output(outputs, tokenizer=tokenizer)
|
|
if not text_out:
|
|
raise RuntimeError("No text output found.")
|
|
out_path = out_dir / f"{prefix}.txt"
|
|
out_path.write_text(text_out + "\n", encoding="utf-8")
|
|
print(f"[end2end] text saved: {out_path}")
|
|
print(text_out)
|
|
return
|
|
|
|
if final_modality == "image":
|
|
image_out = extract_image_output(outputs)
|
|
if image_out is None:
|
|
raise RuntimeError("No image output found.")
|
|
pil = tensor_to_pil_image(image_out)
|
|
out_path = out_dir / f"{prefix}.png"
|
|
pil.save(out_path)
|
|
print(f"[end2end] image saved: {out_path}")
|
|
return
|
|
|
|
if final_modality == "audio":
|
|
audio_out = extract_audio_output(outputs)
|
|
if audio_out is None:
|
|
raise RuntimeError("No audio output found.")
|
|
wav, sr = audio_out
|
|
out_path = out_dir / f"{prefix}.wav"
|
|
save_audio_wav(out_path, wav, sr)
|
|
print(f"[end2end] audio saved: {out_path} (sr={sr}, samples={wav.shape[0]})")
|
|
return
|
|
|
|
raise RuntimeError(f"Unsupported final modality: {final_modality}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|