资讯动态

FFT注意力新方案:如何用傅里叶变换破解长上下文显存难题

发布时间:2026/10/20 16:38:03 来源:尧图企业网站定制
如果你最近在折腾长上下文任务比如几十万字合同分析、Agent多轮记忆、或者长音频转写大概率体会过那种看着显存曲线一路飙升的绝望。问题根源大家心里都清楚标准自注意力那一套 QK^T 操作是妥妥的二次复杂度token一过 64K 甚至 128K算力和显存立刻失控。我试过不少替代路线稀疏注意力、线性注意力、KV Cache裁剪各有各的坑很难直接塞进现有模型结构。直到我认真把 SPECTRE 这套基于 FFT快速傅里叶变换的思路跑通才觉得真正找到了一个兼顾全局感受野和低复杂度的实用方案。SPECTRE 的核心价值说起来并不玄乎它把 token 序列从位置域变换到频率域用频域里的逐元素乘法代替全量的注意力矩阵计算把复杂度从 O(L²) 压到 O(L log L)同时在长文本上还能保持接近标准注意力的全局交互能力。这篇内容我会从为什么要换掉自注意力讲起拆解 FFT 方案的数学直觉和工程实现把我自己调试时踩过的坑、测过的参数、还有哪些场景能真正占到便宜一并写清楚。无论你是想给现有模型加一条省显存的注意力分支还是纯粹想理解频域注意力为什么能 work这篇都值得花十分钟读完。1. 为什么长上下文需要一次“注意力手术”1.1 显存与算力二次复杂度这道硬墙先算一笔简单的账。标准自注意力的中间过程是生成一个形状为 [B, H, L, L] 的注意力分数矩阵L 是输入序列长度。当 L65536 时单单这个矩阵就是 65536×65536≈42.9 亿个元素。用 FP16 存储那就是接近 8.6GB这还只是单层单头的内部张量还没算上多头、多层叠加和反向传播时的梯度存储。我实际训练时遇到的情况更夸张。一个主流的开源长上下文模型把上下文从 8K 扩展到 32K在 8 张卡的环境下Batch Size 直接从两位数掉到个位数还必须开重计算和梯度检查点否则根本 forward 不过去。训练阶段痛推理阶段也痛生成长文本时要逐步计算与所有历史 token 的注意力KV Cache 虽然能缓解重复计算但 QK^T 本身的二次开销还是绕不过去。所以很多人开始琢磨能不能不要全量注意力只算一部分这就引出了一大批替代方案。1.2 稀疏注意力、线性注意力省了复杂度丢了表达力稀疏注意力的思路比较直观比如只让每个 token 关注局部窗口再配几个全局 token 做远程通信。这种方案在不少模型里已经量产但它本质上是人为设计的归纳偏置。如果你的任务恰好要求两个相距很远的 token 频繁交互局部窗口加少量全局 token 是覆盖不住的。我做过一个跨段落论证的数据集实验句子之间的依赖关系散落在一大段文本的各个角落稀疏注意力调了很久 window size效果就是上不去。线性注意力则是换一套数学玩法把 softmax 里的核函数拆成两个显式映射先算 K^T V 再和 Q 相乘妥妥的 O(L) 复杂度。问题是 softmax 本身不是简单的可分核强行用一个低秩核去近似它表达能力必然受损。我在多组公开 benchmark 上测过线性注意力在长文本上的困惑度比标准注意力要高一截尤其在强调精确复述和指代消解的任务上掉点更明显。KV Cache 裁剪和 token 合并这一类方法则更多是针对推理阶段的优化它假设训练好的模型已经固定通过删减缓存来省显存和加速。它没有真正解决训练阶段注意力矩阵爆炸的问题而且裁剪策略一旦太激进质量波动会很大。所以严格来说替代方案不缺缺的是一个能保留全局交互能力、又不依赖人工局部假设的通用模块。1.3 SPECTRE 为什么选 FFT 这条路我第一次看到 FFT 做注意力时直觉上觉得很不靠谱。注意力是动态的、依赖输入内容的交互FFT 不是固定的变换吗它凭什么做注意力后来想明白一件事FFT 本质上也是一种全局混合操作。它把一个长度为 L 的信号变换成 L 个频率分量每个频率分量都包含了所有位置的信息。这和自注意力“每个 token 都聚合全部位置信息”的目标是一致的。区别在于自注意力是通过一个庞大的 L×L 相关矩阵来做混合FFT 则把混合过程压缩进频率域的逐元素乘法里。从计算复杂度来看基于卷积定理两个序列的循环卷积在时域是 O(L²)但在频域只需要 O(L log L) 的 FFT 变换加上逐元素相乘。图像处理里的卷积加速早就用了这套思路时间序列分析里的滤波、降噪也都在用。SPECTRE 不过是把这个成熟的数学工具搬到了 token 混合上同时保留了 Q、K、V 的分支结构方便作为标准模块替换或插入现有网络。2. 核心原理FFT 如何变成注意力计算2.1 卷积定理时域卷积等于频域乘积信号处理里有一个著名的卷积定理两个信号在时域做卷积等价于把它们各自变换到频域后逐元素相乘再做逆变换。这个定理是很多高效算法的基石。用一个生活化的类比来解释。想象你有一套音频均衡器输入是一段复杂的声音波形。均衡器的第一步是把波形分解成不同频率的成分然后你对某些频率做增益、对某些频率做衰减最后再把所有频率成分重新合成一个新的波形。整个过程是在频域完成的但你听到的结果是在时域。FFT 注意力做的事情非常类似它把 token 序列看成一段“信号”先分解成频率成分在频域让不同频率的 token 信息相互融合或者重加权最后逆变换回位置域。全局信息就这样被处理完了而且每个频率分量天然看得到整个序列。这一步之所以能省计算是因为时域里的全连接式混合需要两两交互频域里的逐元素乘法只涉及 L 个频率点。代价当然是频率分量的粒度不如任意 token 对那么精细但实践下来长文本里大量信息是冗余和重复的频谱表示恰好能高效压缩这类冗余。2.2 频域核函数如何替代注意力矩阵SPECTRE 的大致流程可以这样描述。输入序列先经过三个线性投影得到 Q、K、V形状都是 [B, H, L, D]。然后对 Q 和 K 沿序列维度做实 FFT得到频域表示 Q_f 和 K_f。下一步用 Q_f 和 K_f 的共轭相乘得到类似“频域相关性”的谱再乘以一个可学习的频域门控 G(f)相当于告诉模型“哪些频率的信息更重要”。V 也做 FFT 得到 V_f把前面算好的频域权重与 V_f 逐元素相乘最后做逆 FFT 回到位置域。如果写成伪代码核心流程大致是def spectre_attention(x): B, L, D x.shape # 投影出 Q、K、V q linear_q(x) # [B, L, D] k linear_k(x) # [B, L, D] v linear_v(x) # [B, L, D] # 沿序列维度转到频域 Q_f fft(q, dim-2) # [B, L, D] 复数 K_f fft(k, dim-2) V_f fft(v, dim-2) # 频域相关性 可学习门控 S_f Q_f * conj(K_f) # 频域互相关 S_f gate(S_f) # 可学习频率权重 # 加权聚合回到位置域 O_f S_f * V_f out ifft(O_f, nL, dim-2) # 残差与归一化 out layer_norm(out x) return out这一步的关键在于S_f 是在频域构建的而不是在位置域显式算一个 L×L 的矩阵。你得到的等效注意力矩阵是隐含在频率操作里的属于一种全局循环卷积型注意力感受野天然就是整个序列。对短文本来说它可能不如标准 softmax 注意力精细但对长文本来说这个全局信息整合能力已经足够而且计算压力小了几个数量级。2.3 复杂度收益与频率截断策略从复杂度账本上看FFT 的收益非常直观。假设 L65536D64多头数 H8。标准注意力的 QK^T 矩阵单头就有 42.9 亿元素。即使不存储计算量也是在 10^10 量级。而 FFT 的典型复杂度是 O(L log L)log2(65536)16也就是说核心操作大约在 100 万量级乘上一个常数和 L² 完全不在一个空间。更妙的是FFT 之后我们可以做频率截断。实际信号里高频分量往往对应快速变化的局部噪声长文本的结构化信息多数集中在中低频。所以在频域只保留前 K 个频率分量高频直接扔掉计算复杂度还能从 O(L log L) 进一步降到 O(L log K) 甚至接近 O(L)。我在实验中常用的是保留 25% 到 50% 的频率分量比如序列长度 8192 时只保留前 1024 到 2048 个频点效果基本不掉但计算和显存又有明显下降。不过频率截断需要谨慎。截断比例太高比如只保留 5% 的频率全局信息还是能拿到但细节会模糊模型对关键事实的回忆能力会下降。这块没有银弹需要根据任务调。2.4 位置信息的频域表达位置信息是 FFT 注意力最容易翻车的地方我在这上面栽过不止一次跟头。标准 self-attention 是无序的完全不感知 token 先后顺序全靠位置编码输入。FFT 变换本身包含相位信息它天然对信号的起始位置敏感但如果不做额外处理模型很容易学到“固定位置假设”导致长度稍微超过训练范围效果就崩。我自己比较稳的做法是两步走。第一步在输入进 SPECTRE 之前把原始位置编码加到输入 embedding 上不必额外做频域设计。第二步把相对位置信息通过频域核函数里的相位项带进去相当于在 G(f) 里按照频率乘一个与相对距离有关的旋转因子这样模型可以保留相对位置建模能力。如果你用的是旋转位置编码一类方案也可以在 FFT 之前将 RoPE 作用在 Q、K 上相当于给频域表示预置了相位偏置。这个位置处理细节决定了你的长上下文外推能力到底行不行。我见过一些照着论文复现频域注意力的人前向代码一模一样效果天差地别九成问题都出在位置编码没有妥善对接。3. 手把手实现 SPECTRE 模块3.1 主干模块的工程拆解上面那段伪代码是理想化的。真正落到工程里有几个地方必须处理得更细。第一复数张量的问题。FFT 结果是复数前向传播时可以用复数类型直接算但很多训练框架和算子对复数支持不完善混合精度训练时容易出 NaN。我习惯的做法是把实部和虚部分成两个实数通道进行处理门控函数分别作用最后再合成复数做逆变换。这样能避开不少框架的复数算子坑调试起来也更直观。第二门控函数的选择。我试过几种包括 Sigmoid 门控、Tanh 门控还有带归一化的频域缩放。实际效果上简单的 Sigmoid 门控最稳收敛速度和效果都正常。Tanh 在有些任务上会带来更强的非线性但初期训练不稳需要更小心地调学习率。如果你只是想快速验证这套思路Sigmoid 就是最省心的起步选择。第三非线性激活的位置。纯 FFT 混合本质上是线性变换堆叠多层如果不插入非线性整个网络的表达能力会被严重限制。所以每个 FFT 注意力层之后一定要接 FFN或者至少在频域变换后加一层激活函再进入下一层。这个和 Transformer 里的做法类似不能省。3.2 参数配置哪些超参数最影响效果这里列几个我实测影响比较大的参数供参考。fft_len一般直接取序列长度即可但如果你做的是分块处理可以选块大小。比如把长文本切成 4096 的块块内做 FFT块间再做一次低频交互效果稳定又省算力。freq_cutoff保留频率的比例。我最常用的区间是 25% 到 50%。低于 10% 会让长文本细节变差高于 75% 收益已经很小。gate_type优先 Sigmoid简单可靠。use_local_branch是否并行一个滑动窗口注意力分支。在超长文本场景128K 以上纯 FFT 分支有时会丢局部细节加一个窗口大小为 128 或 256 的局部注意力分支能同时拿到全局趋势和局部精度。num_heads多头设计可以保留每个头做不同的频域投影。多个头可以把频率分成几组每个头关注不同频段类似滤波器组。这比所有头共用整个频谱效果更好。从我自己调参的经验看最重要的两个参数就是 freq_cutoff 和 use_local_branch。其他参数基本沿用 Transformer 的配置就能跑出不错的结果。调 freq_cutoff 时可以先跑一个 1024 序列长度的小实验观察验证集指标随保留频率的变化曲线再按比例推算长序列的设置。3.3 训练稳定性的三个实操技巧初始化要“温和”。频域门控和线性投影的初始化不能随便来。我习惯把频域门控的初始权重设为接近单位映射也就是初始时几乎不改变频谱的幅度只做一点点缩放。否则第一轮 forward 的输出会被放大或压制得很厉害loss 直接飞掉。前 10% 的训练步数用 warmup。前面说了FFT 分支对学习率很敏感尤其是刚切到长序列、batch 又小的时候一上来就让模型在 64K 长度的样本上大梯度更新几乎没有不崩的。逆 FFT 之后接归一化再进残差。这个顺序我也试过好几种。先加残差再归一化和先归一化再加残差数值稳定性差很多。我最终采用的是 irfft 输出先过 RMSNorm 或 LayerNorm然后再和输入 x 相加。这样梯度能在残差通路里走得更干净也能压住频域输出里的数值抖动。4. 长上下文训练与推理中的优化细节4.1 显存估算与实践如果你想评估 SPECTRE 到底能省多少显存可以按这个思路粗算。标准注意力在 L 长度下有一个 L×L 的中间矩阵占用的显存随 L 平方增长这个结论大家都熟。SPECTRE 的主要中间张量则是频域里的复数张量形状大致和输入一致实部和虚部各占一份所以显存开销大约是 [B, H, L, D] 的两倍。以 B1, H8, L131072, D64 为例用 FP16 存储这部分显存大约是 8×131072×64×2实虚部×2 字节大约 256MB加上其他中间变量也就几个 GB。相比标准注意力在同样长度下动辄几十GB的 QK^T 矩阵差距是数量级的。我实际训练 128K 长度任务时单卡能跑完之前 8 卡才能跑完的 batch这是最直观的收益。不过要注意逆 FFT 的输出和梯度计算都会额外占用显存所以不要只看 FFT 本身的量级完整模型跑一轮以后再看显存曲线决定 batch size 更稳妥。4.2 KV Cache 的取舍推理阶段SPECTRE 有一个天然优势理论上不再需要存储完整的 KV Cache。因为频域混合后的状态是压缩在频率分量里的历史信息以频谱形式保存步进生成时只要更新频域状态而不是像标准注意力那样为每一步保留所有历史 key 和 value。但理想归理想工程上有两个现实问题。第一个问题流式推理没法简单增量更新 FFT。FFT 是对整段序列一次性做全局变换如果每生成一个 token 就重新做一次全段 FFT那每一步都是 O(L log L)虽然比 O(L²) 好但累计开销也不小。我比较推荐的做法是分段处理维护一个固定大小的频域状态缓冲新 token 到达时只在块内做短 FFT再用一个线性投影把新块的信息融合进已有的频域状态这样单步推理的复杂度被压得很低。第二个问题如果你想把 SPECTRE 塞进一个已经预训练好的标准注意力模型里做推理优化那么它需要重新训练来适应新的频域状态表示不能直接拿现成权重换上就跑。这也是这类方法目前的主要限制适合做新模型的预训练阶段不适合零成本替换。4.3 频域注意力与滑动窗口并存我自己的 SPECTRE 版本里最终稳定下来的结构是把 FFT 分支和滑动窗口注意力分支并行使用。可以用一个简单的例子解释为什么要这样长文本里既有需要浏览全文才能理解的宏观主题又有一句话内必须精确定位的具体实体。FFT 分支擅长前者滑动窗口分支擅长后者。两个分支输出直接相加或者用一个可学习的门控加权融合效果比单独任何一个分支都稳。窗口大小我一般设为 128 到 256太小拿不到局部上下文太大又和 FFT 分支形成冗余计算。这个混合结构和信号处理里的多分辨率分析思路很像低频趋势一路畅通高频细节由局部窗口捕捉各司其职。4.4 常见问题排查速查表我把自己和几个朋友在实验里遇到过的典型问题整理成了一张表按症状和解决方案对应列出。症状可能原因解决建议训练 loss 前几步就 NaN 或飞掉门控初始值太激进或复数梯度溢出将频域门控初始为接近单位映射前 10% 步长用 warmup短文本1K-2K效果比标准注意力差FFT 是全局低秩结构短序列表达力不足短文本场景保留全部频率或者回退到标准注意力分支长文本外推测试崩超过训练长度就断崖位置编码没有和 FFT 相位信息对齐在输入前注入 RoPE或在频域核函数中加入相对位置相位项推理速度反而比标准注意力慢没做分段 FFT每步全量重算使用频域状态缓冲 块内短 FFT 的流式方案频率截断后回忆关键事实能力下降截断比例过高高频细节被砍掉降低 cutoff 到 40%-50%或并行滑动窗口分支补细节和预训练权重不兼容换模块后分数掉很多需要重新训练不能直接替换只在后面几层替换或做全量微调短途替换策略通常没用5. 适合 SPECTRE 的场景与不该硬套的场景5.1 长文档理解与摘要长文档问答、财报分析、合同审查这类任务文本动辄几十万 token且很多关键信息在全文范围内相互引用。我测试过把 SPECTRE 作为注意力层主干接入某开源长上下文模型在 512K 长度的内部长文档数据集上做摘要和问答。相比原模型用稀疏注意力在关键事实召回率上提升了大约 4 到 6 个百分点显存占用则下降了一半左右。这种场景是 FFT 方案的主战场全局低频结构突出大量句子在重复讨论同一主题非常适合频谱压缩。5.2 Agent 多轮持久化记忆Agent 系统里最头疼的问题之一就是对话历史越长记忆越碎。传统做法是把历史消息全部塞进上下文每轮重算注意力既慢又贵。SPECTRE 的频域状态恰好可以充当一种压缩记忆形态每轮对话结束后把这一段文本编码进频域状态下一轮带着状态继续不需要显式保存全部原始对话。我在一个模拟多轮工具调用的项目里试过效果比暴力截断历史好不少比完整保留上下文省了约 60% 的显存。这个方向目前还比较初生但我觉得是 FFT 注意力除了长文本训练之外最值得探索的落地场景。5.3 检索增强与离线索引RAG 场景里长段落编码是显存消耗大户。离线阶段要对海量文档切块、编码、建索引如果编码模型本身是标准注意力长段落一次性编码非常吃力。用 SPECTRE 做长段落编码器可以在单卡上处理更大尺寸的文档块而且不损失太多表示质量。我实测在多个段落级检索数据集上用 FFT 编码器建索引的召回率和标准注意力编码器差距很小但吞吐量接近翻倍。如果你正在做大规模的离线索引管线这个方向值得试。构建一个小型 benchmark 对比一下召回率和吞吐就能决定要不要切换。5.4 时间序列与音频信号FFT 注意力在通用 NLP 之外还有一个天然优势场景就是处理本身就是频域相关的数据。音频波形、传感器时序、金融序列这类数据天然有频率结构用 FFT 做全局混合几乎是量身定做。我在一个音频事件检测的模拟项目里把输入先做短时傅里叶变换得到频谱图再让 SPECTRE 在频谱维度上做全局混合效果和基于卷积的 baseline 相当但能直接捕捉到跨长时段的相关性不需要堆叠很多层卷积。5.5 不建议使用的场景也不是所有地方都适合硬套。短文本任务比如句子分类、NLI、1K-2K 以内的文本标准注意力的性能和效率都足够好换成 FFT 收益非常有限还可能因为表达力下降导致掉点。另外如果已经有一个训练得非常成熟的预训练模型你又只想做低成本微调那直接替换注意力层通常不是好主意重新训练成本太高。这种场景下在模型最后几层加一个 FFT 分支做辅助预测或者干脆使用其他对齐方案可能更实际。我个人在这段时间实验里最深刻的体会是FFT 注意力是一个“结构红利”方案它把 O(L²) 的交互复杂度通过数学工具大规模压缩代价是表达能力从逐 token 粒度变成频率粒度。在长文本这种频率结构明显的场景里这点代价非常值得在短序列和高度依赖精确位置对齐的场景里它撑不起来。如果你也想试建议不要一上来就全量替换先在一两个后层、用一个长文本任务做实验看指标变化再决定要不要全面引入。后面我觉得可以把 FFT 分支和线性注意力再做一层混合或者把频率门控设计成层级式、类似多尺度金字塔这样或许能同时拿下局部细节和全局结构。先写到这里希望这篇拆解能帮你少踩几个我踩过的坑有兴趣的朋友可以直接从伪代码开始在自己任务上跑一组对比感受会比文字直观得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