main
py 318 lines 9.23 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """WARNING: This test runs in both single-node (4 GPUs) and multi-node
4 (2 node with 2 GPUs each) modes. If the test only uses 2 GPUs, it is
5 important to set the distributed backend to "mp" to avoid Ray scheduling
6 all workers in a node other than the head node, which can cause the test
7 to fail.
8 """
9
10 import json
11 import os
12 from dataclasses import dataclass
13 from typing import Literal, NamedTuple
14
15 import lm_eval
16 import pytest
17 import torch
18
19 from tests.utils import RemoteOpenAIServer, create_new_process_for_each_test
20 from vllm.config.model import RunnerOption
21 from vllm.logger import init_logger
22 from vllm.platforms import current_platform
23
24 from ..models.registry import HF_EXAMPLE_MODELS
25
26 logger = init_logger("test_context_parallel")
27
28 VLLM_MULTI_NODE = os.getenv("VLLM_MULTI_NODE", "0") == "1"
29
30 CP_TEST_MODELS = [
31 # TODO support other models
32 # [LANGUAGE GENERATION]
33 "deepseek-ai/DeepSeek-V2-Lite-Chat",
34 "Qwen/Qwen2.5-1.5B-Instruct",
35 "Qwen/Qwen3.5-0.8B", # hybrid attention model
36 ]
37
38 # GSM8K eval configuration
39 NUM_SHOTS = 5 # Few-shot examples
40 TASK = "gsm8k"
41 FILTER = "exact_match,strict-match"
42 NUM_CONCURRENT = 128
43 # tp accuracy with 2% buffer
44 MIN_ACCURACY = {
45 # .buildkite/lm-eval-harness/configs/DeepSeek-V2-Lite-Chat.yaml
46 "deepseek-ai/DeepSeek-V2-Lite-Chat": 0.64,
47 # .buildkite/lm-eval-harness/configs/Qwen2.5-1.5B-Instruct.yaml
48 "Qwen/Qwen2.5-1.5B-Instruct": 0.52,
49 "Qwen/Qwen3.5-0.8B": 0.33,
50 }
51
52
53 class ParallelSetup(NamedTuple):
54 tp_size: int
55 pp_size: int
56 dcp_size: int
57 cp_kv_cache_interleave_size: int
58 eager_mode: bool
59 chunked_prefill: bool
60
61
62 class CPTestOptions(NamedTuple):
63 multi_node_only: bool
64 attn_backend: str | None = None
65
66
67 @dataclass
68 class CPTestSettings:
69 parallel_setups: list[ParallelSetup]
70 distributed_backends: list[str]
71 runner: RunnerOption
72 test_options: CPTestOptions
73
74 @staticmethod
75 def detailed(
76 *,
77 tp_base: int = 4,
78 pp_base: int = 1,
79 dcp_multipliers: list[float] | None = None,
80 cp_kv_cache_interleave_size: int = 1,
81 multi_node_only: bool = False,
82 runner: RunnerOption = "auto",
83 attn_backend: str | None = None,
84 ):
85 parallel_setups = []
86 if dcp_multipliers is None:
87 dcp_multipliers = [
88 0.5,
89 ]
90 for eager_mode_val in [False]:
91 for pp_multiplier in [1]:
92 for dcp_multiplier in dcp_multipliers:
93 for chunked_prefill_val in [True]:
94 parallel_setups.append(
95 ParallelSetup(
96 tp_size=tp_base,
97 pp_size=pp_multiplier * pp_base,
98 dcp_size=int(dcp_multiplier * tp_base),
99 cp_kv_cache_interleave_size=cp_kv_cache_interleave_size,
100 eager_mode=eager_mode_val,
101 chunked_prefill=chunked_prefill_val,
102 )
103 )
104 return CPTestSettings(
105 parallel_setups=parallel_setups,
106 distributed_backends=["mp"],
107 runner=runner,
108 test_options=CPTestOptions(
109 multi_node_only=multi_node_only,
110 attn_backend=attn_backend,
111 ),
112 )
113
114 def iter_params(self, model_id: str):
115 opts = self.test_options
116
117 for parallel_setup in self.parallel_setups:
118 for backend in self.distributed_backends:
119 yield (
120 model_id,
121 parallel_setup,
122 backend,
123 self.runner,
124 opts,
125 )
126
127
128 if current_platform.is_rocm():
129 CP_TEXT_GENERATION_MODELS = {
130 "deepseek-ai/DeepSeek-V2-Lite-Chat": [
131 CPTestSettings.detailed(dcp_multipliers=[1]),
132 ],
133 "Qwen/Qwen2.5-1.5B-Instruct": [
134 CPTestSettings.detailed(dcp_multipliers=[1]),
135 ],
136 }
137 else:
138 CP_TEXT_GENERATION_MODELS = {
139 "deepseek-ai/DeepSeek-V2-Lite-Chat": [
140 CPTestSettings.detailed(dcp_multipliers=[1]),
141 CPTestSettings.detailed(
142 dcp_multipliers=[0.5],
143 cp_kv_cache_interleave_size=64,
144 attn_backend="FLASHMLA",
145 ),
146 ],
147 "Qwen/Qwen2.5-1.5B-Instruct": [
148 CPTestSettings.detailed(
149 cp_kv_cache_interleave_size=16, attn_backend="FLASH_ATTN"
150 ),
151 CPTestSettings.detailed(
152 cp_kv_cache_interleave_size=16, attn_backend="FLASHINFER"
153 ),
154 ],
155 "Qwen/Qwen3.5-0.8B": [
156 CPTestSettings.detailed(
157 cp_kv_cache_interleave_size=16,
158 attn_backend="FLASH_ATTN",
159 ),
160 ],
161 }
162
163
164 def _test_cp_gsm8k(
165 model_id: str,
166 parallel_setup: ParallelSetup,
167 distributed_backend: str,
168 runner: RunnerOption,
169 test_options: CPTestOptions,
170 num_gpus_available: int,
171 *,
172 method: Literal["generate"],
173 is_multimodal: bool,
174 ):
175 (
176 tp_size,
177 pp_size,
178 dcp_size,
179 cp_kv_cache_interleave_size,
180 eager_mode,
181 chunked_prefill,
182 ) = parallel_setup
183
184 multi_node_only, attn_backend = test_options
185
186 model_info = HF_EXAMPLE_MODELS.find_hf_info(model_id)
187 model_info.check_transformers_version(on_fail="skip")
188
189 trust_remote_code = model_info.trust_remote_code
190 tokenizer_mode = model_info.tokenizer_mode
191 hf_overrides = model_info.hf_overrides
192
193 model_info.check_available_online(on_fail="skip")
194
195 if num_gpus_available < tp_size * pp_size:
196 pytest.skip(f"Need at least {tp_size} x {pp_size} GPUs")
197 if VLLM_MULTI_NODE and distributed_backend == "mp":
198 pytest.skip(
199 "Skipping multi-node pipeline parallel test for "
200 "multiprocessing distributed backend"
201 )
202 if multi_node_only and not VLLM_MULTI_NODE:
203 pytest.skip("Not in multi-node setting")
204
205 server_args = [
206 # use half precision for speed and memory savings in CI environment
207 "--dtype",
208 "bfloat16",
209 "--max-model-len",
210 "4096",
211 "--max-num-seqs",
212 "64",
213 ]
214 if chunked_prefill:
215 server_args.append("--enable-chunked-prefill")
216 if eager_mode:
217 server_args.append("--enforce-eager")
218 if runner != "auto":
219 server_args.extend(["--runner", runner])
220 if trust_remote_code:
221 server_args.append("--trust-remote-code")
222 if tokenizer_mode:
223 server_args.extend(["--tokenizer-mode", tokenizer_mode])
224 if hf_overrides:
225 server_args.extend(["--hf-overrides", json.dumps(hf_overrides)])
226
227 server_args.extend(
228 [
229 "--tensor-parallel-size",
230 str(tp_size),
231 "--pipeline-parallel-size",
232 str(pp_size),
233 "--decode-context-parallel-size",
234 str(dcp_size),
235 "--dcp-kv-cache-interleave-size",
236 str(cp_kv_cache_interleave_size),
237 "--distributed-executor-backend",
238 distributed_backend,
239 ]
240 )
241
242 if attn_backend:
243 server_args.append(f"--attention-backend={attn_backend}")
244
245 with RemoteOpenAIServer(
246 model_id,
247 server_args,
248 max_wait_seconds=720,
249 ) as remote_server:
250 url = f"{remote_server.url_for('v1')}/completions"
251
252 model_args = (
253 f"model={model_id},"
254 f"base_url={url},"
255 f"num_concurrent={NUM_CONCURRENT},tokenized_requests=False"
256 )
257
258 results = lm_eval.simple_evaluate(
259 model="local-completions",
260 model_args=model_args,
261 tasks=TASK,
262 num_fewshot=NUM_SHOTS,
263 )
264
265 # Validate accuracy is reasonable
266 accuracy = results["results"][TASK][FILTER]
267 min_accuracy = MIN_ACCURACY[model_id]
268 assert accuracy >= min_accuracy, (
269 f"TP+DCP accuracy too low: {accuracy:.3f} < {min_accuracy:.3f}"
270 )
271
272
273 @pytest.mark.parametrize(
274 (
275 "model_id",
276 "parallel_setup",
277 "distributed_backend",
278 "runner",
279 "test_options",
280 ),
281 [
282 params
283 for model_id, settings in CP_TEXT_GENERATION_MODELS.items()
284 for setting in settings
285 for params in setting.iter_params(model_id)
286 if model_id in CP_TEST_MODELS
287 ],
288 )
289 @create_new_process_for_each_test()
290 def test_cp_generation(
291 model_id: str,
292 parallel_setup: ParallelSetup,
293 distributed_backend: str,
294 runner: RunnerOption,
295 test_options: CPTestOptions,
296 num_gpus_available,
297 ):
298 if (
299 model_id == "deepseek-ai/DeepSeek-V2-Lite-Chat"
300 and torch.cuda.get_device_capability() < (9, 0)
301 ):
302 pytest.skip(reason="MLA+DCP requires compute capability of 9.0 or higher")
303 if (
304 model_id == "Qwen/Qwen2.5-1.5B-Instruct"
305 and torch.cuda.get_device_capability() != (9, 0)
306 ):
307 pytest.skip(reason="GQA+DCP currently requires compute capability of 9.0")
308
309 _test_cp_gsm8k(
310 model_id,
311 parallel_setup,
312 distributed_backend,
313 runner,
314 test_options,
315 num_gpus_available,
316 method="generate",
317 is_multimodal=False,
318 )