从年初到现在,我几乎每个月都会被问到同一个问题:推理框架怎么适配 Hybrid Model。问的人里有刚接触大模型推理的算法工程师,也有部署运维的老手。他们手里的模型五花八门,但核心词几乎都是 MoE(混合专家)、混合注意力结构这类“不是标准 Transformer 全家桶”的模型。很多人拿到一个 MoE 模型,第一反应就是直接丢进 vLLM 里跑,结果发现显存占用、吞吐表现和预期差得远,甚至不如自己用 transformers 写个脚本来得顺。
这篇文章不聊论文复现,也不做框架源码逐行解析,就从一个部署者的角度,讲讲 Hybrid Model 到底在推理框架眼里特殊在哪、框架为了适配它改了什么,以及我们在实际环境下怎么调参、怎么排坑。适合同样在跟 MoE、Mamba、混合注意力模型死磕的工程同学参考。
1. 先搞清楚 Hybrid Model 到底指什么
1.1 推理实践中最常见的几种 Hybrid Model 形态
“Hybrid Model”在学术语境里含义很宽泛,注意力机制混合、词嵌入混合、损失函数混合都算。但放到推理框架适配这个场景下,真正让框架底层逻辑大改的只有两类:
第一类是MoE(Mixture of Experts,混合专家模型),比如 Mixtral 8x7B、DeepSeek-V3 这种。它的特点是把 FFN(前馈网络)层扩展成多个并行的专家子网络,每个 token 只经过少数几个专家。模型总参数量看着吓人,但每次前向计算只激活一小部分权重。
第二类是混合注意力模型,比如 Jamba、Zamba 这类把 Transformer 注意力层和 Mamba(线性注意力)层在模型结构上交替堆叠的模型。这种 Hybrid 的核心是:一部分层使用传统的 KVCache 注意力机制,另一部分层使用固定大小的状态变量来压缩历史信息,完全没有 KVCache。
这两类模型有一个共同点:它们都不是“一层套一层”的标准结构,而是带分支、带路由、带不同状态管理的复杂结构。这给推理框架带来的麻烦是本质性的,不是多加点显存就能解决。
1.2 为什么传统推理框架面对 MoE 会水土不服
先说一个很多人忽略的事实:HuggingFace transformers 是可以跑 MoE 模型的,而且代码写起来很简单。但如果你真的在生产环境这么干,吞吐会惨到没法看。原因很直接:transformers 的推理是逐层 Python 调度,每个 token 都要动态决定去哪几个专家,然后循环加载专家权重。当模型里有几十上百个专家时,这种 Python 层的调度开销会被放大到完全不可接受。
推理框架要解决的核心问题,就是把这个“动态、带分支”的计算图,尽量变成高效、可复用的图结构,并在 GPU 显存和通信层面做针对性优化。比如vLLM、SGLang、TensorRT-LLM这些框架,对 MoE 的适配主要做了三件事:把专家权重按特定方式切分和排布、把 token 到专家的调度做成连续且内存友好的操作、把不同的请求在专家维度上做合并计算。
这背后还有一个所有做推理的人都会关心的点:MoE 模型虽然总参数大,但单个 token 的计算量并不大。这意味着MoE 推理往往是显存瓶颈和调度瓶颈,而不是纯算力瓶颈。传统框架的优化思路集中在算力利用率上,面对 MoE 时代需要转换重心。这是理解下文所有技术方案的出发点。
提示:如果你拿到一个 MoE 模型,第一反应是“它参数多所以算力要求高”,那后面大概率会走弯路。MoE 的真正矛盾在于“显存放不下全部专家”和“喂不饱 GPU 算力”同时存在。
2. 框架适配 MoE 的核心技术路径
2.1 Expert Parallel:把专家真正放平,而不是复制到每张卡
推理框架适配 MoE 最关键的一项技术改造,是引入Expert Parallel(EP,专家并行)。
传统的数据并行(Data Parallel)很简单:每张 GPU 上都放一份完整模型,各自服务不同的请求批次。但对 MoE 模型来说,这个方案极其浪费。以 Mixtral 8x7B 为例,总参数约 47B,如果以 BF16 精度存储,权重大约 94GB。你用 4 张 80GB 的卡做数据并行,每张卡都要完整放下这 94GB,显存利用率低得离谱。
EP 的思路完全不同:既然每个 token 只经过 2 个专家(Top-2 路由),那我为什么非要在每张卡上保留全部 8 个专家?把专家们拆开分布到不同卡上,每张卡只负责一部分专家,路由时把 token 发给对应专家所在的卡。这样 8 张卡跑 8 个专家的模型,每张卡只需要放 94/8 ≈ 12GB 的专家权重,真正做到了“按需存储”。
EP 的实现难点在于通信模式的改变。专家分布在不同的 GPU 上,token 在层与层之间就要跨卡传输。框架需要把注意力层和 MoE 层之间的数据流转从“本地张量计算”改成“远程张量收发”,这个过程在工程上叫All-to-All 通信。
2.2 Token Routing 与 All-to-All 通信:从数据流角度重新设计执行图
现在聊一下 Token 是怎么在 MoE 层流动的,这直接决定了框架执行图长什么样。
一个 token 在注意力层计算完后,进入 MoE 层的第一步是过Gate(门控网络)。Gate 会为每个 token 计算一个“该去哪几个专家”的分数,然后选出 Top-k 个专家。第二步是根据路由结果,把这一批请求中的所有 token 重新分组,要发往同一个专家的 token 打包在一起,这个动作就是Dispatch。第三步,各个专家在自己的卡上处理完这批 token 后,结果需要送回原来的位置,供下一层注意力计算使用,这个动作叫Combine。
从框架角度看,Dispatch 和 Combine 的本质是按 token 对数据进行重排,同时必然伴随跨卡数据传输。以 vLLM 为例,它在 DeepSeek 系列模型上已经开始支持 EP 模式,核心执行图里就内置了这种 token 分组和跨卡收发逻辑。SGLang 对 MoE 的支持同理,并且为了压低 Dispatch 延迟,做了很多内核级优化。
这里面有一个简单但很关键的数学关系需要讲清楚。假设一批请求里有 N 个 token,模型有 E 个专家,每个 token 激活 Top-k 个专家。那么这批 token 在 MoE 层的计算关系总量是 N × k,和 E 无关。换句话说,MoE 的计算量主要由激活专家数决定,而不是总专家数。这也意味着在 EP 场景下,真正的性能调节杆是 k 值、batch 大小、通信带宽三者的配合。
2.3 显存究竟省在哪,又多出哪些开销
很多人有一个误区,认为 EP 一定比 DP 快。实际上 EP 主要省的是显存,算力本身并不会因为 EP 变高,反而可能因为通信开销变慢。合理的框架适配要做的是:让 EP 的显存收益最大化,同时让通信开销可控。
具体拆开看:
- 省下来的显存:专家权重无需全量复制到每张卡上,省下来的显存可以用来增大 KV Cache 存储区、增加并发请求数、容纳更长的上下文。在长文本场景下这收益非常明显。
- 多出来的开销:每层 MoE 都要做一次 All-to-All,跨卡传输 token 的中间激活值。通信量和 batch size 成正比,和序列长度也有关系。卡间通信走 NVLink 时延迟较低,跨节点走 RDMA 网络时开销显著上升。
所以框架适配 EP 时,通常会让用户配置TP(Tensor Parallel)和 EP 的组合比例。比如 8 卡环境,你可以设 TP=4、EP=2,也可以 TP=1、EP=8。前者通信局部性更好但每张卡显存压力更大,后者显存最省但通信压力最大,没有绝对正确,只有适合场景的方案。这部分我会在第三节展开讲实际配置。
3. 推理框架适配 Hybrid Model 的实操与部署参数
3.1 一个具体案例:8 卡环境部署 671B 级 MoE 模型的参数估算
下面我用一个具体例子带你走一遍部署时的参数推理过程,这里以 DeepSeek-V3 这类结构的模型为例说明计算方法,不说具体品牌也能直接套用。
DeepSeek-V3 总参数约 671B,但激活参数只有约 37B。它用了 MLA(Multi-head Latent Attention)来压缩 KV Cache,同时用共享专家 + 256 个路由专家的结构。如果以 BF16 精度部署,671B × 2 字节 ≈ 1.34TB 权重。8 张 H800 80GB 显卡的总显存是 640GB,显然放不下。如果做 FP8 量化,权重降到约 671GB,8 卡总量还是略吃紧,而且显存完全没有余量给 KV Cache 和激活值。所以实际部署必然要拆到更多卡,或者用 DeepSeek 官方给出的优化方案——把 MLA 和 MoE 层做特殊排布,再配合更激进的量化。
这个案例说明一个道理:适配 MoE 模型,显存规划是第一优先级。你拿到一个模型,先别急着想吞吐,先算清楚“权重占多少、激活占多少、KV Cache 要留多少、通信要占多少带宽”,再决定 EP 和 TP 怎么切。
我建议所有做推理的人养成一个习惯:部署前先把显存规划写成一页纸。权重占用(模型总参数 × 每参数字节数)、KV Cache 预估(与 batch size、序列长度、层数、注意力头维度相关)、激活值余量(一般预留 20%–30%)、通信缓冲区(All-to-All 需要额外 buffer),这四项加起来必须明显小于单卡显存,否则后面跑起来必然 OOM。
3.2 三个必须理解的配置项:top-k、专家容量、负载均衡 loss
MoE 模型部署时,有参数虽然名字看起来和推理框架没关系,但实际效果影响极大。
第一个是 top-k 路由数量。这个值在模型训练时就已经定了,推理时一般不能改,因为改了会破坏训练时的路由分布。但你要理解它的意义:k 越大,每个 token 激活的专家越多,算力消耗越大,显存周转越紧张。k=2 是主流选择,少数模型用 k=3 甚至动态 k。
第二个是专家容量(expert capacity)。这是推理框架调度中的一个核心概念:每个专家在处理一个 batch 时,能容纳的最大 token 数。因为 token 路由是动态的,热门专家可能被分到远多于平均值的 token。如果框架严格按照“每个专家处理相同数量的 token”来预分配缓冲区,那必然有一部分 token 会被丢到 overflow(溢出)通道,而溢出通道通常走残差直连,等价于这些 token 没经过专家计算,模型质量会受影响。
实际配置时,框架一般允许设置 capacity factor,比如 1.0、1.2。我建议在线服务场景设为 1.1 左右,既不大幅浪费显存,也不会频繁触发 overflow。如果是离线打分、质量要求高的场景,可以设 1.5 甚至更高,让每个专家有充足余量。这里没有标准答案,只有“调试出来的答案”。
第三个是负载均衡 loss,很多同学会误以为这是训练时才用的东西。其实推理框架做 EP 显存排布时,会参考训练时的路由统计。如果一批 token 在 Gate 上严重偏向少数热门专家,EP 方案就会闲置大量“冷门专家”所在的显存和计算资源。你可以通过框架的 profiling 工具观察路由分布,如果发现明显不均衡,先检查是不是输入数据分布太窄,再考虑是否需要动态路由策略。
3.3 量化与 KV Cache 的适配策略
MoE 模型的量化比 Dense 模型更讲究,原因在于专家权重有很强的“独立性”。你量化一个专家时的精度损失,只影响走这个专家的那部分 token,而不是整个模型均匀退化。这既是坏事也是好事。
实操中我建议对 MoE 模型采用分专家量化的策略:热门专家用高精度(比如 FP8),冷门专家可以适当降低精度。框架层面,vLLM 和 TensorRT-LLM 都支持 per-tensor 或 per-group 的权重量化,但当你切换到 EP 模式后,量化格式和通信格式必须对齐。比如你用了 INT8 权重,但 All-to-All 传输的是 BF16 激活值,那么通信时还得做一次反量化或者提前转换,这里有额外开销要计入总时延。
KV Cache 方面,MoE 模型因为注意力参数占比相对小,KV Cache 压力通常小于同规模 Dense 模型。如果你部署的 MoE 还用了 MLA/GQA 这类压缩 KV Cache 的结构,那显存大头会集中在专家权重上,KV Cache 反而不是瓶颈。不过有个反直觉的点:KV Cache 变小后,模型的吞吐上限更多由 batch size 和专家通信带宽决定,而不是显存。换句话说,你留出一大块 KV Cache 显存,实际根本用不完,还不如减少并发把单请求延迟拉低。
4. 混合线性注意力模型的适配新问题
4.1 为什么混合注意力模型也属于 Hybrid Model 的适配范畴
第二类 Hybrid Model 是 Mamba 结构(纯线性注意力)与 Transformer 注意力结构混合的模型。这类模型在推理框架里遇到的适配问题,和 MoE 完全不同,它挑战的是“两种形态的内存管理”。
Transformer 层的注意力需要显式保存 KVCache,并且 KVCache 的大小随序列长度增长。Mamba 层则完全不同,它维护一个固定大小的状态变量(比如 16 × 64 的矩阵),每读一个 token 就更新一次状态,不产生随序列增长的缓存。因此,一个 Jamba 这样的模型,推理过程中同时存在“随序列增长的 KVCache”和“固定不变的状态变量”两种内存结构。
推理框架适配这类模型时,不能只按处理 Transformer 的结构来设计 buffer 管理。Mamba 的状态变量需要常驻显存、逐 token 更新,而且状态更新的依赖关系是串行的——当前 token 的状态依赖于前一个 token 的计算结果,这导致线性注意力部分天然难以做并行加速,框架需要额外设计卷积核和分段扫描(chunked scan)策略来弥补。
4.2 框架具体需要为线性注意力层做哪些事
这里以 Mamba2 为例展开,细节不堆代码,但原理值得说透。
Mamba 层的计算有两大块:离散卷积 + 分段递归。离散卷积本质上是局部窗口操作,可以很好地并行化;分段递归才是真正的难点。
分段递归的思路是把很长的序列切成若干 chunk,在每个 chunk 内部先并行推进部分步骤,再把 chunk 之间的状态依赖关系串行整合。推理框架为此需要实现一个专门的融合内核,把“状态更新 + 权重矩阵乘法 + 非线性激活”合并到同一个 kernel 里,否则每步都启动多个 kernel,延迟根本压不下去。
另一个让框架头疼的是:Mamba 的状态更新在 decode 阶段必须强制串行,这会导致显存利用率降低。框架层面对此的适配方案是更大胆的请求合并(batching):把多个请求的状态更新矩阵拼在一起做批量更新,尽量把串行 GPU kernel 的启动开销摊薄到更多请求上。
4.3 生产框架对混合注意力模型的支持现状
坦白说,截止到目前,开源主流框架对这类模型的支持水平还远不如 MoE 成熟。vLLM 的 Mamba 支持处于实验性阶段;SGLang 对基于 Mamba 的模型支持也相对有限;TensorRT-LLM 的 Mamba 支持度高一些,但仍集中在固定长度和特定配置下比较稳。
所以我给这类 Hybrid 模型的适配建议是:先想清楚你是追求多高吞吐,还是只要跑通就行。如果只是验证模型效果,直接用 transformers 原生推理能接受慢一点;如果真要部署,优先选有 Mamba 内核支持的框架,并且做足 benchmark,不要假设它和 Transformer 的部署一样“开箱即用”。
注意:混合注意力模型目前最大的坑不在显存容量,而在框架对不同状态结构的兼容度。动手前先去框架官方文档看它支持哪些模型架构版本,能省掉大量排错时间。
5. 常见问题与排查技巧实录
5.1 为什么我的 MoE 推理速度还不如同参数量的 Dense 模型
这个问题的答案基本只有三个:一是 token 路由不均导致专家空闲,二是专家权重反复在显存和计算单元间搬运,三是通信开销没有摊薄。
逐个排查方法如下:
- 路由不均:开启框架的 profiling,打印每个专家的实际 token 分布。如果热门专家和冷门专家的负载比超过 3:1,说明路由本身就偏,优先考虑是不是 prompt 分布太单一。不要急着换框架,先把输入数据分布捋一捋。
- 权重搬运严重:MoE 层如果没能把一批 token 的专家调用合并成大的 GEMM 运算,而是每个 token 逐个调专家,GPU 的算力利用率会掉到个位数。检查框架日志里的吞吐数据,若 decoder 阶段吞吐远低于预期,多半是 batch 太小导致专家合并度不够。
- 通信瓶颈:EP 模式下跨卡通信量太大。如果部署环境跨节点,优先把通信密集的层尽量放在节点内部。
5.2 专家负载不均衡的实际处理
我跑 Mixtral 8x7B 时遇到过 Gate 输出几乎完全偏向两个专家的情况。一开始以为是模型问题,后来定位到是测试集太窄——大量相似结构的 prompt 导致路由坍缩。
处理办法有两个方向:如果输入数据可控制,做 prompt 多样性增强;如果数据固定不可改,就在调度侧引入专家负载感知的排队策略,把不同路由倾向的请求混合进同一个 batch。这比调模型权重更实际。框架层面,SGLang 在 batch 构造时考虑路由分布的整合策略,效果比纯随机拼 batch 好不少。
5.3 显存充足时如何进一步压榨吞吐
显存如果充足,最直接的收益来自增大 batch 和启用持久化 batch(continuous batching)。MoE 模型有个好处:由于专家是分布的,大 batch 能把每个专家要处理的 token 数堆起来,专家计算块的矩阵规模变大,GPU 利用率显著提升。我实测过一个 8 卡环境下的大 MoE 模型,batch 从 64 提到 256,吞吐提升远超线性,就是因为专家 GEMM 的规模效应。
另外可以尝试调高 decode 阶段的预填充(prefill)合并策略,让首 token 延迟和吞吐得到更好的平衡。框架一般有相关开关,但注意不同框架对 prefill 和 decode 合并的调度策略不同,换框架时不能沿用同一套参数。
5.4 不同推理框架如何选择
这是一个永远有人问的话题,我不好说“框架 A 一定比框架 B 好”,只能说按模型类型和场景来筛。
| 模型类型 | vLLM | SGLang | TensorRT-LLM | 说明 |
|---|---|---|---|---|
| 稠密 Transformer | 稳定 | 稳定 | 高性能 | 随便选 |
| MoE(较小规模) | 支持完善 | 支持完善 | 支持较好 | 选熟悉度高的即可 |
| MoE(超大规模) | 大规模部署案例多 | 路由优化积极 | 企业级优化强 | 建议先做小规模验证 |
| Mamba/混合注意力 | 实验性支持 | 有限支持 | 定向支持较好 | 提前确认模型版本 |
选型建议是:主流的 MoE 模型优先考虑 vLLM 或 SGLang,社区案例多、坑容易搜到;企业环境或有特定加速芯片时再看 TensorRT-LLM 这类闭源优化引擎。如果模型规模大到单机装不下,多机通信方案比框架选型更关键,优先把组网方案敲定再谈框架。
5.5 部署过程中遇到过的两个诡异问题
最后分享两个我印象深刻的排错实录,都是从百度和各路社区里查不到、只能靠看源码定位的。
第一个问题是:MoE 模型在 FP8 量化后,batch 大时偶发 NaN。排查了整整两天,最后定位是某几个专家的权重分布范围比其他专家大,量化到 FP8 时极值被截断,导致特定 token 的计算结果溢出。解决方式是改为按专家分组做动态缩放,而不是全模型统一缩放。
第二个问题是:EP 模式下跨节点通信频繁超时。表面看是网络配置问题,实际原因是指定 EP 时没有关闭框架的隐性权重处理功能,导致某些层仍在做非必要的 All-Gather。把相关开关手动关闭后,通信量下降接近一半。
这两个案例说明一个事实:框架适配 MoE 不是“填几行 yaml 就能跑”的事,你得理解框架在 EP、路由、调度三个层面分别做了什么,以及这些行为在你具体的模型和数据上会带来什么后果。
我个人这几年的经验是:做 Hybrid Model 推理,第一步永远是算显存账,第二步是跑 profiling 看路由和通信,第三步才是调参数。把这三步走稳,无论模型是 MoE、Mamba 混合还是日后出现的新结构,你都能快速找到一个“不优雅但能上线”的适配方案,比照抄任何一份官方模板都管用。