SIGN IN SIGN UP

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