Round 36 Track C: qsa_prep Single-Barrier norm_rope & w == 128 tg_sum + qsa_score_mm Loop Unrolling #

Summary & Decision #

  • Owner: argon-r36-qsa-prep-score-and-gdn
  • Owned files: metal/kernels/qsa.metal, metal/kernels/qsa2.metal, metal/kernels/qsa_prompt.metal, metal/tests/qsa_parity.cpp, metal/tests/qsa_prompt_parity.cpp
  • Kernel decision: No-ship on kernel changes (qsa.metal, qsa2.metal, qsa_prompt.metal unchanged); ship paired A/B harness improvements (--ab in qsa_parity.cpp and qsa_prompt_parity.cpp).
    • All tested qsa_prep and qsa_score_mm kernel variants achieved 100% IEEE-754 bitwise parity (memcmp == 0) across every output buffer (qcur, qidx, kq, vq, ksc, vsc, tail, dead, pooled, scores, ids, part, xp), including QSA_MROPE=1 and QSA_Q_F16_FC=true.
    • However, on quiet Apple M5 (10-core GPU) in warmed 15-pair alternating A/B benchmarks, the current working-tree qsa_prep and qsa_score_mm kernels are already at their M5 optimum:
      1. Single-barrier norm_rope (if (rope && tid < QROT) row[tid] = x; before tg_sum256 + partner scale (rs * row[tid ^ 32]) * g[tid ^ 32]): Bitwise identical (memcmp == 0), but slightly slower across decode and prompt shapes (ratio 1.0006–1.0136, +0.5% to +1.4% at T = 1, 1984, 2048) because holding x live across tg_sum256 + issuing a second uncoalesced/divergent device const float* g load (g[tid ^ 32]) for tid < 64 outweighs the threadgroup barrier savings on M5.
      2. tg_sum256 #pragma clang loop unroll(full) + nsg = (w <= 128 ? 4 : 8): Bitwise identical (memcmp == 0), neutral within ±0.2% at T = 1984, 2048 (961.05 -> 962.95 us, ratio 1.0010 at P=0, T=1984; 1000.15 -> 1005.97 us, ratio 0.9994 at P=3000, T=1984; 1039.76 -> 1044.30 us, ratio 1.0004 at P=3000, T=2048), because Metal Clang already unrolls nsg = 8 when constant-folded and qsa_prep is memory-bandwidth bound on qfull / kcur / vcur / kq / vq.
      3. qsa_score_mm #pragma clang loop unroll(full) + qp pre-offsetting (qsa_prompt.metal): Bitwise identical (memcmp == 0), neutral (0.1788 -> 0.1770 ms at pos0=0, 0.4654 -> 0.4699 ms at pos0=2051, 1.3466 -> 1.3326 ms at pos0=8192, 4.4671 -> 4.4764 ms at pos0=30000 for T = 1984), confirming Round 26 (audits/sol_r26_qsa.md) that qsa_score_mm is dominated by the 64x64x128 matmul2d cooperative tensor op and DRAM reads/writes.

Measured Quiet-GPU A/B Results #

1. qsa_prep Variants (qsa_parity --paired / --ab, 15 alternating pairs of 8x/32x dispatches) #

Shape (P, nt) Baseline (us) V1: Single-Barrier norm_rope + nsg=4 (us / ratio) V2: Two-Barrier + nsg=4 + unroll (us / ratio) V3: tg_sum256 unroll only (us / ratio) Bitwise memcmp
P=3000, nt=1 7.047 7.090 (1.0136) 6.939 (0.9881) 7.039 (0.9952) PASS (all 13 buffers)
P=3000, nt=2 2.720 2.730 (1.0029) 2.699 (0.9957) 2.329 (0.9967) PASS (all 13 buffers)
P=3000, nt=4 2.879 2.859 (1.0004) 2.879 (0.9982) 2.854 (0.9959) PASS (all 13 buffers)
P=3000, nt=8 4.611 4.598 (0.9972) 4.581 (0.9835) 4.579 (0.9693) PASS (all 13 buffers)
P=0, nt=1984 969.125 969.359 (1.0006) 962.948 (1.0010) 963.656 (0.9987) PASS (all 13 buffers)
P=3000, nt=1984 1001.609 1013.505 (1.0071) 1005.969 (0.9994) 1006.901 (1.0026) PASS (all 13 buffers)
P=3000, nt=2048 1038.339 1042.750 (1.0051) 1044.297 (1.0004) 1036.656 (1.0036) PASS (all 13 buffers)

2. qsa_score_mm Loop Unrolling + qp Pre-offsetting (qsa_prompt_parity --mm-compare score, 15 alternating pairs) #

T pos0 Baseline (ms) Candidate (ms) Delta static_tg_mem Pre/Post memcmp
1984 0 0.178750 0.176958 -1.00% 16384 -> 16384 PASS / PASS
1984 2051 0.465417 0.469875 +0.96% 16384 -> 16384 PASS / PASS
1984 8192 1.346583 1.332583 -1.04% 16384 -> 16384 PASS / PASS
1984 30000 4.467125 4.476417 +0.21% 16384 -> 16384 PASS / PASS

Harness Additions Shipped in Patch #

  • metal/tests/qsa_parity.cpp:
    • Added --ab REF_DIR [P] mode that runs full-buffer bitwise parity (memcmp == 0) and warmed alternating paired timings across decode nt = 1, 2, 4, 8 and prompt nt = 1984, 2048 in a single < 6s invocation.
    • Switched --paired timing loops to use cx.run({ds[v][i]}, repeats) with cx.warm(...) and removes per-turn CPU memset of GPU buffers inside the timing loop.
  • metal/tests/qsa_prompt_parity.cpp:
    • Added --ab T pos0 baseline_qsa_prompt.metal baseline_qsa.metal pairs mode testing qsa_prep, qsa_score_mm, and combined (qsa_prep + qsa_score_mm) with full 10-buffer bitwise memcmp verification (supporting QSA_MROPE=1 and QSA_QF16=1) and 12-layer-per-command-buffer paired A/B timings.
    • Added cx.warm(...) before --mm-compare paired timing loops.

Verification Artifacts #

  • Unified patch: /tmp/argon_r36_qsa/argon_r36_qsa.patch
  • git -C vendor/strata apply --check /tmp/argon_r36_qsa/argon_r36_qsa.patch: exit 0 (clean).
  • Baseline kernel snapshots preserved in /tmp/argon_r36_qsa/base_kernels/.