main
py 845 lines 29.4 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 import ast
4 import inspect
5 from abc import abstractmethod
6 from collections.abc import Callable, Hashable, Iterable, Mapping
7 from concurrent.futures import ThreadPoolExecutor
8 from contextlib import nullcontext, suppress
9 from contextvars import copy_context
10 from dataclasses import dataclass, field
11 from functools import cache, cached_property, update_wrapper
12 from typing import Any, Generic, ParamSpec, Protocol, TypeVar, cast, overload
13
14 from vllm import envs
15 from vllm.model_executor.warmup.jit_warmup import (
16 VllmJitKernel,
17 get_ast_full_name,
18 get_function_source_node,
19 )
20 from vllm.platforms import current_platform
21 from vllm.triton_utils import HAS_TRITON, triton
22
23 CompileKeyT = TypeVar("CompileKeyT")
24 P = ParamSpec("P")
25 # ``kernel_launcher`` also supports a ``None`` grid when no launch is needed.
26 LaunchSpec = (
27 tuple[tuple[int, ...] | None, dict[str, Any]]
28 | tuple[tuple[int, ...] | None, dict[str, Any], Any]
29 )
30 # Dispatchers select a concrete launch configuration for a Triton kernel.
31 DispatchSpec = (
32 tuple[tuple[int, ...], dict[str, Any]] | tuple[tuple[int, ...], dict[str, Any], Any]
33 )
34
35
36 @dataclass(frozen=True)
37 class _LaunchBindingPlan:
38 input_targets: tuple[tuple[int, str], ...]
39 stride_targets: tuple[tuple[str, str, int], ...]
40
41
42 @cache
43 def _launch_binding_plan(
44 arg_names: tuple[str, ...],
45 input_names: tuple[str, ...],
46 launch_names: tuple[str, ...],
47 ) -> _LaunchBindingPlan:
48 arg_name_set = set(arg_names)
49 launch_name_set = set(launch_names)
50
51 def kernel_arg(input_name: str) -> str | None:
52 pointer_name = f"{input_name}_ptr"
53 candidates = (
54 input_name,
55 pointer_name,
56 input_name.upper(),
57 pointer_name.upper(),
58 )
59 return next(
60 (candidate for candidate in candidates if candidate in arg_name_set),
61 None,
62 )
63
64 input_targets = tuple(
65 (index, target)
66 for index, name in enumerate(input_names)
67 if (target := kernel_arg(name)) is not None and target not in launch_name_set
68 )
69 bound_names = launch_name_set | {target for _, target in input_targets}
70 stride_targets = []
71 for target in arg_names:
72 if target in bound_names:
73 continue
74 name, separator, dim = target.rpartition("_stride")
75 if not separator:
76 name, separator, dim = target.rpartition("_STRIDE")
77 dim = dim.removeprefix("_")
78 if separator and (not dim or dim.isdigit()):
79 pointer = f"{name}_PTR" if separator == "_STRIDE" else f"{name}_ptr"
80 source = name if name in bound_names else pointer
81 if source in bound_names:
82 stride_targets.append((target, source, int(dim or 0)))
83 return _LaunchBindingPlan(input_targets, tuple(stride_targets))
84
85
86 def triton_warmup_inputs(
87 kernel: Any,
88 *args: Any,
89 grid: tuple[int, ...],
90 pointer_dtypes: (
91 Mapping[Any, Iterable[str]] | Iterable[tuple[Any, Iterable[str]]] | None
92 ) = None,
93 **kwargs: Any,
94 ) -> dict[str, Any]:
95 """Build launcher inputs from Triton's native positional argument order."""
96 arg_names = tuple(kernel.arg_names)
97 if len(args) > len(arg_names):
98 raise ValueError(
99 f"Received {len(args)} positional inputs for {len(arg_names)} "
100 "Triton kernel arguments"
101 )
102 inputs = dict(zip(arg_names, args))
103 duplicate_names = inputs.keys() & kwargs.keys()
104 if duplicate_names:
105 raise ValueError(
106 f"Triton inputs passed twice: {', '.join(sorted(duplicate_names))}"
107 )
108 inputs.update(kwargs)
109 if pointer_dtypes is not None:
110 function_def = get_function_source_node(kernel)
111 if not isinstance(function_def, ast.FunctionDef):
112 raise ValueError("Expected Triton kernel to be defined as a function")
113 pointer_names = _pointer_arg_names(function_def, arg_names)
114 groups = (
115 pointer_dtypes.items()
116 if isinstance(pointer_dtypes, Mapping)
117 else pointer_dtypes
118 )
119 for dtype, names in groups:
120 for name in names:
121 if name not in arg_names:
122 raise ValueError(f"Unknown Triton pointer argument: {name}")
123 if name not in pointer_names:
124 raise ValueError(f"Triton argument is not a pointer: {name}")
125 if name in inputs:
126 raise ValueError(f"Triton input passed twice: {name}")
127 inputs[name] = TritonWarmupTensor(dtype)
128 missing_pointers = pointer_names - inputs.keys()
129 if missing_pointers:
130 names = ", ".join(sorted(missing_pointers))
131 raise ValueError(f"Missing Triton pointer inputs: {names}")
132 return {"grid": grid, **inputs}
133
134
135 def triton_scalar_specialization_rep(value: int) -> int:
136 """Return an integer with the same default Triton JIT specialization.
137
138 For an ordinary integer argument, Triton's cache key contains its inferred
139 type (``i32``, ``i64``, or ``u64``) and one of three value classes:
140
141 * ``1`` is specialized as the exact constant ``1``.
142 * Multiples of 16 receive a ``tt.divisibility = 16`` attribute.
143 * All other values have no value specialization.
144
145 Warmup only needs one concrete value for each cache-key class. This helper
146 returns ``1`` for the exact-one class and otherwise returns a divisible or
147 generic representative while preserving the inferred integer type.
148
149 This applies only to non-``constexpr`` integer arguments using Triton's
150 default specialization. Do not use it for arguments listed in
151 ``do_not_specialize`` or ``do_not_specialize_on_alignment``.
152 """
153 if value == 1:
154 return 1
155
156 if -(1 << 31) <= value < (1 << 31):
157 divisible_rep = 16
158 generic_rep = 2
159 elif -(1 << 63) <= value < (1 << 63):
160 divisible_rep = 1 << 31
161 generic_rep = (1 << 31) + 1
162 elif 0 <= value < (1 << 64):
163 divisible_rep = 1 << 63
164 generic_rep = (1 << 63) + 1
165 else:
166 raise OverflowError(f"Integer {value} is outside Triton's scalar range")
167
168 return divisible_rep if value % 16 == 0 else generic_rep
169
170
171 @dataclass(frozen=True)
172 class TritonWarmupTensor:
173 """Compile-only tensor metadata used by Triton warmup.
174
175 ``strides=None`` represents compact row-major storage. Pass explicit strides
176 whenever the runtime tensor can be padded, transposed, or otherwise strided.
177 """
178
179 dtype: Any
180 aligned: bool = True
181 shape: tuple[int, ...] = (1,)
182 strides: tuple[int, ...] | None = None
183 init: Any = 0
184
185 def data_ptr(self) -> int:
186 return 0 if self.aligned else 1
187
188 def ptr_range(self) -> int:
189 return 0
190
191 @property
192 def device(self) -> Any:
193 import torch
194
195 return torch.device(current_platform.device_type)
196
197 def stride(self, dim: int | None = None) -> int | tuple[int, ...]:
198 if self.strides is None:
199 strides: list[int] = []
200 stride = 1
201 for size in reversed(self.shape):
202 strides.append(stride)
203 stride *= size
204 result = tuple(reversed(strides))
205 else:
206 result = self.strides
207 return result if dim is None else result[dim]
208
209
210 class VllmTritonJitKernel(VllmJitKernel[CompileKeyT], Generic[CompileKeyT]):
211 """Triton owner whose runtime launch specification is reused for warmup."""
212
213 kernel: Any
214 _warming = False
215 _warming_compile_key: CompileKeyT | None = None
216 _run_autotune = False
217
218 @abstractmethod
219 def warmup_inputs(self, compile_key: CompileKeyT) -> dict[str, Any]:
220 """Return runtime-shaped inputs that reproduce one compile key."""
221 raise NotImplementedError
222
223 def compile(self, compile_key: CompileKeyT) -> None:
224 inputs = self.warmup_inputs(compile_key)
225 self._warming = True
226 self._warming_compile_key = compile_key
227 try:
228 cast(Callable[..., None], self)(**inputs)
229 finally:
230 self._warming = False
231 self._warming_compile_key = None
232
233 def compile_many(self, compile_keys: Iterable[CompileKeyT]) -> None:
234 """Compile CUDA startup warmup variants in parallel, then wait."""
235 keys = list(compile_keys)
236 num_threads = min(envs.VLLM_TRITON_JIT_WARMUP_NUM_THREADS, len(keys))
237 async_compile = getattr(triton, "AsyncCompileMode", None)
238 # AMD LLVM code generation can abort under concurrent compilation.
239 if (
240 not current_platform.is_cuda()
241 or self._run_autotune
242 or async_compile is None
243 or num_threads <= 1
244 ):
245 return super().compile_many(keys)
246
247 def compile_all() -> None:
248 with (
249 ThreadPoolExecutor(max_workers=num_threads) as executor,
250 async_compile(executor),
251 ):
252 # Dispatch stays on this thread: compile() mutates owner state.
253 # Only Triton's underlying compiler work runs in the pool.
254 super(VllmTritonJitKernel, self).compile_many(keys)
255
256 # Older Triton versions leave async mode active on compilation failure.
257 # Isolate that state so it cannot affect later runtime JIT compilation.
258 copy_context().run(compile_all)
259
260 @cached_property
261 def _kernel_arg_names(self) -> tuple[str, ...]:
262 arg_names = getattr(self.kernel, "arg_names", None)
263 if arg_names is not None:
264 return tuple(arg_names)
265 wrapped = getattr(self.kernel, "func", None)
266 if wrapped is not None:
267 return tuple(inspect.signature(wrapped).parameters)
268 raise TypeError(
269 f"Cannot inspect kernel parameters for {type(self.kernel).__name__}"
270 )
271
272 def _prepare_launch_kwargs(
273 self,
274 input_names: tuple[str, ...],
275 input_values: tuple[Any, ...],
276 launch_kwargs: dict[str, Any],
277 ) -> dict[str, Any]:
278 plan = _launch_binding_plan(
279 self._kernel_arg_names, input_names, tuple(launch_kwargs)
280 )
281 for index, target in plan.input_targets:
282 launch_kwargs[target] = input_values[index]
283 for target, source, dim in plan.stride_targets:
284 launch_kwargs[target] = launch_kwargs[source].stride(dim)
285 return launch_kwargs
286
287 def launch(
288 self,
289 launch_spec: LaunchSpec,
290 inputs: Mapping[str, Any],
291 ) -> Any:
292 if len(launch_spec) == 3:
293 grid, launch_kwargs, outputs = launch_spec
294 else:
295 grid, launch_kwargs = launch_spec
296 outputs = None
297 kwargs = self._prepare_launch_kwargs(
298 tuple(inputs), tuple(inputs.values()), dict(launch_kwargs)
299 )
300 kernel = kwargs.pop("kernel", None) or self.kernel
301 runtime_launcher = kwargs.pop("_runtime_launcher", None)
302 runtime_launcher_arg_count = kwargs.pop("_runtime_launcher_arg_count", 0)
303 if self._warming:
304 if self._run_autotune:
305 assert grid is not None
306 kernel[grid](**kwargs)
307 return outputs
308 kwargs = {
309 name: _triton_metadata_arg(value) for name, value in kwargs.items()
310 }
311 if (
312 self._warming_compile_key is not None
313 and "launch_pdl" in kwargs
314 and hasattr(self._warming_compile_key, "launch_pdl")
315 ):
316 kwargs["launch_pdl"] = self._warming_compile_key.launch_pdl
317 warmup = getattr(kernel, "warmup", None)
318 assert warmup is not None
319 warmup(grid=(1,), **kwargs)
320 return outputs
321 if grid is None:
322 return outputs
323 if runtime_launcher is not None:
324 regular_args = [
325 kwargs.pop(name)
326 for name in self._kernel_arg_names[:runtime_launcher_arg_count]
327 ]
328 runtime_launcher(kernel, grid, *regular_args, **kwargs)
329 else:
330 kernel[grid](**kwargs)
331 return outputs
332
333
334 @dataclass(frozen=True)
335 class TritonJitKey:
336 """Process-local identity of one Triton JIT specialization."""
337
338 jit_function_id: int
339 jit_function_key: Hashable
340 device: Hashable
341 cache_key: Hashable
342
343
344 @dataclass(frozen=True)
345 class TritonCompileKey:
346 """Triton-derived compile key with one non-comparing replay input."""
347
348 jit_keys: frozenset[TritonJitKey]
349 inputs: tuple[tuple[str, Any], ...] = field(compare=False, hash=False, repr=False)
350
351
352 def _triton_key_deriver(
353 kernel: Any,
354 ) -> Callable[[Mapping[str, Any]], set[TritonJitKey]]:
355 """Prepare Triton's key derivation once for one kernel and device."""
356 knobs = triton.knobs
357 Autotuner = triton.runtime.autotuner.Autotuner
358 Heuristics = triton.runtime.autotuner.Heuristics
359 driver = triton.runtime.driver
360 JITFunction = triton.runtime.jit.JITFunction
361 compute_cache_key = triton.runtime.jit.compute_cache_key
362
363 if isinstance(kernel, Heuristics):
364 derive_inner = _triton_key_deriver(kernel.fn)
365
366 def derive_heuristic(kwargs: Mapping[str, Any]) -> set[TritonJitKey]:
367 heuristic_kwargs = dict(kwargs)
368 for name, heuristic in kernel.values.items():
369 heuristic_kwargs[name] = heuristic(heuristic_kwargs)
370 return derive_inner(heuristic_kwargs)
371
372 return derive_heuristic
373
374 if isinstance(kernel, Autotuner):
375 derive_inner = _triton_key_deriver(kernel.fn)
376
377 def derive_autotuned(kwargs: Mapping[str, Any]) -> set[TritonJitKey]:
378 previous_nargs = getattr(kernel, "nargs", None)
379 kernel.nargs = {}
380 try:
381 configs = kernel.prune_configs(dict(kwargs))
382 finally:
383 kernel.nargs = previous_nargs
384 keys: set[TritonJitKey] = set()
385 for config in configs:
386 conflicts = kwargs.keys() & config.kwargs.keys()
387 if conflicts:
388 names = ", ".join(sorted(conflicts))
389 raise ValueError(f"Conflicting autotune parameters: {names}")
390 keys.update(derive_inner(dict(kwargs) | config.all_kwargs()))
391 return keys
392
393 return derive_autotuned
394
395 if not isinstance(kernel, JITFunction):
396 raise TypeError(f"Unsupported Triton kernel wrapper: {type(kernel).__name__}")
397
398 device = cast(Hashable, driver.active.get_current_device())
399 _, kernel_key_cache, _, _, binder = kernel.device_caches[device]
400 jit_function_id = id(kernel)
401 jit_function_key = cast(Hashable, kernel.cache_key)
402 debug = kernel.debug or knobs.runtime.debug
403 instrumentation_mode = knobs.compilation.instrumentation_mode
404 fpsan_homomorphic_casts = getattr(
405 knobs.compilation, "fpsan_homomorphic_casts", None
406 )
407 inspection_hook = knobs.runtime.add_stages_inspection_hook
408
409 def derive_jit(kwargs: Mapping[str, Any]) -> set[TritonJitKey]:
410 binder_kwargs = dict(kwargs)
411 binder_kwargs["debug"] = binder_kwargs.get("debug", debug) or debug
412 binder_kwargs["instrumentation_mode"] = instrumentation_mode
413 if fpsan_homomorphic_casts is not None:
414 binder_kwargs["fpsan_homomorphic_casts"] = fpsan_homomorphic_casts
415 _, specialization, options = binder(**binder_kwargs)
416 if inspection_hook is not None:
417 _, inspection_hash = inspection_hook()
418 specialization.append(f'("custom_pipeline", {inspection_hash})')
419 cache_key = cast(
420 Hashable,
421 compute_cache_key(kernel_key_cache, specialization, options),
422 )
423 return {
424 TritonJitKey(
425 jit_function_id,
426 jit_function_key,
427 device,
428 cache_key,
429 )
430 }
431
432 return derive_jit
433
434
435 WarmupCases = Mapping[str, Any] | Iterable[Mapping[str, Any]]
436
437
438 class _AutomaticTritonJitKernel(VllmTritonJitKernel[TritonCompileKey]):
439 def warmup_inputs(self, compile_key: TritonCompileKey) -> dict[str, Any]:
440 return dict(compile_key.inputs)
441
442 def _provider_cases(
443 self,
444 provider: Callable[..., WarmupCases],
445 *args: Any,
446 **kwargs: Any,
447 ) -> Iterable[Mapping[str, Any]]:
448 cases_node = get_function_source_node(provider)
449 if any(
450 isinstance(node, ast.Call)
451 and (get_ast_full_name(node.func) or "").split(".")[-1]
452 in {"WarmupIntRange", "WarmupChoices", "_when"}
453 for node in ast.walk(cases_node)
454 ):
455 return self._expand_warmup_cases(provider, *args, **kwargs)
456 cases = provider(*args, **kwargs)
457 return (cases,) if isinstance(cases, Mapping) else cases
458
459
460 def _is_autotuned(kernel: Any) -> bool:
461 if not HAS_TRITON:
462 return False
463 Autotuner = triton.runtime.autotuner.Autotuner
464
465 while kernel is not None:
466 if isinstance(kernel, Autotuner):
467 return True
468 kernel = getattr(kernel, "fn", None)
469 return False
470
471
472 def _triton_metadata_arg(value: Any) -> Any:
473 from torch._subclasses.fake_tensor import FakeTensor
474
475 if not isinstance(value, FakeTensor):
476 return value
477 return TritonWarmupTensor(
478 value.dtype,
479 aligned=getattr(value, "_vllm_warmup_aligned", True),
480 shape=tuple(value.shape),
481 strides=tuple(value.stride()),
482 )
483
484
485 def _materialize_warmup_case(
486 case: Mapping[str, Any],
487 *,
488 real: bool,
489 mode: Any,
490 cache: dict[TritonWarmupTensor, Any],
491 ) -> dict[str, Any]:
492 import torch
493
494 def materialize(value: Any) -> Any:
495 if not isinstance(value, TritonWarmupTensor):
496 return value
497 if not real:
498 try:
499 return cache[value]
500 except KeyError:
501 pass
502 except TypeError:
503 # Unhashable initializers are uncommon and safe to materialize directly.
504 pass
505
506 strides = cast(tuple[int, ...], value.stride())
507 with mode:
508 tensor = torch.empty_strided(
509 value.shape,
510 strides,
511 dtype=value.dtype,
512 device=value.device,
513 )
514 if real:
515 if callable(value.init):
516 value.init(tensor)
517 else:
518 tensor.fill_(value.init)
519 else:
520 tensor._vllm_warmup_aligned = value.aligned
521 with suppress(TypeError):
522 cache[value] = tensor
523 return tensor
524
525 return {name: materialize(value) for name, value in case.items()}
526
527
528 class _DecoratedTritonJitKernel(_AutomaticTritonJitKernel):
529 def __init__(
530 self,
531 kernel: Any,
532 warmup_inputs: Callable[..., WarmupCases],
533 dispatch: Callable[..., DispatchSpec] | None,
534 ) -> None:
535 self.kernel = kernel
536 self._run_autotune = _is_autotuned(kernel)
537 self._warmup_inputs_fn = warmup_inputs
538 self._dispatch_fn = dispatch
539 self._dispatch_arg_names: tuple[str, ...] = ()
540 self._dispatch_defaults: dict[str, Any] = {}
541 if dispatch is not None:
542 parameters = tuple(inspect.signature(dispatch).parameters.values())
543 if any(
544 parameter.kind
545 in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD)
546 for parameter in parameters
547 ):
548 raise TypeError("Triton dispatchers require explicit parameters")
549 self._dispatch_arg_names = tuple(parameter.name for parameter in parameters)
550 self._dispatch_defaults = {
551 parameter.name: parameter.default
552 for parameter in parameters
553 if parameter.default is not inspect.Parameter.empty
554 }
555 super().__init__()
556
557 def __call__(self, *args: Any, **kwargs: Any) -> Any:
558 if self._dispatch_fn is None:
559 grid = kwargs.pop("grid") if not args else args[0]
560 kernel_args = () if not args else args[1:]
561 inputs = triton_warmup_inputs(
562 self.kernel, *kernel_args, grid=grid, **kwargs
563 )
564 inputs.pop("grid")
565 return self.launch((grid, inputs), {})
566 spec = self._dispatch_fn(*args, **kwargs)
567 input_values = args
568 if len(args) != len(self._dispatch_arg_names):
569 input_values += tuple(
570 kwargs[name] if name in kwargs else self._dispatch_defaults[name]
571 for name in self._dispatch_arg_names[len(args) :]
572 )
573 return self.launch(
574 spec,
575 dict(zip(self._dispatch_arg_names, input_values)),
576 )
577
578 def __getitem__(self, grid: tuple[int, ...]) -> Callable[..., Any]:
579 return cast(Callable[..., Any], self.kernel[grid])
580
581 def __getattr__(self, name: str) -> Any:
582 return getattr(self.kernel, name)
583
584 def get_warmup_keys(
585 self, *, vllm_config: Any | None = None, **kwargs: Any
586 ) -> list[TritonCompileKey]:
587 keys: list[TritonCompileKey] = []
588 seen_jit_keys: set[frozenset[TritonJitKey]] = set()
589 if "vllm_config" in inspect.signature(self._warmup_inputs_fn).parameters:
590 kwargs.setdefault("vllm_config", vllm_config)
591 cases = self._provider_cases(self._warmup_inputs_fn, **kwargs)
592 derive_keys = _triton_key_deriver(self.kernel)
593 materialize = self._run_autotune
594 mode: Any = nullcontext() if self._run_autotune else None
595 materialized: dict[TritonWarmupTensor, Any] = {}
596 for case in cases:
597 inputs = (
598 _materialize_warmup_case(
599 case,
600 real=self._run_autotune,
601 mode=mode,
602 cache=materialized,
603 )
604 if materialize
605 else case
606 )
607 if self._dispatch_fn is None:
608 prepared = dict(inputs)
609 prepared.pop("grid")
610 else:
611 dispatch_inputs = {
612 name: inputs[name]
613 for name in self._dispatch_arg_names
614 if name in inputs
615 }
616 try:
617 _, launch_kwargs = self._dispatch_fn(**dispatch_inputs)[:2]
618 except (AssertionError, AttributeError, TypeError):
619 if materialize:
620 raise
621 from torch._subclasses.fake_tensor import FakeTensorMode
622
623 materialize = True
624 mode = FakeTensorMode()
625 inputs = _materialize_warmup_case(
626 case,
627 real=False,
628 mode=mode,
629 cache=materialized,
630 )
631 dispatch_inputs = {
632 name: inputs[name]
633 for name in self._dispatch_arg_names
634 if name in inputs
635 }
636 _, launch_kwargs = self._dispatch_fn(**dispatch_inputs)[:2]
637 prepared = self._prepare_launch_kwargs(
638 tuple(inputs),
639 tuple(inputs.values()),
640 dict(launch_kwargs),
641 )
642 if materialize:
643 prepared = {
644 name: _triton_metadata_arg(value)
645 for name, value in prepared.items()
646 }
647 jit_keys = frozenset(derive_keys(prepared))
648 if jit_keys and jit_keys not in seen_jit_keys:
649 seen_jit_keys.add(jit_keys)
650 keys.append(TritonCompileKey(jit_keys, tuple(inputs.items())))
651 return keys
652
653
654 class TritonKernelDispatcher(Protocol[P]):
655 """Callable Triton launcher with compile-time warmup support."""
656
657 def __call__(self, *args: P.args, **kwargs: P.kwargs) -> Any: ...
658
659 def register_warmup(self, *args: Any, **kwargs: Any) -> None: ...
660
661
662 @overload
663 def triton_kernel_dispatcher_with_warmup(
664 *,
665 warmup_inputs: Callable[..., WarmupCases],
666 kernel: None = None,
667 ) -> Callable[[Any], Any]: ...
668
669
670 @overload
671 def triton_kernel_dispatcher_with_warmup(
672 *,
673 warmup_inputs: Callable[..., WarmupCases],
674 kernel: Any,
675 ) -> Callable[[Callable[P, DispatchSpec]], TritonKernelDispatcher[P]]: ...
676
677
678 def triton_kernel_dispatcher_with_warmup(
679 *,
680 warmup_inputs: Callable[..., WarmupCases],
681 kernel: Any | None = None,
682 ) -> Callable[[Any], Any]:
683 """Decorate a dispatch function or a native Triton kernel for warmup."""
684
685 def decorate(dispatch: Any) -> Any:
686 owner = _DecoratedTritonJitKernel(
687 dispatch if kernel is None else kernel,
688 warmup_inputs,
689 None if kernel is None else dispatch,
690 )
691 update_wrapper(owner, dispatch, updated=())
692 return owner
693
694 return decorate
695
696
697 @dataclass(frozen=True)
698 class TritonPointerInputVariant:
699 # Named pointer-alignment variant for compile-only Triton warmup.
700 alignments: tuple[tuple[str, bool], ...]
701
702 @classmethod
703 def from_alignment(cls, **aligned: bool) -> "TritonPointerInputVariant":
704 return cls(tuple(aligned.items()))
705
706 def is_aligned(self, name: str) -> bool:
707 for alignment_name, aligned in self.alignments:
708 if alignment_name == name:
709 return aligned
710 raise KeyError(f"Unknown Triton pointer input variant: {name}")
711
712 def pointer(
713 self,
714 name: str,
715 dtype: Any,
716 shape: tuple[int, ...] = (1,),
717 ) -> TritonWarmupTensor:
718 return TritonWarmupTensor(dtype, aligned=self.is_aligned(name), shape=shape)
719
720
721 def _literal_str_refs(node: ast.AST) -> tuple[str | int, ...]:
722 if isinstance(node, ast.Constant) and isinstance(node.value, str | int):
723 return (node.value,)
724 if isinstance(node, ast.List | ast.Tuple):
725 refs: list[str | int] = []
726 for elt in node.elts:
727 if isinstance(elt, ast.Constant) and isinstance(elt.value, str | int):
728 refs.append(elt.value)
729 else:
730 raise ValueError(
731 f"Unsupported Triton specialization ref: {ast.dump(elt)}"
732 )
733 return tuple(refs)
734 raise ValueError(f"Unsupported Triton specialization refs: {ast.dump(node)}")
735
736
737 def _normalize_arg_refs(
738 refs: tuple[str | int, ...],
739 arg_names: tuple[str, ...],
740 ) -> frozenset[str]:
741 names: set[str] = set()
742 for ref in refs:
743 if isinstance(ref, int):
744 names.add(arg_names[ref])
745 else:
746 names.add(ref)
747 return frozenset(names)
748
749
750 def _decorator_keyword_refs(
751 function_def: ast.FunctionDef,
752 keyword_name: str,
753 ) -> tuple[str | int, ...]:
754 for decorator in function_def.decorator_list:
755 if not isinstance(decorator, ast.Call):
756 continue
757 decorator_name = get_ast_full_name(decorator.func)
758 if decorator_name not in ("triton.jit", "jit"):
759 continue
760 for keyword in decorator.keywords:
761 if keyword.arg == keyword_name:
762 return _literal_str_refs(keyword.value)
763 return ()
764
765
766 def _triton_do_not_specialize_args(
767 kernel: Callable[..., Any],
768 function_def: ast.FunctionDef,
769 arg_names: tuple[str, ...],
770 ) -> frozenset[str]:
771 refs = getattr(kernel, "do_not_specialize", None)
772 if refs is not None:
773 return _normalize_arg_refs(tuple(refs), arg_names)
774 return _normalize_arg_refs(
775 _decorator_keyword_refs(function_def, "do_not_specialize"),
776 arg_names,
777 )
778
779
780 def _triton_constexpr_arg_names(
781 kernel: Callable[..., Any],
782 function_def: ast.FunctionDef,
783 arg_names: tuple[str, ...],
784 ) -> frozenset[str]:
785 constexprs = getattr(kernel, "constexprs", None)
786 if constexprs is not None:
787 return frozenset(arg_names[index] for index in constexprs)
788
789 names: set[str] = set()
790 for arg in function_def.args.args + function_def.args.kwonlyargs:
791 if arg.annotation is None:
792 continue
793 annotation = get_ast_full_name(arg.annotation)
794 if annotation in ("tl.constexpr", "triton.language.constexpr", "constexpr"):
795 names.add(arg.arg)
796 return frozenset(names)
797
798
799 def _leftmost_name(node: ast.AST) -> str | None:
800 if isinstance(node, ast.Name):
801 return node.id
802 if isinstance(node, ast.BinOp):
803 return _leftmost_name(node.left)
804 return None
805
806
807 def _pointer_arg_names(
808 function_def: ast.FunctionDef,
809 arg_names: tuple[str, ...],
810 ) -> frozenset[str]:
811 candidate_names = set(arg_names)
812 pointer_names = {name for name in arg_names if name.endswith("_ptr")}
813 for node in ast.walk(function_def):
814 if not isinstance(node, ast.Call):
815 continue
816 if get_ast_full_name(node.func) not in ("tl.load", "tl.store"):
817 continue
818 if not node.args:
819 continue
820 name = _leftmost_name(node.args[0])
821 if name in candidate_names:
822 pointer_names.add(name)
823 return frozenset(pointer_names)
824
825
826 def trace_triton_kernel_specialization_args(
827 kernel: Callable[..., Any],
828 ) -> tuple[str, ...]:
829 function_def = get_function_source_node(kernel)
830 if not isinstance(function_def, ast.FunctionDef):
831 raise ValueError("Expected Triton kernel to be defined as a function")
832 source_fn = getattr(kernel, "fn", kernel)
833 arg_names = tuple(inspect.signature(source_fn).parameters)
834 constexpr_args = _triton_constexpr_arg_names(kernel, function_def, arg_names)
835 do_not_specialize_args = _triton_do_not_specialize_args(
836 kernel, function_def, arg_names
837 )
838 pointer_args = _pointer_arg_names(function_def, arg_names)
839
840 return tuple(
841 name
842 for name in arg_names
843 if name in constexpr_args
844 or (name not in pointer_args and name not in do_not_specialize_args)
845 )