
做推理优化的人都知道模型能不能在单卡上跑起来很多时候不是算力问题而是显存和带宽问题。前阵子我们内部要在海光K100AI上私有化部署MiniMax-M3这个模型本身的MoE结构和推理侧的部署压力碰在一起试了好几条路之后最后是靠MetaInfer这套推理引擎配合W8A8量化才算真正把吞吐和时延都做到可接受的水平。这篇文章把整个过程讲透从为什么弃W4A16选W8A8、量化校准怎么避坑到MetaInfer的具体配置、实测性能数据和几个我踩过的坑。如果你正在做国产算力的模型适配或者纠结W8A8值不值得搞这篇应该能帮你少走几天弯路。1. 先把背景说清楚MiniMax-M3是个什么量级的模型K100AI的算力边界在哪1.1 MiniMax-M3的MoE架构决定了推理优化的方向MiniMax-M3是MiniMax的第三代主力模型整体采用Mixture of Experts混合专家架构。这类架构的特点老玩家应该都熟总参数量做得很大但单次推理只有一部分参数被激活。M3的总参数量在百B量级激活参数大概控制在3B左右。换句话说它用相对小的单次计算量换来了远超Dense模型同等规模的知识容量。但在部署视角MoE架构有一个很现实的物理问题模型文件极大。几百GB的权重就摆在那哪怕激活参数再少想把完整权重塞进显存都得先过压缩这一关。当初我们评估M3的时候第一反应是激活参数这么小单卡不随便跑吗然后被模型文件大小教育了——MoE的推理优化逻辑和Dense模型完全不一样你优化的不是一个均匀的Transformer而是一堆专家网络加一个路由系统。第二个特征是长上下文能力。M3支持超长上下文实际部署我们目标是32K起步。这个点在后文第5章的KV cache膨胀问题里会展开这里先埋个伏笔MoE加长上下文这两个特性叠在一起显存规划的思路和普通模型完全是两码事。1.2 K100AI的硬件参数INT8算力是真正的突破口K100AI是海光面向AI算力场景推出的加速卡。我们手上这批卡单卡显存64GB HBMFP16BF16算力在百T以上INT8算力大概是FP16的两倍。这个规格放在当下市场里不算最顶级但有个特点很关键INT8算力支持是完整的W8A8这种方案在它身上能吃到真正的算力红利而不是像某些卡那样INT8只是个摆设。顺带提一句K100和K100AI的差异。K100是老型号算力和显存带宽都偏保守K100AI强化了INT8/FP8支持和高带宽显存对量化推理友好的程度明显高一个档次。如果你手里是K100而不是K100AI同样的W8A8方案性能预算要往下调优先看显存带宽够不够。还有一个容易被忽略的点卡间互联带宽。MoE推理里专家并行会产生大量All-to-All通信如果卡间带宽不给力扩展卡数反而会让单token延迟恶化。这个我们后面第5章专门踩过这里先确认结论——K100AI的卡间互联能力足够支撑2卡的MoE推理但往上加卡就得仔细权衡了。1.3 这是一篇什么性质的文章如果你手里也有一批国产加速卡做私有化部署正在纠结怎么把大模型压进显存、怎么把吞吐提上去这篇文章的场景和你的需求几乎重叠。我不打算只丢给你一份配置文件而是把每一步为什么这么做讲清楚。量化方案为什么选W8A8而不是W4A16MetaInfer的参数为什么要那样配连续批处理为什么对MoE模型格外重要——这些判断逻辑比配置本身更值钱。2. W8A8到底省了什么、赚了什么量化方案选型的逻辑2.1 四种主流精度方案的本质差异在动手之前先把方案底账算明白。大模型推理的性能瓶颈通常分两类一类是访存瓶颈受限于权重读取带宽另一类是计算瓶颈受限于GEMM算力。不同的量化方案本质是在这两类瓶颈之间做取舍。方案权重精度激活精度权重显存占用计算加速机制主要瓶颈FP16/BF16FP16FP161x无带宽FP16算力W8A16INT8FP160.5x权重侧可走INT8 GEMM激活反量化开销W8A8INT8INT80.5x权重和激活全走INT8 GEMM量化误差控制W4A16INT4FP160.25x权重反量化后FP16计算反量化开销、INT4算力浪费W8A8的核心逻辑很直接权重和激活全部压成INT8。权重压成INT8显存占用直接减半对几百GB的MoE模型来说是决定性的。激活压成INT8赚到的是算力——在支持INT8矩阵乘法的硬件上INT8算力通常比FP16翻倍。K100AI的INT8算力强于FP16所以量化得当的话理论上限吞吐接近翻倍。2.2 为什么在K100AI上选W8A8而不是W4A16这是被问得最多的一个问题很多人觉得W4A16权重压到INT4显存更省不是更好吗W4A16的显存收益确实是W8A8的两倍模型能塞进更小的卡。但它的致命缺陷在于INT4权重在参与GEMM之前必须反量化回FP16这一步既消耗额外的访存又增加了计算流水线延迟。更尴尬的是当前主流加速卡的INT4算力基本不会被GEMM直接使用实际计算还是在FP16精度下跑的。也就是说W4A16最终是省了显存但没吃到低精度算力红利。而K100AI的INT8算力是实打实能用的W8A8模式下权重和激活都走INT8 GEMM算力利用率远高于W4A16。我们同一套M3模型在2卡K100AI上实测W8A8的吞吐比W4A16高出大概四到六成。如果你的卡INT8算力够强W8A8的价值就是碾压级的。另外提一个更现实的考量部署成本。W8A8权重占用只是减半64GB显存的K100AI配合2卡就能把M3完整装下如果走W4A16虽然单卡就能塞下但推理延迟会恶化线上业务的SLA未必扛得住。与其省显存买延迟不如用合理的卡数换吞吐和稳定性。2.3 W8A8最难的地方激活量化的误差控制如果说权重INT8量化是匀速下降那激活INT8量化就是过山车。权重是静态的量化时可以通过统计全部数值分布来确定缩放系数误差总体可控。但激活是动态的每一条输入、每一层中间结果都不一样。更麻烦的是MoE路由的稀疏性会让不同专家拿到的激活分布差异很大同一个激活缩放系数在某个专家上合适在另一个专家上可能直接把数值截断带崩。我们最终采用的是混合粒度缩放方案权重按per-channel每个输出通道一个独立缩放系数激活按per-token每个token单独一个缩放系数。这个组合是W8A8工程落地最常见的形式误差和计算开销最平衡。但说实话这个平衡是靠实测试出来的不是看论文拍板定的。激活量化的另一个隐藏成本是动态求缩放系数本身也有计算开销。尤其在高并发场景这个开销会被放大所以引擎层面的算子融合就变得很重要这一点在第5章的坑3里会详细说。2.4 量化校准是决定成败的一步W8A8的校准calibration不能省这是整个落地过程里最容易被轻视的环节。M3的权重量化我们准备了一个覆盖数学、代码、通用对话、长文档摘要的混合校准集——注意这四类任务是刻意配进去的后面第5章坑1会讲为什么这么干。校准集大概2万条样本分batch跑一遍前向收集每个层的激活和权重分布再做缩放系数的计算和数值裁切。M3我们试了两条路线RTN四舍五入到最近整数和AWQ激活感知量化。最终AWQ效果更好尤其是专家层数值裁切对长尾异常值的容忍度高不少。校准过程还有个细节值得提校准时的batch size不能太小。batch太小时激活分布的代表性差算出来的缩放系数容易偏向个别样本部署后遇到真实流量会露馅。我们校准batch size控制在64跑下来花了几十分钟这笔时间花得值。3. MetaInfer执行W8A8推理的完整配置链路3.1 环境准备驱动、容器、引擎版本这一步看起来很基础但很多刚上手的人会卡在环境组合上。我们最终锁定的环境是这样的操作系统Ubuntu 22.04 LTSK100AI驱动与硬件配套的驱动版本装完后用设备管理命令能正确识别卡数和显存部署方式Docker容器镜像基于官方PyTorch镜像推理引擎MetaInfer的当前发布版本支持MoE结构、W8A8量化和连续批处理建议装完驱动后先跑一遍MetaInfer自带的硬件检查脚本确认INT8算力、显存带宽、卡间通信三个指标都在正常范围再往下走。我们遇到过驱动版本不匹配导致INT8报错的问题排查了很久最后就是重装驱动解决。3.2 从原始权重到引擎文件转换与校准命令模型转换流程分三步从模型库拉取原始权重跑量化校准导出引擎文件。MetaInfer提供了转换工具命令行风格大概是这样的metainfer-convert --model-type MiniMaxM3 \ --model-path ./MiniMax-M3 \ --quant-scheme w8a8 \ --calib-data ./calib/mixed.jsonl \ --calib-samples 20000 \ --calib-batch-size 64 \ --plugin awq \ --output ./M3_w8a8.miengine几个参数说明一下--quant-scheme w8a8指定W8A8量化方案--calib-data指向校准集我们用的是JSONL格式每行一条请求方便按任务类型采样--calib-samples控制校准样本量--plugin awq选择AWQ量化算法。校准集文件的构造这块多说两句。我们当时是先按任务分类每类任务抽固定比例然后打乱顺序灌成一个JSONL。这样校准的时候不会因为某类任务扎堆导致分布偏移。3.3 服务化配置文件与启动转换完引擎文件后下一步是写推理服务的配置文件。这是个YAML格式的文件里面每个核心参数都需要理解清楚model: engine_path: /models/M3_w8a8.miengine tensor_parallel_size: 2 max_batch_size: 64 max_seq_len: 32768 kv_cache_mem_fraction: 0.45 trust_remote_code: true quant: scheme: w8a8 activation_scaling: per_token weight_scaling: per_channel engine: use_continuous_batching: true max_num_seqs: 96 gpu_mem_utilization: 0.9 fuse_quant_ops: true enable_force_contiguous: false这里解释几个关键决策tensor_parallel_size: 2M3全量权重即使W8A8后仍然超出单卡64GB所以用2卡张量并行。第5章坑5会讲到tp4的不划算所以2是一个平衡点。kv_cache_mem_fraction: 0.45显存里KV cache的分配比例。这个值不能贪大要跟gpu_mem_utilization: 0.9搭配着看整个显存分配其实是模型权重 KV cache 激活临时缓冲区三方抢地盘。max_num_seqs: 96最大并发序列数。这个值和连续批处理行为相关第5章坑4里谈到了调大它的代价。启动命令很简洁metainfer serve --config config.yaml --port 8000启动后建议用一个简单请求先验证连通性再上压测不要一上来就灌全量流量。3.4 MoE专家加载策略常驻与动态加载MoE模型和Dense模型在部署侧有个专门优化点专家加载策略。K100AI单卡64GB装不下全部专家权重所以我们分了两级常驻专家通过统计路由日志把高频token命中的专家常驻显存动态加载低频专家按需求从内存/磁盘加载这个策略是我们实际在用的方案代价是高频路由场景性能理想但长尾任务偶尔会有一次加载延迟。如果不用这个策略就得裁掉一部分专家或压缩上下文长度这两个后果都不可接受。需要强调的是这个策略和量化是独立的优化维度——W8A8解决的是权重整体变小专家加载策略解决的是虽然变小了但依然装不完的问题。两者叠加才能让M3在2卡K100AI上稳定跑起来。4. 实测性能单卡吞吐、首token延迟与batch策略的关系4.1 测试是怎么设计的性能测试我们用一个内部压测工具模拟的是混合请求流短对话、长文档生成、代码补全混在一起。输入长度固定128 token输出长度256 token请求并发从4打到128。为什么这么设计因为线上真实流量就是这样混合的只看单一场景的数据没有参考意义。压测环境就是前面配置的2卡K100AIW8A8量化。重点观测四个指标TTFT首token延迟、TPOT单个输出token延迟、整体吞吐、P99延迟。4.2 关键性能数据总览表格里是batch size从8到64的数据。这里的batch size是指引擎实际参与计算的请求批大小不是单纯的并发数。batchTTFT (ms)TPOT (ms/token)吞吐 (tokens/s)P99 (ms)821821.53726551624224.36588243228729.8107411206434238.516621830对照一下W4A16的数据同样batch32吞吐大概在700-750 tokens/sW8A8的1074高出四成多。FP16我们在这个2卡配置下根本跑不起来权重就放不下。数据是实测的不同驱动版本、不同并发模型会有差异但趋势是稳定的batch越大吞吐越高同时TTFT会恶化。这意味着你不能无脑把batch开大——线上业务对首token延迟是有要求的后面第6章会展开怎么权衡。4.3 连续批处理对MoE模型的特殊价值MoE模型和Dense模型在连续批处理下的表现差距很大。Dense模型每个token的计算量基本平均而MoE模型每个token的计算量取决于路由到的专家组合不同请求的计算量差异可能非常大。举个例子一个请求如果路由到的都是热门专家跑得飞快另一个请求路由到一堆冷门专家加上动态加载延迟就会拖慢整体。连续批处理解决的就是这个木桶效应卡住的请求只占一个slot其他请求照常计算推进不会互相拖后腿。我们开了use_continuous_batching后P99延迟相比关闭时下降了约35%吞吐也涨了一截。对于MoE推理来说这个开关不是可选项是必选项。4.4 一个对照组Qwen3 27B单卡实测搜索K100AI相关信息的人经常问单卡跑Qwen3 27B什么速度我们顺手测了一个对照组。K100AI单卡加W8A8跑Qwen3 27B单请求生成速度在33 token/s左右batch开到32整体吞吐能到900 tokens/s。这个数字可以作为你判断K100AI算力水平的锚点也能侧面说明不同模型在同卡上的效率差异——M3这种激活参数小的MoE模型在批量场景下的吞吐上限远高于同规模Dense模型。5. 这五个坑我替你们踩过了量化校准、算子融合到显存复用这一章节是本文的核心价值所在。每个坑都按现象 → 排查链路 → 解决方式的结构讲清楚你大概率也会遇到其中几个。5.1 坑1校准集只覆盖对话代码专家直接崩了现象跑通后通用对话看起来一切正常但只要一写代码函数名变形成乱码还会出现大量无意义重复token。第一反应是解码参数问题调低temperature没用然后怀疑模板问题换掉prompt模板也没变化。排查链路我们开始在引擎里逐层打印激活值的分布情况对比对话请求和代码请求在进入不同专家前的数值范围。对比之后发现问题很明显——代码类请求在进入代码专家前激活分布和对话场景差异巨大INT8量化后的截断率异常高信息基本被削掉了。根因量化校准集只覆盖了对话样本。MoE模型不同专家分管不同任务类型负责代码的专家在训练时学到的数值分布和对话专家完全不同如果校准阶段根本没让模型见过代码数据代码专家的缩放系数就是瞎猜。解决方式重新构建校准集加入代码任务样本重新校准。校准完成后代码任务输出恢复正常通用对话质量没有下降。经验总结MoE模型校准集的覆盖面必须覆盖所有任务类型这是W8A8落地时最容易忽略的一步。你不一定要2万条样本但必须保证每类任务都有足够的分量。5.2 坑2长上下文场景KV cache膨胀OOM后进程被杀现象配置里max_seq_len设成65536压测长上下文场景时显存OOM推理进程直接被杀掉没有任何重试机会。排查链路用监控工具盯显存曲线发现KV cache峰值迅速逼近上限。再看配置文件kv_cache_mem_fraction设的是0.7gpu_mem_utilization也是0.9两个参数叠一起结果就是KV cache抢了激活缓冲区的地盘一旦遇到长请求就崩。这里给一个KV cache大小的估算公式方便你自己算单请求KV cache 2K和V × 层数 × 隐藏维度 × 序列长度 × 精度字节数以M3这种激活参数3B左右的MoE模型为例假设48层、隐藏维度4096、单请求8192 tokenFP16下单请求的KV cache大概是2 × 48 × 4096 × 8192 × 2字节 6.4GB。压到INT8就是3.2GB跑64路并发时这个数字要乘上去KV cache很容易比模型权重还大。所以KV cache配置抠得细不细直接决定长上下文场景会不会崩。解决方式显存分配重新规划模型权重、KV cache、激活临时缓冲区三者分开设定。最终配置是kv_cache_mem_fraction下调到0.45gpu_mem_utilization保持0.9。调完后长上下文压测稳住了没有再出现OOM。5.3 坑3算子融合没生效INT8算力等于白开现象量化方案配好了性能反而比FP16还低。这就很离谱——理论上INT8算力翻倍就算不是翻倍提升也不该倒挂。排查链路先在引擎里开debug日志看算子层执行计划。结果发现GEMM算子确实走了INT8但前后配套的量化/反量化算子完全没有融合每一层都多做两次完整的数据搬运——这比省下的算力还贵。本质上量化后的数据在内存里反复横跳访存开销把INT8的算力优势吃了。解决方式打开fuse_quant_ops: true让量化、GEMM、反量化合并成一个融合算子。打开后性能立刻回升吞吐相比融合前提升了60%以上。这个坑提醒我一点配置W8A8不只是设个量化方案还得确认引擎真的把量化算子链路优化到了位。不开融合开关你买到的INT8算力就是纸面参数。5.4 坑4并发到阈值后大批请求失败现象并发打到128附近服务开始报KV cache slot exhausted请求失败率飙升但显存明明还有余量。排查链路检查max_num_seqs配置当时设的是128。但奇怪的是引擎报告KV cache的block还有空闲为什么slot会耗尽查了内部调度逻辑才发现MoE模型的专家调度slot和普通KV block slot的分配策略有冲突。部分专家动态加载时占用了额外的slot导致KV block虽然还有但已无可调度位置。解决方式把max_num_seqs下调到96。看似损失了并发数实际上吞吐没有下降稳定性反而上来了。后续用更细粒度的监控验证96这个数字在M3加W8A8加2卡K100AI的组合下是最优的。5.5 坑5多卡通信盖过了算力收益现象张量并行度从2提升到4想着算力翻倍结果吞吐几乎没动单token延迟还涨了。排查链路用性能剖析工具查看卡间通信耗时发现All-to-All通信占比超过40%。MoE模型每层路由之后都要做一遍专家间的数据交换4卡场景通信量翻倍把算力收益全部吃掉了。卡间互联带宽就是这一场景的硬瓶颈。解决方式退回2卡配置同时在引擎里调整了专家并行和张量并行的比例关系。最终结论是MoE模型在多卡场景不是卡越多越好通信和算力的平衡一定要实测不要靠直觉判断。6. 把K100AI的算力榨干的四个方向6.1 batch策略要对着业务SLA来调不是无脑上第4章的表格已经展示了batch和延迟的反向关系。实际操作中我们按业务类型分别设置batch上限交互类场景batch控制在32-48TTFT能压在300ms附近对延迟不敏感的离线批量任务可以打到64以上换更高的吞吐。核心原则是batch不是能开多大就开多大而是让TTFT满足SLA的前提下尽量大。6.2 PD分离prefill和decode各干各的前面的优化基本都在同一批请求同时吃prefill和decode的框架下。进一步优化的方向是PD分离prefill阶段是计算密集decode阶段是访存密集两者的资源需求完全不同。把prefill和decode分到不同副本或不同卡上TTFT能再降20%到30%。这是目前业界公认的方向我们在K100AI上也验证了可行性后续版本可以考虑落地。6.3 KV cache量化是显存的第二个大头模型权重已经W8A8了下一步就该盯KV cache。M3的长上下文本地部署KV cache在FP16下很容易超过模型权重占用的显存。如果把KV cache的K和V压到INT8甚至FP8长上下文场景的显存占用还能再降一半。注意KV cache量化的误差更敏感不是所有模型都能直接压但M3在我们的测试里表现不错。6.4 MoE路由调度让专家被热起来最后一层优化是调度策略。常规调度按请求到达顺序处理完全没有考虑MoE模型专家复用的可能性。如果能让路由到相同专家的请求聚合到同一批专家权重在显存和计算单元里的复用率会大幅提升整体吞吐还能再上一个台阶。这个方向MetaInfer已经预留了接口我们正在基于路由日志做实验目前在小流量验证下吞吐提升约15%。讲到最后说一点个人感受。做国产算力上的推理优化最磨人的不是模型跑不起来而是你永远不知道瓶颈到底是显存、带宽、算力还是调度策略。这个项目跑完回头看W8A8量化解决的是能不能装下的问题MetaInfer的配置解决的是怎么把INT8算力用起来的问题而那五个坑才是真正让方案从能跑变成能上线的关键。如果你也在做类似的事情希望这篇记录能帮你少踩几个我们踩过的坑那就是它最大的价值了。