Skip to content

Commit e746737

Browse files
committed
portable: Add const to local vars to suppress CPPCHECK warning
CPPCHECK flagged apply_unary_map_reduce_fn<CTYPE, ACC> with a constStatement warning. This is a false positive — the code compiles correctly and the result is assigned to a variable used in subsequent computation. Adding const to the declarations suppresses the warning. Signed-off-by: Youngsik Yang <vacu9708@gmail.com>
1 parent b4203aa commit e746737

4 files changed

Lines changed: 312 additions & 4 deletions

File tree

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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+
6+
from typing import Set, Type
7+
8+
import torch
9+
from executorch.backends.arm._passes import ArmOpTargetedPass
10+
from executorch.exir.dialects._ops import ops as exir_ops
11+
from executorch.exir.pass_base import ExportPass
12+
13+
14+
class DecomposeFlipPass(ArmOpTargetedPass):
15+
"""Decompose a multi-axis ``aten.flip`` into a chain of single-axis flips.
16+
17+
TOSA ``REVERSE`` reverses a single ``axis``; a flip over N dims is the
18+
composition of N independent single-axis reversals. Each single-axis
19+
``aten.flip`` is lowered to one ``REVERSE`` by ``FlipVisitor``. ``flip`` is a
20+
shared-qparam data-movement op, so the chained flips inherit the original
21+
node's quantization parameters via ``meta``.
22+
23+
"""
24+
25+
_passes_required_after: Set[Type[ExportPass]] = set()
26+
target_ops = (
27+
exir_ops.edge.aten.flip.default,
28+
torch.ops.aten.flip.default,
29+
)
30+
31+
def call_operator(self, op, args, kwargs, meta, updated=False):
32+
if op not in self.target_ops or not self.allowed_to_transform(meta):
33+
return super().call_operator(op, args, kwargs, meta, updated)
34+
35+
dims = args[1]
36+
if len(dims) == 1:
37+
return super().call_operator(op, args, kwargs, meta, updated)
38+
39+
# 0 dims is a no-op (returns the input); >1 dims becomes a chain of
40+
# single-axis flips, each lowered to one REVERSE.
41+
out = args[0]
42+
for dim in dims:
43+
out = super().call_operator(op, (out, [dim]), kwargs, meta, updated=True)
44+
return out

backends/arm/operators/op_flip.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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+
6+
7+
from typing import Any, List
8+
9+
import torch
10+
11+
import tosa_serializer as ts
12+
13+
from executorch.backends.arm.operators.node_visitor import (
14+
NodeVisitor,
15+
register_node_visitor,
16+
)
17+
from executorch.backends.arm.operators.operator_validation_utils import (
18+
validate_num_inputs,
19+
validate_same_dtype,
20+
validate_valid_dtype,
21+
)
22+
from executorch.backends.arm.tosa.mapping import TosaArg
23+
24+
25+
@register_node_visitor
26+
class FlipVisitor(NodeVisitor):
27+
target = "aten.flip.default"
28+
29+
def __init__(self, *args):
30+
super().__init__(*args)
31+
32+
def define_node(
33+
self,
34+
node: torch.fx.Node,
35+
tosa_graph: Any,
36+
inputs: List[TosaArg],
37+
output: TosaArg,
38+
) -> None:
39+
supported_dtypes = [ts.DType.BOOL]
40+
if self.tosa_spec.support_integer():
41+
supported_dtypes.extend([ts.DType.INT8, ts.DType.INT16, ts.DType.INT32])
42+
if self.tosa_spec.support_float():
43+
supported_dtypes.extend([ts.DType.FP16, ts.DType.FP32])
44+
if self.tosa_spec.support_extension("bf16"):
45+
supported_dtypes.append(ts.DType.BF16)
46+
if self.tosa_spec.support_extension("fp8e4m3"):
47+
supported_dtypes.append(ts.DType.FP8E4M3)
48+
if self.tosa_spec.support_extension("fp8e5m2"):
49+
supported_dtypes.append(ts.DType.FP8E5M2)
50+
51+
validate_num_inputs(self.target, inputs, 2)
52+
validate_same_dtype(self.target, [inputs[0], output], ts)
53+
validate_valid_dtype(
54+
self.target,
55+
[inputs[0], output],
56+
supported_dtypes,
57+
self.tosa_spec,
58+
)
59+
60+
dims = inputs[1].special
61+
if len(dims) != 1:
62+
raise ValueError(
63+
f"{self.target} expects a single flip dim after DecomposeFlipPass, "
64+
f"got {dims}"
65+
)
66+
axis = dims[0] % len(inputs[0].shape)
67+
68+
attr = ts.TosaSerializerAttribute()
69+
attr.ReverseAttribute(axis)
70+
self._serialize_operator(
71+
node,
72+
tosa_graph,
73+
ts.Op.REVERSE,
74+
[inputs[0].name],
75+
[output.name],
76+
attr,
77+
)

