Arm backend: Give SiLU its own output quantization params (#21437)
### Summary SiLU's quantized output is forced to reuse its input's quantization scale, which wastes resolution for no benefit. This commit gives SiLU its own output quantization spec. ### Where this happens: | Quantizer | Mechanism | `silu.default` | `silu_.default` | | --- | --- | --- | --- | | Composable (`_TOSAQuantizerV2`, default since #19758) | `TOSAQuantizationConfig.<br>SHARED_OUTPUT_ACT_QSPEC_PATTERNS` | shared (bug) | shared (bug) | | Legacy (`_TOSAQuantizerV1`) | `quantization_annotator.py` op sets | own qspec | shared (bug) | ### Why quantization sharing is wrong for SiLU: <img width="512" height="492" alt="image" src="https://github.com/user-attachments/assets/ea71702f-bbe0-402a-b2b0-595b25e0d170" /> - **SiLU non-linearly compresses and warps its input range:** - Unlike ReLU, which maintains a 1:1 linear mapping ($y = x$) for non-negative inputs, SiLU is non-linear across its entire domain. - For negative inputs, its output never goes below -0.278, no matter how negative the input gets. So its output range is always narrower than its input's. Reusing the input scale misaligns the quantization bins with the actual output distribution, **wasting resolution.** - **Reusing the input's scale doesn't save any computation at runtime:** SiLU runs as a lookup table (`class TableOps`) whose entries are precomputed at compile time, not runtime. ### Changes | File | Change | Why | | --- | --- | --- | | `quantizer/quantization_config.py` | Remove `silu.default`/`silu_.default` from `SHARED_OUTPUT_ACT_QSPEC_PATTERNS` | The accuracy loss as described above. | | `quantizer/quantization_annotator.py` | Move `silu_.default` into `_one_to_one`, next to `silu.default` | The legacy annotator classified the two variants differently. #17202 put `silu.default` in `_one_to_one` (own qspec) but `silu_.default` in `_one_to_one_shared_input_or_input_act_qspec` (shared qspec). | | `test/ops/test_silu.py` | `Silu.forward` clones its input | Latent bug found while working on this change: SiLU(inplace=True) was mutating the test pipeline's shared input tensor which the pipeline reuses. It stayed invisible because the buggy (too large) output scale made the comparison helper(`compare_rel_frobenius_and_cosine_similarity()`) treat every value as noise and skip the check entirely, so the test kept passing without validating anything. | | `test/misc/test_shared_qspecs.py` | Add `NonSharedQspecSilu` + `test_silu_does_not_share_input_qspec` | Check the emitted q/dq parameters, the same way the rest of `test_shared_qspecs.py` checks sharing. | ### Testing ```bash pytest backends/arm/test/misc/test_shared_qspecs.py # 17 passed pytest backends/arm/test/ops/test_silu.py -k "not vgf" # 34 passed, 32 xfailed ``` cc @kimishpatel @jerryzh168 @digantdesai @freddan80 @per @zingo @oscarandersson8218 @mansnils @Sebastian-Larsson @robell @rascani Signed-off-by: Youngsik Yang <vacu9708@gmail.com>
Y
Youngsik Yang committed
761c64cfbb1d32d2fa5a37f6de9bc79e5a6f4b11
Parent: d29706a
Committed by GitHub <noreply@github.com>
on 7/31/2026, 6:12:29 AM