opus r11 dense: gemm_f16 tile table + fused shared-expert swiglu #

patch: /tmp/opus_r11_dense/opus_r11_dense.patch (git -C vendor/strata apply --check clean, 188 lines; touches metal/kernels/dense_mm.metal, metal/tests/dense_mm_parity.cpp, metal/src/engine/window.cpp 3301-3326 only). experimental/rejected: /tmp/opus_r11_dense/experimental_swiglu_and_26_tiles.patch (fused kernel + all 26 tile instantiations). raw logs: /tmp/opus_r11_dense/tiles/ (sweep M_shape*.txt, A/B final_ab_*, quiet_ab_*). scratch build: /tmp/opus_r11_dense/strata/build-metal (dense_mm_parity + strata_metal_kernels incl. window.cpp compile clean).

headline #

  • every gemm_f16 tile is bitwise identical on M5. 26 tiles (TM 16..128, TN 32..256, SG 1..8) x 12 production (N, K) shapes x M = 256/512/1024/1983/2048: memcmp == 0 against 128_64_4 in all cases (0xFF-poisoned Y, so unwritten elements would also fail). MPP's K reduction order doesn't depend on the tile. So tile choice is free, speed only.
  • new selector: 4 new instantiations (16_64_2, 32_64_2, 32_128_2, 64_32_2), selector rewritten for M > 128 (M <= 128 bit-for-bit the old choice):
    • N <= 64 (Wal/Wbe/Wsg): 16_64_2 for M < 1024, else 64_32_2 (old 64_32_4: 1.3-1.7x on Walbe)
    • N <= 128 (Wik): 16_64_2 for M < 768, else 32_64_2 (1.1-1.3x)
    • HC read (324 x 10240): old tile for M < 1536, 32_128_2 above (1.05-1.11x at 1983)
    • N <= 512 (Wr/Wr2/Wk/Wv/Wiq) and Wshg/Wshu below M 384: 32_64_2 for M < 384, 32_128_2 above (1.02-1.2x)
    • everything else (Wqkv, Wz, Wout, Wq, Wpv, Wshd, Wshg/u): 128_64_4 for M < 1536, 64_128_4 above. That's the old "bug #1" (64_128 only at M >= 2048). At M = 1983 128_64_4 falls off a cliff on Wout (K = 6144): 6.3-8.2 ms vs 4.4-4.7 ms in quiet runs (1.5-1.8x), and is 6-10% slower on the other big shapes. Below ~1792 the two tie (±1%), so the threshold is conservative at 1536.
  • fused gemm_swiglu_f16: built as asked (two get_destination_cooperative_tensor accumulators, dynamic_length_v K, no threadgroup memory, no k0 loop, swiglu_h's exact expression in registers, direct half stores). Bitwise identical (memcmp == 0 on h) for all 10 tiles tried, but NOT faster: best fused 1016 us vs best unfused 976 us (2x gemm 64_64_2
    • swiglu_h) at M 1983. Two TMxTN accumulators double register pressure (fused 64_64_4: 1862 us). swiglu_h + the fp32 g/u traffic is only ~12-20 us/layer (<1 ms/chunk), so even a perfect fusion is capped at ~1 ms/chunk. Rejected, not in the patch. (One untested idea: dq Wshg/Wshu interleaved with perm=2 into one N=1280 B, then one op.run with the gemm_hc_up REG epilogue pairing adjacent columns. One accumulator, but the same ~1 ms ceiling. Not worth it.)
  • Nn = 1 (Wsg): no dedicated row-dot. A hand dot can't match MPP's reduction bits, and at large M it's memory-bound on reading A (~85 us = A at ~120 GB/s). Tile changes give 1.3-1.45x at M <= 512, ~1.03x at 1983.

caveat: GPU contention #

A sibling agent was running moe_mm_parity (and the :8080 server was live) through most of the measurements, so absolute times drift by up to 40% between runs. Within a process the rounds interleave old and new, so the paired ratio is the reliable number. The first sweep (mostly quiet) gives the clearest absolute picture.

