阿明是市图书馆的新任管理员。过去那套推荐系统非常“认真”:每当一位读者对某本书感兴趣,它都要拿这本书和全馆所有书逐一比较,再给出推荐。小阅览室还能撑住,可到了藏书百万册的大城市图书馆,这套“全库两两比对”的做法立刻崩溃——工作量是书本数量的平方。Longformer 就是阿明的管理方案:他不再要求每本书认识全馆,而是先查手边最近的书(滑动窗口),再每隔几本抽样以望得更远(膨胀窗口),最后把少数核心参考书放到前台供所有人查阅(全局 token)。这套稀疏但高效的管理策略,让图书馆的工作量从“平方级”降到“线性级”,也正是 Longformer 处理超长文本的核心思路。
对 AI 从业者而言,掌握 Longformer 意味着你能在生产环境中处理动辄上万 token 的合同、论文和代码,不再被序列长度逼到截断或爆显存,这是长文本 NLP 工程落地的关键能力。
可视化演示
本节配有交互动效(进入视口后自动加载,约 3KB)。
\u6ED1\u52A8\u7A97\u53E3\u6CE8\u610F\u529B: \u6BCF\u4E2AToken\u53EA\u5173\u6CE8\u7A97\u53E3\u5185\u7684\u90BB\u5C45 \u6ED1\u52A8\u7A97\u53E3\u7279\u70B9\u81A8\u80C0\u7A97\u53E3\u4F18\u52BF\u5168\u5...\u6ED1\u52A8\u7A97\u53E3\u7279\u70B9\u81A8\u80C0...</sp...
滑动窗口机制
阿明发现,读者想了解一本书时,最相关的往往是同一排书架、相隔不远的那几本。与其让每本书都和全馆书籍比较,不如立一条规则:每个 token 只关注左右一定范围内的邻居。这个“左右一定范围”就是滑动窗口。
在 Longformer 里,每个 token 不再与整个序列做点积,而是只与窗口内的 token 计算注意力分数。窗口外的位置在 softmax 之前被掩码遮住,相当于直接不在推荐列表里出现。因为窗口大小是人工设定的常数,所以注意力计算量不再随序列长度平方增长。
实际工程中,窗口大小通常选 512 或 1024。如果任务中句子局部依赖很强,比如文本分类和抽取式问答,512 已经够用;如果文档跨度很大,比如长文档摘要,1024 会更稳妥。窗口越大,能看到的上下文越多,但计算和显存也随之上升。
阿明的“邻架规则”之所以有效,是因为语言里的相关性大多是局部的:一句话的核心意思,往往由前后几句话决定。这样既保住了主要信息,又把全库比对变成了线性工作量。
- n(序列长度)文本被切分后的 token 总数,相当于图书馆里的书的总数。
- w(单侧窗口半径)每个 token 能往左、往右各看 w 个位置。
- 2w+1左右各 w 个邻居,再加上自己当前的位置。
- 线性成本每行只算固定个数,不再是整个 n 维向量相乘。
膨胀窗口设计
只看邻架也有问题:有些重要的书隔了三个书架,但阿明的规则永远看不到它。于是他想到一个办法:不连续地查每一本,而是每隔 d 本抽一本。这样同样走一趟,视线却能越过好几个书架。
这就是膨胀注意力。它保留滑动窗口的“局部范围”思想,但窗口内只取距离为 d、2d、3d……的采样点。比如膨胀率 d=2,一个 token 会关注距离 2、4、6 的位置,而不是位置 1、2、3。于是每层计算量没有变多,最远却能看到原来的几倍。
多层 Transformer 叠加后,每一层都让信息再跳一次。浅层看相邻语义,深层看到跨段落关系,这就是膨胀窗口扩大感受野的原理。工程上可以只在靠后的若干层开启膨胀,避免最底层丢失细节。
回到图书馆:阿明不需要把每个书架都停下来,只需隔架扫一眼,就能判断远处是否有值得推荐的书籍。膨胀窗口是一款“省钱望远镜”,代价是可能漏掉中间细节,所以工程中通常与连续窗口搭配使用。
- d(膨胀率)每隔 d 个位置取一个采样点。
- r(每侧采样数)单侧最多抽取 r 个位置。
- 单层跨度最大关注距离约为 2dr。
- 多层叠加L 层之后,信息最远可以传播到大约 2drL 的位置。
全局Token策略
有些书是全馆的核心参考书,比如字典、地图和馆史资料。阿明把它们放到前台,任何读者都能直接查阅;这些书也可以主动跳到任意书架。这个“前台核心书”的概念,对应 Longformer 里的全局 token。
全局 token 的注意力模式非常特殊:它要对序列中所有 token 计算注意力,同时所有 token 也要关注它。换句话说,全局 token 保留了完整注意力的“特权”,但数量必须非常少,否则又会回到全连接的成本。
实际设置时,通常把 CLS 符号、特殊分隔符、问题 token 或任务关键实体设为全局。比如在长文档问答中,把问题整体设为全局,让模型每读到一段正文都能随时对照问题。数量一般控制在几个到几十个,不能像滑动窗口那样大量使用。
阿明在前台只放了几本核心参考书,而不是把整个书架搬过去。全局 token 的价值在于“精准锚点”:它让长文档中的关键信息始终在场,却不会让计算量失控。
- g(全局 token 数)被放到“前台”的特殊位置个数。
- n×g每个普通 token 都要和每个全局 token 做一次注意力计算。
- 窗口部分普通 token 之间仍只做窗口内注意力。
- 总成本只要 g 很小,整体仍是线性复杂度。
线性复杂度原理
原始 Transformer 的注意力,相当于每一位读者都要和全馆所有书做一次比较。如果读者有 n 个,书也有 n 本,总操作就是 n×n,也就是平方级复杂度。阿明的三件套——邻架、抽样、前台——让每本书只需要和固定数量的书发生关联。
Longformer 没有改变注意力公式本身,它改变的是“哪些位置允许互相看”。具体做法是在 QK^T 计算之后加一个稀疏掩码:窗口外、抽样外、非全局位置统统遮掉。这样每个 token 的注意力行只有 k 个非零元素,k 是常数。
从复杂度上看,全注意力需要计算 n² 对关系,Longformer 只需要 n×k 对关系。当序列长度从 512 涨到 8192 时,全注意力的成本会涨 256 倍,而 Longformer 只涨 16 倍,这就是工程意义上的“线性注意力”。
阿明并没有发明一种全新的“推荐算法”,他只是改变了哪些书会出现在比较名单上。名单缩小后,平方级工作变成了线性工作,这才是 Longformer 能在超长文本上跑起来的根本原因。
- 全注意力每一行都要和所有 n 个位置计算。
- 滑动窗口项每行最多关注 2w+1 个邻居。
- 全局 token 项每行还要额外关注 g 个全局位置。
- 总复杂度括号内是常数,所以整体是 O(n)。
显存优化技巧
图书馆旺季时,阿明发现临时借阅记录堆满了仓库。他不再把每一阶段的中间草稿都保存下来,而是只保存关键结果,需要反向计算时再重新生成。这种做法叫梯度检查点,本质是用时间换空间。
显存压力主要来自两部分:一是注意力矩阵本身,二是反向传播需要保存的中间激活。Longformer 用稀疏注意力把注意力矩阵从 n² 压到 n×k,大幅减少第一类开销;梯度检查点则针对第二类开销,随机丢弃部分中间激活,反向传播时重新计算。
分块注意力也是工程中常用的手段。它不让完整 n×n 矩阵一次性驻留显存,而是按块计算和累积注意力分数。配合混合精度训练,可以把长文本模型的训练从“一张卡放不下”变成“多卡也能塞下”。
阿明把仓库里用不到的草稿全部丢掉,等需要时再重新写一遍。虽然多花了一点时间,但图书馆终于不会被资料淹没了。
- 注意力矩阵显存全注意力要存 n² 个分数,稀疏注意力只存 n×k 个。
- k 的组成窗口内 2w+1 个位置,再加上全局 token 数 g。
- 梯度检查点只保存部分激活,反向传播时重算,减少峰值显存。
- 总显存稀疏结构与重计算叠加后,长序列训练成为可能。
推理加速方法
训练完成后,阿明还要面对每天成千上万的现场咨询。他不再每次临时翻书架,而是把最常用的推荐路径做成快捷卡片,直接递给读者。这就是推理阶段的缓存和 kernel 融合。
Longformer 的稀疏注意力天然适合算子融合:把掩码生成、QK^T 计算、softmax 和加权求和合并成一个自定义 kernel,减少中间矩阵存到显存再读回的次数。尤其在推理时,序列是逐步生成的,KV 缓存让已计算过的历史状态可以复用。
另一个实用技巧是批处理。长文本请求往往长度差异很大,动态 padding 和按长度分组可以避免整批被最长的样本拖慢。工程上还可以把 Longformer 导出为 ONNX 或 TorchScript,利用图优化进一步压缩延迟。
阿明的快捷卡片并不改变推荐质量,但能大幅减少每次咨询的翻书时间。推理优化也是如此:算法不变,服务的速度和吞吐量却完全不同。
- 全注意力耗时每行要算 n 个位置的分数。
- 稀疏注意力耗时每行只算 2w+1 个位置。
- 理论加速比两者相除得到 n 除以窗口宽度。
- 工程含义n 越大,稀疏化的收益越明显。
变体横向对比
阿明的图书馆管理方案,在隔壁街区也出现了不同版本。BigBird 图书馆采用了“邻架 + 前台 + 随机抽查”,也就是说除了固定邻居,每条书架上还会随机抽几本作为补充。随机连接让信息传播更均匀,但随机性也给复现带来一点麻烦。
Linformer 图书馆走的则是另一条路:它把所有书浓缩成固定长度的摘要卡,注意力不再发生在原书之间,而是发生在书和摘要卡之间。这样也能把复杂度降到线性,但代价是长距离细节会被压缩。
选择哪个变体,取决于任务特点。如果文本有很强的一维局部结构,比如合同和代码,Longformer 的滑动窗口最自然;如果希望全局信息也能通过随机捷径传播,BigBird 更稳;如果输入长度极度不均又希望结构简单,Linformer 的低秩投影更方便。
阿明并不认为自己的方案天下无敌。每个图书馆的布局、读者习惯和藏书结构不同,只有匹配任务特性的管理策略,才是真正高效的好策略。
- Longformer 模式用滑动窗口和膨胀抽样覆盖局部,用少量全局 token 做锚点。
- BigBird 模式在滑动窗口之上加入随机连接,强化全局信息传播。
- Linformer 模式通过投影矩阵把序列压缩到固定长度。
- 选型要点局部性强的任务优先 Longformer,需要更多全局捷径选 BigBird。
HuggingFace实践
阿明把“窗口、抽样、前台”这三条规则写进了图书馆管理系统的配置文件。以后不管换哪个馆员,只要读取配置,就知道该怎么摆放书架。HuggingFace 的 Longformer 就是这样一个“可配置管理系统”。
使用 LongformerModel.from_pretrained 加载模型后,核心配置是 attention_window。它可以是一个整数,也可以是一个列表,表示每一层 Transformer 的窗口大小。比如浅层用 512,深层用 1024,就能兼顾局部细节和长程语义。
全局 token 通过 input_global_attention_mask 传入:把需要设为全局的位置置 1,其他置 0。这个掩码可以和 input_ids、attention_mask 一起送入模型。训练时建议从预训练权重出发做微调,否则稀疏注意力模式从头训练收敛很慢。
阿明把管理经验沉淀成配置后,新馆员只需要照章办事。对工程师来说,这意味着可以把 Longformer 当成一个可插拔的编码器,快速接入自己的长文本任务。
- attention_window决定每层窗口半径,是 Longformer 最重要的超参数。
- global_tokens由全局注意力掩码标记的位置数量。
- 总成本每一行都要覆盖窗口位置和全局位置。
- 线性增长n 增大时成本按比例增加,不会爆炸。
AI 应用场景
超长法律合同审查
阶段:模型训练与微调
解决的问题:合同文本动辄上万 token,传统做法只能截断前 512 token,导致关键违约条款丢失。Longformer 用滑动窗口覆盖全文,并把合同编号、金额、日期等实体设为全局 token,让模型始终能看到关键信息。
具体案例:某法律科技公司用 Longformer 微调合同风险分类模型。输入整份合同,全局 token 标记“付款条件”和“违约责任”段落,模型在识别高风险条款时比截断方案 F1 提升 8 个百分点。
论文库离线向量化
阶段:离线推理与向量检索
解决的问题:把整篇学术论文编码成向量时,序列太长导致显存溢出。Longformer 的线性复杂度让单卡也能编码完整论文,不需要切段再拼接。
具体案例:某科研情报平台用 HuggingFace 的 Longformer 对 PubMed 摘要加正文编码,生成论文向量后存入向量数据库。用户搜索时可以先语义召回,再让模型精读全文。
代码缺陷定位
阶段:推理加速与线上服务
解决的问题:代码文件常常超过 2048 token,且缺陷往往与几十行之外的函数定义相关。Longformer 能把整个文件喂给模型,同时只对函数签名和 import 语句设置全局注意力。
具体案例:一家 DevOps 公司在 CI 流程中部署 Longformer 代码审查服务,用 ONNX 导出优化推理,把单文件分析延迟从 800ms 降到 350ms,并成功定位跨函数的空指针隐患。
Python 演示
def build_longformer_mask(n, window=4, dilation=2, global_tokens=(0, 15)): mask = [] for i in range(n): allowed = set() for step in range(-window, window + 1): j = i + step if 0 <= j < n: allowed.add(j) gap = dilation while True: left = i - gap right = i + gap if left < 0 and right >= n: break if left >= 0: allowed.add(left) if right < n: allowed.add(right) gap += dilation for g in global_tokens: allowed.add(g) mask.append(sorted(allowed)) nonzero = sum(len(row) for row in mask) full = n * n ratio = nonzero / full * 100 return mask, nonzero, full, ratio if __name__ == "__main__": n = 32 mask, nonzero, full, ratio = build_longformer_mask(n) print("sequence length:", n) print("non-zero attention entries:", nonzero) print("full attention entries:", full) print("non-zero ratio: %.2f%%" % ratio) print("sparsity ratio: %.2f%%" % (100 - ratio))
allowed = set()每个 token 用集合保存它允许关注的位置。for step in range(-window, window + 1):对应滑动窗口的左右邻居遍历。if 0 <= j < n:越界的位置直接丢弃,保证索引合法。while True:循环叠加膨胀采样点,直到左右两侧都越界。gap += dilation让采样距离从 d 变成 2d、3d,不断扩大感受野。for g in global_tokens:把全局 token 强行加入每个 token 的关注列表。mask.append(sorted(allowed))最终生成每行有序的稀疏注意力索引。ratio = nonzero / full * 100计算稀疏注意力实际覆盖的比例,直观看出省了多少计算。
常见误区
误区我听说 Longformer 是线性注意力,所以它应该把所有 QK^T 都换成了核函数或低秩近似。
正解Longformer 并没有改变注意力计算公式,它只是用稀疏掩码让每个 token 只关注固定数量的位置。复杂度是 O(n),但本质是“稀疏注意力”,和 Performer 那种核函数线性注意力不是一回事。
为何常见:因为很多文章把“线性复杂度”简化成“线性注意力”,导致初学者把两类方法混为一谈。
误区既然窗口能看到更多上下文,那我直接把 attention_window 设成 4096,效果一定比 512 好。
正解窗口增大会线性增加计算和显存,而且可能引入噪声。很多长文档任务的局部性很强,512 或 1024 已经足够,关键是配合膨胀窗口和全局 token,而不是盲目加大窗口。
为何常见:人们容易忽略“信息冗余”和“资源成本”的平衡,只想用更大的窗口获得更优效果。
误区全局 token 能看到所有位置,那我干脆把所有 token 都设成全局,模型肯定最强。
正解如果全部设成全局,Longformer 就退化成原始全注意力,n² 的复杂度全部回来,失去稀疏化的意义。全局 token 应该像“前台参考书”一样少而精准。
为何常见:全局 token 听起来像“注意力特权”,容易让人忽略它的开销是 O(n×g),g 一旦等于 n 就会爆炸。
误区BigBird 也是滑动窗口加全局 token,所以它和 Longformer 就是同一个模型换了个名字。
正解两者最大的差别是 BigBird 额外引入了随机注意力:每个 token 还会随机关注少量远处位置。这个随机连接让信息传播更均匀,但也让训练结果带有随机性,复现时需要固定随机种子。
为何常见:只看结构图时,两者都有窗口和全局块,容易忽略中间的“随机块”。
面试题
Q1. Longformer的滑动窗口注意力主要解决什么问题?
- A.提高词向量的表示维度
- B.把注意力成本降到线性← 正确答案
- C.让模型看得更远更全面
- D.取代所有位置编码方法
答案:B。Longformer 用窗口内注意力替代全连接注意力,每行只算固定数量位置,因此把 O(n²) 降到了 O(n)。A、D 与维度或位置编码无关,C 是全局 token 的职责。
Q2. 典型的Longformer单侧窗口大小常设为?
- A.通常取2或4个位置
- B.通常取64或128个位置
- C.通常取512或1024← 正确答案
- D.通常取4096或8192
答案:C。Longformer 论文和工程实践中常用 512 或 1024 作为单侧窗口大小。太小则看不到长程依赖,太大会失去低复杂度优势。
Q3. 膨胀注意力中dilation=2的效果是?
- A.只关注左右2个邻近位置
- B.关注距离为1、2、3的位置
- C.关注距离为2、4、6的位置← 正确答案
- D.完全随机选择关注位置
答案:C。dilation=2 表示每隔 2 个位置取一个采样点,因此关注距离是 2、4、6……这样可以不增加计算量却扩大感受野。A、B 是连续窗口的行为,D 是随机注意力。
Q4. Longformer全局token适合放在哪些位置?
- A.文档开头的CLS符号← 正确答案
- B.段落首句的少量位置← 正确答案
- C.任务关键实体附近位置← 正确答案
- D.每一个词元都设成全局
答案:ABC。全局 token 用于提供稳定锚点,CLS、段落首句和关键实体都适合。如果每个 token 都设为全局,稀疏注意力就会退化成全注意力,失去线性复杂度优势。
Q5. Longformer的计算复杂度是?
- A.序列长度的平方O(n²)
- B.随序列长度线性O(n)← 正确答案
- C.随窗口平方O(w²)
- D.对数级别O(nlogn)
答案:B。滑动窗口和全局 token 都是固定常数个位置,因此总成本和 n 成线性关系。全注意力才是 O(n²),窗口大小不是序列长度。
Q6. 相比全注意力,Longformer在推理时的理论加速比约为?
- A.约等于n除以窗口大小← 正确答案
- B.约等于窗口大小除以n
- C.约等于n乘窗口大小
- D.约等于n平方乘窗口大小
答案:A。全注意力的计算量是 n²,Longformer 是 n×w,两者相除得到 n/w。n 越大,稀疏化带来的加速越明显。
Q7. 关于梯度检查点的说法,哪些是正确的?
- A.不保存全部中间激活← 正确答案
- B.反向传播时重新计算← 正确答案
- C.顺带使用混合精度训练
- D.缩小嵌入维度降显存
答案:AB。梯度检查点通过丢弃部分中间激活、在反向传播时重算来降低峰值显存。混合精度和缩小嵌入维度是其他优化手段,不属于梯度检查点的核心做法。
Q8. 关于BigBird与Longformer,正确的说法是?
- A.两者都使用滑动窗口← 正确答案
- B.BigBird额外加入随机注意← 正确答案
- C.Longformer强制使用全局标记
- D.两者复杂度均为O(n²)
答案:AB。BigBird 也有滑动窗口和全局 token,但它额外引入了随机注意力。Longformer 的全局 token 是可选配置,不是强制;两者复杂度都是 O(n)。
Q9. Linformer与前两者的核心差异是?
- A.用卷积替换所有注意力层
- B.将序列投影到固定低维空间← 正确答案
- C.把滑动窗口扩大到整个序列
- D.用循环网络压缩历史状态
答案:B。Linformer 通过低秩投影把序列长度压缩到固定维度,让注意力矩阵变成固定大小。它没有滑动窗口,也不用随机注意力。
Q10. 使用HuggingFace Longformer时,如果要让某个位置能访问全序列,应该怎么做?
- A.把attention_window设为0
- B.给该位置global mask置1← 正确答案
- C.开启output_hidden_states
- D.在配置里关闭use_cache
答案:B。Longformer 通过 input_global_attention_mask 控制全局注意力,需要全序列访问的位置置 1。window 设为 0 反而会让它完全无法关注别人,后两个选项与全局注意力无关。