Apple M5 (base · 10 GPU cores · 24 GB) · LLM-driven evolutionary kernel search · Part 2 of the measurement project · 2026-07-21 → 07-27

Evolving GPU kernels against a measured map

In 2025, DeepMind's AlphaEvolve made a specific recipe famous: wrap a large language model in an evolutionary loop and point it at problems where a better program is worth real money. The loop is simple: propose a code change, score it with an automated evaluator, keep what wins, mutate what was kept. The reported results ran from a new 4×4 complex matrix multiplication algorithm (48 multiplications, the first improvement on Strassen's 49 in that setting since 1969) to a ~0.7% recovery of Google's fleet compute and a 23% faster matmul kernel inside Gemini's own training stack. The same recipe, in different clothes, produced FunSearch's combinatorics results and AlphaDev's sorting routines that shipped into LLVM's standard library.

This project asks a smaller, sharper version of the question: does that recipe work on my laptop, against Apple's own inference stack, on silicon whose instruction set nobody outside Apple can read? Part 1 built the prerequisite: a measured map of where MLX leaves performance on the table: which operations, at which shapes, against ceilings my machine actually sustains rather than the ones on the spec sheet. This page is what happened when an evolutionary search was pointed at that map, three times.

The short version: the recipe works, and it is a power tool with no judgment. An evolved attention kernel now beats Apple's shipping implementation by 2.3×, reaching 80% of the hardware ceiling on an operation the stock stack ran at 34%. But the same loop, with the same mechanics, the same mutator family, and the same safety rails, returned 0.96× when its fitness function sat on a noise floor, and a kernel worth only 1.04× end-to-end when it was pointed at the wrong operation. The search executes a diagnosis; it does not produce one. Everything below is mined from the per-candidate logs: every score, every failure, and the parent-to-child code diff behind every jump worth naming.

3 campaigns · 265 candidates logged incl. every failure and safety rejection · 212 with full code in the evolution traces · generated 2026-07-27 14:29

The lineage · four systems, one recipe

