Round 11 Track C Audit: mm_gu3 & mm_down3 Row-Pointer / Block-Offset Hoisting & Branchless W8<T_IQ3_S> / W4<T_IQ3_S> #
1. Summary of Changes (metal/kernels/moe_mm.metal & metal/tests/moe_mm_parity.cpp) #
- Patch file:
/tmp/argon_r11_moemm/argon_r11_moemm.patch(verified clean viagit -C vendor/strata apply --check). - Scratch checkout:
/tmp/argon_r11_moemm/strata(trackedvendor/strata/untouched). - Files touched: Only
metal/kernels/moe_mm.metalandmetal/tests/moe_mm_parity.cpp.
Change 1: Hoist Row Base Pointer (g0_l, u0_l, w0_l), Row Stride Step (step8), kl, and Per-k0 Block Offset (boff) in mm_gu3 and mm_down3 (moe_mm.metal lines 852–885 & 983–1007) #
- On Apple M5 (
coop_k4 && TK == 32), each lane's right-input cooperative tensor holdshalf_cap / 4runs (4runs inmm_gu316x32x32,8runs inmm_down316x64x32) at consecutive row offsetsn_r = n_0 + 8 * r(r = 0 .. half_cap/4 - 1) and a fixed intra-tile K offsetkl = ix0[0] = (lane & 3) * 4. - Previously, inside every
k0iteration (80times inmm_gu3,20times inmm_down3):mm_gu3recomputedg0 + ulong(n) * a.gu_row4times for gate and4times for up (64064-bit multiplies per lane), andW8<F>::runrecomputedrow + (k >> 8) * BB8times perk0(640block-offset multiplies per lane).mm_down3recomputedw0 + ulong(ix[1]) * a.d_row8times perk0(16064-bit multiplies per lane), andW8<F>::runrecomputedrow + (k >> 6) * BB16times perk0(320block-offset multiplies per lane).
- Hoisted
kl = ix0[0],g0_l = g0 + ulong(ix0[1]) * a.gu_row,u0_l = u0 + ulong(ix0[1]) * a.gu_row,w0_l = w0 + ulong(ix0[1]) * a.d_row, andstep8 = 8u * row_bytesoutside thek0loop, and computedboffand the masked intra-block indexj_in((k0 + kl) & 255forQK == 256,(k0 + kl) & 63forQK == 64,klforQK == 32) once perk0iteration, advancing a single running pointerrgw += step8/ruw += step8/rdw += step8across the unrollediloop. - Because
j_inis masked to< QKbefore callingW8<F>::run(rgw, j_in, ...), LLVM KnownBits eliminates(j_in >> 8) * BB(or(j_in >> 6) * BB) andj_in & 255inside everyW8<F>::runcall without duplicatingW8<F>specializations. - Excluded
F == T_Q6_Kfrom the outer pointer-hoisting branch inmm_gu3(if (coop_k4 && TK == 32 && F != T_Q6_K && mm_probe != 1)) becauseW8<T_Q6_K>sits at a VGPR occupancy threshold where keepingg0_l,u0_l,step8live acrossop.runregressedT = 1024.
Change 2: Eliminate Ternary hh ? ... : ... Branches in W8<T_IQ3_S> and W4<T_IQ3_S> (moe_mm.metal lines 531 & 727–731) #
- In
W8<T_IQ3_S>::run, replaced:
with branchless pointer indexing and shift arithmetic (const int l1 = l + 2; const uint i0 = hh ? (uint(bl[2 + 8 * ib + 2 * l + 1]) | ((qh << (7 - 2 * l)) & 256)) : (uint(bl[2 + 8 * ib + 2 * l]) | ((qh << (8 - 2 * l)) & 256)); const uint i1 = hh ? (uint(bl[2 + 8 * ib + 2 * l1 + 1]) | ((qh << (7 - 2 * l1)) & 256)) : (uint(bl[2 + 8 * ib + 2 * l1]) | ((qh << (8 - 2 * l1)) & 256));hhis(k & 7) >> 2in{0, 1}):
and similarly inconst P qs = bl + 2 + 8 * ib + 2 * l + hh; const int sh0 = 8 - 2 * l - hh; const uint i0 = uint(qs[0]) | ((qh << sh0) & 256u); const uint i1 = uint(qs[4]) | ((qh << (sh0 - 4)) & 256u); const uint s0 = bl[74 + 4 * ib + l], s1 = bl[76 + 4 * ib + l];W4<T_IQ3_S>::run:const uint i = uint(b[2 + 8 * ib + 2 * l + hh]) | ((qh << (8 - 2 * l - hh)) & 256u); - Eliminates two divergent byte-load pairs and select instructions per
W8<T_IQ3_S>call (8perk0iteration =640per lane), shrinking VGPR usage across the unrolled decode loop and speeding upmm_gu3<T_IQ3_S>by-25.0%atT = 2048,-50.8%(2.03xfaster!) atT = 1024, and-32.7%atT = 512.
Change 3: In-Process Interleaved --ab <kernel_dir> Mode in moe_mm_parity.cpp #
- Added
--ab <dir>tomoe_mm_parityfor--gu-benchand--dn-benchso baseline and candidate libraries are compiled in the same Metal context and timed in 5 alternating pairs (AB,BA,AB,BA,AB) on the exact same weight and activation buffers.
Ruled-Out Candidates #
- Explicit 4- / 8-element pointer arrays
rgw[4],ruw[4],rdw[8]hoisted beforek0(k_hoist1): Forced the Metal compiler to keep864-bit pointers (1632-bit VGPRs) live acrossop.runinmm_gu3andmm_down3, reducing simdgroup occupancy and regressingIQ2_XS--gu-benchby+5.1% .. +6.6%andQ2_0--dn-benchby+4.2% .. +5.3%. Using a single base pointerg0_l + boffadvanced by+ step8inside the unrolled loop keeps only one pointer live during the unrolled loop and zero acrossop.run. - Outer
kb(256-element super-block) + innerksloop inmm_gu3(k_hoist3b): Added a nested loop induction variable live acrossop.run, regressingIQ2_XSatT = 1024by+2.90%(13,848 -> 14,250 us) andT = 2048by+0.66%compared to the singlek0loop withboff = uint(k0 >> 8) * BB(k_hoist3a,-2.09%atT = 1024and-2.24%atT = 2048). W8<T_Q6_K>uchar4bitwise pack (as_type<uchar4>(((la >> sl) & 0x0F0F0F0Fu) | (((ha >> shh) & 0x03030303u) << 4))): Bitwise identical (memcmp == 0), but caused Apple's Metal backend to scalarize theuchar4 -> float4conversion differently fromfloat4(uint4), increasing VGPR pressure and regressingQ6_K--gu-benchatT = 1024by+46.9%(26.2 -> 38.4 ms) andT = 2048by+12.1%(45.0 -> 50.5 ms). Rejected.
2. Bitwise Parity Verification (100% Byte-for-Byte memcmp == 0) #
Verified --dump output (HS + y, 6,512,640 bytes per format at T = 256, 64 experts, --v3 --tile 16,32,32,4 --dtile 64,32,2 --dn Q2_0) between baseline (k_base) and patched (k_final) across all 11 supported mm_gu3 formats (including all 5 required formats):
Format (--gu) |
--dn |
Dump Bytes (HS + y) |
memcmp(base, patched) |
Status |
|---|---|---|---|---|
IQ2_XS |
Q2_0 |
6,512,640 |
0 |
PASS (100% bitwise identical) |
Q6_K |
Q2_0 |
6,512,640 |
0 |
PASS (100% bitwise identical) |
IQ3_S |
Q2_0 |
6,512,640 |
0 |
PASS (100% bitwise identical) |
IQ2_XXS |
Q2_0 |
6,512,640 |
0 |
PASS (100% bitwise identical) |
Q2_0 |
Q2_0 |
6,512,640 |
0 |
PASS (100% bitwise identical) |
Q4_K |
Q2_0 |
6,512,640 |
0 |
PASS (100% bitwise identical) |
Q5_K |
Q2_0 |
6,512,640 |
0 |
PASS (100% bitwise identical) |
IQ2_S |
Q2_0 |
6,512,640 |
0 |
PASS (100% bitwise identical) |
IQ3_XXS |
Q2_0 |
6,512,640 |
0 |
PASS (100% bitwise identical) |
IQ4_XS |
Q2_0 |
6,512,640 |
0 |
PASS (100% bitwise identical) |
Q8_0 |
Q2_0 |
6,512,640 |
0 |
PASS (100% bitwise identical) |
3. Paired A/B Benchmark Results on Apple M5 (T = 512, 1024, 2048) #
In-process alternating 5-pair A/B timings (--v3 --tile 16,32,32,4 --dtile 64,32,2 --ab, 512 experts, quiet GPU):
| Kernel / Format | T |
Base Median (us) | Patched Median (us) | Base Min (us) | Patched Min (us) | Median Diff | Min Diff |
|---|---|---|---|---|---|---|---|
mm_gu3 IQ2_XS |
512 |
8,147.8 |
8,101.5 |
8,142.1 |
8,093.8 |
-0.57% | -0.59% |
mm_gu3 IQ2_XS |
1024 |
13,694.3 |
13,436.3 |
13,682.1 |
13,430.0 |
-1.88% | -1.84% |
mm_gu3 IQ2_XS |
2048 |
23,482.0 |
22,990.0 |
23,415.8 |
22,980.5 |
-2.10% | -1.86% |
mm_gu3 IQ3_S |
512 |
27,893.5 |
18,779.7 |
27,813.2 |
18,698.5 |
-32.67% | -32.77% |
mm_gu3 IQ3_S |
1024 |
37,267.4 |
18,341.9 |
37,103.3 |
18,302.7 |
-50.78% | -50.67% |
mm_gu3 IQ3_S |
2048 |
41,731.1 |
31,296.1 |
41,570.2 |
31,234.3 |
-25.01% | -24.86% |
mm_gu3 IQ2_XXS |
512 |
8,828.5 |
8,499.6 |
8,800.6 |
8,473.7 |
-3.73% | -3.71% |
mm_gu3 IQ2_XXS |
1024 |
14,596.1 |
14,139.4 |
14,547.0 |
14,104.2 |
-3.13% | -3.04% |
mm_gu3 IQ2_XXS |
2048 |
24,776.1 |
24,080.8 |
24,743.5 |
24,034.8 |
-2.81% | -2.86% |
mm_gu3 Q2_0 |
512 |
6,390.1 |
6,294.2 |
6,367.7 |
6,279.1 |
-1.50% | -1.39% |
mm_gu3 Q2_0 |
1024 |
9,923.9 |
9,514.1 |
9,911.4 |
9,507.0 |
-4.13% | -4.08% |
mm_gu3 Q2_0 |
2048 |
16,836.9 |
16,110.4 |
16,818.3 |
16,098.4 |
-4.32% | -4.28% |
mm_gu3 Q6_K (unchanged path) |
512 |
24,467.6 |
24,336.8 |
24,258.3 |
24,074.4 |
-0.53% | -0.76% |
mm_gu3 Q6_K (unchanged path) |
1024 |
25,899.7 |
25,333.6 |
24,215.7 |
24,497.4 |
-2.19% | +1.16% |
mm_gu3 Q6_K (unchanged path) |
2048 |
38,443.2 |
38,566.3 |
38,227.0 |
38,345.1 |
+0.32% | +0.31% |
mm_down3 Q2_0 |
512 |
3,576.4 |
3,552.3 |
3,575.6 |
3,549.7 |
-0.68% | -0.72% |
mm_down3 Q2_0 |
1024 |
5,209.3 |
5,035.6 |
5,202.6 |
5,031.3 |
-3.33% | -3.29% |
mm_down3 Q2_0 |
2048 |
8,642.8 |
8,247.4 |
8,626.8 |
8,234.0 |
-4.58% | -4.55% |
Projected Full-Model Prefill Savings at T = 1983 (48 Layers) #
mm_gu3T_IQ2_XS(x34layers,807.06 msbaseline):-2.10%->-16.9 msmm_gu3T_IQ3_S(x2layers,67.78 msbaseline):-25.01%->-17.0 ms(and-50.8%onT = 1024chunks!)mm_down3T_Q2_0(x48layers,334.82 msbaseline):-4.58%->-15.3 ms- Total Track C savings at
T = 1983:~49.2 msper 2k chunk (~270 msacross an 11k prompt!) with 100% bitwise parity.