main
py 804 lines 31.6 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 import contextlib
4 import copy
5 import os
6 from collections.abc import Callable
7 from contextlib import ExitStack
8 from typing import Any, Literal
9 from unittest.mock import patch
10
11 import torch
12 import torch._inductor.compile_fx
13 import torch.fx as fx
14
15 import vllm.envs as envs
16 from vllm.compilation.counter import compilation_counter
17 from vllm.config import VllmConfig
18 from vllm.config.utils import Range
19 from vllm.env_override import _apply_constrain_to_fx_strides_patch
20 from vllm.logger import init_logger
21 from vllm.utils.hashing import safe_hash
22 from vllm.utils.torch_utils import is_torch_equal_or_newer
23
24 logger = init_logger(__name__)
25
26 _last_inductor_triton_cache_dir: str | None = None
27
28
29 class CompilerInterface:
30 """The interface for a compiler that can be used by vLLM."""
31
32 # The name of the compiler, e.g. inductor.
33 # This is a class-level attribute.
34 name: str
35
36 def initialize_cache(
37 self, cache_dir: str, disable_cache: bool = False, prefix: str = ""
38 ) -> None:
39 """When the vLLM process uses `cache_dir` as the cache directory,
40 the compiler should initialize itself with the cache directory,
41 e.g. by re-directing its own cache directory to a sub-directory.
42
43 prefix can be used in combination with cache_dir to figure out the base
44 cache directory, e.g. there're multiple parts of model being compiled,
45 but we want to share the same cache directory for all of them.
46
47 e.g.
48 cache_dir = "/path/to/dir/backbone", prefix = "backbone"
49 cache_dir = "/path/to/dir/eagle_head", prefix = "eagle_head"
50 """
51 pass
52
53 def compute_hash(self, vllm_config: VllmConfig) -> str:
54 """Gather all the relevant information from the vLLM config,
55 to compute a hash so that we can cache the compiled model.
56
57 See [`VllmConfig.compute_hash`][vllm.config.VllmConfig.compute_hash]
58 to check what information
59 is already considered by default. This function should only
60 consider the information that is specific to the compiler.
61 """
62 return ""
63
64 def compile(
65 self,
66 graph: fx.GraphModule,
67 example_inputs: list[Any],
68 compiler_config: dict[str, Any],
69 compile_range: Range,
70 key: str | None = None,
71 ) -> tuple[Callable[..., Any] | None, Any | None]:
72 """Compile the graph with the given example inputs and compiler config,
73 with a range. The `compile_range` specifies the range of the inputs,
74 it could be concrete size (if compile_sizes is provided), e.g. [4, 4]
75 or a range [5, 8].
76 Right now we only support one variable in ranges for all inputs,
77 which is the batchsize (number of tokens) during inference.
78
79 Dynamo will make sure `graph(*example_inputs)` is valid.
80
81 The function should return a compiled callable function, as well as
82 a handle that can be used to directly load the compiled function.
83
84 The handle should be a plain Python object, preferably a string or a
85 file path for readability.
86
87 If the compiler doesn't support caching, it should return None for the
88 handle. If the compiler fails to compile the graph, it should return
89 None for the compiled function as well.
90
91 `key` is required for StandaloneInductorAdapter, it specifies where to
92 save the compiled artifact. The compiled artifact gets saved to
93 `cache_dir/key`.
94 """
95 return None, None
96
97 def load(
98 self,
99 handle: Any,
100 graph: fx.GraphModule,
101 example_inputs: list[Any],
102 graph_index: int,
103 compile_range: Range,
104 ) -> Callable[..., Any]:
105 """Load the compiled function from the handle.
106 Raises an error if the handle is invalid.
107
108 The handle is the second return value of the `compile` function.
109 """
110 raise NotImplementedError("caching is not supported")
111
112
113 class AlwaysHitShapeEnv:
114 """Why do we need this class:
115
116 For normal `torch.compile` usage, every compilation will have
117 one Dynamo bytecode compilation and one Inductor compilation.
118 The Inductor compilation happens under the context of the
119 Dynamo bytecode compilation, and that context is used to
120 determine the dynamic shape information, etc.
121
122 For our use case, we only run Dynamo bytecode compilation once,
123 and run Inductor compilation multiple times with different shapes
124 plus a general shape. The compilation for specific shapes happens
125 outside of the context of the Dynamo bytecode compilation. At that
126 time, we don't have shape environment to provide to Inductor, and
127 it will fail the Inductor code cache lookup.
128
129 By providing a dummy shape environment that always hits, we can
130 make the Inductor code cache lookup always hit, and we can
131 compile the graph for different shapes as needed.
132
133 The following dummy methods are obtained by trial-and-error
134 until it works.
135 """
136
137 def __init__(self) -> None:
138 self.guards: list[Any] = []
139 # Read by torch._inductor.codecache.FxGraphHashDetails (torch>=2.11)
140 # to incorporate user-provided dynamic-shape hint overrides into the
141 # cache key. We never override hints, so an empty dict is correct.
142 self.var_to_hint_override: dict[Any, int] = {}
143
144 def evaluate_guards_expression(self, *args: Any, **kwargs: Any) -> Literal[True]:
145 return True
146
147 def get_pruned_guards(self, *args: Any, **kwargs: Any) -> list[Any]:
148 return []
149
150 def produce_guards_expression(self, *args: Any, **kwargs: Any) -> Literal[""]:
151 return ""
152
153
154 def _get_vllm_functorch_config() -> dict[str, Any]:
155 """Return the functorch config overrides that vLLM applies at compile time.
156
157 Used by both set_functorch_config() and get_inductor_factors() to ensure
158 the compile-time config and cache key are always consistent."""
159 cfg: dict[str, Any] = {}
160 if not envs.VLLM_USE_MEGA_AOT_ARTIFACT:
161 cfg["bundled_autograd_cache"] = False
162 return cfg
163
164
165 def get_inductor_factors() -> list[Any]:
166 factors: list[Any] = []
167 # summarize system state
168 from torch._inductor.codecache import CacheBase
169
170 system_factors = CacheBase.get_system()
171 factors.append(system_factors)
172
173 # summarize pytorch state
174 from torch._inductor.codecache import torch_key
175
176 torch_factors = torch_key()
177 factors.append(torch_factors)
178
179 from torch._functorch import config as functorch_config
180 from torch._inductor import config as inductor_config
181
182 factors.append(inductor_config.save_config_portable())
183 with functorch_config.patch(_get_vllm_functorch_config()):
184 factors.append(functorch_config.save_config_portable())
185 return factors
186
187
188 def is_compile_cache_enabled(
189 vllm_additional_inductor_config: dict[str, Any],
190 ) -> bool:
191 vllm_inductor_config_disable_cache = vllm_additional_inductor_config.get(
192 "force_disable_caches", False
193 )
194
195 # TODO(gmagogsfm): Replace torch._inductor.config.force_disable_caches
196 # with torch.compiler.config.force_disable_caches when minimum PyTorch
197 # version reaches 2.10
198 return (
199 not envs.VLLM_DISABLE_COMPILE_CACHE
200 and not torch._inductor.config.force_disable_caches
201 and not vllm_inductor_config_disable_cache
202 )
203
204
205 def _patch_standalone_compile_atomic_save() -> None:
206 """Backport of pytorch/pytorch#162432 for torch < 2.10.0.
207
208 Patches CompiledArtifact.save() to use write_atomic for binary format,
209 preventing corrupt cache files when multiple processes compile
210 concurrently.
211 """
212 from torch._inductor.codecache import write_atomic
213 from torch._inductor.standalone_compile import CompiledArtifact as cls
214
215 if getattr(cls.save, "_vllm_patched", False):
216 return
217
218 original_save = cls.save
219
220 def _save(
221 self: Any, *, path: str, format: Literal["binary", "unpacked"] = "binary"
222 ) -> None:
223 if format != "binary":
224 return original_save(self, path=path, format=format)
225 from torch._dynamo.utils import dynamo_timed
226 from torch._inductor.codecache import torch_key
227 from torch.utils._appending_byte_serializer import BytesWriter
228
229 with dynamo_timed("CompiledArtifact.save"):
230 assert self._artifacts is not None
231 artifact_bytes, cache_info = self._artifacts
232 assert len(cache_info.aot_autograd_artifacts) == 1, cache_info
233 key = cache_info.aot_autograd_artifacts[0]
234 assert not os.path.isdir(path)
235 writer = BytesWriter()
236 writer.write_bytes(torch_key())
237 writer.write_str(key)
238 writer.write_bytes(artifact_bytes)
239 write_atomic(path, writer.to_bytes())
240
241 _save._vllm_patched = True # type: ignore[attr-defined]
242 cls.save = _save # type: ignore[assignment]
243 logger.debug("Patched %s.save for atomic writes (torch < 2.10)", cls.__name__)
244
245
246 class InductorStandaloneAdaptor(CompilerInterface):
247 """The adaptor for the Inductor compiler.
248 Requires PyTorch 2.8+.
249 This is not on by default yet, but we plan to turn it on by default for
250 PyTorch 2.8.
251
252 Use VLLM_USE_STANDALONE_COMPILE to toggle this on or off.
253 """
254
255 name = "inductor_standalone"
256
257 def __init__(self, save_format: Literal["binary", "unpacked"]) -> None:
258 if not is_torch_equal_or_newer("2.10.0"):
259 _patch_standalone_compile_atomic_save()
260 self.save_format = save_format
261
262 def compute_hash(self, vllm_config: VllmConfig) -> str:
263 factors = get_inductor_factors()
264 hash_str: str = safe_hash(
265 str(factors).encode(), usedforsecurity=False
266 ).hexdigest()[:10]
267 return hash_str
268
269 def initialize_cache(
270 self, cache_dir: str, disable_cache: bool = False, prefix: str = ""
271 ) -> None:
272 self.cache_dir = cache_dir
273
274 def compile(
275 self,
276 graph: fx.GraphModule,
277 example_inputs: list[Any],
278 compiler_config: dict[str, Any],
279 compile_range: Range,
280 key: str | None = None,
281 ) -> tuple[Callable[..., Any] | None, Any | None]:
282 _apply_constrain_to_fx_strides_patch()
283 compilation_counter.num_inductor_compiles += 1
284 current_config = {}
285 if compiler_config is not None:
286 current_config.update(compiler_config)
287 set_inductor_config(current_config, compile_range)
288 set_functorch_config()
289
290 if compile_range.is_single_size():
291 dynamic_shapes = "from_example_inputs"
292 else:
293 dynamic_shapes = "from_graph"
294
295 from torch._inductor import standalone_compile
296
297 supports_aot = is_torch_equal_or_newer("2.10.0")
298
299 if not supports_aot and envs.VLLM_USE_MEGA_AOT_ARTIFACT:
300 logger.error(
301 "CRITICAL: VLLM_USE_MEGA_AOT_ARTIFACT "
302 "is enabled but PyTorch version does not support 'aot' "
303 "parameter in standalone_compile. This requires PyTorch "
304 "2.10.0+. Falling back to non-AOT mode."
305 )
306
307 compile_kwargs = {
308 "dynamic_shapes": dynamic_shapes,
309 "options": {
310 "config_patches": current_config,
311 },
312 }
313
314 if is_torch_equal_or_newer("2.13.0.dev"):
315 compile_kwargs["donate_graph_module"] = True # type: ignore[assignment]
316
317 use_aot: bool = supports_aot and envs.VLLM_USE_MEGA_AOT_ARTIFACT
318 # only add 'aot' parameter if both supported and enabled...
319 # this will set bundled_autograd_cache
320 # https://github.com/pytorch/pytorch/blob/9bbc5b2905c260adf41bc866a732f9c121a2828a/torch/_inductor/standalone_compile.py#L359 # noqa
321 if use_aot:
322 compile_kwargs["aot"] = True # type: ignore[assignment]
323
324 # Inductor's pre-grad passes don't do anything for vLLM.
325 # The pre-grad passes get run even on cache-hit and negatively impact
326 # vllm cold compile times by O(1s)
327 # Fixed upstream in PyTorch 2.12:
328 # https://github.com/pytorch/pytorch/issues/174502
329 if is_torch_equal_or_newer("2.12.0.dev") or envs.VLLM_ENABLE_PREGRAD_PASSES:
330 pregrad_ctx: Any = contextlib.nullcontext()
331 else:
332 pregrad_ctx = patch(
333 "torch._inductor.compile_fx._recursive_pre_grad_passes",
334 lambda gm, _: gm,
335 )
336
337 # When inputs are FakeTensors (from create_concrete_args),
338 # standalone_compile("from_example_inputs") would normally create
339 # a fresh FakeTensorMode, causing a mode mismatch assertion.
340 # Patch FakeTensorMode in standalone_compile so it reuses the
341 # mode already attached to our FakeTensors. This gives us both
342 # ignore_shape_env=True (from "from_example_inputs") and mode
343 # consistency (from reusing our mode).
344 # Can remove this after the following issue gets fixed:
345 # https://github.com/pytorch/pytorch/issues/176562
346 from torch._subclasses.fake_tensor import FakeTensor
347
348 input_fake_mode = None
349 for x in example_inputs:
350 if isinstance(x, FakeTensor):
351 input_fake_mode = x.fake_mode
352 break
353
354 if input_fake_mode is not None:
355 # Use patch.object on the actual module from sys.modules
356 # because in Python <=3.10 the string-based patch() resolves
357 # torch._inductor.standalone_compile to the wrapper function
358 # (defined in __init__.py) instead of the module.
359 import sys
360
361 fake_mode_ctx: Any = patch.object(
362 sys.modules["torch._inductor.standalone_compile"],
363 "FakeTensorMode",
364 lambda *a, **kw: input_fake_mode,
365 )
366 else:
367 fake_mode_ctx = contextlib.nullcontext()
368
369 with pregrad_ctx, fake_mode_ctx:
370 compiled_graph = standalone_compile(graph, example_inputs, **compile_kwargs)
371
372 if use_aot:
373 from torch._inductor.standalone_compile import AOTCompiledArtifact
374
375 assert isinstance(compiled_graph, AOTCompiledArtifact)
376 assert hasattr(compiled_graph, "serialize")
377 # just return the compiled graph and a key
378 # since we can serialize the bytes using to_bytes
379 # and reload it using the key when reading
380 return compiled_graph, None
381
382 # Save the compiled artifact to disk in the specified path
383 assert key is not None
384 path = os.path.join(self.cache_dir, key)
385
386 def is_saveable_2_10(compiled_artifact):
387 # can just use compiled_artifact.is_saveable in 2.11
388 if compiled_artifact._artifacts is None:
389 return False
390 _, cache_info = compiled_artifact._artifacts
391 return len(cache_info.aot_autograd_artifacts) == 1
392
393 if is_compile_cache_enabled(compiler_config):
394 if not is_saveable_2_10(compiled_graph):
395 raise RuntimeError(
396 "The compiled artifact is not serializable. This usually means "
397 "that the model code has something that is not serializable "
398 "by torch.compile in it. You can fix this by either "
399 "figuring out what is not serializable and rewriting it, "
400 "filing a bug report, "
401 "or suppressing this error by "
402 "disabling vLLM's compilation cache via "
403 "VLLM_DISABLE_COMPILE_CACHE=1 "
404 "(this will greatly increase vLLM server warm start times)."
405 )
406 compiled_graph.save(path=path, format=self.save_format)
407 compilation_counter.num_compiled_artifacts_saved += 1
408 return compiled_graph, (key, path)
409
410 def load(
411 self,
412 handle: Any,
413 graph: fx.GraphModule,
414 example_inputs: list[Any],
415 graph_index: int,
416 compile_range: Range,
417 ) -> Callable[..., Any]:
418 assert isinstance(handle, tuple)
419 assert isinstance(handle[0], str)
420 assert isinstance(handle[1], str)
421 path = os.path.join(self.cache_dir, handle[0])
422 inductor_compiled_graph = torch._inductor.CompiledArtifact.load(
423 path=path, format=self.save_format
424 )
425 compilation_counter.num_compiled_artifacts_loaded += 1
426 from torch._inductor.compile_fx import graph_returns_tuple
427
428 returns_tuple = graph_returns_tuple(graph)
429
430 def compiled_graph_wrapper(*args: Any) -> tuple[Any, ...] | Any:
431 graph_output = inductor_compiled_graph(*args)
432 # unpack the tuple if needed
433 # TODO(rzou): the implication is that we're not
434 # reading the python bytecode correctly in vLLM?
435 if returns_tuple:
436 return graph_output
437 else:
438 return graph_output[0]
439
440 return compiled_graph_wrapper
441
442
443 class InductorAdaptor(CompilerInterface):
444 """The adaptor for the Inductor compiler, version 2.5, 2.6, 2.7."""
445
446 name = "inductor"
447
448 def compute_hash(self, vllm_config: VllmConfig) -> str:
449 factors = get_inductor_factors()
450 hash_str: str = safe_hash(
451 str(factors).encode(), usedforsecurity=False
452 ).hexdigest()[:10]
453 return hash_str
454
455 def initialize_cache(
456 self, cache_dir: str, disable_cache: bool = False, prefix: str = ""
457 ) -> None:
458 global _last_inductor_triton_cache_dir
459 self.cache_dir = cache_dir
460 self.prefix = prefix
461 self.base_cache_dir = cache_dir[: -len(prefix)] if prefix else cache_dir
462 if disable_cache:
463 return
464 # Keep artifacts together unless the caller supplies a Triton cache.
465 inductor_cache = os.path.join(self.base_cache_dir, "inductor_cache")
466 os.makedirs(inductor_cache, exist_ok=True)
467 os.environ["TORCHINDUCTOR_CACHE_DIR"] = inductor_cache
468 triton_cache = os.environ.get("TRITON_CACHE_DIR")
469 # A directory assigned by an earlier adaptor is still a model-local
470 # default, rather than a caller override for subsequent models.
471 if triton_cache is None or triton_cache == _last_inductor_triton_cache_dir:
472 triton_cache = os.path.join(self.base_cache_dir, "triton_cache")
473 _last_inductor_triton_cache_dir = triton_cache
474 os.makedirs(triton_cache, exist_ok=True)
475 os.environ["TRITON_CACHE_DIR"] = triton_cache
476
477 def compile(
478 self,
479 graph: fx.GraphModule,
480 example_inputs: list[Any],
481 compiler_config: dict[str, Any],
482 compile_range: Range,
483 key: str | None = None,
484 ) -> tuple[Callable[..., Any] | None, Any | None]:
485 _apply_constrain_to_fx_strides_patch()
486 compilation_counter.num_inductor_compiles += 1
487 from torch._inductor.compile_fx import compile_fx
488
489 current_config = {}
490 if compiler_config is not None:
491 current_config.update(compiler_config)
492
493 # disable remote cache
494 current_config["fx_graph_cache"] = True
495 current_config["fx_graph_remote_cache"] = False
496
497 set_inductor_config(current_config, compile_range)
498 set_functorch_config()
499
500 # inductor can inplace modify the graph, so we need to copy it
501 # see https://github.com/pytorch/pytorch/issues/138980
502 graph = copy.deepcopy(graph)
503
504 # it's the first time we compile this graph
505 # the assumption is that we don't have nested Inductor compilation.
506 # compiled_fx_graph_hash will only be called once, and we can hook
507 # it to get the hash of the compiled graph directly.
508
509 hash_str, file_path = None, None
510 from torch._inductor.codecache import compiled_fx_graph_hash
511
512 def hijacked_compile_fx_inner(*args: Any, **kwargs: Any) -> Any:
513 output = torch._inductor.compile_fx.compile_fx_inner(*args, **kwargs)
514 nonlocal hash_str
515 inductor_compiled_graph = output
516 if inductor_compiled_graph is not None:
517 nonlocal file_path
518 compiled_fn = inductor_compiled_graph.current_callable
519 file_path = compiled_fn.__code__.co_filename # noqa
520 if (
521 not file_path.startswith(self.base_cache_dir)
522 and compiled_fn.__closure__ is not None
523 ):
524 # hooked in the align_inputs_from_check_idxs function
525 # in torch/_inductor/utils.py
526 for cell in compiled_fn.__closure__:
527 if not callable(cell.cell_contents):
528 continue
529 code = cell.cell_contents.__code__
530 if code.co_filename.startswith(self.base_cache_dir):
531 # this is the real file path
532 # compiled from Inductor
533 file_path = code.co_filename
534 break
535 hash_str = inductor_compiled_graph._fx_graph_cache_key
536 return output
537
538 def hijack_compiled_fx_graph_hash(*args: Any, **kwargs: Any) -> Any:
539 out = compiled_fx_graph_hash(*args, **kwargs)
540 nonlocal hash_str
541 hash_str = out[0]
542 return out
543
544 def _check_can_cache(*args: Any, **kwargs: Any) -> None:
545 # no error means it can be cached.
546 # Inductor refuses to cache the graph outside of Dynamo
547 # tracing context, and also disables caching for graphs
548 # with high-order ops.
549 # For vLLM, in either case, we want to cache the graph.
550 # see https://github.com/pytorch/pytorch/blob/9f5ebf3fc609105a74eab4ccc24932d6353ff566/torch/_inductor/codecache.py#L1221 # noqa
551 return
552
553 def _get_shape_env() -> AlwaysHitShapeEnv:
554 return AlwaysHitShapeEnv()
555
556 with ExitStack() as stack:
557 # for hijacking the hash of the compiled graph
558 stack.enter_context(
559 patch(
560 "torch._inductor.codecache.compiled_fx_graph_hash",
561 hijack_compiled_fx_graph_hash,
562 )
563 )
564
565 # for providing a dummy shape environment
566 stack.enter_context(
567 patch(
568 "torch._inductor.codecache.FxGraphCache._get_shape_env",
569 _get_shape_env,
570 )
571 )
572
573 from torch._functorch._aot_autograd.autograd_cache import AOTAutogradCache
574
575 # torch 2.8+ on main uses _get_shape_env in AOTAutogradCache
576 if hasattr(AOTAutogradCache, "_get_shape_env"):
577 stack.enter_context(
578 patch(
579 "torch._functorch._aot_autograd.autograd_cache.AOTAutogradCache._get_shape_env",
580 _get_shape_env,
581 )
582 )
583
584 # for forcing the graph to be cached
585 stack.enter_context(
586 patch(
587 "torch._inductor.codecache.FxGraphCache._check_can_cache",
588 _check_can_cache,
589 )
590 )
591
592 # Dynamo metrics context, see method for more details.
593 stack.enter_context(self.metrics_context())
594
595 # Disable remote caching. When these are on, on remote cache-hit,
596 # the monkey-patched functions never actually get called.
597 # vLLM today assumes and requires the monkey-patched functions to
598 # get hit.
599 # TODO(zou3519): we're going to replace this all with
600 # standalone_compile sometime.
601 stack.enter_context(
602 torch._inductor.config.patch(fx_graph_remote_cache=False)
603 )
604 # InductorAdaptor (unfortunately) requires AOTAutogradCache
605 # to be turned off to run. It will fail to acquire the hash_str
606 # and error if not.
607 # StandaloneInductorAdaptor (PyTorch 2.8+) fixes this problem.
608 stack.enter_context(
609 torch._functorch.config.patch(enable_autograd_cache=False)
610 )
611 stack.enter_context(
612 torch._functorch.config.patch(enable_remote_autograd_cache=False)
613 )
614
615 # Clear the tracing context before calling compile_fx.
616 # vLLM calls compile_fx from within a PiecewiseCompileInterpreter
617 # that runs under Dynamo's tracing context. The tracing context
618 # has a FakeTensorMode from Dynamo, but the example inputs for
619 # this subgraph have fake tensors from a different FakeTensorMode.
620 # compile_fx's _compile_fx_main calls detect_fake_mode() which
621 # asserts all FakeTensorModes match, causing a crash.
622 # Clearing the tracing context lets compile_fx create its own.
623 saved_tracing_context = torch._guards.TracingContext.try_get()
624 if saved_tracing_context is not None:
625 torch._guards._TLS.tracing_context = None
626
627 def _restore_tracing_context():
628 torch._guards._TLS.tracing_context = saved_tracing_context
629
630 stack.callback(_restore_tracing_context)
631
632 compiled_graph = compile_fx(
633 graph,
634 example_inputs,
635 inner_compile=hijacked_compile_fx_inner,
636 config_patches=current_config,
637 )
638
639 # Turn off the checks if we disable the compilation cache.
640 if is_compile_cache_enabled(compiler_config):
641 if hash_str is None:
642 raise RuntimeError(
643 "vLLM failed to compile the model. The most "
644 "likely reason for this is that a previous compilation "
645 "failed, leading to a corrupted compilation artifact. "
646 "We recommend trying to "
647 "remove ~/.cache/vllm/torch_compile_cache and try again "
648 "to see the real issue. "
649 )
650 assert file_path is not None, (
651 "failed to get the file path of the compiled graph"
652 )
653 return compiled_graph, (hash_str, file_path)
654
655 def load(
656 self,
657 handle: Any,
658 graph: fx.GraphModule,
659 example_inputs: list[Any],
660 graph_index: int,
661 compile_range: Range,
662 ) -> Callable[..., Any]:
663 assert isinstance(handle, tuple)
664 assert isinstance(handle[0], str)
665 assert isinstance(handle[1], str)
666 hash_str = handle[0]
667
668 from torch._functorch._aot_autograd.autograd_cache import AOTAutogradCache
669 from torch._inductor.codecache import FxGraphCache
670
671 with ExitStack() as exit_stack:
672 exit_stack.enter_context(
673 patch(
674 "torch._inductor.codecache.FxGraphCache._get_shape_env",
675 lambda *args, **kwargs: AlwaysHitShapeEnv(),
676 )
677 )
678 # torch 2.8+ on main uses _get_shape_env in AOTAutogradCache
679 if hasattr(AOTAutogradCache, "_get_shape_env"):
680 exit_stack.enter_context(
681 patch(
682 "torch._functorch._aot_autograd.autograd_cache.AOTAutogradCache._get_shape_env",
683 lambda *args, **kwargs: AlwaysHitShapeEnv(),
684 )
685 )
686
687 # Dynamo metrics context, see method for more details.
688 exit_stack.enter_context(self.metrics_context())
689
690 from torch._inductor.output_code import CompiledFxGraphConstantsWithGm
691
692 constants = CompiledFxGraphConstantsWithGm(graph)
693 inductor_compiled_graph, _ = FxGraphCache._lookup_graph(
694 hash_str, example_inputs, True, None, constants
695 )
696 assert inductor_compiled_graph is not None, (
697 "Inductor cache lookup failed. Please remove "
698 f"the cache directory and try again." # noqa
699 )
700
701 # Inductor calling convention (function signature):
702 # f(list) -> tuple
703 # Dynamo calling convention (function signature):
704 # f(*args) -> Any
705
706 # need to know if the graph returns a tuple
707 from torch._inductor.compile_fx import graph_returns_tuple
708
709 returns_tuple = graph_returns_tuple(graph)
710
711 # this is the callable we return to Dynamo to run
712 def compiled_graph(*args: Any) -> tuple[Any, ...] | Any:
713 # convert args to list
714 list_args = list(args)
715 graph_output = inductor_compiled_graph(list_args)
716 # unpack the tuple if needed
717 if returns_tuple:
718 return graph_output
719 else:
720 return graph_output[0]
721
722 return compiled_graph
723
724 def metrics_context(self) -> contextlib.AbstractContextManager[Any]:
725 """This method returns the Dynamo metrics context (if it exists,
726 otherwise a null context). It is used by various compile components.
727 Present in torch>=2.6, it's used inside FxGraphCache in
728 torch==2.6 (but not after). It might also be used in various other
729 torch.compile internal functions.
730
731 Because it is re-entrant, we always set it (even if entering via Dynamo
732 and the context was already entered). We might want to revisit if it
733 should be set at a different mode of compilation.
734
735 This is likely a bug in PyTorch: public APIs should not rely on
736 manually setting up internal contexts. But we also rely on non-public
737 APIs which might not provide these guarantees.
738 """
739 if is_torch_equal_or_newer("2.6"):
740 import torch._dynamo.utils
741
742 return torch._dynamo.utils.get_metrics_context() # type: ignore[no-any-return]
743 else:
744 return contextlib.nullcontext()
745
746
747 def set_inductor_config(config: dict[str, Any], compile_range: Range) -> None:
748 if compile_range.is_single_size():
749 # for a specific batch size, tuning triton kernel parameters
750 # can be beneficial
751 config["max_autotune"] = envs.VLLM_ENABLE_INDUCTOR_MAX_AUTOTUNE
752 config["coordinate_descent_tuning"] = (
753 envs.VLLM_ENABLE_INDUCTOR_COORDINATE_DESCENT_TUNING
754 )
755
756
757 def set_functorch_config() -> None:
758 for k, v in _get_vllm_functorch_config().items():
759 setattr(torch._functorch.config, k, v)
760
761
762 def trigger_inductor_lazy_init(device: torch.device | None = None) -> None:
763 """Eagerly trigger inductor's once-per-process lazy inits (SFDP pattern
764 matcher, pad_mm, misc patterns).
765
766 These normally fire on the first torch.compile invocation and include
767 CUDA syncs. If warmup hits the on-disk compile cache, no compile actually
768 runs so these never fire during warmup, and they'd blow up on the first
769 real-request cache miss once the sync-check gate is on.
770
771 Private torch API; best-effort. Newer torch versions take an
772 `input_device` argument and cache per-device, so pass the current CUDA
773 device to ensure the cache key matches later compile calls.
774 """
775 try:
776 import inspect
777
778 from torch._inductor.fx_passes.joint_graph import (
779 lazy_init as _inductor_lazy_init,
780 )
781
782 if inspect.signature(_inductor_lazy_init).parameters:
783 _inductor_lazy_init(device)
784 else:
785 _inductor_lazy_init()
786 except Exception as e: # noqa: BLE001
787 logger.info("Skipping inductor lazy_init pre-trigger: %s", e)
788
789
790 class EagerAdaptor(CompilerInterface):
791 name = "eager"
792
793 def compile(
794 self,
795 graph: fx.GraphModule,
796 example_inputs: list[Any],
797 compiler_config: dict[str, Any],
798 compile_range: Range,
799 key: str | None = None,
800 ) -> tuple[Callable[..., Any] | None, Any | None]:
801 compilation_counter.num_eager_compiles += 1
802 # we don't need to compile the graph, just return the graph itself.
803 # It does not support caching, return None for the handle.
804 return graph, None