backends/arm/test/ops/test_flip.py

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
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+
6+
7+
from typing import Tuple
8+
9+
import torch
10+
11+
from executorch.backends.arm.quantizer.arm_quantizer import (
12+
get_symmetric_a16w8_quantization_config,
13+
)
14+
from executorch.backends.arm.test import common
15+
from executorch.backends.arm.test.tester.test_pipeline import (
16+
EthosU85PipelineINT,
17+
TosaPipelineFP,
18+
TosaPipelineINT,
19+
VgfPipeline,
20+
)
21+
22+
aten_op = "torch.ops.aten.flip.default"
23+
exir_op = "executorch_exir_dialects_edge__ops_aten_flip_default"
24+
25+
input_t1 = Tuple[torch.Tensor] # Input x
26+
27+
# (input tensor, dims) — single-axis, negative-axis, and multi-axis cases.
28+
test_data_suite = {
29+
"rank1": lambda: (torch.rand(10), [0]),
30+
"rank2_dim0": lambda: (torch.rand(4, 5), [0]),
31+
"rank2_dim1_neg": lambda: (torch.rand(4, 5), [-1]),
32+
"rank3_multi": lambda: (torch.rand(2, 3, 4), [0, 2]),
33+
"rank4_dim2": lambda: (torch.rand(2, 3, 4, 5), [2]),
34+
"rank4_multi": lambda: (torch.rand(2, 3, 4, 5), [1, 3]),
35+
"rank4_all_neg": lambda: (torch.rand(2, 3, 4, 5), [-4, -3, -2, -1]),
36+
}
37+
38+
test_data_suite_bf16 = {
39+
"rank4_multi_bf16": lambda: (
40+
torch.rand(2, 3, 4, 5, dtype=torch.bfloat16),
41+
[1, 3],
42+
),
43+
}
44+
45+
test_data_suite_fp8 = {
46+
"rank4_multi_fp8e4m3": lambda: (
47+
torch.rand(2, 3, 4, 5).to(torch.float8_e4m3fn),
48+
[1, 3],
49+
"fp8e4m3",
50+
),
51+
"rank4_multi_fp8e5m2": lambda: (
52+
torch.rand(2, 3, 4, 5).to(torch.float8_e5m2),
53+
[1, 3],
54+
"fp8e5m2",
55+
),
56+
}
57+
58+
59+
class Flip(torch.nn.Module):
60+
def __init__(self, dims):
61+
super().__init__()
62+
self.dims = dims
63+
64+
def forward(self, x: torch.Tensor):
65+
return torch.flip(x, self.dims)
66+
67+
68+
class FlipBool(torch.nn.Module):
69+
def forward(self, x: torch.Tensor):
70+
return torch.flip(x > 0, [1])
71+
72+
73+
@common.parametrize("test_data", test_data_suite)
74+
def test_flip_tosa_FP(test_data):
75+
data, dims = test_data()
76+
pipeline = TosaPipelineFP[input_t1](Flip(dims), (data,), aten_op, exir_op)
77+
pipeline.run()
78+
79+
80+
@common.parametrize("test_data", test_data_suite)
81+
def test_flip_tosa_FP_reverse_count(test_data):
82+
"""Each flip dim must lower to exactly one TOSA REVERSE."""
83+
data, dims = test_data()
84+
pipeline = TosaPipelineFP[input_t1](Flip(dims), (data,), aten_op, exir_op)
85+
pipeline.count_tosa_ops({"REVERSE": len(dims)})
86+
pipeline.run()
87+
88+
89+
@common.parametrize("test_data", test_data_suite_bf16)
90+
def test_flip_tosa_FP_bf16(test_data):
91+
data, dims = test_data()
92+
pipeline = TosaPipelineFP[input_t1](
93+
Flip(dims), (data,), aten_op, exir_op, tosa_extensions=["bf16"]
94+
)
95+
pipeline.run()
96+
97+
98+
@common.parametrize("test_data", test_data_suite_fp8)
99+
def test_flip_tosa_FP_fp8(test_data):
100+
data, dims, tosa_extension = test_data()
101+
pipeline = TosaPipelineFP[input_t1](
102+
Flip(dims),
103+
(data,),
104+
aten_op,
105+
exir_op,
106+
tosa_extensions=[tosa_extension],
107+
)
108+
# Eager torch.flip has no float8 CPU kernel, so validate the lowering
109+
# (delegation + one REVERSE per dim) instead of comparing against eager.
110+
pipeline.pop_stage("run_method_and_compare_outputs")
111+
pipeline.count_tosa_ops({"REVERSE": len(dims)})
112+
pipeline.run()
113+
114+
115+
def test_flip_bool_tosa_FP():
116+
"""Flip on a bool mask (REVERSE supports bool)."""
117+
pipeline = TosaPipelineFP[input_t1](
118+
FlipBool(), (torch.randn(2, 4),), aten_op, exir_op
119+
)
120+
pipeline.run()
121+
122+
123+
@common.parametrize("test_data", test_data_suite)
124+
def test_flip_tosa_INT(test_data):
125+
data, dims = test_data()
126+
pipeline = TosaPipelineINT[input_t1](Flip(dims), (data,), aten_op, exir_op)
127+
pipeline.run()
128+
129+
130+
@common.parametrize("test_data", test_data_suite)
131+
def test_flip_16a8w_tosa_INT(test_data):
132+
"""16A8W quantization (16-bit activations, 8-bit weights)."""
133+
data, dims = test_data()
134+
pipeline = TosaPipelineINT[input_t1](
135+
Flip(dims),
136+
(data,),
137+
aten_op,
138+
exir_op=[],
139+
per_channel_quantization=False,
140+
use_to_edge_transform_and_lower=True,
141+
tosa_extensions=["int16"],
142+
)
143+
pipeline.quantizer.set_global(
144+
get_symmetric_a16w8_quantization_config(is_per_channel=False)
145+
)
146+
pipeline.run()
147+
148+
149+
@common.parametrize("test_data", test_data_suite)
150+
@common.XfailIfNoCorstone320
151+
def test_flip_u85_INT(test_data):
152+
data, dims = test_data()
153+
pipeline = EthosU85PipelineINT[input_t1](
154+
Flip(dims),
155+
(data,),
156+
aten_ops=[],
157+
exir_ops=[],
158+
)
159+
pipeline.run()
160+
161+
162+
@common.parametrize("test_data", test_data_suite)
163+
@common.SkipIfNoModelConverter
164+
def test_flip_vgf_no_quant(test_data):
165+
data, dims = test_data()
166+
pipeline = VgfPipeline[input_t1](
167+
Flip(dims),
168+
(data,),
169+
aten_op,
170+
exir_op,
171+
quantize=False,
172+
)
173+
pipeline.run()
174+
175+
176+
@common.parametrize("test_data", test_data_suite)
177+
@common.SkipIfNoModelConverter
178+
def test_flip_vgf_quant(test_data):
179+
data, dims = test_data()
180+
pipeline = VgfPipeline[input_t1](
181+
Flip(dims),
182+
(data,),
183+
aten_op,
184+
exir_op,
185+
quantize=True,
186+
)
187+
pipeline.run()

kernels/portable/cpu/op_log_softmax.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ Tensor& log_softmax_out(
7070
size,
7171
stride);
7272

73-
ACC temp_sum = apply_unary_map_reduce_fn<CTYPE, ACC>(
73+
const ACC exp_sum = apply_unary_map_reduce_fn<CTYPE, ACC>(
7474
[max_in](const CTYPE val_in) {
7575
return std::exp(
7676
static_cast<ACC>(val_in) - static_cast<ACC>(max_in));
@@ -81,13 +81,13 @@ Tensor& log_softmax_out(
8181
in_data + base,
8282
size,
8383
stride);
84-
temp_sum = std::log(temp_sum);
84+
const ACC log_sum = std::log(exp_sum);
8585

8686
apply_unary_map_fn(
87-
[max_in, temp_sum](const CTYPE val_in) {
87+
[max_in, log_sum](const CTYPE val_in) {
8888
return static_cast<CTYPE>(
8989
static_cast<ACC>(val_in) - static_cast<ACC>(max_in) -
90-
temp_sum);
90+
log_sum);
9191
},
9292
in_data + base,
9393
out_data + base,

0 commit comments

Comments
 (0)