
1. 推理延迟的账单是怎么算出来的先搞懂瓶颈在内存带宽而不是算力先说个真实场景。某公司内部基于开源模型搭了个智能助手模型选的是7B级别显卡拉满到8卡A100结果上线第一天就炸了——并发一超过20路P99响应时间直接飙到8秒开外GPU利用率却只有可怜的15%。当时团队所有人的第一反应都是算力不够加卡。但把profiling数据拉出来一看问题根本不是算力不够是显存带宽被吃满了。这件事其实是绝大多数LLM推理项目的通病我们对大模型的直觉还停留在训练很贵、推理应该还好的阶段但训练和推理的成本结构完全相反。训练是计算密集型矩阵乘法吃得越满越好推理的自回归解码autoregressive decoding则是每次只生成一个token而且每个token的生成都要把整个模型权重从显存里读一遍。7B模型光权重就接近14GBFP16H100的显存带宽约3.35TB/s理想情况下每生成一个token至少要读一次全量权重这决定了单token的理论延迟下限。你可以把整个推理过程类比成一个人查字典写作文每写一个字都得把整本字典从头翻一遍找到合适的词再落笔。翻字典这个动作消耗的时间远比想这个词怎么写要多得多。所以真正制约推理速度的是翻字典的速度——也就是显存带宽——而不是你的脑子转得多快。实际测算方法很简单。以7B模型、FP16精度为例单token生成需要读取的权重量14GB显存带宽按3TB/s估算H100/A100级别单token纯读取耗时14GB ÷ 3TB/s ≈ 4.6ms这还没算KV Cache读取、注意力计算、采样等开销。如果再叠加并发场景所有并发请求都在抢同一条带宽单token延迟会进一步恶化。所以推理优化的核心命题不是把算力用得更满而是把每次翻字典的代价降下来——要么少翻几页量化、剪枝、蒸馏要么翻得更快更短缓存、投机解码、调度优化。这部分认知不建立起来后面所有优化动作都会变成瞎调参。下面我按实际项目的推进顺序把每一步能拿到什么收益、会踩什么坑原原本本过一遍。2. 量化不是无脑降精度权重INT4 KV Cache INT8的组合玩法量化是推理优化里见效最快的一招也是最容易翻车的一招。很多人一上来就上INT4结果模型回答质量肉眼可见地下降然后又灰溜溜调回FP16。根本原因在于他们对量化的敏感度分布没有概念把量化精度理解成了全局统一降准。2.1 哪些层对量化敏感哪些层无所谓以我实际做过的7B/13B模型实测来看注意力层的QKV投影对量化非常敏感而MLP层尤其是Feed Forward Network的前两层投影相对钝感。直观理解是QKV的输出会被拿去算注意力分数这是模型做信息路由的关键环节稍微有点精度损失就会导致该注意的地方没注意到而MLP本质是高维空间的特征变换误差经过ReLU/SiLU这类非线性激活会被部分抹平。所以现在业界主流的方案已经不太推荐纯靠训练后量化PTQ一把梭而是用带权重重要性感知的量化方法。我在工程里长期用的组合是权重部分用INT4量化配合AWQ或GPTQ方案KV Cache部分用INT8量化激活值activation保持FP16这个组合的收益是显存占用大幅下降同时质量损失基本可控。7B模型FP16权重占约14GBINT4量化后降到约4GB加上KV Cache的INT8化一共能省出接近10GB的显存。这意味着原来只能放下一个7B模型的机器现在可以同时跑两个或者把上下文长度从8K拉到32K级别。2.2 AWQ和GPTQ怎么选、按什么标准选AWQ和GPTQ是目前开源生态里用得最多的两种方法区别在于它们解决问题的路径不同GPTQ的思路是逐层量化、按列做误差补偿通过对每层权重的二次误差做近似逆向求解适合离线批量量化精度控制相对均衡。AWQ的思路是找出对输出影响显著的重要权重通道通过缩放因子保护它们不需要反向传播速度更快而且在小模型上的平均质量表现普遍更好。我从多个开源模型上的实测结论是7B及以下量级AWQ明显更稳13B以上两者差距缩小但AWQ的显存开销和量化耗时仍然更低。实际操作中我基本都走AWQ然后对量化后的模型跑一遍校准集上的困惑度perplexity和几个核心业务prompt的对比测试确认退化在可接受范围内再上线。2.3 KV Cache量化最容易忽略的坑这里重点提醒一个很多人踩过的坑KV Cache INT8量化不是改了配置就万事大吉要关注注意力分数可能出现的异常放大。KV Cache是自回归解码中缓存的历史token的Key和Value本身是激活值没有weights的量化保护。如果某个token的值恰好落在量化区间的边缘反量化误差会被注意力权重放大导致生成质量劣化。实测中最典型的表现是长上下文场景下模型突然出现忘事和重复生成的情况排查半天发现是KV Cache量化在超过一定长度后开始打折扣。我的经验做法是中小模型7B-13B默认开启KV Cache INT8收益大于风险对质量要求极高的场景如代码生成、金融报告生成先做5000 token以上长度的对比测试如果出现长上下文质量劣化把KV Cache切回FP16只保留weight INT4损失一点显存换质量稳定注意量化方案本身没有绝对的好坏选型的标准永远是你的业务场景最不能接受哪一类损失。省显存、保质量、降时延这三者需要按业务属性排优先级。这个阶段的成果是实打实的我跑过一个13B模型的服务量化前P99生成长度为512 token的回复约9.8秒量化后降到约6.2秒同时单卡并发容量从8路提升到16路。后面所有更复杂的优化都是在量化这个地基上叠加的。3. 投机解码与前缀缓存用预判和记忆换推理速度的两条隐蔽路径量化解决的是每次翻字典的代价而投机解码Speculative Decoding和前缀缓存Prefix Caching解决的是能不能少翻几次字典——前者用更小的模型预判大模型的输出后者让重复计算的上下文直接复用结果。3.1 投机解码的核心逻辑与实测收益投机解码的思路很有意思自回归解码是一个token一个token地生成每一步都要等大模型跑一遍。但如果你用一个很小、很便宜的草稿模型draft model先去猜接下来五六个token会是什么然后把这几个token一次性喂给大模型验证——大模型一次前向计算就能给出这几个token是否被接受的确认。猜对了几个就等于只花一次大模型推理成本就生成了几个token速度自然上去了。这里有一个关键前提草稿模型必须和大模型共享tokenizer和词表否则draft token和target token的映射对不上整条链路直接废掉。这也是为什么业界的草稿模型基本都是同系列小规模版本比如7B/13B的蒸馏小模型或同词表的1B左右模型而不是随便拿一个别的模型来凑。我实测的收益分两种情况批处理规模小单batch或并发低时投机解码收益非常明显一个8B主模型配一个1B草稿模型单线延迟能降40%到50%。批处理规模大并发高、batch size大时收益反而变小。因为大模型本来就在高并发下接近满负载运转草稿模型预判的token命中率跟不上大batch的吞吐节奏还会额外占用显卡算力。所以我的建议是如果你的业务是低并发的实时交互比如Agent单轮调用、IDE代码补全投机解码非常值得上如果是高并发纯吞吐型业务比如批量摘要生成把算力留给主模型更划算。3.2 前缀缓存让重复的Prompt不再重复计算前缀缓存解决的问题更朴素也更常见同一套系统提示词system prompt、同样的多轮对话历史、同一批few-shot示例在每次请求中都要被从头到尾做一次prefill计算——这些计算其实是重复的完全没必要。vLLM中RadixAttention的实现思路是把请求的Prompt前缀做树状缓存后续请求如果命中了相同前缀就直接复用已缓存的KV状态跳过一次前向计算。命中率越高吞吐提升越明显。我实际观察到的效果在某些Agent场景系统提示词加上历史对话模板占了请求输入的大头前缀缓存命中后prefill耗时能下降70%以上。一个17B模型原本平均每请求要处理3000 token的重复上下文缓存命中后只剩新增的几百token需要计算整体时延下降大约30%。但前缀缓存有个隐性成本需要留意——缓存会占显存。如果并发请求不同前缀太多缓存命中率低KV Cache缓存反而挤占了正常推理的空间。所以一般要给缓存区域设一个上限超过阈值就把最老的缓存逐出保证推理本身的显存优先级。我个人的经验值缓存区域显存占总显存的10%到20%之间比较健康。命中率低于50%的业务场景建议直接把缓存调小把显存还给并发推理。4. 服务框架选型vLLM、TensorRT-LLM和SGLang真实差距在哪优化做到这一步你的模型和推理内核已经具备不错的性能基座但真正决定线上表现的是服务化调度框架。现在开源主流基本是vLLM、TensorRT-LLM简称TRT-LLM和SGLang三分天下。我三个都用过也做过全流程的横向对比说点不是文档里能直接看出来的东西。4.1 三家框架的核心差异对比维度做法引入效果生效方式加载权重时近似计算向量化对比速度极快毫秒级完成对比维度vLLMTensorRT-LLMSGLang调度机制PagedAttention动态batch灵活静态batch图优化需显存预分配RadixAttention前缀缓存核心连续batch内核优化丰富但深度一般对特定GPU深度优化延迟最低前缀树缓存机制最强Agent场景适配好部署难度极低vLLM直接跑较高需要模型转换和build engine中低API风格简洁高并发吞吐优秀优秀但调优门槛高优秀尤其长上下文场景生态兼容性最好HuggingFace模型即插即用一般需要格式转换较好原生支持多种模型适合场景快速上线、多模型灵活切换极致性能、固定模型长期压榨Agent、长上下文、高前缀复用从实测的角度补充几个数字。我在A100 80GB上做过一组并发布比较vLLM在并发128路的场景下单卡吞吐约3800 token/s7B模型P99时延约2.4秒TRT-LLM在同样条件下吞吐约4200 token/sP99时延约2.1秒但整个engine构建和调优花了我两天时间SGLang在同样的模型和并发下吞吐约4000 token/s但P50时延明显低尤其多轮对话场景中因为前缀缓存命中率高。4.2 选型建议别只看榜单上的吞吐数字很多团队选框架只看峰值吞吐数字这其实是个误导。部署框架的选型维度需要考虑一是你的流量特征。高并发纯粹填吞吐TRT-LLM肯定香但如果你的请求是Agent式的多轮交互每次请求都带着很长且重复的系统提示词和历史上下文SGLang的前缀缓存会带来跨请求的收益这是单纯吞吐榜上根本看不出来的。二是工程维护成本。我用TRT-LLM踩过最大的坑是模型每次改动都要重新构建engine整个流程在CI/CD里很重vLLM则基本可以做到模型文件丢进去就能部署迭代速度完全不同。三是团队的技术底盘。如果你的团队是偏业务应用、不太愿意在内核层面折腾vLLM是稳妥起点如果有专职推理优化工程师TRT-LLM值得花时间。我的做法是线上核心服务先用vLLM稳定跑起同时用SGLang做竞品验证如果某个模型在vLLM上前缀缓存收益不明显再考虑切换。框架切换是最后一步不是第一步。5. 服务参数调优从能跑到跑满四个关键旋钮逐个拧框架选好了模型也量化好了但这只代表服务能跑。很多团队卡在这一步——明明模型没毛病、显卡也不算差吞吐就是上不去时延就是压不下来。这一节说四个最关键的旋钮全部来自我踩过的坑和调过的参数。5.1 max_num_seqs并发数与显存使用的平衡杆大多数框架默认的max_num_seqs都很保守比如vLLM默认值通常只有几十甚至更低。这个参数控制了框架最多同时处理多少条序列直接决定了并发上限。我实测7B模型AWQ量化后把max_num_seqs从默认值拉到128单卡吞吐量提升了接近一倍代价是每请求的平均时延略有上升。原因很简单序列数越多同一批次内被一起调度的token越多GPU的利用率越饱和整体吞吐自然也更高。但单独请求的时延会被batch中的其他请求拖慢。5.2 max_num_batched_tokens连续批处理的粒度控制这个参数规划了每次批处理时最大能够批处理多少token。如果设置得太大调度器可能长时间hold住一批token等待填满导致请求被卡在队列里如果太小批处理的规模又不够GPU计算空档会增多token生成吞吐上不去。我在不同模型上总结的经验值7B模型max_num_batched_tokens建议设在2048到4096之间13B模型设为1024到2048更稳需要配合观测GPU利用率动态调整不能一次到位就不管了5.3 gpu_memory_utilization显存预留的边界直觉这个参数控制框架最多占用GPU显存的百分比。很多人直接填0.9然后上线就OOM原因是KV Cache和激活值在运行时的实际增长被低估了。我的建议是首次部署先用官方默认值一般是0.9跑到真实负载下观察有无OOM然后逐步下调。稳妥的做法是先设定到0.85后续观察内存分配曲线再决定是否上调。这里的关键是记住显存预留不当会有连锁效应——不只影响当前服务还会影响同卡上其他进程尤其当你想在同一块GPU上跑多个模型实例时。5.4 长上下文场景的chunked prefill开关如果服务要处理很长的输入比如法律文书摘要、代码仓库问答建议打开chunked prefill。它的本质是把一次大的prefill切成多个小块与decode阶段交错执行避免一个大输入的prefill霸占整卡、阻塞其他请求的解码。我在一个要处理平均8000 token输入的RAG服务上做了A/B对比关闭chunked prefill时P99时延高达14秒开启后P99降到约5.5秒。代价是单请求的prefill耗时略微变长但对整体吞吐和稳定性来说收益远大于损失。这样一个旋钮一个旋钮地调下来一组可以复用的实测数据大致是7B AWQ模型、A100 80GB单卡、max_num_seqs96、batched_tokens3072、gpu_memory_utilization0.9开启chunked prefill后稳定跑出了约2600 token/s的吞吐和约3.8秒的P99时延序列长度约1024 token输出。这些参数不可能一次调对我的建议是每调一个参数变更就做一轮压测记录吞吐、P50、P99、GPU利用率四个指标形成一条回归曲线再综合权衡。宁可用数据说话也不要拍脑袋。6. 上线后躲不过去的三个坑显存碎片、长尾时延与扩容策略前几节都在说怎么把性能调上去但真正上线运维后你会发现工程化的问题和推理性能同样致命。我复盘过几次实际生产环境里的问题把其中最典型、最容易复发的三个坑记录下来。6.1 显存碎片化导致的虚假OOM线上服务跑了一段时间后突然莫名其妙地OOM。查了显存总占用明明还剩下20多GB为什么分配不出来原因是vLLM的PagedAttention虽然通过分页大幅缓解了KV Cache碎片问题但模型权重、激活值和工作区之间依然会产生细碎的不可复用显存空洞。尤其是高并发下不同长度序列的内存分配和释放不断交错碎片会逐渐累积。排查方法在监控面板上同时看显存总占用和模型及KV Cache占用的合计值如果两者差距随时间越拉越大基本就是碎片问题。解决办法有两种定期在低峰期重启服务释放显存或者给每个请求的max_model_len做上限控制避免极端长度的序列撑开内存空洞。我后来在生产上选择了后者把max_model_len设为每个业务场景的实际上限约20%余量碎片化速度大幅度下降OOM基本消失了。6.2 长尾时延的元凶长输入prefill阻塞高并发场景下P99时延一直在涨但P50很平稳关掉一些并发后P99立刻回落。这类问题的本质通常是长输入的prefill阻塞了解码。举个例子100路并发里混进来一个输入长度为5000 token的请求在chunked prefill未开启时这个请求的prefill会长时间占用计算资源导致同批次的短请求全部排队等待。表现就是大部分请求丝滑但偶尔几个请求被拖到七八秒甚至更久。处理方案分三层调度层开启chunked prefill让prefill和decode交错执行架构层把超长输入拆成多个切片配合检索只对相关片段做全局attention应用层对输入长度分级超长请求走独立的低优先级通道处理不挤占交互通道。6.3 扩容不是简单加卡成本和收益需要同步评估很多团队优化一轮后遇到底层瓶颈的第一反应是再加两张卡。但推理服务扩容不是简单的乘法因为显存带宽是多卡共享的多卡并行遇到短请求时通信开销甚至可能超过计算收益。我的某次实测一个13B模型4卡A100的吞吐是单卡的3.1倍P99时延降到单卡的约55%但当并发请求的序列都很短128 token以内时4卡只比单卡快约1.4倍——大量时间花在卡间通信上了。所以扩容之前先做两件事一是看你的请求是长序列密集型还是短序列密集型前者值得扩卡二是把单卡调参做实常常还有一倍以上的优化空间没榨出来这时候扩卡反而是最偷懒也最浪费钱的选择。另外推荐一个边上线边监控的方法论把吞吐、时延、显存三个维度的指标全部落到业务维度上去看——按token换算单位成本。比如跑一个批处理任务500万token用了多少钱、多少时间、多少GPU这个数字才是你和业务方沟通优化效果的通用货币。7. 阶段性沉淀我们最终拿到的一组可参考数据把上述所有优化叠加后以一个典型的7B AWQ模型、A100 80GB单卡为例我沉淀出一组具备可参考性的生产环境数据供大家对照自己的环境评估权重精度INT4AWQKV Cache为INT8框架vLLM开启chunked prefillmax_num_seqs96max_num_batched_tokens3072吞吐约2600 token/sP50时延约1.1秒输出256 tokenP99时延约3.8秒输出256 token单卡并发容量约60路同时在线交互不跌入重度排队相比未优化状态FP16、默认参数吞吐约提升3倍P99时延约下降70%换到更大的模型或更长的上下文时数据会有所变化但优化的路径和每个动作的收益逻辑基本一致。你可以把上面的参数当成起点通过压测找到自己业务场景下的最优值。推理优化与部署是一个没有终点的系统工程投入产出比最高的顺序永远是先量化再调度再换框架最后才是加硬件。每一步都需要用线上数据来验证而不是用直觉做决策。根据我个人的实践经验绝大多数团队的推理成本至少能通过这条链路压缩50%以上。