
我们应该也遇到过这样的情况,在和模型应用沟通的过程中,聊着聊着它就忘了开头的要求;或者让模型工具分析一本几百页的电子书,它只记住了最后几页的内容?我们也反复讨论过,这不是大模型记性差,而是它的注意力范围有限,原始的大模型注意力机制,看的文本越长,计算量就会像滚雪球一样爆炸式增长,普通电脑根本扛不住。
而滑动窗口和稀疏注意力,就是给大模型扩宽视野的两个核心妙招:一个让大模型用放大镜看长文本,只看局部,快但短视,一个让大模型跳着看长文本,注重局部和重点,稍慢但全面。今天咱们就从这两个技术入手,看看它们的底层逻辑、数学原理以及实际用法,针对这些问题可以有一个推荐的解决方案。

当我们读这句话的时候,应该要有一种似曾相识的感觉,应该会不自觉地关联前面的内容,比如看到“扩窗技术”,会想起前文所说的“大模型视野有限”。大模型的“注意力机制” 和这个过程一模一样:

左图:原始注意力
中图:滑动窗口注意力
右图:稀疏注意力
不管是哪种注意力,核心都是“加权求和”,先看原始注意力的公式,再了解其他注意力的差别:
Attention(x_i) = ∑(softmax( (q_i · k_j) / √d ) × v_j) (j从1到n)
公式里每个符号详细说明:
核心:缩小求和范围:
SWAttention(x_i) = ∑(softmax((q_i · k_j) / √d ) × v_j) (j从max(1, i-W/2)到min(n, i+W/2))
差异点:
核心:选关键位置求和
SparseAttention(x_i) = ∑(softmax((q_i · k_j) / √d ) × v_j) (j属于S_i)
差异点:


步骤说明:
示例展示:
文本序列:[1] [2] [3] [4] [5] [6] [7] [8] i=1,W=3 → 窗口:[1][2][3] → 关注:● ● ● □ □ □ □ □ i=4,W=3 → 窗口:[3][4][5] → 关注:□ □ ● ● ● □ □ □ i=8,W=3 → 窗口:[6][7][8] → 关注:□ □ □ □ □ ● ● ● (●=关注,□=不关注)


步骤说明:
示例展示:
文本序列:[1] [2] [3] [4] [5] [6] [7] [8] i=4,W=2,S=3 → S_i={1,3,4,5,7,8} 关注情况:● □ ● ● ● □ ● ● (●=关注,□=不关注)

