☰
TIME-MADEIRA模型解析:软MoE与块稀疏注意力融合的时序预测
2026/10/1 11:33:46 网站建设 项目流程

做时间序列预测做到这两年,大家应该都有一种很真实的纠结:Transformer 架构在长序列上的能力强,但开销也大;想用混合专家(MoE)来摊薄成本吧,又常常被离散路由的 token 丢弃、负载不均和训练不稳定搞到心态爆炸。最近我在整理实验笔记时又翻到 TIME-MADEIRA 这个模型,全称是 Time-Series Forecasting via Time-SoftMoE,一个把块状稀疏注意力和时间维度上的软混合专家融合进同一个 Transformer 块的工作。名字里的 Madeira 是陈年加强葡萄酒,作者用这个名字想表达的意思,我理解下来大概是:时间序列的每一段局部,都值得被更细腻、更专业地加工。这篇文章我准备顺着这条线从头拆到底:这个模型到底在解决什么问题、两个核心组件各自的工作原理是什么、复现时有哪些关键实现细节,以及我在实际调参中踩过的坑。适合正在做长序列预测、想尝试在时序任务里引入 MoE、或者被传统 MoE 各种毛病折磨过的同学阅读。

1. 项目解读:TIME-MADEIRA 到底在解决什么问题

1.1 长序列预测绕不开的两个瓶颈

先说第一个瓶颈:注意力机制的计算量。标准自注意力中,长度为 n 的序列要计算 n×n 的注意力矩阵,复杂度是 O(n²)。在文本任务里,人们可以用稀疏注意力、线性注意力这些手段来缓解;但时间序列有自己的脾气——序列不是一堆无关 token 的集合,而是高度自相关的连续观测,盲目剪掉注意力边又会破坏长距离依赖。PatchTST 的思路是把连续观测切成若干 patch,用 patch 作为 token,一下子把 n 缩小了一个量级。但 patch 只是在输入端减少了 token 数,Transformer 内部每个 token 依然走同一套前馈网络,换句话说,模型对"凌晨 3 点的用电负荷"和"中午 12 点的用电负荷"用的是同一组参数来建模。

第二个瓶颈就藏在这里:时序数据天然具有局部模式差异。同一个预测任务里,不同时间段往往对应不同的形态——工作日早高峰、节假日波动、季节性周期、突发漂移。一个稠密 Transformer 只有一个共享的 FFN,它必须把所有模式都压缩进同一组权重里;而一个通常意义上的 MoE 又只在 token 级别做离散路由,选中的专家处理、未选中的 token 直接被丢掉。对 NLP 来说,丢掉一些冗余 token 可能还能接受;对时间序列来说,丢掉任何一个观测点都可能意味着丢掉一个拐点、一个异常峰值,甚至一个周期相位。这就是时序任务里直接套用传统 MoE 最容易翻车的地方。

所以在读 TIME-MADEIRA 的时候,我特别注意到它的两个设计取舍:一是把"稀疏"从 token 选择层面换到了路径层面,二是把 MoE 从特征维度搬到了时间维度。下面分开拆。

1.2 从硬 MoE 到软 MoE:时序场景为什么更难

传统 MoE(比如 Switch Transformer、GShard)在每层都放一个 router,它对每个 token 做 top-k 选择,k 一般取 1 或 2。这种硬路由有四个公认的问题:token 丢弃(没被选中的 token 不进入任何专家前馈,信息直接蒸发)、负载不均衡(router 容易把所有 token 都塞给同一个专家)、专家坍缩(少数专家学会偷懒,梯度集中在几个专家上)、训练不稳定(离散选择和后续层梯度之间缺乏平滑的映射)。

时序任务还会放大其中两个问题。第一,相邻 token 高度相关,top-k 硬路由经常把相邻的一段时间片断分配给同一个专家,表面上看负载均衡 loss 正常了,实际上专家成了"分段的二传手",根本没有学到有区分度的局部模式。第二,时间序列的分布不是静态的,训练集里工作日和周末的比例可能差异很大,硬路由会放大这种分布偏差,让某些专家在某个时间段内完全失活。TIME-MADEIRA 把 Soft MoE 引入时间维度,正是冲着这两点去的:路由不再做离散取舍,而是给每个 token 在所有专家槽位上分配一个连续权重,所有 token 的信息都以加权形式进入专家、再以同样的加权方式汇合回来。信息不会被丢弃,梯度也能平滑回传,负载由软分配天然摊开。

