Skip to content

Skip fully masked key tiles in Flint sliding-window prefill - #2006

Open
tc3oliver wants to merge 4 commits into
ml-explore:mainfrom
tc3oliver:banded-sliding-prefill
Open

tc3oliver wants to merge 4 commits into
ml-explore:mainfrom
tc3oliver:banded-sliding-prefill

Conversation

@tc3oliver

@tc3oliver tc3oliver commented Oct 24, 2026 •

Copy link
Copy Markdown
  • ☑️ I understand it is strictly prohibited to use AI to write PR description
  • AI usage disclosure: I used Claude Code to find the cause, write the patch, and write the tests. I reviewed and ran everything myself.

Summary

Flint’s sliding-window prefill processes fully masked key tiles in SDPA.

Split prefill into 256-query bands, skipping unreachable keys. Key starts are aligned to 64 to preserve the fused kernel’s tile boundaries.

Only Flint’s sliding-attention layers are affected. Unsupported cases fall back to the original SDPA. Shared base.py is unchanged.

Benchmarks

M4 Max (40-core GPU, 64 GB), MLX 0.32.4, RecursiveSI/flint-int4. 2048-token chunks, median of three alternating runs.

Context Upstream This PR Improvement
8K 1,572.7 tok/s 1,781.3 tok/s +13.3%
32K 1,164.3 tok/s 1,278.2 tok/s +9.8%

Prefill throughput, not server TTFT. Last-token logits MD5 matched.

Correctness

Outputs match the original SDPA exactly (mx.array_equal) across tested cases:

  • Head dimensions 64/128, GQA, attention sinks
  • Cache rotation, prior lengths, chunk boundaries
  • Windows 300/1024 with cache size 513
  • Full prefill with a small Flint model

Head dimension 256, short tails, and unsupported configurations use the original path.

Tests

python -m unittest discover tests:

  • This PR: 385 tests, 1 skipped, 2 errors
  • Upstream: 382 tests, same 2 errors and 1 skip
  • All 3 new tests passed

Both errors are from missing optional dependencies (datasets, lm_eval). Black, isort, Ruff, and mutation checks passed.

Tested on M4 Max; M5 not tested.

Reproduction

Script: ab2.py

  • Baseline: 4cfc647
  • This PR: 86bbfe7
  • Model revision: 3b93f9053b530488405ae9fca51d4900d6ce21f0
  • macOS 27.0, Python 3.12.14, MLX 0.32.4
  • Fixed random input (RandomState(0)), 2048-token chunks
  • One warmup, three alternating runs, fresh process per version
  • Timing: perf_counter starts after cache creation and ends after the final chunk evaluation.

A RotatingKVCache returns window - 1 + L keys for an L-token chunk, and the
array mask built for it made the fused kernel visit every one of them for each
query row. Process query bands of 256 rows over only the keys their window can
reach, starting on a 64-key boundary so the kernel's tile grid and online
softmax order are unchanged. The result is bit-identical to the single call.

Chunks of 8 rows or fewer (and tails of 8 rows or fewer) keep the single call,
since the fused kernel hands those to sdpa_vector, which accumulates in a
different order. Head dims other than 64 and 128 run the unfused fallback,
whose softmax reduction depends on the key count, so they keep the single call
too (gemma-3 uses 256). Caches with keep > 0 and masks that are not [L, keys]
are left on the old path.
The banded path inferred a sliding-window mask from a [L, kL] array mask on
a keep=0 RotatingKVCache and took its window from cache.max_size. A mask
built with a wider window_size, or any other [L, kL] mask, then lost keys
it allows: max abs error 0.38 for window 4096 on a 513-slot cache and 0.36
for an all-True mask.

scaled_dot_product_attention now takes an optional sliding_window, and
bands only when it equals cache.max_size, i.e. when the caller states the
mask is that cache's own window mask. Flint passes it for its sliding
layers; every other model keeps the single SDPA call.
The shared scaled_dot_product_attention cannot tell whether a mask is a
causal sliding-window mask, or which window it uses, so banding there is
only correct when every caller passes the right window. Do it in Flint's
attention instead, with the band bounds taken from the model's own
sliding_window, and leave base.py unchanged.
@tc3oliver tc3oliver changed the title Skip fully masked key tiles in Flint sliding-window Skip fully masked key tiles in Flint sliding-window prefill Oct 24, 2026
@tc3oliver tc3oliver changed the title Skip fully masked key tiles in Flint sliding-window prefill Skip fully masked key tiles in Flint sliding-window prefill Oct 24, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant