Attention Variants Compared
Full, sliding window, strided, BigBird, differential and native sparse attention. Cost, KV cache, code, fixes.
- Concepts and terms
- Tasks and commands
- Annotated code
- Diagrams
- Comparisons
- Defaults and numbers
- Troubleshooting
- Questions and tips
Variants side by side#
| Variant | Compute | KV cache | Quality vs full |
|---|---|---|---|
| Full attention | O(N^2) | O(N) per layer | baseline |
| SWA, window 1024 | N times W | O(W) per layer | -0.1 ppl, good with global layers |
| Local + strided sparse | N times sqrt N | mixed | similar to SWA |
| BigBird, local + global + random | O(N) approx | mixed | matches full at 2× context |
| Native Sparse (DeepSeek-V3.2) | N times active fraction | O(N) | within 0.05 ppl |
| Differential | 2 times N squared | O(2N) | -5 to -10% ppl |
From Lesson 07.15
Use X when#
- Full attention every layer up to ~16K context, or when retrieval quality is paramount.
- SWA + global mix long context above 32K. The 2026 default above 32K.
- Sparse block attention custom kernel, custom pattern. For retrieval and audio.
- Differential attention attention sinks hurt, as in long-context RAG and needle-in-haystack.
- DIFF from the start a new model that targets 64k-plus effective context.
- NSA pre-training or continued training that targets 64k-plus context.
- Not DIFF or NSA a pre-trained dense model, or context under 16k.
From Lesson 07.15, Lesson 10.16, Lesson 10.17
Mask builders#
def swa_mask(n, window): M = [[NEG_INF] * n for _ in range(n)] for i in range(n): lo = max(0, i - window + 1)window >= n gives full causal for j in range(lo, i + 1): M[i][j] = 0.00.0 attends, NEG_INF masks return Mdef strided_mask(n, window, stride):local window, then: for j in range(0, i + 1, stride): M[i][j] = 0.0every stride-th token back to 0render(swa_mask(8, window=4), "sliding window (W=4)")render(strided_mask(8, window=2, stride=3), "local (W=2) + strided (stride=3)")SWA with FlexAttention#
from torch.nn.attention.flex_attention import flex_attention, create_block_maskdef swa_pattern(b, h, q_idx, kv_idx): return (q_idx - kv_idx < 1024) & (q_idx >= kv_idx)window 1024, causalmask = create_block_mask(swa_pattern, B=batch, H=heads, Q_LEN=n, KV_LEN=n)out = flex_attention(q, k, v, block_mask=mask)compiles to a Triton kernelFrom Lesson 07.15
Q&A#
- Does Flash Attention cut compute?
- No. It hides the activation memory. Every token still attends to every token.
- Why is NSA trainable?
- Gradients flow through the compressed-branch scores.
top_konly picks blocks to load. - Why does DIFF help more at 128k?
- The noise floor grows with context. At 4k it is small.
- Is DIFF V2 GQA compatible?
- Yes. V2 adds Q heads, not KV heads.
- NSA keys at 1024 tokens, l 32, k 4, w 128?
- 32 + 128 + 128 = 288 keys, 3.5x fewer.
Glossary#
- SWA
- sliding window attention. Each query attends to its last W tokens.
- Receptive field
- an L-layer SWA stack with window W sees up to L × W tokens.
- Attention sink
- rows sum to 1, so uninformative queries put weight on position 0.
- NSA
- Native Sparse Attention. Learned block sparsity, zero blocks skipped in the kernel.
- DSA
- Deepseek Sparse Attention, the successor of NSA.
- HBM
- High Bandwidth Memory. About 3 TB/s on H100.
- SRAM
- per-SM on-chip memory. About 30 TB/s.
From Lesson 07.15, Lesson 07.12, Lesson 10.17
NSA: three branches#
Flash Attention tile loop#
Tasks in code#
| Task | Code |
|---|---|
| SWA causal mask | swa_mask(n, |
| Local plus strided mask | strided_mask(n, |
| Count attended cells | count_nonmasked(M) |
| KV cache bytes | kv_cache_bytes(N, |
| Flash style softmax | tiled_softmax_dot(q, |
| Signal to noise of a row | snr(weights_row, |
| DIFF V1 and V2 params | compute_param_diff(hidden= |
| NSA keys per query | count_nsa(N, |
From 07.15 code/main.py, 07.12 code/main.py, 10.16 code/main.py, 10.17 code/main.py
Numbers to know#
| Setting | Default | Note |
|---|---|---|
| Llama 3 70B KV per token per layer | 4096 bytes | 80 layers, 8 KV heads, fp16 |
| Llama 3 70B KV, 32K context | 10.4 GB | GQA with 8 KV heads |
| Gemma 3 layer mix | 5:1 SWA:global | window=1024 |
| SWA KV at 128K context | 128× | window 1024, last W tokens |
| Gemma-3 mix KV | ~6x | vs pure full attention |
DIFF lambda_init | 0.8 | learned per head |
NSA window W | 512 | l = 64, k = 16 at 64k, 25x |
| NSA at 128k | 3536 keys | 36x reduction |
From Lesson 07.12, Lesson 07.15, 07.15 code/main.py, Lesson 10.16, Lesson 10.17
Troubleshooting#
| Symptom | Cause | Fix |
|---|---|---|
| SWA model fails long-range retrieval | No global layers | Interleave full layers, 5:1 |
| Weight bleeds to token 0 | Attention sink from softmax | Differential attention |
| Lost in the middle past 32k | Softmax noise floor grows with N | DIFF from the start, or LoRA on Q |
| DIFF V1 decode slower than baseline | Value cache loaded twice | DIFF V2 |
| Late training unstable at 70B-plus | V1 per-head RMSNorm | V2 removes it |
| NSA on a dense pre-trained model | No retrofit | Continued training |
| No NSA gain under 16k | Three-branch overhead | Full attention |
| Inference-time sparsity gives small speedup | Model never routed through it | Natively trained sparsity |