Skip to content

Cortex-M backend: dispatch quantized_linear AOT layout on target ISA#19676

Open
rascani wants to merge 1 commit into
pytorch:mainfrom
rascani:cortex-m-linear-bias-fix
Open

Cortex-M backend: dispatch quantized_linear AOT layout on target ISA#19676
rascani wants to merge 1 commit into
pytorch:mainfrom
rascani:cortex-m-linear-bias-fix

Conversation

@rascani
Copy link
Copy Markdown
Contributor

@rascani rascani commented May 19, 2026

Summary

CMSIS-NN's arm_fully_connected_s8 has three runtime paths, gated by compile-time ARM_MATH_MVEI / ARM_MATH_DSP. They split the bias and input_offset×sum(weight) offset term between two inputs, in incompatible conventions:

  • MVE: reads ctx.buf as a precomputed kernel_sum that must already include input_offset × sum(weight) and the bias contribution. The bias argument is (void)bias; — ignored.
  • DSP / scalar: read the bias argument directly and fold the input_offset contribution at runtime. ctx.buf (kernel_sum) is (void)kernel_sum; — ignored.

ConvertToCortexMPass._get_linear_replacement previously emitted only the MVE shape (kernel_sum populated, bias=None). On any non-MVE build the DSP/scalar path started the int32 accumulator at 0 instead of at bias + input_offset × sum(weight), dropping both the bias and the offset contribution. The accumulator wound up much smaller than intended, requantization collapsed it to the output zero point, and every classifier with a deep, narrow tail produced essentially uniform near-zero outputs on non-MVE Cortex-M builds.

Use the target-ISA plumbing added by the CortexMTargetConfig PR (#19470) to dispatch the right input shape at AOT time: on MVE targets emit kernel_sum with bias folded in (bias=None); on DSP and scalar targets emit the raw int32 bias directly (kernel_sum=None). The CMSIS-NN runtime then matches exactly what it expects.

Update quantized_linear_impl in operators.py to mirror the same contract: dispatch off whichever of kernel_sum / bias is non-None. Threading happens automatically via CortexMPassManager's signature injection of target_config into the pass's __init__.

Test Plan

Add backends/cortex_m/test/misc/test_quantized_linear_small_magnitude.py as a regression. A tiny nn.Linear(512, 10) on uniform[0, 0.002] input is the minimal reproducer for the small-magnitude regime where the missing offset terms dominate. The dialect test parametrizes over MVE/DSP/scalar target configs; the implementation test runs against whatever path the runner build matches.

The DSP & Scalar tests will need #19520 for CI testing.

Authored with Claude.

CMSIS-NN's `arm_fully_connected_s8` has three runtime paths, gated by
compile-time `ARM_MATH_MVEI` / `ARM_MATH_DSP`. They split the bias and
input_offset×sum(weight) offset term between two inputs, in
incompatible conventions:

* MVE (Helium): reads `ctx.buf` as a precomputed kernel_sum that must
  already include `input_offset × sum(weight)` and the bias
  contribution. The `bias` argument is `(void)bias;` — ignored.
* DSP / scalar (Armv7E-M, Armv7-M, Armv6-M, Armv8-M Baseline): read
  the `bias` argument directly and fold the input_offset contribution
  at runtime. `ctx.buf` (kernel_sum) is `(void)kernel_sum;` — ignored.

`ConvertToCortexMPass._get_linear_replacement` previously emitted only
the MVE shape (kernel_sum populated, bias=None). On any non-MVE build
the DSP/scalar path started the int32 accumulator at 0 instead of at
`bias + input_offset × sum(weight)`, dropping both the bias and the
offset contribution. The accumulator wound up much smaller than
intended, requantization collapsed it to the output zero point, and
every classifier with a deep, narrow tail produced essentially
uniform near-zero outputs on Cortex-M0/0+/M3/M4/M7/M23/M33 builds —
exactly where `CortexMTargetConfig.backend != cmsis_nn.Backend.MVE`.

Use the target-ISA plumbing added by the CortexMTargetConfig PR
(pytorch#19470) to dispatch the right input shape at AOT time: on MVE
targets emit kernel_sum with bias folded in (bias=None); on DSP and
scalar targets emit the raw int32 bias directly (kernel_sum=None).
The CMSIS-NN runtime then matches exactly what it expects — no
redundant copy of the bias in the .pte, no silent miscompute on
target mismatch (the runtime errors loudly if ctx.buf is None on an
MVE build, instead of producing garbage).

Update `quantized_linear_impl` in `operators.py` to mirror the same
contract: dispatch off whichever of kernel_sum / bias is non-None.
Threading happens automatically via `CortexMPassManager`'s signature
injection of `target_config` into the pass's `__init__`.

Add `backends/cortex_m/test/misc/test_quantized_linear_small_magnitude.py`
as a regression. A tiny `nn.Linear(512, 10)` on uniform[0, 0.002]
input is the minimal reproducer for the small-magnitude regime where
the missing offset terms dominate. The dialect test parametrizes over
MVE/DSP/scalar target configs; the implementation test runs against
whatever path the runner build matches.

Identified by bisecting STResNet Pico's int8-output collapse on
Corstone-300. The same collapse explains the historical MV2 / MV3
"deep classifier PTQ flakiness" xfails — both classifiers have
small-magnitude inputs to their final quantized_linear and likely
hit the same bug on any non-MVE deployment target.

Authored with Claude.
@pytorch-bot
Copy link
Copy Markdown

pytorch-bot Bot commented May 19, 2026

🔗 Helpful Links

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

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

❗ 2 Active SEVs

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

❌ 8 New Failures

As of commit 776a3a1 with merge base 1c9c115 (image):

NEW FAILURES - The following jobs have failed:

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 19, 2026
@rascani rascani requested review from AdrianLundell, mansnils and psiddh and removed request for mansnils May 19, 2026 17:15
@github-actions
Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant