vllm-project--vllm-omni
223 行
6.5 KiB
Python
223 行
6.5 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
"""
|
|
This example shows how to use vLLM-Omni for running offline inference
|
|
with the correct prompt format on Covo-Audio-Chat.
|
|
|
|
Usage:
|
|
python end2end.py --audio-path /path/to/audio.wav
|
|
"""
|
|
|
|
import os
|
|
|
|
import soundfile as sf
|
|
from vllm.assets.audio import AudioAsset
|
|
from vllm.multimodal.media.audio import load_audio
|
|
from vllm.sampling_params import SamplingParams
|
|
from vllm.utils.argparse_utils import FlexibleArgumentParser
|
|
|
|
from vllm_omni.entrypoints.omni import Omni
|
|
from vllm_omni.model_executor.models.covo_audio.prompt_utils import (
|
|
COVO_AUDIO_INPUT_PREFIX,
|
|
build_covo_audio_chat_prompt,
|
|
)
|
|
|
|
SEED = 42
|
|
|
|
|
|
def get_audio_query(
|
|
question: str | None = None,
|
|
audio_path: str | None = None,
|
|
sampling_rate: int = 16000,
|
|
) -> dict:
|
|
if question is None:
|
|
question = "请回答这段音频里的问题。"
|
|
user_content = COVO_AUDIO_INPUT_PREFIX + question
|
|
prompt = build_covo_audio_chat_prompt(user_content)
|
|
|
|
if audio_path is None:
|
|
audio_data = AudioAsset("mary_had_lamb").audio_and_sample_rate
|
|
else:
|
|
import numpy as np
|
|
|
|
audio_signal, sr = load_audio(audio_path, sr=sampling_rate)
|
|
audio_data = (audio_signal.astype(np.float32), sr)
|
|
|
|
return {
|
|
"prompt": prompt,
|
|
"multi_modal_data": {"audio": audio_data},
|
|
"modalities": ["audio"],
|
|
}
|
|
|
|
|
|
def main(args):
|
|
query_result = get_audio_query(
|
|
question=args.text,
|
|
audio_path=args.audio_path,
|
|
sampling_rate=args.sampling_rate,
|
|
)
|
|
|
|
omni = Omni(
|
|
model=args.model_name,
|
|
stage_configs_path=args.stage_configs_path,
|
|
log_stats=args.log_stats,
|
|
stage_init_timeout=args.stage_init_timeout,
|
|
batch_timeout=args.batch_timeout,
|
|
init_timeout=args.init_timeout,
|
|
shm_threshold_bytes=args.shm_threshold_bytes,
|
|
)
|
|
|
|
# Stage 0: fused_thinker_talker
|
|
# stop_token_ids=[151645] (<|im_end|>) and ignore_eos=True are required
|
|
# so the model generates interleaved text+audio tokens before stopping.
|
|
thinker_sampling_params = SamplingParams(
|
|
temperature=0.0,
|
|
top_p=1.0,
|
|
top_k=-1,
|
|
max_tokens=2048,
|
|
seed=SEED,
|
|
detokenize=True,
|
|
repetition_penalty=1.05,
|
|
stop_token_ids=[151645],
|
|
ignore_eos=True,
|
|
)
|
|
# Stage 1: code2wav (audio codes, not real token IDs — skip detokenize)
|
|
code2wav_sampling_params = SamplingParams(
|
|
temperature=0.0,
|
|
top_p=1.0,
|
|
top_k=-1,
|
|
max_tokens=2048,
|
|
seed=SEED,
|
|
detokenize=False,
|
|
repetition_penalty=1.1,
|
|
)
|
|
|
|
sampling_params_list = [
|
|
thinker_sampling_params,
|
|
code2wav_sampling_params,
|
|
]
|
|
|
|
prompts = [query_result for _ in range(args.num_prompts)]
|
|
|
|
omni_outputs = omni.generate(prompts, sampling_params_list)
|
|
|
|
output_dir = args.output_dir
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
|
|
for stage_outputs in omni_outputs:
|
|
output = stage_outputs.request_output
|
|
if stage_outputs.final_output_type == "text":
|
|
request_id = output.request_id
|
|
text_output = output.outputs[0].text
|
|
prompt_text = output.prompt
|
|
out_txt = os.path.join(output_dir, f"{request_id}.txt")
|
|
lines = [
|
|
"Prompt:\n",
|
|
str(prompt_text) + "\n",
|
|
"vllm_text_output:\n",
|
|
str(text_output).strip() + "\n",
|
|
]
|
|
try:
|
|
with open(out_txt, "w", encoding="utf-8") as f:
|
|
f.writelines(lines)
|
|
except Exception as e:
|
|
print(f"[Warn] Failed writing text file {out_txt}: {e}")
|
|
print(f"Request ID: {request_id}, Text saved to {out_txt}")
|
|
elif stage_outputs.final_output_type == "audio":
|
|
request_id = output.request_id
|
|
audio_tensor = output.outputs[0].multimodal_output.get("audio")
|
|
if audio_tensor is None:
|
|
continue
|
|
output_wav = os.path.join(output_dir, f"{request_id}.wav")
|
|
audio_numpy = audio_tensor.float().detach().cpu().numpy()
|
|
if audio_numpy.ndim > 1:
|
|
audio_numpy = audio_numpy.flatten()
|
|
sf.write(output_wav, audio_numpy, samplerate=24000, format="WAV")
|
|
print(f"Request ID: {request_id}, Audio saved to {output_wav}")
|
|
|
|
omni.close()
|
|
|
|
|
|
def parse_args():
|
|
parser = FlexibleArgumentParser(description="Offline inference demo for Covo-Audio-Chat")
|
|
parser.add_argument(
|
|
"--model-name",
|
|
"-m",
|
|
type=str,
|
|
default="tencent/Covo-Audio-Chat",
|
|
help="Model path or HuggingFace model ID.",
|
|
)
|
|
parser.add_argument(
|
|
"--text",
|
|
"-t",
|
|
type=str,
|
|
default=None,
|
|
help="Text prompt / question for the audio.",
|
|
)
|
|
parser.add_argument(
|
|
"--audio-path",
|
|
"-a",
|
|
type=str,
|
|
default=None,
|
|
help="Path to local audio file. Uses default asset if not provided.",
|
|
)
|
|
parser.add_argument(
|
|
"--sampling-rate",
|
|
type=int,
|
|
default=16000,
|
|
help="Sampling rate for audio loading (default: 16000).",
|
|
)
|
|
parser.add_argument(
|
|
"--stage-configs-path",
|
|
type=str,
|
|
default=None,
|
|
help="Path to stage configs YAML file.",
|
|
)
|
|
parser.add_argument(
|
|
"--log-stats",
|
|
action="store_true",
|
|
default=False,
|
|
help="Enable writing detailed statistics.",
|
|
)
|
|
parser.add_argument(
|
|
"--stage-init-timeout",
|
|
type=int,
|
|
default=300,
|
|
help="Timeout for initializing a single stage in seconds.",
|
|
)
|
|
parser.add_argument(
|
|
"--batch-timeout",
|
|
type=int,
|
|
default=5,
|
|
help="Timeout for batching in seconds.",
|
|
)
|
|
parser.add_argument(
|
|
"--init-timeout",
|
|
type=int,
|
|
default=300,
|
|
help="Timeout for initializing stages in seconds.",
|
|
)
|
|
parser.add_argument(
|
|
"--shm-threshold-bytes",
|
|
type=int,
|
|
default=65536,
|
|
help="Threshold for using shared memory in bytes.",
|
|
)
|
|
parser.add_argument(
|
|
"--output-dir",
|
|
default="./output_audio",
|
|
help="Output directory for generated files.",
|
|
)
|
|
parser.add_argument(
|
|
"--num-prompts",
|
|
type=int,
|
|
default=1,
|
|
help="Number of prompts to generate.",
|
|
)
|
|
return parser.parse_args()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
args = parse_args()
|
|
main(args)
|