Round 28 Track C: ple.metal tg_sum Barrier Halving & sample.metal tg_argmax Optimization #

Summary of Changes #

1. metal/src/engine/msl/ple.metal #

  • Halved tg_sum barriers (2 -> 1 per reduction) by removing the trailing threadgroup_barrier(mem_flags::mem_threadgroup) inside tg_sum and alternating part[0..7] and part[8..15] across back-to-back reductions (threadgroup float part[16] = 64 B vs 32 B):
    • ple_gnorm: 2 -> 1 barrier (-50%), using threadgroup float part[8].
    • ple_gate_norm: 4 -> 2 barriers (-50%), call 1 (acc) on part[0..7], call 2 (ss) on part + 8.
    • ple_query_gate_norm & ple_comb_query_gate_norm: 6 -> 3 barriers (-50%), gnorm_regs(q) on part[0..7], gate_norm_regs first reduction (acc) on part + 8 (whose barrier synchronizes prior readers of part[0..7]), and gate_norm_regs second reduction (ss) on part[0..7].
    • ple_moe_gate_norm & ple_moe_gate_norm_c (moe_gate_norm_row): 8 -> 4 barriers per stream (-50%), gnorm_regs(k) on part + 8, gnorm_regs(q) on part[0..7], and gate_norm_regs on part + 8 then part[0..7]. Across hc = 4 streams in ple_moe_gate_norm_c, total tg_sum barriers drop from 32 -> 16 (33 -> 17 including the initial roff setup barrier).
  • Added #pragma clang loop unroll(full) to both for (int r = 0; r < 9; ++r) loops in ple_hist_adv so float col[9] stays in registers without dynamic indexing.

2. metal/kernels/sample.metal #

  • Added #pragma clang loop unroll(full) to both fixed-trip loops in tg_argmax:
    • for (ushort m = 16; m > 0; m >>= 1) (5-step warp butterfly reduction).
    • for (int k = 0; k < 8; ++k) (8-way shared-memory reduction).
  • Evaluated & Rejected (tid == 0 pruning and unroll_count(4) in argmax_part / argmax_final):
    • if (sg == 0 && lane == 0) / if (tid == 0) around the post-barrier loop in tg_argmax: Because all 8 simdgroups must wait for threadgroup completion anyway and simdgroup 0 still executes the 8-way loop when tid == 0 is taken, adding divergent predication inside tg_argmax regressed nv = 40525 (nt = 1..8) by +2.0% to +3.5% and nv = 248064, nt = 1..2 by +0.8% to +2.0%. Leaving the unrolled 8-way reduction uniform across lanes avoids the branch and is faster.
    • #pragma clang loop unroll_count(4) in argmax_part and argmax_final: In argmax_part, each iteration contains if (!isnan(v)) better(bv, bi, v, int(i)). Unrolling by 4 (or 2) either via pragma or manual split-loop (i + 1024 < b1) bloated register/predicate state and regressed the draft-vocab path (nv = 40525, per = 338, only 1–2 iterations per thread) by +7.5% to +16.4% (+1.0% to +3.8% even with a per >= 2048 guard). In argmax_final, a.parts = 120 < 256, so the loop executes at most once (tid < 120) and unroll_count(4) added dead duplicate checks (+0.5% to +2.0% regression). Both loops are kept unrolled-by-1.

3. metal/tests/ple_moe_parity.cpp & metal/tests/sample_parity.cpp #

  • ple_moe_parity.cpp:
    • Added ple_gnorm, ple_gate_norm, ple_comb_query_gate_norm, and ple_comb_query_gate_norm + conv_res to --ab bitwise parity and paired A/B timing checks across T in {1, 3, 7, 9, 10, 64, 512, 1024, 2048}.
    • Deferred prompt.metal / moe_mm.metal compilation out of --ab mode and sliced only the needed kernels (gr_norm, moe_out, gr_norm_h*, gr_apply_moe*, mm_combine) in full-parity mode, cutting cold compilation + execution time from >20s (timeout) to 1.2s.
  • sample_parity.cpp:
    • Added nv = 248064 (nt = 1..8) and nv = 40525 (nt = 1, 2, 4, 8) to the paired A/B benchmark and bitwise parity sweep.
    • Reset lg to normal finite random floats before timing() (previously trial == 6 left -INFINITY in lg, skewing isnan/branch timing) and reported median per-pair ratios over 9 alternating pairs of 64 reps.

Verification & Measured A/B Results on Apple M5 #

1. ple_moe_parity (100% Bitwise Parity memcmp == 0 across all kernels & T) #