这一节的要点是:在做模型选型时,时序任务比 NLP 更需要"软"的 MoE 设计。如果只把 NLP 的 MoE 代码搬到时间序列上,大概率会在 20 步以内见到 loss 抖动和专家退化,而不是稳定下降。

2. 核心机制拆解:Time-SoftMoE 与块状稀疏注意力

2.1 软路由怎么做到既稀疏又稳定

Soft MoE 的核心是"槽位"(slot)。假设我们有 E 个专家,每个专家配置 s 个槽位,那么这一层总共有 E×s 个槽位。前向过程可以分成三步:第一步,对每个输入 token 学出一个长度为 E×s 的分配权重,用 softmax 在槽位维度归一;第二步,把每个槽位当作一个"容器",把所有 token 按权重加权求和到这个容器里,得到每个槽位的输入向量,注意这里是加权和而不是选 top-k,所以每个槽位都收到了全部 token 的信息,只是侧重不同;第三步,每个专家只处理属于自己那 s 个槽位的向量,专家的输出再按同样的分配权重加权回传给所有 token。

对比一下硬路由,软路由最大的变化在于没有离散选择,只有连续加权。你可以想象一个自助餐厅:传统 MoE 是窗口打饭,服务员只给你面前这一排选好的菜;Soft MoE 是每个槽位都像一口锅,所有食材按不同比例倒进每一口锅,每一口锅炖完后,又按原来的比例把味道分回每一个人的盘子里。因为整个过程完全可微,梯度可以顺着权重一路传回 router,训练时就不需要辅助负载均衡 loss 了,run 起来会省心不少。

在 TIME-MADEIRA 里,这个 Soft MoE 被特意作用在时间维度:token 序列被划分成若干块,路由权重是在块内甚至是块间学出来的分配关系。设计意图很清楚——让模型自动学会"哪些时间段的 token 内容,应该交给哪些更懂这种模式的专家处理"。专家容量不取决于输入序列长度,而取决于槽位总数,所以当槽位总数明显小于输入 token 数时,FFN 的计算量可以被显著压低,这也是它在参数增加的同时还能控制 FLOPs 的原因。

2.2 块状稀疏注意力:注意力开销从平方降到接近线性

光有软路由还不够,注意力开销依然是时序 Transformer 的大头。TIME-MADEIRA 给注意力做的处理叫块状稀疏注意力(Blockwise Sparse Attention)。做法不复杂:把长度为 n 的序列平均分成 B 个块,每块有 L 个 token,注意力不再允许每个 token 看全序列,而是只允许它看到自己所在块内的 token,外加少数几个全局 token(一般是可学习的全局向量或者序列开头的一个汇总 token)。

这样复杂度就变成了 B×L² 加上对全局 token 的 n×g,其中 g 是全局 token 数。拿一个具体数字感受一下:输入长度 1024,块长 32,全局 token 数取 2,注意力计算量大约是 32×32² + 1024×2 ≈ 34816 次关联计算;而全量自注意力要算 1024×1024 ≈ 1048576。差了约 30 倍。更关键的是,B×L² 本质上等于 n×L,当块长 L 固定、序列长度 n 变大时,计算量是线性增长的,这就是它可以平稳外推到更长序列的原因。

这里要特别澄清一个常见误解:块状稀疏注意力并不是把所有 token 锁死在局部视野里。因为每个块都还保留了对全局 token 的注意力,全局 token 相当于一个信息汇聚中心,序列的长期趋势、全局周期信息是通过这个中心在各块之间流动的。你可以把它理解成"每个小区都保有一个通向城市主干道的匝道",局部信息在小区内交换,全局信息通过匝道汇总。实际使用时,全局 token 的个数不需要多,1 到 4 个基本够了,太多反而会稀释局部注意力。

2.3 两个组件如何配合出"局部专家"

