Round 23 Track C: moe_mm.metal Prompt Prefill mm_gu3 & mm_down3 Optimization (argon-moe-mm-prompt-opt) #

1. Summary of Shipped Changes #

  • Deliverable patch: /tmp/argon_r23_moemm/argon_r23_moemm.patch (git -C vendor/strata apply --check verified clean, exit 0).
  • Touched files: metal/kernels/moe_mm.metal (lines 507–574, 688–765) and metal/tests/moe_mm_parity.cpp (lines 306–336).

Key Optimizations in metal/kernels/moe_mm.metal #

  1. 4-bit Register Sign-Parity Helper sign4_iq2xs(uint j, int hh) (W4 & W8 for T_IQ2_XS, T_IQ2_XXS, T_IQ3_XXS):
    • w4_signed(g, s, sc) only reads the low 4 bits (s & 1, s & 2, s & 4, s & 8) of its second argument.
    • When hh == 0 (50% of SIMD lanes), the low 4 bits of ksigns_iq2xs[j] are simply j itself (v >> 9) — bit 7 (popcount(j) & 1u) is not needed at all.
    • When hh == 1, (ksigns_iq2xs[j] >> 4) & 15 is (j >> 4) | ((popcount(j) & 1u) << 3).
    • sign4_iq2xs(j, hh) = hh ? ((j >> 4) | ((popcount(j) & 1u) << 3)) : j eliminates divergent threadgroup byte loads ksg(tab, ..., j) in W4<T_IQ2_XS>, W8<T_IQ2_XS>, W4<T_IQ2_XXS>, W8<T_IQ2_XXS>, W4<T_IQ3_XXS>, and W8<T_IQ3_XXS>, while avoiding popcount on hh == 0 lanes.
    • Note on moe_mm_parity --ab sign convention: moe_mm_parity.cpp:424 reports diff = (mb - ma) / ma * 100.0 where A is the candidate and B is --ab, so positive +X% means A is faster than B (ma < mb). Direct head-to-head A/B comparison of A = ksg vs B = sign4_iq2xs (with all other changes identical) confirmed B (sign4_iq2xs) is 3.47%–10.63% faster than A (ksg).
  2. Constant-Offset Pointer Folding ((k & 255) >> 3 and (k & 255) >> 2):
    • In W8<T_IQ2_XS>, 4 * ib + l (with ib = (k & 255) >> 5, l = (k & 31) >> 3, l < 2) is identical to (k & 255) >> 3. Computing device const ushort* q = (device const ushort*)(bl + 2) + ((k & 255) >> 3) turns q[l], q[l + 2] into constant-offset loads q[0], q[2] and eliminates l.
    • In W8<T_IQ3_S>, 8 * ib + 2 * l + hh is (k & 255) >> 2, 4 * ib + l is (k & 255) >> 3, and 2 * l + hh is (k & 31) >> 2. Folding these into qs = bl + 2 + ((k & 255) >> 2) (qs[0], qs[4]) and sp = bl + 74 + ((k & 255) >> 3) (sp[0], sp[2]) eliminates l in the hot path and reduces VGPR pressure across the 4-way unrolled mm_gu3 loop.
    • Applied the same constant-offset pointer folding to W8<T_IQ2_S>, W8<T_IQ3_XXS>, W4<T_IQ2_XS>, W4<T_IQ2_S>, W4<T_IQ3_S>, and W4<T_IQ3_XXS>.
  3. Bitwise-Identical fma Scale Math under MTLMathModeSafe:
    • Because 0.25f ($2^{-2}$) and 0.5f ($2^{-1}$) are exact powers of two and rdh(bl) is an fp16 value promoted to fp32 (exponent $\ge -24$), d * fma(float(nib), 0.25f, 0.125f) is 100% bitwise identical to d * (0.5f + float(nib)) * 0.25f across all 65,536 fp16 bit patterns and all 16 nibble values 0..15, while replacing FADD + FMUL + FMUL (which MTLMathModeSafe cannot reassociate) with FMA + FMUL and removing rdh(bl) from the first stage of the dependency chain.
  4. Ruled-Out Candidates (Not Shipped):
    • Custom W8<T_Q2_0> in mm_down3: tested both shared-d and split-d W8<T_Q2_0> specializations; both regressed mm_down3 by 0.7%–3.0% at T=1024, 2048 because CSEing the block pointer across the two W4 halves allows LLVM to hoist all 16 loads across the 8-way unrolled loop, increasing VGPR pressure near the occupancy boundary. Kept the generic two-W4<T_Q2_0> path untouched.
    • W8<T_Q6_K> scalar byte packing: regressed Q6_K by 2.4%–4.5% vs uint4(as_type<uchar4>(...)). Reverted so W8<T_Q6_K> is untouched.