quiet-ish sweep, ms per 48-layer chunk (min of 1-2 runs/shape, 12 shapes x calls) #

M old selector sweep-best per shape
256 151.2 131.1
512 257.2 248.4
1024 515.5 508.1
1983 1215.8 (kprof said 1241.6) 982.0

final paired A/B, shipped selector (dense_mm_parity --ab M, 5 runs x 9 rounds, contended) #

M old ms/chunk new ms/chunk speedup saved ms/chunk
256 143.5 140.9 1.018x 2.5
512 283.2 279.2 1.014x 4.0
1024 626.1 631.9 0.991x -5.8
1983 1469.4 1401.6 1.048x 67.8
2048 1465.2 1460.1 1.004x 5.2

(At 1024 the shipped selector is identical to old for all big shapes; the -0.9% there comes from the earlier 1024-threshold version and from noise. Rerun after the switch to 1536: 1024 1.01x, 1536 1.01x, 1983 1.05x.) The contended runs understate the 1983 win. In quieter runs the 1983 per-chunk saving is ~140-230 ms (1.13-1.19x), mostly Wout (1.5-1.8x) plus Wqkv/Wz/Wq/hc_read (1.06-1.11x).

per-shape paired A/B at 1024 and 1983 (pre-1536-threshold run; at 1024 the big shapes are now old=new) #

