ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

LLM推理优化技术地图:从KV Cache到量化与投机解码

LLM推理优化技术地图:从KV Cache到量化与投机解码 从第一次把 7B 模型接进内部问答系统开始我就被一个现实问题反复折磨模型效果没问题但生成速度永远跟不上业务预期。单条请求还好并发一上来显存先爆接着延迟飙升最后整个推理服务像被卡住一样。那段时间我几乎把 LLM 推理优化相关的框架、论文、源码翻了个遍踩了不少坑也慢慢把整个优化体系串起来了。如果你也在做 LLM 推理、部署或相关工具链这篇文章应该能帮你建立一张完整的技术地图——从自回归生成的根本瓶颈到 KV Cache、连续批处理、量化、投机解码这些主流优化手段的原理再到生产环境到底怎么选型、怎么调参。1. 推理慢的根源自回归生成与显存带宽瓶颈很多刚开始接触 LLM 部署的人会有一个直觉模型的算力越强推理就应该越快。但在实际压测里你会发现哪怕 A100 的 FP16 算力高达 312 TFLOPS单卡跑一个 7B 模型做 decode 时每秒能生成的 token 数依然远低于你的预期。这里面的关键不是算力不够而是 LLM 的生成方式和传统模型完全不同。1.1 一次只吐一个词自回归让吞吐天然受限LLM 的生成本质是自回归autoregressive每生成一个 token都要把它拼接到已有的序列后面然后重新送入模型计算下一步的条件概率分布。这意味着从结构上它就无法像图像分类那样一次前向传播出全部结果而是被迫执行 N 次串行的前向传播来生成 N 个 token。单看一次前向传播其实很快但问题在于这个循环是严格串行的。第 i1 个 token 必须等第 i 个 token 算完才能开始中间没有任何并行空间。这就像一条只有一个人的生产线每个工位都快得要命但整个流水线一次只能处理一个零件。所以 LLM 推理的时间下限被必须串行执行的 decode 次数死死卡住。这里还要区分两个阶段prefill预填充和 decode解码。prefill 是处理输入 prompt 的阶段一次前向传播吃下整段 prompt并行度极高decode 是逐 token 生成的阶段每一步只有 1 个新 token 参与计算。两者在计算特性上是完全不同的物种后面所有优化手段几乎都是围绕如何分别优化这两个阶段展开的。1.2 预填充阶段与解码阶段两种完全不同的计算形态从计算量看一个输入长度为 L 的请求prefill 阶段的计算量大约是 2 × N × LN 是模型参数量而 decode 阶段每生成一个 token 的计算量大约是 2 × N。假设一个 7B 模型输入 prompt 是 2048 个 token那么 prefill 的计算量是 2 × 7e9 × 2048 ≈ 28.7 TFLOPs而 decode 每步只有约 14 GFLOPs。两者差了上千倍。但从访存量看decode 阶段每一步都需要把模型的全部权重从显存搬到计算单元里。7B 的 FP16 权重是 14GB意味着每生成一个 token至少要读 14GB 的权重。就算 A100 有 2TB/s 的 HBM 带宽光是搬运权重就需要约 7 毫秒。而实际单步计算本身只需要零点几毫秒。也就是说decode 阶段的绝大多数时间都花在搬数据而不是算数据上。prefill 阶段则相反由于 L 很大同样的权重被反复用了 L 次计算密度极高属于典型的算力受限compute-bound场景。这也是为什么现代推理框架普遍只对 prefill 做矩阵乘法优化而对 decode 阶段则想尽办法减少访存量、提高 GPU 利用率。1.3 用 Roofline 模型解释为什么算力过剩Roofline 模型是理解这个问题的最好工具。它定义一个关键指标算术强度arithmetic intensity 总计算量 / 总访存量单位是 FLOP/byte。GPU 存在一个转折点当算术强度低于这个点时性能受内存带宽限制高于这个点性能受算力限制。以 A100 为例BF16 算力约 312 TFLOPS内存带宽约 2TB/s转折点就是 312e12 / 2e12 ≈ 156 FLOP/byte。prefill 阶段的算术强度很容易达到几千 FLOP/byte稳坐算力受限区而 decode 阶段生成一个 token 需要读 14GB 权重只做 14 GFLOPs 计算算术强度大约只有 1 FLOP/byte远低于转折点。这意味着在 decode 阶段哪怕算力再提升十倍对速度也几乎没有帮助真正卡脖子的是显存带宽。这个结论改变了我的整套优化思路既然 decode 受带宽限制那就从两个方向下手——要么减少每次读取的数据量量化、更紧凑的 KV Cache要么提高每个 token 的算术强度增大 batch、投机解码让一次前向传播验证多个 token。后面几章的所有技术本质上都是这两个方向的具象化。2. KV Cache 机制为什么显存总是捉襟见肘自回归生成还有一个隐藏代价每次前向传播都要重新计算注意力层的历史 K 和 V。如果不做缓存序列越长重复计算量越大最终会慢到无法接受。所以主流推理框架都会用一块显存专门缓存历史 token 的 Key 和 Value这就是 KV Cache。2.1 注意力计算里的 K 和 V 为什么要缓存在自注意力机制中当前 token 的 Query 需要和序列中所有历史 token 的 Key 做点积再对 Value 加权求和。从数学上是这样但从工程角度历史 token 的 K、V 向量一旦算出来在后续每一步都是不变的——因为它们是历史 token 的投影结果不会随当前 token 变化。既然不变就没必要每步重算直接存下来复用就行。KV Cache 就是干这个的。它的存在让 decode 阶段只需要计算当前 token 的 Q、K、V然后用当前 Q 去和缓存里的历史 K、V 做注意力运算。这带来两个好处一是省掉了历史 token 的全部重复计算二是每步需要读取的数据量从全部序列降为全部序列的 KV。但天下没有免费的午餐。KV Cache 把计算压力转嫁成了显存压力而且这个压力会随着 batch 大小、序列长度、层数、注意力头数线性增长。在长对话、长文档这类场景里KV Cache 的显存占用往往会超过模型权重本身成为显存的第一大消耗者。2.2 一张表算清楚 KV Cache 占多少显存KV Cache 的显存占用可以用一个公式估算显存字节 2 × 层数 × KV头数 × Head维度 × 序列长度 × Batch大小 × 字节数为什么前面有个 2因为 K 和 V 各一份。下面我用一个 7B 模型32 层、8 个 KV 头、Head 维度 128、FP16每元素 2 字节来算不同场景下的占用场景序列长度BatchKV Cache 显存短对话102412×32×8×128×1024×1×2 1GB中等长度409682×32×8×128×4096×8×2 4GB长文档RAG819282×32×8×128×8192×8×2 8GB极限长上下文32768162×32×8×128×32768×16×2 32GB注意模型 FP16 权重本身约 14GB。也就是说在 batch8、序列长度 4096 的场景下KV Cache 占用 4GB虽然还没超过权重但已经不容忽视到了 batch16、上下文 32K 时KV Cache 高达 32GB是权重的两倍还多。如果你用的是 A100 80GB光 KV Cache 就占了近一半。这还是 8 个 KV 头的情况如果是原始 MHA 结构的 32 个头数字要翻四倍直接爆掉。这就是为什么长上下文场景下显存规划的第一优先级不是模型权重而是 KV Cache。我见过不少团队在本地部署长文档 RAG 服务时模型本身加载没问题一跑长 prompt 就 OOM基本都是没算清楚这笔账。2.3 MHA、GQA、MQA从注意力头结构省显存既然 KV Cache 的显存压力和 KV 头数线性相关那么最直接的优化方向就是把 KV 头数降下来。这里有三条路线MHAMulti-Head Attention每个注意力头都有独立的 K、V。效果最好显存开销最大。MQAMulti-Query Attention所有注意力头共享一组 K、VKV Cache 直接缩小到原来的 1/头数但质量损失有时明显。GQAGrouped-Query Attention把所有头分成若干组组内共享 K、V效果和显存取折中。比如 32 个 Query 头配 8 个 KV 头KV Cache 就是 MHA 的 1/4。现在的开源模型基本都转向了 GQA 或 MQA 结构LLaMA 2/3、Mistral 等都是这个路线。作为使用者这个结构选择是模型训练时定的你改变不了但在买卡、做显存规划时必须把它算进去同样参数量下GQA 模型能支撑的并发和上下文长度就是比 MHA 模型高出一截。不过 KV Cache 的优化不止停留在模型结构层面。后面章节会讲的 PagedAttention 解决的是 KV Cache 的碎片化问题KV Cache 量化则是在精度可接受的前提下把它的字节数再砍一刀这些都是在工程侧继续压低显存压力的手段。3. 批处理与显存管理连续批处理和 PagedAttention 的思路在面试或者技术评审里我经常被问到LLM 推理为什么需要专门的框架。最核心的答案其实就两个动态批处理和显存管理。传统深度学习推理框架在这两件事上的表现距离好用差得很远。3.1 静态批处理为什么浪费传统推理框架处理请求的方式是静态批处理把多个请求凑成一整个 batch然后整个 batch 一起前向传播等全部请求生成完毕后再释放整个 batch 的资源。如果你的服务是图像分类这类一次前向传播出结果的场景这套机制没有任何问题。但换到 LLM 场景情况完全不同。不同请求的 prompt 长度不一样生成的目标长度也不一样。有的请求生成 50 个 token 就停了有的要生成 500 个。静态批处理只能按最长的那个请求统一推进短的请求生成完了也只能在 batch 里干等着。更糟糕的是新来的请求必须等当前这个 batch 彻底清空才能进来哪怕 GPU 此刻有一半的计算单元在空转。这会带来两个直接后果一是 batch 内的计算浪费短请求占着位置不干活二是 batch 间的调度死板吞吐峰值上不去。在 LLM 这种 decode 阶段本就是带宽受限的场景里这种浪费等于把本就不富裕的算术强度进一步摊薄。3.2 连续批处理的工作方式连续批处理Continuous Batching的核心思路是把整批进入、整批退出改成逐请求进入、逐请求退出。框架在每步 decode 时都会问一遍batch 里有没有请求已经生成结束了有的话就把它踢出去释放它占用的 KV Cache 显存和计算槽位同时从等待队列里拉一个或多个新请求进来参与下一轮前向传播。这样做的好处非常直观GPU 上永远跑着尽可能多的活跃请求显存和带宽的利用率被拉满。即使某个请求只生成 30 个 token 就结束也只占用 30 步的资源不会拖累别人。在实际部署中连续批处理能让单卡吞吐提升数倍到十倍不等具体取决于请求的长度分布——请求越长、长度差异越大收益越明显。这套机制原本是 Orca 论文提出来的现在已经是 vLLM、TensorRT-LLM、SGLang 这些主流框架的标配能力。但要注意一点连续批处理会显著提高单卡上的并发请求数这要求调度器能精确管理每个请求的显存占用于是 PagedAttention 就应运而生了。3.3 PagedAttention把显存当虚拟内存页来管理在 PagedAttention 出现之前KV Cache 是按请求为单位连续分配的。一个请求从开始到结束它在显存里占用的是一整块连续空间。问题是这个空间必须按最大可能长度预留——比如你设定最大序列长度 8192那么每个请求从一开始就要占满 8192 个 token 的 KV Cache 空间哪怕它实际只用了 500。这种分配方式带来两个致命问题一是内部碎片预留的空间利用率经常不到一半二是由于显存里塞满了预留空间外部碎片也在不断积累最终导致明明显存还有不少剩余却找不到一块足够大的连续空间给新请求用。PagedAttention 借鉴的是操作系统虚拟内存的思路把 KV Cache 切分成固定大小的块block每个请求的 KV Cache 由若干块组成这些块在物理显存上可以是不连续的。用一个块表记录逻辑块到物理块的映射就行。只有当请求真正产生新的 KV 时才分配新的块请求结束块立即回收。这个设计的精妙之处在于它几乎消灭了显存浪费不再需要按最大长度预分配块粒度足够小内部碎片可以忽略。同时它还顺带支持了类似 copy-on-write 的机制——比如多个采样结果共享同一段前缀的 KV Cache 时只需要复制块表而不复制实际显存数据。实测中 PagedAttention 能让显存利用率提升到接近原始方案的 90% 以上这直接决定了单卡能承载的并发请求上限。3.4 调度时机与流量控制有了连续批处理和 PagedAttention框架的吞吐上限大幅提高了但随之而来的是一类新问题调度策略和流量控制。首先是调度时机。每个请求的最优并发接受量要看显存是否有足够的空闲块给新请求分配 KV Cache。调度器需要在每步 decode 之后检查显存水位决定是否从等待队列拉新请求。拉得太激进可能因为显存不足导致前向传播失败拉得太慢GPU 利用率又不够。主流框架都允许你配置max_num_seqs和gpu_memory_utilization这两个参数来调节水位前者控制最大并发序列数后者控制显存使用率上限。其次是队头阻塞问题。连续批处理中如果一个请求突然生成了极长的输出它会持续占用批量槽位导致后面短请求的等待时间被拉长。实际部署时我一般会把请求按优先级分队列或者给单请求设置max_tokens上限避免极端情况拖垮整体延迟。这里还有一个很多人忽略的点连续批处理提升的是吞吐throughput而不是单请求延迟latency。如果你接的是实时对话场景需要的是单 token 延迟足够低这时候反而要控制并发量给每个请求留足显存和带宽。吞吐和延迟的权衡是 LLM 推理调优里永远绕不开的主题。4. 量化压缩精度与吞吐的权衡艺术如果说 KV Cache 和批处理解决的是显存和并发问题那么量化解决的就是带宽和容量的底层瓶颈。这也是为什么量化几乎是所有 LLM 推理优化文章里必谈的话题——它直接作用于我们第一章说的那个算术强度公式的分母每次读取的字节数。4.1 为什么要量化带宽瓶颈下的必然选择回到第一张图的结论decode 阶段每生成一个 token都需要把模型权重完整读一遍。7B 模型 FP16 权重是 14GB如果量化到 4bit权重直接缩到 3.5GB。这意味着每一轮 decode 的访存量下降到原来的四分之一在带宽受限的前提下理论生成速度也能相应提升接近四倍。你可以把量化理解成用更低的数字精度来存储和计算。FP16 每个数占 2 字节INT8 占 1 字节INT4 只占 0.5 字节。位数越低模型文件越小加载和推理时读写的数据量越少。对 LLM 而言权重中的冗余信息很多用低精度表示并不会明显损害生成质量——这是量化的理论基础。不过量化不是免费午餐。位数越低数值表示的精度越低模型输出质量下降的风险越大。不同的量化方法和校准策略对最终效果的损伤程度差别非常大。这也是为什么量化领域会有那么多不同的方案它们本质上是精度、速度、显存三者之间的不同权衡点。4.2 主流量化方案与实测对比目前主流方案大概分两类训练后量化PTQ和量化感知训练QAT。LLM 领域由于训练成本极高绝大多数实践都选 PTQ也就是拿一个训练好的模型用少量校准数据计算缩放因子然后直接转换。以下是几个常见方案的特点方案位宽量化对象代表实现精度损失适用场景GPTQ4bit权重AutoGPTQ、vLLM较低通用部署对速度要求高的服务AWQ4bit权重vLLM、TensorRT-LLM较低激活值分布敏感的模型SmoothQuant8bit权重激活TensorRT-LLM低需要高吞吐且不想太大精度损失GGUF Q4_K_M4bit权重llama.cpp、Ollama中等本地部署、CPU/边缘设备GPTQ 和 AWQ 是目前服务端部署最常见的两个 4bit 权重量化方案。它们的核心区别在于处理离群值outlier的思路GPTQ 用近似算法逐层寻找最小化量化误差的权重矩阵AWQ 则通过观察激活值分布保护对模型输出影响最大的那部分权重通道。实测中两者效果接近但 AWQ 在激活值分布存在明显尖峰的模型上往往更稳健。SmoothQuant 的思路值得一提它不直接砍权重的位宽而是把激活值里的离群值平滑转移到权重中让激活值分布更均匀从而实现对权重和激活同时做 INT8 量化。因为激活值量化往往比权重量化更容易掉精度SmoothQuant 在这类场景的价值在于提供了一条不那么痛的路径。4.3 量化容易踩的坑量化真正落地时坑远比想象中多。我按踩过的频率排序说几个最典型的。第一只看权重文件大小不看实际显存。很多人以为模型量化后显存占用就变成 3.5GB 了其实推理时还要额外算上激活值、KV Cache、临时计算缓冲。另外有些框架在量化后仍然以 FP16 精度做部分计算实际显存并没有你想象中省那么多。第二KV Cache 不量化长上下文时依然会爆显存。很多团队对一个 70B 模型做了 AWQ 4bit 量化权重确实从 140GB 降到了 35GB但序列一长KV Cache 照样把显存吃穿。这时候需要考虑像 KV Cache INT8 量化这类技术它能再砍掉 KV Cache 的一半字节数。当前主流框架基本都支持对 KV Cache 做量化但精度损失在不同模型上表现差异很大建议上线前使用业务真实数据单独验证。第三校准集和业务数据分布不一致导致量化后某些输出质量崩坏。GPTQ、AWQ 都需要选校准数据。有些人图省事直接用论文里的公开数据集结果在中文、代码、医疗等特定领域上效果明显退化。这时的解法是准备几百条贴近真实业务场景的样本重新校准一遍往往能救回来不少质量。第四量化后的评测不能只看困惑度perplexity。困惑度是一个整体指标对局部任务的说谎、格式、逻辑错误并不敏感。量化前后应该跑一套和你业务强相关的评测集比如开放域问答的准确率、代码生成的通过率而不是只看那个全局的 PPL。4.4 从模型结构侧理解量化激活值为什么比权重难搞权重量化之所以能做到 4bit 仍维持不错的效果是因为权重矩阵里绝大多数元素的值域很集中少数异常大的离群值只要单独保护起来整体误差就可控。但激活值的问题在于它的值域是每层动态变化的且随输入不同而波动无法像权重一样提前离线分析。这就解释了为什么权重激活一起量化的方案难度远高于纯权重量化。你可以用一个类比来理解权重量化像给一张静态照片压缩成低分辨率图细节损失有限激活量化则像给一段每个镜头亮度都在变的视频做压缩很容易过曝或欠曝。所以如果你的场景对延迟要求极高比如流式对话希望把权重和激活都压到 8bit 以同时减少访存和计算量建议走 SmoothQuant 这类经过验证的方案而不是自己拍脑袋直接 INT8效果大概率会让你失望。5. 投机解码与并行策略从算法层面省步骤前面说的 KV Cache、批处理、量化都是在优化每一步访存和执行的成本。但自回归生成还有另一个层面的优化空间能不能少做几步投机解码Speculative Decoding和并行解码就是从这个角度切入的思路非常巧妙。5.1 投机解码小模型写草稿大模型批改投机解码的核心思想很反直觉用一个又快又小的小模型draft model先快速生成若干候选 token然后让大模型一次前向传播验证这些候选是否正确。如果小模型的候选大部分被大模型接受了那么一次前向传播就赚到了多个 token 的生成量总体延迟和吞吐都得到改善。这里的关键在于验证是非自回归的。大模型可以在一次前向传播里同时计算多个候选位置各自的下一个 token 分布不需要逐个串行等待。这跟老师批改学生作业很像——学生小模型先把答案写出来老师大模型一次性批改所有答案而不是老师每写一个字就叫一次学生。接受率是投机解码的核心指标。如果小模型和大模型行为差异大候选 token 经常被拒绝那么每次验证只能接受一两个 token甚至需要重新生成反而比直接跑大模型更慢。论文和工业界的实测表明在用一个小一号的同族模型做 draft 时接受率能到 0.7-0.8端到端加速通常有 2-3 倍。但选 draft 模型不能只图小和主模型的分布相似度比模型大小更重要。5.2 多 token 并行Medusa 和独立解码头投机解码需要一个单独的小模型这不是所有团队都愿意接受的额外负担。Medusa 提供了另一条思路在原始模型之上添加多个平行的解码头每个头各自负责预测当前位置之后第 n 个位置的 token。训练时用原始模型生成的数据微调这些解码头推理时同样采用草稿验证的模式。这个方案的工程好处是不需要维护两个独立的模型所有解码头都挂在同一个骨干网络上。缺点是解码头需要针对每个模型单独训练属于一次工作多次收益的类型。目前 vLLM、SGLang 等框架都已经对 Medusa 提供了一定支持实测在短文本生成场景里能获得 2 倍左右的加速。另外还有一种思路是并行采样parallel sampling在某些解码策略下同一 prompt 的一次前向传播可以产生多个候选分支然后挑选最优分支继续推。它和投机解码的数学目标不完全一样但同样利用了一次前向传播计算多个位置的分布这个能力适合对延迟极其敏感的搜索或规划类任务。5.3 张量并行与流水线并行单卡放不下的解法当模型大到单卡放不下时并行策略是绕不开的话题。张量并行Tensor Parallelism是把一个 Transformer 层的矩阵运算切分到多张卡上每张卡持有权重的一部分计算时通过 all-reduce 通信同步结果。这适合单节点多卡因为通信量非常大跨节点用会直接被网络带宽拖垮。流水线并行Pipeline Parallelism则是按层切分不同的层放在不同的卡上数据像流水线一样从前到后流过所有卡。它的通信量比张量并行小得多更利于跨节点部署但会引入流水线气泡bubble也就是某些卡在等待上游数据时处于空闲状态。实际生产里7B 到 13B 的模型单卡基本能放下主要用不到模型并行70B 以上的模型通常会采用张量并行 流水线并行的组合比如 8 卡张量并行、4 组流水线并行。这里有一个反直觉的经验一张 80GB 的卡放得下 70B 模型权重时很多人会觉得不需要并行但 decode 阶段单卡带宽有限多卡张量并行可以把权重访存分摊到多张卡的带宽上单个 token 的生成延迟能随之下降。所以并行不只是为了放得下也是为了跑得快。5.4 收益评估与适用场景投机解码不是所有场景都能稳定拿收益。如果业务主要是短文本生成比如翻译、摘要每段只有几十个 token投机解码的前期开销占比太高收益很容易被摊薄而长文本生成、代码补全这类场景序列越长省下的步数越多收益越明显。我个人的经验是上线前先做一个 30 分钟的小实验用业务真实流量测一下有无投机解码的 TPOT单个 token 的生成时间和吞吐。如果提升不足 30%那就不如把精力放在量化和批处理参数调优上。另外投机解码会增加显存占用小模型或 Medusa 头、验证器的 KV Cache所以显存紧张的环境里要额外注意。6. 落地选型与实战经验原理讲得再多最后都要落到生产环境里到底该用哪个框架、怎么配置参数这个问题上。作为一个踩过不少坑的人我把自己的选型逻辑和调参经验整理如下。6.1 主流推理框架对比目前开源社区里最常用的几个推理框架各有各的擅长场景框架核心优势适合场景注意事项vLLMPagedAttention吞吐高社区活跃在线服务、高并发对某些算子的覆盖不如专用引擎细TensorRT-LLMNVIDIA 官方优化算子极致固定卡型、追求极致性能编译时间长模型兼容性需要维护SGLangRadixAttention前缀复用强RAG、多轮对话等共享前缀场景相对年轻生态还在追赶llama.cpp / OllamaCPU 和消费级 GPU 友好本地部署、边缘设备吞吐上限低于服务端框架vLLM 是绝大多数团队的第一选择因为它的 PagedAttention 把显存效率做得很高同时支持连续批处理和多种量化格式部署成本最低。如果你的显存固定、卡型固定、业务量也稳定可以考虑 TensorRT-LLM它会在算子层面再做一轮针对性的极致优化吞吐还能再上一个台阶。SGLang 在前缀复用上有独到优势如果你的服务大量请求共享同一个系统提示词或长文档前缀它的收益会非常直观。6.2 关键参数怎么定配置推理服务时我一般按这个顺序来调参数gpu_memory_utilization显存利用率上限vLLM 默认 0.9。建议从 0.85 起步留出一点余量给 CUDA context 和其他开销稳定后再往上加。max_num_seqs最大并发序列数。这个值不是越大越好它受限于 KV Cache 的可用显存。可以先设一个保守值观察显存剩余和队列积压情况再逐步上调。max_model_len模型支持的最大序列长度注意包括输入输出总和。设得过大同样会推高 KV Cache 预留空间。max_tokens单请求输出上限建议按业务真实场景设不要给用户无限生成的自由空间。它直接影响极端情况下的服务稳定性。enable_prefix_cachingvLLM 中的相关开关如果你的请求有大量重复前缀开启后能重用 KV Cache显著降低首 token 延迟。调参的总体原则是先保证不 OOM再追求吞吐。我见过太多人一上来就把显存利用率拉到 0.95结果请求一多就爆显存进程直接崩掉这比慢一点可怕得多。6.3 我实际踩过的坑第一个坑量化模型和框架版本不匹配。曾经有一版 AWQ 量化模型在某框架的早版本上推理结果偶尔出现乱码排查了很久才发现是量化格式兼容性问题。现在我的流程是固定框架版本并且每次升级框架都要回归跑一遍业务评测集不能只看能不能加载。第二个坑长尾请求导致的延迟抖动。连续批处理下一个特别长的请求会拖长同批次里所有请求的完成时间。后来我在网关层把请求按预期输出长度分桶短请求走低延迟通道长请求走高吞吐通道整体 P99 延迟立刻好了很多。第三个坑预热不足。很多推理框架在第一次收到请求时才做算子选择和图优化导致冷启动时首 token 延迟特别高。上线前一定要发一批预热请求把 CUDA kernel、显存分配都触发一遍。第四个坑多副本部署时的负载均衡策略。如果把长请求和短请求混在同一个负载均衡队列里会出现某个副本被长请求占满、其他副本空闲的情况。最好在负载均衡层根据请求特征做哈希或加权避免这类热副本现象。6.4 性能指标怎么衡量与调优顺序衡量 LLM 推理性能我建议至少关注四个指标TTFTTime to First Token首 token 延迟直接影响用户第一感知。主要受 prefill 速度和调度效率影响。TPOTTime Per Output Token平均每个输出 token 的生成耗时决定输出流畅度。吞吐量tokens/s整个服务每秒生成的 token 总数。并发能力显存允许的最大并发请求数。调优顺序上我的建议是先算显存账确保 KV Cache 规划合理然后调批处理参数把吞吐拉起来再考虑量化把带宽瓶颈松一松最后才是投机解码这类算法级优化。原因很简单显存是地基批处理是主干量化是放大器投机解码是锦上添花。顺序反了容易在某个环节反复横跳事倍功半。最后分享一个我个人的小习惯每次做推理优化我都会先写一个几十行的压测脚本固定住 prompt 模板、生成长度、并发数这些变量把 TTFT、TPOT、吞吐三个数打出来留档。因为这些数字受模型版本、框架版本、硬件状态影响太大没有基线数据任何优化都说不清到底有没有效果。有了基线每次改动带来的收益或回退都能一目了然这才是推理优化能持续迭代下去的基础。
返回列表