news 2026/9/8 19:37:28

FPGA加速混合专家模型推理:稀疏路由与硬件设计实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FPGA加速混合专家模型推理:稀疏路由与硬件设计实战

前阵子和朋友聊一个边缘推理项目,对方甩过来一个混合专家模型的规格书,说总参数规模挺吓人,但单token实际跑起来,计算量反而比同体量稠密模型小。我当时第一反应是:这不正好给FPGA留了口子?后来我们真把MoE模型在FPGA上这条路线从头到尾捋了一遍,从门控网络的路由逻辑,到专家阵列的资源分布,再到DDR带宽和量化策略,逐项核对下来,发现里面能做的文章确实不少,但坑也比想象中深。

这篇文章不重复MoE模型的算法推导,也不打算直接丢一份能综合的代码,而是从硬件设计的视角拆解几个核心问题:稀疏路由到底改变了什么?FPGA在MoE推理里哪个位置介入最划算?路由器和专家计算单元应该怎么搭?以及上板前后最容易踩哪些坑。适合两类人看:一类是熟悉FPGA但想了解MoE的工程师,另一类是在做推理加速选型、想搞清楚FPGA与GPU边界的产品研发。

1. 稀疏路由改变的不只是参数量,还有硬件资源形态

1.1 MoE的“省计算”到底省在哪

常规Transformer的某一层里,FFN做的事情可以简化成两条乘加链:输入x先乘一个升维矩阵W_up,中间维度膨胀几倍,过激活函数,再乘一个降维矩阵W_down回到原始维度。MoE做的事情其实很朴素——把这一套FFN复制成E份,每份叫一个“专家”,然后用一个门控网络给每个token挑最合适的Top-K个专家来算。

这里有两件事要分开看。第一是参数量:从1个FFN变成E个FFN,模型文件肯定变大,总参数接近线性放大。第二是计算量:每个token每层只激活K个专家,所以实际发生的矩阵乘法和E没有直接关系,只跟K挂钩。这就导致一种“壳大内小”的形态——参数看着像几十B的大模型,真正跑一次前向时活跃权重可能只有十几分之一。

拿具体数字说话。假设一个专家模块的输入维度d=2048,中间维度h=8192。那么单个专家的升维和降维两个矩阵,参数总量是2048乘以8192再乘以2,约33.5M个参数,FP16存储下约67MB。如果这一层有16个专家,那这一层专家权重加起来就超过1GB。但是每个token只访问2个专家,活跃参数只有约128MB。模型的“纸面规模”和“实际活跃计算”之间差了一个数量级,这个差异是后面所有硬件设计决策的出发点。

1.2 负载均衡:算法里的一项正则,硬件里的一片雷区

MoE训练会额外加一个辅助损失,用来压制专家被选择的次数偏离期望值太远。原因很直接:如果某一个专家被80%的token选中,其他专家基本就荒废了,MoE退化成了一个小规模的稠密模型,那还不如不做。

但训练时负载均衡收敛,不代表部署时就能高枕无忧。真实线上推理的prompt分布是长尾的,某些语义簇会集中命中某几个专家,导致偶发性的分配偏斜。这个偏斜在GPU上还不算致命,因为SM数量多,某个专家任务少时SM可以切去跑别的kernel,硬件利用率不会掉得太难看。但在FPGA上,如果预先把DSP和BRAM按专家静态划分,那么一个专家队列挤爆、另一个专家空闲的情况会直接表现为DSP阵列利用率腰斩,流水线也会因为队头阻塞而卡顿。这个问题在本文第4节会专门谈硬件上的缓解办法,这里先提个结论:设计MoE的FPGA加速架构,不要用“每个专家独占一组计算单元”的思路,而要保留计算资源在专家之间动态调配的能力。

1.3 GPU上MoE的别扭,恰好是FPGA的突破口

