@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 15e302dfce
Fork 0

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 9, 19:30 UTC

[Misc][BE] Turn on strict type coverage for vllm/compilation (#31756)

Signed-off-by: Lucas Kabela <lucaskabela@meta.com>

Lucas Kabela committed Jan 22, 2026 at 07:12 UTC 15e302dfced9e0fc31e4b6c2e8138b6957ed4740
11 files changed +121 -68
pyproject.toml
+7
@@ -100,6 +100,13 @@ ignore_missing_imports = true
100 check_untyped_defs = true
101 follow_imports = "silent"
102
103 +[[tool.mypy.overrides]]
104 +module = "vllm.compilation.*"
105 +disallow_untyped_defs = true
106 +disallow_incomplete_defs = true
107 +warn_return_any = true
108 +follow_imports = "silent"
109 +
110 [tool.pytest.ini_options]
111 markers = [
112 "slow_test",
tests/compile/test_pass_manager.py
+1 -1
@@ -28,7 +28,7 @@ def test_bad_callable():
28 pass_manager.configure(config)
29
30 with pytest.raises(AssertionError):
31 - pass_manager.add(simple_callable)
31 + pass_manager.add(simple_callable) # type: ignore[arg-type]
32
33
34 # Pass that inherits from InductorPass
tools/pre_commit/mypy.py
+37 -3
@@ -77,6 +77,11 @@ EXCLUDE = [
77 "vllm/v1/attention/ops",
78 ]
79
80 +# Directories that should be checked with --strict
81 +STRICT_DIRS = [
82 + "vllm/compilation",
83 +]
84 +
85
86 def group_files(changed_files: list[str]) -> dict[str, list[str]]:
87 """
@@ -108,11 +113,17 @@ def group_files(changed_files: list[str]) -> dict[str, list[str]]:
113 return file_groups
114
115
116 +def is_strict_file(filepath: str) -> bool:
117 + """Check if a file should be checked with strict mode."""
118 + return any(filepath.startswith(strict_dir) for strict_dir in STRICT_DIRS)
119 +
120 +
121 def mypy(
122 targets: list[str],
123 python_version: str | None,
124 follow_imports: str | None,
125 file_group: str,
126 + strict: bool = False,
127 ) -> int:
128 """
129 Run mypy on the given targets.
@@ -124,6 +135,7 @@ def mypy(
135 follow_imports: Value for the --follow-imports option or None to use
136 the default mypy behavior.
137 file_group: The file group name for logging purposes.
138 + strict: If True, run mypy with --strict flag.
139
140 Returns:
141 The return code from mypy.
@@ -133,6 +145,8 @@ def mypy(
145 args += ["--python-version", python_version]
146 if follow_imports is not None:
147 args += ["--follow-imports", follow_imports]
148 + if strict:
149 + args += ["--strict"]
150 print(f"$ {' '.join(args)} {file_group}")
151 return subprocess.run(args + targets, check=False).returncode
152
@@ -149,9 +163,29 @@ def main():
163 for file_group, changed_files in file_groups.items():
164 follow_imports = None if ci and file_group == "" else "skip"
165 if changed_files:
152 - returncode |= mypy(
153 - changed_files, python_version, follow_imports, file_group
154 - )
166 + # Separate files into strict and non-strict groups
167 + strict_files = [f for f in changed_files if is_strict_file(f)]
168 + non_strict_files = [f for f in changed_files if not is_strict_file(f)]
169 +
170 + # Run mypy on non-strict files
171 + if non_strict_files:
172 + returncode |= mypy(
173 + non_strict_files,
174 + python_version,
175 + follow_imports,
176 + file_group,
177 + strict=False,
178 + )
179 +
180 + # Run mypy on strict files with --strict flag
181 + if strict_files:
182 + returncode |= mypy(
183 + strict_files,
184 + python_version,
185 + follow_imports,
186 + f"{file_group} (strict)",
187 + strict=True,
188 + )
189 return returncode
190
191
vllm/compilation/backends.py
+1 -1
@@ -68,7 +68,7 @@ def make_copy_and_call(
68 A wrapper function that copies inputs and calls the compiled function
69 """
70
71 - def copy_and_call(*args):
71 + def copy_and_call(*args: Any) -> Any:
72 list_args = list(args)
73 for i, index in enumerate(sym_tensor_indices):
74 runtime_tensor = list_args[index]
vllm/compilation/caching.py
+12 -11
@@ -43,15 +43,15 @@ class StandaloneCompiledArtifacts:
43 split on attn)
44 """
45
46 - def __init__(self):
46 + def __init__(self) -> None:
47 # dict from submodule name to byte hash
48 - self.submodule_bytes = {}
48 + self.submodule_bytes: dict[str, str] = {}
49 # dict from byte hash to bytes
50 - self.submodule_bytes_store = {}
50 + self.submodule_bytes_store: dict[str, bytes] = {}
51 # dict from byte hash to loaded module
52 - self.loaded_submodule_store = {}
52 + self.loaded_submodule_store: dict[str, Any] = {}
53
54 - def insert(self, submod_name: str, shape: str, entry: bytes):
54 + def insert(self, submod_name: str, shape: str, entry: bytes) -> None:
55 hasher = hashlib.sha256()
56 hasher.update(entry)
57 hex_digest = hasher.hexdigest()
@@ -86,7 +86,7 @@ class StandaloneCompiledArtifacts:
86 self.submodule_bytes[f"{submod_name}_{shape}"]
87 ]
88
89 - def get_loaded(self, submod_name: str, shape: str):
89 + def get_loaded(self, submod_name: str, shape: str) -> Any:
90 logger.debug(
91 "getting artifact for submod %s with shape %s",
92 submod_name,
@@ -119,7 +119,7 @@ class StandaloneCompiledArtifacts:
119
120 from torch._inductor.standalone_compile import AOTCompiledArtifact
121
122 - def _load_entry(entry_bytes) -> AOTCompiledArtifact:
122 + def _load_entry(entry_bytes: bytes) -> AOTCompiledArtifact:
123 entry = pickle.loads(entry_bytes)
124 return AOTCompiledArtifact.deserialize(entry)
125
@@ -132,13 +132,13 @@ class StandaloneCompiledArtifacts:
132
133 logger.debug("loaded all %s submodules", self.num_artifacts())
134
135 - def __getstate__(self):
135 + def __getstate__(self) -> dict[str, dict[str, str] | dict[str, bytes]]:
136 return {
137 "submodule_bytes": self.submodule_bytes,
138 "submodule_bytes_store": self.submodule_bytes_store,
139 }
140
141 - def __setstate__(self, state):
141 + def __setstate__(self, state: dict[str, dict[str, Any]]) -> None:
142 self.submodule_bytes = state["submodule_bytes"]
143 self.submodule_bytes_store = state["submodule_bytes_store"]
144 self.loaded_submodule_store = {}
@@ -387,7 +387,7 @@ def reconstruct_serializable_fn_from_mega_artifact(
387 standalone_compile_artifacts.load_all()
388
389 submod_names = standalone_compile_artifacts.submodule_names()
390 - compiled_callables: dict[str, dict[str, Callable]] = {}
390 + compiled_callables: dict[str, dict[str, Callable[..., Any]]] = {}
391
392 for cache_key in standalone_compile_artifacts.submodule_bytes:
393 submod_name, shape_str = cache_key.rsplit("_", 1)
@@ -495,9 +495,10 @@ def _compute_code_hash_with_content(file_contents: dict[str, str]) -> str:
495 # e.g. exec(). We can't actually check these.
496 continue
497 hash_content.append(content)
498 - return safe_hash(
498 + result: str = safe_hash(
499 "\n".join(hash_content).encode(), usedforsecurity=False
500 ).hexdigest()
501 + return result
502
503
504 def _compute_code_hash(files: set[str]) -> str:
vllm/compilation/collective_fusion.py
+15 -13
@@ -30,19 +30,15 @@ from .vllm_inductor_pass import VllmInductorPass, VllmPatternMatcherPass
30
31 FP8_DTYPE = current_platform.fp8_dtype()
32
33 +flashinfer_comm: ModuleType | None = None
34 if find_spec("flashinfer"):
35 try:
35 - import flashinfer.comm as flashinfer_comm
36 + import flashinfer.comm as _flashinfer_comm
37
37 - flashinfer_comm: ModuleType | None = ( # type: ignore[no-redef]
38 - flashinfer_comm
39 - if hasattr(flashinfer_comm, "trtllm_allreduce_fusion")
40 - else None
41 - )
38 + if hasattr(_flashinfer_comm, "trtllm_allreduce_fusion"):
39 + flashinfer_comm = _flashinfer_comm
40 except ImportError:
43 - flashinfer_comm = None # type: ignore[assignment]
44 -else:
45 - flashinfer_comm = None # type: ignore[assignment]
41 + pass
42
43 logger = init_logger(__name__)
44
@@ -441,7 +437,7 @@ class AsyncTPPass(VllmPatternMatcherPass):
437 ):
438 return True
439 tp_size = get_tensor_model_parallel_world_size()
444 - return compile_range.is_single_size() and compile_range.end % tp_size == 0
440 + return bool(compile_range.is_single_size() and compile_range.end % tp_size == 0)
441
442 @VllmInductorPass.time_and_log
443 def __call__(self, graph: fx.Graph) -> None:
@@ -516,7 +512,7 @@ if flashinfer_comm is not None:
512 # Get one shot input size limit for the current world size
513 # for the current device capability
514 max_one_shot_size = _FI_ALLREDUCE_ONE_SHOT_MAX_SIZES_MB.get(
519 - device_capability, # type: ignore[arg-type]
515 + device_capability, # type: ignore[arg-type, unused-ignore]
516 {},
517 ).get(world_size, None)
518 # Use one shot if no max size is specified
@@ -666,6 +662,7 @@ class AllReduceRMSNormPattern(BasePattern):
662 ) -> tuple[torch.Tensor, torch.Tensor]:
663 residual = torch.zeros_like(input)
664 rms_result = torch.empty_like(input)
665 + assert flashinfer_comm is not None, "FlashInfer must be enabled"
666 allreduce = auto_functionalized(
667 flashinfer_trtllm_fused_allreduce_norm,
668 allreduce_in=input,
@@ -722,6 +719,7 @@ class AllReduceFusedAddRMSNormPattern(BasePattern):
719 def replacement(
720 residual: torch.Tensor, input: torch.Tensor, weight: torch.Tensor
721 ) -> tuple[torch.Tensor, torch.Tensor]:
722 + assert flashinfer_comm is not None, "FlashInfer must be enabled"
723 allreduce = auto_functionalized(
724 flashinfer_trtllm_fused_allreduce_norm,
725 allreduce_in=input,
@@ -800,6 +798,7 @@ class AllReduceFusedRMSNormStaticQuantFP8Pattern(BasePattern):
798 residual = torch.zeros_like(input)
799 result_rms = torch.empty_like(input)
800 result_quant = torch.empty_like(input, dtype=self.quant_dtype)
801 + assert flashinfer_comm is not None, "FlashInfer must be enabled"
802 allreduce = auto_functionalized(
803 flashinfer_trtllm_fused_allreduce_norm,
804 allreduce_in=input,
@@ -875,6 +874,7 @@ class AllReduceFusedAddRMSNormStaticQuantFP8Pattern(BasePattern):
874 scale: torch.Tensor,
875 ) -> tuple[torch.Tensor, torch.Tensor]:
876 result_quant = torch.empty_like(input, dtype=self.quant_dtype)
877 + assert flashinfer_comm is not None, "FlashInfer must be enabled"
878 allreduce = auto_functionalized(
879 flashinfer_trtllm_fused_allreduce_norm,
880 allreduce_in=input,
@@ -960,6 +960,7 @@ class AllReduceFusedRMSNormStaticQuantNVFP4Pattern(BasePattern):
960 ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
961 residual = torch.zeros_like(input)
962 result_rms = torch.empty_like(input)
963 + assert flashinfer_comm is not None, "FlashInfer must be enabled"
964 allreduce = auto_functionalized(
965 flashinfer_trtllm_fused_allreduce_norm,
966 allreduce_in=input,
@@ -1055,6 +1056,7 @@ class AllReduceFusedAddRMSNormStaticQuantNVFP4Pattern(BasePattern):
1056 weight: torch.Tensor,
1057 input_global_scale: torch.Tensor,
1058 ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
1059 + assert flashinfer_comm is not None, "FlashInfer must be enabled"
1060 allreduce = auto_functionalized(
1061 flashinfer_trtllm_fused_allreduce_norm,
1062 allreduce_in=input,
@@ -1131,7 +1133,7 @@ class AllReduceFusionPass(VllmPatternMatcherPass):
1133 )
1134
1135 self.ipc_handles, workspace_tensor = (
1134 - flashinfer_comm.trtllm_create_ipc_workspace_for_all_reduce_fusion( # type: ignore[misc]
1136 + flashinfer_comm.trtllm_create_ipc_workspace_for_all_reduce_fusion(
1137 tp_rank=rank,
1138 tp_size=self.tp_size,
1139 max_token_num=self.max_token_num,
@@ -1204,7 +1206,7 @@ class AllReduceFusionPass(VllmPatternMatcherPass):
1206 if self.disabled:
1207 logger.warning_once("AllReduce fusion pass is disabled.")
1208 return False
1207 - return compile_range.end <= self.max_token_num
1209 + return bool(compile_range.end <= self.max_token_num)
1210
1211 @VllmInductorPass.time_and_log
1212 def __call__(self, graph: fx.Graph) -> None:
vllm/compilation/compiler_interface.py
+6 -6
@@ -201,9 +201,9 @@ class InductorStandaloneAdaptor(CompilerInterface):
201
202 def compute_hash(self, vllm_config: VllmConfig) -> str:
203 factors = get_inductor_factors()
204 - hash_str = safe_hash(str(factors).encode(), usedforsecurity=False).hexdigest()[
205 - :10
206 - ]
204 + hash_str: str = safe_hash(
205 + str(factors).encode(), usedforsecurity=False
206 + ).hexdigest()[:10]
207 return hash_str
208
209 def initialize_cache(
@@ -319,9 +319,9 @@ class InductorAdaptor(CompilerInterface):
319
320 def compute_hash(self, vllm_config: VllmConfig) -> str:
321 factors = get_inductor_factors()
322 - hash_str = safe_hash(str(factors).encode(), usedforsecurity=False).hexdigest()[
323 - :10
324 - ]
322 + hash_str: str = safe_hash(
323 + str(factors).encode(), usedforsecurity=False
324 + ).hexdigest()[:10]
325 return hash_str
326
327 def initialize_cache(
vllm/compilation/decorators.py
+17 -17
@@ -45,10 +45,10 @@ logger = init_logger(__name__)
45
46 IGNORE_COMPILE_KEY = "_ignore_compile_vllm"
47
48 -_T = TypeVar("_T", bound=type[nn.Module])
48 +_T = TypeVar("_T", bound=nn.Module)
49
50
51 -def ignore_torch_compile(cls: _T) -> _T:
51 +def ignore_torch_compile(cls: type[_T]) -> type[_T]:
52 """
53 A decorator to ignore support_torch_compile decorator
54 on the class. This is useful when a parent class has
@@ -68,7 +68,7 @@ def ignore_torch_compile(cls: _T) -> _T:
68 return cls
69
70
71 -def _should_ignore_torch_compile(cls: _T) -> bool:
71 +def _should_ignore_torch_compile(cls: type[_T]) -> bool:
72 """
73 Check if the class should be ignored for torch.compile.
74 """
@@ -79,21 +79,21 @@ def _should_ignore_torch_compile(cls: _T) -> bool:
79 def support_torch_compile(
80 *,
81 enable_if: Callable[[VllmConfig], bool] | None = None,
82 -) -> Callable[[_T], _T]: ...
82 +) -> Callable[[type[_T]], type[_T]]: ...
83
84
85 @overload
86 def support_torch_compile(
87 *,
88 dynamic_arg_dims: dict[str, int | list[int]] | None,
89 -) -> Callable[[_T], _T]: ...
89 +) -> Callable[[type[_T]], type[_T]]: ...
90
91
92 @overload
93 def support_torch_compile(
94 *,
95 mark_unbacked_dims: dict[str, int | list[int]] | None,
96 -) -> Callable[[_T], _T]: ...
96 +) -> Callable[[type[_T]], type[_T]]: ...
97
98
99 @overload
@@ -101,21 +101,21 @@ def support_torch_compile(
101 *,
102 dynamic_arg_dims: dict[str, int | list[int]] | None,
103 mark_unbacked_dims: dict[str, int | list[int]] | None,
104 -) -> Callable[[_T], _T]: ...
104 +) -> Callable[[type[_T]], type[_T]]: ...
105
106
107 @overload
108 -def support_torch_compile(cls: _T) -> _T: ...
108 +def support_torch_compile(cls: type[_T]) -> type[_T]: ...
109
110
111 def support_torch_compile(
112 - cls: _T | None = None,
112 + cls: type[_T] | None = None,
113 *,
114 dynamic_arg_dims: dict[str, int | list[int]] | None = None,
115 mark_unbacked_dims: dict[str, int | list[int]] | None = None,
116 enable_if: Callable[[VllmConfig], bool] | None = None,
117 shape_invariants: Callable[..., None] = lambda *args, **kwargs: None,
118 -) -> Callable[[_T], _T] | _T:
118 +) -> Callable[[type[_T]], type[_T]] | type[_T]:
119 """
120 A decorator to add support for compiling the forward method of a class.
121
@@ -182,7 +182,7 @@ def support_torch_compile(
182 errors.
183 """
184
185 - def cls_decorator_helper(cls: _T) -> _T:
185 + def cls_decorator_helper(cls: type[_T]) -> type[_T]:
186 # helper to pass `dynamic_arg_dims` to `_support_torch_compile`
187 # to avoid too much indentation for `_support_torch_compile`
188 if not hasattr(cls, "forward"):
@@ -263,12 +263,12 @@ def _verify_source_unchanged(
263
264
265 def _support_torch_compile(
266 - cls: _T,
266 + cls: type[_T],
267 dynamic_arg_dims: dict[str, int | list[int]],
268 mark_unbacked_dims: dict[str, int | list[int]] | None = None,
269 enable_if: Callable[[VllmConfig], bool] | None = None,
270 shape_invariants: Callable[..., None] = lambda *args, **kwargs: None,
271 -) -> _T:
271 +) -> type[_T]:
272 """
273 A decorator to add support for compiling the forward method of a class.
274 """
@@ -325,12 +325,12 @@ def _support_torch_compile(
325 self.compiled = False
326
327 # Handled by monkeypatching `TorchCompileWithNoGuardsWrapper` into base class
328 - TorchCompileWithNoGuardsWrapper.__init__(self) # type: ignore[arg-type]
328 + TorchCompileWithNoGuardsWrapper.__init__(self)
329
330 cls.__init__ = __init__
331
332 def _mark_dynamic_inputs(
333 - mod: _T, ds_type: DynamicShapesType, *args: Any, **kwargs: Any
333 + mod: type[_T], ds_type: DynamicShapesType, *args: Any, **kwargs: Any
334 ) -> None:
335 def mark_dynamic(arg: torch.Tensor, dims: list[int]) -> None:
336 if ds_type == DynamicShapesType.UNBACKED:
@@ -382,7 +382,7 @@ def _support_torch_compile(
382 else:
383 torch._dynamo.decorators.mark_unbacked(arg, dims)
384
385 - def __call__(self: _T, *args: Any, **kwargs: Any) -> Any:
385 + def __call__(self: type[_T], *args: Any, **kwargs: Any) -> Any:
386 # torch.compiler.is_compiling() means we are inside the compilation
387 # e.g. TPU has the compilation logic in model runner, so we don't
388 # need to compile the model inside.
@@ -564,7 +564,7 @@ def _support_torch_compile(
564 return output
565
566 # triggers VllmSerializableFunction.serialize()
567 - def save_aot_compiled_function(self):
567 + def save_aot_compiled_function(self: type[_T]) -> None:
568 if self.was_aot_compile_fn_loaded_from_disk:
569 logger.debug("AOT compiled function was loaded from cache, skipping save")
570 return
vllm/compilation/matcher_utils.py
+13 -9
@@ -141,15 +141,18 @@ class MatcherRotaryEmbedding(MatcherCustomOp):
141 key: torch.Tensor | None,
142 cos_sin_cache: torch.Tensor,
143 ) -> tuple[torch.Tensor, torch.Tensor | None]:
144 - return RotaryEmbedding.forward_static(
145 - positions,
146 - query,
147 - key,
148 - self.head_size,
149 - self.rotary_dim,
150 - cos_sin_cache,
151 - self.is_neox,
144 + result: tuple[torch.Tensor, torch.Tensor | None] = (
145 + RotaryEmbedding.forward_static(
146 + positions,
147 + query,
148 + key,
149 + self.head_size,
150 + self.rotary_dim,
151 + cos_sin_cache,
152 + self.is_neox,
153 + )
154 )
155 + return result
156
157
158 class MatcherRMSNorm(MatcherCustomOp):
@@ -275,9 +278,10 @@ class MatcherFusedAddRMSNorm(MatcherCustomOp):
278 weight: torch.Tensor,
279 residual: torch.Tensor,
280 ) -> tuple[torch.Tensor, torch.Tensor]:
278 - return RMSNorm.forward_static(
281 + result: tuple[torch.Tensor, torch.Tensor] = RMSNorm.forward_static(
282 input, self.epsilon, input.size(-1), self.model_dtype, weight, residual
283 )
284 + return result
285
286
287 class MatcherQuantFP8(MatcherCustomOp):
vllm/compilation/piecewise_backend.py
+8 -6
@@ -25,7 +25,7 @@ logger = init_logger(__name__)
25 class RangeEntry:
26 compile_range: Range
27 compiled: bool = False
28 - runnable: Callable = None # type: ignore
28 + runnable: Callable[..., Any] = None # type: ignore
29
30
31 class PiecewiseBackend:
@@ -38,7 +38,7 @@ class PiecewiseBackend:
38 sym_shape_indices: list[int],
39 vllm_backend: VllmBackend,
40 returns_tuple: bool,
41 - compiled_runnables: dict[str, Callable] | None = None,
41 + compiled_runnables: dict[str, Callable[..., Any]] | None = None,
42 ):
43 """
44 The backend for piecewise compilation.
@@ -138,8 +138,10 @@ class PiecewiseBackend:
138
139 self.on_compilation_complete = _on_compilation_complete_callback.get()
140
141 - def get_compiled_graph_wrapper(self, compiled_graph):
142 - def compiled_graph_wrapper(*args):
141 + def get_compiled_graph_wrapper(
142 + self, compiled_graph: Callable[..., Any]
143 + ) -> Callable[..., Any]:
144 + def compiled_graph_wrapper(*args: Any) -> Any:
145 graph_output = compiled_graph(*args)
146 # unpack the tuple if needed
147 # TODO(rzou): the implication is that we're not
@@ -163,7 +165,7 @@ class PiecewiseBackend:
165
166 def to_bytes(self) -> dict[str, bytes]:
167 class StandaloneCompiledArtifactsPickler(Pickler):
166 - def reducer_override(self, obj):
168 + def reducer_override(self, obj: object) -> Any:
169 if isinstance(obj, CachingAutotuner):
170 obj.prepare_for_pickle()
171 return pickle.loads, (
@@ -173,7 +175,7 @@ class PiecewiseBackend:
175 )
176 return NotImplemented
177
176 - def serialize(fn) -> bytes:
178 + def serialize(fn: Callable[..., Any]) -> bytes:
179 assert hasattr(fn, "serialize"), "fn must have serialize method"
180 with torch._functorch.config.patch("bundled_autograd_cache", True):
181 entry = fn.serialize()
vllm/compilation/sequence_parallelism.py
+4 -1
@@ -358,7 +358,10 @@ class SequenceParallelismPass(VllmPatternMatcherPass):
358 ):
359 return True
360 tp_size = get_tensor_model_parallel_world_size()
361 - return (compile_range.is_single_size()) and (compile_range.end % tp_size == 0)
361 + result: bool = (compile_range.is_single_size()) and (
362 + compile_range.end % tp_size == 0
363 + )
364 + return result
365
366 @VllmInductorPass.time_and_log
367 def __call__(self, graph: fx.Graph) -> None: