[Feature] Triton kernel dispatcher (#43048)
Signed-off-by: wangxiyuan <wangxiyuan1007@gmail.com>
wangxiyuan committed
Sep 25, 2026 at 12:07 UTC
44257c2e358c4dbd648f99273a21a3f496f23148
8 files changed
+482
-98
tests/kernels/mamba/cpu/test_cpu_gdn_ops.py
+12
-1
@@ -1071,7 +1071,18 @@ def test_batch_memcpy_cpu_fallback() -> None:
1071
copy each src into its dst, validating the (src_ptrs, dst_ptrs, sizes)
1072
argument order against ctypes.memmove(dst, src, size).
1073
"""
1074
- from vllm.utils.cpu_triton_utils import batch_memcpy_kernel
1074
+ from vllm.triton_utils.dispatcher import (
1075
+ KernelOverride,
1076
+ register_kernels,
1077
+ )
1078
+ from vllm.utils.cpu_triton_utils import _batch_memcpy_impl
1079
+
1080
+ register_kernels(
1081
+ {"vllm.v1.worker.mamba_utils.batch_memcpy_kernel": _batch_memcpy_impl}
1082
+ )
1083
+ from vllm.v1.worker.mamba_utils import batch_memcpy_kernel
1084
+
1085
+ assert isinstance(batch_memcpy_kernel, KernelOverride)
1086
1087
# Varied byte sizes, including a non-power-of-two run.
1088
sizes_bytes = [256, 1024, 17 * 4, 4096]
tests/model_executor/test_triton_dispatcher.py
new
+222
@@ -0,0 +1,222 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+"""Tests for the apply-time Triton kernel override registry.
4
+
5
+The registry must let out-of-tree platforms replace kernels defined as
6
+plain ``@triton.jit`` functions, patching every hold-site (defining
7
+module, from-import copies, and JIT warmup owners) so that existing call
8
+sites dispatch to the override without any core-side decoration.
9
+"""
10
+
11
+import sys
12
+import types
13
+from typing import Any
14
+
15
+import pytest
16
+
17
+from vllm.model_executor.warmup.jit_warmup_triton_helper import (
18
+ _DecoratedTritonJitKernel,
19
+)
20
+from vllm.triton_utils import tl, triton
21
+from vllm.triton_utils.dispatcher import (
22
+ KernelOverride,
23
+ register_kernels,
24
+)
25
+
26
+
27
+class _FakeTritonKernel:
28
+ """Stands in for a Triton JITFunction (works without Triton installed)."""
29
+
30
+ arg_names = ("output_ptr", "input_ptr", "replace_from", "MAX_NUM_TOKENS")
31
+
32
+ def __getitem__(self, grid: Any) -> Any:
33
+ def launch(*args: Any, **kwargs: Any) -> Any:
34
+ return ("default", grid, kwargs)
35
+
36
+ return launch
37
+
38
+
39
+@pytest.fixture
40
+def _fake_module(monkeypatch: pytest.MonkeyPatch) -> Any:
41
+ """Expose a fake defining module holding the kernel and its warmup owner."""
42
+ import types
43
+
44
+ kernel = _FakeTritonKernel()
45
+ owner = _DecoratedTritonJitKernel(
46
+ kernel,
47
+ lambda: {},
48
+ lambda output, input, replace_from: ( # noqa: ARG005
49
+ (1,),
50
+ dict(MAX_NUM_TOKENS=8),
51
+ ),
52
+ )
53
+ module = types.ModuleType("vllm.tests.fake_kernel_home")
54
+ module.expand_kernel = kernel # type: ignore[attr-defined]
55
+ module._expand = owner # type: ignore[attr-defined]
56
+ monkeypatch.setitem(sys.modules, module.__name__, module)
57
+ return module
58
+
59
+
60
+def test_override_patches_defining_module_and_warmup_owner(
61
+ _fake_module: Any,
62
+) -> None:
63
+ calls: list[tuple[Any, ...]] = []
64
+
65
+ def cpu_impl(output_ptr, input_ptr, replace_from, MAX_NUM_TOKENS=None):
66
+ calls.append((output_ptr, input_ptr, replace_from, MAX_NUM_TOKENS))
67
+ return "cpu"
68
+
69
+ register_kernels(
70
+ {
71
+ "vllm.tests.fake_kernel_home.expand_kernel": cpu_impl,
72
+ }
73
+ )
74
+
75
+ overridden = _fake_module.expand_kernel
76
+ assert isinstance(overridden, KernelOverride)
77
+ # Launch with kernel-arg names like the warmup launch path does.
78
+ assert overridden[(4,)](output_ptr=1, input_ptr=2, replace_from=0) == "cpu"
79
+ assert calls == [(1, 2, 0, None)]
80
+
81
+ # The warmup owner's captured kernel is patched too, and its cached
82
+ # argument-name introspection is invalidated.
83
+ assert _fake_module._expand.kernel is overridden
84
+
85
+
86
+def test_override_forwards_positionally_for_mismatched_names(
87
+ _fake_module: Any,
88
+) -> None:
89
+ calls: list[tuple[Any, ...]] = []
90
+
91
+ def cpu_impl(output, input_val, replace_from, MAX_NUM_TOKENS=None):
92
+ calls.append((output, input_val, replace_from, MAX_NUM_TOKENS))
93
+ return "cpu"
94
+
95
+ register_kernels(
96
+ {
97
+ "vllm.tests.fake_kernel_home.expand_kernel": cpu_impl,
98
+ }
99
+ )
100
+ overridden = _fake_module.expand_kernel
101
+ assert overridden[(4,)](output_ptr=1, input_ptr=2, replace_from=0) == "cpu"
102
+ assert calls == [(1, 2, 0, None)]
103
+
104
+
105
+def test_override_covers_from_import_copies(_fake_module: Any) -> None:
106
+ import types
107
+
108
+ importer = types.ModuleType("vllm.tests.fake_kernel_importer")
109
+ importer.expand_kernel = _fake_module.expand_kernel # type: ignore[attr-defined]
110
+ sys.modules["vllm.tests.fake_kernel_importer"] = importer
111
+
112
+ def cpu_impl(output_ptr, input_ptr, replace_from, MAX_NUM_TOKENS=None):
113
+ return "cpu"
114
+
115
+ register_kernels(
116
+ {
117
+ "vllm.tests.fake_kernel_home.expand_kernel": cpu_impl,
118
+ }
119
+ )
120
+ assert isinstance(importer.expand_kernel, KernelOverride)
121
+ del sys.modules["vllm.tests.fake_kernel_importer"]
122
+
123
+
124
+def test_override_owner_launch_goes_through_impl(_fake_module: Any) -> None:
125
+ launched: list[Any] = []
126
+
127
+ def cpu_impl(output_ptr, input_ptr, replace_from, MAX_NUM_TOKENS=None):
128
+ launched.append((output_ptr, input_ptr, replace_from, MAX_NUM_TOKENS))
129
+ return "cpu"
130
+
131
+ register_kernels(
132
+ {
133
+ "vllm.tests.fake_kernel_home.expand_kernel": cpu_impl,
134
+ }
135
+ )
136
+ owner = _fake_module._expand
137
+ # In-place kernels return None from launch; assert on the recorded call.
138
+ owner(1, 2, 0)
139
+ assert launched == [(1, 2, 0, 8)]
140
+
141
+
142
+def test_register_kernel_auto_imports_defining_module(
143
+ monkeypatch: pytest.MonkeyPatch,
144
+) -> None:
145
+ """Registering a kernel whose defining module is not imported yet must
146
+ import it on the spot and apply the override."""
147
+ kernel_name = "vllm.v1.sample.rejection_sampler.expand_kernel"
148
+ module_name = kernel_name.rpartition(".")[0]
149
+ calls: list[tuple[Any, ...]] = []
150
+
151
+ def cpu_impl(
152
+ output_ptr,
153
+ input_ptr,
154
+ cu_num_tokens_ptr,
155
+ replace_from,
156
+ replace_to,
157
+ MAX_NUM_TOKENS=None,
158
+ ):
159
+ calls.append((output_ptr, replace_from, MAX_NUM_TOKENS))
160
+ return "cpu"
161
+
162
+ # Ensure the module is NOT imported before registration. Drop it from
163
+ # sys.modules; a fresh import creates new kernel/owner objects, which
164
+ # is what a cold-start platform process would see.
165
+ saved = sys.modules.pop(module_name, None)
166
+ # Re-run the registry patch against the original module afterwards so
167
+ # other tests that already imported it keep working.
168
+ try:
169
+ assert module_name not in sys.modules
170
+ register_kernels({kernel_name: cpu_impl})
171
+ assert module_name in sys.modules
172
+ finally:
173
+ if saved is None:
174
+ # First import in the whole session: keep the fresh module.
175
+ saved = sys.modules[module_name]
176
+ else:
177
+ # Restore the original module object and re-apply the override
178
+ # to its (pre-existing) kernel so both generations agree.
179
+ sys.modules[module_name] = saved
180
+ register_kernels({kernel_name: cpu_impl})
181
+
182
+ overridden = saved.expand_kernel
183
+ assert isinstance(overridden, KernelOverride)
184
+ overridden[(2,)](
185
+ output_ptr=1,
186
+ input_ptr=2,
187
+ cu_num_tokens_ptr=3,
188
+ replace_from=0,
189
+ replace_to=0,
190
+ MAX_NUM_TOKENS=8,
191
+ )
192
+ assert calls == [(1, 0, 8)]
193
+ # The warmup owner captured at the fresh import must be patched too
194
+ # (when the module existed before, there are two module generations).
195
+ fresh = sys.modules[module_name]
196
+ if fresh is not saved:
197
+ assert fresh._expand.kernel is overridden
198
+
199
+
200
+def test_real_triton_kernel_override_end_to_end() -> None:
201
+ """Override a real @triton.jit kernel through the registry."""
202
+ if not triton.__class__.__module__.startswith("triton"):
203
+ pytest.skip("Triton is not installed")
204
+
205
+ @triton.jit
206
+ def _add_one_kernel(x_ptr, out_ptr, BLOCK: tl.constexpr): # pragma: no cover
207
+ offsets = tl.arange(0, BLOCK)
208
+ tl.store(out_ptr + offsets, tl.load(x_ptr + offsets) + 1)
209
+
210
+ module = types.ModuleType("vllm.tests.real_jit_kernel_home")
211
+ module._add_one_kernel = _add_one_kernel # type: ignore[attr-defined]
212
+ sys.modules[module.__name__] = module
213
+
214
+ def cpu_impl(x_ptr, out_ptr, BLOCK=None): # noqa: ARG001
215
+ return "cpu"
216
+
217
+ register_kernels({"vllm.tests.real_jit_kernel_home._add_one_kernel": cpu_impl})
218
+ assert module._add_one_kernel.arg_names == ( # type: ignore[attr-defined]
219
+ "x_ptr",
220
+ "out_ptr",
221
+ "BLOCK",
222
+ )
vllm/platforms/cpu.py
+54
@@ -647,6 +647,60 @@ class CpuPlatform(Platform):
647
except ImportError as e:
648
logger.warning_once("Failed to import from vllm._C: %s", repr(e))
649
650
+ @classmethod
651
+ def register_triton_kernel_overrides(cls) -> None:
652
+ """Fallback C++ implementations for core Triton kernels.
653
+
654
+ Used when the Triton-CPU backend is unavailable. When Triton-CPU is
655
+ present, the Triton kernels can run natively and the overrides
656
+ would only shadow them, so registration is skipped.
657
+ """
658
+ from vllm.triton_utils import HAS_TRITON
659
+
660
+ if HAS_TRITON:
661
+ return
662
+
663
+ from vllm.triton_utils.dispatcher import register_kernels
664
+ from vllm.utils import cpu_triton_utils as cpu_tl
665
+
666
+ register_kernels(
667
+ {
668
+ "vllm.v1.worker.block_table.ComputeSlotMappingKernel.kernel": (
669
+ cpu_tl._compute_slot_mapping_kernel_impl
670
+ ),
671
+ "vllm.v1.spec_decode.utils.eagle_step_slot_mapping_metadata_kernel": (
672
+ cpu_tl._eagle_step_slot_mapping_metadata_kernel_impl
673
+ ),
674
+ "vllm.v1.spec_decode.utils.eagle_prepare_inputs_padded_kernel": (
675
+ cpu_tl._eagle_prepare_inputs_padded_kernel_impl
676
+ ),
677
+ "vllm.v1.spec_decode.utils.eagle_prepare_next_token_padded_kernel": (
678
+ cpu_tl._eagle_prepare_next_token_padded_kernel_impl
679
+ ),
680
+ "vllm.v1.spec_decode.utils.copy_and_expand_eagle_inputs_kernel": (
681
+ cpu_tl._copy_and_expand_eagle_inputs_kernel_impl
682
+ ),
683
+ "vllm.v1.spec_decode.utils.copy_and_expand_dflash_inputs_kernel": (
684
+ cpu_tl._copy_and_expand_dflash_inputs_kernel_impl
685
+ ),
686
+ "vllm.v1.sample.rejection_sampler.rejection_greedy_sample_kernel": (
687
+ cpu_tl._rejection_greedy_sample_kernel_impl
688
+ ),
689
+ "vllm.v1.sample.rejection_sampler.rejection_random_sample_kernel": (
690
+ cpu_tl._rejection_random_sample_kernel_impl
691
+ ),
692
+ "vllm.v1.sample.rejection_sampler.expand_kernel": (
693
+ cpu_tl._expand_kernel_impl
694
+ ),
695
+ "vllm.v1.sample.rejection_sampler.sample_recovered_tokens_kernel": (
696
+ cpu_tl._sample_recovered_tokens_kernel_impl
697
+ ),
698
+ "vllm.v1.worker.mamba_utils.batch_memcpy_kernel": (
699
+ cpu_tl._batch_memcpy_impl
700
+ ),
701
+ }
702
+ )
703
+
704
@classmethod
705
def pack_kv_cache(
706
cls,
vllm/platforms/interface.py
+13
@@ -366,6 +366,19 @@ class Platform:
366
with contextlib.suppress(ImportError):
367
import vllm._moe_C_stable_libtorch # noqa: F401
368
369
+ @classmethod
370
+ def register_triton_kernel_overrides(cls) -> None:
371
+ """Override core Triton kernels with platform implementations.
372
+
373
+ Platforms whose Triton kernels need replacement implementations
374
+ (e.g. CPU fallbacks when the Triton-CPU backend is unavailable)
375
+ override this method and call
376
+ :func:`vllm.triton_utils.dispatcher.register_kernels`
377
+ with their overrides. It runs during model runner init, before any
378
+ kernel launch. The default implementation does nothing.
379
+ """
380
+ return
381
+
382
@classmethod
383
def get_attn_backend_cls(
384
cls,
vllm/triton_utils/dispatcher.py
new
+175
@@ -0,0 +1,175 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+"""Registry for overriding Triton kernels with platform implementations.
4
+
5
+Non-CUDA platforms can replace Triton kernels defined in vLLM core
6
+without modifying core code:
7
+
8
+ from vllm.triton_utils.dispatcher import register_kernels
9
+
10
+ register_kernels({
11
+ "vllm.v1.sample.rejection_sampler.expand_kernel": my_expand_impl,
12
+ "vllm.v1.worker.mamba_utils.batch_memcpy_kernel": my_memcpy_impl,
13
+ })
14
+"""
15
+
16
+import importlib
17
+import inspect
18
+import sys
19
+from collections.abc import Callable, Mapping
20
+from typing import Any
21
+
22
+from vllm.logger import init_logger
23
+
24
+logger = init_logger(__name__)
25
+
26
+_registry: dict[str, tuple[Callable[..., Any], Callable[..., Any]]] = {}
27
+
28
+
29
+def _kernel_arg_names(kernel: Callable[..., Any]) -> tuple[str, ...]:
30
+ """Return the argument names of a Triton kernel or its fallback."""
31
+ names = getattr(kernel, "arg_names", None)
32
+ if names is None:
33
+ # No real Triton (TritonPlaceholder): read the plain function.
34
+ fn = getattr(kernel, "func", kernel)
35
+ names = tuple(inspect.signature(fn).parameters)
36
+ return tuple(names)
37
+
38
+
39
+class KernelOverride:
40
+ """Stands in for a Triton kernel and routes launches to ``impl``.
41
+
42
+ The JIT warmup infrastructure launches kernels as
43
+ ``kernel[grid](**kwargs)`` where ``kwargs`` are keyed by the original
44
+ kernel's argument names. This wrapper forwards those launch arguments
45
+ to the platform implementation: by keyword when the implementation's
46
+ parameter names match the kernel's, otherwise positionally in the
47
+ kernel's parameter order.
48
+ """
49
+
50
+ def __init__(self, kernel: Callable[..., Any], impl: Callable[..., Any]) -> None:
51
+ self._impl = impl
52
+ # Mirror the original kernel's names so launch binding and warmup
53
+ # introspection keep working against this wrapper.
54
+ self.arg_names = _kernel_arg_names(kernel)
55
+ self.constexprs = getattr(kernel, "constexprs", None)
56
+ self.func = impl
57
+ self.__name__ = getattr(impl, "__name__", "kernel_override")
58
+ self.__module__ = getattr(kernel, "__module__", "")
59
+ self._forward_by_name = (
60
+ tuple(inspect.signature(impl).parameters) == self.arg_names
61
+ )
62
+
63
+ def __getitem__(self, grid: Any) -> Callable[..., Any]:
64
+ return self._launch
65
+
66
+ def _launch(self, *args: Any, **kwargs: Any) -> Any:
67
+ if args:
68
+ return self._impl(*args, **kwargs)
69
+ if self._forward_by_name:
70
+ return self._impl(**kwargs)
71
+ unexpected = [name for name in kwargs if name not in self.arg_names]
72
+ if unexpected:
73
+ raise RuntimeError(
74
+ f"Override for {self.__name__!r} received unexpected launch "
75
+ f"arguments: {unexpected}"
76
+ )
77
+ return self._impl(*(kwargs[name] for name in self.arg_names if name in kwargs))
78
+
79
+ def __call__(self, *args: Any, **kwargs: Any) -> Any:
80
+ return self._impl(*args, **kwargs)
81
+
82
+
83
+def _resolve_kernel(name: str) -> tuple[Any, str]:
84
+ """Return the (host object, attribute) holding the kernel `name`."""
85
+ module_name, attr = name.rsplit(".", 1)
86
+ parts = module_name.split(".")
87
+ if parts[-1][:1].isupper():
88
+ class_module = importlib.import_module(".".join(parts[:-1]))
89
+ return getattr(class_module, parts[-1]), attr
90
+ importlib.import_module(module_name)
91
+ module = sys.modules[module_name]
92
+ if hasattr(module, attr):
93
+ return module, attr
94
+ raise ValueError(f"Kernel {name!r} not found for override")
95
+
96
+
97
+def _rebind_kernels(
98
+ overrides: list[tuple[Callable[..., Any], KernelOverride]],
99
+) -> None:
100
+ """Point every reference to each original kernel at its wrapper.
101
+
102
+ A kernel object can be captured in two kinds of places besides its
103
+ defining module, and both must be patched for the overrides to take
104
+ effect everywhere:
105
+
106
+ * plain attributes of other modules (``from mod import kernel``
107
+ copies), rebound to the wrapper;
108
+ * JIT warmup owners, whose ``kernel`` attribute is the kernel they
109
+ launch; swapped, and their cached ``_kernel_arg_names`` invalidated
110
+ so the launch binding re-derives from the wrapper.
111
+
112
+ One pass over ``sys.modules`` handles all kernels in `overrides`.
113
+ """
114
+ originals = {original: wrapper for original, wrapper in overrides}
115
+
116
+ def lookup(value: Any) -> KernelOverride | None:
117
+ # Only compare by identity: this scan runs over every module
118
+ # namespace, and some attribute values (e.g. PlaceholderModule
119
+ # sentinels) trigger imports or errors on hash/eq.
120
+ for original in originals:
121
+ if value is original:
122
+ return originals[original]
123
+ return None
124
+
125
+ for module in list(sys.modules.values()):
126
+ namespace = getattr(module, "__dict__", None)
127
+ if not namespace:
128
+ continue
129
+ for attr, value in list(namespace.items()):
130
+ wrapper = lookup(value)
131
+ if wrapper is not None:
132
+ setattr(module, attr, wrapper)
133
+ continue
134
+ # JIT warmup owners (e.g. _DecoratedTritonJitKernel) hold the
135
+ # kernel as an instance attribute; read the instance dict directly
136
+ # so module-level __getattr__ hooks are never triggered, and so
137
+ # this scan needs no dependency on the warmup machinery.
138
+ if hasattr(value, "__dict__"):
139
+ wrapper = lookup(vars(value).get("kernel"))
140
+ if wrapper is None:
141
+ continue
142
+ value.kernel = wrapper
143
+ if not isinstance(value, type):
144
+ value.__dict__.pop("_kernel_arg_names", None)
145
+
146
+
147
+def register_kernels(overrides: Mapping[str, Callable[..., Any]]) -> None:
148
+ """Register platform-specific implementations for Triton kernels.
149
+
150
+ Args:
151
+ overrides: Mapping from fully qualified kernel name to the
152
+ platform implementation. Kernel names look like
153
+ "vllm.v1.sample.rejection_sampler.expand_kernel"; for kernels
154
+ owned by a JIT warmup class, include the attribute path, e.g.
155
+ "vllm.v1.worker.block_table.ComputeSlotMappingKernel.kernel".
156
+ Implementations are invoked like the kernels themselves, with
157
+ the launch arguments in the original kernel's parameter order
158
+ (or by keyword when their parameter names match the kernel's).
159
+
160
+ """
161
+ resolved: list[tuple[str, Any, str, Callable[..., Any], KernelOverride]] = []
162
+ for name, impl in overrides.items():
163
+ if name in _registry:
164
+ logger.warning("Kernel %s is already registered. Overwriting.", name)
165
+ host, attr = _resolve_kernel(name)
166
+ original = getattr(host, attr)
167
+ wrapper = KernelOverride(original, impl)
168
+ _registry[name] = (original, impl)
169
+ resolved.append((name, host, attr, original, wrapper))
170
+ # Rebind other holders first: once the defining attributes are swapped
171
+ # below, the scan could no longer find the original kernels.
172
+ _rebind_kernels([(original, wrapper) for *_, original, wrapper in resolved])
173
+ for name, host, attr, _, wrapper in resolved:
174
+ setattr(host, attr, wrapper)
175
+ logger.debug("Registered kernel override %s", name)
vllm/utils/cpu_triton_utils.py
-36
@@ -4,19 +4,10 @@
4
"""Contains replacement functions to fallback Triton usages in CPU backend."""
5
6
import ctypes
7
-from collections.abc import Callable
7
8
import torch
9
10
12
-class _FuncWrapper:
13
- def __init__(self, func: Callable) -> None:
14
- self.func = func
15
-
16
- def __getitem__(self, *args, **kwargs) -> Callable:
17
- return self.func
18
-
19
-
11
# For _compute_slot_mapping_kernel in vllm/v1/worker/block_table.py
12
def _compute_slot_mapping_kernel_impl(
13
num_tokens: int,
@@ -47,9 +38,6 @@ def _compute_slot_mapping_kernel_impl(
38
)
39
40
50
-compute_slot_mapping_kernel = _FuncWrapper(_compute_slot_mapping_kernel_impl)
51
-
52
-
41
def _ensure_int64(t: torch.Tensor) -> torch.Tensor:
42
return t if t.dtype == torch.int64 else t.to(torch.int64)
43
@@ -454,31 +442,7 @@ def _sample_recovered_tokens_kernel_impl(
442
output_token_ids.copy_(output_i64.to(orig_dtype))
443
444
457
-eagle_prepare_inputs_padded_kernel = _FuncWrapper(
458
- _eagle_prepare_inputs_padded_kernel_impl
459
-)
460
-eagle_prepare_next_token_padded_kernel = _FuncWrapper(
461
- _eagle_prepare_next_token_padded_kernel_impl
462
-)
463
-copy_and_expand_eagle_inputs_kernel = _FuncWrapper(
464
- _copy_and_expand_eagle_inputs_kernel_impl
465
-)
466
-copy_and_expand_dflash_inputs_kernel = _FuncWrapper(
467
- _copy_and_expand_dflash_inputs_kernel_impl
468
-)
469
-eagle_step_slot_mapping_metadata_kernel = _FuncWrapper(
470
- _eagle_step_slot_mapping_metadata_kernel_impl
471
-)
472
-rejection_greedy_sample_kernel = _FuncWrapper(_rejection_greedy_sample_kernel_impl)
473
-rejection_random_sample_kernel = _FuncWrapper(_rejection_random_sample_kernel_impl)
474
-expand_kernel = _FuncWrapper(_expand_kernel_impl)
475
-sample_recovered_tokens_kernel = _FuncWrapper(_sample_recovered_tokens_kernel_impl)
476
-
477
-
445
def _batch_memcpy_impl(src_ptrs, dst_ptrs, sizes, BLOCK_SIZE=None):
446
# BLOCK_SIZE is unused; kept for signature parity with the Triton kernel.
447
for src, dst, size in zip(src_ptrs.tolist(), dst_ptrs.tolist(), sizes.tolist()):
448
ctypes.memmove(dst, src, size)
482
-
483
-
484
-batch_memcpy_kernel = _FuncWrapper(_batch_memcpy_impl)
vllm/v1/worker/cpu_model_runner.py
-61
@@ -1,13 +1,11 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
-import sys
3
from contextlib import AbstractContextManager, contextmanager
4
from typing import Any
5
6
import torch
7
import torch.nn as nn
8
10
-import vllm.utils.cpu_triton_utils as cpu_tl
9
from vllm.config import (
10
CompilationMode,
11
VllmConfig,
@@ -37,7 +35,6 @@ class CPUModelRunner(GPUModelRunner):
35
self.cascade_attn_enabled = False
36
37
self._postprocess_tensors()
40
- self._postprocess_triton()
38
39
def _postprocess_tensors(self) -> None:
40
# Note: replace device tensors with cpu tensors
@@ -62,64 +59,6 @@ class CPUModelRunner(GPUModelRunner):
59
if isinstance(v, CpuGpuBuffer):
60
v.gpu = v.cpu
61
65
- def _postprocess_triton(self) -> None:
66
- from vllm.triton_utils import HAS_TRITON
67
-
68
- if HAS_TRITON:
69
- logger.info(
70
- "Triton-CPU backend is available; skipping C++ monkey-patches "
71
- "for Triton kernels."
72
- )
73
- return
74
-
75
- import vllm.v1.worker.block_table
76
-
77
- vllm.v1.worker.block_table._COMPUTE_SLOT_MAPPING_KERNEL.kernel = (
78
- cpu_tl.compute_slot_mapping_kernel
79
- )
80
-
81
- # Speculative decoding fallbacks
82
- import vllm.v1.sample.rejection_sampler
83
- import vllm.v1.spec_decode.utils as spec_decode_utils
84
-
85
- spec_decode_utils._eagle_prepare_inputs_padded.kernel = (
86
- cpu_tl.eagle_prepare_inputs_padded_kernel
87
- )
88
- spec_decode_utils._eagle_prepare_next_token_padded.kernel = (
89
- cpu_tl.eagle_prepare_next_token_padded_kernel
90
- )
91
- spec_decode_utils._copy_and_expand_eagle_inputs.kernel = (
92
- cpu_tl.copy_and_expand_eagle_inputs_kernel
93
- )
94
- spec_decode_utils.copy_and_expand_dflash_inputs_kernel = (
95
- cpu_tl.copy_and_expand_dflash_inputs_kernel
96
- )
97
- dflash_module = sys.modules.get("vllm.v1.spec_decode.dflash")
98
- if dflash_module is not None:
99
- dflash_kernel_name = "copy_and_expand_dflash_inputs_kernel"
100
- setattr(
101
- dflash_module,
102
- dflash_kernel_name,
103
- cpu_tl.copy_and_expand_dflash_inputs_kernel,
104
- )
105
- spec_decode_utils.eagle_step_slot_mapping_metadata_kernel = (
106
- cpu_tl.eagle_step_slot_mapping_metadata_kernel
107
- )
108
- vllm.v1.sample.rejection_sampler.rejection_greedy_sample_kernel = (
109
- cpu_tl.rejection_greedy_sample_kernel
110
- )
111
- vllm.v1.sample.rejection_sampler.rejection_random_sample_kernel = (
112
- cpu_tl.rejection_random_sample_kernel
113
- )
114
- vllm.v1.sample.rejection_sampler.expand_kernel = cpu_tl.expand_kernel
115
- vllm.v1.sample.rejection_sampler.sample_recovered_tokens_kernel = (
116
- cpu_tl.sample_recovered_tokens_kernel
117
- )
118
-
119
- import vllm.v1.worker.mamba_utils
120
-
121
- vllm.v1.worker.mamba_utils.batch_memcpy_kernel = cpu_tl.batch_memcpy_kernel
122
-
62
@instrument(span_name="Loading (CPU)")
63
def load_model(self, load_dummy_weights: bool = False) -> None:
64
if load_dummy_weights:
vllm/v1/worker/worker_base.py
+6
@@ -268,6 +268,12 @@ class WorkerWrapperBase:
268
269
load_general_plugins()
270
271
+ # Let the platform replace core Triton kernels (e.g. CPU fallback
272
+ # implementations) before any worker or model code can launch them.
273
+ from vllm.platforms import current_platform
274
+
275
+ current_platform.register_triton_kernel_overrides()
276
+
277
parallel_config = vllm_config.parallel_config
278
if isinstance(parallel_config.worker_cls, str):
279
worker_class: type[WorkerBase] = resolve_obj_by_qualname(