Reject QDQ Gemm→QGemm fusion when alpha != 1 with bias (#28131)
### Description
Add an `alpha == 1.0` check to `GemmNodeGroupSelector::Check` (symmetric
to the existing `beta == 1.0` check) so the QDQ `Gemm → QGemm` fusion is
skipped when a bias is present and `alpha != 1`. Extend the QDQ Gemm
transformer tests (both the default and fastmath variants) with
`alpha_not_one` cases.
### Motivation and Context
Fixes #28130.
When a Gemm has `alpha != 1` and a bias, the QDQ fusion produces
incorrect results. The root cause is in the QGemm kernel:
```
acc_int32 = (A - zpA)(B - zpB) + C_int // bias added to int accumulator
Y_float = (alpha * sa * sb) * acc_int32 // output scale applied to everything
```
The output scale (`alpha * a_scale * b_scale`) is applied to the whole
int32 accumulator, which already contains the bias, so `alpha` ends up
multiplying the bias too:
```
Y = alpha * A_dq * B_dq + alpha * C_float (buggy)
Y = alpha * A_dq * B_dq + C_float (expected)
```
This matches the reporter's observation that the discrepancy vanishes
when the bias is zero.
This PR is the minimal correctness fix — it rejects the fusion in the
broken case rather than producing wrong output. The follow-up discussed
on the issue is to absorb `alpha` into the int32 bias at graph-transform
time (`C_int_new = round(C_int / alpha)` inside `GemmReplaceWithQuant`),
so `alpha != 1` cases can keep the fused path. That is a separate PR
because the precision/overflow characteristics want their own review.
### Test plan
- [x] Extend `QDQTransformerGemmTests` and its fastmath variant with an
`alpha_not_one` parameter. Set `alpha=2.0` on the Gemm node when true,
and adjust `check_binary_op_graph` so fusion is expected to be skipped
when bias is present.
- [x] On `main` the new cases fail — both on op count and on output
(`expected -0.624, got -0.429, diff 0.195, tol 0.016`), matching the bug
report.
- [x] With the selector fix, `QDQTransformerTests.Gemm_*` (8),
`QDQTransformerTests.*` (146), and `GraphTransformationTests.Gemm*` (16)
all pass locally on macOS arm64.
- [x] End-to-end with the reporter's `gemm_alpha.py` against a locally
built wheel (macOS arm64) matches optimized vs unoptimized across all
cases:
```bash
alpha=1.0, optimize=False [[ 39 221 173]]
alpha=1.0, optimize=True [[ 39 221 173]]
alpha=2.0, optimize=False [[ 47 216 169]]
alpha=2.0, optimize=True [[ 47 216 169]] (was [[0 255 218]] on main)
alpha=2.0, optimize=False, bias=0 [[144 118 120]]
alpha=2.0, optimize=True, bias=0 [[144 118 120]]
```