AlphaTensor (DeepMind, 2022)
Reinforcement learning over tensor decompositions found faster matrix-multiplication algorithms, but only for problems small and formal enough to encode as a game.
AlphaDev (2023)
The same machinery aimed at assembly found branchless sorting routines that shipped into LLVM's libc++: real code, but a search space of a few dozen instructions.
FunSearch (2023)
The pivotal move: replace the game encoding with a large language model proposing whole programs, and keep only an automated evaluator as the arbiter. New results in combinatorics from evolving Python functions.
AlphaEvolve (2025)
The mature form. LLM mutations over full codebases, population management, automated scoring at scale. Reported: a 48-multiplication 4×4 complex matmul (Strassen's 49 stood since 1969), ~0.7% of Google's fleet compute recovered, a 23% faster matmul kernel in Gemini's own training stack.

Why kernels are the perfect prey

Everything this recipe needs, GPU kernels have. An objective, automatic score: kernels are timed, not judged. A cheap, reliable correctness oracle: compare against a reference implementation on shapes the candidate has never seen. A search space too gnarly for gradient methods and too large for humans to sweep: tile sizes, memory layouts, thread mappings, and algorithmic restructurings interact in ways nobody can predict on undocumented silicon. That is exactly the regime where trying ten thousand things beats reasoning about one.

The catch, and the theme of this page: the recipe outsources proposing and scoring, but not choosing what to optimise or deciding whether a score can be trusted. Those stayed human problems here, and every failure below traces to one of them.

How the loop works · evolution with an LLM where random mutation used to be

Classical genetic programming mutates programs blindly: flip a node in a syntax tree and hope. The modern recipe replaces blind mutation with a language model that has read every GPU programming guide ever written: it receives a parent program, the parent's measured scores per test shape, the error text of recent failures, and a system prompt carrying the project's hardware facts, then proposes a complete rewritten program. Selection stays classical: an archive keeps winners, parents are drawn from the archive, and losers are discarded but logged.

Two structures keep the population from collapsing onto its first good idea. MAP-Elites keeps the best candidate per region of a feature space (here, efficiency × code complexity), so a slow-but-novel approach survives next to the current champion instead of being culled by it. Islands evolve three sub-populations with occasional migration, so lineages can develop differently before competing. And because most mutations are simply broken, cascade evaluation kills them cheaply: a small correctness smoke first, the expensive full measurement only for survivors.

The under-appreciated channel is the failure text. A candidate that dies returns its compiler error, its mismatch against the reference, or its safety rejection , verbatim, to the mutator's next prompt. Half of what looks like insight in the ascent charts below is the loop reading its own autopsies.

Why OpenEvolve underneath

The controller here is OpenEvolve, an open-source implementation of the AlphaEvolve recipe: the MAP-Elites database, islands, cascade evaluation, checkpoint/resume, and the artifact channel that feeds failure text back to the mutator. Three properties made it the right base. It is model-agnostic: the mutator is whatever command you give it, which here means the same flat-rate CLI the rest of the project runs on, swapped between model tiers with one config line. It is inspectable: when a run stopped early or scored oddly, the answer was in readable Python, twice found by reading it. And it checkpoints, which matters on a machine that this project has crashed.

What had to be built around it

the evaluatorsevery gate in them is a scar from Part 1
the safety railsscreens, subprocess isolation, dead-man
champion defenseworst-of-3 spaced re-measurement
the prompt packthe measured map, compressed for the mutator
the seedshand-written kernels that already beat stock
post-run verificationfresh-process gates, E2E quality gates
The division of labour is honest: OpenEvolve supplies the evolution; everything that made the evolution trustworthy had to be supplied. The controller will happily evolve against a broken fitness function. Campaign #1 proved it.

The loop, once

A campaign is a fitness function with a population attached. Each iteration: pick a parent from a MAP-Elites archive, hand the mutator the program, its measured scores per shape, and the failure text of anything that just died; get back a complete rewritten program; score it. Correctness runs first: an fp32 reference, ragged holdout shapes, both mask modes, and bit-determinism across calls. A candidate that fails any of it scores zero with the reason fed back to the mutator. Speed is measured paired against the shipping kernel, and anything beating the best-so-far faces the champion defense: the worst of three spaced re-measurements becomes its score.

The prompt is where Part 1 lives. The mutator is told the measured ceilings, which silicon each path runs on, the fallback's real throughput, the threadgroup budget, the correctness traps, and the one mistake that hangs the machine rather than failing. Every fact in that prompt was paid for by a measurement; the campaigns that lacked one paid for it differently.

The rails, because one bad kernel can kill my machine

static screenno unbounded loops; rejected before the GPU
process isolationevery eval in a SIGKILL-able subprocess
hang cap240 s, then the worker dies, not the machine
dead-man switchfirst hang disables ALL further GPU work
machine-wide lockone flock for every GPU entrypoint
telemetryevery candidate logged, including the rejects
These exist because my laptop was frozen once and kernel-panicked twice before they did. A search that can emit arbitrary GPU code will eventually emit a kernel that never terminates.

Campaign #1 · why MoE FFN, and how it was configured

The first target was chosen the intuitive way, which is the cautionary part: the mixture-of-experts feed-forward block moves the most bytes per decoded token, and the project's first audit (single-call timing, the only kind that existed yet) showed it well short of the bandwidth roofline. Big op, apparent headroom, obvious campaign. Both premises were wrong: the headroom was an artifact of timing operations in isolation, and the op was actually running at 90–97% of its ceiling.

Configuration · campaign #1

target opgather_qmm MoE FFN, decode shapes
mutatorclaude-sonnet-5, xhigh reasoning
population80 · 3 islands · MAP-Elites archive
fitnessspeedup vs stock, single-call timing (v1-era)
promotionbest score wins; no noise gate yet
safety screenno unbounded loops, no barriers

Campaign #1 · MoE FFN · the staircase that was not there

0.000.360.721.091.45116304560parity with the libraryiteration 1: 0.9951iteration 1: 0.0000iteration 1: 1.0477iteration 1: 1.0308iteration 1: 1.0300iteration 1: 1.0356iteration 1: 1.0341iteration 2: 1.0120iteration 2: 0.0000iteration 2: 0.0000iteration 2: 0.0000iteration 2: 0.9970iteration 2: 1.0337iteration 3: 1.0052iteration 3: 0.0000iteration 3: 1.0427iteration 3: 1.0243iteration 4: 1.0248iteration 5: 1.0100iteration 5: 1.0163iteration 6: 1.0185iteration 7: 0.0000iteration 7: 1.0161iteration 8: 0.0000iteration 8: 1.0224iteration 9: 1.0441iteration 9: 1.0314iteration 10: 0.0000iteration 10: 1.0395iteration 11: 1.0311iteration 12: 1.0418iteration 13: 1.0280iteration 14: 1.0342iteration 15: 1.0353iteration 16: 1.0330iteration 17: 1.0327iteration 18: 1.0349iteration 19: 1.0276iteration 20: 1.0503iteration 21: 1.0373iteration 22: 1.0464iteration 23: 1.0354iteration 25: 1.0300iteration 26: 1.0484iteration 27: 1.0158iteration 28: 1.0469iteration 29: 1.0514iteration 30: 1.0323iteration 31: 1.0385iteration 32: 1.0484iteration 33: 1.0527iteration 34: 1.0255iteration 35: 1.0134iteration 36: 1.0401iteration 37: 1.0456iteration 38: 1.0514iteration 39: 1.0214iteration 40: 1.0284iteration 41: 1.0319iteration 43: 1.0440iteration 44: 1.0595iteration 45: 1.0352iteration 46: 1.0411iteration 47: 1.0343iteration 48: 1.0391iteration 49: 1.0394iteration 50: 1.0378iteration 51: 1.0500iteration 52: 1.0340iteration 53: 1.0349iteration 54: 1.0117iteration 55: 1.0421iteration 56: 1.0357iteration 57: 1.0307iteration 58: 1.0502iteration 59: 1.0466iteration 60: 1.0377re-verified: 0.96iteration
candidaterunning bestindependent re-verificationthe ✕ is sourced from the project log; it predates structured re-verification telemetry
Six “new bests,” 0.995 to 1.059, every step 0.5–1% on a fitness whose paired noise ran ±3–5%. The staircase climbs, the code churns, and none of it is real: the champion re-measured at 0.96×, worse than its seed. This is the winner's curse drawn from its own telemetry.

What #1 taught, and why #2 targeted decode attention

Campaign #1 forced the two reforms every later run stands on. Timing moved to dependent chains: each sample a strip of 64 chained calls, the way generation actually executes. That collapsed the phantom headroom and re-drew the whole opportunity map. And promotion grew a noise gate: a would-be champion must beat the incumbent by more than max(2σ, 1%), then survive three spaced re-measurements with the worst run as its score. On the re-drawn map, one gap survived: decode attention, at 49–71% of its bandwidth roofline across product shapes while everything else sat above 85%. That is where campaign #2 went.

Configuration · campaign #2

target opsdpa decode, 3 product shapes (hd 64/256)
mutatorclaude-sonnet-5, xhigh reasoning
population80 · 3 islands · cascade evaluation
fitnessgeomean chained-strip speedup vs mx.fast.sdpa
promotionmax(2σ, 1%) gate + worst-of-3 defense
holdoutsodd lengths, hidden from the search
run129 iterations, early-stopped on plateau

Campaign #2 · decode attention · 129 iterations, the long grind

0.000.360.721.091.451336597129parity with the libraryiteration 1: 0.6751iteration 2: 0.6139iteration 2: 1.0241iteration 3: 1.0205iteration 4: 0.7654iteration 5: 0.0000iteration 6: 0.6767iteration 7: 0.7101iteration 8: 0.6811iteration 9: 1.0185iteration 10: 0.6281iteration 11: 0.6501iteration 12: 0.7732iteration 13: 1.0305iteration 14: 0.6443iteration 15: 1.0194iteration 16: 1.0093iteration 18: 0.6904iteration 19: 1.0293iteration 20: 0.5342iteration 21: 1.0187iteration 22: 1.0274iteration 23: 1.0264iteration 25: 0.6643iteration 26: 1.0228iteration 28: 1.0209iteration 29: 1.0144iteration 30: 1.0189iteration 31: 0.6740iteration 32: 1.0210iteration 33: 0.6730iteration 34: 1.0068iteration 35: 1.0203iteration 36: 1.0245iteration 37: 0.6853iteration 39: 1.0217iteration 40: 1.0647iteration 41: 1.0203iteration 42: 1.0011iteration 43: 1.0703iteration 44: 0.6835iteration 45: 1.0475iteration 46: 1.0277iteration 47: 1.0627iteration 48: 0.9989iteration 49: 1.0281iteration 50: 1.0551iteration 51: 1.0000iteration 52: 1.0674iteration 53: 1.0466iteration 54: 0.6933iteration 55: 1.0677iteration 56: 1.0586iteration 57: 1.0542iteration 58: 1.0575iteration 59: 1.0718iteration 60: 1.0733iteration 61: 1.0547iteration 62: 1.0605iteration 63: 1.0585iteration 64: 1.0681iteration 65: 0.6882iteration 66: 1.0752iteration 67: 1.0540iteration 68: 1.0685iteration 69: 1.0588iteration 70: 1.0009iteration 71: 1.0623iteration 72: 1.0517iteration 73: 1.0062iteration 74: 1.0522iteration 75: 1.0776iteration 76: 1.0546iteration 77: 1.0721iteration 78: 0.9695iteration 79: 1.1165iteration 80: 1.0526iteration 81: 1.0735iteration 83: 1.0628iteration 84: 1.0611iteration 85: 1.0696iteration 86: 1.1254iteration 87: 1.0746iteration 88: 1.0652iteration 89: 1.0685iteration 90: 1.0090iteration 93: 1.0817iteration 94: 1.0715iteration 95: 1.0658iteration 96: 1.0071iteration 97: 1.0768iteration 98: 1.0507iteration 99: 1.0547iteration 100: 1.0784iteration 101: 1.0214iteration 102: 1.0635iteration 103: 1.0611iteration 104: 1.0829iteration 105: 1.0481iteration 106: 1.0629iteration 107: 1.0649iteration 108: 1.0377iteration 109: 1.0817iteration 110: 1.0612iteration 111: 1.0580iteration 112: 1.0717iteration 114: 1.0657iteration 115: 1.0590iteration 116: 1.0980iteration 117: 1.0696iteration 118: 1.0771iteration 119: 1.0663iteration 120: 1.0628iteration 121: 1.0767iteration 122: 1.0687iteration 125: 1.0684iteration 126: 1.1027iteration 127: 1.0704iteration 129: 1.0543iteration
candidatefailed = 0running best · hover the markers for what each jump changedin-run scores; the shipped champion re-verified at 1.23× geomean, 3× fresh
Half the ascent is subtraction: the jump at 40 deleted failed cleverness on the recorded reasoning that it had measured as a loss. The jumps at 79 and 86 found the real structure: an explicit register budget, then per-shape dispatch.

What #2 taught, and why #3 targeted prefill attention

Campaign #2 produced a genuine kernel, 1.23× re-verified, and the project's most expensive pricing lesson: end-to-end it was worth 1.04×, because decode attention's roofline gap was large but its share of time was not. The value model built in response multiplies efficiency-gap by time-share, and it pointed somewhere else entirely: prefill attention at head-dim 256, running at 34% of the compute ceiling on the operation that dominates time-to-first-token. Two more prerequisites landed before launch: a control experiment separating the kernel deficit from the mask path, and a hand-written seed proving a fused Neural-Accelerator kernel could beat the fallback at all: 1.31×, after a first attempt on the wrong silicon lost at 0.37×.

Configuration · campaign #3

target opprefill attention hd=256, causal, 3 shapes
mutatorclaude-opus-5, xhigh reasoning
seedbest Opus cold shot (1.47×) over the hand seed
fitnessgeomean causal speedup vs the MLX fallback
correctnessfp32 reference · ragged holdouts · hd=192 · both mask modes · bit-determinism
promotionmax(2σ, 1%) gate + worst-of-3 defense
stopping50 iterations max · patience 5 · Δ 0.005
safetybarriers allowed, threadgroup-uniform only
runearly-stopped at ~15 iterations, 80 minutes

Campaign #3 · prefill attention · 15 iterations to 2.11×

0.000.611.231.842.451481215parity with the libraryiteration 1: 1.3120iteration 1: 1.5107iteration 2: 1.3304iteration 2: 1.5154iteration 3: 1.3924iteration 3: 1.1755iteration 4: 1.8788iteration 5: 1.4061iteration 6: 1.5130iteration 7: 1.8972iteration 10: 2.0831iteration 11: 1.4690iteration 12: 1.5109iteration 13: 1.5116iteration 14: 1.8023iteration 15: 2.1103iteration
candidaterunning best · hover the markers for what each jump changed50 candidates logged incl. 2 zero-scored; champion re-verified twice in fresh processes
Fifteen iterations, four mechanisms, every one an occupancy trade. The search discovered occupancy-versus-memory as the axis of this kernel and then built machinery to navigate it per shape.

What the meaningful improvements actually were · from the parent-to-child diffs

Deleting failed cleverness (c2, iteration 40)
Removed half2 pointer-cast loads and a hand-unrolled simd-shuffle butterfly, both carried in from earlier candidates and both measured losses. Plain scalar loads and an L-adaptive partition count replaced them.
The candidate's own comment records that manual vectorisation had taken one shape from 1.081× to 0.463×. The search kept the measurement and discarded the idea.
A register budget, made explicit (c2, iterations 79–86)
At hd=256, putting all 8 query heads of a KV group in one simdgroup needs 128 accumulator floats per lane: register spill and a collapse to 0.24×. The fix splits heads across simdgroups under an explicit per-lane register cap, then picks the kernel variant per head-width.
This is the shipped champion's structure, and the register arithmetic in its comments is the transferable artifact.
Occupancy bought with threadgroup memory (c3, iteration 4)
One shared in-place fp16 score/probability buffer instead of separate fp32 + fp16 buffers: 24.9 KB → 8.4 KB, which moves residency from one threadgroup per core to three, so one group's softmax hides behind another's matmul.
The single largest jump in any campaign: 1.51 → 1.88.
A regression kept as a lesson (c3, iteration 10)
A fast-looking variant parked score tiles in registers and halved throughput: spill again, this time diagnosed in the candidate's own docstring. The successor re-read scores from threadgroup memory, restored the fp32 layout as the proven default, and wrapped the risky layouts in output-validated runtime selection.
The autotune machinery the champion ships with was born as a defence mechanism, not a feature.
The dispatch grid as a cache policy (c3, iteration 15)
Reordering threadgroup launch so the query-head replicas of one KV group run adjacently: resident groups then share a single K/V stream through the cache instead of four.
Fixed the one shape (GQA-8) the campaign had never beaten: 2.19× where the seed sat at 1.56×.

What analysis provided · in the prompt, before any search

the target itselfhd=256 prefill at 34% of ceiling, from the op audit
the silicon constraintmatmuls must run on the Neural Accelerators
the 0.37× trapa fused kernel on the wrong silicon loses
the API contractmatmul2d overwrites; accumulate in cooperative tensors
the correctness trapscausal alignment, ragged tails, no KV copies
the budget32 KB threadgroup memory, and what the seed spends
the safety rulebarriers must be threadgroup-uniform

What the search invented · in the candidates, never prompted

kv-head-major grid orderresident groups share one K/V stream
in-place fp16 score tile24.9 → 8.4 KB, 3× residency
explicit register budgetsheads-per-simdgroup as arithmetic
runtime layout autotuneoutput-validated challenger adoption
uniform rescale skipexact fast path when no row max moves
overlapping final tilesbit-identical double-compute instead of guards
Neither column works alone. The left column without the search is a good seed at 1.31×. The right column without the left is campaign #1: mutation energy spent against an unmeasured target, producing motion indistinguishable from noise.

Is the bigger mutator worth it? Same prompt, same parent, measured cold

candidateminutes / calloutput tokensscore (seed = 1.312)
opus 12088k1.4673
opus 21986k1.4216
sonnet 1953k1.3013
sonnet 220112k1.3063
Both Opus cold shots beat the seed substantially, and beat the best result Sonnet reached with three rounds of measured feedback (1.392). Both also fixed a deficit that carried no score reward at all. Under a flat-rate plan the real currencies are wall-clock and quality-per-call, and the trade is stark: roughly twice the latency per call, several times fewer calls to a given quality. Campaign #3 ran on the bigger model and finished in 80 minutes and 15 iterations; the decode campaign a day earlier took 129.
full-campaign medians: Sonnet (c2) 3.9 min and 22k output tokens per call over 131 calls · Opus (c3): median 15.6 min and 71k over 15 calls

After the run · a champion is a claim, not a result

1 · Re-verify in a fresh process, twice, spaced
The in-run score is best-of-many under one process's thermal and cache conditions. The campaign-independent gate re-runs correctness on every shape and re-times against the library with fresh random inputs.
C3's champion: 2.32× and 2.33× on the two runs, ±0.01×. C1's champion is why this step exists.
2 · Integrate behind an envelope, and prove invocation
A patch layer routes only the measured envelope to the new kernel (here: hd=256 prefill) and falls back everywhere else. Counters assert the kernel actually ran, because a patch that silently fails to bind reports perfect parity.
~640 hook calls expected per 32k prefill; the assert voids the run below 600.
3 · Gate output quality, not just invocation
The first integrated A/B ran a full arm on garbage: the model's activations are bf16, the kernel was fp16, and its pointer casts reinterpreted the bits. 640 verified invocations, fluent-rate nonsense output, timed as if real. It read as a 2.1× slowdown and could as easily have read as a win.
The gate: greedy decoding on identical prompts, prefix agreement between arms. With it, and a cast at the boundary, the same patch measured 1.13× faster.
4 · Pair the arms in one process
The same configuration measured 11% apart on consecutive days. Stock and patched arms therefore run against the same loaded model, minutes apart, and only paired ratios are reported.
stock 52.3 s → patched 46.3 s at 32k, decode unchanged, peak memory DOWN 0.48 GiB
5 · Look for what the win unlocks
The champion never materialises the fallback's score matrix, so its memory peak is lower, which un-blocked the larger prefill chunk a previous session had proven fatal, which in turn tripled the routed-expert kernel's efficiency at its real workload.
A kernel-level score cannot see system-level wins. Only the end-to-end test surfaces them, and here the side effect was worth more than the kernel.

What shipped · time-to-first-token · 32k context · Qwen3.6-35B on my laptop

0%25%50%75%stock · first measurement (cold)stock · first measurement (cold): 70.2% of roofline70%70.2 sstock · warmed up, chunk 512stock · warmed up, chunk 512: 58.1% of roofline58%58.1 s+ routed-expert tile fix (BM=32)+ routed-expert tile fix (BM=32): 58.9% of roofline59%58.9 s+ evolved attention kernel+ evolved attention kernel: 46.3% of roofline46%46.3 s+ chunk 1024, unblocked by the kernel's memory+ chunk 1024, unblocked by the kernel's memory: 35.5% of roofline36%35.5 s
bars are seconds; lower is better. Every step below the first two is a same-day paired measurement, because cross-day comparisons drift by up to 11%.
70.2 s → 35.5 s. The evolved kernel's own step is the 1.13×, but its side effect, freeing the memory that pinned the chunk size, bought the largest single step on the chart. The kernel and the tile fix are both offered upstream (mlx#3924, mlx#3925); until then they ship here as a patch layer over stock mlx-lm.

In closing · findings, lessons, and what comes next

Findings. The AlphaEvolve recipe does transfer to a single laptop and a proprietary stack: an evolved fused attention kernel runs at 80% of my machine's measured compute ceiling where Apple's shipping path ran at 34%, and it survived every gate built to kill it: ragged holdout shapes, bit-determinism, fresh-process re-verification, and an end-to-end A/B with output-quality checks. Combined with a one-line tile fix found by reading MLX's dispatch source and the chunk-size raise the kernel's own memory savings unlocked, time-to-first-token on a 32k-context 35B model fell from 70.2 s to 35.5 s on my laptop. Both fixes are offered upstream. The search's genuine inventions (the kv-head-major dispatch order, the in-place score tile, the output-validated runtime autotune) are things no prompt asked for.

Lessons. Three campaigns, three grades of the same loop, and the grade tracked the measurement discipline, not the mutation engine. A search with a noisy fitness invents staircases out of nothing (campaign #1, re-verified at 0.96×). A search pointed at the op with the biggest efficiency gap rather than the biggest share of time optimises the wrong thing beautifully (campaign #2, a real 1.23× kernel worth 1.04× end-to-end). And even a winning campaign's champion is only a claim until it passes gates the search never saw. The bf16 incident, where a perfectly bound integration computed fluent garbage at speed, is the cleanest argument this project can offer that invocation is not correctness and speed is not truth. The evaluator is the product; the kernels are the receipts.

Next. The remaining headroom splits cleanly into two different problems. Sustained generation is nearly done: the decode step already runs at 91% of my machine's measured memory bandwidth, so no kernel rewrite can buy more than single digits. The one real lever left is fusing the dozens of launch-bound micro-ops (norms, rotary, routing) whose cost is being separate kernels at all, worth perhaps 7%. Time-to-first-token still has real ground: of the 35.5 s, the matrix multiplies are saturated and attention now sits at 80% of ceiling, but the gated-delta-net recurrence, the linear-attention layers' state update, runs at 3% of both ceilings at once, because its shipping kernel is a long chain of sequential dependent steps: a formulation problem, not a tuning problem. The chunked delta-rule algorithm from the linear-attention literature is the known fix, and early attempts confirmed it must be a hand-written fused kernel: every ops-level expression loses to the incumbent before any search could tune it. That kernel is the next seed, worth ~3 s; a small fused output-norm is worth ~1.5 s more; together they point at roughly 30 s. Hybrid architectures like this one are the direction the field is moving, so the kernel outlives the model that motivated it. Beyond my desk: shepherding the two upstream contributions: mlx#3924, a static_assert closing the silent no-op that returns zeros at full speed, and mlx#3925, the routed-expert tile fix with its measurements. Also on the list: validating portability on other Apple Silicon tiers. The technique transfers; the kernels are shape-specific and will age out. That asymmetry is the reason Part 1 and this page lead with method.

Dotted terms carry definitions. Hover, tap, or tab to them. them. Like this.