它修改可见性,和“支持更长位置”不是一回事
标准 causal attention 中位置 可见所有 。Sliding Window Attention(SWA)再加入距离限制:
是包含当前位置在内的窗口大小。它改变模型的 attention dependency:远于 的 token 不能被该层直接读取。相比之下,RoPE scaling/长上下文位置映射改变 position→angle 映射,不自动限制可见 token;PagedAttention改变 KV 的物理分配/查表,不改变可见性。
Dense band mask 与 decode 截断 cache 的等价路径
对 ,每个 Query 可见的 key 数是:
position 0 1 2 3 4 5 6 7 8
visible count 1 2 3 4 4 4 4 4 4
两条路径:
- Dense reference:保留完整
K/V:[B,H,T,D],构造 lower-band mask,只在每行最近 列 softmax。 - Incremental decode:位置 只提供
K/V[..., max(0,i-W+1):i+1, :],不再需要 mask。
如果 position ids、RoPE 和边界定义一致,两者逐位置输出应相等。窗口 off-by-one 是常见 bug:本页定义 时当前位置加前三个历史位置,共 4 个。
| tensor | full causal | windowed decode |
|---|---|---|
| Query | [B,Hq,1,D] | [B,Hq,1,D] |
| logical K/V source at position i | [B,Hkv,i+1,D] | [B,Hkv,min(i+1,W),D] |
| score | [B,Hq,1,i+1] | [B,Hq,1,min(i+1,W)] |
| old KV ownership | request/rank 持有全部历史 | 早于窗口且无其他依赖的 blocks 可回收 |
min(i+1,W) 是逻辑长度;Paged allocator 仍按 block size 向上取整,最后 partial block 和 sink/global blocks 会让物理容量略高。
可运行的逐位置等价实验
import math,platform,torch
torch.manual_seed(211)
DEVICE=torch.device("cuda" if torch.cuda.is_available() else "cpu")
DTYPE=torch.float32
def dense_sliding_window(q,k,v,window):
tokens=q.shape[-2]
scores=q@k.transpose(-2,-1)/math.sqrt(q.shape[-1])
query_pos=torch.arange(tokens,device=q.device)[:,None]
key_pos=torch.arange(tokens,device=q.device)[None,:]
visible=(key_pos<=query_pos)&(key_pos>query_pos-window)
probs=torch.softmax(scores.masked_fill(~visible,-torch.inf),dim=-1)
return probs@v,visible
def incremental_sliding_window(q,k,v,window):
outputs=[]; cache_lengths=[]
for position in range(q.shape[-2]):
start=max(0,position-window+1)
k_cache=k[...,start:position+1,:]; v_cache=v[...,start:position+1,:]
scores=q[...,position:position+1,:]@k_cache.transpose(-2,-1)
scores=scores/math.sqrt(q.shape[-1])
outputs.append(torch.softmax(scores,dim=-1)@v_cache)
cache_lengths.append(k_cache.shape[-2])
return torch.cat(outputs,dim=-2),cache_lengths
def main():
b,h,t,d,w=1,2,9,4,4
q=torch.randn(b,h,t,d,device=DEVICE,dtype=DTYPE)
k=torch.randn_like(q); v=torch.randn_like(q)
dense,visible=dense_sliding_window(q,k,v,w)
incremental,lengths=incremental_sliding_window(q,k,v,w)
torch.testing.assert_close(incremental,dense,rtol=1e-5,atol=1e-6)
assert lengths==[1,2,3,4,4,4,4,4,4]
assert visible.sum(-1).tolist()==lengths
full_elements=2*b*h*t*d; window_elements=2*b*h*w*d
print(f"python={platform.python_version()} torch={torch.__version__}")
print(f"device={DEVICE} dtype={DTYPE} Q=K=V={tuple(q.shape)} window={w}")
print(f"visible_keys_per_query={visible.sum(-1).tolist()}")
print(f"incremental_cache_lengths={lengths}")
print(f"sliding_vs_dense_mask_max_abs_diff={(incremental-dense).abs().max():.3e}")
print(f"full_KV_elements={full_elements} capped_window_KV_elements={window_elements}")
if __name__=="__main__":
with torch.inference_mode(): main()
完整文件位于 examples/sliding_window_attention.py。2026-08-13 实际最大差 3.576e-07;完整 K/V 有 144 元素,窗口封顶 K/V 为 64 元素。后者是序列到达 后某一时刻需保留的 payload,不是全序列累计写入量。
教学代码按位置重新 slice 并用 dense matmul,不是 ring-buffer/paged decode kernel,也没有性能意义。生产实现可让逻辑 position 单调增加、物理 slots 按环形或 block eviction 复用;RoPE 必须使用绝对/模型定义 position,而不能因物理 slot 回绕把位置重置为 0。
容量与成本
若全部 层都使用同一窗口,标准 GQA/MHA cache 上限从:
变为:
若只有 local layers 集合 ,global layers 仍随 增长:
每步 local attention 读取/计算也封顶 ,但模型可能通过多层传播间接传递更远信息;直接 receptive field 仍受 architecture 限制。窗口越小,容量/带宽越低,但长距离检索、copy、needle 或跨段一致性可能下降。不能只用 perplexity 或一个随机张量决定业务精度。
何时收益打折或失效
- runtime 只加 mask,却仍保留/读取完整 KV;计算语义正确但容量收益未兑现;
- block size 大于窗口余量,internal fragmentation 占比高;
- global/sink tokens 永久保留,实际 cache 不是纯最近 ;
- prefix cache 共享 block 仍被其他请求引用,当前请求不能直接释放物理 block;
- prefill kernel 不支持 band mask fast path,fallback 到 dense math;
- position ids 随 ring buffer slot 回绕,RoPE 语义错误;
- 模型训练时没有 windowed attention,却在部署时强行截断。
上下游关系
- KV Cache定义跨 step 生命周期;本页在模型允许时给它设置每层历史上限。
- Attention Sink也给 cache 设置上限,但在 recent window 外永久保留少量开头 tokens;它对应
sink ∪ recentmask,不是本页的纯 recent band。 - PagedAttention负责窗口滑动时 block 分配、驱逐与映射;window mask 不等同 page table。
- RoPE/长上下文负责位置映射与外推;窗口复用物理 slots 时仍要保留正确 logical position。
- FlashAttention可以实现 local/band mask 的 tile 跳过与 online softmax,但 backend 支持需实际验证。