main
py 207 lines 6.92 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Test the piecewise compilation with a simple model so that we
4 can exactly calculate the expected output and side effects.
5 """
6
7 import pytest
8 import torch
9 from torch import nn
10
11 from vllm.compilation.counter import compilation_counter
12 from vllm.compilation.decorators import support_torch_compile
13 from vllm.config import (
14 CompilationConfig,
15 CompilationMode,
16 CUDAGraphMode,
17 VllmConfig,
18 set_current_vllm_config,
19 )
20 from vllm.forward_context import BatchDescriptor, set_forward_context
21 from vllm.utils.torch_utils import is_torch_equal_or_newer
22
23 from ...utils import create_new_process_for_each_test
24
25 # This import automatically registers `torch.ops.silly.attention`
26 from ..silly_attention import get_global_counter, reset_global_counter
27
28
29 # Custom op that returns an unbacked symint during graph capture
30 @torch.library.custom_op("mylib::foo", mutates_args=())
31 def foo(x: torch.Tensor) -> int:
32 return 3
33
34
35 @foo.register_fake
36 def _(x):
37 return torch.library.get_ctx().new_dynamic_size()
38
39
40 @support_torch_compile
41 class SillyModel(nn.Module):
42 def __init__(
43 self,
44 *,
45 vllm_config: VllmConfig,
46 prefix: str = "",
47 intermediate_unbacked=False,
48 **kwargs,
49 ) -> None:
50 super().__init__()
51 self.intermediate_unbacked = intermediate_unbacked
52
53 def forward(self, x: torch.Tensor) -> torch.Tensor:
54 """Overall effect:
55 x = 3 * x + 19
56 global_counter += 2
57 """
58 x = x + 1
59 x = x + 2
60 out = torch.empty_like(x)
61 torch.ops.silly.attention(x, x, x, out)
62 x = out
63 x = x - 2
64
65 if self.intermediate_unbacked:
66 # Test for unbacked symints: the following is a fancy way to multiply by 1
67 u0 = foo(x)
68 ones = x.new_ones(x.shape[0], u0).sum(-1) / 3
69 x = x * ones
70
71 x = x - 1
72 out = torch.empty_like(x)
73 torch.ops.silly.attention(x, x, x, out)
74 x = out
75 x = x + 1
76 return x
77
78
79 @torch._dynamo.config.patch(capture_dynamic_output_shape_ops=True)
80 def _run_simple_model(
81 splitting_ops,
82 use_inductor_graph_partition,
83 backend,
84 expected_num_piecewise_graphs_seen,
85 expected_num_piecewise_capturable_graphs_seen,
86 expected_num_backend_compilations,
87 expected_num_cudagraph_captured,
88 *,
89 intermediate_unbacked=False,
90 ):
91 vllm_config = VllmConfig(
92 compilation_config=CompilationConfig(
93 mode=CompilationMode.VLLM_COMPILE,
94 backend=backend,
95 splitting_ops=splitting_ops,
96 use_inductor_graph_partition=use_inductor_graph_partition,
97 cudagraph_copy_inputs=True,
98 cudagraph_capture_sizes=[1, 2],
99 )
100 )
101 with set_current_vllm_config(vllm_config):
102 model = SillyModel(
103 vllm_config=vllm_config,
104 prefix="",
105 intermediate_unbacked=intermediate_unbacked,
106 )
107
108 inputs = torch.randn(100).cuda()
109
110 with (
111 compilation_counter.expect(
112 num_graphs_seen=1, # one graph for the model
113 num_piecewise_graphs_seen=expected_num_piecewise_graphs_seen,
114 num_piecewise_capturable_graphs_seen=expected_num_piecewise_capturable_graphs_seen,
115 num_backend_compilations=expected_num_backend_compilations,
116 num_cudagraph_captured=expected_num_cudagraph_captured,
117 ),
118 set_forward_context(None, vllm_config=vllm_config),
119 ): # background context
120 # warm up with background context
121 model(inputs)
122
123 # capturing/replaying should under context of cudagraph dispatching
124 with set_forward_context(
125 None,
126 vllm_config=vllm_config,
127 cudagraph_runtime_mode=CUDAGraphMode.PIECEWISE,
128 batch_descriptor=BatchDescriptor(
129 num_tokens=2,
130 ),
131 ):
132 model(torch.randn(2).cuda())
133 with set_forward_context(
134 None,
135 vllm_config=vllm_config,
136 cudagraph_runtime_mode=CUDAGraphMode.PIECEWISE,
137 batch_descriptor=BatchDescriptor(
138 num_tokens=1,
139 ),
140 ):
141 model(torch.randn(1).cuda())
142
143 input = torch.zeros(2).cuda()
144 reset_global_counter()
145 with set_forward_context(
146 None,
147 vllm_config=vllm_config,
148 cudagraph_runtime_mode=CUDAGraphMode.PIECEWISE,
149 batch_descriptor=BatchDescriptor(
150 num_tokens=2,
151 ),
152 ):
153 output = model(input)
154 assert get_global_counter() == 2
155 assert torch.allclose(output.cpu(), torch.tensor([19.0, 19.0]))
156
157
158 @pytest.mark.parametrize("backend", ["inductor", "eager"])
159 @pytest.mark.parametrize("intermediate_unbacked", [True, False])
160 @torch.inference_mode()
161 @create_new_process_for_each_test("spawn")
162 def test_simple_piecewise_compile(backend, intermediate_unbacked, monkeypatch):
163 # `intermediate_unbacked` flips a control-flow branch inside
164 # `SillyModel.forward`, but the AOT-compile cache key only hashes the
165 # forward function's qualname + line number, so both parametrize variants
166 # share the same cache slot. Disabling the cache forces each variant to
167 # compile fresh; otherwise the second-running variant loads the first's
168 # artifact and segfaults with an illegal memory access.
169 monkeypatch.setenv("VLLM_DISABLE_COMPILE_CACHE", "1")
170 _run_simple_model(
171 splitting_ops=["silly::attention"],
172 use_inductor_graph_partition=False,
173 backend=backend,
174 # 2 * num_layers + 1
175 expected_num_piecewise_graphs_seen=5,
176 # 1 + num_layers
177 expected_num_piecewise_capturable_graphs_seen=3,
178 # num_piecewise_capturable_graphs_seen
179 expected_num_backend_compilations=3,
180 # num_cudagraph_sizes * num_piecewise_capturable_graphs_seen
181 expected_num_cudagraph_captured=6,
182 intermediate_unbacked=intermediate_unbacked,
183 )
184
185
186 @torch.inference_mode()
187 def test_simple_inductor_graph_partition(monkeypatch):
188 if not is_torch_equal_or_newer("2.9.0.dev"):
189 pytest.skip("inductor graph partition is only available in PyTorch 2.9+")
190
191 # disable compile cache so that we run separately for different splitting_ops
192 # and get the expected number of cudagraphs captured.
193 monkeypatch.setenv("VLLM_DISABLE_COMPILE_CACHE", "1")
194
195 _run_simple_model(
196 splitting_ops=["silly::attention"],
197 use_inductor_graph_partition=True,
198 backend="inductor",
199 # Since not splitting at fx graph level
200 expected_num_piecewise_graphs_seen=1,
201 # Since not splitting at fx graph level
202 expected_num_piecewise_capturable_graphs_seen=1,
203 # Since not splitting at fx graph level
204 expected_num_backend_compilations=1,
205 # Inductor graph partition still captures 6 graph, same as fx graph partition
206 expected_num_cudagraph_captured=6,
207 )