A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 14, 05:30 UTC
[torch.compile] Fix tests for torch==2.9 inductor partition (#26116)
Signed-off-by: ProExpertProg <lgovedic@redhat.com> Signed-off-by: Luka Govedič <lgovedic@redhat.com>
Luka Govedič committed
Oct 14, 2025 at 19:55 UTC
2dcd12d3571b070432ad1cd321a67b840b4a34b2
8 files changed
+138
-72
tests/compile/piecewise/test_full_cudagraph.py
+21
-8
@@ -11,6 +11,7 @@ from tests.v1.attention.utils import full_cg_backend_configs as backend_configs
11
from vllm import LLM, SamplingParams
12
from vllm.config import CompilationConfig
13
from vllm.platforms import current_platform
14
+from vllm.utils import is_torch_equal_or_newer
15
16
17
@contextlib.contextmanager
@@ -32,13 +33,13 @@ def temporary_environ(env_vars):
33
os.environ[k] = v
34
35
35
-test_params_full_cudagraph = []
36
+model_backends_full_cudagraph = []
37
38
# deepseek-ai/DeepSeek-V2-Lite with MLA
39
MLA_backends = ["FlashMLA", "FlashAttentionMLA", "CutlassMLA"]
40
for mla_backend in MLA_backends:
40
- test_params_full_cudagraph.append(
41
- pytest.param(("deepseek-ai/DeepSeek-V2-Lite", backend_configs[mla_backend]))
41
+ model_backends_full_cudagraph.append(
42
+ ("deepseek-ai/DeepSeek-V2-Lite", backend_configs[mla_backend])
43
)
44
45
# Qwen/Qwen2-1.5B-Instruct with other backends
@@ -46,14 +47,18 @@ other_backend_configs = [
47
backend_configs[c] for c in backend_configs if c not in MLA_backends
48
]
49
for backend_config in other_backend_configs:
49
- test_params_full_cudagraph.append(
50
- pytest.param(("Qwen/Qwen2-1.5B-Instruct", backend_config))
51
- )
50
+ model_backends_full_cudagraph.append(("Qwen/Qwen2-1.5B-Instruct", backend_config))
51
52
53
@pytest.fixture(scope="class")
54
def llm_pair(request):
56
- model, backend_config = request.param
55
+ model, backend_config, use_inductor_graph_partition = request.param
56
+ backend_config.comp_config["use_inductor_graph_partition"] = (
57
+ use_inductor_graph_partition
58
+ )
59
+
60
+ if use_inductor_graph_partition and not is_torch_equal_or_newer("2.9.0.dev"):
61
+ pytest.skip("Inductor graph partition only supported in torch>=2.9")
62
63
# Dynamically skip test if GPU capability is not met
64
if (
@@ -104,7 +109,15 @@ def llm_pair(request):
109
)
110
111
107
-@pytest.mark.parametrize("llm_pair", test_params_full_cudagraph, indirect=True)
112
+@pytest.mark.parametrize(
113
+ "llm_pair",
114
+ [
115
+ pytest.param((model, backend_config, use_inductor_graph_partition))
116
+ for model, backend_config in model_backends_full_cudagraph
117
+ for use_inductor_graph_partition in [True, False]
118
+ ],
119
+ indirect=True,
120
+)
121
class TestFullCUDAGraph:
122
"""
123
Use a class such that an llm pair is constructed once for all
tests/compile/piecewise/test_multiple_graphs.py
+27
-11
@@ -5,6 +5,7 @@ Test (piecewise) compilation with a simple model where multiple submodules
5
are compiled and graph captured separately.
6
"""
7
8
+import pytest
9
import torch
10
from torch import nn
11
@@ -190,7 +191,12 @@ def run_model(
191
return output.cpu()
192
193
193
-def test_multi_graph_piecewise_compile_outputs_equal():
194
+@pytest.mark.parametrize("use_inductor_graph_partition", [False, True])
195
+def test_multi_graph_piecewise_compile(use_inductor_graph_partition: bool):
196
+ if use_inductor_graph_partition:
197
+ # FIXME(luka/boyuan): this currently fails
198
+ pytest.skip("Inductor graph partition not supported with multi-graph")
199
+
200
outputs = []
201
202
# piecewise compile
@@ -200,6 +206,7 @@ def test_multi_graph_piecewise_compile_outputs_equal():
206
use_cudagraph=True,
207
splitting_ops=["silly::attention"],
208
cudagraph_capture_sizes=[1, 2],
209
+ use_inductor_graph_partition=use_inductor_graph_partition,
210
)
211
)
212
cudagraph_runtime_mode = CUDAGraphMode.PIECEWISE
@@ -220,16 +227,24 @@ def test_multi_graph_piecewise_compile_outputs_equal():
227
# static tensor addresses
228
inputs = torch.randn(BATCH_SIZE, MLP_SIZE).cuda()
229
223
- with compilation_counter.expect(
224
- num_graphs_seen=2, # two graphs for the model
225
- num_piecewise_graphs_seen=6,
230
+ if use_inductor_graph_partition:
231
+ # Splitting happens at Inductor lowering level,
232
+ # total piecewise fx graphs is equal to total graphs
233
+ num_piecewise_fx = 2
234
+ num_piecewise_capturable_fx = 2
235
+ else:
236
# attn_one, attn_two each has 3 piecewise graphs
237
# (pre attn, post attn, silly_attention) each
228
- num_piecewise_capturable_graphs_seen=4,
238
+ num_piecewise_fx = 6
239
# attn_one, attn_two has pre attn and post attn each, total=4
230
- num_backend_compilations=4, # num_piecewise_capturable_graphs_seen
231
- num_cudagraph_captured=8,
232
- # num_cudagraph_sizes * num_piecewise_capturable_graphs_seen
240
+ num_piecewise_capturable_fx = 4
241
+
242
+ with compilation_counter.expect(
243
+ num_graphs_seen=2, # two graphs for the model
244
+ num_piecewise_graphs_seen=num_piecewise_fx,
245
+ num_piecewise_capturable_graphs_seen=num_piecewise_capturable_fx,
246
+ num_backend_compilations=num_piecewise_capturable_fx,
247
+ num_cudagraph_captured=8, # num_cudagraph_sizes * num_partitions
248
):
249
outputs.append(run_model(vllm_config, model, inputs, cudagraph_runtime_mode))
250
@@ -268,6 +283,7 @@ def test_multi_graph_piecewise_compile_outputs_equal():
283
level=CompilationLevel.PIECEWISE,
284
use_cudagraph=False,
285
splitting_ops=["silly::attention"],
286
+ use_inductor_graph_partition=use_inductor_graph_partition,
287
)
288
)
289
cudagraph_runtime_mode = CUDAGraphMode.PIECEWISE
@@ -286,9 +302,9 @@ def test_multi_graph_piecewise_compile_outputs_equal():
302
303
with compilation_counter.expect(
304
num_graphs_seen=2,
289
- num_piecewise_graphs_seen=6,
290
- num_piecewise_capturable_graphs_seen=4,
291
- num_backend_compilations=4,
305
+ num_piecewise_graphs_seen=num_piecewise_fx,
306
+ num_piecewise_capturable_graphs_seen=num_piecewise_capturable_fx,
307
+ num_backend_compilations=num_piecewise_capturable_fx,
308
num_cudagraph_captured=0, # no cudagraph captured
309
):
310
outputs.append(run_model(vllm_config, model, inputs, cudagraph_runtime_mode))
tests/compile/piecewise/test_toy_llama.py
+74
-43
@@ -9,6 +9,7 @@ if the config `tractable_init` is set to True. Otherwise, the weights are
9
initialized randomly with a fixed seed.
10
"""
11
12
+from copy import deepcopy
13
from dataclasses import dataclass
14
from typing import Any
15
@@ -26,6 +27,7 @@ from vllm.config import (
27
set_current_vllm_config,
28
)
29
from vllm.forward_context import BatchDescriptor, set_forward_context
30
+from vllm.utils import is_torch_equal_or_newer
31
32
# This import automatically registers `torch.ops.silly.attention`
33
from .. import silly_attention # noqa: F401
@@ -257,27 +259,13 @@ def tractable_computation(
259
260
261
@torch.inference_mode
260
-def run_model(
261
- llama_config, use_compile: bool, backend: str, split_attn: bool = False
262
-) -> torch.Tensor:
263
- if use_compile:
264
- compilation_config = CompilationConfig(
265
- level=CompilationLevel.PIECEWISE,
266
- use_cudagraph=True,
267
- backend=backend,
268
- cudagraph_capture_sizes=[1, 2],
269
- )
270
- if split_attn:
271
- compilation_config.splitting_ops = ["silly::attention"]
272
- cudagraph_runtime_mode = CUDAGraphMode.PIECEWISE
273
- else:
274
- compilation_config = CompilationConfig(
275
- level=CompilationLevel.NO_COMPILATION,
276
- )
277
- cudagraph_runtime_mode = CUDAGraphMode.NONE
262
+def run_model(llama_config, compile_config: CompilationConfig) -> torch.Tensor:
263
+ # Start with a fresh copy to make sure there's no cache dir sharing
264
+ compile_config = deepcopy(compile_config)
265
+ cudagraph_runtime_mode = compile_config.cudagraph_mode
266
267
vllm_config = VllmConfig(
280
- compilation_config=compilation_config, additional_config=llama_config
268
+ compilation_config=compile_config, additional_config=llama_config
269
)
270
with set_current_vllm_config(vllm_config):
271
model = (
@@ -338,8 +326,25 @@ def run_model(
326
return output.cpu()
327
328
341
-@pytest.mark.parametrize("backend", ["inductor", "eager"])
342
-def test_toy_llama(backend: str):
329
+@pytest.mark.parametrize(
330
+ "backend, use_inductor_graph_partition",
331
+ [
332
+ ("eager", False), # No inductor
333
+ ("inductor", False), # Inductor, Dynamo partition
334
+ ("inductor", True), # Inductor, Inductor partition
335
+ ],
336
+)
337
+def test_toy_llama(
338
+ backend: str, use_inductor_graph_partition: bool, monkeypatch, tmp_path
339
+):
340
+ # We disable the vLLM compile cache into a new tmp dir for 2 reasons:
341
+ # 1. To make sure we can properly track the number of Inductor compilations.
342
+ # 2. Inductor partitioning does not play nicely with Autograd cache (below)
343
+ monkeypatch.setenv("VLLM_DISABLE_COMPILE_CACHE", "1")
344
+
345
+ if use_inductor_graph_partition and not is_torch_equal_or_newer("2.9.0.dev"):
346
+ pytest.skip("Inductor graph partition only supported in torch>=2.9")
347
+
348
# compare output with and without piecewise compilation
349
350
llama_config = LlamaConfig(
@@ -350,6 +355,32 @@ def test_toy_llama(backend: str):
355
hidden_size=128, mlp_size=256, vocab_size=128, num_layers=2, tractable_init=True
356
)
357
358
+ compile_config_no_compile = CompilationConfig(
359
+ level=CompilationLevel.NO_COMPILATION,
360
+ cudagraph_mode=CUDAGraphMode.NONE,
361
+ backend="eager",
362
+ )
363
+
364
+ compile_config_no_split = CompilationConfig(
365
+ level=CompilationLevel.PIECEWISE,
366
+ use_inductor_graph_partition=use_inductor_graph_partition,
367
+ cudagraph_mode=CUDAGraphMode.PIECEWISE,
368
+ backend=backend,
369
+ cudagraph_capture_sizes=[1, 2],
370
+ )
371
+
372
+ # FIXME(luka/boyuan): the graph from the previous test case
373
+ # (no inductor partition) gets cached by AotAutograd so then the
374
+ # compilation with inductor partitioning incorrectly loads an unpartitioned
375
+ # graph and never partitions. I think this is a bug with custom inductor
376
+ # partitioning but does not affect vLLM more generally as vLLM uses its own
377
+ # cache (which takes inductor partitioning into account).
378
+ if use_inductor_graph_partition:
379
+ compile_config_no_split.inductor_compile_config["force_disable_caches"] = True
380
+
381
+ compile_config_split = deepcopy(compile_config_no_split)
382
+ compile_config_split.splitting_ops = ["silly::attention"]
383
+
384
outputs = []
385
with compilation_counter.expect(
386
num_graphs_seen=0,
@@ -358,8 +389,9 @@ def test_toy_llama(backend: str):
389
num_backend_compilations=0,
390
num_cudagraph_captured=0,
391
):
361
- outputs.append(run_model(llama_config, backend="eager", use_compile=False))
362
- run_model(tractable_config, backend="eager", use_compile=False)
392
+ outputs.append(run_model(llama_config, compile_config_no_compile))
393
+
394
+ run_model(tractable_config, compile_config_no_compile)
395
396
if backend == "inductor":
397
kwargs = {"num_inductor_compiles": 1, "num_eager_compiles": 0}
@@ -367,35 +399,34 @@ def test_toy_llama(backend: str):
399
kwargs = {"num_eager_compiles": 1, "num_inductor_compiles": 0}
400
401
with compilation_counter.expect(
370
- # One graph for the model
371
- num_graphs_seen=1,
402
+ num_graphs_seen=1, # one graph for the model
403
num_piecewise_graphs_seen=1,
404
num_piecewise_capturable_graphs_seen=1,
374
- # num_piecewise_capturable_graphs_seen
375
- num_backend_compilations=1,
376
- # num_cudagraph_sizes * num_piecewise_capturable_graphs_seen
405
+ num_backend_compilations=1, # num_piecewise_capturable_graphs_seen
406
num_cudagraph_captured=2,
407
**kwargs,
408
):
380
- outputs.append(run_model(llama_config, backend=backend, use_compile=True))
381
- run_model(tractable_config, backend=backend, use_compile=True)
409
+ outputs.append(run_model(llama_config, compile_config_no_split))
410
+
411
+ run_model(tractable_config, compile_config_no_split)
412
+
413
+ if use_inductor_graph_partition:
414
+ num_piecewise_fx = 1
415
+ num_piecewise_capturable_fx = 1
416
+ else:
417
+ num_piecewise_fx = 2 * llama_config.num_layers + 1
418
+ num_piecewise_capturable_fx = 1 + llama_config.num_layers
419
420
with compilation_counter.expect(
421
num_graphs_seen=1, # one graph for the model
385
- num_piecewise_graphs_seen=2 * llama_config.num_layers + 1, # 2 * num_layers + 1
386
- num_piecewise_capturable_graphs_seen=1
387
- + llama_config.num_layers, # 1 + num_layers
388
- num_backend_compilations=1
389
- + llama_config.num_layers, # num_piecewise_capturable_graphs_seen
390
- num_cudagraph_captured=2
391
- * (
392
- 1 + llama_config.num_layers
393
- ), # num_cudagraph_sizes * num_piecewise_capturable_graphs_seen
422
+ num_piecewise_graphs_seen=num_piecewise_fx,
423
+ num_piecewise_capturable_graphs_seen=num_piecewise_capturable_fx,
424
+ num_backend_compilations=num_piecewise_capturable_fx,
425
+ # num_cudagraph_sizes * num_partitions
426
+ num_cudagraph_captured=2 * (1 + llama_config.num_layers),
427
):
395
- outputs.append(
396
- run_model(llama_config, backend=backend, use_compile=True, split_attn=True)
397
- )
398
- run_model(tractable_config, backend=backend, use_compile=True, split_attn=True)
428
+ outputs.append(run_model(llama_config, compile_config_split))
429
+ run_model(tractable_config, compile_config_split)
430
431
for i in range(1, len(outputs)):
432
assert torch.allclose(outputs[0], outputs[i])
tests/compile/silly_attention.py
-1
@@ -62,5 +62,4 @@ direct_register_custom_op(
62
mutates_args=["out"],
63
fake_impl=silly_attention_fake,
64
target_lib=silly_lib,
65
- tags=(torch._C.Tag.cudagraph_unsafe,),
65
)
tests/compile/test_decorator.py
+3
@@ -73,6 +73,7 @@ def test_ignore_torch_compile_decorator():
73
use_cudagraph=True,
74
splitting_ops=["silly::attention"],
75
cudagraph_capture_sizes=[1, 2],
76
+ use_inductor_graph_partition=False, # TODO test both?
77
)
78
)
79
cudagraph_runtime_mode = CUDAGraphMode.PIECEWISE
@@ -188,6 +189,7 @@ def test_conditional_compile_enable_if():
189
use_cudagraph=True,
190
splitting_ops=["silly::attention"],
191
cudagraph_capture_sizes=[1, 2],
192
+ use_inductor_graph_partition=False, # TODO test both
193
),
194
)
195
cudagraph_runtime_mode = CUDAGraphMode.PIECEWISE
@@ -220,6 +222,7 @@ def test_conditional_compile_enable_if():
222
use_cudagraph=True,
223
splitting_ops=["silly::attention"],
224
cudagraph_capture_sizes=[1, 2],
225
+ use_inductor_graph_partition=False, # TODO test both?
226
),
227
)
228
vllm/attention/layer.py
-6
@@ -38,10 +38,6 @@ from vllm.utils import GiB_bytes, direct_register_custom_op
38
39
logger = init_logger(__name__)
40
USE_XFORMERS_OPS = None
41
-try:
42
- tag_cudagraph_unsafe = (torch._C.Tag.cudagraph_unsafe,)
43
-except AttributeError:
44
- tag_cudagraph_unsafe = () # type: ignore[assignment]
41
42
43
def check_xformers_availability():
@@ -879,7 +875,6 @@ direct_register_custom_op(
875
op_name="unified_attention",
876
op_func=unified_attention,
877
fake_impl=unified_attention_fake,
882
- tags=tag_cudagraph_unsafe,
878
)
879
880
@@ -931,7 +926,6 @@ direct_register_custom_op(
926
op_func=unified_attention_with_output,
927
mutates_args=["output", "output_block_scale"],
928
fake_impl=unified_attention_with_output_fake,
934
- tags=tag_cudagraph_unsafe,
929
)
930
931
vllm/compilation/partition_rules.py
+11
-2
@@ -2,6 +2,7 @@
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
import contextlib
5
+import logging
6
from typing import TYPE_CHECKING
7
8
from torch._library.utils import lookup_op
@@ -38,8 +39,16 @@ def resolve_defined_ops(op_names: list[str]) -> list["torch._ops.OpOverload"]:
39
resolved.append(lookup_op(op_name))
40
except Exception:
41
# Skip operators that don't exist (e.g., model-specific ops)
41
- logger.warning(
42
- "Failed to resolve operator for Inductor partition: %s", op_name
42
+ # Do not warn for attention ops, warn for others
43
+ # (most likely manually specified)
44
+ from vllm.config import CompilationConfig
45
+
46
+ logger.log(
47
+ logging.DEBUG
48
+ if op_name in CompilationConfig._attention_ops
49
+ else logging.WARNING,
50
+ "Failed to resolve operator for CUDAGraph partition: %s",
51
+ op_name,
52
)
53
continue
54
vllm/config/compilation.py
+2
-1
@@ -201,7 +201,7 @@ class CompilationConfig:
201
(it sees a part of the graph). The backend can not be custom for compilation
202
level 3, i.e. the backend must be either eager or inductor. Furthermore,
203
compilation is only piecewise if splitting ops is set accordingly and
204
- use_inductor_cudagraphs_partition is off. Note that the default options for
204
+ use_inductor_graph_partition is off. Note that the default options for
205
splitting ops are sufficient for piecewise compilation.
206
"""
207
custom_ops: list[str] = field(default_factory=list)
@@ -431,6 +431,7 @@ class CompilationConfig:
431
factors.append(self.custom_ops)
432
factors.append(self.splitting_ops)
433
factors.append(self.use_inductor)
434
+ factors.append(self.use_inductor_graph_partition)
435
factors.append(self.inductor_compile_config)
436
factors.append(self.inductor_passes)
437
factors.append(self.pass_config.uuid())