GPU做MoE推理时,很多人会发现一个反直觉的现象:模型计算量下降了,推理速度却不升反降,或者提升远达不到预期。原因出在显存带宽。GPU的SM执行矩阵乘之前,必须先把权重从HBM搬到片上,不管这个专家最终被多少个token选中,权重搬运的开销都是实打实的。MoE把计算量降下来了,但显存流量没有同比例下降,于是大量时间其实花在等待权重从HBM搬运到SM的路上。

FPGA的切入点恰恰在这里。FPGA可以把最常被访问的几个专家权重常驻在片上BRAM/URAM里,让这些专家的权重读取带宽从DDR的几十GB/s提升到片上存储的TB级;不活跃的专家权重则继续放在DDR里,根本不占片上资源。再加上片内数据通路可以用AXI-Stream做点对点广播,比GPU的通用互连更直接。在小批量、低延迟的场景下,这种特性是有实际意义的。批量一大,GPU的算术强度优势会重新压过FPGA,所以这从来不是一个全场景替代的命题,而是“在哪个区间FPGA更划算”的问题。

2. FPGA和GPU的真正分水岭:算术强度、通信开销和能耗

2.1 先算算“搬权重”和“算乘法”的比例

我经常让团队在动工之前先填一张表,把每层要搬多少权重、要算多少MAC列出来。以单个token为例,d=2048、h=8192、8个专家、Top-2的情况下,这一层MoE需要读取2个专家的权重,也就是128MB左右。假设DDR4四通道理论带宽按76.8GB/s算,但实际应用中能到60%已经算优化得不错,大约46GB/s。搬128MB需要接近2.8ms。而计算这2个专家对单token的FFN,大约只有64M次MACs,哪怕FPGA算力只有2T MACs,也就是几十微秒级别。权重的搬运时间是计算时间的几十倍。

这个账一算,结论就很清楚:在单token、小batch的推理场景下,MoE在FPGA上同样是访存受限,而不是算力受限。那FPGA为什么还有戏?因为它可以把一部分专家权重放到片上URAM里,把带宽从DDR的几十GB/s提升到接近片上总线的TB/s。但片上容量有限,不可能塞下所有专家。所以MoE在FPGA上的设计本质,就是“专家权重热替换”的调度问题——哪些专家常驻片上,哪些专家留在DDR,以及什么时候预取,这决定了最终吞吐。

2.2 All-to-All通信:多机部署的隐性杀手

MoE在训练阶段有一个著名的通信瓶颈——All-to-All。因为每个token路由到的专家不固定,如果专家分散在多块计算设备上,token特征需要跨设备频繁搬运。推理端如果做多卡或多FPGA部署,这个问题同样躲不掉。

一个容易低估的细节是:跨板卡传输的不是一两个数字,而是高维特征向量。d=2048维的FP16向量,一个token就是4KB,一个batch 64个token就是256KB。如果某些token被路由到远端专家,每层通信量累积起来相当可观。很多团队最后选择妥协,在推理时限制路由只选本地专家,宁可损失一点模型表现,也不让跨板通信成为延迟黑洞。这个取舍在FPGA方案里尤其常见,因为板间通用互联的带宽通常不如GPU集群的NVLink。

2.3 功耗指标在实际部署里的分量

功耗这个点很多人一听就觉得是老生常谈,但真正影响选型的往往不是芯片TDP数字本身,而是整机改造费用。一块主流FPGA加速卡整板功耗在75到100W,一块面向推理的GPU动辄300W往上。在车载、电力隧道、户外机柜这类散热条件受限的现场,电源和散热改造的预算可能超过板卡本身。

我并不是说FPGA一定能取代GPU,而是说如果项目对单点功耗有硬指标,对延迟抖动有确定性要求,FPGA会在方案评估表上获得一个非常靠前的位置。MoE模型因为计算量相对小、参数体量大,正好放大了FPGA低功耗优势的价值。

3. 整体架构先落纸面:路由、专家执行、量化与数据流

3.1 把推理链路切成四个子模块

