195 lines
13 KiB
Markdown
195 lines
13 KiB
Markdown
# Parked D512 fused split-KV decode design
|
|
|
|
Status: research/design only. Do not add a selector or Metal pipeline until the BK128 prefill
|
|
decision, current-production 128K server acceptance, and selector-20 score-tile8 decision are
|
|
closed in that order.
|
|
|
|
The selector-independent CPU oracle prerequisite is now implemented in `src/ops.c`. It constructs
|
|
online-softmax partials, merges empty and nonempty states with the exact rescaling identity, and
|
|
finalizes the numerator. Unit coverage compares 1/2/3/4/7/16-way partitions over a 13-token,
|
|
17-dimension non-power-of-two fixture; the 16-way case necessarily includes empty partitions.
|
|
Invalid finite-state combinations and scratch-size overflow fail closed. This code is not called by
|
|
the production graph and does not add a Metal pipeline.
|
|
|
|
`gem-online-merge-check` wraps that oracle with an independent double-precision direct-softmax
|
|
reference. Fixed P=32/DV=512 audits at 8,191, 8,192, 8,193, 65,536, and 131,072 tokens all produce
|
|
finite, repeat-deterministic output. Correlation is at least `0.999999999999`, NMSE is at most
|
|
`1.42e-11`, and maximum absolute error is below `1.64e-7`.
|
|
`analyze-online-merge-boundary.sh` binds those exact positions, geometry, thresholds, seeds, hashes,
|
|
and raw-input digests. This discharges only the CPU algebra prerequisite; the future Metal candidate
|
|
must still pass the same lengths plus greedy model continuations against forced selector 19.
|
|
|
|
## Fixed production shape
|
|
|
|
This design is only for the ten full-attention layers of Gemma 4 31B at batch-one decode and an
|
|
8K-or-longer prefix:
|
|
|
|
- 32 query heads, four KV heads, eight query heads per KV head;
|
|
- symmetric `DK=DV=512`, F32 KV cache, no sliding window;
|
|
- selector 19's graph, Q4 weights, split16 value route, sampler, and server behavior remain the
|
|
baseline;
|
|
- exact dense attention and deterministic execution remain mandatory.
|
|
|
|
The production "dim32" name is a value workgroup dimension tile. It does not mean the value head
|
|
has width 32. The authentic GGUF contract requires both full-attention key and value lengths to be
|
|
512 (`src/model.c`).
|
|
|
|
## Measured baseline
|
|
|
|
Selector 19 currently uses three attention stages plus the split16 value reduction:
|
|
|
|
1. The GQA score kernel stages each K row once for all eight query heads and writes `32*live` F32
|
|
scores: 8 MiB at 64K and 16 MiB at 128K.
|
|
2. One normalization threadgroup per query head performs the full-row max, exponentiation, and sum.
|
|
3. The dim32/split16 value kernel divides each 512-wide output into sixteen dimension tiles and the
|
|
sequence into sixteen partitions. It dispatches 1,024 workgroups and writes 1 MiB of partials.
|
|
4. A final elementwise kernel reduces the sixteen value partials.
|
|
|
|
Retained exact-shape profiles put the 64K score, normalize, and value stages at approximately
|
|
3.12 ms, 0.11 ms, and 2.65 ms. At 128K, stable samples are approximately 6.20 ms, 0.33 ms, and
|
|
5.38 ms. Score/K is therefore about half the remaining attention time, but a candidate still needs
|
|
an authentic whole-token gain; operator time alone is not a promotion claim.
|
|
|
|
The reproducible `fused-split-kv-ceiling.json` attribution compares those exact-shape profiles with
|
|
the retained selector-19 authentic stage profiles. The ten global-attention calls account for a
|
|
theoretical maximum of 33.11% of whole-token GPU time at 64K and 43.73% at 128K; score alone accounts
|
|
for 17.54% and 22.02%. Clearing the 3% whole-token admission floor therefore requires at least a
|
|
9.06%/6.86% reduction within global attention at 64K/128K. This admits one diagnostic but cannot
|
|
predict its gain because compulsory K/V reads, dot products, exponentials, and reduction remain.
|
|
|
|
The minimum logical intermediate traffic explains why fusion is worth one bounded experiment. Let
|
|
`S=32*live*4` be score-scratch bytes (8 MiB at 64K, 16 MiB at 128K). The score stage writes `S`.
|
|
Normalization makes one max read, one read/write exponentiation pass, and one read/write scaling
|
|
pass (`5S`). Each of the sixteen dim32 value groups then reads the complete normalized score rows
|
|
for its output-dimension tile (`16S`). The route therefore issues `22S`, or 176/352 MiB at
|
|
64K/128K, of logical score traffic in addition to reading K and V once. GPU caches may satisfy part
|
|
of it, so this is not a device-bandwidth claim; the candidate must confirm the avoided traffic with
|
|
Metal counters. Its partition partials add only bounded P-dependent traffic.
|
|
|
|
## Candidate ownership and storage
|
|
|
|
The first candidate is a two-pass exact online-softmax kernel with one first-pass threadgroup per
|
|
`(kv_head, sequence_partition)` and eight SIMD groups per threadgroup. Each SIMD group owns one of
|
|
the eight query heads sharing that KV head.
|
|
|
|
For each sequence tile:
|
|
|
|
1. The 256 threads cooperatively stage the tile's K and V rows once in threadgroup memory.
|
|
2. Each SIMD group computes its query head's score using `simd_sum`.
|
|
3. Each SIMD group updates its own exact online-softmax `(M,S,O[512])` state in sequence order.
|
|
4. The threadgroup writes one `(M,S,O)` partial per query head and partition.
|
|
|
|
Each lane owns 16 output dimensions, so one query and one output accumulator require 32 F32 values
|
|
per thread before scalar state and temporaries. Start with a four-token tile: K and V staging uses
|
|
`4*512*2*4 = 16,384` bytes. An eight-token tile consumes the full public 32,768-byte limit before
|
|
any other threadgroup state and is not the first candidate. Only an Xcode GPU trace may establish
|
|
whether the D512 register and 16 KiB threadgroup footprint leave useful occupancy.
|
|
|
|
The independently released Open-TQ-Metal Gemma kernel provides compilation-level evidence for the
|
|
same D512 per-lane register shape: each lane holds 16 query and 16 output floats. Its ownership is
|
|
otherwise unsuitable here. It launches one threadgroup per query head, maps that head back to a KV
|
|
head, rereads shared GQA K/V for every query head, and serializes a 16-SIMD-group output reduction
|
|
through barriers. Gem's candidate instead uses eight SIMD groups for the eight query heads of one KV
|
|
head and must prove that its additional 16 KiB tile staging does not destroy occupancy.
|
|
|
|
The pinned adjacent DSV4 vector kernel also proves why Gem cannot preserve GQA ownership by merely
|
|
replicating its threadgroup layout. At symmetric D512, cache tile 32, and one SIMD group, its exact
|
|
scratch formula is `(pad(DK,128) + 4*C + 2*pad(DV,128))*2 = 3,328` bytes per query head: shared Q,
|
|
score/mask state, and an F32 output accumulator. Eight heads would require 26,624 bytes before K/V
|
|
staging. Adding Gem's tile4 shared K/V allocation would total 43,008 bytes, exceeding the M4 Max's
|
|
32,768-byte limit. The first candidate must therefore keep Q and O lane-local in registers and use
|
|
threadgroup memory only for the shared K/V tile and small coordination state. If that register shape
|
|
lowers the compiled pipeline below 256 launchable threads, the fixed candidate fails its resource
|
|
gate; it must not silently fall back to a four-head layout that rereads K/V or changes the one-axis
|
|
experiment.
|
|
|
|
The second pass merges partition states with the exact identity
|
|
`M=max(Ma,Mb)`, `S=Sa*exp(Ma-M)+Sb*exp(Mb-M)`, and the corresponding rescaled output numerator.
|
|
It then writes `O/S`. The partial layout is partition-major and contiguous in output dimension so
|
|
each lane reads its 16 dimensions coalesced. At P partitions, output partials consume
|
|
`P*32*512*4` bytes: 2, 4, 8, and 16 MiB for P=32, 64, 128, and 256 respectively, plus small M/S
|
|
arrays. This is bounded by context-independent P and replaces the linear score scratch on the
|
|
candidate route.
|
|
|
|
`gem_attention_online_scratch_bytes` fixes the complete byte contract before Metal allocation. For
|
|
P=32 it reports 4,096 bytes each for maxima and denominators, 2,097,152 numerator bytes, and
|
|
2,105,344 bytes total. Larger partition counts scale this entire layout linearly and all products
|
|
are checked for `size_t` overflow.
|
|
|
|
The future compiled resource receipt is also fixed before implementation. The first pass must
|
|
report SIMD width 32, at least 256 launchable threads, exactly 256 dispatched threads/eight SIMD
|
|
groups, and exactly 16,384 dynamic threadgroup bytes for tile4 K+V staging. The reducer must report
|
|
SIMD width 32, at least 256 launchable threads, exactly 256 dispatched threads, and no dynamic
|
|
threadgroup allocation. Static plus dynamic memory for either pipeline must fit the M4 Max's 32 KiB
|
|
limit. `analyze-fused-split-kv-resources.sh` additionally binds D512/P32/F32/no-window geometry,
|
|
the 2,105,344-byte scratch contract, device family, and source/binary digests. Passing this receipt
|
|
only proves the pipeline is compilable and launchable; it cannot substitute for an occupancy trace,
|
|
correctness, or timing.
|
|
|
|
The subsequent correctness artifact is likewise defined before implementation.
|
|
`analyze-fused-split-kv-correctness.sh` consumes the digested CPU gate and compiled-resource
|
|
analysis, five raw operator differentials at 8,191/8,192/8,193/65,536/131,072 tokens, and three raw
|
|
evaluated-prompt model differentials at the routing boundary. Operator records must preserve the
|
|
fixed D512/P32/tile4/F32 geometry, use selector 19 as baseline, be finite and repeat-deterministic,
|
|
and clear correlation 0.9999/NMSE 1e-4. Model records additionally require 60-layer 31B identity,
|
|
unchanged production selection, matching argmax and ordered top-10, repeat-deterministic candidate
|
|
logits, and at least eight identical greedy continuation tokens. Every raw file is bound by digest.
|
|
This gate cannot report promotion eligibility; clean-host timing and the adjacent backend-op oracle
|
|
still follow it.
|
|
|
|
## One-axis experiments
|
|
|
|
Do not mix these experiments:
|
|
|
|
1. Correctness-only D512 fused split-KV implementation at fixed tile4 and P=32.
|
|
2. Sequence partitions P=32 versus 64, then the winner versus 128; admit 256 only if a trace shows
|
|
insufficient first-pass parallelism. Each comparison is a separate one-axis ABBA run.
|
|
3. Only after selecting P, compare tile4 with another tile size under a separate gate.
|
|
|
|
Direct V reads, F16/quantized KV, paged KV, sparse attention, Metal 4 TensorOps, and multi-request
|
|
scheduling are separate axes and excluded from this cycle.
|
|
|
|
## Fail-closed evidence
|
|
|
|
Before authentic model timing, require:
|
|
|
|
- finite seeded CPU differential tests at exactly 8,191, 8,192, and 8,193 live tokens, plus 64K and
|
|
128K tails; the CPU-only algebra portion of all five lengths is complete, while Metal
|
|
differentials remain;
|
|
- a forced-selector-19 comparison over the same boundary inputs so changing dispatch routes is
|
|
numerically invisible, including matching greedy tokens across a multi-token continuation;
|
|
- repeat determinism, finite output, matching argmax/top-10, correlation at least 0.9999, and NMSE
|
|
no greater than 1e-4;
|
|
- exact partial-merge tests covering empty/tail partitions and non-power-of-two live lengths;
|
|
- buffer overflow/range tests and release plus ASan/UBSan suites;
|
|
- a profile receipt recording pipeline thread width, maximum threads, static/dynamic threadgroup
|
|
memory, raw GPU timestamps, and an Xcode occupancy/limiter trace; the fail-closed compiled-resource
|
|
receipt analyzer is ready, while an authentic receipt requires the future pipeline;
|
|
- three interleaved repetitions at 64K and 128K with the frozen clean-host policy;
|
|
- authentic deterministic logits and whole-token timing before any selector change;
|
|
- the correctness analyzer contract for these operator/model records is ready, while authentic raw
|
|
records require the future Metal pipeline;
|
|
- the adjacent backend-op correctness oracle after a kernel change.
|
|
|
|
The candidate must beat selector 19 by more than 3% and observed variation at both long contexts,
|
|
without a short-context regression beyond the protocol. Passing an operator screen only admits the
|
|
authentic model gate; it never changes production directly.
|
|
|
|
The ceiling screen is regenerated with `scripts/analyze-fused-split-kv-ceiling.sh`; it validates
|
|
the selector, exact D512 geometry, context, stage totals, repetitions, sample coverage, and input
|
|
digests before making the non-promotional attribution.
|
|
|
|
## Primary design references
|
|
|
|
- MLX D64/D128 duplication-free GQA and two-pass reduction at
|
|
`d73eb752ef2e6288fd95b032c0bff0a15a4a9e93`.
|
|
- MLX issue 4022, which localizes a deterministic GQA correctness failure to a two-pass routing
|
|
boundary and motivates Gem's explicit boundary and continuation canaries.
|
|
- Open-TQ-Metal's D512 Gemma specialization at
|
|
`mutable-state-inc/gemma4metal@0f09466b7fde772a4876bf7bee3ccdeb34313304`, used only as
|
|
register-shape evidence. Its one-query-head ownership and short-context int4 results are not a
|
|
performance or correctness oracle for this design.
|
|
- Adjacent DeepSeek-V4 llama.cpp vector online-attention/reduction oracle at
|
|
`d8df12ebc4592b55dcecf97a32953623d031efdc`.
|
|
- Apple Xcode occupancy, memory-bandwidth, and limiter counters. Static resource limits alone do
|
|
not establish resident workgroup count.
|