main
py 126 lines 3.94 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import pytest
5 import torch
6
7 import vllm.config
8 from tests.compile.backend import TestBackend
9 from vllm.compilation.passes.vllm_inductor_pass import (
10 VllmFusionPatternMatcherPass,
11 VllmPatternMatcherPass,
12 VllmPatternReplacement,
13 )
14 from vllm.config import CompilationConfig, CompilationMode, VllmConfig
15 from vllm.platforms import current_platform
16
17
18 class ReluToAbsPattern(VllmPatternReplacement):
19 """Replaces relu(x) with abs(x) — a minimal test fixture."""
20
21 @property
22 def pattern(self):
23 def _pattern(x: torch.Tensor) -> torch.Tensor:
24 return torch.ops.aten.relu.default(x)
25
26 return _pattern
27
28 @property
29 def replacement(self):
30 def _replacement(x: torch.Tensor) -> torch.Tensor:
31 return torch.ops.aten.abs.default(x)
32
33 return _replacement
34
35 def get_inputs(self) -> list[torch.Tensor]:
36 return [self.empty_fp32(4)]
37
38
39 class ExpToSqrtPattern(VllmPatternReplacement):
40 """A second distinct pattern type — used to test uuid differentiation."""
41
42 @property
43 def pattern(self):
44 def _pattern(x: torch.Tensor) -> torch.Tensor:
45 return torch.ops.aten.exp.default(x)
46
47 return _pattern
48
49 @property
50 def replacement(self):
51 def _replacement(x: torch.Tensor) -> torch.Tensor:
52 return torch.ops.aten.sqrt.default(x)
53
54 return _replacement
55
56 def get_inputs(self) -> list[torch.Tensor]:
57 return [self.empty_fp32(4)]
58
59
60 class ReluFusionPass(VllmFusionPatternMatcherPass):
61 def __init__(self, config: VllmConfig) -> None:
62 super().__init__(config, "test_relu_fusion")
63 self.register(ReluToAbsPattern())
64
65
66 class TwoPatternFusionPass(VllmFusionPatternMatcherPass):
67 def __init__(self, config: VllmConfig) -> None:
68 super().__init__(config, "test_two_pattern_fusion")
69 self.register(ReluToAbsPattern())
70 self.register(ExpToSqrtPattern())
71
72
73 @pytest.fixture
74 def vllm_config():
75 return VllmConfig(
76 compilation_config=CompilationConfig(mode=CompilationMode.VLLM_COMPILE),
77 )
78
79
80 @pytest.mark.skipif(not current_platform.is_cuda_alike(), reason="Requires CUDA")
81 def test_register_tracks_patterns(vllm_config):
82 """register() appends each VllmPatternReplacement to _pattern_replacements."""
83 with vllm.config.set_current_vllm_config(vllm_config):
84 single = ReluFusionPass(vllm_config)
85 two = TwoPatternFusionPass(vllm_config)
86
87 assert len(single._pattern_replacements) == 1
88 assert len(two._pattern_replacements) == 2
89
90
91 @pytest.mark.skipif(not current_platform.is_cuda_alike(), reason="Requires CUDA")
92 def test_uuid_stable(vllm_config):
93 """Two instances of the same pass class produce identical uuids."""
94 with vllm.config.set_current_vllm_config(vllm_config):
95 p1 = ReluFusionPass(vllm_config)
96 p2 = ReluFusionPass(vllm_config)
97 p3 = TwoPatternFusionPass(vllm_config)
98
99 assert p1.uuid() == p2.uuid()
100 assert p1.uuid() != p3.uuid()
101 assert p2.uuid() != p3.uuid()
102
103
104 @pytest.mark.skipif(not current_platform.is_cuda_alike(), reason="Requires CUDA")
105 @pytest.mark.parametrize("N", [1, 2, 4])
106 def test_matched_count_and_match_table(vllm_config, N):
107 """matched_count and match_table reflect the number of matched patterns."""
108
109 class Model(torch.nn.Module):
110 def forward(self, *inputs):
111 # N independent relus
112 return sum(torch.relu(x) for x in inputs)
113
114 with vllm.config.set_current_vllm_config(vllm_config):
115 torch.set_default_device("cuda")
116 torch.set_default_dtype(torch.float32)
117
118 fusion_pass = ReluFusionPass(vllm_config)
119 backend = TestBackend(fusion_pass)
120 model = torch.compile(Model(), backend=backend)
121
122 inputs = [torch.rand(8) for _ in range(N)]
123 model(*inputs)
124
125 assert fusion_pass.matched_count == N
126 assert VllmPatternMatcherPass.match_table["test_relu_fusion"] >= N