把两块拼到一起,才看得出 TIME-MADEIRA 真正的设计心法。注意力负责的是"信息视野":每个 token 该跟谁交换信息,是空间维度的关系建模;Soft MoE 负责的是"处理策略":这些信息该由哪组参数来加工,是能力维度的专业化分工。块状边界同时服务于两者——注意力在块内收敛,MoE 也以块为基本粒度去学习路由分配。

这个设计的直接收益是,模型参数量可以做得比较大,但实际计算量不会跟着线性膨胀。比如你可以把专家数从 4 加到 16,每个专家的 FFN 参数也成倍增加,但只要槽位总数固定,前馈计算量基本维持不变。传统稠密 Transformer 想做同样的事只能硬着头皮加大 hidden size,计算量和参数量一起涨。论文在 ETTh、ETTm、Electricity、Traffic 等常见基准的多数预测尺度上,用这样的结构比 PatchTST、TiDE 等基线拿到了更好的 MSE/MAE,本质上就是用更聪明的参数布局换精度,而不是单纯堆参数。

我在实际理解时喜欢用"科室分工"来类比:一般 Transformer 像是一个全科医生接诊所有病人;PatchTST 像是把病人先统一分诊到内科,但内科只有一位医生;TIME-MADEIRA 则是分诊之后,内科里还分了呼吸、心血管、神经几个亚专科,每个专科医生只看自己最擅长的病例,而分诊台(router)由模型自己动态学习,不动声色。

3. 从论文到复现:关键实现细节与超参经验

3.1 数据与任务设定:先把基准跑对

如果想验证 TIME-MADEIRA 的真实水平,第一步是把实验和论文对齐。常见的公开数据集有四个系列:ETT 系列(电力变压器温度,包含 ETTh1/ETTh2、ETTm1/ETTm2,分别按小时和 15 分钟采样)、Weather(气象观测站数据,10 分钟采样)、Electricity(321 个用户的用电负荷,15 分钟采样)、Traffic(道路占用率,小时采样)。绝大部分论文都会在这四类数据上做长序列预测,预测长度取 96、192、336、720,输入长度一般取 96 或者 336。

数据划分和归一化是两个最容易埋雷的地方。ETT 系列内部有官方划分,前 12 个月训练、4 个月验证、4 个月测试;其他数据集的常见做法也是按时间顺序切,一定不能随机打乱——时序数据一旦随机切,验证集就把未来信息泄漏进训练集了。归一化我建议按通道做独立统计:每个 channel 用自己的均值方差做标准化,预测完再反归一化回原始量纲;如果为了省事用全局统计,遇上通道之间量纲差异大的数据集(比如 Electricity 和 Traffic),误差会明显放大。

数据集采样频率特征通道数常见预测长度
ETTh1 / ETTh2小时级796 / 192 / 336 / 720
ETTm1 / ETTm215 分钟级796 / 192 / 336 / 720
Weather10 分钟级2196 / 192 / 336 / 720
Electricity15 分钟级32196 / 192 / 336 / 720
Traffic小时级86296 / 192 / 336 / 720

需要提醒一句:ETT 系列里面 ETTh 和 ETTm 虽然都只有 7 个通道,但采样频率差 4 倍,同样的块长在不同数据上对应的真实时间跨度完全不同,后面调 block size 时要格外小心。

3.2 TST 块实现的关键点

TIME-MADEIRA 的完整代码在官方仓库里可以找到,但如果你是自己复现,核心的 Soft MoE 前向其实可以浓缩成下面这段逻辑:

import torch import torch.nn.functional as F def soft_moe_forward(x, expert_ffn, slot_projector, expert_proj, num_experts, slots_per_expert): # x: (batch, n, d) B, n, d = x.shape total_slots = num_experts * slots_per_expert # 1. 为每个 token 生成分配到所有槽位的权重 dispatch_logits = slot_projector(x) # (B, n, total_slots) dispatch_weights = F.softmax(dispatch_logits, dim=-1) # 槽位维度归一,全局无丢弃 # 2. 把所有 token 按权重加权汇入每个槽位 slot_input = torch.einsum('bns,bsd->bsd', dispatch_weights, x) # 3. 每个专家只处理自己那 slots_per_expert 个槽位 slot_input = slot_input.view(B, num_experts, slots_per_expert, d) expert_out = torch.empty_like(slot_input) for e in range(num_experts): expert_out[:, e] = expert_ffn[e](slot_input[:, e]) # 每个专家一个小 FFN # 4. 输出按相同权重加权回每个 token expert_out = expert_out.view(B, total_slots, d) out = torch.einsum('bns,bsd->bnd', dispatch_weights, expert_out) return out

