Skip to content

Commit 5e77882

Browse files
[None][fix] AutoDeploy: return fused_weight_dims so fused QKV split sizes are rescaled under TP (NVIDIA#15351)
Signed-off-by: Srijan Upadhyay <srjnupadhyay@gmail.com>
1 parent 1c9a902 commit 5e77882

2 files changed

Lines changed: 103 additions & 1 deletion

File tree

tensorrt_llm/_torch/auto_deploy/transform/library/sharding.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2710,7 +2710,7 @@ def _excl_name(n):
27102710

27112711
def _determine_fused_weight_dims(
27122712
linear_nodes: List[Node],
2713-
) -> None:
2713+
) -> Optional[list]:
27142714
"""
27152715
Determine the fused weight dims for the given linear nodes and subgraph nodes.
27162716
"""
@@ -2755,6 +2755,8 @@ def _determine_fused_weight_dims(
27552755
weight_dim = linear_node.meta["val"].shape[2]
27562756
fused_weight_dims = [weight_dim // num_chunks] * num_chunks
27572757

2758+
return fused_weight_dims
2759+
27582760

27592761
def _find_upstream_qk_proj(node: Node, gm: GraphModule) -> Optional[str]:
27602762
"""
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
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+
"""CPU-only regression tests for ``_determine_fused_weight_dims``.
17+
18+
These guard the fix for the Phi-4 TP=2 regression (issue #11220). A fused QKV
19+
linear is column-sharded, but the downstream ``split_with_sizes`` sizes were
20+
never rescaled by ``world_size`` because ``_determine_fused_weight_dims``
21+
computed the fused dims into a local variable and then fell off the end of the
22+
function with no ``return`` statement, so the caller always received ``None``
23+
and the ``if fused_weight_dims is not None`` rescaling branch never ran.
24+
"""
25+
26+
import torch
27+
import torch.fx as fx
28+
import torch.nn as nn
29+
from torch.fx import GraphModule
30+
31+
import tensorrt_llm._torch.auto_deploy.custom_ops # noqa: F401 — register custom ops
32+
from tensorrt_llm._torch.auto_deploy.transform.library.sharding import _determine_fused_weight_dims
33+
from tensorrt_llm._torch.auto_deploy.utils.node_utils import is_linear_op
34+
35+
36+
def _linear_node(gm: GraphModule):
37+
for n in gm.graph.nodes:
38+
if is_linear_op(n):
39+
return n
40+
raise AssertionError(f"no linear node in graph: {[n.target for n in gm.graph.nodes]}")
41+
42+
43+
def test_fused_qkv_split_returns_split_sizes():
44+
"""A linear feeding a ``split_with_sizes`` must report the fused split sizes.
45+
46+
This is the exact Phi-4 fused-QKV shape: out_features = 5120 + 1280 + 1280.
47+
The pre-fix bug made this return ``None``.
48+
"""
49+
q, k, v = 5120, 1280, 1280
50+
51+
graph = fx.Graph()
52+
x = graph.placeholder("x")
53+
w = graph.placeholder("w")
54+
lin = graph.call_function(torch.ops.aten.linear, args=(x, w))
55+
split = graph.call_function(
56+
torch.ops.aten.split_with_sizes, args=(lin, [q, k, v]), kwargs={"dim": -1}
57+
)
58+
graph.output(split)
59+
gm = GraphModule(nn.Module(), graph)
60+
61+
fused_weight_dims = _determine_fused_weight_dims([_linear_node(gm)])
62+
63+
assert fused_weight_dims is not None, (
64+
"fused_weight_dims must not be None for a fused QKV split "
65+
"(regression: missing return in _determine_fused_weight_dims)"
66+
)
67+
assert list(fused_weight_dims) == [q, k, v]
68+
69+
70+
def test_unfused_linear_returns_none():
71+
"""A linear with no split/slice/chunk user is not fused and must return None.
72+
73+
This guards against the fix over-reporting fused dims (which would make the
74+
caller wrongly rescale unrelated split nodes).
75+
"""
76+
graph = fx.Graph()
77+
x = graph.placeholder("x")
78+
w = graph.placeholder("w")
79+
lin = graph.call_function(torch.ops.aten.linear, args=(x, w))
80+
relu = graph.call_function(torch.ops.aten.relu, args=(lin,))
81+
graph.output(relu)
82+
gm = GraphModule(nn.Module(), graph)
83+
84+
assert _determine_fused_weight_dims([_linear_node(gm)]) is None
85+
86+
87+
def test_more_than_one_linear_returns_none():
88+
"""The helper only handles a single fused linear; multiple inputs return None."""
89+
graph = fx.Graph()
90+
x = graph.placeholder("x")
91+
w0 = graph.placeholder("w0")
92+
w1 = graph.placeholder("w1")
93+
lin0 = graph.call_function(torch.ops.aten.linear, args=(x, w0))
94+
lin1 = graph.call_function(torch.ops.aten.linear, args=(x, w1))
95+
graph.output((lin0, lin1))
96+
gm = GraphModule(nn.Module(), graph)
97+
98+
linears = [n for n in gm.graph.nodes if is_linear_op(n)]
99+
assert len(linears) == 2
100+
assert _determine_fused_weight_dims(linears) is None

0 commit comments

Comments
 (0)