main
py 171 lines 5.67 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Compile-only helpers for TileLang JIT warmup."""
4
5 from __future__ import annotations
6
7 import logging
8 from abc import abstractmethod
9 from collections.abc import Callable, Iterator, Mapping, MutableMapping
10 from contextlib import contextmanager
11 from dataclasses import dataclass, field
12 from functools import wraps
13 from typing import Any, ClassVar, Generic, TypeAlias, TypeVar, cast
14
15 import torch
16
17 from vllm.model_executor.warmup.jit_warmup import VllmJitKernel
18
19 CompileKeyT = TypeVar("CompileKeyT")
20 # (kernel_args, launch_args), optionally followed by keywords and a result.
21 TileLangLaunchSpec: TypeAlias = (
22 tuple[tuple[Any, ...], tuple[Any, ...]]
23 | tuple[tuple[Any, ...], tuple[Any, ...], Mapping[str, Any] | None]
24 | tuple[tuple[Any, ...], tuple[Any, ...], Mapping[str, Any] | None, Any]
25 )
26
27
28 @contextmanager
29 def _quiet_tilelang_warmup_logs() -> Iterator[None]:
30 loggers = [
31 logging.getLogger("tilelang.cache.kernel_cache"),
32 logging.getLogger("tilelang.jit.kernel"),
33 ]
34 previous_levels = [logger.level for logger in loggers]
35 try:
36 for logger in loggers:
37 logger.setLevel(logging.ERROR)
38 yield
39 finally:
40 for logger, level in zip(loggers, previous_levels):
41 logger.setLevel(level)
42
43
44 @dataclass(frozen=True)
45 class TileLangWarmupTensor:
46 """Minimal tensor-like object accepted by TileLang compile().
47
48 TileLang builds its cache key and TIR from tensor dtype, shape and stride.
49 This object deliberately has no storage, so compile-only warmup does not
50 allocate GPU memory and does not launch the kernel.
51 """
52
53 dtype: torch.dtype
54 shape: tuple[int, ...] = (1,)
55 strides: tuple[int, ...] | None = field(default=None)
56
57 def stride(self) -> tuple[int, ...]:
58 if self.strides is not None:
59 return self.strides
60
61 strides: list[int] = []
62 stride = 1
63 for size in reversed(self.shape):
64 strides.append(stride)
65 stride *= size
66 return tuple(reversed(strides))
67
68 def new_empty(
69 self,
70 shape: tuple[int, ...],
71 *,
72 dtype: torch.dtype | None = None,
73 device: Any = None,
74 ) -> TileLangWarmupTensor:
75 return TileLangWarmupTensor(dtype or self.dtype, shape)
76
77
78 def make_tilelang_warmup_tensor(
79 dtype: torch.dtype,
80 *shape: int,
81 strides: tuple[int, ...] | None = None,
82 ) -> TileLangWarmupTensor:
83 return TileLangWarmupTensor(dtype=dtype, shape=tuple(shape), strides=strides)
84
85
86 def _tilelang_call_kwargs(kwargs: dict[str, Any]) -> dict[str, Any]:
87 call_kwargs = dict(kwargs)
88 tune_params = call_kwargs.pop("__tune_params", {})
89 if isinstance(tune_params, dict):
90 call_kwargs.update(tune_params)
91 return call_kwargs
92
93
94 def compile_tilelang(jit_impl: Any, *args: Any, **kwargs: Any) -> None:
95 """Compile one TileLang specialization and populate its call cache.
96
97 TileLang's ``compile()`` materializes the kernel without launching it.
98 We also store the compiled kernel in ``_kernel_cache`` using the same
99 parsed key as ``__call__`` so runtime does not report a cache miss for an
100 already materialized specialization.
101 """
102 with _quiet_tilelang_warmup_logs():
103 compiled = jit_impl.compile(*args, **kwargs)
104 func = getattr(jit_impl, "func", None)
105 parse_args = getattr(func, "parse_args", None)
106 cache = getattr(jit_impl, "_kernel_cache", None)
107 if not callable(parse_args) or not isinstance(cache, MutableMapping):
108 return
109
110 key, _ = parse_args(*args, **_tilelang_call_kwargs(kwargs))
111 cache[key] = compiled
112
113
114 class VllmTileLangJitKernel(VllmJitKernel[CompileKeyT], Generic[CompileKeyT]):
115 """TileLang owner whose runtime launch specification is reused for warmup."""
116
117 kernel: ClassVar[Any]
118 _warming_key: CompileKeyT | None = None
119
120 @abstractmethod
121 def warmup_inputs(self, compile_key: CompileKeyT) -> dict[str, Any]:
122 """Return runtime-shaped inputs that reproduce one compile key."""
123 raise NotImplementedError
124
125 def compile(self, compile_key: CompileKeyT) -> None:
126 self._warming_key = compile_key
127 try:
128 cast(Callable[..., Any], self)(**self.warmup_inputs(compile_key))
129 finally:
130 self._warming_key = None
131
132 def launch(
133 self,
134 jit_impl: Any,
135 args: tuple[Any, ...],
136 kwargs: Mapping[str, Any] | None = None,
137 ) -> Any:
138 call_kwargs = dict(kwargs or {})
139 if self._warming_key is not None:
140 return compile_tilelang(jit_impl, *args, **call_kwargs)
141 return jit_impl(*args, **call_kwargs)
142
143
144 def kernel_launcher(
145 call_fn: Callable[..., TileLangLaunchSpec],
146 ) -> Callable[..., Any]:
147 """Launch TileLang from declarative kernel and runtime argument tuples."""
148
149 @wraps(call_fn)
150 def wrapper(
151 self: VllmTileLangJitKernel[Any],
152 *args: Any,
153 **kwargs: Any,
154 ) -> Any:
155 launch_spec = call_fn(self, *args, **kwargs)
156 kernel_args, launch_args = launch_spec[:2]
157 if self._warming_key is not None and kernel_args not in (
158 (),
159 (self._warming_key,),
160 ):
161 raise RuntimeError(
162 "TileLang warmup inputs produced a different compile key: "
163 f"expected {self._warming_key!r}, got kernel arguments "
164 f"{kernel_args!r}"
165 )
166 launch_kwargs = launch_spec[2] if len(launch_spec) > 2 else None
167 output = launch_spec[3] if len(launch_spec) > 3 else None
168 result = self.launch(self.kernel(*kernel_args), launch_args, launch_kwargs)
169 return output if len(launch_spec) > 3 else result
170
171 return wrapper