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 4, 17:30 UTC
[Compilation] Refactor SiluMul activation+quant Fusion Pass (#39684)
Signed-off-by: BadrBasowid <badr.basowid@gmail.com>
BadrBasowid committed
Apr 23, 2026 at 21:10 UTC
2196bac1359a62a29f76936f37380235cc47f096
4 files changed
+81
-103
tests/compile/fusions_e2e/conftest.py
+7
-1
@@ -189,7 +189,7 @@ def run_e2e_fusion_test(monkeypatch, caplog_mp_spawn):
189
# TODO: Remove log counting in unit tests
190
# once all matchers implement VllmFusionPatternMatcherPass
191
n_expected = tp_size * num_ranges_activated
192
- if match_name != "attn_quant_fusion":
192
+ if match_name not in ("attn_quant_fusion", "act_quant_fusion"):
193
assert len(log_matches) == n_expected, (
194
f"Could not find {n_expected} {match_name} "
195
f"(found {len(log_matches)}) in:\n {log_holder.text}"
@@ -250,6 +250,12 @@ def run_e2e_fusion_test(monkeypatch, caplog_mp_spawn):
250
f"entries (SP took precedence), found: {log_matches}"
251
)
252
253
+ elif match_name == "act_quant_fusion":
254
+ actual_match = match_table.get("activation_quant_fusion_pass", 0)
255
+ assert actual_match == expected_matches * n_expected, (
256
+ f"Could not find {expected_matches * n_expected} "
257
+ f"{match_name} (found {actual_match})."
258
+ )
259
elif match_name == "attn_quant_fusion":
260
actual_match = match_table.get(
261
"attn_quant_fusion", 0
tests/compile/passes/test_silu_mul_quant_fusion.py
+1
-1
@@ -168,7 +168,7 @@ class TestSiluMulGroupFp8QuantModel(torch.nn.Module):
168
169
def forward(self, x):
170
y = self.silu_and_mul(x)
171
- x2 = self.w8a8_block_fp8_linear(y, self.w, self.wscale)
171
+ x2 = self.w8a8_block_fp8_linear(y)
172
return x2
173
174
def ops_in_model_before(self):
vllm/compilation/passes/fusion/act_quant_fusion.py
+59
-72
@@ -1,16 +1,11 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
-from abc import ABC, abstractmethod
4
+import itertools
5
from typing import Any
6
7
import torch
8
from torch._higher_order_ops.auto_functionalize import auto_functionalized
9
-from torch._inductor.pattern_matcher import (
10
- PatternMatcherPass,
11
- fwd_only,
12
- register_replacement,
13
-)
9
from torch._ops import OpOverload
10
11
from vllm.config import VllmConfig
@@ -24,8 +19,7 @@ from vllm.model_executor.layers.quantization.utils.quant_utils import (
19
)
20
from vllm.platforms import current_platform
21
27
-from ..inductor_pass import enable_fake_mode
28
-from ..vllm_inductor_pass import VllmInductorPass, VllmPatternMatcherPass
22
+from ..vllm_inductor_pass import VllmFusionPatternMatcherPass, VllmPatternReplacement
23
from .matcher_utils import MatcherQuantFP8, MatcherSiluAndMul
24
from .rms_quant_fusion import QUANT_OPS, empty_bf16, empty_fp32, empty_i32
25
@@ -50,9 +44,9 @@ if current_platform.is_cuda_alike():
44
FUSED_OPS[kFp8Dynamic64Sym] = torch.ops._C.silu_and_mul_per_block_quant.default
45
46
53
-class ActivationQuantPattern(ABC):
47
+class ActivationQuantPattern(VllmPatternReplacement):
48
"""
55
- The base class for Activation+Quant fusions.
49
+ Base class for Activation+Quant fusions.
50
Should not be used directly.
51
"""
52
@@ -79,10 +73,6 @@ class ActivationQuantPattern(ABC):
73
kwargs = {"dtype": self.quant_dtype, "device": "cuda", **kwargs}
74
return torch.empty(*args, **kwargs)
75
82
- @abstractmethod
83
- def register(self, pm_pass: PatternMatcherPass) -> None:
84
- raise NotImplementedError
85
-
76
77
class SiluMulFp8StaticQuantPattern(ActivationQuantPattern):
78
"""
@@ -100,8 +90,9 @@ class SiluMulFp8StaticQuantPattern(ActivationQuantPattern):
90
scale,
91
]
92
103
- def register(self, pm_pass: PatternMatcherPass) -> None:
104
- def pattern(
93
+ @property
94
+ def pattern(self):
95
+ def _pattern(
96
input: torch.Tensor,
97
scale: torch.Tensor,
98
) -> torch.Tensor:
@@ -109,7 +100,11 @@ class SiluMulFp8StaticQuantPattern(ActivationQuantPattern):
100
result_quant = self.quant_matcher(result_silu_mul, scale)
101
return result_quant[0]
102
112
- def replacement(
103
+ return _pattern
104
+
105
+ @property
106
+ def replacement(self):
107
+ def _replacement(
108
input: torch.Tensor,
109
scale: torch.Tensor,
110
) -> torch.Tensor:
@@ -123,10 +118,7 @@ class SiluMulFp8StaticQuantPattern(ActivationQuantPattern):
118
)
119
return at[1]
120
126
- inps = self.get_inputs()
127
- pattern(*inps)
128
-
129
- register_replacement(pattern, replacement, inps, fwd_only, pm_pass)
121
+ return _replacement
122
123
124
class SiluMulNvfp4QuantPattern(ActivationQuantPattern):
@@ -144,8 +136,9 @@ class SiluMulNvfp4QuantPattern(ActivationQuantPattern):
136
scale = empty_fp32(1, 1)
137
return [result, output_scale, input_, scale]
138
147
- def register(self, pm_pass: PatternMatcherPass) -> None:
148
- def pattern(
139
+ @property
140
+ def pattern(self):
141
+ def _pattern(
142
result: torch.Tensor,
143
output_scale: torch.Tensor,
144
input: torch.Tensor,
@@ -162,7 +155,11 @@ class SiluMulNvfp4QuantPattern(ActivationQuantPattern):
155
)
156
return at[1], at[2]
157
165
- def replacement(
158
+ return _pattern
159
+
160
+ @property
161
+ def replacement(self):
162
+ def _replacement(
163
result: torch.Tensor,
164
output_scale: torch.Tensor,
165
input: torch.Tensor,
@@ -177,7 +174,7 @@ class SiluMulNvfp4QuantPattern(ActivationQuantPattern):
174
)
175
return at[1], at[2]
176
180
- register_replacement(pattern, replacement, self.get_inputs(), fwd_only, pm_pass)
177
+ return _replacement
178
179
180
class SiluMulBlockQuantPattern(ActivationQuantPattern):
@@ -210,10 +207,9 @@ class SiluMulBlockQuantPattern(ActivationQuantPattern):
207
scale = self.quant_matcher.empty_f32(1, 1)
208
return self.silu_and_mul_matcher.inputs() + [scale]
209
213
- def register(self, pm_pass: PatternMatcherPass) -> None:
214
- is_scale_transposed = self.is_scale_transposed
215
-
216
- def pattern(
210
+ @property
211
+ def pattern(self):
212
+ def _pattern(
213
input: torch.Tensor,
214
scale: torch.Tensor,
215
) -> tuple[torch.Tensor, torch.Tensor]:
@@ -235,12 +231,16 @@ class SiluMulBlockQuantPattern(ActivationQuantPattern):
231
fp8_min=finfo.min,
232
fp8_max=finfo.max,
233
scale_ue8m0=self.is_e8m0,
238
- dummy_is_scale_transposed=is_scale_transposed,
234
+ dummy_is_scale_transposed=self.is_scale_transposed,
235
dummy_is_tma_aligned=self.is_tma_aligned,
236
)
237
return result, scale
238
243
- def replacement(
239
+ return _pattern
240
+
241
+ @property
242
+ def replacement(self):
243
+ def _replacement(
244
input: torch.Tensor,
245
scale: torch.Tensor,
246
) -> tuple[torch.Tensor, torch.Tensor]:
@@ -249,7 +249,7 @@ class SiluMulBlockQuantPattern(ActivationQuantPattern):
249
result = torch.empty(
250
output_shape, device=input.device, dtype=self.quant_dtype
251
)
252
- if is_scale_transposed:
252
+ if self.is_scale_transposed:
253
scale = torch.empty(
254
(d // self.group_size, input.shape[0]),
255
device=input.device,
@@ -268,15 +268,14 @@ class SiluMulBlockQuantPattern(ActivationQuantPattern):
268
scales=scale,
269
group_size=self.group_size,
270
scale_ub=None,
271
- is_scale_transposed=is_scale_transposed,
271
+ is_scale_transposed=self.is_scale_transposed,
272
)
273
return at[1], at[2]
274
275
- inps = self.get_inputs()
276
- register_replacement(pattern, replacement, inps, fwd_only, pm_pass)
275
+ return _replacement
276
277
279
-class ActivationQuantFusionPass(VllmPatternMatcherPass):
278
+class ActivationQuantFusionPass(VllmFusionPatternMatcherPass):
279
"""
280
This pass fuses a pre-defined set of custom ops into fused ops.
281
It uses the torch pattern matcher to find the patterns and replace them.
@@ -286,45 +285,33 @@ class ActivationQuantFusionPass(VllmPatternMatcherPass):
285
https://github.com/pytorch/pytorch/pull/139321#issuecomment-2452354980
286
"""
287
289
- @enable_fake_mode
288
def __init__(self, config: VllmConfig) -> None:
291
- super().__init__(config)
292
-
293
- self.patterns: PatternMatcherPass = PatternMatcherPass(
294
- pass_name="activation_quant_fusion_pass"
295
- )
289
+ super().__init__(config, "activation_quant_fusion_pass")
290
297
- pattern_silu_mul_fp8 = SiluMulFp8StaticQuantPattern()
298
- pattern_silu_mul_fp8.register(self.patterns)
291
+ self.register(SiluMulFp8StaticQuantPattern())
292
293
if silu_and_mul_nvfp4_quant_supported:
301
- pattern_silu_mul_nvfp4 = SiluMulNvfp4QuantPattern()
302
- pattern_silu_mul_nvfp4.register(self.patterns)
294
+ self.register(SiluMulNvfp4QuantPattern())
295
296
if current_platform.is_cuda():
305
- for quant_key in [kFp8Dynamic128Sym, kFp8Dynamic64Sym]:
306
- for is_scale_transposed in [False, True]:
307
- for is_e8m0 in [True, False]:
308
- for is_tma_aligned in [False, True]:
309
- SiluMulBlockQuantPattern(
310
- quant_key,
311
- is_scale_transposed=is_scale_transposed,
312
- is_e8m0=is_e8m0,
313
- is_tma_aligned=is_tma_aligned,
314
- ).register(self.patterns)
315
-
316
- self.dump_patterns(config, self.patterns)
317
-
318
- @VllmInductorPass.time_and_log
319
- def __call__(self, graph: torch.fx.Graph) -> None:
320
- self.matched_count = self.patterns.apply(graph)
321
- logger.debug("Replaced %s patterns", self.matched_count)
322
-
323
- def uuid(self) -> str:
324
- return VllmInductorPass.hash_source(
325
- self,
326
- ActivationQuantPattern,
327
- SiluMulFp8StaticQuantPattern,
328
- SiluMulNvfp4QuantPattern,
329
- SiluMulBlockQuantPattern,
330
- )
297
+ for (
298
+ quant_key,
299
+ is_scale_transposed,
300
+ is_e8m0,
301
+ is_tma_aligned,
302
+ ) in itertools.product(
303
+ [kFp8Dynamic128Sym, kFp8Dynamic64Sym],
304
+ [False, True],
305
+ [True, False],
306
+ [False, True],
307
+ ):
308
+ self.register(
309
+ SiluMulBlockQuantPattern(
310
+ quant_key,
311
+ is_scale_transposed=is_scale_transposed,
312
+ is_e8m0=is_e8m0,
313
+ is_tma_aligned=is_tma_aligned,
314
+ )
315
+ )
316
+
317
+ self.dump_patterns(config, self.pm_pass)
vllm/compilation/passes/fusion/rocm_aiter_fusion.py
+14
-29
@@ -28,7 +28,6 @@ from ..vllm_inductor_pass import (
28
VllmPatternMatcherPass,
29
VllmPatternReplacement,
30
)
31
-from .act_quant_fusion import ActivationQuantPattern
31
from .matcher_utils import (
32
MatcherFusedAddRMSNorm,
33
MatcherQuantFP8,
@@ -345,7 +344,7 @@ class RocmAiterRMSNormQuantFusionPass(VllmPatternMatcherPass):
344
return self.hash_source(self, *fusion_patterns)
345
346
348
-class AiterSiluMulFp8GroupQuantPattern(ActivationQuantPattern):
347
+class AiterSiluMulFp8GroupQuantPattern(VllmPatternReplacement):
348
"""
349
This pattern fuses aiter silu_and_mul & group fp8 quant custom
350
ops into an aiter silu_and_mul_group_fp8_quant op.
@@ -364,26 +363,29 @@ class AiterSiluMulFp8GroupQuantPattern(ActivationQuantPattern):
363
self.silu_and_mul_matcher.inputs()[0],
364
]
365
367
- def register(self, pm_pass: PatternMatcherPass) -> None:
368
- def pattern(
366
+ @property
367
+ def pattern(self):
368
+ def _pattern(
369
input: torch.Tensor,
370
) -> tuple[torch.Tensor, torch.Tensor]:
371
at1 = self.silu_and_mul_matcher(input)
372
at2 = self.quant_matcher(at1)
373
return at2[0], at2[1]
374
375
- def replacement(
375
+ return _pattern
376
+
377
+ @property
378
+ def replacement(self):
379
+ def _replacement(
380
input: torch.Tensor,
381
) -> tuple[torch.Tensor, torch.Tensor]:
382
at = self.FUSED_SILU_MUL_QUANT_OP(x=input, group_size=128)
383
return at[0], at[1]
384
381
- pm.register_replacement(
382
- pattern, replacement, self.get_inputs(), pm.fwd_only, pm_pass
383
- )
385
+ return _replacement
386
387
386
-class RocmAiterSiluMulFp8GroupQuantFusionPass(VllmPatternMatcherPass):
388
+class RocmAiterSiluMulFp8GroupQuantFusionPass(VllmFusionPatternMatcherPass):
389
"""
390
This pass fuses a pre-defined set of custom ops into fused ops.
391
It uses the torch pattern matcher to find the patterns and replace them.
@@ -393,29 +395,12 @@ class RocmAiterSiluMulFp8GroupQuantFusionPass(VllmPatternMatcherPass):
395
https://github.com/pytorch/pytorch/pull/139321#issuecomment-2452354980
396
"""
397
396
- @enable_fake_mode
398
def __init__(self, config: VllmConfig) -> None:
398
- super().__init__(config)
399
-
400
- self.patterns: PatternMatcherPass = PatternMatcherPass(
401
- pass_name="rocm_aiter_silu_mul_fp8_group_quant_fusion_pass"
402
- )
403
-
404
- AiterSiluMulFp8GroupQuantPattern().register(self.patterns)
405
-
406
- self.dump_patterns(config, self.patterns)
399
+ super().__init__(config, "rocm_aiter_silu_mul_fp8_group_quant_fusion_pass")
400
408
- @VllmInductorPass.time_and_log
409
- def __call__(self, graph: torch.fx.Graph) -> None:
410
- self.matched_count = self.patterns.apply(graph)
411
- logger.debug("Replaced %s patterns", self.matched_count)
401
+ self.register(AiterSiluMulFp8GroupQuantPattern())
402
413
- def uuid(self) -> str:
414
- fusion_patterns = [
415
- ActivationQuantPattern,
416
- AiterSiluMulFp8GroupQuantPattern,
417
- ]
418
- return VllmInductorPass.hash_source(self, *fusion_patterns)
403
+ self.dump_patterns(config, self.pm_pass)
404
405
406
class AddAiterRMSNormPadPattern: