# round 29 track b: paired moe gate/up decode

## result

- patch: `/tmp/sol_r29_moemm/sol_r29_moemm.patch` (sha256 `0504a66525ab48e1a297ec9d4b7c39018de9f43c5fe4ccee566f1fa224c09645`). `git -C vendor/strata apply --check` exits 0.
- isolated source: `/tmp/sol_r29_moemm/strata`; baseline kernels: `/tmp/sol_r29_moemm/baseline`. vendor head at snapshot: `296b59d2f73c8f04a805df29c2fa97655adace78`; snapshot includes its then-current working files, not just head.
- all four requested formats at all three requested token counts passed **HS_memcmp=PASS YE_memcmp=PASS Y_memcmp=PASS** on final source, exit 0.
- all invocations were individually under 10 seconds (final maximum 9.768 seconds). no server calls or full-model benchmarks.
- iq2_s scale caching has a reproducible positive signal (~2–5% gate/up latency reduction). iq2_xxs direct byte loads have a ~2–3% preliminary signal. iq3_s fma and table trim alone did not establish a speed gain.
- later runs became heavily contended/noisy (unchanged iq2_xs also varied dramatically). do not interpret final noisy non-iq2_s medians as clean regressions or wins; confirm in a quiet gpu window before promoting.

## implementation

only owned lines/files changed:

1. iq2_s table: 2080 -> 2048 uints. loader writes exactly 2048, and index max is 2047. source-level static allocation drops from 16512 to 16384 bytes for the selected tile; this alone did not measurably change time.
2. iq2_s paired decoder now has an in-block helper accepting the already-loaded scale. mm_gu3 caches gate/up row scales once per 256-weight block and reuses them across eight k32 matmuls, preserving accumulation order. cache is `TN * TK / 256` floats per operand/lane (four floats each for the target tile), no new threadgroup storage. existing wrapper retains full-row semantics for other callers. non-iq2_s keeps a single-iteration inner loop.
3. iq2_xxs: replace 32-bit grid-index read plus variable byte shifts with two byte reads. no changes to sign or scale math.
4. iq3_s: integer odd-scale construction -> equivalent fma.
5. standalone test: shared bitwise comparator used in both parity and isolated a/b bench paths; supports the requested `-T` alias and four-number down tile, retaining existing three-number form. mismatched down TM fails pre-gpu with exit 2 (tested). a/b timing remains the original alternating 19 paired samples and original filtering.

## evaluator / command

apple m5, existing `libmtl_rt.a`, clang++ c++20 -O2. standalone executable `/tmp/sol_r29_moemm/moe_mm_parity` built from scratch test source linked to `vendor/strata/build-metal/libmtl_rt.a` and metal/foundation frameworks. runtime uses the scratch kernels via `STRATA_METAL_KERNELS`:

```
STRATA_METAL_KERNELS=/tmp/sol_r29_moemm/strata/metal/kernels \
/tmp/sol_r29_moemm/moe_mm_parity --v3 --gu FORMAT --dn Q2_0 \
  --tile 16,32,32,4 --dtile 16,64,32,2 -T TOKENS \
  --ab /tmp/sol_r29_moemm/baseline --gu-bench
```

formats: IQ2_S / IQ2_XXS / IQ3_S / IQ2_XS; tokens: 512 / 1024 / 2048. seeded existing data generator; 512 experts. existing runtime fast compilation supplies MM_V3_ONLY. a = candidate, b = baseline. positive printed diff is `(b-a)/a`, **not** latency reduction `(b-a)/b`.

## earlier low-noise evidence

`logs/v1_*`: table trim + iq2_xxs byte loads + iq3_s fma, before scale caching:

| format | t512 a / b us | t1024 a / b us | t2048 a / b us |
|---|---:|---:|---:|
| IQ2_S | 10067 / 10062 | 16811 / 16860 | 28917 / 28492 |
| IQ2_XXS | 7813 / 8097 | 13109 / 13417 | 22609 / 23034 |
| IQ3_S | 10478 / 10463 | 16844 / 16807 | 28485 / 28364 |

`logs/v2_IQ2_S_*`: cached-scale source with a redundant min-bounded loop, subsequently simplified:

| t | candidate us | baseline us | latency reduction |
|---|---:|---:|---:|
| 512 | 9886.7 | 10105.5 | 2.17% |
| 1024 | 15990.6 | 16924.1 | 5.52% |
| 2048 | 27140.5 | 28495.9 | 4.76% |

an independent scale-cache 1024 run (`v2fix_IQ2_S_1024.txt`) gave 15992.5 / 16870.9 us (5.20% reduction). the min-bounded loop unexpectedly regressed iq3_s (~8–25%); replacing it with a fixed compile-time trip count removed that in `v3_IQ3_S_1024.txt`: 16860.9 / 16775.4 us (-0.51%, noise). this correction is in the patch.

## final exact-source matrix

all final logs: `/tmp/sol_r29_moemm/logs/final_FORMAT_T.txt`. all 12 have 3/3 memcmp passes. timing below is raw, including severe contention later in the matrix:

| format | t | candidate median us | baseline median us |
|---|---:|---:|---:|
| IQ2_S | 512 | 10022.3 | 10291.1 |
| IQ2_S | 1024 | 16276.1 | 16926.2 |
| IQ2_S | 2048 | 28069.6 | 28867.4 |
| IQ2_XXS | 512 | 11142.1 | 11032.1 |
| IQ2_XXS | 1024 | 23791.9 | 24683.8 |
| IQ2_XXS | 2048 | 49890.8 | 39101.6 |
| IQ3_S | 512 | 45668.4 | 41311.6 |
| IQ3_S | 1024 | 46026.5 | 44646.8 |
| IQ3_S | 2048 | 115838.9 | 89895.8 |
| IQ2_XS | 512 | 14022.5 | 13563.5 |
| IQ2_XS | 1024 | 22634.9 | 22979.8 |
| IQ2_XS | 2048 | 29303.8 | 28569.8 |

final iq2_s relative reductions: 2.61%, 3.84%, 2.76%. later samples show multi-fold slowdowns, including the unchanged iq2_xs control. raw medians must not be cherry-picked into claims.

## limitations / next check

bitwise correctness is established for requested formats/tile/token counts against frozen baseline, not an exhaustive format/tile proof. no full-model throughput estimate is claimed. quiet confirmation is needed for the final non-iq2_s timing matrix. source reviews used view_diff and view_code; no tracked vendor files were modified.