不管最终用哪家FPGA,MoE推理的顶层功能划分都建议切成四块:路由决策、数据重排、专家计算、输出聚合。路由决策模块接收当前层输入的特征向量,通过门控网络计算logits并选出Top-K专家编号,同时产生融合权重。数据重排模块根据路由结果把属于同一专家的token特征归拢到同一队列。专家计算模块承载若干组矩阵乘,每个专家在自己的数据上执行FFN。最后输出聚合模块把同一个token被多个专家算出的结果按概率权重加权.

Attention层在MoE层之前或之后,可以由同一个推理引擎复用,只是attention本身不涉及稀疏路由,复杂度低一个档次。四块模块之间是典型的流水关系:路由决策下一拍紧接着数据重排,数据重排结束专家计算开始,专家计算的输出又回到下一层路由决策。

3.2 分桶调度:比逐token流式处理更实用

一个新手容易犯的错是把token一个接一个送去专家计算单元,结果每个专家每次只处理一两个token,矩阵乘的并行度完全提不起来。更通用的做法是分桶调度。取一个时间窗口内的若干个token(比如64或128),先统计路由结果,按专家编号分桶,然后把同一批token一起送进对应的专家分组。

分桶的代价是路由缓冲必须有足够深度,但这个代价换来的是DSP阵列利用率的大幅提升。比如8个专家、4组计算簇,每个簇拿到一批token后可以按矩阵乘的batch维度充分展开,算力利用率和逐token方式完全不在一个量级。这个思路和算法侧说的“专家并行+token重排”是一回事,放在FPGA上就叫“静态调度基础上的动态分桶”。

3.3 量化策略:专家单独校准,门控保留高精度

MoE模型的量化比稠密模型要更谨慎。不同专家学到的特征分布差异较大,有些专家对数值精度异常敏感,直接用同一个scale量化全模型,掉点会很严重。更合理的做法是per-expert量化:每个专家单独统计激活和权重的min/max,按per-channel算scale,把精度损失压到最低。

门控网络本身占用的存储极少,但它的计算精度建议保留FP16甚至FP32。原因很简单:Top-K选路是排序问题,两个logits差0.01,排序结果可能就完全不同。选错专家的代价远大于专家内部计算时量化带来的微小噪声。这个不对称性决定了量化方案必须是“专家计算使劲压位宽,路由选择保守留浮点”。

3.4 三级存储体系:权重热替换的基石

权重在FPGA上的存放,我建议设计成三级存储。L0紧贴专家计算单元,用一组小容量的BRAM/URAM存放当前正在计算的1到2个专家权重,并配合双缓冲预取下一个专家。L1是片上较大的URAM池,存放4到8个最常被访问的专家。L2就是DDR或HBM,存放全部专家权重。

三级之间带宽递减、容量递增。设计核心问题变成:什么时候把专家权重从L2提升到L1,什么时候从L1换入L0,以及预取深度多少。这个策略和CPU的cache替换很像,但FPGA上你可以完全按模型的结构定制替换规则,不需要通用的LRU算法留下的不确定性。

4. 路由决策模块:门控网络、Top-K选路和负载均衡的电路设计

4.1 门控网络的计算结构

路由决策在FPGA上并不复杂。门控权重矩阵的尺寸是d乘以E,每个周期送入一个d维向量,就能并行得到E个logits。因为E一般只有几十,完全不需要上脉动阵列,用E个并行的点积单元就行。

比较关键的一点是softmax的处理。推理时,最终输出需要对Top-K专家的结果按softmax概率加权,所以logits选择索引只是第一步,还得计算选中专家的概率。这里有两种做法。第一种是对全部E个logits做完整softmax,再取Top-K,精度最高但平白算了大量用不到的概率。第二种是先排序拿到Top-K索引,只对被选中的K个logits做exp和归一化。FPGA上我倾向第二种,因为节省的那部分exp电路面积和LUT资源,在E很大时是很可观的。

4.2 Top-K比较器:状态机循环还是二叉树

Top-K选路的硬件实现有两条典型路径。一条是状态机循环做E次比较,每次抠出当前最大值,面积很小但时延随E线性增长。另一条是二叉比较树,第一级E/2个比较器,第二级E/4个,log2(E)级就能完成,代价是组合逻辑明显变大。