M shape (N x K) calls old tile old us new tile new us paired speedup
256 hc_read (324 x 10240) 96 64_64_4 194.0 64_64_4 195.2 1.000x
256 Wqkv (10240 x 2560) 37 128_64_4 967.8 128_64_4 967.3 0.999x
256 Wz (6144 x 2560) 36 128_64_4 607.9 128_64_4 604.2 1.003x
256 Wout (2560 x 6144) 48 128_64_4 597.4 128_64_4 599.8 0.998x
256 Wq (12288 x 2560) 12 128_64_4 1384.9 128_64_4 1385.9 1.000x
256 Wpv (2560 x 2560) 1 128_64_4 298.5 128_64_4 297.1 1.001x
256 Wshgu (640 x 2560) 96 128_64_4 71.3 32_64_2 69.5 1.080x
256 Wshd (2560 x 640) 48 128_64_4 65.3 128_64_4 65.5 0.997x
256 W512 (512 x 2560) 119 128_64_4 64.9 32_64_2 53.5 1.213x
256 Wik (128 x 2560) 12 64_64_4 32.5 16_64_2 24.7 1.312x
256 Walbe (48 x 2560) 72 64_32_4 29.8 16_64_2 21.4 1.388x
256 Wsg (1 x 2560) 48 64_32_4 27.3 16_64_2 18.9 1.444x
512 hc_read (324 x 10240) 96 64_64_4 294.5 64_64_4 295.4 0.998x
512 Wqkv (10240 x 2560) 37 128_64_4 1912.6 128_64_4 1884.6 1.005x
512 Wz (6144 x 2560) 36 128_64_4 1293.9 128_64_4 1286.1 1.002x
512 Wout (2560 x 6144) 48 128_64_4 1360.1 128_64_4 1361.3 1.000x
512 Wq (12288 x 2560) 12 128_64_4 2779.6 128_64_4 2767.6 1.000x
512 Wpv (2560 x 2560) 1 128_64_4 575.6 128_64_4 576.5 1.001x
512 Wshgu (640 x 2560) 96 128_64_4 145.6 128_64_4 145.6 0.999x
512 Wshd (2560 x 640) 48 128_64_4 129.7 128_64_4 127.4 1.001x
512 W512 (512 x 2560) 119 128_64_4 115.9 32_128_2 103.4 1.120x
512 Wik (128 x 2560) 12 64_64_4 36.6 16_64_2 31.1 1.174x
512 Walbe (48 x 2560) 72 64_32_4 34.5 16_64_2 25.4 1.362x
512 Wsg (1 x 2560) 48 64_32_4 30.5 16_64_2 23.2 1.315x
1024 hc_read (324 x 10240) 96 128_64_4 670.6 128_64_4 672.8 0.997x
1024 Wqkv (10240 x 2560) 37 128_64_4 4440.1 64_128_4 4562.4 0.993x
1024 Wz (6144 x 2560) 36 128_64_4 2784.0 64_128_4 2775.6 1.001x
1024 Wout (2560 x 6144) 48 128_64_4 2938.9 64_128_4 3041.4 0.966x
1024 Wq (12288 x 2560) 12 128_64_4 5928.4 64_128_4 5930.2 1.003x
1024 Wpv (2560 x 2560) 1 128_64_4 1300.8 64_128_4 1290.5 1.006x
1024 Wshgu (640 x 2560) 96 128_64_4 324.0 64_128_4 322.5 1.001x
1024 Wshd (2560 x 640) 48 128_64_4 301.0 64_128_4 305.7 1.030x
1024 W512 (512 x 2560) 119 128_64_4 263.3 32_128_2 247.4 1.064x
1024 Wik (128 x 2560) 12 128_64_4 73.0 32_64_2 57.3 1.262x
1024 Walbe (48 x 2560) 72 64_32_4 58.2 64_32_2 38.2 1.529x
1024 Wsg (1 x 2560) 48 64_32_4 35.9 64_32_2 34.0 1.059x
1983 hc_read (324 x 10240) 96 128_64_4 1497.2 32_128_2 1408.9 1.063x
1983 Wqkv (10240 x 2560) 37 128_64_4 9938.0 64_128_4 9562.1 1.041x
1983 Wz (6144 x 2560) 36 128_64_4 6553.3 64_128_4 6313.0 1.044x
1983 Wout (2560 x 6144) 48 128_64_4 7152.7 64_128_4 6726.0 1.062x
1983 Wq (12288 x 2560) 12 128_64_4 14676.2 64_128_4 13957.6 1.040x
1983 Wpv (2560 x 2560) 1 128_64_4 3087.7 64_128_4 2953.1 1.051x
1983 Wshgu (640 x 2560) 96 128_64_4 771.2 64_128_4 769.1 1.002x
1983 Wshd (2560 x 640) 48 128_64_4 758.8 64_128_4 737.8 1.026x
1983 W512 (512 x 2560) 119 128_64_4 600.6 32_128_2 592.2 1.015x
1983 Wik (128 x 2560) 12 128_64_4 174.9 32_64_2 155.8 1.112x
1983 Walbe (48 x 2560) 72 64_32_4 157.4 64_32_2 89.6 1.732x
1983 Wsg (1 x 2560) 48 64_32_4 86.9 64_32_2 83.2 1.042x
2048 hc_read (324 x 10240) 96 128_64_4 1527.5 32_128_2 1472.4 1.034x
2048 Wqkv (10240 x 2560) 37 64_128_4 10085.9 64_128_4 10099.5 0.992x
2048 Wz (6144 x 2560) 36 64_128_4 6538.5 64_128_4 6593.6 0.989x
2048 Wout (2560 x 6144) 48 64_128_4 7122.7 64_128_4 7182.9 1.000x
2048 Wq (12288 x 2560) 12 64_128_4 14137.9 64_128_4 14126.0 0.998x
2048 Wpv (2560 x 2560) 1 64_128_4 3071.0 64_128_4 3067.0 1.000x
2048 Wshgu (640 x 2560) 96 128_64_4 762.8 64_128_4 766.5 1.005x
2048 Wshd (2560 x 640) 48 128_64_4 688.5 64_128_4 690.9 1.001x
2048 W512 (512 x 2560) 119 128_64_4 602.7 32_128_2 596.9 1.011x
2048 Wik (128 x 2560) 12 128_64_4 164.2 32_64_2 150.9 1.115x
2048 Walbe (48 x 2560) 72 64_32_4 155.5 64_32_2 92.0 1.718x
2048 Wsg (1 x 2560) 48 64_32_4 88.5 64_32_2 85.4 1.032x