四行核心逻辑分别对应路由、汇聚、专家处理、回传。实际工程里 expert_ffn 通常是一个共享参数但分组的 FFN,可以一次性完成 E 个专家的前向计算,不需要循环;上面的写法只是为了把语义讲清楚。要注意的是 dispatch_weights 在 slot 维度做了 softmax,但如果总槽位很多,softmax 输入很大,数值上容易推不动,可以在 softmax 之前对 logits 做一次缩放(除以 sqrt(d) 之类),这是我从实际训练里学到的稳定化技巧。

块状稀疏注意力的实现更依赖框架习惯。我用的方案是:先把序列按块长 reshape 成 (B, num_blocks, block_len, d) 的形状,在块内做普通自注意力,同时把全局 token 的 KV 拼到块内注意力后面。PyTorch 自带 F.scaled_dot_product_attention 支持 mask,可以把它当成一个小型 meta-batch 去做;如果数据通道多,还可以考虑把通道维度并进 batch 维,实现 channel independence 的同时减少显存碎片。

3.3 训练配置与显存优化

复现时序 Transformer 的训练配置有一个通用起点,我测过几次基本都能收敛:优化器用 Adam,初始学习率 1e-4 到 5e-4,前 5 到 10 个 epoch 线性 warmup,后面用 cosine 或者阶段性下降;batch size 32 到 128 之间,看显存和数据集调整;训练轮数不用太多,早停在验证集上基本 30 到 60 epoch 就有结论了。MSE 作为主损失函数,评估指标用 MSE 和 MAE。

三个训练细节值得单独说。第一,混合精度建议优先用 bf16 而不是 fp16,因为路由权重和 softmax 对精度更敏感,fp16 在大专家数量下容易出现梯度溢出。第二,梯度累积节省不了实验时间但能稳定训练,如果你的 batch size 因为显存限制压得特别小,先用梯度累积把等效 batch 撑到 64 以上,再去看模型结构问题。第三,Soft MoE 层不强制加辅助负载均衡损失,如果你想加也可以加,但加了之后路由权重的熵会明显上升,反而可能抑制专家的专业化,我实测下来不如不加。

显存方面,最大的两个头是注意力矩阵和路由矩阵。块注意力把注意力矩阵从 O(n²) 降到了 O(n·L),这个头基本解决了;路由矩阵是 n×E×s,如果专家数到了 16 且输入长度 720,它也是一笔不小的开销。建议:槽位数先从 1 开始,验证集有提升再加到 2;全局 token 数量保持在 2 到 4;实在不够就开 gradient checkpointing,只对 TST 块做 checkpoint,其他层照常。

3.4 与 PatchTST 公平对比的注意事项

如果你是想跟 PatchTST 做对比或者复现论文表格,有两点容易被人忽略。第一是 patch 长度的对齐。PatchTST 的 patch size 通常取 16,而 MADEIRA 的块长本质是一个类似的概念,如果你用块长 32 的 MADEIRA 去比 patch size 16 的 PatchTST,比较的是两种 token 粒度,结论没意义。建议先在同一输入长度下扫一遍块长(16、24、32),各自取最优值再比较。第二是参数量的口径。MoE 模型的总参数量包含所有专家的参数,但每次前向只激活一部分,直接拿总参数量对比稠密模型会显得不公平,主流的做法是同时汇报参数量和 FLOPs,单看 FLOPs 更有实际价值。

最后,多 seed 平均。时序模型在不同随机种子下的结果波动比我预想的大,尤其路由初始化的位置对最终收敛影响不小。我一般跑 3 个 seed,报均值和方差,如果时间紧张,至少跑两个 seed 确认趋势不是随机种子带来的。

