main
py 394 lines 12.7 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
5 import pytest
6 import torch
7
8 import vllm.envs as envs
9 from tests.compile.backend import TestBackend
10 from tests.utils import (
11 multi_gpu_test,
12 )
13 from vllm.compilation.passes.fusion.collective_fusion import AsyncTPPass
14 from vllm.config import (
15 CompilationConfig,
16 DeviceConfig,
17 ModelConfig,
18 PassConfig,
19 VllmConfig,
20 set_current_vllm_config,
21 )
22 from vllm.config.utils import Range
23 from vllm.distributed import (
24 tensor_model_parallel_all_gather,
25 tensor_model_parallel_reduce_scatter,
26 )
27 from vllm.distributed.parallel_state import (
28 init_distributed_environment,
29 initialize_model_parallel,
30 )
31 from vllm.platforms import current_platform
32 from vllm.utils.network_utils import get_file_store_init_method
33 from vllm.utils.system_utils import update_environment_variables
34 from vllm.utils.torch_utils import set_random_seed
35
36 DEVICE_TYPE = current_platform.device_type
37 FP8_DTYPE = current_platform.fp8_dtype()
38
39 prompts = [
40 "Hello, my name is",
41 "The president of the United States is",
42 "The capital of France is",
43 "The future of AI is",
44 ]
45
46
47 class TestMMRSModel(torch.nn.Module):
48 def __init__(self, hidden_size=16, dtype=torch.bfloat16):
49 super().__init__()
50 self.hidden_size = hidden_size
51 self.dtype = dtype
52 self.gate_proj = torch.nn.Parameter(
53 torch.empty((self.hidden_size * 2, hidden_size)), requires_grad=False
54 )
55 # Initialize weights
56 torch.nn.init.normal_(self.gate_proj, std=0.02)
57
58 def forward(self, hidden_states):
59 """Forward pass implementing the mm + reduce scatter in the FX graph."""
60 # Reshape input
61 view = hidden_states.reshape(-1, self.hidden_size)
62
63 # matrix multiplication
64 permute = self.gate_proj.permute(1, 0)
65 mm = torch.mm(view, permute)
66 reduce_scatter = tensor_model_parallel_reduce_scatter(mm, dim=0)
67 return reduce_scatter
68
69 def ops_in_model_before(self):
70 return [torch.ops.vllm.reduce_scatter.default]
71
72 def ops_in_model_after(self):
73 return [torch.ops.symm_mem.fused_matmul_reduce_scatter.default]
74
75
76 class TestAGMMModel(torch.nn.Module):
77 def __init__(self, hidden_size=16, dtype=torch.bfloat16):
78 super().__init__()
79 self.hidden_size = hidden_size
80 self.dtype = dtype
81 self.weight = torch.nn.Parameter(
82 torch.empty((hidden_size, hidden_size)), requires_grad=False
83 )
84 # Initialize weights
85 torch.nn.init.normal_(self.weight, std=0.02)
86
87 def forward(self, hidden_states):
88 """Forward pass implementing the mm + all gather in the FX graph."""
89 # Reshape input
90 view = hidden_states.reshape(-1, self.hidden_size)
91 all_gather = tensor_model_parallel_all_gather(view, dim=0)
92 permute = self.weight.permute(1, 0)
93 mm = torch.mm(all_gather, permute)
94 return mm
95
96 def ops_in_model_before(self):
97 return [torch.ops.vllm.all_gather.default]
98
99 def ops_in_model_after(self):
100 return [torch.ops.symm_mem.fused_all_gather_matmul.default]
101
102
103 class _BaseScaledMMModel(torch.nn.Module):
104 def __init__(self, hidden_size=16, dtype=torch.bfloat16):
105 super().__init__()
106 self.hidden_size = hidden_size
107 self.dtype = dtype
108 self.weight = (
109 torch.empty([hidden_size, hidden_size], dtype=FP8_DTYPE)
110 .contiguous()
111 .transpose(0, 1)
112 )
113
114 # Initialize scale_b for _scaled_mm.
115 self.scale_b = torch.ones(1, self.hidden_size, dtype=torch.float32)
116
117
118 class TestScaledMMRSModel(_BaseScaledMMModel):
119 def forward(self, input: torch.Tensor):
120 """Forward pass implementing the scaled_mm + reduce scatter in the FX graph."""
121 fp8_input = input.to(FP8_DTYPE)
122 scale_a = torch.ones(input.shape[0], 1, dtype=torch.float32)
123 scaled_mm = torch._scaled_mm(
124 fp8_input,
125 self.weight,
126 scale_a=scale_a,
127 scale_b=self.scale_b,
128 out_dtype=self.dtype,
129 )
130 reduce_scatter = tensor_model_parallel_reduce_scatter(scaled_mm, dim=0)
131 return reduce_scatter
132
133 def ops_in_model_before(self):
134 return [torch.ops.vllm.reduce_scatter.default]
135
136 def ops_in_model_after(self):
137 return [torch.ops.vllm.patched_fused_scaled_matmul_reduce_scatter.default]
138
139
140 class TestAGScaledMMModel(_BaseScaledMMModel):
141 def forward(self, input: torch.Tensor):
142 """Forward pass implementing the all gather + scaled_mm in the FX graph."""
143 # Reshape input
144 fp8_input = input.to(FP8_DTYPE)
145 all_gather = tensor_model_parallel_all_gather(fp8_input, dim=0)
146
147 scale_a = torch.ones(all_gather.shape[0], 1, dtype=torch.float32)
148 scaled_mm = torch._scaled_mm(
149 all_gather,
150 self.weight,
151 scale_a=scale_a,
152 scale_b=self.scale_b,
153 out_dtype=self.dtype,
154 )
155 return scaled_mm
156
157 def ops_in_model_before(self):
158 return [torch.ops.vllm.all_gather.default]
159
160 def ops_in_model_after(self):
161 return [torch.ops.symm_mem.fused_all_gather_scaled_matmul.default]
162
163
164 class TestCutlassScaledMMRSModel(_BaseScaledMMModel):
165 def forward(self, input: torch.Tensor):
166 """Forward pass implementing the cutlass_scaled_mm + reduce scatter
167 in the FX graph
168
169 """
170 fp8_input = input.to(FP8_DTYPE)
171 scale_a = torch.ones(input.shape[0], 1, dtype=torch.float32)
172 mm_out = torch.empty(
173 (fp8_input.shape[0], self.weight.shape[1]),
174 dtype=self.dtype,
175 device=input.device,
176 )
177 torch.ops._C.cutlass_scaled_mm(
178 mm_out, fp8_input, self.weight, scale_a, self.scale_b, None
179 )
180 reduce_scatter = tensor_model_parallel_reduce_scatter(mm_out, dim=0)
181 return reduce_scatter
182
183 def ops_in_model_before(self):
184 return [torch.ops.vllm.reduce_scatter.default]
185
186 def ops_in_model_after(self):
187 return [torch.ops.vllm.patched_fused_scaled_matmul_reduce_scatter.default]
188
189
190 class TestAGCutlassScaledMMModel(_BaseScaledMMModel):
191 def forward(self, input: torch.Tensor):
192 """Forward pass implementing the all gather + cutlass_scaled_mm
193 in the FX graph
194 """
195 # Reshape input
196 fp8_input = input.to(FP8_DTYPE)
197 all_gather = tensor_model_parallel_all_gather(fp8_input, dim=0)
198
199 scale_a = torch.ones(all_gather.shape[0], 1, dtype=torch.float32)
200
201 mm_out = torch.empty(
202 (all_gather.shape[0], self.weight.shape[1]),
203 dtype=self.dtype,
204 device=all_gather.device,
205 )
206 torch.ops._C.cutlass_scaled_mm(
207 mm_out, all_gather, self.weight, scale_a, self.scale_b, None
208 )
209 return mm_out
210
211 def ops_in_model_before(self):
212 return [torch.ops.vllm.all_gather.default]
213
214 def ops_in_model_after(self):
215 return [torch.ops.symm_mem.fused_all_gather_scaled_matmul.default]
216
217
218 @multi_gpu_test(num_gpus=2)
219 @pytest.mark.parametrize(
220 "test_model",
221 [
222 TestMMRSModel,
223 TestAGMMModel,
224 TestScaledMMRSModel,
225 TestAGScaledMMModel,
226 pytest.param(
227 TestCutlassScaledMMRSModel,
228 marks=pytest.mark.skipif(
229 not hasattr(torch.ops._C, "cutlass_scaled_mm"),
230 reason="Requires cutlass_scaled_mm",
231 ),
232 ),
233 pytest.param(
234 TestAGCutlassScaledMMModel,
235 marks=pytest.mark.skipif(
236 not hasattr(torch.ops._C, "cutlass_scaled_mm"),
237 reason="Requires cutlass_scaled_mm",
238 ),
239 ),
240 ],
241 )
242 @pytest.mark.parametrize("batch_size", [8])
243 @pytest.mark.parametrize("seq_len", [16])
244 @pytest.mark.parametrize("hidden_size", [16])
245 @pytest.mark.parametrize("dtype", [torch.bfloat16])
246 @pytest.mark.parametrize("dynamic", [True, False])
247 @pytest.mark.skipif(envs.VLLM_TARGET_DEVICE not in ["cuda"], reason="Only test on CUDA")
248 def test_async_tp_pass_replace(
249 test_model: str,
250 batch_size: int,
251 seq_len: int,
252 hidden_size: int,
253 dtype: torch.dtype,
254 dynamic: bool,
255 ):
256 num_processes = 2
257 distributed_init_method = get_file_store_init_method()
258
259 def run_torch_spawn(fn, nprocs):
260 # need to use torch.mp.spawn otherwise will have problems with
261 # torch.distributed and cuda
262 torch.multiprocessing.spawn(
263 fn,
264 args=(
265 num_processes,
266 test_model,
267 batch_size,
268 seq_len,
269 hidden_size,
270 dtype,
271 dynamic,
272 distributed_init_method,
273 ),
274 nprocs=nprocs,
275 )
276
277 run_torch_spawn(async_tp_pass_on_test_model, num_processes)
278
279
280 def test_async_tp_pass_requires_full_graph_compilation():
281 vllm_config = VllmConfig()
282 vllm_config.compilation_config.use_inductor_graph_partition = False
283 vllm_config.compilation_config.splitting_ops = [
284 "vllm::unified_attention_with_output"
285 ]
286
287 async_tp_pass = object.__new__(AsyncTPPass)
288 async_tp_pass.compilation_config = vllm_config.compilation_config
289
290 with pytest.raises(
291 AssertionError, match="AsyncTPPass requires full-graph compilation"
292 ):
293 async_tp_pass.is_applicable_for_range(Range(start=8, end=8))
294
295
296 def async_tp_pass_on_test_model(
297 local_rank: int,
298 world_size: int,
299 test_model_cls: torch.nn.Module,
300 batch_size: int,
301 seq_len: int,
302 hidden_size: int,
303 dtype: torch.dtype,
304 dynamic: bool,
305 distributed_init_method: str,
306 check_numerics: bool = False,
307 ):
308 set_random_seed(0)
309
310 device = torch.device(f"{DEVICE_TYPE}:{local_rank}")
311 torch.accelerator.set_device_index(device)
312 torch.set_default_device(device)
313 torch.set_default_dtype(dtype)
314
315 update_environment_variables(
316 {
317 "RANK": str(local_rank),
318 "LOCAL_RANK": str(local_rank),
319 "WORLD_SIZE": str(world_size),
320 }
321 )
322
323 # initialize distributed
324 init_distributed_environment(
325 world_size=world_size,
326 rank=local_rank,
327 distributed_init_method=distributed_init_method,
328 local_rank=local_rank,
329 )
330
331 # configure vllm config for SequenceParallelismPass
332 vllm_config = VllmConfig()
333 vllm_config.compilation_config = CompilationConfig(
334 pass_config=PassConfig(
335 fuse_gemm_comms=True,
336 ),
337 )
338 vllm_config.device_config = DeviceConfig(device=torch.device(DEVICE_TYPE))
339
340 # this is a fake model name to construct the model config
341 # in the vllm_config, it's not really used.
342 model_name = "RedHatAI/Llama-3.2-1B-Instruct-FP8"
343 vllm_config.model_config = ModelConfig(
344 model=model_name, trust_remote_code=True, dtype=dtype, seed=42
345 )
346
347 with set_current_vllm_config(vllm_config):
348 initialize_model_parallel(tensor_model_parallel_size=world_size)
349
350 async_tp_pass = AsyncTPPass(vllm_config)
351 backend = TestBackend(async_tp_pass)
352
353 assert (
354 async_tp_pass.compilation_config.splitting_ops
355 == vllm_config.compilation_config.splitting_ops
356 )
357 assert (
358 async_tp_pass.compilation_config.use_inductor_graph_partition
359 == vllm_config.compilation_config.use_inductor_graph_partition
360 )
361
362 model = test_model_cls(hidden_size, dtype) # Pass dtype to model constructor
363
364 hidden_states = torch.randn(
365 (batch_size * seq_len, hidden_size), dtype=dtype, requires_grad=False
366 )
367 if check_numerics:
368 hidden_states += local_rank * 0.125
369
370 if dynamic:
371 torch._dynamo.mark_dynamic(hidden_states, 0)
372
373 compiled_model = torch.compile(model, backend=backend)
374 actual = compiled_model(hidden_states)
375
376 if check_numerics:
377 # These are new checks, so no existing tolerance is relaxed.
378 # Allow BF16 rounding vs. eager, but keep the lifetime check exact.
379 expected = model(hidden_states)
380 torch.testing.assert_close(actual, expected, rtol=1e-2, atol=1e-2)
381 snapshot = actual.clone()
382 changed = compiled_model(-hidden_states)
383 torch.testing.assert_close(changed, -expected, rtol=1e-2, atol=1e-2)
384 torch.testing.assert_close(actual, snapshot, rtol=0, atol=0)
385
386 assert async_tp_pass.matched_count == 1
387
388 # In pre-nodes, all gather or reduce scatter should exist,
389 # fused_matmul_reduce_scatter or fused_all_gather_matmul should not
390 backend.check_before_ops(model.ops_in_model_before(), fully_replaced=False)
391
392 # In post-nodes, fused_matmul_reduce_scatter or \
393 # fused_all_gather_matmul should exist
394 backend.check_after_ops(model.ops_in_model_after())