full sweep: best 3 tiles per shape (us/call, min over runs), all bitwise #

M shape old tile (us) fastest 3 (us)
256 hc_read 64_64_4 (352) 32_32_1 185, 64_32_2 189, 32_128_4 200
256 Wqkv 128_64_4 (960) 128_128_8 945, 128_64_4 960, 64_128_8 965
256 Wz 128_64_4 (579) 64_64_2 562, 32_128_2 571, 64_128_4 577
256 Wout 128_64_4 (559) 64_256_8 553, 64_64_2 555, 64_128_4 556
256 Wq 128_64_4 (1122) 128_64_4 1122, 64_64_2 1127, 64_128_4 1132
256 Wpv 128_64_4 (243) 64_128_2 235, 64_256_4 236, 64_64_2 236
256 Wshgu 128_64_4 (65) 64_64_4 60, 32_128_4 60, 64_32_2 60
256 Wshd 128_64_4 (65) 128_256_8 63, 128_64_8 63, 64_256_4 63
256 W512 128_64_4 (61) 32_64_2 54, 64_32_2 54, 32_32_1 54
256 Wik 64_64_4 (33) 16_64_2 25, 16_32_1 26, 16_128_4 26
256 Walbe 64_32_4 (31) 16_128_4 21, 16_64_2 22, 16_32_1 23
256 Wsg 64_32_4 (27) 16_32_1 18, 16_64_2 19, 16_128_4 21
512 hc_read 64_64_4 (335) 32_128_4 295, 32_128_2 300, 64_64_2 300
512 Wqkv 128_64_4 (1872) 128_128_8 1835, 128_64_4 1872, 64_128_4 1877
512 Wz 128_64_4 (1113) 128_64_4 1113, 64_64_2 1118, 64_128_4 1119
512 Wout 128_64_4 (1111) 64_128_4 1098, 64_64_2 1104, 128_128_8 1105
512 Wq 128_64_4 (2237) 64_128_4 2226, 128_128_8 2236, 64_64_2 2236
512 Wpv 128_64_4 (463) 64_64_2 462, 128_64_4 463, 128_128_8 463
512 Wshgu 128_64_4 (116) 128_64_4 116, 128_128_8 116, 64_128_4 117
512 Wshd 128_64_4 (121) 128_128_8 120, 128_64_4 121, 64_128_4 121
512 W512 128_64_4 (116) 32_128_2 101, 64_64_2 103, 32_64_2 106
512 Wik 64_64_4 (34) 16_64_2 31, 16_32_1 32, 16_128_4 34
512 Walbe 64_32_4 (35) 16_64_2 26, 16_32_1 26, 16_128_4 30
512 Wsg 64_32_4 (30) 16_32_1 21, 16_64_2 23, 32_32_2 28
1024 hc_read 128_64_4 (660) 128_64_4 660, 64_128_4 663, 64_64_2 669
1024 Wqkv 128_64_4 (3702) 64_128_4 3694, 128_64_4 3702, 128_128_8 3703
1024 Wz 128_64_4 (2236) 128_128_8 2232, 64_128_4 2233, 128_64_4 2236
1024 Wout 128_64_4 (2331) 128_64_4 2331, 64_128_4 2343, 64_64_2 2353
1024 Wq 128_64_4 (4430) 128_128_8 4412, 128_64_4 4430, 64_128_4 4431
1024 Wpv 128_64_4 (977) 64_128_4 936, 64_64_2 967, 32_128_4 972
1024 Wshgu 128_64_4 (256) 128_128_4 232, 64_64_2 236, 64_128_2 236
1024 Wshd 128_64_4 (248) 128_128_8 236, 64_128_4 237, 64_256_8 237
1024 W512 128_64_4 (214) 64_64_2 198, 32_128_2 199, 64_64_4 206
1024 Wik 128_64_4 (62) 32_64_2 53, 64_32_2 53, 32_32_1 54
1024 Walbe 64_32_4 (58) 16_32_1 35, 16_64_2 36, 128_64_8 37
1024 Wsg 64_32_4 (36) 16_32_1 34, 16_64_2 34, 64_32_2 34
1983 hc_read 128_64_4 (1314) 32_128_2 1170, 64_64_2 1214, 64_128_4 1252
1983 Wqkv 128_64_4 (7729) 64_128_4 7239, 32_128_2 7251, 32_256_4 7330
1983 Wz 128_64_4 (4645) 64_128_4 4361, 32_128_2 4385, 64_64_2 4406
1983 Wout 128_64_4 (6311) 32_256_4 4424, 32_128_2 4437, 64_128_4 4448
1983 Wq 128_64_4 (9301) 64_128_4 8686, 32_128_2 8740, 64_64_2 8818
1983 Wpv 128_64_4 (1963) 64_128_4 1824, 32_128_2 1842, 32_256_4 1855
1983 Wshgu 128_64_4 (486) 32_128_2 475, 64_128_4 477, 64_64_2 480
1983 Wshd 128_64_4 (473) 64_256_8 460, 64_128_4 463, 64_64_2 463
1983 W512 128_64_4 (414) 32_128_4 396, 32_128_2 401, 64_64_2 404
1983 Wik 128_64_4 (119) 32_128_2 104, 64_64_2 106, 32_64_2 108
1983 Walbe 64_32_4 (116) 64_32_2 85, 128_64_8 87, 16_64_2 87
1983 Wsg 64_32_4 (88) 16_32_1 85, 16_64_2 85, 32_32_2 86
2048 hc_read 128_64_4 (1325) 32_128_2 1196, 128_128_4 1220, 64_64_2 1265
2048 Wqkv 64_128_4 (7558) 64_128_4 7558, 32_128_2 7560, 32_256_4 7616
2048 Wz 64_128_4 (4674) 32_128_2 4647, 64_128_4 4674, 32_256_4 4686
2048 Wout 64_128_4 (4685) 32_256_4 4668, 64_128_4 4685, 32_128_2 4733
2048 Wq 64_128_4 (9334) 32_128_2 9299, 64_128_4 9334, 64_256_8 9371
2048 Wpv 64_128_4 (1936) 64_128_4 1936, 32_128_2 1947, 32_256_4 1975
2048 Wshgu 128_64_4 (497) 64_128_4 478, 64_64_2 492, 128_128_8 497
2048 Wshd 128_64_4 (475) 128_64_4 475, 64_256_8 476, 64_128_4 480
2048 W512 128_64_4 (421) 64_128_4 410, 32_256_4 411, 32_128_2 412
2048 Wik 128_64_4 (119) 32_128_2 107, 64_64_2 108, 32_64_2 109
2048 Walbe 64_32_4 (118) 128_64_4 86, 64_32_2 90, 128_64_8 98
2048 Wsg 64_32_4 (92) 16_64_2 91, 16_32_1 92, 32_32_2 92

verification #

  • dense_mm_parity (existing parity): ALL PASS with the patched kernels.
  • dense_mm_parity --tiles M [shape] / --ab M [shape]: every run exits 0 with "ALL BITWISE" (memcmp vs 128_64_4). Each invocation runs < 5 s (max seen 4.2 s under contention, 1.5-2.7 s typically).
  • window.cpp compile-checked via ninja strata_metal_kernels in the scratch tree. No full-model run (per the rules), so the end-to-end kprof gain still needs to be measured by the parent.
  • Instantiation cost: 26 extra tiles added ~0.8-1 s to the runtime MSL compile, which is why only the 4 the selector uses ship.
  • new_tile in the test mirrors window.cpp by hand. If one changes, change the other.