main
py 182 lines 6.37 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 from collections.abc import Sequence
5
6 import pytest
7 from transformers import AutoModelForSpeechSeq2Seq
8
9 from vllm.logprobs import SampleLogprobs
10 from vllm.lora.request import LoRARequest
11 from vllm.platforms import current_platform
12
13 from ....conftest import AudioTestAssets, HfRunner, PromptAudioInput, VllmRunner
14 from ...registry import HF_EXAMPLE_MODELS
15 from ...utils import check_logprobs_close
16
17 HF_AUDIO_PROMPT = "<|start_of_role|>system<|end_of_role|>Knowledge Cutoff Date: April 2024.\nToday's Date: December 19, 2024.\nYou are Granite, developed by IBM. You are a helpful AI assistant<|end_of_text|>\n<|start_of_role|>user<|end_of_role|><|audio|>can you transcribe the speech into a written format?<|end_of_text|>\n<|start_of_role|>assistant<|end_of_role|>" # noqa: E501
18
19
20 def vllm_to_hf_output(
21 vllm_output: tuple[list[int], str, SampleLogprobs | None],
22 ) -> tuple[list[int], str, SampleLogprobs | None]:
23 """Sanitize hf output to be comparable with vllm output."""
24 output_ids, output_str, out_logprobs = vllm_output
25
26 hf_output_str = output_str + "<|end_of_text|>"
27
28 return output_ids, hf_output_str, out_logprobs
29
30
31 MODEL_NAME = "ibm-granite/granite-speech-3.3-2b"
32 MODEL_NAME_4_0 = "ibm-granite/granite-4.0-1b-speech"
33 # "plus" variant of granite speech (uses GraniteSpeechPlusForConditionalGeneration).
34 MODEL_NAME_4_1_PLUS = "ibm-granite/granite-speech-4.1-2b-plus"
35 # Audio lora co-exists directly in the 3.3 model directory,
36 # the 4.0 and 4.1-plus models have adapters merged into the weights.
37 models: dict[str, str | None] = {
38 MODEL_NAME: MODEL_NAME,
39 MODEL_NAME_4_0: None,
40 MODEL_NAME_4_1_PLUS: None,
41 }
42
43
44 @pytest.fixture
45 def granite_speech_attention_config():
46 """Return attention config for Granite Speech tests on ROCm."""
47 if current_platform.is_rocm():
48 from vllm.platforms.rocm import get_cdna_version
49
50 # -1 (unknown arch) is truthy; gate on CDNA3+ like other call sites.
51 if get_cdna_version() > 2:
52 return {"backend": "ROCM_AITER_FA"}
53 return {"backend": "TRITON_ATTN"}
54 return None
55
56
57 def run_test(
58 hf_runner: type[HfRunner],
59 vllm_runner: type[VllmRunner],
60 inputs: Sequence[tuple[list[str], PromptAudioInput]],
61 model: str,
62 *,
63 max_model_len: int,
64 dtype: str,
65 max_tokens: int,
66 num_logprobs: int,
67 tensor_parallel_size: int,
68 distributed_executor_backend: str | None = None,
69 attention_config: dict | None = None,
70 audio_lora_path: str | None = None,
71 ):
72 """Inference result should be the same between hf and vllm.
73
74 All the audio fixtures for the test are from AUDIO_ASSETS.
75 For huggingface runner, we provide the audio as input.
76 For vllm runner, we provide MultiModalDataDict objects
77 and corresponding MultiModalConfig as input.
78 Note, the text input is also adjusted to abide by vllm contract.
79 The text output is sanitized to be able to compare with hf.
80 """
81 # NOTE: take care of the order. run vLLM first, and then run HF.
82 # vLLM needs a fresh new process without cuda initialization.
83 # if we run HF first, the cuda initialization will be done and it
84 # will hurt multiprocessing backend with fork method (the default method).
85 # max_model_len should be greater than image_feature_size
86 with vllm_runner(
87 model,
88 runner="generate",
89 max_model_len=max_model_len,
90 max_num_seqs=1,
91 dtype=dtype,
92 limit_mm_per_prompt={"audio": 1},
93 tensor_parallel_size=tensor_parallel_size,
94 distributed_executor_backend=distributed_executor_backend,
95 enable_lora=audio_lora_path is not None,
96 max_lora_rank=64,
97 enforce_eager=True,
98 attention_config=attention_config,
99 ) as vllm_model:
100 lora_request = (
101 LoRARequest("audio", 1, audio_lora_path) if audio_lora_path else None
102 )
103 vllm_outputs_per_case = [
104 vllm_model.generate_greedy_logprobs(
105 prompts,
106 max_tokens,
107 num_logprobs=num_logprobs,
108 audios=audios,
109 lora_request=lora_request,
110 )
111 for prompts, audios in inputs
112 ]
113
114 with hf_runner(model, dtype=dtype, auto_cls=AutoModelForSpeechSeq2Seq) as hf_model:
115 hf_processor = hf_model.processor
116 eos_token_id = hf_processor.tokenizer.eos_token_id
117
118 hf_outputs_per_case = [
119 hf_model.generate_greedy_logprobs_limit(
120 prompts,
121 max_tokens,
122 num_logprobs=num_logprobs,
123 audios=[audios],
124 eos_token_id=eos_token_id,
125 )
126 for prompts, audios in inputs
127 ]
128
129 for hf_outputs, vllm_outputs in zip(hf_outputs_per_case, vllm_outputs_per_case):
130 check_logprobs_close(
131 outputs_0_lst=hf_outputs,
132 outputs_1_lst=[vllm_to_hf_output(output) for output in vllm_outputs],
133 name_0="hf",
134 name_1="vllm",
135 )
136
137
138 @pytest.mark.parametrize("model,audio_lora_path", models.items())
139 @pytest.mark.parametrize(
140 "dtype", ["float16"] if current_platform.is_rocm() else ["bfloat16"]
141 )
142 @pytest.mark.parametrize(
143 "max_model_len", [512] if current_platform.is_rocm() else [2048]
144 )
145 @pytest.mark.parametrize("max_tokens", [128])
146 @pytest.mark.parametrize("num_logprobs", [10])
147 def test_models(
148 hf_runner,
149 vllm_runner,
150 model: str,
151 audio_lora_path: str | None,
152 audio_assets: AudioTestAssets,
153 granite_speech_attention_config,
154 dtype: str,
155 max_model_len: int,
156 max_tokens: int,
157 num_logprobs: int,
158 ) -> None:
159 model_info = HF_EXAMPLE_MODELS.find_hf_info(model)
160 model_info.check_available_online(on_fail="skip")
161 model_info.check_transformers_version(on_fail="skip")
162
163 audio, sr = audio_assets[0].audio_and_sample_rate
164 # This model expects 16k sample rate, which our test audio
165 # already is; if this changes, it may break this test,
166 # so we check it directly
167 assert sr == 16000
168 run_test(
169 hf_runner,
170 vllm_runner,
171 [
172 ([HF_AUDIO_PROMPT], [audio]),
173 ],
174 model,
175 dtype=dtype,
176 max_model_len=max_model_len,
177 max_tokens=max_tokens,
178 num_logprobs=num_logprobs,
179 tensor_parallel_size=1,
180 attention_config=granite_speech_attention_config,
181 audio_lora_path=audio_lora_path,
182 )