Add quantized_div op (#21294)#21294
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/21294
Note: Links to docs will display an error until the docs builds have been completed. ✅ No FailuresAs of commit 2141c1e with merge base b20f16a ( This comment was automatically generated by Dr. CI and updates every 15 minutes. |
|
@3l1 has exported this pull request. If you are a Meta employee, you can view the originating Diff in D113440315. |
This PR needs a
|
Summary: Adds a `quantized_div` operator to the ExecuTorch Cortex-M (CMSIS-NN) backend, giving int8 elementwise division alongside the existing `quantized_add`/`quantized_mul` ops. CMSIS-NN has no integer elementwise-division primitive, so — following the Arm reference path in bolt's `flatbuffers_ops` backend — the quotient of the zero-point-corrected int8 operands is evaluated in float and rescaled by the effective scale `scale_a / (scale_b * scale_out)`. That scale is folded AoT into `output_multiplier`/`output_shift` (so the schema matches `quantized_mul`) and reconstructed to a float in the kernel the same way `op_softmax` reconstructs its input scale. A zero denominator maps to a 0 quotient in both the kernel and the reference. Division is not commutative, so the operand-swap trick that lets `quantized_mul` support channel broadcasting does not apply here; the op and its quantizer pattern check (`CortexMDivCheck`) require identically shaped int8 per-tensor inputs and reject any broadcasting. Wiring: `operators.py` (schema + reference impl), `op_quantized_div.cpp` (kernel), `operators.yaml`/`targets.bzl`/`CMakeLists.txt` (registration and build), `aten_to_cortex_m_pass.py` (`aten.div.Tensor` lowering), and the quantizer support dict. All changes are mirrored across the fbcode and xplat trees. Review order: start with `op_quantized_div.cpp` and the `quantized_div` block in `operators.py` (the arithmetic), then the pass and quantizer wiring, then the registration/build files and the test. Authored with Claude Code. Differential Revision: D113440315
ffc40e1 to
7a8d40f
Compare
Summary: Adds a `quantized_div` operator to the ExecuTorch Cortex-M (CMSIS-NN) backend, giving int8 elementwise division alongside the existing `quantized_add`/`quantized_mul` ops. CMSIS-NN has no integer elementwise-division primitive, so — following the Arm reference path in bolt's `flatbuffers_ops` backend — the quotient of the zero-point-corrected int8 operands is evaluated in float and rescaled by the effective scale `scale_a / (scale_b * scale_out)`. That scale is folded AoT into `output_multiplier`/`output_shift` (so the schema matches `quantized_mul`) and reconstructed to a float in the kernel the same way `op_softmax` reconstructs its input scale. A zero denominator maps to a 0 quotient in both the kernel and the reference. Division is not commutative, so the operand-swap trick that lets `quantized_mul` support channel broadcasting does not apply here; the op and its quantizer pattern check (`CortexMDivCheck`) require identically shaped int8 per-tensor inputs and reject any broadcasting. Wiring: `operators.py` (schema + reference impl), `op_quantized_div.cpp` (kernel), `operators.yaml`/`targets.bzl`/`CMakeLists.txt` (registration and build), `aten_to_cortex_m_pass.py` (`aten.div.Tensor` lowering), and the quantizer support dict. All changes are mirrored across the fbcode and xplat trees. Review order: start with `op_quantized_div.cpp` and the `quantized_div` block in `operators.py` (the arithmetic), then the pass and quantizer wiring, then the registration/build files and the test. Authored with Claude Code. Differential Revision: D113440315
7a8d40f to
984afe1
Compare
Summary: Adds a `quantized_div` operator to the ExecuTorch Cortex-M (CMSIS-NN) backend, giving int8 elementwise division alongside the existing `quantized_add`/`quantized_mul` ops. CMSIS-NN has no integer elementwise-division primitive, so the quotient of the zero-point-corrected int8 operands is evaluated in float and rescaled by the effective scale `scale_a / (scale_b * scale_out)`. That scale is folded AoT into `output_multiplier`/`output_shift` (so the schema matches `quantized_mul`) and reconstructed to a float in the kernel the same way `op_softmax` reconstructs its input scale. A zero denominator maps to a 0 quotient in both the kernel and the reference. Division is not commutative, so the operand-swap trick that lets `quantized_mul` support channel broadcasting does not apply here; the op and its quantizer pattern check (`CortexMDivCheck`) require identically shaped int8 per-tensor inputs and reject any broadcasting. Wiring: `operators.py` (schema + reference impl), `op_quantized_div.cpp` (kernel), `operators.yaml`/`targets.bzl`/`CMakeLists.txt` (registration and build), `aten_to_cortex_m_pass.py` (`aten.div.Tensor` lowering), and the quantizer support dict. All changes are mirrored across the fbcode and xplat trees. This also wires the Cortex-M op tests under `backends/cortex_m/test/ops/` into Buck for the first time — they previously ran only via raw `pytest`. `test/TARGETS` now generates one `python_pytest` target per op test file, plus the `tester` and `conftest` `python_library` wrappers the tests import. These targets live in `TARGETS` (fbcode-only), not `targets.bzl`/`BUCK`, so they never enter the OSS-parsed build graph; open-source keeps running these files via `pytest`. Authored with Claude Code. Differential Revision: D113440315
984afe1 to
083a996
Compare
Summary: Adds a `quantized_div` operator to the ExecuTorch Cortex-M (CMSIS-NN) backend, giving int8 elementwise division alongside the existing `quantized_add`/`quantized_mul` ops. CMSIS-NN has no integer elementwise-division primitive, so — following the Arm reference path in bolt's `flatbuffers_ops` backend — the quotient of the zero-point-corrected int8 operands is evaluated in float and rescaled by the effective scale `scale_a / (scale_b * scale_out)`. That scale is folded AoT into `output_multiplier`/`output_shift` (so the schema matches `quantized_mul`) and reconstructed to a float in the kernel the same way `op_softmax` reconstructs its input scale. A zero denominator maps to a 0 quotient in both the kernel and the reference. Differential Revision: D113440315
083a996 to
2c27357
Compare
Summary: Adds a `quantized_div` operator to the ExecuTorch Cortex-M (CMSIS-NN) backend, giving int8 elementwise division alongside the existing `quantized_add`/`quantized_mul` ops. CMSIS-NN has no integer elementwise-division primitive, so the quotient of the zero-point-corrected int8 operands is evaluated in float and rescaled by the effective scale `scale_a / (scale_b * scale_out)`. That scale is folded AoT into `output_multiplier`/`output_shift` (so the schema matches `quantized_mul`) and reconstructed to a float in the kernel the same way `op_softmax` reconstructs its input scale. A zero denominator maps to a 0 quotient in both the kernel and the reference. Differential Revision: D113440315
2c27357 to
6ee96cf
Compare
Summary: Adds a `quantized_div` operator to the ExecuTorch Cortex-M (CMSIS-NN) backend, giving int8 and int16 elementwise division alongside the existing `quantized_add`/`quantized_mul` ops. CMSIS-NN has no integer elementwise-division primitive, so the quotient of the zero-point-corrected operands is evaluated in float and rescaled by the effective scale `scale_a / (scale_b * scale_out)`. That scale is folded AoT into `output_multiplier`/`output_shift` (so the schema matches `quantized_mul`) and reconstructed to a float in the kernel the same way `op_softmax` reconstructs its input scale. A zero denominator maps to a 0 quotient in both the kernel and the reference. int8 and int16 activations are both supported. The kernel dispatches on the output dtype (`Char`/`Short`) into a templated loop, and the composite reference clamps to the dtype range. A new `INT16_PER_TENSOR_CONFIG` (symmetric int16 activations) is added, `CortexMDivCheck` accepts int8 or int16, and `CortexMQuantizer` takes an optional `per_tensor_config` so callers can opt into int16. The op schema and the lowering pass are dtype-transparent and unchanged. Differential Revision: D113440315
527ef5d to
8b5f571
Compare
| : 0.0f; | ||
|
|
||
| int32_t result = | ||
| static_cast<int32_t>(std::round(quotient * effective_scale)) + out_zp; |
There was a problem hiding this comment.
If we're not worried about performance here, we might want to keep this and the clamp in float to avoid any risk of exceeding int32 bounds.
Summary: Adds a `quantized_div` operator to the ExecuTorch Cortex-M (CMSIS-NN) backend, giving int8 and int16 elementwise division alongside the existing `quantized_add`/`quantized_mul` ops. CMSIS-NN has no integer elementwise-division primitive, so the quotient of the zero-point-corrected operands is evaluated in float and rescaled by the effective scale `scale_a / (scale_b * scale_out)`. That scale is folded AoT into `output_multiplier`/`output_shift` (so the schema matches `quantized_mul`) and reconstructed to a float in the kernel the same way `op_softmax` reconstructs its input scale. A zero denominator maps to a 0 quotient in both the kernel and the reference. int8 and int16 activations are both supported. The kernel dispatches on the output dtype (`Char`/`Short`) into a templated loop, and the composite reference clamps to the dtype range. A new `INT16_PER_TENSOR_CONFIG` (symmetric int16 activations) is added, `CortexMDivCheck` accepts int8 or int16, and `CortexMQuantizer` takes an optional `per_tensor_config` so callers can opt into int16. The op schema and the lowering pass are dtype-transparent and unchanged. Reviewed By: rascani Differential Revision: D113440315
8b5f571 to
dfd1c6f
Compare
Summary: Adds a `quantized_div` operator to the ExecuTorch Cortex-M (CMSIS-NN) backend, giving int8 and int16 elementwise division alongside the existing `quantized_add`/`quantized_mul` ops. CMSIS-NN has no integer elementwise-division primitive, so the quotient of the zero-point-corrected operands is evaluated in float and rescaled by the effective scale `scale_a / (scale_b * scale_out)`. That scale is folded AoT into `output_multiplier`/`output_shift` (so the schema matches `quantized_mul`) and reconstructed to a float in the kernel the same way `op_softmax` reconstructs its input scale. A zero denominator maps to a 0 quotient in both the kernel and the reference. int8 and int16 activations are both supported. The kernel dispatches on the output dtype (`Char`/`Short`) into a templated loop, and the composite reference clamps to the dtype range. A new `INT16_PER_TENSOR_CONFIG` (symmetric int16 activations) is added, `CortexMDivCheck` accepts int8 or int16, and `CortexMQuantizer` takes an optional `per_tensor_config` so callers can opt into int16. The op schema and the lowering pass are dtype-transparent and unchanged. Reviewed By: rascani Differential Revision: D113440315
dfd1c6f to
0df6fd1
Compare
Summary: Adds a `quantized_div` operator to the ExecuTorch Cortex-M (CMSIS-NN) backend, giving int8 and int16 elementwise division alongside the existing `quantized_add`/`quantized_mul` ops. CMSIS-NN has no integer elementwise-division primitive, so the quotient of the zero-point-corrected operands is evaluated in float and rescaled by the effective scale `scale_a / (scale_b * scale_out)`. That scale is folded AoT into `output_multiplier`/`output_shift` (so the schema matches `quantized_mul`) and reconstructed to a float in the kernel the same way `op_softmax` reconstructs its input scale. A zero denominator maps to a 0 quotient in both the kernel and the reference. int8 and int16 activations are both supported. The kernel dispatches on the output dtype (`Char`/`Short`) into a templated loop, and the composite reference clamps to the dtype range. A new `INT16_PER_TENSOR_CONFIG` (symmetric int16 activations) is added, `CortexMDivCheck` accepts int8 or int16, and `CortexMQuantizer` takes an optional `per_tensor_config` so callers can opt into int16. The op schema and the lowering pass are dtype-transparent and unchanged. Reviewed By: rascani Differential Revision: D113440315
0df6fd1 to
74ca6ee
Compare
Summary: Adds a `quantized_div` operator to the ExecuTorch Cortex-M (CMSIS-NN) backend, giving int8 and int16 elementwise division alongside the existing `quantized_add`/`quantized_mul` ops. CMSIS-NN has no integer elementwise-division primitive, so the quotient of the zero-point-corrected operands is evaluated in float and rescaled by the effective scale `scale_a / (scale_b * scale_out)`. That scale is folded AoT into `output_multiplier`/`output_shift` (so the schema matches `quantized_mul`) and reconstructed to a float in the kernel the same way `op_softmax` reconstructs its input scale. A zero denominator maps to a 0 quotient in both the kernel and the reference. int8 and int16 activations are both supported. The kernel dispatches on the output dtype (`Char`/`Short`) into a templated loop, and the composite reference clamps to the dtype range. A new `INT16_PER_TENSOR_CONFIG` (symmetric int16 activations) is added, `CortexMDivCheck` accepts int8 or int16, and `CortexMQuantizer` takes an optional `per_tensor_config` so callers can opt into int16. The op schema and the lowering pass are dtype-transparent and unchanged. Reviewed By: rascani Differential Revision: D113440315
74ca6ee to
ee5f86d
Compare
Summary: Pull Request resolved: #21294 Adds a `quantized_div` operator to the ExecuTorch Cortex-M (CMSIS-NN) backend, giving int8 and int16 elementwise division alongside the existing `quantized_add`/`quantized_mul` ops. CMSIS-NN has no integer elementwise-division primitive, so the quotient of the zero-point-corrected operands is evaluated in float and rescaled by the effective scale `scale_a / (scale_b * scale_out)`. That scale is folded AoT into `output_multiplier`/`output_shift` (so the schema matches `quantized_mul`) and reconstructed to a float in the kernel the same way `op_softmax` reconstructs its input scale. A zero denominator maps to a 0 quotient in both the kernel and the reference. int8 and int16 activations are both supported. The kernel dispatches on the output dtype (`Char`/`Short`) into a templated loop, and the composite reference clamps to the dtype range. A new `INT16_PER_TENSOR_CONFIG` (symmetric int16 activations) is added, `CortexMDivCheck` accepts int8 or int16, and `CortexMQuantizer` takes an optional `per_tensor_config` so callers can opt into int16. The op schema and the lowering pass are dtype-transparent and unchanged. Reviewed By: rascani Differential Revision: D113440315
Summary: Pull Request resolved: #21294 Adds a `quantized_div` operator to the ExecuTorch Cortex-M (CMSIS-NN) backend, giving int8 and int16 elementwise division alongside the existing `quantized_add`/`quantized_mul` ops. CMSIS-NN has no integer elementwise-division primitive, so the quotient of the zero-point-corrected operands is evaluated in float and rescaled by the effective scale `scale_a / (scale_b * scale_out)`. That scale is folded AoT into `output_multiplier`/`output_shift` (so the schema matches `quantized_mul`) and reconstructed to a float in the kernel the same way `op_softmax` reconstructs its input scale. A zero denominator maps to a 0 quotient in both the kernel and the reference. int8 and int16 activations are both supported. The kernel dispatches on the output dtype (`Char`/`Short`) into a templated loop, and the composite reference clamps to the dtype range. A new `INT16_PER_TENSOR_CONFIG` (symmetric int16 activations) is added, `CortexMDivCheck` accepts int8 or int16, and `CortexMQuantizer` takes an optional `per_tensor_config` so callers can opt into int16. The op schema and the lowering pass are dtype-transparent and unchanged. Reviewed By: rascani Differential Revision: D113440315
Erik-Lundell
left a comment
There was a problem hiding this comment.
I think you should revisit not supporting broadcasting, looks good otherwise.
|
|
||
| const float quotient = (denominator != 0) | ||
| ? static_cast<float>(numerator) / static_cast<float>(denominator) | ||
| : 0.0f; |
There was a problem hiding this comment.
Can you motivate the choice of 0 here?
| output_scale = node.meta["output_qparams"][0].scale | ||
| output_zero_point = node.meta["output_qparams"][0].zp | ||
|
|
||
| output_mult, output_shift = quantize_multiplier_aot( |
There was a problem hiding this comment.
Why do this an then reverse in the kernel? Is this just to mirror other operators?
| return out; | ||
| } | ||
|
|
||
| // Division is not commutative, so channel broadcasting (which relies on |
There was a problem hiding this comment.
This is not a mathematical blocker right? I don't see a good reason for not supporting broadcasting, the loop just needs to be a bit smarter about how it selects indices.
| class CortexMQuantizer(ComposableQuantizer): | ||
|
|
||
| def __init__(self) -> None: | ||
| def __init__(self, per_tensor_config: Optional[QuantizationConfig] = None) -> None: |
There was a problem hiding this comment.
Changes public API, but looks reasonable to me. Can you add a proper docstring?
|
|
||
| def __init__(self) -> None: | ||
| def __init__(self, per_tensor_config: Optional[QuantizationConfig] = None) -> None: | ||
| # Per-tensor activation config used for the "global" (non-conv) ops such |
There was a problem hiding this comment.
Hm, I have not heard of "global" ops. What does that mean?
|
|
||
| # Divisors are kept strictly positive so the quantized denominator never lands | ||
| # on its zero point (which the kernel maps to a 0 quotient). | ||
| test_cases = { |
There was a problem hiding this comment.
Can we get a test case with a very small denominator?
Summary:
Adds a
quantized_divoperator to the ExecuTorch Cortex-M (CMSIS-NN)backend, giving int8 and int16 elementwise division alongside the
existing
quantized_add/quantized_mulops.CMSIS-NN has no integer elementwise-division primitive, so the quotient
of the zero-point-corrected operands is evaluated in float and rescaled
by the effective scale
scale_a / (scale_b * scale_out). That scale isfolded AoT into
output_multiplier/output_shift(so the schema matchesquantized_mul) and reconstructed to a float in the kernel the same wayop_softmaxreconstructs its input scale. A zero denominator maps to a 0quotient in both the kernel and the reference.
int8 and int16 activations are both supported. The kernel dispatches on
the output dtype (
Char/Short) into a templated loop, and thecomposite reference clamps to the dtype range. A new
INT16_PER_TENSOR_CONFIG(symmetric int16 activations) is added,CortexMDivCheckaccepts int8 or int16, andCortexMQuantizertakes anoptional
per_tensor_configso callers can opt into int16. The op schemaand the lowering pass are dtype-transparent and unchanged.
Reviewed By: rascani
Differential Revision: D113440315