Reduce local KV memory in BF16 Flash attention - #1020
Conversation
jan-wassenberg
left a comment
There was a problem hiding this comment.
Thank you! It seems like this would make a big difference for longer sequences.
But unfortunately, as-is, this is adding too much tech debt :/ We'd have a third layout: kv/k/v, compact, per-layer - semi-invisible, based on whether we use the 3-arg ctor or not. And the abstraction leaks, user code has to remember to check ring.
Let's think about alternatives. Can we use the compact everywhere in flash attention?
| size_t kRoundedTokens = 0; | ||
| for (size_t qi = 0; qi < qbatch.Size(); ++qi) { | ||
| kRoundedTokens = HWY_MAX( | ||
| kRoundedTokens, |
There was a problem hiding this comment.
This is not constexpr, please use normal variable naming (snake_case).
| [](const LayerConfig& layer) { | ||
| return layer.type == LayerAttentionType::kGemma; | ||
| }); | ||
| if (dense_gemma && runtime_config.attention_impl == AttentionImpl::kFlash) { |
There was a problem hiding this comment.
RuntimeConfig is the older/deprecated mechanism. Prefer to pass attention_impl directly, and ideally later we'll move it to InferenceArgs; but definitely let's avoid further dependency on RuntimeConfig.
Closes #1016. Supersedes #1017, which was based on
main.The default Flash path on
devreserves full-context BF16 KV/K/V storage for local layers and also allocates compact tiled buffers it does not use. This change gives each owning Gemma layer separate BF16 buffers: local rings retain the attention window plus batch and SIMD padding, while global layers retain the configured context. The default Flash path no longer allocates unused tiled buffers.Shared layers alias their source buffers. Ring growth preserves live flat and transposed history, snapshots remain independent, and cache recycling clears the owned buffers. API sessions and conversation bindings use the runtime-aware constructor. The legacy constructor and tiled, DeepSeek, and encoder/decoder layouts remain supported.
Validation
Against
upstream/devat1658f88, all 13 A/B pairs on Gemma 3 270M and 1B produced identical decoded bytes and token IDs: 1,664 generated tokens per build. Runs used a shared baseline-calibrated matrix plan, six pinned workers on an i5-12400F, context 8,192, prefill batch 256, and 128 greedy output tokens.Medians from three alternating pairs with a 3,367-token prompt:
The measured benefit is lower memory use; these results do not demonstrate a speedup. A slower single short-prompt 270M sample did not reproduce in three confirmation pairs (median decode 49.29 → 49.21 tokens/s).
The commit contains production changes, regression tests, and their build wiring. Benchmark harnesses, reports, and generated artifacts are excluded.