import torch
import torch.nn.functional as F
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
# ---------------------- 核心函数 ----------------------
def sliding_window_attention(q, k, v, window_size=4):
"""滑动窗口注意力(带关注矩阵记录)"""
seq_len, d = q.shape
output = torch.zeros_like(v)
# 记录关注矩阵(seq_len×seq_len,1=关注,0=不关注)
attention_mask = torch.zeros((seq_len, seq_len))
for i in range(seq_len):
# 计算窗口范围:当前位置i向前看window_size个位置
start = max(0, i - window_size + 1)
end = i + 1
# 标记关注的位置(从start到i)
attention_mask[i, start:end] = 1
q_i = q[i:i+1, :]
k_window = k[start:end, :]
v_window = v[start:end, :]
scores = torch.matmul(q_i, k_window.T) / torch.sqrt(torch.tensor(d))
weights = F.softmax(scores, dim=-1)
output[i, :] = torch.matmul(weights, v_window)
return output, attention_mask
def sparse_attention(q, k, v, window_size=2, stride=3):
"""稀疏注意力(带关注矩阵记录)"""
seq_len, d = q.shape
output = torch.zeros_like(v)
attention_mask = torch.zeros((seq_len, seq_len))
for i in range(seq_len):
# 1. 局部窗口:当前位置i向前看window_size个位置
start = max(0, i - window_size + 1)
end = i + 1
local_j = list(range(start, end))
# 2. 跨步位置:从当前位置i向前按stride步长跳跃
stride_j = []
j = i - stride
while j >= 0:
stride_j.append(j)
j -= stride
# 3. 全局位置(首尾)
global_j = [0, seq_len-1] if seq_len > 1 else [0]
# 合并去重
all_j = list(set(local_j + stride_j + global_j))
all_j.sort() # 排序保证顺序
# 标记关注的位置
attention_mask[i, all_j] = 1
q_i = q[i:i+1, :]
k_sparse = k[all_j, :]
v_sparse = v[all_j, :]
scores = torch.matmul(q_i, k_sparse.T) / torch.sqrt(torch.tensor(d))
weights = F.softmax(scores, dim=-1)
output[i, :] = torch.matmul(weights, v_sparse)
return output, attention_mask
# ---------------------- 可视化函数 ----------------------
def plot_attention_comparison(sw_mask, sparse_mask, seq_len):
"""绘制注意力关注矩阵对比图(生成图片)"""
# 创建画布(1行3列,对比原始/滑动窗口/稀疏注意力)
fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize=(15, 5))
# 1. 原始注意力(全关注)
original_mask = torch.ones((seq_len, seq_len))
im1 = ax1.imshow(original_mask.numpy(), cmap='Blues', vmin=0, vmax=1)
ax1.set_title('原始注意力(全关注)', fontsize=12)
ax1.set_xlabel('关注的位置j')
ax1.set_ylabel('当前位置i')
# 2. 滑动窗口注意力
im2 = ax2.imshow(sw_mask.numpy(), cmap='Blues', vmin=0, vmax=1)
ax2.set_title('滑动窗口注意力(W=3)', fontsize=12)
ax2.set_xlabel('关注的位置j')
# 3. 稀疏注意力
im3 = ax3.imshow(sparse_mask.numpy(), cmap='Blues', vmin=0, vmax=1)
ax3.set_title('稀疏注意力(W=2+S=3)', fontsize=12)
ax3.set_xlabel('关注的位置j')
# 统一刻度(方便对比)
for ax in [ax1, ax2, ax3]:
ax.set_xticks(range(seq_len))
ax.set_yticks(range(seq_len))
ax.set_xticklabels([f'{i+1}' for i in range(seq_len)])
ax.set_yticklabels([f'{i+1}' for i in range(seq_len)])
ax.set_xlabel('关注的位置j')
ax.set_ylabel('当前位置i')
# # 添加颜色条(放在右侧外部)
# cbar = fig.colorbar(im1, ax=[ax1, ax2, ax3], shrink=0.8, pad=0.95, aspect=30)
# cbar.set_label('关注状态(1=关注,0=不关注)', fontsize=10, rotation=270, labelpad=20)
# 保存图片(新手可改路径)
plt.tight_layout()
plt.savefig('attention_comparison.png', dpi=300, bbox_inches='tight')
print("图片已保存为:attention_comparison.png")
plt.show()
# ---------------------- 测试+生成图片 ----------------------
if __name__ == "__main__":
# 模拟输入:序列长度=8,向量维度=2
seq_len = 8
d = 2
q = torch.randn(seq_len, d)
k = torch.randn(seq_len, d)
v = torch.randn(seq_len, d)
# 计算滑动窗口注意力(窗口=3)
sw_output, sw_mask = sliding_window_attention(q, k, v, window_size=3)
# 计算稀疏注意力(窗口=2,跨步=3)
sparse_output, sparse_mask = sparse_attention(q, k, v, window_size=2, stride=3)
# 生成对比图
plot_attention_comparison(sw_mask, sparse_mask, seq_len)
# 打印关键信息(新手验证)
print("滑动窗口注意力关注矩阵:")
print(sw_mask.numpy().astype(int)) # 1=关注,0=不关注
print("\n稀疏注意力关注矩阵:")
print(sparse_mask.numpy().astype(int))输出结果:
滑动窗口注意力关注矩阵: [[1 0 0 0 0 0 0 0] [1 1 0 0 0 0 0 0] [1 1 1 0 0 0 0 0] [0 1 1 1 0 0 0 0] [0 0 1 1 1 0 0 0] [0 0 0 1 1 1 0 0] [0 0 0 0 1 1 1 0] [0 0 0 0 0 1 1 1]] 稀疏注意力关注矩阵: [[1 0 0 0 0 0 0 1] [1 1 0 0 0 0 0 1] [1 1 1 0 0 0 0 1] [1 0 1 1 0 0 0 1] [1 1 0 1 1 0 0 1] [1 0 1 0 1 1 0 1] [1 0 0 1 0 1 1 1] [1 1 0 0 1 0 1 1]]

