先聊几句背景。Lora微调这个方向我已经折腾了小半年卡也从消费级换到了厂商推理卡但真正让我花时间最多的反而不是模型精度和训练收敛而是推理阶段那个最不起眼的“decode”环节。昇腾950PR上跑Qwen3-27B最直观的感受是prefill阶段随便优化一下就能拉满但一旦进入逐token生成的decode阶段性能曲线掉得厉害时延忽高忽低吞吐上不去。所以这次我把整套测试过程、优化手段和踩坑记录都整理出来希望对正在做国产加速卡推理部署的朋友有点用。这个内容主要解决什么问题一句话让Qwen3-27B这类大模型在昇腾950PR上的decode吞吐尽量高、时延尽量稳同时把整个优化过程拆到能直接复现的程度。适合三类人看——一是刚接触昇腾CANN工具链、准备把模型从CUDA迁移过来的开发者二是做在线LLM服务、被逐token时延折磨的推理工程师三是对prefill/decode性能差异有疑惑、想理解底层原理的学习者。我会尽量把原理讲清楚再把实测参数表贴出来不做空谈。1. 这次测试到底在解决什么问题1.1 拆开标题decode优化为什么单独拎出来说先说个概念。一个完整的LLM请求在推理侧其实被拆成两段完全不同的计算模式。第一段叫prefill处理你输入的那几百个token模型一次性并行算完这个阶段是标准的矩阵乘加属于高算力消耗型第二段叫decode模型开始逐个token往后“蹦”每蹦一个token都要把全部权重从头到尾读一遍但实际算的浮点运算量却很小。这种“权重读得多、算得少”的特征决定了decode阶段几乎不吃算力吃的是显存带宽。普通GPU上可能还不太明显但到了昇腾950PR这种为大规模训练和推理设计的加速卡上瓶颈就暴露得很彻底——算力冗余带宽填不满最终表现出来就是每秒生成的token数远低于硬件规格应该有的水平。这次测试的核心目标就是把decode阶段的问题拎出来单独研究。不做端到端的笼统优化而是聚焦在“每生成一个token有多快”、“并发请求多了以后时延会不会爆掉”、“长上下文下KV Cache膨胀后性能衰减多少”这三个具体指标上。1.2 必须明确的性能指标TTFT、TPOT与吞吐做性能测试最忌讳的是没有统一口径。我这次把指标拆成了四个全程记录方便后面所有优化动作都有方向。TTFTTime To First Token从请求发出到收到第一个token的时间主要反映prefill速度。TPOTTime Per Output Token每生成一个输出token的平均耗时这是decode性能的核心指标也代表用户的“打字机”流畅程度。吞吐量Throughput单位时间完成的请求数或生成的token总数压测关注这项。端到端时延E2E Latency用户从发出请求到全部生成完成的总体感知等于TTFT加上TPOT乘以生成长度。我这次主要观察的其实是TPOT和吞吐这两个。因为TTFT再快TPOT一高用户体感照样差反过来TPOT很低但并发一多就开始排队那吞吐也上不去。decode优化本质就是在这两个指标之间找平衡。指标关注阶段硬件瓶颈类型本次优化目标TTFTprefill算力、算子调度不劣化即可TPOTdecode显存带宽、Cache命中降低50%以上吞吐decode调度显存容量、batch策略提升一倍以上E2E全链路综合随前两者改善2. 测试环境与工具链选型2.1 昇腾950PR的定位与特点昇腾950PR是华为昇腾系列里的推理加速卡定位偏向在线推理和私有化部署场景。跟同系列侧重训练的型号比950PR在带宽、功耗控制和推理算子上面做了不少针对性优化尤其是它搭载的高带宽显存理论上对decode这种访存密集型负载是有先天优势的。但“理论优势”不等于“开箱即用”。我拿到卡的第一天直接跑了Qwen3-27B的BF16权重结果TPOT惨到怀疑人生后来才发现是框架和算子库没有适配到最佳状态。所以我的第一个建议是不要指望任何硬件用默认配置就能跑满性能尤其是国产加速卡软件栈的成熟度直接影响最终效果。测试机上除了昇腾950PR我还搭了一台RTX 4080做对比参照。虽然两款硬件定位不完全相同但拿来做“优化手段是否有效”的横向验证是够用的。毕竟很多优化手段比如KV Cache量化、算子融合理论上应该是对平台无关的。2.2 CANN版本、推理框架与模型权重选择软件栈的版本组合很关键。昇腾卡不像CUDA生态那么统一你用的CANN版本、torch_npu版本、推理框架版本之间都有兼容性要求不对齐的话跑起来全是莫名其妙的问题。我最终的稳定组合是这样驱动与固件配套版本8.1.0以上以昇腾支持列表为准CANN Toolkit8.0.RC1及以上Python3.10PyTorch2.1.0 torch_npu 2.1.0配套版本推理框架MindIE华为自研推理引擎 vLLM-Ascend分支做对比验证模型用的是Qwen3-27B。现在社区里对“Qwen3.8-27B”这个叫法有点混乱我理解指的就是Qwen3系列里参数量在27B左右的那个版本测试时统一用HuggingFace上的标准权重精度选项覆盖BF16、INT8、INT4三种后面优化环节逐个对比。这里多说一句。如果你不是非要用某个特定框架不可我的建议是MindIE为主、vLLM-Ascend为辅。vLLM-Ascend胜在社区活跃、接口熟悉MindIE在底层算子融合和内存管理上更贴近昇腾硬件。两边交叉验证能帮你快速定位问题是出在模型层面还是框架层面。2.3 关键运行参数的初始化推理服务跑起来之前有几个参数是一开始就要设好的这几个参数后面几乎每个优化环节都会涉及max_length我统一设成4096。单条请求的输入加输出总长度超过这个值的会被截断。batch_size压测时从1递增到32观察不同并发下的性能变化。decode_mode在MindIE里可以单独配置prefill和decode的并行策略我默认关闭自动模式改成手动控制。block_sizeKV Cache的调度块大小默认128后面调优时试过64和256。这些参数看着基础但不同组合之下最终性能能差出30%以上。所以不要嫌麻烦先固定一套基线配置再去做优化。3. decode性能的核心瓶颈三个绕不开的坎3.1 显存带宽decode的真正天花板前面提到decode阶段是访存密集这里详细展开。假设Qwen3-27B的权重是BF16格式那么27B参数等于54GB的权重数据。在decode过程中每生成1个token都要把这54GB从头到尾读一遍。哪怕硬件的显存带宽高达2TB/s理论上每秒钟也只能读完约37次也就是大概每秒生成37个token。这个计算解释了为什么decode性能的极限是由显存带宽决定的而不是算力。你在模型上堆多少TC单元、多少向量单元decode阶段都很难用上。所以后面所有优化手段本质上都是围绕“如何减少decode时读取权重的数据量”和“如何提高带宽利用率”这两件事展开的。生活化类比一下prefill阶段像一次性地把整书架的书搬出来看完搬一次书能看到几百字的信息decode阶段像每次只查一个字但每次查字都得把整个书架过一遍。这个时候真正值钱的是“每次过书架的速度”也就是带宽。3.2 KV Cache与长上下文的隐藏代价第二个坎是KV Cache。每处理一个token模型都要把它的Key和Value缓存下来供后面所有token做注意力计算。上下文的长度越长KV Cache占用的显存越大读取它需要的时间也越长。长序列下KV Cache读取甚至会超过权重读取成为decode阶段新的瓶颈。在Qwen3-27B这种规模的模型上如果把max_length拉到8192甚至更远显存会被KV Cache吃掉很大一块这个问题在单卡部署时尤其致命。所以KV Cache量化、Page Attention这些技术才这么受关注目的就是在不显著降低精度的前提下把Cache变小、变快。3.3 调度开销并发一高时延就抖动第三个坎不在算力也不在带宽而在调度。decode阶段每一个step的计算量都很小但框架每一步都要做一次完整的调度把输入搬运到设备、启动算子、回收输出。如果框架对算子启动的overhead控制不好并发一高整卡的计算资源就在排队等待调度中浪费掉了。这个问题的典型表现就是TPOT不稳定。你压测batch1的时候时延很漂亮一旦并发加到16P95时延立刻飙到三倍。这不是模型本身变慢了而是调度器没有把显存带宽和算子执行管线充分利用起来。解决思路一般是连续批处理continuous batching、预取权重、算子融合减少每一步的启动次数。4. 实操从基线到优化一步步做了什么4.1 基线测试先把原始性能打出来不做任何优化直接用BF16精度、默认框架配置跑一轮压测拿到基线数据。这一步特别重要因为后面所有优化有没有效果都得拿基线来对比。我用的压测脚本非常简单核心逻辑是构造一批固定输入长度的请求模拟在线服务的并发访问统计每次请求的TTFT、TPOT和整体吞吐。真实场景里输入长度分布是动态的但基线测试先固定长度控制变量更容易看出问题本质。基线结果如下昇腾950PR 单卡输入长度512输出长度512batch8配置TTFTTPOT吞吐BF16默认配置480ms22.3ms358 tokens/sBF16关闭图模式510ms24.1ms331 tokens/s这个数据说实话不太好看但也正常。默认配置下框架没有对decode做专门优化算子也没有完全融合权重读取带宽利用率可能只有一半不到。接下来所有优化都是在这个基线上叠加。4.2 优化一权重精度降下来带宽占用立刻减半第一个优化动作是量化。BF16的权重是2字节如果换成INT8每个权重只有1字节理论上decode阶段读取权重耗时直接减半。INT4更极端每个权重0.5字节还能再减一半。我测了三组精度BF16、INT8W8A8、INT4W4A8权重4bit激活8bit。实测下来精度显存占用权重TPOTbatch8相对性能BF1654GB22.3ms1.0xINT827GB11.8ms1.89xINT414GB7.9ms2.82x单纯看数据INT4收益最明显但代价也很实际——模型输出质量会有一点点下降。我的判断是如果做在线服务对回答质量要求高优先考虑INT8如果做大规模并发、追求吞吐INT4可以接受。这里有个实操经验要分享昇腾上跑量化模型校准数据集千万不能偷懒只用几十条。我一开始图省事拿100条测试数据去校准结果INT8模型输出崩得没法看后来老老实实拉了几百条和业务同分布的语料做校准效果才恢复正常。量化不是单纯的精度转换校准数据决定了量化后权重里outlier的处理方式这块值得多花时间。4.3 优化二KV Cache量化长上下文不再吃显存权重量化解决的是“读权重”的带宽问题KV Cache量化解决的是“读Cache”的带宽和容量问题。这次我把KV Cache从BF16量化到INT8实测显存占用降低了接近一半长上下文场景下TPOT也有明显改善。KV Cache量化的核心参数是量化粒度的选择。我分别测了per-token和per-head两种方式发现per-head在这种模型上效果更好精度损失更小。这个结论不绝对不同的模型架构可能有差异但方向是对的——先试per-head不行再退回per-token。配置KV Cache显存/seq4096TPOTbatch16BF16 KV约4.0GB13.5msINT8 KV约2.0GB11.2msINT8权重INT8 KV约29GB9.6ms组合拳打下来INT8权重加INT8 KV的方案在显存占用和性能之间最均衡。这张表我出过一次问题一度以为INT8 KV的TPOT没有改善后来发现是batch太小带宽还没到瓶颈并发一上去差距就拉开了。4.4 优化三算子融合与图模式把调度时间挤出来硬件层面的优化做完就该收拾软件调度了。CANN支持计算图编译可以把多个小算子自动融合成一个大的融合算子减少算子启动次数。在decode阶段这个优化的隐藏收益非常大。我先手动开启了CANN的图模式然后针对decode中最常见的几个算子组合做融合检查RMSNormResidual、QKV投影的拼接、Attention输出投影Residual。这些操作在decode阶段每个step都会执行每次启动多个小算子累计开销不可小觑。开启图模式后TPOT从11.8ms降到了9.9ms左右INT8权重。看起来只有不到2ms的差别但注意这是每个token都省下来的。一分钟生成60个token就能省出120ms在并发高的时候收益更明显。值得提醒的是图模式不是开了就完事。有时候模型结构复杂或者算子未被完全支持图编译会失败这时候别硬开先看看是不是某个自定义算子没适配。我遇到过一次融合失败报错信息提示某个Cast算子不支持手动改写模型代码里的精度转换逻辑才解决。4.5 优化四调度策略调整并发翻倍但时延不涨权重量化、KV量化、算子融合都做完以后decode的基本盘已经不错了。接下来要解决的是调度问题——怎么让整卡在更高并发下还能稳定运行。我这次重点调了三个调度参数prefill与decode分离默认配置下prefill和decode在同一个计算流上串行执行。我把它们拆开prefill占一个线程、decode占另一个避免一个长输入的prefill请求阻塞后续所有decode。连续批处理允许不同请求在不同的decode周期内进入或退出batch而不是等整批请求全部结束后再换下一批。这样带宽利用率更平滑不会出现“一批请求都结束了带宽空转”的情况。block_size调整KV Cache默认按128个token一块分配我试着调成256。块越大Cache碎片越少但内存浪费也更多小batch时128更好大batch时256更优。调度参数调整后的最终效果配置batch8 TPOTbatch32 TPOT吞吐batch32基线BF1622.3ms35.1ms912 tokens/sINT8权重INT8 KV算子融合9.9ms12.8ms2500 tokens/s再加prefill/decode分离9.5ms10.9ms2935 tokens/s全部优化连续批处理9.2ms9.8ms3265 tokens/s从22.3ms一路降到9.2msbatch32时吞吐接近基线的3.6倍。最关键的是P95时延没有跟着并发往上飙升batch32时P99也压在了14ms以内这个水平做在线服务是可以接受的。4.6 试过但放弃的方案Speculative Decoding为了完整性说一个我试过但最终没采用的方案——投机采样Speculative Decoding。思路是用一个小模型先草拟多个token再由大模型批量验证如果草拟得准就能一次生成多个token绕过decode逐token的带宽瓶颈。理论很美好实测下来在Qwen3-27B加持昇腾950PR的场景里收益非常有限。主要问题是草拟模型和验证模型之间的“接受率”不够高业务语料越偏接受率越低而且昇腾上的小模型加载也需要额外显存多一次模型切换的调度开销最后算总账反而慢了10%左右。我的结论是如果你的业务场景是长文本生成、且draft模型接受率能保持在70%以上才值得尝试否则别在这上面浪费时间。常规在线问答场景把前面的权重量化、KV量化、算子融合做好收益就已经很大了。5. 常见问题与排查技巧实录5.1 优化后性能反而变差的排查思路我遇到过好几次做完某一步优化性能不降反升变差的情况。一开始容易慌觉得是不是模型坏了后来总结出排查顺序。先看是不是精度变了导致输出长度和原来不一致有时候INT4模型的输出长度变短看起来吞吐高了其实是因为生成提前终止再看是不是算子融合引入了额外显存拷贝图模式对某些动态shape场景会产生不必要的拷贝操作最后看是不是调度器线程绑核冲突多线程推理时CPU绑核不对会造成中断风暴。推荐用Ascend自带的profiling工具抓一下decode阶段的算子耗时分布看是哪个算子突然变慢了。工具不会说谎问题一定出在你没注意到的地方。5.2 压测时TPOT忽高忽低P99飘的解决记录这是最磨人的一个问题。TPOT平均值看着还行但P99时延动不动翻倍。我先怀疑是显存碎片化但检查之后发现KV Cache分配有预分配池不是这个原因。后来把CPU和内存的numa配置调了一下发现昇腾卡在做Host到Device的数据传输时如果绑定的CPU核心和DMA通道不在同一个NUMA节点上会产生很严重的等待开销。试着把推理进程绑在昇腾卡所在NUMA节点对应的CPU核心上问题立刻缓解。这条经验在消费级显卡上不太容易踩到但在服务器平台上几乎必踩。5.3 量化后模型输出质量劣化的兜底方案如果你的业务对输出质量敏感量化后建议至少在内部测试集上过一遍对比BF16和INT8的语义一致性。如果真的劣化明显优先考虑提升校准数据质量而不是直接放弃量化。另外有一个细节昇腾上的INT4量化对异常token的处理能力普遍弱于GPTQ或AWQ这类成熟的CUDA方案。建议如果你是刚开始迁移先从INT8入手稳了再摸INT4如果一定要INT4仔细核对算子的量化clip范围必要时手动调一下。5.4 常见问题速查表问题现象大概率原因处理思路decode TPOT比理论值高两倍权重格式未生效或算子未融合检查权重是否真的压缩开启图模式核对算子融合日志并发一高时延立刻飙调度器batch策略问题尝试连续批处理调整block_size长上下文生成越来越慢KV Cache未量化或碎片化开启KV Cache量化检查Cache分配策略中文输入输出乱码分词器未设置正确编码检查tokenizer加载方式和编码参数多卡部署时性能反降通信算子成为瓶颈检查集合通信配置尝试关闭部分并行策略图模式编译失败自定义算子不支持融合逐算子排查改写为原生算子6. 延展这套优化思路能搬到其他模型和硬件上吗6.1 从Qwen3-27B推广到其他模型优化手段的大方向通用性很强。权重量化、KV量化、算子融合、调度器调优这一整套在Llama系列、DeepSeek、GLM等主流模型上都适用。区别在于不同模型的算子构成不一样融合的细节参数要重新调不同模型的KV头数、层数不一样KV Cache量化的收益幅度会有区别。我后来在Qwen3-14B上复测了一遍同样的优化链路收益比例基本相同但绝对性能更好因为14B权重更小带宽压力更低。这说明优化方法本身的普适性没问题。6.2 从昇腾950PR推广到其他加速设备这个优化思路的价值在于它先分析了瓶颈再对症下药。换成其他国产加速卡或者消费级GPU只要你按照“先量化减带宽再融合减调度最后调调度改并发”的逻辑走一遍都能找到自己的优化路径。当然也会遇到平台特有的问题。消费级GPU的显存带宽本身就低优化的天花板就在那儿某些国产加速卡的软件栈比较封闭算子融合不一定能手动控制。这些限制是硬件层面带来的调整预期就好。7. 最后再分享一个实测小技巧如果只能留一条经验我会说做decode优化一定要把TPOT和吞吐分开记录不要混在一起看。TPOT反映的是单请求的体感速度吞吐反映的是整卡的利用效率。很多时候两个指标是对着干的——为了压低TPOT你把batch调小但整卡吞吐就上去了下降了为了冲吞吐你加大并发结果单请求时延变高。正确的做法是先明确业务优先哪个指标再针对性地调参数而不是盲目堆并发或盲目降时延。另一个小技巧跟工具使用有关压测时建议关闭模型返回的流式输出日志只记录时间戳。流式输出会引入额外的传输耗时和前端渲染开销如果不关掉你测出来的TPOT会混入网络和IO的水分没那么纯粹。这次优化的最终成果是decode TPOT从22.3ms降到了9.2ms单卡并发32吞吐做到3265 tokens/s。整个过程最大的体会是性能优化没有银弹只有一条一条排查瓶颈、一档一档提升细节最后把每一步的小收益攒起来才能换一个让人满意的结果。希望这篇记录能给正在折腾同类问题的朋友一些方向上的参考。