round 28 track b: fused router #

decision #

primary patch: /tmp/sol_r28_router/sol_r28_router.patch. it adds the short paired --router-ab evaluator and a q8_0 router entry point. route_body and router_route behavior are unchanged, as requested by parent after the initial negative results. moe.metal is untouched. no tracked vendor files were edited.

an optional alternative (not cumulative) patch is /tmp/sol_r28_router/sol_r28_router_f32_nt7_optional.patch. it additionally specializes f32, ntok=7 using direct four-chain fmas. this narrow case repeatedly improved ~5%, but does not accelerate this model's bf16 router. parent can choose whether to audit/promote it.

contract and setup #

  • question: do fixed inner-loop unrolls, removing the lookup-table barrier, or direct register combines speed the real fused router without changing any output bits?
  • baseline: working-tree kernel snapshot, vendor head 296b59d2f73c8f04a805df29c2fa97655adace78; copied into /tmp/sol_r28_router/base. q8 entry point appended to baseline solely to expose the requested evaluator case. source snapshots and raw run records remain under /tmp/sol_r28_router/.
  • owner/path: router kernel's per-layer fused gemv/routing dispatch. preserve ABI, counter reset and fences, fma/reduction order, split plans and residency rules. no host changes.
  • hardware: local apple m5, safe math (STRATA_METAL_MATH=safe). shared machine/live server was left running; absolute clocks visibly fluctuate, so use alternating paired ratios, not ratio of separately aggregated medians.
  • dimensions: n_in=2560; n_expert=512 or 256. types 30/bf16, 8/q8_0, 0/f32; ntok 1..8; split 0/1.
  • decision rule written before sweep: aggregate improvement >=2%, no stable >3% regression; then independent confirmation. no claim of whole-model speedup.
  • workload budget: no :8080 requests, no full-model runs, serial short standalone tests only. cached matrix invocations ~0.1–0.5s; first successful cold compile 13.4s. an initial attempt including the entire gemv kernel library hit its 20s compile deadline and was killed; evaluator repaired to supply only the gemv argument struct and NTOK constant.

parity and timing #

four deterministic parity trials per case cover random matrices, n_expert=256, exact ties (identical rows), residency maps, moce on/off and the draft-moCE flag. memcmp compares the entire allocated logits, ids, weights and plan buffers, including sentinel-filled inactive output space; both counters must reset to zero. 48 cases x4 = 192 comparisons per complete sweep, all pass for each ablation and final primary/optional matrices.

timing: 15ms warmup, seven alternating a/b pairs, 16 dispatch repetitions each; report median paired candidate/base ratio. early ablation sweeps timed the final parity trial's draft-moCE settings. final harness explicitly resets moce to zero for timing. seed 928; random weight seeds 928..931.

early ablation type id median ratio over ntok/split min–max
unroll 0 1.00373 0.99935–1.01229
unroll 8 0.99866 0.98896–1.01130
unroll 30 0.99910 0.99032–1.01450
guard 0 1.01801 0.99831–1.04143
guard 8 1.00790 0.99006–1.02247
guard 30 1.01531 1.00424–1.04164
unroll+guard+direct-bf16 0 1.02106 1.00761–1.04995
unroll+guard+direct-bf16 8 1.00716 0.99807–1.02363
unroll+guard+direct-bf16 30 1.04608 1.01363–1.11216
direct-only 0 1.02086 0.94422–1.08185
direct-only 30 1.04110 1.01015–1.11666

conclusion: no reproducible benefit from unrolling alone; the barrier guard generally regressed slightly; direct bf16 worsened ntok5 by ~11% and ntok6/7 by ~7–9%. these proposed production-bf16 changes are rejected, not shipped. source-identical controls gave ratios 0.99823 / 0.99009 / 1.00559; small differences near 1% are not wins.

the isolated optional f32 ntok7 path was a repeatable exception. moce-off confirmation, three invocations per split:

repetition split0 ratio split1 ratio
1 0.93889 0.94086
2 0.94834 0.95160
3 0.95195 0.94308

representative steady-clock gpu times: 66.711 -> 63.505us (split0), 67.125 -> 63.633us (split1). this is narrowly a standalone f32 seven-token result, not a bf16/live-server result. four fma chains preserve unit_combine<16>'s arithmetic order. full optional matrix: 48/48 cases passed. optional confirmation source differs from primary only by the scoped direct path.

legacy cpu-reference route test: 400 windows, zero id/plan mismatches, max relative weight errors 2.16e-7 (k512) and 3.06e-7 (k256/moce), exit0, 2.20s. primary patch's behavior-identical matrix also passes 48/48.

reproduction and artifacts #

build command (from project root):

clang++ -std=c++20 -O2 /tmp/sol_r28_router/strata/metal/tests/route_parity.cpp -Ivendor/strata/metal/src vendor/strata/build-metal/libmtl_rt.a -framework Metal -framework Foundation -o /tmp/sol_r28_router/route_parity

single case (numeric type ids 30/8/0; ntok1..8; split0/1):

STRATA_METAL_MATH=safe STRATA_METAL_KERNELS=/tmp/sol_r28_router/strata/metal/kernels /tmp/sol_r28_router/route_parity --router-ab /tmp/sol_r28_router/base 30 1 0

baseline directories must contain the q8 router instantiation when testing type8. comparator compiles only quant, units, moe and router, avoiding unrelated gemv specialization compile cost.

raw records: direct-results.json, guard-results.json, unroll-results.json, direct-only-results.json, confirmation.json, narrow-results.json, production-confirmation.json, final-results.json, primary-results.json, legacy-parity.txt, control.txt under /tmp/sol_r28_router/. candidate snapshots: unroll_guard.metal, guard.metal, direct.metal, direct-only.metal. the primary scratch tree now contains only the harness and q8 entry, not optional specialization.

review: visual diff and surrounding code reviewed; helper search found gemv_raw_combine, but it requires decoded thread-local q arrays and thus does not implement the optional direct weight-load path. git diff --check passes. git -C vendor/strata apply --check /tmp/sol_r28_router/sol_r28_router.patch passes. no subagents, server interaction or full-model benchmarks.