import matplotlib.pyplot as plt
# 配置字体(支持中文和符号)
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
# 序列设置
positions = list(range(1, 11)) # [1, 2, ..., 10]
n = len(positions)
# 1. 原始注意力:全部关注
original = [True] * n
# 2. 滑动窗口注意力 (W=3, i=5) → 关注 4,5,6
window = [False] * n
for idx in [4, 5, 6]: # 位置 4,5,6(注意索引从1开始)
window[idx - 1] = True
# 3. 稀疏注意力:关注 1,4,5,6,8,10(按你给出的结果)
sparse_set = {1, 4, 5, 6, 8, 10}
sparse = [pos in sparse_set for pos in positions]
# 所有行数据
rows = [
{
"title": "原始注意力",
"desc": "全部关注",
"attention": original,
"color": "tab:green"
},
{
"title": "滑动窗口注意力",
"desc": "W=3, i=5 → 关注位置 4,5,6",
"attention": window,
"color": "tab:blue"
},
{
"title": "稀疏注意力",
"desc": "W=2 + S=3, i=5 → 关注位置 1,4,5,6,8,10",
"attention": sparse,
"color": "tab:orange"
}
]
# 创建子图
fig, axes = plt.subplots(3, 1, figsize=(12, 6))
fig.suptitle("注意力机制对比(序列长度=10)", fontsize=16, weight='bold')
for ax, row in zip(axes, rows):
# 绘制每个位置
for j, pos in enumerate(positions):
is_attend = row["attention"][j]
symbol = '●' if is_attend else '□'
color = row["color"] if is_attend else 'lightgray'
ax.text(pos, 0.5, symbol, fontsize=24, ha='center', va='center', color=color)
ax.text(pos, 0.2, f'[{pos}]', fontsize=9, ha='center', va='center', color='black')
# 设置坐标轴
ax.set_xlim(0.5, n + 0.5)
ax.set_ylim(0, 1)
ax.axis('off')
# 添加左侧标题(竖排)
ax.text(-0.05, 0.5, row["title"], transform=ax.transAxes,
rotation=90, va='center', ha='center',
fontsize=12, weight='bold', color=row["color"])
# 添加顶部说明文字
ax.text(0.5, 0.92, row["desc"], transform=ax.transAxes,
fontsize=11, ha='center', va='top',
bbox=dict(boxstyle="round,pad=0.3", facecolor="whitesmoke"))
# 调整布局
plt.tight_layout(rect=[0.05, 0, 1, 0.95])
plt.show()输出结果:

我们将展示三种注意力机制的计算复杂度:
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
# 设置序列长度范围
n_values = np.linspace(1000, 10000, 500)
# 计算不同模型下的计算量(相对值)
original_attention = n_values ** 2 / (1000 ** 2) # 归一化处理以便比较
sliding_window = n_values * 0.001 # 假设比例因子使得其在图中显示合理
sparse_attention = n_values * 0.003 # 稀疏注意力斜率稍高于滑动窗口
# 创建图表
plt.figure(figsize=(10, 6))
plt.tight_layout(pad=0)
plt.subplots_adjust(left=0.08, right=0.98, top=0.95, bottom=0.1)
# 绘制三条曲线
plt.plot(n_values, original_attention, label='原始注意力 ( $ O(n^2) $ )', color='red')
plt.plot(n_values, sliding_window, label='滑动窗口 ( $ O(n) $ )', linestyle='--', color='blue')
plt.plot(n_values, sparse_attention, label='稀疏注意力 ( $ O(n) $ )', linestyle='-.', color='green')
# 添加标题和标签
plt.title('计算量随序列长度的变化曲线')
plt.xlabel('序列长度 n')
plt.ylabel('计算量(相对值)')
# 显示图例
plt.legend()
# 展示图表
plt.grid(True)
plt.show()输出图示:

我们可以把大模型的“上下文长度”理解为它的“记忆长度”,越长,能处理的文本(比如整本书、长对话、代码文件)就越长。扩窗技术的核心价值是:
1. 滑动窗口:让大模型跑得起来
2. 稀疏注意力:让大模型记得更全
3. 整体意义:推动大模型更贴合实际应用
今天我们说的滑动窗口和稀疏注意力,本质就是给大模型扩视野的两个方法,核心都是解决一个问题:原始注意力看文本越长,计算量越爆炸,普通电脑扛不住。
滑动窗口就像给大模型装了个固定大小的放大镜,看当前内容时只聚焦周围一小片,超出范围的全忽略。它最大的优点是简单、跑得快、不费内存,普通显卡都能跑,我们日常用的聊天机器人,大多靠它撑着。但缺点也明显,短视,聊久了、处理长文本时,前面的关键信息超出窗口就忘了,只能顾着眼前。
稀疏注意力就聪明多了,走的是聪明跳读路线:既保留滑动窗口的局部视野,又每隔一段挑几个远处位置看看,还必抓首尾关键信息。虽然比滑动窗口稍复杂、计算量略高,但能兼顾局部细节和全局重点,像分析整本书、超长代码这种活儿,全靠它撑场面。
总结下来就是:追求快、处理中短文本,选滑动窗口;要兼顾全面性、搞定超长文本,就用稀疏注意力。但技术没有好坏,都是为了让大模型既能看得更远,又能跑得动,把以前只能靠天价显卡才能做的长文本任务,可以实际落地具体的应用场景。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。