main
py 256 lines 8.59 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 from typing import Any
4
5 import torch
6 from torch import fx as fx
7 from torch import nn
8
9 # This import automatically registers `torch.ops.silly.attention`
10 import tests.compile.silly_attention # noqa
11 from vllm.compilation.counter import compilation_counter
12 from vllm.compilation.decorators import support_torch_compile
13 from vllm.compilation.passes.inductor_pass import (
14 InductorPass,
15 get_pass_context,
16 )
17 from vllm.config import (
18 VllmConfig,
19 set_current_vllm_config,
20 )
21 from vllm.config.compilation import CompilationConfig, CompilationMode
22 from vllm.config.scheduler import SchedulerConfig
23 from vllm.config.utils import Range
24 from vllm.forward_context import set_forward_context
25
26 BATCH_SIZE = 64
27 MLP_SIZE = 128
28
29
30 @support_torch_compile
31 class TestModel(nn.Module):
32 def __init__(self, *, vllm_config: VllmConfig, prefix: str = "", **kwargs) -> None:
33 super().__init__()
34
35 def forward(self, x: torch.Tensor) -> torch.Tensor:
36 x = x + x
37 attn_output = torch.empty_like(x)
38 torch.ops.silly.attention(x, x, x, attn_output)
39 x = attn_output
40 x = x * 3
41 return x
42
43
44 @torch.inference_mode
45 def run_model(vllm_config: VllmConfig, model: nn.Module, batch_sizes: list[int]):
46 with set_forward_context({}, vllm_config=vllm_config):
47 model(torch.randn(BATCH_SIZE, MLP_SIZE))
48 for batch_size in batch_sizes:
49 model(torch.randn(batch_size, MLP_SIZE))
50
51
52 class PostGradRangeChecker(InductorPass):
53 def __init__(self, ranges: list[Range]):
54 self.ranges = ranges
55 self.num_calls = 0
56
57 def __call__(self, graph: fx.Graph):
58 compile_range = get_pass_context().compile_range
59 assert compile_range in self.ranges, (
60 f"Compile range {compile_range} not in {self.ranges}"
61 )
62 self.num_calls += 1
63
64 def uuid(self) -> str:
65 state: dict[str, Any] = {}
66 return InductorPass.hash_dict(state)
67
68
69 def test_compile_ranges(disable_vllm_compile_cache):
70 post_grad_range_checker = PostGradRangeChecker(
71 [
72 Range(start=1, end=8),
73 Range(start=16, end=16),
74 Range(start=9, end=32),
75 Range(start=64, end=64),
76 Range(start=128, end=128),
77 Range(start=33, end=8192),
78 ]
79 )
80 torch.set_default_device("cuda")
81 vllm_config = VllmConfig(
82 scheduler_config=SchedulerConfig(
83 max_num_batched_tokens=8192,
84 max_model_len=8192,
85 is_encoder_decoder=False,
86 ),
87 compilation_config=CompilationConfig(
88 mode=CompilationMode.VLLM_COMPILE,
89 compile_ranges_endpoints=[8, 32],
90 compile_sizes=[16, 64, 128],
91 inductor_compile_config={
92 "post_grad_custom_post_pass": post_grad_range_checker,
93 },
94 ),
95 )
96
97 with set_current_vllm_config(vllm_config):
98 model = TestModel(vllm_config=vllm_config, prefix="").eval()
99 # Number of compilations: 3 compile ranges + 3 compile sizes
100 batch_sizes = [1, 4, 16, 24, 48, 64, 8192]
101
102 with compilation_counter.expect(
103 num_graphs_seen=1,
104 num_piecewise_graphs_seen=1,
105 num_backend_compilations=6,
106 ):
107 run_model(vllm_config, model, batch_sizes)
108 assert post_grad_range_checker.num_calls == 6
109
110
111 def test_compile_config_get_compile_ranges():
112 compilation_config = CompilationConfig(
113 compile_ranges_endpoints=[8, 32],
114 )
115 VllmConfig(
116 scheduler_config=SchedulerConfig(
117 max_num_batched_tokens=8192,
118 max_model_len=8192,
119 is_encoder_decoder=False,
120 ),
121 compilation_config=compilation_config,
122 )
123 assert compilation_config.get_compile_ranges() == [
124 Range(start=1, end=8),
125 Range(start=9, end=32),
126 Range(start=33, end=8192),
127 ]
128
129
130 class PostGradStaticShapeChecker(InductorPass):
131 """Asserts that compile_sizes entries produce graphs with fully concrete
132 (non-symbolic) shapes, and compile_ranges entries have symbolic shapes."""
133
134 def __init__(self):
135 self.num_static_calls = 0
136 self.num_dynamic_calls = 0
137
138 def __call__(self, graph: fx.Graph):
139 from torch.fx.experimental.symbolic_shapes import is_symbolic
140
141 compile_range = get_pass_context().compile_range
142 is_single = compile_range.is_single_size()
143
144 for node in graph.nodes:
145 val = node.meta.get("val")
146 if val is None:
147 val = node.meta.get("example_value")
148 if isinstance(val, torch.Tensor):
149 has_symbolic = any(is_symbolic(d) for d in val.shape)
150 if is_single:
151 assert not has_symbolic, (
152 f"compile_sizes entry {compile_range}: "
153 f"node '{node.name}' has symbolic shape "
154 f"{val.shape}"
155 )
156 else:
157 # compile_ranges should have at least some
158 # symbolic shapes (the batch dimension)
159 if has_symbolic:
160 self.num_dynamic_calls += 1
161 return
162
163 if is_single:
164 self.num_static_calls += 1
165
166 def uuid(self) -> str:
167 state: dict[str, Any] = {}
168 return InductorPass.hash_dict(state)
169
170
171 def test_compile_sizes_produce_static_shapes(disable_vllm_compile_cache):
172 """Verify that compile_sizes entries are compiled with fully concrete
173 shapes (no SymInts), while compile_ranges entries retain dynamic shapes."""
174 checker = PostGradStaticShapeChecker()
175 torch.set_default_device("cuda")
176 vllm_config = VllmConfig(
177 scheduler_config=SchedulerConfig(
178 max_num_batched_tokens=8192,
179 max_model_len=8192,
180 is_encoder_decoder=False,
181 ),
182 compilation_config=CompilationConfig(
183 mode=CompilationMode.VLLM_COMPILE,
184 compile_ranges_endpoints=[8],
185 compile_sizes=[16],
186 inductor_compile_config={
187 "post_grad_custom_post_pass": checker,
188 },
189 ),
190 )
191
192 with set_current_vllm_config(vllm_config):
193 model = TestModel(vllm_config=vllm_config, prefix="").eval()
194 # 3 compilations: Range(1,8), Range(9,8192), single-size 16
195 with compilation_counter.expect(
196 num_graphs_seen=1,
197 num_piecewise_graphs_seen=1,
198 num_backend_compilations=3,
199 ):
200 run_model(vllm_config, model, [1, 16, 64])
201
202 # compile_sizes=16 should produce static shapes
203 assert checker.num_static_calls == 1, (
204 f"Expected 1 static compilation, got {checker.num_static_calls}"
205 )
206 # compile_ranges should produce dynamic shapes
207 assert checker.num_dynamic_calls == 2, (
208 f"Expected 2 dynamic compilations, got {checker.num_dynamic_calls}"
209 )
210
211
212 def test_inductor_cache_compile_ranges(disable_vllm_compile_cache):
213 # disable_vllm_compile_cache sets VLLM_DISABLE_COMPILE_CACHE=1 to force
214 # multiple compilations by disabling vLLM's on-disk compile cache.
215 post_grad_range_checker = PostGradRangeChecker(
216 ranges=[
217 Range(start=1, end=8),
218 Range(start=9, end=8192),
219 ]
220 )
221 scheduler_config = SchedulerConfig(
222 max_num_batched_tokens=8192,
223 max_model_len=8192,
224 is_encoder_decoder=False,
225 )
226 torch.set_default_device("cuda")
227
228 def create_vllm_config():
229 return VllmConfig(
230 scheduler_config=scheduler_config,
231 compilation_config=CompilationConfig(
232 mode=CompilationMode.VLLM_COMPILE,
233 compile_ranges_endpoints=[8],
234 inductor_compile_config={
235 "post_grad_custom_post_pass": post_grad_range_checker,
236 },
237 ),
238 )
239
240 vllm_config_1 = create_vllm_config()
241 with set_current_vllm_config(vllm_config_1):
242 model1 = TestModel(vllm_config=vllm_config_1, prefix="").eval()
243 batch_sizes = [1, 16]
244 run_model(vllm_config_1, model1, batch_sizes)
245 assert post_grad_range_checker.num_calls == 2
246
247 post_grad_range_checker.num_calls = 0
248 # Create a new vllm config with the new pass context
249 vllm_config_2 = create_vllm_config()
250 with set_current_vllm_config(vllm_config_2):
251 model2 = TestModel(vllm_config=vllm_config_2, prefix="").eval()
252 batch_sizes = [4, 32]
253 run_model(vllm_config_2, model2, batch_sizes)
254 # Check that cache is used, so the number of calls
255 # should be 0
256 assert post_grad_range_checker.num_calls == 0