Restrict each query to the most recent keys, making attention and bounding the KV Cache to tokens per layer.
Equation
Stacking windowed layers gives an effective receptive field of tokens, since information hops one window per layer.
From scratch
import torch
def sliding_window_mask(n_q, n_k, w, device='cpu'):
i = torch.arange(n_k - n_q, n_k, device=device)[:, None] # (n_q, 1)
j = torch.arange(n_k, device=device)[None, :] # (1, n_k)
keep = (j <= i) & (j > i - w) # causal AND in-window
return ~keep # True = masked
def swa(q, k, v, w): # (b, h, n, dh)
s = torch.einsum('bhid,bhjd->bhij', q, k) / q.shape[-1]**0.5
m = sliding_window_mask(q.shape[2], k.shape[2], w, q.device)
return torch.einsum('bhij,bhjd->bhid',
s.masked_fill(m, float('-inf')).softmax(-1), v)
class RollingCache: # ring buffer, never exceeds w
def __init__(self, w): self.w, self.k, self.v = w, None, None
def update(self, k, v):
self.k = k if self.k is None else torch.cat([self.k, k], 2)[:, :, -self.w:]
self.v = v if self.v is None else torch.cat([self.v, v], 2)[:, :, -self.w:]
return self.k, self.vNotes
- Common pattern: interleave windowed layers with a few full-attention layers (e.g. 3:1) so global information still has a direct path.
- Attention sinks: keeping the first few tokens permanently in the cache alongside the rolling window prevents the perplexity blowup seen when the initial tokens are evicted (StreamingLLM).
- The dense mask above is pedagogical; real kernels skip out-of-window blocks entirely, which is where the speedup comes from.
- Pairs naturally with ALiBi and Relative Position, since both encode a recency prior.
Related: Scaled Dot-Product Attention, KV Cache Implementation, Linear Attention