Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion backends/samsung/_passes/conv1d_to_conv2d.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ def update_kernel(self, weight_node: torch.Tensor):
weight_3d, -1
)
else:
RuntimeError("Weight of 1d conv should be constant tensor or Parameter obj")
raise RuntimeError(
"Weight of 1d conv should be constant tensor or Parameter obj"
)
weight_node.meta["val"] = weight_node.meta["val"].data.unsqueeze(dim=-1)

def call(self, graph_module: torch.fx.GraphModule):
Expand Down
5 changes: 4 additions & 1 deletion backends/xnnpack/operators/node_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,10 @@ def define_tensor( # noqa: C901
elif quant_params.axis == 1:
quant_params.axis = 0
else:
assert f"Unsupported weight per channel quantization axis for depthwise conv2d / conv_transpose2d : {quant_params.axis}, expecting 0 / 1."
check_or_raise(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shoudln't we use 'why()'?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why() here is WhyNoPartition (exir/backend/utils.py), and every call to it in this backend sits in partition/config/*.py, or in a pass that receives it as an argument: it logs a reason and the caller returns False, so the node is left on CPU. define_tensor runs after partitioning, at serialization time, so there is no why in scope and nothing left to decline. The options here are to raise or to serialize a wrong axis silently. I used the helper this file already uses (four other check_or_raise calls), and op_conv2d.py performs these same two axis checks one level up (lines 88 and 93), which is why this else should be unreachable on the conv2d path.

Your question did surface two things, though.

First, check_or_raise(False, ...) is the only literal-False call in the repo. Happy to give it a real condition, which also drops the else:

check_or_raise(
    quant_params.axis in (0, 1),
    f"Unsupported weight per channel quantization axis for depthwise conv2d / conv_transpose2d : {quant_params.axis}, expecting 0 / 1.",
)
if quant_params.axis == 0:
    quant_params.axis = len(dims) - 1
else:
    quant_params.axis = 0

Second, ConvolutionConfig.check_constraints uses why() for the transpose per-channel axis (gemm_configs.py:406), but there is no equivalent gate for the depthwise one. A per-channel depthwise conv with axis != 0 is therefore partitioned and then raises at op_conv2d.py:93, instead of staying on CPU. The axis is read verbatim from the dequant node's 4th arg (quant_params.py:272), so a quantizer other than XNNPACKQuantizer can produce it. Would you like that why() gate added here, or kept out of this PR (which only makes dead guards fire) and done as a follow-up?

False,
f"Unsupported weight per channel quantization axis for depthwise conv2d / conv_transpose2d : {quant_params.axis}, expecting 0 / 1.",
)

# Serialize tensor value
custom_meta = tensor.meta.get("custom", None)
Expand Down
2 changes: 1 addition & 1 deletion export/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -714,7 +714,7 @@ def print_delegation_info(self) -> None:
)
if not lowering_stage:
raise RuntimeError(
"No delegation info available, atleast one of the lowering stages should be present"
"No delegation info available, at least one of the lowering stages should be present"
)

stage_artifact = self._stage_to_artifacts.get(lowering_stage[0])
Expand Down
48 changes: 48 additions & 0 deletions export/tests/test_print_delegation_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.


import unittest

import torch
from executorch.export import ExportRecipe, ExportSession
from executorch.export.types import StageType


class Tiny(torch.nn.Module):
def forward(self, x: torch.Tensor) -> torch.Tensor:
return x + 1


class TestPrintDelegationInfoGuards(unittest.TestCase):
def test_print_delegation_info_requires_a_lowering_stage(self) -> None:
session = ExportSession(
model=Tiny(),
example_inputs=[(torch.randn(2, 3),)],
export_recipe=ExportRecipe(
name="test",
pipeline_stages=[StageType.TORCH_EXPORT],
),
)
with self.assertRaises(RuntimeError) as cm:
session.print_delegation_info()
self.assertIn("at least one of the lowering stages", str(cm.exception))

def test_print_delegation_info_requires_lowering_artifact(self) -> None:
session = ExportSession(
model=Tiny(),
example_inputs=[(torch.randn(2, 3),)],
export_recipe=ExportRecipe(
name="test",
pipeline_stages=[
StageType.TORCH_EXPORT,
StageType.TO_EDGE_TRANSFORM_AND_LOWER,
],
),
)
with self.assertRaises(RuntimeError) as cm:
session.print_delegation_info()
self.assertIn("run the lowering stage first", str(cm.exception))
Loading