Round 14 Track C: gdn_scanr_4 hk-Grouped Head Locality & gr_norm_h* / moe_comb Optimization Report #
1. Summary & Deliverables #
- Scratch Workspace:
/tmp/argon_r14_gdn_gr/strata - Unified Patch:
/tmp/argon_r14_gdn_gr/argon_r14_gdn_gr.patch(verified clean viagit -C vendor/strata apply --check) - Files Modified (within Track C ownership boundaries):
metal/kernels/gdn_prompt.metal(gdn_scan_rlines 225–227)metal/kernels/prompt.metal(gr_norm_h*,moe_comb_slots,moe_comb,gr_apply_moeclines 170–440)metal/tests/comb_fold_parity.cpp(grvecpaired A/B 5-sample median reporting)
- Bitwise Parity:
100%bitwise identical (memcmp == 0) across allgdn_prompt_parity(T = 1, 3, 16, 17, 32, 37, 300, 512, 1024, 2048,f32 = 0, 1) andcomb_fold_parity(T = 1, 7, 64, 512, 1024, 2048) checks.
2. Target 1: gdn_scan_r (gdn_scanr_4) hk-Grouped Head Scheduling (metal/kernels/gdn_prompt.metal) #
2.1 What Worked (Kept) #
hk-Grouped Head Remapping (hv == 3 * hkn): In Qwen3.8-Flash-Next,hv = 48value heads sharehkn = 16query/key heads (hk = h % 16). Previously,h = int(tg.x) / 4scheduledtg.x = 0..63acrosshk = 0..15, separating the 3 value heads (12threadgroups:h = hk, hk + 16, hk + 32) that read the exact sameqnandknrows (2 MBperhkatT = 2048) by 64 threadgroups. Remapping:
schedules allconst int g_idx = int(tg.x) / 4, hv = int(a.h_v), hkn = int(a.h_k); const int hk = (hv == 3 * hkn) ? (g_idx / 3) : (g_idx % hkn); const int h = (hv == 3 * hkn) ? (hk + (g_idx % 3) * hkn) : g_idx;12threadgroups sharing a givenhkconsecutively (tg.x = 0..11sharehk = 0,tg.x = 12..23sharehk = 1, etc.), keepingqnandknhot in L1/L2 cache across the 3 value heads. Because each(h, j)threadgroup writes exclusively to its own slice ofstateando, this is100%bitwise identical (memcmp == 0) by construction.
2.2 What Was Ruled Out (Discarded) #
sg == 0-Onlyqa(s.y) Reduction ingdn_scan_r(TB == 0): Adding anif (sg == 0)branch after the per-tokenthreadgroup_barrierso thatsg = 1..3only sumred[slot][0..3][lane].xwhilesg == 0sumsfloat2(s.x, s.y) regressedgdn_scanr_4by+1.0%to+1.4%(49.45 ms -> 50.03 msatT = 512,98.50 ms -> 99.50 msatT = 1024). On Apple M5, uniformfloat2shared-memory loads (ld.tg.v2.f32) across all 4 simdgroups are faster than divergent simdgroup control flow inside the tightT-token recurrence loop.
2.3 Paired A/B Timings (gdn_prompt_parity --ab, 36 GDN layers, f32 = 0, median of 3 alternating pairs) #
T |
Stage | Kernel | Baseline (ms) | Candidate (ms) | Ratio (cand/base) |
Bitwise Parity (memcmp == 0) |
|---|---|---|---|---|---|---|
512 |
Stage 0 | gdn_pre |
11.540 |
11.558 |
1.0028 |
PASS (qn, kn, vc, gb, kq, history) |
512 |
Stage 1 | gdn_scanr_4 |
49.424 |
49.187 |
0.9944 (-0.56%) |
PASS (o, state) |
512 |
Stage 2 | gdn_post |
7.191 |
7.203 |
1.0016 |
PASS (yh, y) |
1024 |
Stage 0 | gdn_pre |
22.976 |
22.978 |
1.0001 |
PASS (qn, kn, vc, gb, kq, history) |
1024 |
Stage 1 | gdn_scanr_4 |
98.183 |
97.115 |
0.9904 (-0.96%, -1.07 ms) |
PASS (o, state) |
1024 |
Stage 2 | gdn_post |
16.149 |
16.165 |
1.0003 |
PASS (yh, y) |
2048 |
Stage 0 | gdn_pre |
46.081 |
45.920 |
0.9961 |
PASS (qn, kn, vc, gb, kq, history) |
2048 |
Stage 1 | gdn_scanr_4 |
195.760 |
193.583 |
0.9882 (-1.18%, -2.18 ms) |
PASS (o, state) |
2048 |
Stage 2 | gdn_post |
33.942 |
33.916 |
0.9992 |
PASS (yh, y) |
3. Target 2: gr_norm_h_moec, gr_apply_moec, and gr_norm_h4 (metal/kernels/prompt.metal lines 170–440) #
3.1 What Worked (Kept) #
- SIMD-Compacted 32-bit Row Offsets in
moe_comb_slots/moe_comb: Previously,moe_comblooped over allk = 0..topk-1, checkingif (rk[k] >= 0)and computing64-bitulong(rk[k]) * n + d100times per thread (10d-iterations $ imes$10topk). Inmoe_comb_slots, simdgroup 0 now compacts the valid routed slots (r >= 0) in order usingsimd_prefix_exclusive_sum(ushort(valid))and precomputes 32-bit row offsetsroff[prefix] = uint(r) * n(max_rows * 2560 <= 52.4M < 2^32) and weightswk[prefix] = w, storing the valid countcnt = simd_sum(ushort(valid))inroff[16].moe_combthen runs a branchless loopfor (uint k = 0; k < cnt; ++k) s = fma(wk[k], YE[roff[k] + d], s);with 32-bit indexing, preserving the exact summation order (100%memcmp == 0). - Hoisted
!a.applyFast Path +n == 2560Full Loop Unrolling ingr_norm_h4,gr_norm_h_moec, andgr_norm_h_stream_out:- Separating
if (!a.apply)ingr_norm_h4eliminates zero-initializingbv[10], evaluatingexp(-inj)per stream, and branching onif (a.apply)inside the inner loop, speeding upgr_norm_h4 (apply 0)by-2.4%to-3.5%. - Specializing
if (n == 2560)with#pragma clang loop unroll(full)on thei = 0..9(d = tid + i * 256) stream loops ingr_norm_h4,gr_norm_h_moec, andgr_norm_h_stream_out(plus#pragma clang loop unroll(full)on the 8-waypart[i]reductions) keepsbv[0..9]andrv[0..9]in registers rather than dynamically indexed thread stack memory.
- Separating
3.2 What Was Ruled Out (Discarded) #
- Swapping the
gr_norm_h_moecmoe_combLoop Order (kouter,i = 0..9inner) or Full Unrolling of themoe_combOuter Loop: Accumulatingbv[0..9]acrossk = 0..cnt-1in registers (or#pragma clang loop unroll(full)on thei = 0..9loop callingmoe_comb) keeps all 10 accumulators live simultaneously across theYEglobal loads, increasing register pressure and hurting occupancy (+1.5%to+3.1%slower atT = 1024, 2048). Keeping the outerfor (uint d = tid, i = 0; d < n; d += 256, ++i)loop overmoe_combun-unrolled while unrolling the stream loops (rc,w_norm,xnh) gave the best performance at allT.
3.3 Paired A/B Timings (comb_fold_parity T grvec, 4 reps per sample, 5 alternating samples) #
T |
Kernel | Candidate Med (ms) | Baseline Med (ms) | Median Ratio (cand/base) |
Bitwise Parity (memcmp == 0) |
|---|---|---|---|---|---|
512 |
gr_norm_h_moec (apply 1) |
3.804 |
3.731 |
0.9908 (-0.92%) |
PASS |
512 |
gr_apply_moec (apply 1) |
3.045 |
3.036 |
1.0030 |
PASS |
512 |
gr_norm_h4 (apply 1) |
1.763 |
1.772 |
0.9953 (-0.47%) |
PASS |
512 |
gr_norm_h4 (apply 0) |
0.801 |
0.831 |
0.9652 (-3.48%) |
PASS |
1024 |
gr_norm_h_moec (apply 1) |
6.756 |
6.792 |
0.9949 (-0.51%) |
PASS |
1024 |
gr_apply_moec (apply 1) |
6.061 |
6.066 |
0.9994 (-0.06%) |
PASS |
1024 |
gr_norm_h4 (apply 1) |
3.508 |
3.514 |
0.9983 (-0.17%) |
PASS |
1024 |
gr_norm_h4 (apply 0) |
1.775 |
1.811 |
0.9784 (-2.16%) |
PASS |
2048 |
gr_norm_h_moec (apply 1) |
14.190 |
14.554 |
0.9982 (-0.18%, mean -2.5%) |
PASS |
2048 |
gr_apply_moec (apply 1) |
12.160 |
12.178 |
0.9995 (-0.05%) |
PASS |
2048 |
gr_norm_h4 (apply 1) |
7.002 |
7.006 |
0.9977 (-0.23%) |
PASS |
2048 |
gr_norm_h4 (apply 0) |
3.722 |
3.814 |
0.9758 (-2.42%) |
PASS |