Skip to content

Commit 2b061a7

Browse files
authored
Arm backend: Fix for pad on VGF. (pytorch#24793)
When the TOSA specification includes FP but excludes bf16, TOSAPartitioner.ops_to_not_decompose() incorrectly preserves BF16 aten.pad. cc @digantdesai @freddan80 @per @zingo @oscarandersson8218 @mansnils @Sebastian-Larsson @robell @rascani Signed-off-by: Elena Zhelezina <elena.zhelezina@arm.com>
1 parent 45c091f commit 2b061a7

2 files changed

Lines changed: 88 additions & 0 deletions

File tree

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Copyright 2026 Arm Limited and/or its affiliates.
2+
#
3+
# This source code is licensed under the BSD-style license found in the
4+
# LICENSE file in the root directory of this source tree.
5+
"""Regression tests for BF16 aten.pad fallback without the TOSA bf16
6+
extension.
7+
"""
8+
9+
import pytest
10+
import torch
11+
import torch.nn.functional as F
12+
from executorch.backends.arm.tosa.compile_spec import TosaCompileSpec
13+
from executorch.backends.arm.tosa.partitioner import TOSAPartitioner
14+
from executorch.backends.arm.vgf import VgfCompileSpec, VgfPartitioner
15+
from executorch.exir import EdgeCompileConfig, to_edge_transform_and_lower
16+
17+
18+
class Pad(torch.nn.Module):
19+
def forward(self, x: torch.Tensor) -> torch.Tensor:
20+
return F.pad(x, (1, 1)) * 2
21+
22+
23+
def _pad_node(dtype: torch.dtype):
24+
ep = torch.export.export(Pad(), (torch.ones(1, 4, 8, dtype=dtype),))
25+
node = next(
26+
node for node in ep.graph.nodes if node.target == torch.ops.aten.pad.default
27+
)
28+
return ep, node
29+
30+
31+
@pytest.mark.parametrize("dtype", (torch.float32, torch.float16, torch.bfloat16))
32+
@pytest.mark.parametrize("enable_bf16", (False, True))
33+
def test_vgf_pad_preservation_respects_bf16_extension(dtype, enable_bf16):
34+
spec = "TOSA-1.0+FP+INT+int4+int16"
35+
if enable_bf16:
36+
spec += "+bf16"
37+
38+
ep, node = _pad_node(dtype)
39+
keep_ops, filter_fn = VgfPartitioner(VgfCompileSpec(spec)).ops_to_not_decompose(ep)
40+
41+
assert torch.ops.aten.pad.default in keep_ops
42+
assert filter_fn is not None
43+
# FP32/FP16 are supported by the FP profile. BF16 requires +bf16.
44+
assert filter_fn(node) is (dtype != torch.bfloat16 or enable_bf16)
45+
46+
47+
@pytest.mark.parametrize("enable_bf16", (False, True))
48+
def test_tosa_pad_preservation_respects_bf16_extension(enable_bf16):
49+
spec = "TOSA-1.0+FP" + ("+bf16" if enable_bf16 else "")
50+
ep, node = _pad_node(torch.bfloat16)
51+
keep_ops, filter_fn = TOSAPartitioner(TosaCompileSpec(spec)).ops_to_not_decompose(
52+
ep
53+
)
54+
55+
assert torch.ops.aten.pad.default in keep_ops
56+
assert filter_fn is not None
57+
assert filter_fn(node) is enable_bf16
58+
59+
60+
def test_vgf_bf16_pad_without_extension_exports_to_pte():
61+
# The entire BF16 graph stays portable with
62+
# this explicitly BF16-disabled spec.
63+
x = torch.randn(1, 4, 8, dtype=torch.bfloat16)
64+
ep = torch.export.export(Pad(), (x,))
65+
edge = to_edge_transform_and_lower(
66+
ep,
67+
partitioner=[VgfPartitioner(VgfCompileSpec("TOSA-1.0+FP+INT+int4+int16"))],
68+
compile_config=EdgeCompileConfig(_check_ir_validity=False),
69+
)
70+
targets = [
71+
str(node.target)
72+
for node in edge.exported_program().graph.nodes
73+
if node.op == "call_function"
74+
]
75+
assert any("constant_pad_nd" in target for target in targets)
76+
assert not any("aten.pad" in target for target in targets)
77+
assert not any("executorch_call_delegate" in target for target in targets)
78+
# The unfixed version raises: Missing out variants: {'aten::pad'}.
79+
edge.to_executorch()

‎backends/arm/tosa/partitioner.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,15 @@ def filter_fn(node: torch.fx.Node) -> bool:
10091009
"""
10101010
if _is_custom_partition_op(self._custom_partition_ops, node.target):
10111011
return True
1012+
# BF16 pad must decompose when the selected TOSA spec lacks +bf16.
1013+
# Otherwise it is left on the CPU as aten.pad, which has no
1014+
# portable out variant. Decomposition produces constant_pad_nd.
1015+
if (
1016+
node.target == torch.ops.aten.pad.default
1017+
and get_first_fake_tensor(node).dtype == torch.bfloat16
1018+
and not self.tosa_spec.support_extension("bf16")
1019+
):
1020+
return False
10121021
if (
10131022
node.target in ops_to_not_decompose
10141023
and get_first_fake_tensor(node).dtype == torch.float64

0 commit comments

Comments
 (0)