4. 常见问题与排查实录

4.1 效果反而不如 PatchTST?

我在复现 MADEIRA 的第一周就遇到过这个情况,块长 32、专家数 8、槽位 1,号称改进了所有基线,结果在 ETTh1 的 720 步预测上就是追不上 PatchTST。后来逐项排查,问题出在块长和数据周期的错配上。ETTh1 是小时级数据,日周期是 24 个点,周周期是 168 个点;我选的块长 32 既不能完整包含一个 24 点日周期,又和 168 不整除,路由学到的大多是一些破碎的边界。把块长改成 24 后,正好对齐一个自然日周期,效果立刻上来了。

第二个常见问题是专家数量设置不敏感。如果槽位总数远小于块数,路由退化成"每个专家平均分摊所有块",没有分工;如果槽位总数接近 token 数,Soft MoE 又退化成普通的稠密 FFN。我的经验是:先把槽位总数控制在块数的四分之一到一半这个量级,再看路由矩阵的熵是否处于中间状态——熵太大说明分工不明显,熵太小说明路由太自信,容易脆。

第三个原因在输入长度上。PatchTST 在短输入(96)时表现很好,因为 patch 天然做了局部压缩;MADEIRA 的块注意力需要更长的上下文才能体现优势。如果你在预测长度较大(336/720)但输入长度只有 96 的配置下测试,模型会有点吃亏,建议对比实验统一用输入长度 336。

4.2 训练不稳定或显存不够

训练过程中最容易遇到的是 loss 曲线突然跳到 NaN。优先查这三处:是否用了 fp16 混合精度(换成 bf16);路由 logits 是否没做缩放(加上类似 sqrt(d) 的缩放);batch size 是否过小导致梯度异常。还有一个不那么常见但真的会发生的:数据集里存在 NaN 或无穷值,特别是 Weather 这种传感器数据,预处理时最好先做一次缺失值检查。

显存不够的优化顺序我建议是:先减全局 token 数(从 4 降到 2)→ 再减槽位数(从 2 降到 1)→ 再减专家数(从 16 降到 8)→ 还不行才用梯度 checkpointing。为什么不建议一上来就开 checkpointing?因为它会显著拖慢训练,等于用时间换显存,排查问题时节奏会变得很慢。设置上还有一个容易被忽略的点:PyTorch 默认会为每个优化器参数保有 momentum 和二阶矩,MoE 层参数量大,优化器状态可能比模型本身还占显存,必要时对路由层用 Adafactor 或者 SGD 试试,虽然收敛速度会变,但显存明显缓解。

4.3 推理阶段的效率与部署心得

模型跑通之后,如果还要上线或者做大批量回测,推理效率是另一个重点。块状稀疏注意力在推理阶段的优势是 KV cache 小——每个 token 只需要缓存本块的 KV 和全局 token 的 KV,不需要缓存全序列;这在线性增长的序列长度下非常友好。实际部署时可以把每个专家 FFN 单独导出或量化,按输入动态加载,存储和内存压力都能摊薄。

一个小坑是批量推演时的 padding。序列长度不是块长的整数倍时,直接 reshape 会出问题,我的做法是在序列末尾做零填充,并把填充位置加到注意力 mask 里,防止填充 token 污染全局 token 的聚合。如果你要对比推理速度,务必统一 batch 大小、输入长度和输出长度,只改变模型结构,否则测出来的差异更多是框架层面的。

最后说一点我自己实验中的体会:TIME-MADEIRA 最值得借鉴的其实不是某一个组件,而是"把稀疏性从选择层面挪到权重层面"这个思路。我在很多任务里把硬路由换成软路由之后,即使模型结构没怎么改,训练的稳定性和最终精度都有改善,这个收益比单纯调专家数大得多。另外,块长与数据周期的对齐几乎是免费的增益,动手之前先去做一下周期分析,比闷头扫参数高效得多。如果你打算在自己数据上试,建议从块长 24、专家 8、槽位 1 这个配置出发,先跑通一个短预测长度的 baseline,再逐步加长预测距离。这个模型后续往多变量通道交互、非平稳序列分解这些方向扩展的空间还很大,值得持续关注。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询