2. Paired A/B Benchmark Results on M5 (moe_mm_parity --v3 --tile 16,32,32,4 --dtile 64,32,2 --gu-bench) #

Note: In moe_mm_parity --ab, A is the candidate kernel, B is the baseline reference kernel, and diff = (B - A) / A * 100% (+ = candidate A is faster than baseline B).

Format T Candidate A Med (Min) us Baseline B Med (Min) us Speedup diff Med (Min)
IQ2_XS 512 7,682.3 (7,669.9) 7,982.3 (7,978.3) +3.91% (+4.02%)
IQ2_XS 1024 16,603.6 (12,901.3) 17,684.0 (13,439.3) +6.51% (+4.17%)
IQ2_XS 2048 25,535.6 (25,486.3) 26,712.9 (26,492.7) +4.61% (+3.95%)
IQ3_S 512 17,946.8 (17,777.0) 19,823.0 (19,711.9) +10.45% (+10.88%)
IQ3_S 1024 28,841.0 (28,506.7) 31,010.2 (30,957.8) +7.52% (+8.60%)
IQ3_S 2048 48,501.1 (46,976.4) 52,371.5 (51,207.6) +7.98% (+9.01%)
IQ2_S 512 35,342.2 (34,052.6) 36,953.1 (35,455.6) +4.56% (+4.12%)
IQ2_S 1024 44,448.1 (42,382.8) 45,269.8 (44,114.7) +1.85% (+4.09%)
IQ2_XXS 1024 32,583.5 (30,793.0) 33,326.4 (32,109.3) +2.28% (+4.27%)
IQ2_XXS 2048 56,247.3 (54,307.5) 58,809.2 (56,789.6) +4.55% (+4.57%)
IQ3_XXS 512 28,919.5 (22,779.1) 33,020.1 (24,348.8) +14.18% (+6.89%)
IQ3_XXS 1024 33,955.7 (29,351.1) 35,193.0 (32,111.7) +3.64% (+9.41%)
IQ3_XXS 2048 50,338.7 (45,779.3) 53,857.8 (50,270.8) +6.99% (+9.81%)

3. Bitwise Parity Verification (HS, YE, Y memcmp == 0) #

Verified 100% bitwise identity (HS_memcmp=PASS YE_memcmp=PASS Y_memcmp=PASS) between candidate and unmodified baseline across all formats (IQ2_XS, IQ3_S, Q6_K, Q2_0, IQ2_S, IQ2_XXS, IQ3_XXS) at T = 256, 512, 1024, 2048 on both TK=32 (W8) and TK=16 (W4):

[W8 TK=32] IQ2_XS   T=256..2048: HS_memcmp=PASS YE_memcmp=PASS Y_memcmp=PASS
[W8 TK=32] IQ3_S    T=256..2048: HS_memcmp=PASS YE_memcmp=PASS Y_memcmp=PASS
[W8 TK=32] Q6_K     T=256..2048: HS_memcmp=PASS YE_memcmp=PASS Y_memcmp=PASS
[W8 TK=32] Q2_0     T=256..2048: HS_memcmp=PASS YE_memcmp=PASS Y_memcmp=PASS
[W8 TK=32] IQ2_S    T=256..2048: HS_memcmp=PASS YE_memcmp=PASS Y_memcmp=PASS
[W8 TK=32] IQ2_XXS  T=256..2048: HS_memcmp=PASS YE_memcmp=PASS Y_memcmp=PASS
[W8 TK=32] IQ3_XXS  T=256..2048: HS_memcmp=PASS YE_memcmp=PASS Y_memcmp=PASS
[W4 TK=16] IQ2_XS..IQ3_XXS T=256: HS_memcmp=PASS YE_memcmp=PASS Y_memcmp=PASS