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 inmetal/kernels/moe_mm.metal,git -C vendor/strata apply --checkOK) - 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.cppunchanged (the existing--ab/--gu-benchmode 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. turningibinto a loop-carried scalar (ki >> 5of 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
ibchanges. the 8-step ki loop plus the dg/du arrays cost more than the onerdha 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.