Skip to content

Add extended support from new Neutron C flow for Clamp operator#19510

Open
StrycekSimon wants to merge 4 commits into
pytorch:mainfrom
nxp-upstream:feature/nxg10272/EIEX-864-add-clamp-support-using-new-neutron-flow
Open

Add extended support from new Neutron C flow for Clamp operator#19510
StrycekSimon wants to merge 4 commits into
pytorch:mainfrom
nxp-upstream:feature/nxg10272/EIEX-864-add-clamp-support-using-new-neutron-flow

Conversation

@StrycekSimon
Copy link
Copy Markdown
Collaborator

@StrycekSimon StrycekSimon commented May 12, 2026

Summary

  • Moves flag indicating use of the new Neutron C flow from CustomCompileConfig to NeutronTargetSpec
  • Adds new Neutron C flow support for Clamp operator

Test plan

New test cases for Clamp are introduced. The relocation of new flag is covered by already existing unit tests.

cc @robert-kalmar @JakeStevens @digantdesai @rascani

@pytorch-bot
Copy link
Copy Markdown

pytorch-bot Bot commented May 12, 2026

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/19510

Note: Links to docs will display an error until the docs builds have been completed.

❗ 1 Active SEVs

There are 1 currently active SEVs. If your PR is affected, please view them below:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label May 12, 2026
@StrycekSimon StrycekSimon added module: nxp Issues related to NXP Neutron NPU delegation and code under backends/nxp/ release notes: nxp Changes to the NXP Neutron backend delegate labels May 12, 2026
@StrycekSimon StrycekSimon force-pushed the feature/nxg10272/EIEX-864-add-clamp-support-using-new-neutron-flow branch from 38c5e26 to 3a90b03 Compare May 13, 2026 05:45
@StrycekSimon StrycekSimon force-pushed the feature/nxg10272/EIEX-864-add-clamp-support-using-new-neutron-flow branch from 3a90b03 to f65fe45 Compare May 15, 2026 07:50
@StrycekSimon StrycekSimon changed the title Draft: Add extended support from new Neutron C flow for Clamp operator Add extended support from new Neutron C flow for Clamp operator May 15, 2026
@StrycekSimon StrycekSimon marked this pull request as ready for review May 15, 2026 08:20
Copilot AI review requested due to automatic review settings May 15, 2026 08:20
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR moves the use_new_flow_neutron_c flag from CustomDelegationOptions onto NeutronTargetSpec so it can be threaded through the quantizer and converters as part of target configuration, and extends the new Neutron‑C flow's clamp support beyond Relu‑shaped bounds by emitting Maximum/Minimum ops with quantized constants.

Changes:

  • Move use_new_flow_neutron_c from CustomDelegationOptions to NeutronTargetSpec, updating all call sites and converters that read the flag.
  • Extend ClampConverter to lower arbitrary clamp bounds to a Max+Min sequence under the new flow, factoring _is_convertible_to_relu out as a helper used by both the converter and the new ClampPattern.get_anchors.
  • Refactor QuantizationPattern to require neutron_quantizer in the base constructor, propagate this through all subclasses, and rework ClampPattern to use a shared‑spec anchor under the new flow.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
backends/nxp/backend/custom_delegation_options.py Removes use_new_flow_neutron_c from CustomDelegationOptions.
backends/nxp/backend/neutron_target_spec.py Adds use_new_flow_neutron_c constructor argument and attribute.
backends/nxp/nxp_backend.py Threads flag into NeutronTargetSpec constructions; drops it from CustomDelegationOptions.
backends/nxp/backend/ir/converter/node_converters/ops_converters/abs_converter.py Reads new flag from target spec instead of delegation options.
backends/nxp/backend/ir/converter/node_converters/ops_converters/avg_pool_2d_converter.py Same flag-source switch.
backends/nxp/backend/ir/converter/node_converters/ops_converters/max_pool2d_with_indices_converter.py Same flag-source switch.
backends/nxp/backend/ir/converter/node_converters/ops_converters/mul_tensor_converter.py Same flag-source switch.
backends/nxp/backend/ir/converter/node_converters/ops_converters/clamp_converter.py Adds Max/Min lowering for non‑Relu clamp bounds under new flow; introduces _is_convertible_to_relu.
backends/nxp/quantizer/patterns.py QuantizationPattern now takes neutron_quantizer; ClampPattern adds shared‑spec anchors when new flow is on.
backends/nxp/quantizer/neutron_quantizer.py Passes self into all pattern constructors.
backends/nxp/tests/executorch_pipeline.py Construct NeutronTargetSpec with the new flag; minor formatting.
backends/nxp/tests/ir/converter/node_converter/test_clamp_converter.py Adds parameterized tests for clamp under the new Neutron‑C flow.
examples/nxp/aot_neutron_compile.py Forwards CLI flag into NeutronTargetSpec.
Comments suppressed due to low confidence (1)

