第一个 token 为什么吸走绝大部分注意力,这一现象的机制、代价,以及为何消除它要留到 Gated Attention
配套代码
上一章我们看到 GQA 把 KV cache 压到 MHA 的 1/4,代价是部分任务上略有质量下降。但 GQA 改的是 KV head 的数量,没有动 attention 本身的数学结构:还是 softmax,还是 causal mask,还是 𝑄 𝐾 𝑇 / 𝑑 QK T / d 。
如果换一个角度,绕过 KV cache,只盯着 attention 权重看,会发现一个跟 head 数、跟 group 数都无关的现象:推理时,绝大部分注意力都流向第一个 token。
这就是 Attention Sink。本章我们在 Qwen3-0.6B 上把它从头看到尾;而从机制上看,只要 attention 还是 softmax + causal 的组合,它就会出现。
Schematic: what an attention sink looks like
Each row is a query, each column a key; under the causal mask a query only sees itself and the keys to its left (dashed cells are masked out). Notice column 0: almost every query puts most of its attention weight there, forming a vertical bright stripe across all queries. That stripe is the attention sink.
本章回答三个问题:
我们用 Qwen3-0.6B 跑 4 条 prompt(英文 3 条 + 中文 1 条),把每一层每一个 head 的 attention 矩阵导出来,统计第一个 token 在 query > 0 位置上的平均权重。代码很简单,关键只有一处:把 attn_implementation 设为 eager,推理时传 output_attentions=True。
basics/architecture/attention/02_attention_sink_curve.py
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
tok = AutoTokenizer.from_pretrained("Qwen/Qwen3-0.6B")
model = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen3-0.6B",
dtype=torch.bfloat16,
attn_implementation="eager", # 必须 eager 才能拿到 attention 权重
)
model.eval()
prompt = "The quick brown fox jumps over the lazy dog."
ids = tok(prompt, return_tensors="pt").input_ids
with torch.no_grad():
out = model(ids, output_attentions=True)
# out.attentions: (num_layers,) 个形状 (B, H, N, N) 的张量
# 取 token 0 在 query > 0 位置上的平均权重(跳过 token 0 attend 自己的 trivial 1.0)
for layer_idx, attn in enumerate(out.attentions):
first_token_attn = attn[0, :, 1:, 0].mean()
print(f"layer {layer_idx:2d}: {first_token_attn.item():.3f}")
跑出来的曲线是这样的: