opus r36 track A: decode predL_/predL2_ token-0 fast path + gemv micro-opts #
patch: /tmp/opus_r36_decode/opus_r36_decode.patch (git -C vendor/strata apply --check clean against the current working tree)
scratch: /tmp/opus_r36_decode/strata (build in build-metal, METAL_CPP_DIR set; window.cpp + strata_window_cli build clean, no new warnings)
what changed #
window.cppWindow::layerpred block: whenSTRATA_METAL_PREFETCH_DRAFT <= 0(read the same way as stream.cppenv_int, default 1) thepredL_/predL2_gemvs run atntok = 1on the window's tile (tile_ntok = ntok). otherwise unchanged. verified readers:ExpertStream::aheadis the only consumer of the float pred rows (viaat_layer/after_layer), and withk_draft_max <= 0it skips everyt > 0. prompt chains passpred = nullptr(cut_ids_), so they're unaffected. live server (pid 78503) runs withSTRATA_METAL_PREFETCH_DRAFT=0, so this path is on in production. rows t >= 1 of predL_/predL2_ now hold stale data; nothing reads them in this mode.Window::gemv: new trailingint tile_ntok = 0(0 = use ntok). NOTE: this touches the declaration at window.cpp:320 (outside the listed line ranges, but the default parameter can't be added any other way).- prewarm: no change needed,
prewarm_pipelinesrunslayer()for t = 1..max_t_, so it makesgemv_8_16_1_4_1#1. gemv_parity --pred TYPE NIN NOUT NTOK L,R,S: new mode. token 0 of 2x gemv#NTOK vs 2x gemv#1 vs 1x gemvc#1, memcmp, then 3-way rotated paired timings (GEMV_AB_COLD=1 for DRAM weights).gemv.metal: no change shipped (see below).
measurements (M5, live server running, so a few runs were noisy outliers; the 6x repeats below were stable) #
parity: token 0 bitwise for nt = 2, 3, 4 (all three variants). gemv_parity default: ALL PASS (7.9 s).
Q8_0 2560 -> 512+512, tile 16,1,4,1, median of 15 rotated rounds, 6 invocations each:
| 2 gemv #4 (before) | 2 gemv #1 (shipped) | gemvc #1 | |
|---|---|---|---|
| hot | 21.64-21.66 us | 18.09-18.14 us (-16.3%) | 18.47-18.54 us (-14.5%) |
| cold | 24.07-24.32 us | 22.72-23.13 us (-5.6%) | 22.82-23.15 us (-5.2%) |
nt=3: hot -3.1%, cold ~0. nt=2: hot +1.3%, cold -0.8% (#2 already takes the raw-x direct path, so it's a wash). gemv_cat at #1 loses to the concurrent pair in every config, so it's NOT wired. expected saving per 4-token verify window: ~46 x (1.4 .. 3.5) us = ~65-160 us of GPU time, before overlap with the router GEMV they sit beside. small but free; not measured end-to-end (no full-model runs per rules).
gemv.metal experiments (rejected, all bitwise, --ab vs pristine kernels) #
gemv_direct_xwidened to NTOK <= 4 at LPR 16 NR 1: nt=4 +1.7% hot / +4.3% cold (2560->512), +1.3..1.4% (2560->10240); nt=3 ~0. matches the existing "wider raw-dot windows did not improve" note. reverted.[[max_total_threads_per_threadgroup(SG * 32)]]on gemv: 0.0 +- 0.9% across nt 1/4 hot/cold. reverted.- for reference, single gemv #1 2560->512 Q8_0: 16,1,4,1 5.9 us hot / 12.0 cold, 32,1,4,4 7.1 / 11.7, 32,1,4,1 6.4 / 11.6, so a non-bitwise tile swap for the pred path wouldn't buy anything either.