Skip to content

Commit c9688e9

Browse files
authored
Arm backend: Fix Rescale debug names (#20787)
Fix Rescale debug names cc @digantdesai @freddan80 @per @zingo @oscarandersson8218 @mansnils @Sebastian-Larsson @robell @rascani Signed-off-by: Elena Zhelezina <elena.zhelezina@arm.com>
1 parent 5a1d9fe commit c9688e9

2 files changed

Lines changed: 111 additions & 64 deletions

File tree

backends/arm/operators/op_tosa_rescale.py

Lines changed: 60 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -150,71 +150,67 @@ def _create_const_ops_for_rescale(
150150
return [multipliers.name, shifts.name, input_zp.name, output_zp.name]
151151

152152

153-
def _build_rescale(
154-
tosa_fb: Any,
155-
scale: list[float],
156-
input_node: Any,
157-
output_name: str,
158-
output_type: Any,
159-
input_zp: list[int],
160-
output_zp: list[int],
161-
rounding_mode: ts.RoundingMode,
162-
per_channel: bool = False,
163-
is_scale32: bool = True,
164-
input_unsigned: bool = False,
165-
output_unsigned: bool = False,
166-
):
167-
"""Insert a TOSA RESCALE operator configured for the quantized path.
153+
@register_node_visitor
154+
class RescaleVisitor(NodeVisitor):
155+
target = "tosa.RESCALE.default"
168156

169-
Args:
170-
tosa_fb (Any): Graph builder receiving the RESCALE operator.
171-
scale (list[float]): Scale factors applied during rescaling.
172-
input_node (Any): Input tensor node feeding the operator.
173-
output_name (str): Name assigned to the RESCALE output tensor.
174-
output_type (ts.DType): Data type of the output tensor.
175-
input_zp (list[int]): Quantization zero points for the input tensor.
176-
output_zp (list[int]): Quantization zero points for the output tensor.
177-
rounding_mode (ts.RoundingMode): Rounding policy for the RESCALE op.
178-
per_channel (bool): Whether scales are applied per output channel.
179-
is_scale32 (bool): Declared scale width; ignored when the input type is
180-
``ts.DType.INT48``.
157+
def _build_rescale(
158+
self,
159+
node: Node,
160+
tosa_graph: Any,
161+
scale: list[float],
162+
input_node: TosaArg,
163+
output: TosaArg,
164+
input_zp: list[int],
165+
output_zp: list[int],
166+
rounding_mode: ts.RoundingMode,
167+
per_channel: bool = False,
168+
input_unsigned: bool = False,
169+
output_unsigned: bool = False,
170+
) -> None:
171+
"""Insert a TOSA RESCALE operator configured for the quantized path.
181172
182-
"""
183-
scaleWidth = 16 if input_node.dtype == ts.DType.INT48 else 32
184-
is_scale32 = False if input_node.dtype == ts.DType.INT48 else True
185-
multipliers, shifts = _compute_multiplier_and_shift(scale, scaleWidth)
186-
rescale_inputs = _create_const_ops_for_rescale(
187-
tosa_fb,
188-
is_scale32,
189-
input_node.dtype,
190-
output_name,
191-
multipliers,
192-
shifts,
193-
input_zp,
194-
output_zp,
195-
output_type,
196-
ts,
197-
)
198-
attr_rescale = ts.TosaSerializerAttribute()
199-
attr_rescale.RescaleAttribute(
200-
scale32=is_scale32,
201-
rounding_mode=rounding_mode,
202-
per_channel=per_channel,
203-
input_unsigned=input_unsigned,
204-
output_unsigned=output_unsigned,
205-
)
173+
RESCALE is serialized through NodeVisitor._serialize_operator so that
174+
debug-location metadata is attached consistently with other TOSA ops.
175+
The scale width is derived from the input dtype: INT48 uses 16-bit
176+
multipliers, otherwise 32-bit multipliers are used.
206177
207-
tosa_fb.addOperator(
208-
ts.Op.RESCALE,
209-
[input_node.name, *rescale_inputs],
210-
[output_name],
211-
attr_rescale,
212-
)
178+
"""
179+
scale_width = 16 if input_node.dtype == ts.DType.INT48 else 32
180+
is_scale32 = input_node.dtype != ts.DType.INT48
213181

182+
multipliers, shifts = _compute_multiplier_and_shift(scale, scale_width)
214183

215-
@register_node_visitor
216-
class RescaleVisitor(NodeVisitor):
217-
target = "tosa.RESCALE.default"
184+
rescale_inputs = _create_const_ops_for_rescale(
185+
tosa_graph,
186+
is_scale32,
187+
input_node.dtype,
188+
output.name,
189+
multipliers,
190+
shifts,
191+
input_zp,
192+
output_zp,
193+
output.dtype,
194+
ts,
195+
)
196+
197+
attr_rescale = ts.TosaSerializerAttribute()
198+
attr_rescale.RescaleAttribute(
199+
scale32=is_scale32,
200+
rounding_mode=rounding_mode,
201+
per_channel=per_channel,
202+
input_unsigned=input_unsigned,
203+
output_unsigned=output_unsigned,
204+
)
205+
206+
self._serialize_operator(
207+
node,
208+
tosa_graph,
209+
ts.Op.RESCALE,
210+
[input_node.name, *rescale_inputs],
211+
[output.name],
212+
attr_rescale,
213+
)
218214

219215
def define_node(
220216
self,
@@ -254,12 +250,12 @@ def define_node(
254250
raise ValueError(
255251
f"If output dtype is not int8 or int16, output_zp must be 0. Got {ts.DTypeNames[output_dtype]}, {output_zp=}"
256252
)
257-
_build_rescale(
258-
tosa_graph,
253+
self._build_rescale(
254+
node=node,
255+
tosa_graph=tosa_graph,
259256
scale=scales,
260257
input_node=inputs[0],
261-
output_name=output.name,
262-
output_type=output.dtype,
258+
output=output,
263259
input_zp=[input_zp],
264260
output_zp=[output_zp],
265261
rounding_mode=ts.RoundingMode.SINGLE_ROUND,

backends/arm/test/misc/test_debug_feats.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,17 @@
2121
EthosU55PipelineINT,
2222
TosaPipelineFP,
2323
TosaPipelineINT,
24+
VgfPipeline,
2425
)
2526
from executorch.backends.test.harness.stages import StageType
2627

2728
input_t1 = Tuple[torch.Tensor] # Input x
29+
input_t2 = Tuple[torch.Tensor, torch.Tensor]
30+
31+
32+
class AddModel(torch.nn.Module):
33+
def forward(self, x, y):
34+
return x + y
2835

2936

3037
class Linear(torch.nn.Module):
@@ -363,3 +370,47 @@ def test_fail_dump_ops_u55_INT(capsys, test_data: input_t1):
363370
error_msg = "Can not get operator distribution for Vela command stream."
364371
with pytest.raises(NotImplementedError, match=error_msg):
365372
pipeline.run()
373+
374+
375+
def _is_rescale_op(op: dict) -> bool:
376+
return op.get("op") == "RESCALE" or op.get("opcode") == "RESCALE"
377+
378+
379+
@common.SkipIfNoModelConverter
380+
def test_vgf_rescale_tosa_debug_location_is_not_empty():
381+
# Repro test for a bug with debug loc for RESCALE
382+
with tempfile.TemporaryDirectory() as tmpdir:
383+
pipeline = VgfPipeline[input_t2](
384+
module=AddModel(),
385+
test_data=(torch.ones(5), 2 * torch.ones(5)),
386+
aten_op="torch.ops.aten.add.Tensor",
387+
exir_op="executorch_exir_dialects_edge__ops_aten_add_Tensor",
388+
run_on_vulkan_runtime=False,
389+
vgf_compiler_flags="--emit-debug-info",
390+
symmetric_io_quantization=True,
391+
custom_path=tmpdir,
392+
tosa_debug_mode=ArmCompileSpec.DebugMode.TOSA,
393+
tosa_spec="TOSA-1.0+INT",
394+
)
395+
396+
pipeline.run()
397+
398+
tosa_files = list(Path(tmpdir).glob("*.tosa"))
399+
assert tosa_files, "Expected VGF lowering to dump a TOSA artifact"
400+
401+
rescale_ops = []
402+
for tosa_file in tosa_files:
403+
with tosa_file.open("rb") as f:
404+
tosa_json = dbg_tosa_fb_to_json(f.read())
405+
406+
ops = tosa_json["regions"][0]["blocks"][0]["operators"]
407+
rescale_ops.extend([op for op in ops if _is_rescale_op(op)])
408+
409+
assert (
410+
rescale_ops
411+
), "Expected the quantized AddModel repro to emit TOSA RESCALE ops"
412+
413+
for op in rescale_ops:
414+
location_text = op["location"]["text"]
415+
assert location_text, f"RESCALE op has empty debug location: {op}"
416+
json.loads(location_text)

0 commit comments

Comments
 (0)