实际选型取决于时序预算。我在一个E=16、门控logits读取时延占总周期比例较大的项目里用了三级比较树,综合后LUT占用不到400,关键路径反而落在后端的数据选择mux上。如果你在FPGA上做的是高并行度版本,建议先做二叉树的面积评估,大多数情况下多出的LUT开销比它省下的时延更值得。

4.3 路由表与指针搬移:别把token数据来回拷

路由结果确定之后,最直接的做法是把token特征字节拷贝到对应专家的输入FIFO。但这个做法有个隐藏代价:拷贝本身消耗BRAM写带宽,专家计算前又从FIFO读一遍,等于同一份数据被读写两次。

更好的方案是维护一张路由表,只记录token_id、专家编号和概率权重。每个专家队列里存放的是指向共享缓冲区的指针,而不是特征数据本身。真正进入专家计算单元前,再由交叉开关按指针从共享缓冲读出向量。虽然最终还是要读一次,但避免了“先拷贝进专家队列再读出来”的双倍搬运。这个细节点很多开源实现不会讲,但对BRAM带宽紧张的FPGA设计来说,差异非常明显。

4.4 在线负载均衡的几个可落地的机制

算法训练阶段的辅助损失管不到部署时的动态分布,所以FPGA上必须自己做在线负载均衡。我试过几种手段,效果比较直接的有三个。

第一是概率扰动。在logits上加入固定种子的随机噪声,让一部分本来会扎堆的token分散到次优专家,代价是模型表现轻微波动。第二是次优专家回填。当某个专家队列深度超过阈值,就把溢出的token放进当前负载最小的专家队列,并分配一个很小的权重。第三是动态重配计算簇。利用FPGA的可重构特性,在层间调整DSP簇的划分,把“4+4”改成“3+5”,适应下一层可能出现的偏斜。

这些机制写起来简单,但每一个都需要在仿真里把队列深度、计算簇延迟和路由质量三者同时建模,才能找到合适的阈值。我个人的体会是硬件监控模块越早放进设计越好,别等上板之后才发现负载均衡没做观测点。

5. 专家计算单元:脉动阵列、共享DSP与激活函数近似

5.1 把链式GEMM拆给多个小阵列

专家FFN的本质是升维、激活、降维三条链式乘加。FPGA上稳妥的做法不是做一个超大矩阵乘阵列,而是把链路切碎。先做x乘以W_up,得到中间张量,过激活函数,再乘以W_down。中间张量的尺寸是batch乘以h,h可能高达8192甚至14336,如果这个中间量搬回DDR,带宽压力会非常夸张。实际工程里,我会把激活函数的输出用片内BRAM/URAM缓存,直接作为下一级矩阵乘的输入,避免外部存储往返。

5.2 二维脉动阵列和一维点积单元的取舍

很多人一提到矩阵乘加速就想到二维脉动阵列,但专家FFN这个场景有其特殊性。专家矩阵的形状固定,d乘以h,权重矩阵每一列与输入特征做点积,天然可以展开成h个并行的一维点积单元。这个结构实现简单、时序友好,不用处理二维阵列那样复杂的权重加载和部分和传递。

二维脉动阵列在d和h都非常大的稠密GEMM里优势明显,但在中型FPGA上布一个大型二维阵列,资源占用和布线复杂度都会急剧上升。我的建议是V1版本先用一维多通道点积阵列跑通,板子上验证完性能和瓶颈之后,再考虑要不要往二维方向优化。先把正确性打通,再谈极致吞吐。

5.3 DSP资源的估算逻辑

DSP资源是专家计算单元的命门。以Kintex UltraScale+系列为参考,DSP48E2数量大约在7000个量级,单板INT8乘累加能力能做到2到5TOPS,取决于具体型号和时钟。一个专家FFN(升维+降维)对单个token大约是33.6M MACs,对64个token的batch大约是2.1G MACs。按2T MACs算,计算部分大约耗时1毫秒,和之前6.1里算的访存开销一对比,谁才是瓶颈非常清楚。