backends/nxp/backend/ir/converter/node_converters/ops_converters/clamp_converter.py:169

  • The min/max constant tensors are hard-coded to np.int8, but _is_supported_on_target returns True unconditionally when use_new_flow_neutron_c is set, with no dtype filter. If the input is uint8 (which is listed as a supported type by other converters in this flow, e.g. abs_converter.py, avg_pool_2d_converter.py), values >127 will silently overflow when stored as int8, producing incorrect outputs. The dtype passed to np.array should be derived from the dequant node's dtype (or from quant_min/quant_max).
        min_tensor = self.builder.create_tensor_for_data(
            np.array([min_value], np.int8), "min"
        )
        self.propagate_quantization(x, min_tensor)
        max_tensor = self.builder.create_tensor_for_data(
            np.array([max_value], np.int8), "max"
        )

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +83 to 84
return True

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Nice one. :)

Comment on lines +425 to +446
if (
self.neutron_quantizer.neutron_target_spec.use_new_flow_neutron_c
and not _is_convertible_to_relu(node)
):
# Shared spec pattern
assert len(fused_partition[0].input_nodes) == 1
prev_node = fused_partition[0].input_nodes[0]

# Previous node was not quantized => we are not able to share q-params
if Q_ANNOTATION_KEY not in prev_node.meta:
return None

qspec = SharedQuantizationSpec(prev_node)

return PartitionAnchors(
inputs=[(node, NodeArgsIdx(0))],
weights=[],
biases=[],
output=[
(node, qspec),
],
)
@@ -1 +1 @@
# Copyright 2025 NXP
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit: 2026

class BatchNormPattern(QuantizationPattern):
def __init__(self, is_qat: bool):
super().__init__(is_qat=is_qat)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why was this removed?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It was not needed as is was already identical with QuantizationPattern init. Since now the QuantizationPattern has also the neutron_quantizer parameter, this init overrides the QuantizationPattern with inconsistent number of parameters - so I removed it.

if bounds not in ClampConverter.SUPPORTED_BOUNDS.values():
return False
if neutron_target_spec.use_new_flow_neutron_c:
return True
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please add type checking here.

if not NodeConverter.uses_quantization_type_for_io(
    node,
    supported_types=[torch.int8, torch.uint8],
    input_indices=[0],
    output_indices=[0],
):
    return False



class ClampConverter(NodeConverter):
SUPPORTED_BOUNDS = {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This should be renamed now as there are not the only supported bounds.


@staticmethod
def propagate_quantization(from_node, to_node):
to_node.quantization = copy(from_node.quantization)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please add type hints.
A torch.fx Node doesn't have a quantization attribute.

Edit:
This method is used with Neutron IR tensors in the code, so the parameters should be renamed. There already is a function which does this: quantization_utils.py:propagate_quantization().

Since you used the same name, were you aware of this and was there a reason you wrote this method instead?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I didn't know the function existed. There is no need to reimplement it as it does the same thing.

return np.clip(rescaled_value, quant_min, quant_max)

def convert(self, node: Node):
"""Convert the `aten.clamp.default` operator to Neutron IR `Relu*` operators.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is no longer true. It can be converted to other NeutronIR operators.

if x.quantization != y.quantization:
raise AssertionError(
"Input and output quantization should be same in order to convert to max/min."
)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This check should be in the is_supported_on_target() method.
I understand you updated the clamp pattern to quantize with this in mind, but someone could use a different quantizer (for example when combining backends), and we don't want to raise an exception in that case.


# Make sure the `clamp` was NOT delegated.
assert graph_contains_any_of_ops(delegated_ep.graph, [Clamp])

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please add the new tests inside a class. It's a new convention we have started with the new flow:
class TestClampNewNeutronFlow:
We can then use shorter test names with improved clarity.

Please draw inspiration from other open PRs which enable new ops (e.g. here). The TestClampNewNeutronFlow class should test all aspects of the operator with the new flow, because eventually we will remove the old tests with the old flow. So please add more tests.

# Make sure the tested program contains the `clamp`.
assert graph_contains_any_of_ops(intermediate_ep.graph, [Clamp])

convert_run_compare(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please use lower_run_compare instead. We no longer want to compare the Neutron IR model with the edge model. Instead, we are now always testing the full models with Neutron delegate calls (with NSYS).

Again, you can draw inspiration from other similar PRs.

pytest.param(10, 17, id="min = 10, max = 17 (Max/Min)"),
pytest.param(0, 1, id="min = 0, max = 1 (Relu0To1)"),
pytest.param(-1, 1, id="min = -1, max = 1 (ReluN1To1)"),
pytest.param(0, None, id="min = 0, max = None (Relu)"),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Would be nice to see some assertions that the node is indeed converted to a Relu variant when possible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. module: nxp Issues related to NXP Neutron NPU delegation and code under backends/nxp/ release notes: nxp Changes to the NXP Neutron backend delegate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants