opus r32 track A: mm_gu3 half4 HS store for IQ2_S/IQ3_S, uniform ib / hoisted l, IQ3_S super-block loop #

short version: one tiny change ships, the half4 coalesced HS store turned on for T_IQ2_S, for about -0.3% on the IQ2_S gate/up at T=1024/2048 (34 of 48 layers). the uniform-ib / hoisted-l rewrite and moving IQ3_S into the super-block loop are both 100% bitwise identical but +1.3..4.4% slower, so they're dropped (´・ω・`)

  • patch: /tmp/opus_r32_moemm/opus_r32_moemm.patch (1 line in metal/kernels/moe_mm.metal, git -C vendor/strata apply --check OK)
  • scratch: /tmp/opus_r32_moemm/strata, baseline kernels /tmp/opus_r32_moemm/base_kernels, every variant in /tmp/opus_r32_moemm/v_*/
  • moe_mm_parity.cpp unchanged (the existing --ab / --gu-bench mode was enough)

method #

existing build-metal/moe_mm_parity with STRATA_METAL_KERNELS=<dir> vs --ab <dir>, --v3 --dn Q2_0 --tile 16,32,32,4 --dtile 64,32,2 --gu-bench. each cell is 4 or 6 runs that alternate which kernel is A, and the number is the mean of the candidate-vs-base median diffs (negative = faster). every run also memcmps HS/YE/Y. base vs base on IQ2_S@1024 came out -0.02% [-0.07..+0.04]. the identical-code cells (IQ2_XXS/IQ2_XS under H_all) spread about ±0.4% (one +1.3% outlier), with the :8080 server resident, so I only treat a move as real if every run agrees on the sign.

results (all bitwise PASS: HS_memcmp / YE_memcmp / Y_memcmp) #

variant change fmt T=512 T=1024 T=2048
H_all / F half4 HS store, IQ2_S IQ2_S -0.10 (4), -0.17 (6) -0.38 [all 4 <0], -0.35 [all 6 <0] -0.26 [all 4 <0], -0.06 (6, one +2.0 outlier; median -0.22)
H_all / F half4 HS store, IQ3_S IQ3_S -1.25 (very noisy), +0.32 [-0.38..+1.27] -0.33 [all <0], -0.30 [all <0] -0.10, -0.60 (noisy)
U block(bl, ib = ki>>5, l = kl>>3 hoisted, ...) for IQ2_S/XXS/XS IQ2_S +4.29 [3.6..4.7] +1.29
U same IQ2_XXS +2.72
U3 U + IQ3_S block (ib/l uniform) inside the 256 super-block sb loop IQ3_S +3.61
S3 IQ3_S in the sb loop, still indexed by j_in (no ib change) IQ3_S +4.05 +4.43

shipped #

T_IQ2_S added to the half4 store gate. extra checks: default parity PASS (rel L2 1.60e-05), --ab at T=512 bitwise PASS with tiles 16,32,32,4 and 16,64,32,2.

not shipped:

  • IQ3_S half4: -0.3% at T=1024 and 2048, but T=512 has no clear sign (+0.32 mean, runs from -0.38 to +1.27), and r14 measured +3..5% for IQ3_S at mid T with this store. it's only 2 layers, so it's not proven enough to ship. flipping it on later is just adding || F == T_IQ3_S.
  • IQ4_XS / K-quants / Q8_0 (removing the F gate entirely): not benched. r14 saw Q6_K +1.1% at T=512, so they keep the scalar store.
  • target 2 (uniform ib / hoisted l): a reliable regression. this matches r14 variant D, where the compiler already got the lane-constant part from j_in. turning ib into a loop-carried scalar (ki >> 5 of the non-unrolled 8-step ki loop) seems to make the codegen worse, not better.
  • IQ3_S in the sb loop: +4% whether or not ib changes. the 8-step ki loop plus the dg/du arrays cost more than the one rdh a 32-step that it saves.

take-away #

the mm_gu3 decode address math is still not the bottleneck. 3 rounds now (r14 D, r32 U, U3, S3) have shown that restructuring the indexing loses. the epilogue store is good for a few tenths of a percent at most.