所以做资源评估时,先估算DSP数量能不能支撑目标batch下的计算时延,再看URAM容量能不能装下中间张量和常驻专家,最后才轮到LUT。如果DSP占用超过70%,优先砍并行度,而不是加资源。

5.4 激活函数:查表、多项式还是分段线性

GELU和SiLU这类激活函数在FPGA上有几种实现路径。最省事的是查表:输入分布通常可预估,预先做一个256项、每项16bit的表,用定点数高8位做索引。这个精度损失经INT8推理验证,几乎不可感知。

SiLU比GELU更好算,它等于x乘以sigmoid(x),可以直接做分段线性近似,误差在1e-3以下。SwiGLU变体则更进一步,把W_up拆成两个子矩阵,分别生成x_w和x_g,再逐元素相乘。FPGA实现的收益在于两个子矩阵可以在同一组DSP上分时复用,中间结果各占一块BRAM,再在LUT里做逐元素乘法,省掉了一次外部存储往返。

6. 访存预取与层间调度,上板前先算清带宽账

6.1 带宽账不能凭感觉估算

我在第2.1节算过单token的访存开销,这里再把batch放大到64看一次。假设一个专家权重67MB,Top-2、64个token、去重后实际选中12个左右专家,一次MoE层需要从DDR读取的权重约为800MB。即便DDR4四通道跑到76.8GB/s的理论带宽,也需要10毫秒以上。而64个token的专家计算量约2.1G MACs,按5T MACs算只需要0.4毫秒左右。访存比计算慢了二十几倍。

这个比例说明,想提高MoE在FPGA上的真实吞吐,重点全部落在“提高权重复用”上。分桶调度让更多token共享同一个专家的权重加载结果,就是在和这个不等式搏斗。

6.2 预取引擎:掩盖DDR延迟的方法

FPGA上DDR读请求发出后,返回数据的延迟通常有上百个周期,如果不做预取,计算单元只能干等。我给每个专家簇配一个预取引擎,在当前专家还在计算的时候,已经按AXI突发读的方式把下一个专家的权重发出去,放到双缓冲的另一半。

预取深度不是越大越好。深度小了盖不住延迟,深度大了占用的大量URAM会挤压其他模块的存储资源。我习惯取半拍覆盖延迟所需字节数的1.5倍,实现最小时延加适度冗余。上板后用ILA观察DDR的outstanding请求数和URAM占用率,再迭代调整。

6.3 KV缓存与token重排怎么共存

MoE层本身不产生新的KV缓存,但token经过路由重排后,所属序列的索引位置可能被打乱。自回归生成时,下一步还要根据当前token找到同一个序列的KV。如果数据被搬走,再回找就麻烦了。

简单方案是为每个序列固定一个槽位,token重排只改指针,不搬KV数据。并行解码时KV缓存的存储量要按序列乘以层数预先分配,同时要留意同一序列的token被路由到不同计算簇后,KV写回可能产生的总线冲突。这个问题的严重性到不了阻塞架构的程度,但设计时尽早把指针方案定下来,能省下后期大量调试时间。

6.4 跨FPGA连接:PCIe还是光口

如果单颗FPGA资源不够放所有专家,就要做多FPGA方案。PCIe连接的优点是生态成熟、DMA链路方便,但带宽通常受限于主机侧,实时性一般。光口点对点延迟更低,带宽取决于SFP+或QSFP配置,适合对时延敏感的推理场景。

我踩过的坑是运行时才去配置网络拓扑,结果带来延迟的不可控。建议板级设计最开始时就把拓扑固定,明确哪一片FPGA负责哪一组专家,哪条链路上传输哪类数据,反而比等到运行时动态调整更稳妥。

7. 上板调试经验与资源利用率的调整笔记

7.1 一次综合后的资源参考

