Skip to content

Commit e58e758

Browse files
pcicottimojombo
andauthored
[https://nvbugs/5940460][fix] Harden FP8 quant fusion matching after PyTorch 26.02 update (#14697)
Signed-off-by: Dan Hansen <1+dhansen-nvidia@users.noreply.github.com> Signed-off-by: Pietro Cicotti <5833013+pcicotti@users.noreply.github.com> Co-authored-by: Dan Hansen <1+dhansen-nvidia@users.noreply.github.com>
1 parent 33e0ee3 commit e58e758

5 files changed

Lines changed: 437 additions & 102 deletions

File tree

tensorrt_llm/_torch/compilation/backend.py

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
from collections import OrderedDict
23
from typing import List, Optional
34

45
import torch
@@ -14,6 +15,7 @@
1415
from tensorrt_llm.mapping import Mapping
1516

1617
from .multi_stream.auto_multi_stream import multi_stream_schedule
18+
from .patterns import MATCHER_SUBSYSTEM
1719
from .patterns.ar_residual_norm import register_ar_fusions
1820
from .patterns.residual_add_norm import (register_add_norm,
1921
register_add_norm_quant)
@@ -24,7 +26,6 @@
2426

2527
class Backend:
2628

27-
_custom_pass_instances: List[PatternMatcherPass] = None
2829
_graph_pool_handle: tuple[int, int] = None
2930

3031
# Following classes are used to let weakref ref the stream and eventlist objects.
@@ -49,8 +50,8 @@ def __init__(
4950
self.module_inference_time = 0
5051
self.call_count = 0
5152
self.mapping = mapping
52-
self.custom_passes = Backend.get_custom_pass(enable_userbuffers,
53-
mapping)
53+
self.custom_passes = Backend.build_custom_passes(
54+
enable_userbuffers, mapping)
5455
self.rank = tensorrt_llm.mpi_rank()
5556
self.enable_inductor = enable_inductor
5657
self.capture_num_tokens = sorted(capture_num_tokens or [])
@@ -64,30 +65,35 @@ def __init__(
6465
Backend._graph_pool_handle = torch.cuda.graph_pool_handle()
6566

6667
self.match_count = []
68+
self.match_count_by_pass = OrderedDict()
6769

6870
@classmethod
69-
def get_custom_pass(cls, enable_userbuffers, mapping: Mapping):
71+
def build_custom_passes(cls, enable_userbuffers, mapping: Mapping):
7072
world_size = tensorrt_llm.mpi_world_size()
71-
if not cls._custom_pass_instances:
72-
# Really naive pass manager here
73-
cls._custom_pass_instances = [PatternMatcherPass()]
74-
if world_size > 1:
75-
# Currently torch compile cannot work properly with lamport fusion kernel
76-
# TO-DO: Fix this issue
77-
os.environ["DISABLE_LAMPORT_REDUCE_NORM_FUSION"] = "1"
78-
ub_enabled = enable_userbuffers and tensorrt_llm.bindings.internal.userbuffers.ub_supported(
79-
)
80-
register_ar_fusions(cls._custom_pass_instances, mapping,
81-
ub_enabled)
82-
# Fallback: fuse remaining add+rmsnorm not preceded by allreduce
83-
cls._custom_pass_instances.append(PatternMatcherPass())
84-
register_add_norm(cls._custom_pass_instances[-1])
85-
else:
86-
# Add fp8 quant pattern before fp16/bf16 pattern
87-
register_add_norm_quant(cls._custom_pass_instances[-1])
88-
cls._custom_pass_instances.append(PatternMatcherPass())
89-
register_add_norm(cls._custom_pass_instances[-1])
90-
return cls._custom_pass_instances
73+
# Really naive pass manager here
74+
custom_passes = [PatternMatcherPass("add_norm", MATCHER_SUBSYSTEM)]
75+
if world_size > 1:
76+
# Currently torch compile cannot work properly with lamport fusion kernel
77+
# TO-DO: Fix this issue
78+
os.environ["DISABLE_LAMPORT_REDUCE_NORM_FUSION"] = "1"
79+
ub_enabled = enable_userbuffers and tensorrt_llm.bindings.internal.userbuffers.ub_supported(
80+
)
81+
custom_passes[-1] = PatternMatcherPass("ar_residual_norm",
82+
MATCHER_SUBSYSTEM)
83+
register_ar_fusions(custom_passes, mapping, ub_enabled)
84+
# Fallback: fuse remaining add+rmsnorm not preceded by allreduce
85+
custom_passes.append(
86+
PatternMatcherPass("add_norm_fallback", MATCHER_SUBSYSTEM))
87+
register_add_norm(custom_passes[-1])
88+
else:
89+
# Add fp8 quant pattern before fp16/bf16 pattern
90+
custom_passes[-1] = PatternMatcherPass("add_norm_quant",
91+
MATCHER_SUBSYSTEM)
92+
register_add_norm_quant(custom_passes[-1])
93+
custom_passes.append(
94+
PatternMatcherPass("add_norm_fallback", MATCHER_SUBSYSTEM))
95+
register_add_norm(custom_passes[-1])
96+
return custom_passes
9197

9298
def bypass_optimization(self):
9399
self.no_optimization = True
@@ -107,10 +113,20 @@ def optimize(
107113
example_inputs: List[torch.Tensor],
108114
):
109115
graph = gm.graph
116+
self.match_count = []
117+
self.match_count_by_pass = OrderedDict()
110118
for custom_pass in self.custom_passes:
111-
self.match_count.append(custom_pass.apply(graph))
112-
while self.match_count[-1]:
113-
self.match_count.append(custom_pass.apply(graph))
119+
total_match_count = 0
120+
match_count = custom_pass.apply(graph)
121+
self.match_count.append(match_count)
122+
total_match_count += match_count
123+
while match_count:
124+
match_count = custom_pass.apply(graph)
125+
self.match_count.append(match_count)
126+
total_match_count += match_count
127+
pass_name = custom_pass.pass_name or (
128+
f"unnamed_pass_{len(self.match_count_by_pass)}")
129+
self.match_count_by_pass[pass_name] = total_match_count
114130
graph.eliminate_dead_code()
115131
# After this pass, cannot run any dce!!!
116132
remove_copy_for_mutates_args(graph)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
MATCHER_SUBSYSTEM = "torch_compile"

tensorrt_llm/_torch/compilation/patterns/ar_residual_norm.py

Lines changed: 133 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,50 @@
88
PatternMatcherPass, fwd_only,
99
register_replacement)
1010

11+
from tensorrt_llm.mapping import Mapping
12+
1113
from ...custom_ops.torch_custom_ops import BufferKind
1214
from ...distributed import AllReduceFusionOp, AllReduceStrategy
15+
from . import MATCHER_SUBSYSTEM
1316

1417
aten = torch.ops.aten
15-
from tensorrt_llm.mapping import Mapping
18+
19+
20+
def _append_named_pass(custom_passes: List[PatternMatcherPass], pass_name: str):
21+
custom_passes.append(PatternMatcherPass(pass_name, MATCHER_SUBSYSTEM))
22+
23+
24+
def _check_getitem_only_users(match: Match, pattern_node) -> bool:
25+
node = match.ctx.pattern_to_node[pattern_node]
26+
if not isinstance(node, torch.fx.graph.Node):
27+
return False
28+
for user in node.users:
29+
if user.op != "call_function" or user.target is not getitem:
30+
return False
31+
return True
32+
33+
34+
def _has_getitem_user(match: Match, pattern_node, index: int) -> bool:
35+
node = match.ctx.pattern_to_node[pattern_node]
36+
if not isinstance(node, torch.fx.graph.Node):
37+
return False
38+
for user in node.users:
39+
if (user.op == "call_function" and user.target is getitem
40+
and user.args[1] == index):
41+
return True
42+
return False
43+
44+
45+
def _make_fp8_quant_extra_check(input_node, strategy_node, quant_node,
46+
require_scale_output: bool):
47+
48+
def extra_check(match: Match) -> bool:
49+
return (check_f16_bf16_input(match, input_node)
50+
and check_non_ub_strategy(match, strategy_node)
51+
and _check_getitem_only_users(match, quant_node) and
52+
_has_getitem_user(match, quant_node, 1) == require_scale_output)
53+
54+
return extra_check
1655

1756

1857
def register_ar_residual_norm(custom_pass: PatternMatcherPass, mapping: Mapping,
@@ -135,15 +174,16 @@ def register_ar_residual_norm_out_fp8_quant(custom_pass: PatternMatcherPass,
135174
torch.ops.tensorrt_llm.static_quantize_e4m3_per_tensor.default,
136175
getitem_0,
137176
KeywordArg("scale"),
138-
_users=2)
139-
getitem_2 = CallFunction(getitem,
140-
static_quantize_e4m3_per_tensor_default,
141-
0,
142-
_users=2)
177+
_users=MULTIPLE)
178+
getitem_2 = CallFunction(getitem, static_quantize_e4m3_per_tensor_default,
179+
0)
143180
getitem_3 = CallFunction(getitem, static_quantize_e4m3_per_tensor_default,
144181
1)
145-
pattern = MultiOutputPattern([getitem_0, getitem_1, getitem_2, getitem_3
146-
]) # norm_out, residual_out, quant_out, scale
182+
pattern_with_scale = MultiOutputPattern(
183+
[getitem_0, getitem_1, getitem_2,
184+
getitem_3]) # norm_out, residual_out, quant_out, scale
185+
pattern_without_scale = MultiOutputPattern(
186+
[getitem_0, getitem_1, getitem_2]) # norm_out, residual_out, quant_out
147187

148188
def empty_pattern(
149189
input: torch.Tensor,
@@ -174,18 +214,48 @@ def target_pattern(
174214
trigger_completion_at_end)
175215
return allreduce[0], allreduce[2], allreduce[1], scale
176216

177-
def extra_check(match: Match) -> bool:
178-
return check_f16_bf16_input(
179-
match, input_node) and check_non_ub_strategy(match, strategy_node)
217+
def target_pattern_without_scale(
218+
input: torch.Tensor,
219+
residual: torch.Tensor,
220+
gamma: torch.Tensor,
221+
workspace: torch.LongTensor,
222+
strategy: int,
223+
eps: float,
224+
scale: torch.Tensor,
225+
trigger_completion_at_end: bool,
226+
):
227+
allreduce = allreduce_func(
228+
input, residual, gamma, scale, None, workspace, mapping.tp_group,
229+
int(strategy),
230+
int(AllReduceFusionOp.RESIDUAL_RMS_NORM_OUT_QUANT_FP8), float(eps),
231+
trigger_completion_at_end)
232+
return allreduce[0], allreduce[2], allreduce[1]
233+
234+
extra_check_with_scale = _make_fp8_quant_extra_check(
235+
input_node, strategy_node, static_quantize_e4m3_per_tensor_default,
236+
True)
237+
extra_check_without_scale = _make_fp8_quant_extra_check(
238+
input_node, strategy_node, static_quantize_e4m3_per_tensor_default,
239+
False)
180240

181241
register_replacement(
182242
empty_pattern,
183243
target_pattern,
184244
[],
185245
fwd_only,
186246
custom_pass,
187-
search_fn_pattern=pattern,
188-
extra_check=extra_check,
247+
search_fn_pattern=pattern_with_scale,
248+
extra_check=extra_check_with_scale,
249+
)
250+
251+
register_replacement(
252+
empty_pattern,
253+
target_pattern_without_scale,
254+
[],
255+
fwd_only,
256+
custom_pass,
257+
search_fn_pattern=pattern_without_scale,
258+
extra_check=extra_check_without_scale,
189259
)
190260

191261

@@ -213,15 +283,15 @@ def register_ar_residual_norm_fp8_quant(custom_pass: PatternMatcherPass,
213283
torch.ops.tensorrt_llm.static_quantize_e4m3_per_tensor.default,
214284
getitem_0,
215285
KeywordArg("scale"),
216-
_users=2)
217-
getitem_2 = CallFunction(getitem,
218-
static_quantize_e4m3_per_tensor_default,
219-
0,
220-
_users=2)
286+
_users=MULTIPLE)
287+
getitem_2 = CallFunction(getitem, static_quantize_e4m3_per_tensor_default,
288+
0)
221289
getitem_3 = CallFunction(getitem, static_quantize_e4m3_per_tensor_default,
222290
1)
223-
pattern = MultiOutputPattern([getitem_1, getitem_2,
224-
getitem_3]) # residual_out, quant_out, scale
291+
pattern_with_scale = MultiOutputPattern(
292+
[getitem_1, getitem_2, getitem_3]) # residual_out, quant_out, scale
293+
pattern_without_scale = MultiOutputPattern([getitem_1, getitem_2
294+
]) # residual_out, quant_out
225295

226296
def empty_pattern(
227297
input: torch.Tensor,
@@ -251,18 +321,47 @@ def target_pattern(
251321
float(eps), trigger_completion_at_end)
252322
return allreduce[1], allreduce[0], scale
253323

254-
def extra_check(match: Match) -> bool:
255-
return check_f16_bf16_input(
256-
match, input_node) and check_non_ub_strategy(match, strategy_node)
324+
def target_pattern_without_scale(
325+
input: torch.Tensor,
326+
residual: torch.Tensor,
327+
gamma: torch.Tensor,
328+
workspace: torch.LongTensor,
329+
strategy: int,
330+
eps: float,
331+
scale: torch.Tensor,
332+
trigger_completion_at_end: bool,
333+
):
334+
allreduce = allreduce_func(
335+
input, residual, gamma, scale, None, workspace, mapping.tp_group,
336+
int(strategy), int(AllReduceFusionOp.RESIDUAL_RMS_NORM_QUANT_FP8),
337+
float(eps), trigger_completion_at_end)
338+
return allreduce[1], allreduce[0]
339+
340+
extra_check_with_scale = _make_fp8_quant_extra_check(
341+
input_node, strategy_node, static_quantize_e4m3_per_tensor_default,
342+
True)
343+
extra_check_without_scale = _make_fp8_quant_extra_check(
344+
input_node, strategy_node, static_quantize_e4m3_per_tensor_default,
345+
False)
257346

258347
register_replacement(
259348
empty_pattern,
260349
target_pattern,
261350
[],
262351
fwd_only,
263352
custom_pass,
264-
search_fn_pattern=pattern,
265-
extra_check=extra_check,
353+
search_fn_pattern=pattern_with_scale,
354+
extra_check=extra_check_with_scale,
355+
)
356+
357+
register_replacement(
358+
empty_pattern,
359+
target_pattern_without_scale,
360+
[],
361+
fwd_only,
362+
custom_pass,
363+
search_fn_pattern=pattern_without_scale,
364+
extra_check=extra_check_without_scale,
266365
)
267366

268367

@@ -773,16 +872,20 @@ def extra_check(match: Match) -> bool:
773872
extra_check=extra_check,
774873
)
775874

776-
custom_passes.append(PatternMatcherPass())
875+
_append_named_pass(
876+
custom_passes,
877+
f"ub_convert_supported_ar_to_ub:{allreduce_func.__name__}")
777878
register_convert_supported_ar_to_ub(custom_passes[-1])
778879

779-
custom_passes.append(PatternMatcherPass())
880+
_append_named_pass(custom_passes, f"ub_prologue:{allreduce_func.__name__}")
780881
register_ub_prologue_patterns(custom_passes[-1])
781882

782-
custom_passes.append(PatternMatcherPass())
883+
_append_named_pass(custom_passes, f"ub_finalize:{allreduce_func.__name__}")
783884
register_ub_finalize_patterns(custom_passes[-1])
784885

785-
custom_passes.append(PatternMatcherPass())
886+
_append_named_pass(
887+
custom_passes,
888+
f"insert_copy_for_graph_output:{allreduce_func.__name__}")
786889
insert_copy_for_graph_output(custom_passes[-1])
787890

788891

@@ -793,7 +896,7 @@ def register_ar_fusions(custom_passes: List[PatternMatcherPass],
793896
register_ar_residual_norm(custom_passes[-1], mapping,
794897
torch.ops.trtllm.tunable_allreduce)
795898

796-
custom_passes.append(PatternMatcherPass())
899+
_append_named_pass(custom_passes, "ar_residual_norm_quant")
797900
for allreduce_func in [
798901
torch.ops.trtllm.allreduce, torch.ops.trtllm.tunable_allreduce
799902
]:

tests/integration/test_lists/waives.txt

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -354,22 +354,6 @@ unittest/_torch/modules/moe/test_moe_module.py::test_configurable_moe_multi_gpu[
354354
unittest/_torch/modules/moe/test_moe_module.py::test_configurable_moe_multi_gpu_eplb -k "MEGAMOE_DEEPGEMM" SKIP (https://nvbugs/6175060)
355355
unittest/_torch/modules/moe/test_moe_module.py::test_configurable_moe_single_gpu[e256_k8_h7168_i2048-seq=1-dtype=torch.bfloat16-backend=MEGAMOE_DEEPGEMM-quant=W4A8_MXFP4_MXFP8-routing=DeepSeekV3] SKIP (https://nvbugs/6175060)
356356
unittest/_torch/modules/tests_lora_modules/test_lora_attention_pytorch_flow_vs_trt.py::TestLoraAttentionPytorchFlowVsTRT::test_lora_attention SKIP (https://nvbugs/5701421)
357-
unittest/_torch/multi_gpu/test_user_buffers.py::test_user_buffers_mm_add_prologue[2-bf16-_tokens16-_hidden32] SKIP (https://nvbugs/5940460)
358-
unittest/_torch/multi_gpu/test_user_buffers.py::test_user_buffers_mm_add_prologue[2-bf16-_tokens16-_hidden512] SKIP (https://nvbugs/5940460)
359-
unittest/_torch/multi_gpu/test_user_buffers.py::test_user_buffers_mm_add_prologue[2-bf16-_tokens256-_hidden32] SKIP (https://nvbugs/5940460)
360-
unittest/_torch/multi_gpu/test_user_buffers.py::test_user_buffers_mm_add_prologue[2-bf16-_tokens256-_hidden512] SKIP (https://nvbugs/5940460)
361-
unittest/_torch/multi_gpu/test_user_buffers.py::test_user_buffers_mm_add_prologue[2-fp16-_tokens16-_hidden32] SKIP (https://nvbugs/5940460)
362-
unittest/_torch/multi_gpu/test_user_buffers.py::test_user_buffers_mm_add_prologue[2-fp16-_tokens16-_hidden512] SKIP (https://nvbugs/5940460)
363-
unittest/_torch/multi_gpu/test_user_buffers.py::test_user_buffers_mm_add_prologue[2-fp16-_tokens256-_hidden32] SKIP (https://nvbugs/5940460)
364-
unittest/_torch/multi_gpu/test_user_buffers.py::test_user_buffers_mm_add_prologue[2-fp16-_tokens256-_hidden512] SKIP (https://nvbugs/5940460)
365-
unittest/_torch/multi_gpu/test_user_buffers.py::test_user_buffers_pass[2-bf16-_tokens16-_hidden32] SKIP (https://nvbugs/5940460)
366-
unittest/_torch/multi_gpu/test_user_buffers.py::test_user_buffers_pass[2-bf16-_tokens16-_hidden512] SKIP (https://nvbugs/5940460)
367-
unittest/_torch/multi_gpu/test_user_buffers.py::test_user_buffers_pass[2-bf16-_tokens256-_hidden32] SKIP (https://nvbugs/5940460)
368-
unittest/_torch/multi_gpu/test_user_buffers.py::test_user_buffers_pass[2-bf16-_tokens256-_hidden512] SKIP (https://nvbugs/5940460)
369-
unittest/_torch/multi_gpu/test_user_buffers.py::test_user_buffers_pass[2-fp16-_tokens16-_hidden32] SKIP (https://nvbugs/5940460)
370-
unittest/_torch/multi_gpu/test_user_buffers.py::test_user_buffers_pass[2-fp16-_tokens16-_hidden512] SKIP (https://nvbugs/5940460)
371-
unittest/_torch/multi_gpu/test_user_buffers.py::test_user_buffers_pass[2-fp16-_tokens256-_hidden32] SKIP (https://nvbugs/5940460)
372-
unittest/_torch/multi_gpu/test_user_buffers.py::test_user_buffers_pass[2-fp16-_tokens256-_hidden512] SKIP (https://nvbugs/5940460)
373357
unittest/_torch/thop/serial/test_moe.py::TestMoeFp4::test_no_autotune[use_score_as_input-RoutingDSv3-swiglu-1024-1024-1] SKIP (https://nvbugs/5908070)
374358
unittest/_torch/thop/serial/test_moe.py::TestMoeFp4::test_no_autotune[use_score_as_input-RoutingRenormalize_qwen_next-swiglu-1024-1024-150] SKIP (https://nvbugs/5908070)
375359
unittest/_torch/thop/serial/test_moe.py::TestMoeFp4::test_no_autotune[use_score_as_input-RoutingRenormalize_topk_4-swiglu-1024-1024-150] SKIP (https://nvbugs/5908070)

0 commit comments

Comments
 (0)