先说一个可能让很多人意外的事实我手里这套配置一张 RTX 3090一台双路 Xeon v4 老服务器内存 256GB跑一个总参数量 1250 亿的 MoE 模型生成吞吐最终稳定在 108 tok/s 左右。这数字不是 PPT 里拍出来的是我们在 Strata 推理引擎上连续调了两周从最初只有 40 tok/s 一步步提上来的真实结果。如果你也想在消费级显卡或者老款双路服务器上跑大模型这篇文章值得看完。里面没有虚的全是能直接抄走的参数、思路和踩坑经验。先交代一下背景这块 3090 是单卡24GB 显存PCIe 3.0 槽位CPU 是双路 Xeon v4内存通道 8 路 DDR4理论带宽大约 150GB/s实际大概率只能跑到 110GB/s 上下。模型按常见 MoE 结构来算总参数 125B共享层大约 10B剩下约 115B 分布在 64 个专家上推理时每层只激活 Top-2 专家单 token 实际激活参数约 13.6B。很多人一看 MoE 就觉得“显存压力小”但真跑起来你会发现稀疏性只帮你省了算力没帮你省权重占用该装不下的还是装不下。1. 先别急着跑命令把硬件约束和模型结构账算清楚1.1 一张 3090 面对 125B MoE 模型的现实问题先说权重这关。125B 参数如果全部用 BF16 存储裸权重就是 250GB就算全量化到 INT4也要 62.5GB。这块 3090 只有 24GB 显存连 INT4 的完整权重都放不进去更别说中间还有激活值、KV cache、临时 buffer。所以摆在面前的第一道选择题不是“要不要 offload”而是“怎么把 62.5GB 量化后权重塞进一个 24GB 的显存容器里同时还得跑得动”。很多第一次接触 MoE offload 的人会想既然单 token 只激活 2 个专家那把激活的专家临时搬进去不就行了想法没问题但忽略了另一件事。13.6B 的激活参数在 BF16 下就是 27GB依旧超过 24GB。这意味着哪怕只用共享层加两个专家显存照样爆。所以单纯靠“把专家临时搬进显存”这一招不够必须配合量化、常驻缓存和批处理复用把单步计算需要的显存压到可控范围。1.2 双路 Xeon v4 与 PCIe 3.0 链路能提供什么Xeon v4 是个很有意思的平台。它内存容量可以做得很大256GB 起步很轻松但内存带宽和 PCIe 拓展能力都是老一代水平。这里有个很容易被忽略的细节3090 本身支持 PCIe 4.0但插在 Xeon v4 的 PCIe 3.0 槽位上就只能跑 3.0 的链路。PCIe 3.0 x16 单向带宽约 15GB/s实际可用大概只有 12GB/s 左右。这个数字决定了权重搬运的上限。调优前必须先算一笔账。假设每个生成 token 需要从内存读到显存的权重是 2GB那光是权重传输就需要 2GB / 12GB/s约 170ms换算下来不到 6 tok/s。而我们的目标是 100 tok/s也就是每 token 预算只有 10ms。所以任何“每次生成都从内存读权重”的方案都不可能达标。正确思路是让 GPU 显存变成专家缓存层而不是权重仓库。绝大多数命中缓存的计算不需要走 PCIe只有缓存 miss 才需要搬运。1.3 MoE 的稀疏性整个方案能成立的最大前提如果这是普通稠密模型125B 参数激活参数就是 125B24GB 显存加 256GB 内存的组合基本没戏。但 MoE 不一样路由机制决定了每个 token 只经过一小部分网络。从计算量上看实际参与计算的参数只有 13.6B 左右3090 的算力完全能承受从访存上看由于专家权重可以复用只要批处理做得好传输压力也能被平摊。说到底MoE 稀疏性把我们原本需要面对的“超级大模型”降级成了“一批中等规模的权重分片 一张用量合理的显存卡”。后面所有优化手段包括专家缓存、KV offload、投机解码本质上都是围绕这个稀疏性做文章。理解了这一点再去调参就有章法不会一会儿改这个参数一会儿改那个参数最后都不知道瓶颈在哪。2. 三层解耦把推理过程拆成“权重加载、计算、状态管理”2.1 GPU 显存当成专家缓存而不是权重仓库我们的做法很直接把 64 个专家分成“热专家”和“冷专家”。热专家是路由频率最高的那些常驻显存冷专家留在 CPU 内存需要时按需加载。为什么能这么干因为 MoE 路由在真实业务数据上存在明显的头部效应少数几个专家承接了大部分 token。我们在模拟项目 X 的线上流量分布里测过Top 12 热专家能承接超过 90% 的路由请求。这个特性给了我们巨大的优化空间。具体实现上专家缓存表用 LRU 策略管理显存里常驻 12 个热门专家每个专家在 INT4 量化后大约 0.9GB12 个就是 10.8GB。加上共享层量化后约 5GB、KV cache 预留一部分、临时激活 buffer24GB 显存刚好兜住。缓存 miss 时后台线程从 CPU 内存加载冷专家权重同时尽量把同一层的多个专家请求合并到一次 PCIe 拷贝避免零散传输打满带宽。2.2 KV cache 从显存搬到 CPU 内存按层预取KV cache 是个容易拖垮显存的东西。模型层数一旦上去KV 是按“层 × 注意力头数 × 序列长度”膨胀的。我们在测试里把最大上下文控制在 2048单条序列的 KV cache 大约是 780KBBF16。听起来不大但 16 条并发序列同时跑就是 12MB 乘以层数分摊……最后全塞进显存也会挤占专家缓存的空间。所以 KV cache 也走 offload 路线量化成 INT8放 CPU 内存。计算每一层时当前层的 KV 从 CPU 预取到显存计算完再异步拷回去。这里必须注意流水线设计预取下一层 KV 的 DMA 拷贝要和当前层计算重叠不能等这一层算完才开始搬下一层否则 GPU 算子间隙会被拉满利用率直接掉一半。后面实测也验证了调整好预取流水线之后GPU 利用率从 30% 提升到了 85% 以上。2.3 量化策略分层不同部位用不同精度量化不是一刀切。共享层、专家、KV cache 的分量不同敏感度也不同我们分别用了不同设置部位精度方案group size原因共享层Attention/FFN共享INT4 权重量化128共享层参数总量约 10B是显存占用大头量化后能省 20GB敏感度相对低专家层INT4 权重量化64专家权重被高频加载质量影响直接group size 小一点量化误差更低KV cacheINT8 量化不分组KV cache 量变化快INT8 足够稳定实测对生成质量影响很小RMSNorm/激活BF16不量化数值敏感保持原精度这里想多说一句 group size 的差异。量化到 4bitgroup size 从 128 改成 64权重体积会增加一点但误差明显下降。我们测过相同校准集下w4g64 的困惑度比 w4g128 低 0.3 左右这对长文本生成来说值得多占那一点点内存。2.4 为什么说投机解码是绕不开的一步前面这些优化把瓶颈从“显存放不下”缓解到“勉强能跑”但还没解决本质问题MoE 每生成一个 token都要经过完整的多层路由、专家计算、KV 更新GPU 算力被压缩在比较低的利用率区间。单纯靠 3090 直接推大模型生成速度大概率停在三五十 tok/s。要上 100必须减少大模型自身 forward 的次数投机解码就是干这个用的。投机解码的思路是用一个更小的草稿模型先生成 4 到 8 个候选 token然后把这几个候选 token 打包成一次大模型 forward逐位置验证。接受率高的时候相当于大模型一次前向就“确认”了多个新 token端到端吞吐自然翻倍。这也是我们在第三轮实测中能达到 100 的关键增量。后面章节会详细讲草稿模型怎么选、草稿长度怎么调。3. 落地实操从编译选项到调度器的调优记录3.1 环境准备里最容易踩的坑编译选项和指令集先说环境。我们在 Xeon v4 上用源码编译推理引擎第一件事就是关掉所有面向 AVX-512 的编译优化。Xeon v4 只支持 AVX2如果用 AVX-512 的指令集编译出来的二进制要么根本无法运行要么会触发非法指令错误。这条经验说出来很简单但当时确实有人踩了白白折腾了一下午。另一个关键参数是 GPU 架构码。RTX 3090 是 Ampere 架构计算能力 8.6编译时一定要指定对应的架构选项否则编译器默认生成的核函数可能性能差很多。实际验证中正确指定架构后矩阵乘法的峰值性能能提升 20% 以上。我们最后的生产编译参数大概是# 仅示意具体按引擎的构建系统调整 cmake -DCMAKE_CXX_FLAGS-marchhaswell -O3 \ -DGPU_ARCHsm_86 \ -DENABLE_AVX512OFF \ -DCMAKE_BUILD_TYPERelease另外内存这块强烈建议开启大页。双路 Xeon v4 支持 1GB 大页把 KV cache 池放到大页内存里TLB 命中率提升明显。我们实测启动参数加了大页后长序列场景下 CPU 访存开销降低了将近 10%。3.2 权重分片按专家切文件而不是一次性加载 250GB第一次启动这模型时我们天真地想直接把完整权重 load 进内存结果 40 分钟都没起来最后还差点 OOM。后来改成权重分片加载共享层单独一个文件64 个专家每个单独一个分片文件启动阶段只注册文件句柄和布局信息专家权重按需 mmap 到内存。冷专家第一次被路由命中时才真正触发磁盘读。分片带来的另一个好处是缓存管理更干净。如果整块权重都放内存显存加载时还要做偏移计算切分之后每个专家分片就是一个独立的可换入换出单元缓存命中、淘汰、预取都以“一个专家”为粒度逻辑清晰也不会出现半个专家在显存、半个在内存的尴尬状态。启动耗时也从 40 分钟降到了 4 分钟左右。这个优化对“重启调参”特别重要否则每次改一个配置都要等大半个小时根本没法迭代。3.3 算子改造MoE 路由分组 GEMM 和 QKV 融合真正拉开第二轮性能差距的是算子层面的改造。MoE 的专家计算不能简单写成“对每个请求单独跑一个 GEMM”那样 kernel 启动开销会吃掉大部分收益。我们的做法是把一个 batch 中路由到相同专家的请求合并成一个分组用分组 GEMM 一次算完。比如某层有 8 个不同专家被命中本来要启动 16 次小 kernel合并后只要 8 次而且每次的矩阵更大GPU 利用率自然更高。QKV 投影融合也值得做。原来 Q、K、V 三个投影各走一次 GEMM三次 kernel 启动融合成一次大的 GEMM 后虽然总计算量没变但 kernel 启动次数减少了三分之二memory transfer 也有明显优化。调度器层面再把 RMSNorm 和残差 add 融合进同一个 CUDA kernel减少中间张量的落盘和读取。这些单点优化看着都不大但叠加起来对吞吐的影响非常可观。3.4 连续批处理让 GPU 不等慢请求大模型推理有个特点不同请求长度不一样有的已经生成了 200 个 token有的刚进来。如果按固定 step 同步处理整个 batch 都要等最慢的那个请求GPU 会被拖到很难看。我们把调度器改成了连续批处理模式请求完成一个就腾出位置给新请求decode 阶段不再整批次同步推进。这个改动直接提升了 token 吞吐尤其是多用户并发场景。配合页式 KV cache 管理每个请求的 KV 按页分配允许不连续存储内存碎片少了很多。我们在测试机上试过连续批处理开启后相同并发下吞吐比固定 batch 提高了约 18%GPU 利用率也能稳定在 80% 以上。对于老平台来说这一步收益明显但容易被忽略。3.5 投机解码接入草稿模型选多大、草稿长度设多长草稿模型的选择是投机解码最重要的调试点。太大草稿生成本身就慢收益被吃掉太小接受率低验证成本反而高。我们试过从几百 M 到 7B 好几个量级的草稿模型最后锁定在 1B 左右。原因是1B 模型在 3090 上生成速度足够快能达到 200 到 300 tok/s而且和 125B MoE 大模型的知识分布比较接近接受率能到 0.7 到 0.8。草稿长度设置上固定长度 8 是最稳的。太长会导致后排 token 接受率快速下降白做计算太短又浪费大模型验证能力。后来我们做了动态调整最近若干步的平均接受率高于 0.75 时草稿长度加 2低于 0.5 时减 2。实测动态调整比固定长度又提升了约 6% 的吞吐。这一点小收益在 100 tok/s 这个量级上是实实在在的。4. 三轮实测数据从 40 tok/s 到 108 tok/s4.1 第一轮全量 BF16 offload 加单请求结果只有 40 tok/s第一轮我们没有做任何量化全权重 BF16 扔 CPU 内存靠最朴素的按需加载跑。结果生成吞吐只有 40 tok/s。这个数据其实比想象中好一点但离目标差得远。分析瓶颈时发现PCIe 链路基本处于接近饱和状态每次层切换都要同步等权重拷贝GPU 算子之间全是空洞。显存里 KV cache 又被塞得满满的稍微长一点的序列就开始告急。这轮的意义在于把“不可行”变成了“勉强能跑”。它告诉我们一个很现实的问题这套平台跑 125B MoE不是算力不够是数据搬运路径太差。接下来要做的不是堆机器而是把搬运量降下来、把搬运过程和计算重叠起来。4.2 第二轮量化加专家缓存加连续批处理冲到 72 tok/s第二轮上了完整组合拳共享层和专家层 INT4 量化显存常驻 12 个热专家KV cache INT8 并 offload 到 CPU调度器切换为连续批处理batch 上限设到 16。这轮 PCIe 实际搬运量骤降专家缓存命中率超过 90%大部分计算都命中显存里的热专家无需等内存加载。GPU 利用率从 30% 升到 75% 左右吞吐来到 72 tok/s。这轮结束后性能翻了一半但我们仍然不满足。瓶颈已经从“显存放不下”变成了“大模型自身 forward 次数太多”而这个问题靠 offload 已经解决不了得换一条思路。4.3 第三轮投机解码加 NUMA 绑定稳定破 100第三轮接入投机解码。草稿模型用 1B 稠密小模型初始草稿长度 8动态调整。经过一轮调试后平均接受率在 0.72 左右实际加速比大约 2.1 倍。再配合双路 CPU 的 NUMA 绑定优化把推理线程绑到与 GPU 同一 NUMA 节点的处理器上最后平均生成吞吐落在了 108 tok/s。连续跑 100 轮测试最低也有 104最高到过 116。到这里目标已经达成。而且最后的显存占用一直没超过 23GB长时间运行也没有 OOM。后来我们还试过把 batch 上限调大到 24吞吐又能往上走 5% 左右但代价是首 token 延迟明显变大所以线上还是压回了 16 到 20 之间。4.4 三轮实测数据对比表配置项第一轮第二轮第三轮权重精度BF16INT4w4g64/w4g128INT4w4g64/w4g128专家缓存无12 个热专家常驻12 个热专家常驻KV cacheFP16 显存INT8 CPU offloadINT8 CPU offload批处理模式固定 batch连续批处理连续批处理投机解码无无1B 草稿模型动态草稿长度平均生成吞吐40 tok/s72 tok/s108 tok/s峰值显存占用24GB濒临 OOM21GB22GBGPU 平均利用率30%75%83%看这张表就知道每一轮改了什么、效果是什么一目了然。这也给了我们一个明确结论性能提升从来不是某个“魔法参数”带来的而是量化、缓存、调度、投机解码共同作用的结果。5. 调优路上最容易翻车的细节与完整排查链路5.1 GPU 利用率只有 30% 但 CPU 吃满先把 PCIe 搬运串行化问题解决了第一轮调优时最诡异的现象是GPU 利用率只有 30%CPU 侧线程却全部跑满看起来像是 CPU 成了瓶颈。用性能分析器抓了一遍时间线才发现问题出在 PCIe 搬运和计算完全串行每个层先等专家权重从内存拷到显存拷完才启动计算计算完又开始等下一层拷贝。CPU 线程忙的是等拷贝和发起拷贝GPU 则是经常闲着。解法是双缓冲预取。权重预取队列深一层下一层的专家权重在当前层还在计算时就开始从 CPU 内存搬到显存目标内存区域预先分配好避免动态分配造成额外等待。修改后 GPU 利用率立刻从 30% 拉升到 75% 以上。这一步是整个调优过程中收益最大的单一改动也让我彻底记住了“先看时间线别猜瓶颈”的教训。5.2 量化后输出质量下降校准集和 group size 的取舍量化不是免费的第二轮刚开始时我们发现生成质量比 BF16 版本有明显下降某些长文本开始出现逻辑断裂。排查下来是两个原因。一个是校准集覆盖不够只有代码类样本导致自然语言路由到冷专家后失真严重。另一个是 group size 选得太大128 的 group 对专家权重来说太粗。解决方法是把校准集合扩展到代码、对话、摘要、多语种混合样本同时把专家层的 group size 从 128 改成 64。重新量化后困惑度下降约 0.3生成质量肉眼可见恢复。还留了一个开关对“要求极高质量”的场景可以只把共享层量化到 INT4专家层保持 BF16牺牲一些吞吐换质量。5.3 KV cache 水位失控导致的显存雪崩连续批处理开了以后并发请求一多KV cache 就像滚雪球一样涨。刚开始我们没管水位线结果某次压测跑到第四十分钟显存直接 OOM进程崩了。复盘发现个别超长序列的 KV 没有及时换出把专家缓存挤得只剩几个专家 miss 率飙升PCIe 带宽被打满然后恶性循环。修复方式是在调度器里加了两条规则。第一显存剩余低于 3GB 时优先回收最久未活跃序列的 KV 页把它们转储到 CPU 内存的 KV 池第二每个新请求进入前先预检显存水位不够就强制等待或踢出长空闲请求。加上这两条之后8 小时压测没有再出现 OOM。5.4 双路 CPU 的 NUMA 效应绑核前后差了 18%Xeon v4 是双路平台两个 CPU 各带一半内存访问对端内存要跨 NUMA 节点延迟高不少。第一次跑投机解码时草稿模型被调度到了另一个 socket 上的核导致它和大模型主线程跨节点交换数据频繁吞吐莫名掉了 18%。我们一开始还以为是模型参数问题后来用工具一看 NUMA 访问比例才发现。解法很简单用系统工具把推理主线程和草稿模型线程都绑定到 GPU 所在的 NUMA 节点并把相应内存页提前分配在本节点。改完之后只需要加一行启动参数吞吐立刻回升。这个坑在单路平台上完全遇不到但只要是双路平台排查性能问题时一定要优先检查。# 示意绑定到节点0并设置内存亲和性 numactl --cpunodebind0 --membind0 ./strata_engine --config infer_3090.yaml5.5 稳定性验证连续压测对调度器的真正考验性能达标之后我们又跑了 8 小时连续压测中间夹杂模拟的超长请求、空请求、超大 batch 峰值。整体吞吐曲线比较平稳平均在 105 到 110 tok/s 之间波动。但也暴露出两个新问题一个是冷启动阶段前几个请求因为专家缓存还没有预热首 token 延迟会高一些另一个是某些极端长序列会导致 KV 池短暂增长但水位线规则能及时把它压回去。针对冷启动我们加了一个预热流程服务启动后先用一组模拟请求把热专家缓存填满再对外提供正式服务。别看这个改动不起眼它让线上前 30 秒的平均首 token 延迟降低了约 40%。调完这套方案之后我最大的感受是所谓“老平台跑不了大模型”的说法很多时候只是没有把数据搬运路径拆开看。换一张更贵的卡当然省事但在预算受限的场景里把 PCIe 带宽当作和显存一样宝贵的资源来管理同样能交出过得去的成绩。这次调优也让我养成了一个习惯遇到底层性能问题先画带宽流水线再谈参数调整。
阅读完成 · 觉得有帮助?