以一个E=8、d=2048、h=8192、Top-2的配置为参考,路由决策模块大约消耗3000个LUT、4000个FF和少量BRAM;数据重排与路由缓冲大约消耗8000个LUT、10000个FF和8到16块URAM;专家计算单元如果做4个计算簇,每簇8个并行点积单元,DSP消耗可能在2000到3000之间,LUT消耗在6万左右;输出聚合模块是轻量级的。

这些数字不是绝对准确,但可以给你一个直觉——资源和逻辑的分布比例。如果综合出来LUT占用超过70%,不要急着改代码,先用并行度换资源,把DSP阵列的并行度降下来,通常比在逻辑里抠面积更有效。

7.2 时序收敛最容易卡在数据重排的mux上

经验上,FPGA实现MoE最麻烦的关键路径往往不是DSP乘累加,而是数据重排的交叉开关选择器。路由决策在T周期产生结果,数据在T+1周期就要通过Xbar选通,而Xbar的mux逻辑会随着端口数平方增长,组合延迟很容易超。

解决办法有两个:一是提前一拍保存路由结果,让Xbar的选择信号与数据错开;二是把数据缓冲的读出地址提前计算,用双口RAM的读端口消除data-dependent延迟。我在实际项目里做了这两个改动后,Fmax从130MHz提高到了190MHz以上,属于投入小回报大的典型。

7.3 上板后必须监控的三个指标

性能仿真和板子实测之间总有差距,我上板后的第一件事是接轻量级统计模块。重点看三个指标:每个专家队列在当前batch收到多少token、每个专家计算单元的利用率、DDR带宽有多少时间打在打满状态。这三个指标能直接定位问题是出在路由偏斜、计算空闲还是访存排队。

有些问题在验证集上看不出,上线用户流量一变才暴露。比如真实流量的前缀命中后,某个专家队列可能持续拥塞,产生偶发的高延迟。这种问题靠指标监控比反复调路由阈值更容易定位和处理。

7.4 建议的开发顺序

如果现在让我从零开始做一个MoE的FPGA加速卡,我不会按算法论文的顺序走,而是先用C或SystemC搭一个功能模型,把路由分桶、量化逻辑跑通。接着用HLS做路由模块原型,上板先把DDR读写和DMA链路调通,不要急着挂MoE。然后在FPGA里跑通一个小规模的端到端模型,比如E=4、d=512、h=2048。最后再扩展E和h,同时观察资源与带宽利用率的变化。

最后啰嗦一句个人体会:FPGA做MoE不是要把推理场景全盘从GPU手里抢过来,而是给那些对功耗、延迟、数据私密性有硬性要求的场景多一个可选方案。这个方向现在不缺宏观分析,缺的是把路由决策、数据重排、专家预取这些细节点做实的人。先把一个最小的E=4模型完整跑通,再逐步加规模,每一版都记录资源与带宽的变化,这是我最推荐的切入方式。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 19:34:52

Codex额度消耗分析与预算管理实战:Token计费、任务拆分与成本优化

上周三早上,我照例打开 Codex 准备开始一天的第一段自动编码任务,结果启动页直接弹出了额度不足。我盯着“本周额度已用完”的提示愣了几秒——三天前它才刚重置过。这件事给我的冲击不是“这个月要多花一笔钱”,而是让我第一次真正意识到&am…

作者头像 李华
网站建设 2026/9/8 19:32:18

硬件在环(HiL)测试全解析:原理、应用与职业发展指南

1. HiL 测试到底是做什么的:先把这个行当看清楚再说值不值得很多想入行的人一开始听到“HiL 测试”,脑子里冒出来的问题是:这不就是测测硬件吗?跟板卡测试、整机测试有什么区别?其实差别大了。HiL 全称是 Hardware-in-…

作者头像 李华
网站建设 2026/9/8 19:31:38

【C++ 第二阶段:】智能指针与现代 RAII

C 第二阶段:智能指针与现代 RAII 前言 第一阶段我们手动管理过: FILE* → fopen / fclose char* → new[] / delete[]第二阶段的目标不是“把裸指针换成智能指针”,而是建立清晰的资源所有权模型: 谁拥有资源? 谁负责…

作者头像 李华