A/B T 1..2048: ple_gnorm PASS, ple_gate_norm PASS, ple_hist_adv PASS
A/B T 1..2048: ple_moe_gate_norm PASS, ple_moe_gate_norm_c PASS, ple_query_gate_norm PASS, ple_comb_query_gate_norm PASS
Kernel T = 1 (base -> cand, ratio) T = 3 (base -> cand, ratio) T = 7 (base -> cand, ratio) T = 64 (base -> cand, ratio) T = 2048 (base -> cand, ratio)
ple_gnorm (2 -> 1 bar) 0.0060 -> 0.0058 ms (0.9655, -3.5%) 0.0074 -> 0.0071 ms (0.9635, -3.7%) 0.0098 -> 0.0097 ms (0.9710, -2.9%) 0.0195 -> 0.0196 ms (1.0091, +0.9%) 1.2843 -> 1.2838 ms (1.0014, +0.1%)
ple_gate_norm (4 -> 2 bar) 0.0042 -> 0.0042 ms (0.9855, -1.5%) 0.0043 -> 0.0043 ms (0.9990, -0.1%) 0.0048 -> 0.0049 ms (1.0143, +1.4%) 0.0514 -> 0.0513 ms (0.9977, -0.2%) 3.3655 -> 3.3687 ms (0.9999, -0.0%)
ple_query_gate_norm (6 -> 3 bar) 0.0033 -> 0.0033 ms (0.9911, -0.9%) 0.0038 -> 0.0037 ms (0.9783, -2.2%) 0.0050 -> 0.0050 ms (0.9974, -0.3%) 0.0517 -> 0.0519 ms (1.0045, +0.5%) 3.3083 -> 3.3120 ms (1.0003, +0.0%)
ple_comb_query_gate_norm (6 -> 3 bar) 0.0067 -> 0.0067 ms (0.9933, -0.7%) 0.0088 -> 0.0087 ms (0.9936, -0.6%) 0.0115 -> 0.0114 ms (0.9875, -1.3%) 0.2365 -> 0.2361 ms (0.9985, -0.1%) 10.6802 -> 10.6833 ms (1.0012, +0.1%)
ple_moe_gate_norm (8 -> 4 bar) 0.0042 -> 0.0041 ms (0.9636, -3.6%) 0.0051 -> 0.0051 ms (0.9964, -0.4%) 0.0068 -> 0.0067 ms (0.9840, -1.6%) 0.0910 -> 0.0907 ms (0.9973, -0.3%) 4.6449 -> 4.6330 ms (0.9978, -0.2%)
ple_moe_gate_norm_c (33 -> 17 bar) 0.0245 -> 0.0240 ms (0.9785, -2.2%) 0.0257 -> 0.0252 ms (0.9807, -1.9%) 0.0264 -> 0.0258 ms (0.9765, -2.3%) 0.1105 -> 0.1099 ms (0.9852, -1.5%) 4.6122 -> 4.6190 ms (0.9988, -0.1%)
ple_hist_adv (unroll) 0.0029 -> 0.0029 ms (0.9955, -0.5%) 0.0029 -> 0.0030 ms (1.0121, +1.2%) 0.0032 -> 0.0032 ms (1.0042, +0.4%) 0.0023 -> 0.0023 ms (0.9902, -1.0%) 0.0024 -> 0.0024 ms (1.0001, +0.0%)

2. sample_parity (100% Bitwise Parity memcmp == 0 across all nv, nt, and edge-case trials) #

argmax nv=248320 nt=1 baseline 25.275 us candidate 25.307 us ratio 1.0026 (+0.26%)
argmax nv=248320 nt=2 baseline 39.385 us candidate 39.453 us ratio 0.9994 (-0.06%)
argmax nv=248320 nt=3 baseline 134.310 us candidate 134.270 us ratio 0.9996 (-0.04%)
argmax nv=248320 nt=4 baseline 178.275 us candidate 178.151 us ratio 1.0011 (+0.11%)
argmax nv=248320 nt=5 baseline 222.010 us candidate 221.879 us ratio 0.9995 (-0.05%)
argmax nv=248320 nt=6 baseline 265.524 us candidate 265.486 us ratio 0.9999 (-0.01%)
argmax nv=248320 nt=8 baseline 339.676 us candidate 337.559 us ratio 0.9911 (-0.89%)
argmax nv=248064 nt=1 baseline 5.432 us candidate 5.426 us ratio 1.0014 (+0.14%)
argmax nv=248064 nt=2 baseline 8.635 us candidate 8.628 us ratio 0.9999 (-0.01%)
argmax nv=248064 nt=3 baseline 131.288 us candidate 130.807 us ratio 0.9999 (-0.01%)
argmax nv=248064 nt=4 baseline 178.063 us candidate 178.022 us ratio 0.9998 (-0.02%)
argmax nv=248064 nt=5 baseline 206.575 us candidate 202.772 us ratio 0.9957 (-0.43%)
argmax nv=248064 nt=6 baseline 265.328 us candidate 265.270 us ratio 0.9999 (-0.01%)
argmax nv=248064 nt=7 baseline 299.301 us candidate 299.935 us ratio 1.0000 (+0.00%)
argmax nv=248064 nt=8 baseline 352.632 us candidate 352.615 us ratio 0.9999 (-0.01%)
argmax nv=40525 nt=1 baseline 3.287 us candidate 3.323 us ratio 1.0016 (+0.16%)
argmax nv=40525 nt=2 baseline 4.628 us candidate 4.622 us ratio 0.9983 (-0.17%)
argmax nv=40525 nt=4 baseline 7.477 us candidate 7.443 us ratio 0.9978 (-0.22%)
argmax nv=40525 nt=8 baseline 13.010 us candidate 13.027 us ratio 1.0011 (+0.11%)
argmax nv=7 nt=1 baseline 2.857 us candidate 2.728 us ratio 0.9549 (-4.51%)
argmax pv/pi/out bitwise + cpu: 0 wrong PASS

Patch Location #

  • Clean unified diff: /tmp/argon_r28_ple/argon_r28_ple.patch (verified with git -C vendor/strata apply --check).