MoE推理加速的FPGA实践:从稀疏激活到动态路由的硬件实现
发布时间:2026/9/6 2:15:39 作者:尧图编辑部 阅读量:1,286

1. 从稀疏激活到硬件实现MoE凭什么值得用FPGA跑聊MoEMixture of Experts混合专家模型之前我先说一个挺有意思的现象。这两年大模型参数越做越大从百亿到千亿再到万亿但真正落地的推理成本却卡住了很多人。大家第一反应是堆GPU结果发现显存翻了几倍算力利用率却上不去。这时候MoE架构被频繁提起原因很简单它把一个大模型拆成多个专家子网络每次推理只激活其中一小部分理论上能用更少的计算量换取更大的模型容量。但问题也出在这里。MoE的稀疏激活在软件层面看似省了算力落到硬件上却带来了全新的挑战。我手头有块中高端的FPGA开发板想做MoE推理加速的实验最初以为跟在GPU上写CUDA一样把矩阵乘和激活函数映射过去就完事了。真正动手之后才发现MoE的瓶颈根本不在计算单元而在路由和访存这两件被大多数人忽视的事情上。这篇博客我就围绕MoE模型的核心机制以及我在FPGA上实现MoE推理加速时的完整思路、踩过的坑和最后的方案取舍来写。内容不会停留在PPT层面的架构图而是会给出可复现的模块划分、资源估算方法和调试经验。如果你正好也在纠结FPGA到底适不适合跑MoE或者想搞清楚MoE在硬件上到底难在哪里这篇文章应该能给你一个比较完整的参考。先说清楚一个基础认知MoE并不是一个新概念早在2017年前后谷歌就提出了稀疏专家模型的思想但真正让它火起来是近两年大模型参数竞赛的结果。MoE的核心是条件计算——不是让所有参数都对输入起作用而是通过一个门控网络Router决定哪些专家参与计算。比如一个8专家、Top-2的MoE层每个token只激活2个专家算力开销和2倍专家数的稠密模型相当但模型总参数量是8倍。听起来很完美对吧但从硬件视角看这恰恰是噩梦的开始。你的计算单元得随时准备好处理来自任意专家的数据而数据往哪个专家送是由路由结果动态决定的。这种动态性、不规则性和FPGA擅长的流水线式固定计算完全是两个思路。接下来的内容我会从计算特征、系统架构、资源估算、排坑经验几个维度逐一拆解。2. 搞懂MoE的计算特征先说清楚它和普通Transformer差在哪2.1 稀疏激活带来的访存压力才是真正的拦路虎很多初次接触MoE硬件加速的人第一反应是参数多了计算量大了。实际上MoE层单个token的计算量反而比同规模稠密层小真正的麻烦是访存。我举个例子你就明白了。假设一个MoE层有8个专家每个专家是一个前馈网络FFN隐藏维度是4096。在稠密模型中一个token进来所有参数都要读一遍做矩阵乘但在MoE中如果只激活2个专家理论上只需要读取1/4的参数。听起来访存压力小了但问题在于你没发提前知道哪些专家会被激活。所以硬件设计上要么把全部专家参数都存在片上等着被读取要么得有一个足够快的动态加载机制。FPGA的片上存储BRAM/URAM通常就几十兆比特一个中等规模的专家FFN参数可能就有几十MB。你根本塞不下全部专家只能把参数放在DDR里。这就意味着每次路由决策之后得从DDR里把对应专家的权重搬到片上这个搬运延迟和带宽消耗往往比计算本身还大。我做了一个简单的带宽估算假设DDR4-2400理论带宽约19.2GB/s实际能跑到60%已经不错了大概12GB/s。如果专家权重总量是2GB就算每token只读1/4假设每秒处理1000个token需要的带宽是2GB×1000×1/4 500GB/s不对这里算错了。重新说一下2GB是全部专家参数总量每token读1/4即0.5GB每秒1000个token就是500GB/s的带宽需求DDR4完全扛不住。所以硬件实现MoE必须做权重重排和缓存优化否则性能会断崖式下跌。2.2 门控网络和动态路由FPGA最不擅长的不规则控制流Transformer里标准的FFN结构是线性的输入→升维→激活→降维→输出整个数据流是固定的非常适合FPGA做流水线。但MoE在FFN前面加了一个Router这个Router输出的是哪个专家被激活的概率分布然后通过Top-K选择确定具体走哪条路。Top-K选取本身并不复杂就是一个排序问题FPGA实现排序也不是难事。真正的麻烦在于选中的专家序号是运行时的动态值。这意味着数据通路的切换不能靠编译期确定必须靠运行时控制逻辑动态路由。FPGA里做动态路由常见方案有两种一是用交叉开关Crossbar把所有专家输入输出互联二是用共享总线或NoC片上网络。交叉开关面积大8专家互联就得64条通道共享总线简单但带宽有限。这是个典型的面积换灵活性的权衡。除此之外多专家并行度也是问题。如果8个专家同时工作那需要有8份独立的计算阵列或者复用一套阵列串行处理8次。前者面积爆炸后者会引入流水线气泡。我在实验中采用的折中方案是用2~4组计算阵列配合批量调度把不同token的专家请求混合起来处理尽量填满计算单元的流水线。这个思路后面会详细说。2.3 负载均衡问题硬件设计时最容易忽略的隐藏炸弹MoE训练时有个经典问题叫专家退化——少数专家被频繁激活其他专家几乎闲置。软件层面通过负载均衡损失函数来缓解但硬件层面这个不均衡会直接影响资源利用率和实时性。想象一下如果你的FPGA设计只支持同时处理4个不同专家的计算但某一批token全部路由到了同一个专家那计算阵列的利用率瞬间掉到25%。更糟的是如果某个专家被频繁请求它的参数会一直被缓存读取而其他专家的参数反复换入换出Cache Miss率上升延迟大幅增加。所以做硬件MoE加速不能只看平均吞吐还得看最坏情况延迟。我采用的策略是把专家分成热点专家和冷门专家两级调度热点专家权重常驻片上存储冷门专家从DDR按需加载。这个策略需要软件层配合做profiling确定哪些专家是热点的。虽然不能彻底解决负载均衡问题但至少能把最坏情况拉回可接受范围。3. FPGA实现MoE的系统架构我最终选定了这套方案3.1 顶层架构设计计算、路由、存储三权分立经过反复评估我最终把系统拆成四个大模块遵循路由决策、参数加载、矩阵计算三个环节的相对独立和流水化模块功能职责硬件资源Router Unit完成门控网络计算、Top-K选择、生成路由表DSP、BRAMExpert Loader根据路由表从DDR加载对应专家权重到片上缓存DDR控制器、URAMCompute Array完成专家FFN的矩阵乘和激活函数计算DSP阵列Output Combine把多个专家输出按权重加权求和输出最终结果DSP、BRAM这个架构的核心思想是让三个环节像流水线一样并行运转。Router处理第N1个token的同时Expert Loader在加载第N个token需要的权重Compute Array在算第N-1个token的矩阵乘。流水线能掩盖一部分DDR加载延迟但前提是每个环节的处理速度要匹配否则会出现某个环节成为瓶颈。3.2 路由模块的FPGA实现省掉Softmax的Top-K技巧Router的计算本质上是一个线性层加Softmax再加Top-K。Softmax在FPGA上是比较贵的操作涉及指数运算和除法。但我后来发现MoE推理时完全不需要算Softmax——因为Softmax是单调递增函数它不会改变元素的相对大小关系。Top-K选取的是最大的K个概率值直接对logits做Top-K和先Softmax再做Top-K选出的专家序号是一样的。这个优化能省掉一大块硬件资源。实际实现时logits通过一个矩阵向量乘得到然后进排序网络。如果专家数是8Top-2选取我直接用了一个8输入的比较器树两级比较就能找出前两名最大的logits。硬件开销很小延迟只有几个时钟周期。3.3 专家加载与缓存策略从DDR到片上存储的搬运工程这一块是FPGA实现MoE的核心难点也是我调试时间最长的地方。先说结论不要天真地按token粒度做权重加载一定要做Batch级别的权重复用。具体来说设Batch Size为32每个token经过Router都会选Top-2专家。在理想情况下32个token可能覆盖4~5个不同的专家每个专家权重被加载一次之后被多个token复用。这样DDR的加载流量就能除以复用因子。但问题在于如果32个token全都分散在8个专家上那每个专家只被复用几次加载效率依然不高。我采用的方案是两层缓存第一层是URAM容量约4MB放最热门的1~2个专家权重第二层是BRAM容量约1MB放其他专家的临时权重采用LRU替换策略。实测下来在路由分布比较均匀的情况下URAM命中率大概能到60%~70%配合流水线预取DDR带宽压力能缓解不少。3.4 计算阵列设计矩阵乘的精度与并行度取舍专家FFN的计算核心是GEMM通用矩阵乘法。FPGA上做GEMM有几种映射方式脉动阵列、数据流阵列、CPU-like多核阵列。考虑到FPGA的逻辑资源有限我选了8×8的脉动阵列数据位宽是INT8。这里要补充一个关键决策为什么用INT8而不是FP16一方面INT8的DSP资源占用是FP16的一半同样的DSP数量能塞下两倍的MAC单元另一方面MoE推理对精度敏感度其实不算特别高INT8量化加上per-token的激活缩放精度损失在可接受范围内。当然如果你做的是科研场景对精度要求极高可以考虑FP16但资源开销要提前核算清楚。脉动阵列的关键在于数据复用。实现时我把权重固定驻留在DSP旁边的寄存器文件中输入激活按行流入阵列乘累加结果按列流出。一个8×8阵列每个时钟周期能完成64次MAC运算。假设FPGA时钟跑200MHz单阵列峰值是12.8GMAC/s两套阵列就是25.6GMAC/s。这个算力跑大模型肯定不够看但对于中等规模的MoE层验证和特定场景比如边缘端推理是够用的。4. 资源估算与选型手里的FPGA到底跑不跑得动MoE4.1 资源消耗速查表先算账再动手很多人拿到一块FPGA开发板就想直接开干结果综合布局布线的时候才发现资源不够用。我建议动手之前先按下面的维度做个粗算资源项消耗来源8专家MoE层估算DSP矩阵乘阵列、Router线性层约120~200个DSP48EBRAM权重缓存、中间结果缓存约200~300个36Kb BRAMURAM热门专家权重驻留约32~64个URAM4MB~8MBLUT控制逻辑、路由表、Top-K约50k~100k LUT外部带宽DDR权重加载视专家规模和Batch策略而定以Xilinx 7系列中端的XC7Z045为例它大概有900个DSP、19.2Mb Block RAM、资源算中等偏上。按上面的估算跑一个8专家的MoE层资源占用率大概在40%~60%加上控制逻辑和外设整体占用会在70%左右属于能跑但扩展性有限的状态。如果换成高端的UltraScale系列比如VU9P资源就宽裕多了——有超过6800个DSP理论上可以支持更大的专家并行度和更高精度的计算。但说实话UltraScale的板卡价格和开发复杂度也不是一个量级前期验证用中端板卡就够了。4.2 性能瓶颈预判为什么DDR带宽决定一切我做完资源估算后其实对计算资源还挺乐观的但真正让我觉得这事没那么简单的是带宽核算。来算一笔账假设MoE层有8个专家每个专家的FFN权重矩阵是4096×4096FP16精度单专家权重约33.5MB。32个token的Batch每个token激活2个专家最坏情况下32个token对应8个不同的专家需要加载8×33.5MB 268MB权重。DDR4-2400实际可用带宽约10GB/s光是加载这些权重就需要27ms。而计算32个token的矩阵乘以25GMAC/s算大约需要215ms的纯计算时间8个专家×4096×4096×2×32次MAC。等等这个计算量算出来带宽反而不是瓶颈重新算一下。按我的配置8个专家每个专家FFN的MAC次数是4096×4096×2升维降维33.5M MAC per token。32个token、每token激活2个专家总MAC就是33.5M×32×2 2.14G MAC。以25GMAC/s计算约86ms。权重加载268MBDDR带宽10GB/s约27ms。所以计算时间86ms 加载时间27ms看起来计算才是瓶颈。但这是最理想情况。如果Batch Size缩小到8token都集中在2~3个专家上权重加载量骤降到100MB以内10ms计算时间变成21ms计算瓶颈依然不变。这下结论就比较清晰了在这个配置下计算阵列的规模反而是决定性能的关键DDR带宽还没到极限。但如果你用更小的FPGA或者更大的模型情况就会反过来。所以做设计时不能拍脑袋一定要把两层算力、带宽、容量的实际数值代入看看。4.3 设计空间探索什么情况下FPGA比GPU更有优势谈到FPGA vs GPU很多人会直接纠结算力数字。但我的体会是FPGA做MoE推理的真正优势不在峰值算力而在于三点低延迟和确定性FPGA没有GPU那样复杂的调度和显存管理数据路径是硬连线的延迟抖动小。对于实时控制、低延迟推理场景FPGA的优势非常明显。灵活的精度配置FPGA上可以混合使用INT4、INT8、FP16不同层用不同精度资源利用率更高。GPU虽然也支持多种精度但切换和优化空间没有FPGA灵活。IO和接口适配FPGA可以直连传感器、网络接口、自定义协议设备不需要像GPU那样通过PCIe和CPU来回搬运数据。当然如果论大规模并行吞吐能力FPGA目前还是追不上高端GPU。但MoE这种稀疏动态路由的负载恰恰放大了GPU在数据搬运和动态调度上的开销FPGA的定制化数据通路反而更适合做Token级流水线优化。这也是我觉得FPGA跑MoE值得持续投入研究的核心原因。5. 踩坑实录从硬件调试到系统集成的五个大坑5.1 坑一Top-K排序模块在批量场景下出现了路由震荡第一个坑是在单token测试没问题一上批量就翻车。单token时Top-K选择是按logits大小排出来的没问题。但批量模式下我为了提升吞吐把Router模块做成了每个时钟周期输入一个token的流水线模式。结果发现连续输入的token会触发Top-K模块里比较器树的输入仲裁问题——同一组专家索引寄存器被多个流水级同时写入导致路由结果偶尔错乱。排查过程用了两三天。先是仿真能看到但不确定是哪一级的问题。后来在综合后的网表仿真中定位到是Top-K模块里的缓存寄存器没有做流水级隔离。修复方案很简单在每个流水级后面加一组valid-ready握手信号确保上一级的输出被下一级锁存后再释放寄存器。这个改动让Router模块的时序收敛从300MHz降到250MHz但路由稳定性彻底解决了。5.2 坑二DDR读取带宽和计算流水线没对齐导致大量空泡这个坑其实是我自己设计上的疏忽。Expert Loader模块在加载权重到BRAM时用的是突发读取模式一次读256bit。但计算阵列的脉动单元是按8×8的小矩阵块来消费数据的每次需要读16个连续激活值也就是128bit。结果就是加载模块读出来的数据计算阵列一次用不完得缓存等第二三个Block读完才能凑齐输入流水线大量空泡。后来我把读取粒度改成512bit并加了异步FIFO做缓冲把来自DDR的数据先攒成计算阵列友好的格式。这个改动让有效计算效率从40%提升到了75%左右。经验就是数据DDR读取的突发粒度尽量取计算单元输入位宽的整数倍比如我们是128bit输入就选512bit的突发这样最省事。5.3 坑三INT8量化后精度崩了问题出在激活缩放上INT8量化最大的坑就是激活的分布范围。MoE里不同专家的激活值分布差异很大有的专家输出范围在[-6, 6]有的在[-1, 1]。如果用全局统一的缩放因子小范围的专家输出直接被量化噪声淹没精度瞬间崩掉。我最终采用的方式是per-expert的激活缩放per-expert activation scaling。就是在每个专家模块的入口和出口各加一个可配置的移位器根据该专家的激活统计动态调整缩放系数。实现起来不复杂就是在权重加载时顺便加载一组缩放参数每专家2个16bit寄存器。这个方案在精度恢复上效果显著INT8推理的精度损失从原来的2%降到0.4%以内基本可用。5.4 坑四多个计算阵列的负载不均衡一个阵列忙死一个闲死前面提到我用了两套8×8脉动阵列来提升吞吐。实际运行中我发现如果不做任务分配很容易出现一个阵列在疯狂计算另一个阵列闲置的状态。原因是专家的计算量不一热点专家的计算请求排成队冷门专家的请求寥寥无几。这个问题靠硬件调度解决不了因为调度器不知道每个专家接下来的负载情况。我的做法是加了一个软件层把路由统计信息每隔一段时间反馈给上位机然后由上位机动态调整任务划分策略。比如把热点专家优先分配到阵列A冷门专家分配到阵列B保持两个阵列的队列长度大致均衡。这个软硬件协同调度的思路虽然增加了一些延迟但整体利用率提升很明显。5.5 坑五综合工具的资源利用率优化策略直接影响时序收敛最后聊一个工具层面的坑。同样的Verilog代码工程实践差的工程师写出来时序就是收敛不了。我在优化过程中发现几个很实用的小技巧给乘法器显式指定DSP实现在Vivado里用综合属性(* use_dsp yes *)避免工具把乘法器推断成LUT逻辑。矩阵乘阵列的寄存器打拍脉动阵列里每个PE处理单元的计算路径很容易成为关键路径要在每个PE的输入输出都加寄存器代价是多一到两个周期的流水线延迟但频率能稳定提升。BRAM的数据位宽尽量用满一个36Kb的BRAM可以配置成18K×18或36K×8等如果只用到16bit最好两个16bit拼成32bit再存提升存储效率。6. 还没完MoE在FPGA上还能往哪个方向深入6.1 Batch Size与实时性的权衡小Batch场景更有潜力从我做实验的体会看FPGA跑MoE不适合追大吞吐更适合做小Batch低延迟的场景。比如Batch Size在1~8之间延迟控制在亚毫秒级别用于在线推荐、实时控制这类场景。这种负载下FPGA的确定性延迟优势很明显GPU反而会因为调度开销和显存分配导致抖动。如果要做这种优化推荐把Router的Top-K选择和Expert Loader的预取逻辑结合起来提前一个token做路由决策这样真正计算时权重已经在BRAM里备好了延迟能进一步压缩。6.2 多专家并行架构的进一步探索从Crossbar走向NoC8个专家以内的系统Crossbar方案还能接受。但如果模型规模扩大专家数到32甚至128Crossbar的互联复杂度会爆炸。一个思路是采用简单的2D Mesh NoC把Router和计算单元都挂在网络节点上数据包根据路由表动态转发。我在实验里用Xilinx的FPGA搭过一个4×4 Mesh的雏形每个节点是一个小路由器加一个可配置的计算单元。效果是灵活性大幅提升但资源开销也确实可观。如果你打算做这个方向建议先用仿真评估路由包数量再决定NoC的缓冲深度否则BRAM会被缓存耗尽。6.3 和量化感知训练结合从硬件角度反推软件优化最后一个想分享的想法是FPGA做MoE不能只等着别人训练好的模型。如果能在训练阶段就引入硬件约束比如专家权重的结构化稀疏、量化友好的激活分布FPGA侧的实现会轻松很多。我现在在尝试和训练团队配合在MoE训练时对专家矩阵做额外的结构化剪枝让剪枝后的权重矩阵能在FPGA上按块跳过计算。比如按8×16的块做剪枝FPGA侧就能通过一个位掩码直接跳过对应的MAC操作效果比后训练剪枝好很多精度损失也小。这个方向我觉得是未来MoE FPGA落地的关键——软硬协同设计而不是软了之后硬去适配。如果能把训练阶段和推理硬件拉到同一张设计图纸上很多现在让我头疼的问题可能根本不会出现。说到底FPGA做MoE这条路现在还处于早期探索阶段没有一套成熟好用的工具链大量工作得靠手工Verilog和脚本完成。但正因为如此先动手趟一遍的人积累的经验才更有价值。如果你也在搞类似的方向欢迎在评论区交流资源和调度的具体方案踩坑心得互换一下能少走不少弯路。