资讯动态

vLLM推理服务需求文档实战:从KV Cache算显存到定性能指标

发布时间:2026/10/18 9:06:19 来源:尧图企业网站定制
1. 从KV Cache的账单说起一份规格说明书真正要回答的问题前阵子我负责给内部推理服务平台补文档同事丢给我一句话“把vLLM部署起来性能要求写清楚月底上线。”我一开始以为写软件需求规格说明书就是列功能清单支持文本补全、支持多轮对话、支持流式输出……后来发现真正麻烦的不是这些接口而是背后那些看着不起眼、实际会自动打架的参数——并发数、序列长度、显存、时延、吞吐量。不把这几项算清楚文档写出来也验收不了。vLLM这两年出现在几乎所有大模型推理平台的技术选型里核心就一句话让GPU显存不再被“预留”而是被“按需分配”同时用迭代级调度把GPU利用率顶上去。为什么这个能力重要你可以先算一笔账。假设你部署一个开源的7B参数模型FP16权重大约占14GB显存。同时模型在推理时需要为每个请求维护一组KV Cache。按32层Transformer、每组8个KV头、每个头128维、上下文4096 token来估算一个请求的KV Cache大约需要2 × 32 × 8 × 128 × 4096 × 2字节约等于512MB。也就是说并发32条请求时KV Cache就要吃掉大约16GB——这还不算模型权重和其他中间张量。看到这里你就明白同样的卡如果把KV Cache按“最大并发”预先留够显存马上见底如果按实际请求量动态分配、用页表管理碎片能塞进去的并发请求会多得多。vLLM的PagedAttention就是解决这个问题的关键。所以我要写的这份规格说明书核心不是“部署vLLM”而是“在vLLM的这些机制之上把性能边界、接口约束、验收指标写清楚”。这篇内容适合正准备把大模型推理服务做成正式项目的人——无论是后端工程师、算法工程师还是技术负责人。我会结合我们实际搭建平台时写需求文档、定指标、排坑的经历把“需求规格说明书里到底该写哪些东西”和“vLLM背后的技术取舍”一起拆开讲。2. 先算清KV Cache这笔账显存预算怎么做2.1 KV Cache为什么会成为推理服务的显存大户先补一个基础概念。Transformer在生成下一个token时需要读取之前所有token的Key和Value向量来做注意力计算。如果每生成一个token都把之前的计算结果重新算一遍算力和耗时会不可接受所以工程实现上会把历史token的K、V向量缓存下来这就是KV Cache。它的体量随序列长度和并发数线性增长却经常被低估。KV Cache的大小可以这样快速估算大小 2K和V两组× 层数 × KV头数 × 头维度 × 最大序列长度 × 并发数 × 存储字节数。拿7B模型举例假设是32层、8个KV头、每个头128维最大序列长度4096并发32路使用FP16每个数占2字节单请求单token的KV Cache 2 × 32 × 8 × 128 × 2 字节 1MB左右这里只算一个token的K和V单请求完整4096序列 1MB × 4096 ≈ 4GB不对这里要小心。上面这个估算口径粗了。更准确的口径是一个请求在生成完整4096序列时KV Cache总量 2 × 层数 × KV头数 × 头维度 × 序列长度 × 字节数 2 × 32 × 8 × 128 × 4096 × 2算下来是约536MB。这才是“一个满长请求占到最后的Cache占用”。我之前开头说的“单请求约512MB”就是按这个口径来的。32路并发就是16GB上下。它和模型权重14GB加在一起已经接近一张A100 80GB的半个卡了如果换成24GB显存的卡权重就已经塞不下。这里的关键是KV Cache和模型权重不一样它是动态增长的每个新生成的token都要往里追加内容直到请求结束才释放。所以部署前必须按“最大序列长度 × 最大并发数”的峰值去算显存而不是按平均请求长度算。我在现实中见过一个很典型的翻车某同事在需求文档里只写了“支持并发32路”没有写明序列长度和模型层数结果用一张24GB显存的卡去部署13B模型权重本身就超过24GB了KV Cache根本没地方放。后来把模型量化成INT4才勉强跑起来。2.2 需求文档里的显存预算表怎么写把估算过程放进需求文档不是为了让文档变厚而是为了在评审阶段就让所有人对齐“这张卡的边界在哪里”。我当时在规格说明书里加了一张显存资源预算表几列分别是模型参数量、推理精度、最大序列长度、最大并发数、单卡显存、KV Cache峰值、权重占用、剩余余量。给个我们当时实际用过的组合7B模型、FP16权重、最大序列长度8192、最大并发64路。按公式算KV Cache峰值大概为2 × 32 × 8 × 128 × 8192 × 64 × 2字节约33.5GB。权重14GB再加上激活值、临时张量和CUDA context80GB的卡余量大概还能剩20GB左右。这个余量不算宽裕但跑生产可以接受。于是我们在文档里补了一条约束“当并发超过64或序列长度超过8192时必须切换量化模式或扩容节点。”这句话看着简单但它是让运维和开发不再互相甩锅的关键。提示KV Cache估算公式里的“KV头数”不一定等于注意力头数。现在很多模型用了GQA分组查询注意力KV头数远小于查询头数Cache占用会小很多。估算前先去模型配置里确认这两个数不要拍脑袋。2.3 为什么PagedAttention能打破“整段预留”的浪费vLLM的PagedAttention借鉴了操作系统虚拟内存的思路它不再为每个请求预分配一整段连续的KV Cache空间而是把Cache切成固定大小的块按需分配。KV Cache在物理上可以是不连续的再用块表把逻辑地址映射到物理块上。这样做有三个直接好处第一是显存利用率更高不再有“整段预留但用不满”的浪费。第二是碎片可以被重新利用一个请求结束后它的块能立刻回收给下一个请求。第三是最重要的——多个请求如果共享相同前缀比如同一个系统提示词它们的KV Cache块可以直接指向同一块物理内存只算一次存储。共享前缀这一点对客服类、Agent类场景非常关键。我见过一个真实服务系统提示词有800多token每个用户会话都是新请求。如果没有前缀共享这800多个token的KV Cache每个会话都要存一遍显存开销成倍增加开启前缀缓存后这部分只存一份后续请求共享实测显存峰值可以省掉一个数量级。这个收益写进需求文档时不应该当成“优化项”一笔带过而应该作为“系统提示词较长的并发场景下的必配项”。3. 从静态批处理到Continuous Batching吞吐量是怎么被顶上去的3.1 静态批处理为什么浪费GPU很多自建推理服务最初用的是静态批处理把一批请求凑齐一起送进GPU等这一批里最慢的那个生成完整批一起退出下一批再进来。这种做法有个明显的空转问题——批次切换的间隙里GPU可能闲着批次内部的慢请求也会拖住快请求快的生成完了也得等慢的。如果把GPU比作一个食堂窗口静态批处理就是“凑满一桌才开饭”——人少时窗口空着人多时先到的人也得等后面的人凑齐。vLLM实现的Continuous Batching连续批处理把调度粒度从“请求级”降到了“迭代级”一个请求每生成一个token就算完成一次计算步它可以在这个迭代结束时让出资源新请求也能在下一个迭代随时插进来。还是那个食堂窗口相当于改成了“来一个炒一个”窗口永远不会因为等人而闲着。这个机制直接决定了两个关键体验指标首批token时延TTFT和单token生成速度TPOT。静态批处理下新请求可能要等一整批结束才能获得计算TTFT波动很大连续批处理下新请求可以在下一次迭代就进入计算TTFT更稳定整体吞吐也更高。我们在测试环境做过基础对比同样混合长短请求开启连续批处理后整机吞吐大约提升2倍以上P95 TTFT的毛刺明显变少。不过要注意这类数据跟模型尺寸、请求分布强相关拿到自己环境里复测才是正道。3.2 性能指标不能只写“高并发、低延迟”我见过太多需求文档写“系统应支持高并发、低延迟”然后就没有然后了。这种话完全无法验收。实际定指标时我把它拆成了四类全部落到分位数TTFTTime To First Token从请求到达服务到返回第一个token的时间P95目标设在1.5秒以内。TPOTTime Per Output Token每生成一个token的间隔P95目标设在50毫秒以内。吞吐量单位时间生成的token数按整卡计算目标设定基于压力测试结果。排队时间请求在队列中等待的时间P95目标设在500毫秒以内超出时要有背压策略。这四项分开定是因为它们在连续批处理下会互相牵制。你把max_num_seqs调得很大吞吐上去了TPOT可能恶化你把并发限制得很小延迟好看但吞吐平庸。所以在需求章节里我把“性能指标”和“关键参数上限”放在同一张表里注明调整哪个参数会影响哪个指标。指标口径P95目标受影响的关键参数TTFT请求到达至首个token返回≤ 1.5smax_num_seqs、排队策略TPOT单个token生成间隔≤ 50msmax_num_seqs、批大小、量化档位吞吐量整卡每秒生成token数以压测基线为准并发数、量化档位、请求长度分布排队时间请求在队列中等待时长≤ 500ms流量控制、限流策略3.3 请求长度差异对SLO的影响必须在文档里写死还有一个容易被忽略的点线上请求长度是长短混合的。如果压测脚本里全是短问答吞吐数字会非常漂亮一旦混入几个长序列生成请求它们会长时间占用大量KV Cache块把短请求的TTFT顶上去。我们在验收时专门定了一个“请求分布模型”30%短问答、40%中等长度、30%长文生成混合压测才算合格。这个请求分布写进规格说明书后测试团队不再自由发挥前后几次压测的数据也可比了。实测提醒不要用“平均时延”做指标要用P95或P99。平均时延会被大量短请求拉低掩盖长尾问题。我们上线后第一次正式压测P95比平均值高了好几倍排查了半天才发现是几个超长生成请求在拖后腿。4. 量化与精度边界需求文档里最容易漏掉的开关4.1 量化在推理服务里是刚需不是可选项很多规格说明书把精度当成“模型训练的事”在推理平台文档里只字不提。但vLLM这类推理服务恰恰是精度控制最容易出问题的地方同一套模型权重FP16、BF16、W8A8、W4A16跑出来显存占用和生成质量都不一样。如果不提前写清楚算法团队说“模型效果没问题”运维团队说“显存放不下”两边会对着文档吵架。量化在这里起两个作用一是降显存7B FP16权重约14GBW4A16后可能降到4GB左右二是降带宽压力推理任务普遍是访存密集型低位宽权重从显存取到芯片的时间更短生成更快。代价是模型质量可能有回退尤其是长尾知识、罕见词、非规范文本这类场景。4.2 常见精度格式的选型逻辑我在需求文档里放了一张表后来它成了算法和运维两边对齐的基准精度格式权重显存以7B为例质量回退风险典型用途FP16/BF16约14GB低默认基线质量敏感场景W8A8约7GB较低吞吐优先算力充足时用W4A16如AWQ、GPTQ等约4GB中显存受限追求更高并发写这张表不是让你直接拍板用哪一档而是把选择权明确交给业务方。比如我们的内部服务分两类面向普通用户的问答接口允许W4A16面向评测和数据验证的接口强制FP16。这个策略写进规格说明书之后测试团队就知道两套精度要分别验证而不是默认全服务一个档位。4.3 量化场景下最容易翻车的细节我踩过一个具体的坑量化模型在采样温度调高、或者生成长文本时偶尔会出现重复内容或逻辑断裂。一开始怀疑是解码参数设置问题后来把同样请求用FP16和W4A16各跑一遍才确认是量化精度损失在长序列上被放大了。从那以后我们在需求文档里加了一条“量化模型必须通过固定评测集校验评测集至少包含长文本、带噪文本和常见指令三类。”另外vLLM加载量化模型的方式和原生模型有差异AWQ、GPTQ这些格式需要专门的文件或运行时配置。需求文档里最好明确“哪个接口默认用精度A、哪个接口可以用精度B、切换精度是否需要重启服务”避免上线后为了一个开关来回重启服务。5. 从单卡到多卡部署拓扑和扩展性怎么写才不翻车5.1 显存放不下时的三条路单卡塞不下大模型或者并发能力不够时常规思路是上多卡分布式。但“支持多卡部署”这六个字很危险——多卡之间怎么切、通信开销多大直接决定吞吐收益。vLLM生态里常见的是两大类张量并行Tensor Parallel把某一层的矩阵切成多块放在不同卡上卡间通信频繁适合单机多卡。流水线并行Pipeline Parallel把不同层分给不同卡流水式执行适合跨机扩展。在专家模型上还有专家并行等更细的切法但至少要知道TP和PP的区别。打个比方TP是把一个收银台拆成几个窗口同时算一笔账单笔变快但每步都要对账PP是把排队的顾客按步骤分给不同窗口吞吐更高但单笔时延可能变大。选型时如果想要低延迟、高并发单机内用TP如果想要突破单机显存上限、做大规模扩展上PP或两者组合。5.2 扩展性指标不能只写“提升N倍”我们在文档里把扩展性拆成了三条可测试的目标第一同一模型从1卡扩展到4卡后最大并发数至少提升到单卡模式的2.5倍以上。第二扩展到8卡时吞吐提升不得低于扩展前整机的1.5倍如果低于这个值说明通信开销已经吞噬了收益。第三新增节点或卡后服务需要支持滚动接入不需要整体重启。这里要特别提醒张量并行的收益不是线性的。跨卡通信的频率很高8卡以上如果走的是普通以太网而不是高速互联性能衰减会非常明显。我们的实测里2卡到4卡的收益还算理想但4卡到8卡时增速明显放缓。所以需求文档里别拍脑门写“支持百卡规模”先写清楚“在哪些硬件互联条件下达到什么目标”否则验收时必然翻车。5.3 故障和容错边界也要写明白分布式部署之后的容错问题必须在规格说明书里留一段。生产环境最常见的是某张卡温度过高导致性能下降、某个节点OOM之后整个服务假死。我们的处理方式是不追求自动恢复所有坏卡而是用健康检查定期探测每张卡的使用率和错误计数发现异常就摘掉这个节点并把流量切换到其他节点。需求文档里要写的是“服务在单节点故障时P95时延允许劣化到正常水平的1.5倍持续不超过N分钟且故障节点恢复后自动回流不需要人工介入。”这一条看起来苛刻但它逼着运维和开发把可观测性做起来而不是等出事了才翻日志。6. 实测踩坑记录规格说明书之外的那些隐性成本6.1 前缀缓存没生效显存直接翻倍第一次上线客服会话服务时我们以为共享系统提示词的KV Cache是默认行为结果压测到60并发左右就报了显存OOM。排查链路是这样的先看模型加载方式没问题再看请求处理路径发现每个请求的prompt都被套了一层多轮对话模板系统提示词和实际请求被拼成了不同的前缀形状共享前缀命中率非常低。把模板拼装逻辑调整成统一前缀让所有子请求复用同一段context之后显存占用立刻降下来。现在我们在需求文档里专门有一条“系统提示词及对话模板必须保证前缀一致不得在请求内动态插入前缀内容。”6.2 指标只写平均值长尾体验没人管有一次压测显示“平均生成速度很快”但线上用户反馈转圈。我们把日志按请求长度分组发现长文生成的TPOT在P95上比平均值差了好几倍。根因是max_num_seqs设置过大短请求不断插队导致长请求每生成一个token都要跟一大批短请求抢计算资源。调低并发上限、给长请求单独设置超时和线程池隔离之后P95回落到了可接受范围。这个经历让我确定了一个原则SLO里每个指标都必须带分位数并且要和请求长度分布绑定。6.3 量化模型在特定输入下异常怎么定位另一个坑是量化后模型偶尔输出乱码。排查过程是这样的先把输入长度缩短问题消失再把采样温度调低问题也消失最后固定温度、固定随机种子用FP16基线跑同样请求复现不了。对比结论是W4A16对非规范输入更敏感尤其是在温度较高、序列较长时。我们后来给量化服务加了一个“回退通道”遇到置信度偏低的生成或用户主动选择高质量模式时自动切换到FP16节点。这种双档运行的机制写进需求文档既是功能需求也是运维兜底。6.4 多卡扩展的预期管理还有一次我们把8卡扩展目标定成“吞吐提升6倍”结果压测只有3倍出头。拆解下来原因在于请求平均长度偏短单次生成计算量小跨卡通信的固定开销占比被放大。后来我们把压测请求分布改成长短混合同样配置下吞吐提升接近5倍才算勉强达标。这件事说明扩展性需求必须和负载特征绑定描述“在什么请求分布下达到多少倍提升”才是一句能验收的话。7. 我的收尾建议写这份规格说明书的过程中我最深的体会是需求文档的价值不在列功能而在把技术决策的边界凝固下来。PagedAttention让显存利用变高效Continuous Batching让吞吐变好看量化让部署变轻但它们每一项都有限制条件。把这些限制条件写清楚比把功能清单写全更影响项目成败。如果你接下来也要写类似文档我建议从三件事开始第一先用公式估算峰值显存把模型权重、并发数、序列长度、量化档位的关系列成一张表第二把性能指标从“高并发、低延迟”改成TTFT、TPOT、吞吐量、排队时间四类并带上P95目标和请求分布说明第三把前缀共享、量化回退、多卡扩展的边界条件写进去哪怕当时看起来用不上。文档写完之后再拿去让运维、算法、测试各看一遍如果他们不再追问“到底怎么算”“到底怎么验”这份规格说明书就算合格了。最后再分享一个小技巧不要一开始就把SLO定死先跑一轮基准测试再根据测试结果反推合理的指标然后把测试条件和指标一起写进文档。我们后来所有验收都复用同一套压测脚本和请求分布环境变了就在那一页标明环境差异这样版本之间、团队之间的争议会少很多。

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

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

免费获取报价 →
↑