资讯动态

大模型推理优化与部署实战:从KV Cache、量化到服务压测

发布时间:2026/10/21 4:52:52 来源:尧图企业网站定制
大语言模型能写诗、能写代码可是一旦真实跑到线上去服务用户推理优化与部署才是真正决定系统能不能撑住的那道关卡。很多团队花大力气训好了模型结果一上线就暴露各种问题单请求时延太高、并发上来直接OOM、一个长对话把显存吃光。这周我正好在整理一套LLM工程系统的推理优化与部署环节把从指标拆解、算力优化、服务架构到上线排查的完整思路梳理了一遍。这篇文章适合正在或准备做LLM应用落地的算法工程师、后端开发同学也适合想搞懂大模型服务成本与性能瓶颈的技术负责人。这里不聊训练只聊推理而且尽量说人话把原理和步骤都摊开讲。1. 推理优化到底在解决什么问题1.1 从自回归生成看瓶颈来源大模型推理和传统深度学习推理最大的不同在于“自回归解码”。模型每次只能根据已经生成的 token 预测下一个 token然后把新生成的 token 拼到输入序列尾巴上再接着预测下一个。这个串行过程意味着一个长度为 1000 的回复至少要经历 1000 次前向计算每一步都依赖上一步的输出无法像图像分类那样一次性算出全部结果。这个特性带来了两个直接后果第一单个请求的延迟天然偏大尤其当上下文长度不断增大时每一步需要处理的序列都在变长第二显存消耗不仅是模型权重本身还包括不断累加的中间状态也就是 KV Cache。如果你把模型类比成一个特别认真的作者每写一个新字都要把前面所有内容重新读一遍那就很容易理解为什么大模型推理会又慢又费内存。在实际服务中模型权重当然要占显存KV Cache 往往比权重更占地方。以常见的 70 亿参数模型为例BF16 格式的权重大约 14GB而每个请求的 KV Cache 会随着上下文长度和并发用户数成倍增长。如果同时服务几十个用户每个用户都有一长段上下文显存通常会在权重之上再占用几十甚至上百GB。这也是为什么很多团队一开始天真地用“模型权重大小”来估算显存需求结果一上线就翻车。1.2 必须死磕的四个核心指标搞推理优化之前先把评价指标定下来否则很容易陷入“调了一堆参数但不知道有没有变好”的尴尬。我通常把这四个指标作为项目对齐的基准指标代表什么一般目标首token延迟TTFT用户发出请求到收到第一个字的时间通常希望小于 1 秒流式聊天场景越短越好每token生成延迟TPOT后续每个字之间的间隔通常希望小于 50ms否则有明显的“卡顿”感吞吐量单位时间内系统总共生成的 token 数尽可能高直接决定服务成本显存占用量权重、KV Cache 和框架开销的总和决定单卡能支撑多少并发与多长上下文TTFT 反映的是“预填充”阶段的效率。预填充阶段要把用户输入的所有 prompt 一次性并行算完计算密集所以非常吃算力不能简单通过增加并发来解决。TPOT 反映的是“解码”阶段的效率是系统连续推理能力的体现。吞吐量则跟批处理策略、显存管理、量化程度强相关。实际优化时这四个指标经常互相矛盾无限并发可以提高吞吐但会让每个请求互相争抢算力导致 TPOT 变差量化可以减少显存但如果量化校准没做好输出质量会明显下降。所以做推理优化的本质不是追求某一项指标登顶而是在给定算力预算下找到延迟和吞吐、成本和效果的平衡点。2. 模型与计算层面的优化手段2.1 量化不只是降低精度这么简单量化的核心思路是用更低精度的数值表示模型参数从而减少显存占用和计算量。最常用的是把 FP16/BF16 降到 INT8 或 INT4。为什么神经网络可以接受这种压缩因为模型参数存在大量冗余而且很多权重的分布集中在某个较小的数值范围内我们可以在尽量不损失信息的前提下用更少的比特去近似它。量化方式分两大类训练后量化PTQ和量化感知训练QAT。推理部署场景里PTQ 最常用因为它不需要重新训练模型。PTQ 又分为只量化权重的 Weight-only Quantization以及同时量化激活值和 KV Cache 的 W8A8 等方案。对 LLM 来说权重量化是最直接省显存的方式把 70 亿参数从 FP16 压成 INT4权重可以从约 14GB 降到约 3.5GB单卡能容纳的模型规模立刻变大。不过量化不是白嫖。如果只做简单的 round-to-nearest 量化模型能力往往会明显退化如今主流方案会结合激活值分布、通道离散度做优化这类方法能显著降低量化误差。实操中我一般建议如果 GPU 显存充足先用 BF16/FP16 跑通全部业务场景再尝试 INT8只有当显存确实放不下更长上下文或更高并发时才考虑 INT4。千万不能为了节省显存而盲目上低比特最后模型输出内容质量崩塌反而得不偿失。另外要注意量化对注意力层的影响往往比其他层更大。某些架构中K、V、Q 这些投影层的权重对精度更敏感社区里也有针对特定敏感层做“跳过量化”的做法。所以上线前一定要准备一个评估集对比量化前后的输出效果不要只看显存省了多少。2.2 投机采样让小模型帮你打头阵自回归解码的串行本质看似无解但投机采样Speculative Decoding提供了一条“用小模型探路大模型验证”的思路。简单说首先用一个很小的草稿模型尝试生成接下来几步的 token然后把这些 token 一次性交给大模型进行并行验证。如果草稿模型足够准大模型一次前向就能确认多个 token把原本多次串行计算压缩成一次延迟自然大幅下降。这个方案听起来很美好但对草稿模型质量要求极高。如果草稿模型预测的 token 经常被大模型拒绝验证时会浪费算力整体反而变慢。我实测下来草稿模型最好和主模型来自同语系或同任务训练且与主模型的 tokenizer 完全一致另外生成温度等解码参数也会影响接受率低温确定性场景下收益更明显。投机采样适合算力有冗余、且对单请求延迟敏感的场景。如果系统本来就已经满负荷运行草稿模型还会抢占本来属于主模型的算力吞吐量可能不升反降。做方案选型前先跑几组小规模实验对比“直出”和“投机采样”的 TTFT 与吞吐差异再决定是否上线。2.3 连续批处理与动态请求调度传统批处理是将一批请求集合到最大值再统一计算空闲时 GPU 只能干等。LLM 场景中不同请求的长度极不均匀有人问“你好”有人粘贴了一整篇论文。静态批处理会让短请求被长请求拖住长请求又因为 batch 未满而浪费时间整体效率很低。连续批处理也叫在途批处理改变了游戏规则GPU 不再等待整个 batch 全部完成而是每步结束就释放已完成或已经到最大长度的请求同时立即把新请求插入空位。这个机制让显存和算力始终处于“边来边算”的流水线状态吞吐提升非常明显。和连续批处理配套的是现代化的 KV Cache 管理方式。传统显存空间需要预先为每个请求预留整段连续空间容易产生大量碎片现在业界普遍采用类似操作系统内存分页的思路把 KV Cache 切分成更小的块按需分配显存利用率可以大幅提升。实际部署中这两个特性基本是靠推理框架层面实现的选择框架时要格外关注是否支持这直接影响你能同时服务多少并发。2.4 KV Cache 优化与上下文长度管理KV Cache 是自回归推理中为了省掉重复计算的缓存前向计算时会算出每层 attention 的 key 和 value若全部丢弃每次生成新 token 都要重新计算前面所有 token 的 K/V那成本是不可接受的。所以推理框架会把历史 K/V 缓存起来代价就是显存占用随序列长度线性增长。KV Cache 的显存开销可以做这样近似估算每个 token 的 KV Cache 大小 2K 和 V 两份 × 层数 × 注意力头数 × 每个头的维度 × 数据类型字节数。举一个常见例子假设一个模型有 32 层、32 个注意力头、每个头维度 128BF16 类型占 2 字节那么每 token 的 KV Cache 约 2 × 32 × 32 × 128 × 2 512KB。如果每个用户对话平均上下文是 2000 token单用户就需要约 1GB 的 KV Cache。并发 100 个用户光 KV Cache 就有 100GB 级别。这也是为什么很多团队想让模型支持“超长上下文”却被显存卡得死死的。工程上除了依赖框架的显存管理外还有几个常用手段第一是 prefix caching多个请求的公共前缀比如系统的 system prompt复用 KV Cache能省下一大块重复计算第二是 KV Cache 量化用 INT8 甚至更低精度保存缓存第三是结合业务场景对单请求上下文长度做限制不要给无限长的 prompt 留太多显存。我见过太多服务因为有人发了超长输入直接把整卡显存打爆结果所有用户一起遭殃。3. 服务架构与部署形态3.1 推理引擎选型的关键维度后端推理服务到底用哪套引擎直接决定了优化手段能不能落地。我的习惯是先列出一张需求清单再逐项打勾而不是看哪个框架热度高就无脑上车。最关键的几个维度是否支持量化模型加载特别是 INT4/INT8 权重格式。是否支持连续批处理和分页式 KV Cache 管理。是否支持多卡张量并行以便把大模型拆到多块 GPU 上。是否提供与主流生态兼容的 HTTP API方便业务接入。是否支持流式输出聊天场景几乎必备。社区活跃度和运维文档是否完善这个在出问题时尤其重要。目前开源的推理后端基本都覆盖了上面大部分能力但每个框架在显存分配策略、调度算法和算子优化上各有取舍。我的建议是不要只看 benchmark最好拿你自己的模型、自己的真实 prompt 分布在同一批 GPU 上做压测对比。不同框架对长上下文的支持差异很大有的在 batch 较大时表现好有的在单请求延迟上更极致适合的场景不一样。部署形态上优先考虑把“推理引擎”作为一个有状态的服务独立部署不要和业务后端进程混在一起。大模型推理进程容易因为显存问题被系统杀掉混部会造成整个业务崩溃独立部署还能方便单独扩缩容。3.2 多卡多机张量并行与流水线并行当模型单卡放不下或者需要更高吞吐时多卡并行不可避免。最常用的是张量并行把一层的权重矩阵切成多份分布到多张卡上协同计算和流水线并行把模型层按顺序切分到多张卡每张卡负责一段。张量并行速度快但每算一步都要在卡间做大量数据同步通信压力极大通常需要高速卡间互联才能发挥效果。流水线并行通信压力小但天然存在流水线气泡算力利用率不够满。生产环境里小规模部署一般用张量并行更省心到了千亿级别模型纯张量并行会因为单卡无法容纳一层而被迫使用流水线并行。另一个容易被忽略的点是多卡部署后推理引擎对外要能根据显存余量自动调度请求。框架内部需要把 batch 里的不同请求均衡地分发到各卡不要出现一张卡满载、另一张卡空闲的情况。上线前可以专门测试一下多卡下的负载均衡表现方法很简单压测时分别观察每张卡的利用率如果某个卡明显偏低说明均衡策略有问题。3.3 服务化网关、限流与流式输出推理引擎本身更像一个“计算函数”对外暴露模型能力但生产环境还需要一层服务化工程。我的典型架构是客户端 → 接入网关/负载均衡 → 推理引擎实例池 → 监控系统。网关负责请求路由、超时控制、限流和鉴权。LLM 推理一个请求可能要执行几十秒甚至几分钟网关超时时间不能按普通 HTTP 接口的几秒来设置但同时也要防止个别超长请求无限占用资源所以一般会设置“最大执行时间”和“最大生成 token 数”。限流更是必要否则一旦有突发流量进来所有请求全部排队延迟会急剧恶化甚至拖垮整个实例。流式输出对用户体验影响非常大。聊天类场景里用户看到首字出现的时间比等到全部答案再一次性返回要“快”得多。实现上可以基于 SSE 或 WebSocket让推理引擎每生成一个 token 就推送一次。做流式输出时要注意客户端的断连处理用户中途关掉页面后后端如果还在傻傻地继续生成会产生无效计算不少推理框架支持取消机制接入时要确认这一点。3.4 显存估算与实例规格选择搭建服务前先做一个显存预算表能省掉很多上线后的麻烦。预算表包括模型权重、KV Cache、激活值、CUDA 上下文和推理框架开销。简单估算方式模型权重参数量 × 单参数字节数。7B 模型 FP16 约 13GBBF16 约 14GBINT8 约 7GBINT4 约 3.5GB。KV Cache按上文公式估算再乘上目标并发数和平均上下文长度。激活值与 batch size 和隐藏层维度相关预填充阶段比较吃显存但通常比权重和 KV Cache 小。框架开销一般预留 1~2GB。举个例子假设我们在单张 48GB 的卡上部署一个 13B BF16 模型权重约 26GB剩下约 20GB 可以给 KV Cache。如果设置平均单用户 KV Cache 约 0.5GB那么理论并发上限约 40 个。但实际还要考虑激活值和碎片通常只能跑到理论值的 70% 左右。想要提高并发要么换更大显存卡要么降低精度要么限制上下文长度。这个预算表建议做成动态脚本方便在模型或并发目标变化时快速重新计算。4. 一个从加载到调优的实操示例4.1 环境准备与模型加载我拿一个模拟项目 X 举例模型是一个开源的 7B 规模中文对话模型服务目标是线上聊天场景。硬件条件只是普通的 4 卡 48GB GPU 服务器但初期只用单卡做验证。环境准备阶段先把推理框架、模型权重和依赖库准备好。这里用一段示意性的启动命令具体名称已隐去表达通用的配置方式# 拉取模型权重到本地目录 # 启动推理服务 llm-engine serve ./model_7b \ --host 0.0.0.0 \ --port 8000 \ --dtype auto \ --max-model-len 8192 \ --gpu-memory-utilization 0.9 \ --max-num-seqs 16其中--max-model-len表示模型支持的最大序列长度--gpu-memory-utilization表示预留多少比例的显存给 KV Cache 和运行时--max-num-seqs控制了单个实例内部同时计算的请求数。初次启动时我一般把max-model-len设为业务实际需要的长度而不是盲目拉满。如果设置太长框架会为极长上下文预留大量 KV Cache导致实际并发数降低。启动后先用 curl 发送一个最简单的请求验证服务健康再检查日志里有没有显存不足的警告。这里有个容易被忽略点模型加载本身也可能触发 CUDA 初始化第一次请求往往比后续请求慢很多所以不要拿第一次请求的耗时当作真实性能。4.2 压测与关键参数调整服务跑起来后先做一轮基线压测。压测脚本我这里用一个简单的 Python 并发请求来做说明import asyncio import time import httpx async def send_one(client, prompt: str): payload { prompt: prompt, max_tokens: 256, temperature: 0.7, } start time.perf_counter() first_token_time None async with client.stream(POST, http://localhost:8000/generate, jsonpayload) as resp: async for chunk in resp.aiter_bytes(): if first_token_time is None: first_token_time time.perf_counter() - start return first_token_time, time.perf_counter() - start async def main(): prompts [介绍一下大模型推理优化。 for _ in range(40)] async with httpx.AsyncClient(timeout120) as client: tasks [send_one(client, p) for p in prompts] results await asyncio.gather(*tasks) avg_ttft sum(r[0] for r in results) / len(results) avg_total sum(r[1] for r in results) / len(results) print(fAVG TTFT: {avg_ttft:.2f}s, AVG total: {avg_total:.2f}s)跑完基线后记录三件事TTFT 均值、生成 256 个 token 的总耗时、GPU 显存和利用率。如果发现 TTFT 明显偏高优先检查是不是并发请求太多导致的排队如果显存利用率接近 100%说明max-num-seqs或max-model-len设置过大需要往回收。调参顺序我总结为一个原则先调整显存利用率再调整并发数最后才考虑量化。具体操作是先把gpu-memory-utilization从默认值调整到比如 0.85~0.95观察显存是否稳定然后逐步提高max-num-seqs每次加一倍并压测找到吞吐增长的“拐点”——通常加到某个值后TPOT 会显著上升再继续加就没意义了。如果显存确实不够再上量化或者限制上下文长度。4.3 上线后的监控与扩缩容推理服务上线后我建议至少盯住这几项指标GPU 利用率、显存占用、请求排队长度、TTFT/TPOT 的 P50 和 P99、错误率和超时率。这个环节最容易被低估的是“排队长度”。很多人看 GPU 利用率挺高就放心了但实际所有请求都在队列里等着用户侧延迟已经爆炸。所以监控里一定要有排队请求数超过设定阈值就触发扩容或限流。扩缩容策略不要只盯着 CPU 或内存LLM 推理瓶颈在显存和算力应该以 GPU 利用率和队列深度为核心信号。自动扩缩容时还要考虑推理服务的“冷启动时间”。一个实例从创建到真正能接流量往往要加载权重、构建 CUDA 内核耗时可能几分钟。所以不能等队列爆了才扩容应该设置稍小的队列阈值提前扩容缩容时则要谨慎避免把正在处理长请求的实例杀掉。5. 常见问题与排查技巧实录5.1 OOM 与显存突然爆掉这是推理部署第一大坑。症状通常是服务日志里出现显存不足报错或者整个进程被操作系统杀掉请求全部失败。排查思路按顺序来先看是什么请求触发。如果只是某些超长输入导致说明上下文长度控制没做好需要在网关层限制max_prompt_length。再看并发设置是否过高。max-num-seqs调太高时即使平均显存够用峰值也会因为多个长请求叠加而被击穿。检查推理框架的显存碎片情况。如果频繁出现显存不够但重新启动后又能撑一会儿大概率是碎片化问题这时可以尝试开启更积极的内存整理或者降低gpu-memory-utilization留下一部分余量。预防 OOM 的实用手段在网关处按上下文长度分级限流比如“长 prompt 请求最多同时 N 个”或者让推理框架拒绝超过max-model-len的请求并返回明确错误提示而不是硬着头皮算。5.2 首字很慢TTFT 过高用户反馈“半天不说话”十有八九是 TTFT 问题。调试时先分清是排队慢还是预填充计算慢如果是排队慢压测结果会看到大量请求积压此时优先优化调度降低并发请求数、增加实例、开启连续批处理。如果是预填充计算慢说明单请求第一次前向计算开销太大。常见原因是输入 prompt 长度过长预填充阶段要对全部 token 做并行注意力计算此时可以考虑对 prompt 做压缩或摘要也可以把公共 system prompt 提前缓存避免每次都重新计算。此外预填充和解码阶段可以拆分执行。有些框架支持把 prefill 操作放在单独的 GPU 流或单独的实例上避免长 prompt 请求拖慢所有在线生成请求。能拆分的话TTFT 能稳定不少。5.3 量化后输出质量明显下降很多人第一次跑 INT4 模型时会惊讶地发现“模型变傻了”。这不是模型坏了而是量化策略没有校准好。先用一个不量化的同版本模型跑同样的测试集对比输出确认差异范围。如果确实差异明显查看量化配置文件确认是否把所有层都压到低比特。此时可以尝试只对部分敏感层保持 FP16、其他层量化。另一个思路是增加校准数据集让它更贴近线上真实 prompt 分布而不是用公开数据集。量化模型上线前至少要跑一遍黄金测试集包含业务侧的高频问题、长文档摘要、多轮对话等场景。5.4 几个容易忽略的小窍门模型预热。新实例启动后先发送几个普通请求让 CUDA kernel、显存分配、缓存都初始化完成再接入真实流量。否则第一批请求可能极其缓慢甚至触发超时。压测时的 prompt 不要用完全重复的文本。如果所有请求内容都一样系统可能命中 prompt cache性能数据会虚高。压测数据要尽量模拟真实长度分布和话题多样性。流式输出时网络层容易成为瓶颈。如果每次只推一个字符HTTP 头开销占比太高。可以配置批量推送比如每 5~10 个 token 合并发送一次这样在保证流畅度的同时减少网络压力。我在实际项目里踩过最深的坑是上线前只测了平均延迟没测 P99。结果线上突然来了一波长文本请求P99 直接飙到 30 秒以上用户反馈汹涌而来。后来我把压测的重点放在长尾延迟和不同长度混合请求场景上并且在监控面板里长期展示队列深度与显存碎片率这才算真正把推理服务做稳。推理优化不是一锤子买卖先拿到干净基线再一个变量一个变量地调每一步都有数据支撑远比盲目追求某张 benchmark 截图靠谱。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