news 2026/11/3 16:20:49

大模型推理Infra架构解析:从连续批处理到KV Cache优化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
大模型推理Infra架构解析:从连续批处理到KV Cache优化

1. 推理和训练为什么必须分家:从资源特性说起

先说一个我自己踩过的坑。早两年做算法平台,团队里大家的惯性思维是"训练那套Infra这么成熟,推理直接套过来不就完事了吗"。于是我们把模型权重加载到GPU上,写个Flask服务包装一下,请求进来就forward一把,看起来完全没毛病。结果线上流量一上来,GPU利用率只有20%多,但每个请求都要等三五秒,长一点的文档任务甚至能等到半分钟,客户投诉电话直接被打爆。

问题出在哪?推理和训练在资源特性上根本不是一回事。

训练是高吞吐、大batch、可容忍延迟的批量计算。你把几千条样本攒在一起,GPU一次forward吃掉一大批数据,算得越久越划算,只要最终能出loss更新权重就行。它的优化目标是"单位时间处理多少样本",延迟高几秒、几分钟根本无所谓。

推理是低延迟、动态batch、反复交互的在线服务。用户发一句话,系统必须在几百毫秒到几秒内给出响应,而且请求是持续涌入的,每个请求长度不一样、生成的目标长度也不一样。你没法像训练那样把所有请求攒够了再一口气算,因为用户等不起。这就像一个是中央厨房做预制菜,一个是高端餐馆现点现做:前者追求批量效率,后者追求的是每桌客人的出餐速度和服务质量。

再往深一层,MaaS(Model as a Service)这个模式把推理的压力放大了。MaaS本质上是把模型推理封装成标准化的服务能力对外出售,用户不关心你底层跑了什么模型、用了几张卡,只关心你的接口快不快、稳不稳、贵不贵。这对Infra提出了几个训练时代根本没怎么想过的要求:

第一个是多租户隔离与公平调度。一个在线推理服务同时服务千百家客户,有人发短文本,有人发长篇文档,有人做高并发问答,如果某个客户的大请求把GPU霸占了,其他客户全部被阻塞,那SLA就崩了。你得在设计上用调度策略把这个"一锅端"的风险隔离掉。

第二个是成本敏感度极高。训练贵有贵的道理,模型效果好,老板咬牙也就买了。推理是持续运行的,每个token都在烧钱,一个热门的7B模型服务一天跑下来,电费、折旧、运维成本都是真金白银。系统的吞吐效率直接决定毛利率,同样的GPU资源能不能多接住一倍的用户,就是盈利能力的分水岭。

第三个是稳定性要求完全是另一个量级。训练任务挂了可以重新跑,推理服务挂了客户立刻流失。MaaS平台需要一个完整的链路来保证跨请求、跨实例、跨集群的稳定:路由、熔断、限流、降级、灰度发布、弹性扩缩容,一样都不能少。

所以在"AI Infra for 推理(MaaS)"这个话题上,第一性原理就是:别把推理当成训练的简化版,它是一个独立的系统工程。理解了这一点,后面所有模块的设计逻辑才能串起来。

2. 一条推理请求的一生:拆解MaaS推理链路的核心模块

要给"推理Infra"画结构图,最好的办法是跟着一个请求从进来到出去走一遍,看它经过哪些关键节点,每个节点在干什么,瓶颈在哪,这也是我梳理架构时最常用的思路。

抛开具体产品差异,一个典型的MaaS在线推理请求大致会经历下面这些阶段:

请求网关层。用户把HTTP/GRPC请求打到平台上,先经过网关做基础的事情:鉴权、限流、路由、协议转换。限流算法可以用令牌桶或者基于QPS的滑动窗口,防止某个突发流量把后端打爆。路由则是决定这个请求交给哪个模型服务——是Qwen还是Llama?是7B还是72B?是CPU推理还是GPU推理?这个映射表需要灵活配置,往往还要支持按用户级别、按模型版本灰度路由。

上下文预处理层。请求里的prompt文本要先被tokenizer转换成token序列,可能要做system prompt的拼接、多轮对话历史的组装、长度检查。别看这一层简单,它直接影响生成质量,比如不同模型对chat模板的格式极其敏感,少一个<|im_start|>可能输出就会乱掉。

推理引擎层。这是整个Infra的核心位置。引擎要做几件事:把token序列送给模型做前向计算,第一个阶段叫prefill(预填充),把整个输入上下文一次性算完,生成首个输出token;然后进入decode(解码)阶段,每个token都要把上一轮新生成的token加入序列再做一次前向推理,逐步生成后面的token。这两个阶段的特点差异巨大,prefill是计算密集型的,decode是对延迟敏感且每步只算一个token,这两个阶段的资源分配和优化策略完全不同,后面我会专门展开讲。

采样与解码策略层。模型输出的是一个概率分布,需要按你指定的策略来选具体的token:贪心解码、top-k/top-p采样、温度系数、beam search、repetition penalty等。采样参数不光影响质量,还会影响吞吐——beam search的开销远高于贪心解码,实际服务中这都是要按场景做组合配置的。

输出与反馈层。生成结束后做tokenizer解码、按需做后处理(剔除特殊token、过滤敏感词)、计费上报、日志落盘、指标采集,最后把文本返回给调用方。如果是流式输出(这是MaaS最常见的交互方式),引擎还需要支持SSE(Server-Sent Events)或WebSocket分chunk把结果一个个推给客户端,而不是等全部生成完再吐。

这条链路上最容易被人忽略的不是单个模块的性能,而是模块之间的节奏匹配。网关限定了并发上限,但如果引擎端的调度策略和显存规划没跟上,网关放进来的请求到了引擎还是得排队。就像餐厅门口排号系统放了一批客人进去,结果后厨没有那么多灶台,客人全堆在过道里,体验只会更糟糕。

另一个容易踩坑的点是,prefill和decode混在一起会导致"长尾效应"特别明显。我自己实测过,如果不对prefill做拆分控制,一个超长文档的prefill请求进来,它会占用整块GPU计算资源好几百毫秒甚至几秒,这段时间里其他所有请求的decode都被堵住,线上的P99延迟直接飙升到几十秒。这是推理服务第一个需要在架构层解决的核心矛盾:不要让一个heavy的prefill拖垮一批正在解码的轻量请求。

3. 连续批处理的高性能原理:一个能提升数倍吞吐的机制

在上面说的环节里,推理引擎层是Infra的重中之重,而谈到现代推理引擎就绕不开一个名词:连续批处理(Continuous Batching),也有人叫它动态批处理(Dynamic Batching)或in-flight batching。可以说,这项机制是让推理服务从"能用"走向"高效"的分水岭。

传统做法是很朴素的静态批次拼接。假设你的服务最大支持一次处理4个请求,就把网络里攒到的请求攒满4个再一起送进GPU;这4个请求中任何一个生成完了,整个batch都要停下来等最慢的那个也生成完,然后一起释放显存、整体换下一批。这就像一条流水线,一个工位卡住了,整条线都得停着等。

这个做法最大的问题是,请求的生成长度完全不同。有人问"你好"只需要生成10个token,有人要求"写一篇800字的文章"要生成1000个token,让它们在一个batch里同步进退,快的请求必须陪着慢的请求走完全程。这段时间里GPU明明有空闲算力,却因为机制限制没法利用起来。

连续批处理的核心思路是:不要整批同进同出,谁完成了谁立刻退出,新请求随即补位。引擎在每个解码步骤都做一次调度:检查哪些序列生成了结束符或达到最大长度,就把它们从显存中释放掉,空出来的显存立即分配给正在排队的请求,继续迭代计算。这样一批里同时存在的都是"尚未完成"的序列,GPU的有效吞吐被拉到非常高的水平。

举一个我实际估算过的例子。我用一个7B模型在单张A100上部署,如果走静态批处理,即使batch size设到8,因为慢请求拖着快请求,实际有效并发吞吐大约只有每分钟800个左右的短请求;切到连续批处理之后,虽然单次请求的端到端延迟没有明显变短,但同样的GPU每分钟能处理2500到3000个短请求,比之前翻了3倍以上。这就是为什么所有头部推理框架(vLLM、TensorRT-LLM、PyTorch Serve等)都把它当作默认机制的原因。

要自己理解这套机制,最好的学习材料其实是nano-vllm。它是vLLM的精简教学版,代码量比正经vLLM少两个数量级,但你可以在里面清楚地看到Continuous Batching是怎么逐帧跑起来的。我当时跟着它的源码走了一遍,几个关键的实现点印象非常深:

  • 调度器是独立的组件。它维护一个待处理队列和一个正在处理的序列集合,每个step都重新决策哪些序列进、哪些序列出。
  • 序列状态用step数来区分。每个序列记录自己已经生成了多少token,调度器用这些信息来决定它是否满足退出条件。
  • padding mask的处理是性能细节。因为每个step序列长度都在变化,GPU计算时通常还是按最大序列长度做padding,长度对齐的策略直接决定MEMORY bandwidth的浪费程度。

在工程落地时,还有一个容易被忽略的问题:连续批处理的收益和并发请求数强相关。如果同时在线请求只有个位数,连续批处理的效果并不明显;反之,如果请求堆积很严重,它的价值就完全体现出来了。所以做容量规划时,不要只看"峰值延迟"或者"平均吞吐",还要观察队列深度,这个指标才能真正暴露调度器的压力。

顺带说一句,很多人以为提升推理吞吐就是靠"加大batch size",但在显存固定的情况下,batch size能开多大其实是由每个请求的KV Cache占用决定的,这就自然引出了下一个话题——KV Cache的管理。

4. KV Cache就是推理的隐形命门:显存管理、PagedAttention与长序列

如果说连续批处理决定的是"调度效率",那么KV Cache就是决定"物理能装下多少并发"的硬约束。

先解释一下KV Cache为什么存在。Transformer模型在推理decode阶段,每生成一个新token,都需要让模型重新"回顾"整个上下文的Key和Value。如果不做缓存,每生成一个token就要全量重新计算所有历史token的Key和Value,计算量随着生成长度线性爆炸,完全不可接受。所以引擎会把每一步计算出来的Key矩阵和Value矩阵缓存下来,后续step直接读取历史缓存,只需要处理新增的token。这套缓存就是KV Cache。

问题在于,KV Cache的占用极其惊人。以一个7B模型、fp16精度为例,模型权重大概是14GB左右,已经很占显存了;但KV Cache的占用同样不可小觑。如果模型的层数、注意力头数、隐层维度配置比较典型,每个token大约需要几百KB到1MB的显存来存它的Key和Value(具体数值取决于模型结构和精度),而一个长文档请求动辄有几千上万个token,光一个请求的KV Cache就可能吃下几个GB。多个请求并发时,显存分分钟被打满。

更麻烦的是KV Cache的动态性。训练阶段显存占用是静态的,模型多大就是多大,计算前就能算好。推理阶段每个请求的KV Cache大小是随着它一步步生成token而随时变化的,你没法提前精确得知用户最终会生成多少个token。如果预留多了,显存浪费,能承接的并发请求变少;如果预留少了,生成到一半显存不够,直接OOM,服务崩溃。

vLLM团队提出的PagedAttention就是这个问题的经典解法。它的想法借鉴了操作系统虚拟内存的分页机制:把KV Cache按固定大小的block(页)来管理,比如每个block能装下16个token的Key和Value。每个序列不需要在显存里占用一段连续空间,而是按需分配若干个block,通过block table(页表)把逻辑上的连续序列映射到物理上分散的block上。序列继续生成时,像按需调页一样动态追加block;序列结束时,整个block链条被完整回收,碎片化问题大幅缓解。

这个设计最值钱的地方在于:显存利用率被提高到了极致。以前静态预分配动不动浪费40%的一半显存,用分页之后物理显存几乎每个字节都在为真实的token服务。另外,block如果分配得够细,还能支持多个序列共享同一个block的KV(比如做并行采样时,同一个prompt在多个分支上复用前面的KV Cache),省显存效果进一步放大。

我在实际用vLLM部署服务时,对KV Cache的认知是被一个真实故障狠狠教育的。当时我把--gpu-memory-utilization设到0.95,想着显存别浪费,结果模型上线后经常过一两个小时就报KV cache size out of capacity。后来排查才发现问题出在:这个参数控制的是给模型权重 + KV Cache总共预留的显存比例,但超出了之后引擎并不自动驱逐或换出请求,而是直接报错拒绝服务。所以现在我的习惯是:这个值最多设到0.85到0.9,宁可牺牲一点点显存利用率,也要留出安全余量来应对峰值突发。

还有一个小技巧:如果是对话场景,KV Cache的优化绝对绕不开前缀复用(prefix caching)。系统提示词和对话历史在很多请求里高度重合,如果能把这些公共前缀的KV Cache缓存起来直接复用,长文档、多轮对话场景的首token延迟可以砍掉一大半。vLLM后来把Prefix Caching做得比较成熟了,它会在block级别记录哪些block是跨请求共享的,新来请求的prompt如果命中已有前缀就直接映射过去,不用重新计算。

说回到结构梳理这件事上。KV Cache管理能力直接决定了一个推理引擎能不能支撑MaaS平台的规模化运营。如果底层这层做不好,上面无论怎么做策略优化都是空中楼阁:反正显存一满,所有请求都进不来。

5. 量化与投机采样:追求吞吐时不能牺牲掉的关键环节

在Infra的结构里,除了引擎和显存管理,还有两类策略层面的手段经常被拿来优化推理性能:模型量化和投机解码。这两块都是典型的"收益大、坑也深"的领域,值得单独拎出来说。

量化这个事儿,很多人的认知停留在"把fp16变成int8或者int4就能省显存"。方向没错,但落地的时候细节特别多。如果你只是在框架层面改了数据精度,而没有匹配的量化kernel支持,往往会出现"显存确实下来了,但推理速度反而变慢"的诡异情况——因为反量化操作本身是有开销的。

常见的量化粒度有两大类:一是仅权重量化(weight-only),比如常见的AWQ、GPTQ,把模型权重压到4bit,但激活值仍是fp16,计算时反量化回fp16做矩阵乘;二是权重激活一起量化(W8A8),权重和激活全部压到8bit,直接走INT8矩阵乘来提速。对不同模型、不同任务,这两条路线的效果差异很大。我在一个72B模型的服务上试过,W4A16的AWQ能把显存占用压到原来的一半不到,但真正决定能部署在哪张卡上的永远是显存墙,而决定用户体感的则是首Token延迟和吞吐,这俩指标不一定跟量化位宽线性相关。

实操中我的经验是:不要迷信"4bit一定比8bit好"。量化位宽越低,模型质量的损失就越大,尤其对数学推理、代码生成这类需要精确逻辑的任务,低比特量化经常会出现回答质量明显下降的情况。最稳妥的路径是:跑完量化后必须做一组标准评测比对,用固定的评测集对比量化前后的通过率、困惑度等指标,差太多就升档位。

投机采样(speculative decoding)是另一个有意思的优化方向。它的核心思路非常反直觉:既然大模型每个token的解码都慢,那我能不能先用一个小模型(draft model)快速草拟出接下来几个token,再让大模型一次性并行验证这几个token是否正确。如果验证通过,等于一次前向算出了好几个token,解码速度直接翻倍;如果某一步验证失败,就在出错位置回滚并从那里重新生成。这个方案在batch size较小、延迟敏感的场景里收益特别明显,实测常常能让生成速度提升1.5到2倍甚至更多,主要取决于草稿模型的命中率。

但它在MaaS服务里并没有被普遍用起来,原因也很现实:需要同时加载一个草稿模型,占一份额外显存;对小批量请求红利大,但大批量场景里大模型的连续批处理已经让GPU算力很满,草稿模型反而容易变成负担。另外,投机采样对采样参数的语义兼容性要仔细验证——如果你改了temperature或者top-p,draft和verify的一致性逻辑可能会出问题,生成结果的分布是否和原来一致也是个需要专门评测的问题。

放在Infra的全局视角里,量化和投机采样都属于"优化手段工具箱"里的选项,不应该把它们当作默认开启的标准配置。一个成熟的推理平台,应该按模型、按场景、按SLA级别灵活组合这些能力。比如低延迟的在线对话用投机采样来压延迟,离线批量生成任务就直接跑连续批处理吃满吞吐,长文档处理用int8量化来扩大并发容量——配置得越细,整个集群的效率才越接近最优。

6. 部署与服务化落地:路由策略、弹性伸缩和观测指标

结构梳理到这一步,已经覆盖了推理引擎的核心机制,但真正把这些能力变成线上稳定服务的,是部署和服务化这层工程能力。这也是很多团队"引擎跑通了但线上天天报警"的根源所在。

先看路由和调度。MaaS平台往往同时在线上服务着多个模型,而且每个模型通常有不止一个副本。请求进来时,路由层需要做几个决策:去哪个模型、去哪个集群、去哪个GPU实例。模型选择相对直观,难的是实例选择。两个常见问题:一是实例之间的负载不均,有的GPU已经排队排到几十条,有的GPU还闲着,简单地按固定权重轮询就很容易翻车;二是亲和性损失,同一个用户在多轮对话场景下,最好能连续落在同一副本上,因为连续对话的KV Cache可能可以复用——如果不做亲和性路由,每次把请求打给不同副本,前缀缓存的效果就白费了。

弹性伸缩这块,MaaS场景和传统Web服务有本质区别。传统Web服务是CPU/内存指标起来就扩容,一两个实例拉起就开始接流量;推理服务扩容涉及模型权重的加载,动辄需要几十秒钟甚至几分钟(尤其大模型),而且单实例能承受的算力是有限的,GPU本身就是瓶颈。所以推理场景的弹性伸缩必须做提前量和预测式,不能等指标报警了再扩。我见过的方案是:基于请求队列深度设置多级水位线,水位线达到60%就开始预热扩容,90%就强制扩容;缩容同理,要确保运行中的请求走完再回收实例,否则在线请求直接被掐断。

MaaS的计费与算力规划也离不开观测数据治理。没有精细化的指标,就没法算明白成本和做容量规划。我自己在搭建观测体系时,最关注下面这几个指标:

指标含义有什么用
TTFT首Token生成时间最直观的用户体感,暴露prefill和排队问题
TPOT每个Token的生成时间衡量decode阶段的速度,反映引擎效率
ITL流式输出中相邻Token间延迟体验类指标,换行卡顿就是它的事
吞吐(Tokens/s)单位时间生成的Token总数算成本和机器利用率的核心
队列深度等待调度的请求数观察调度器压力的早期信号
KV Cache利用率已用显存/总显存判断容量是否充足、是否该扩容

我一直觉得,队列深度比延迟指标更早反映系统的健康状态。延迟是结果指标,等它飙上去再处理已经太晚了;队列深度是过程指标,它一上来就意味着积压正在形成,马上扩容或者降级都来得及。

部署层面还有一个容易翻车的点:多模型共置。为了节省显存,有人喜欢把多个小模型放在同一张卡上,理论上可行,但不同模型的推理模式不同,一个模型出现长文生成高峰时,另一个模型的延迟也会被拉爆。如果非要做共置,建议在调度器层面把不同模型的执行分组错开,或者干脆启用了推理框架的queue机制,让不同模型走独立的优先级队列。

最后再谈谈成本。推理服务的成本大头是三块:GPU机的折旧和电费、运维的人力和带宽成本。GPU成本通常占80%以上,所以成本优化的主战场在于:能不能更充分地利用每张卡的FLOPS和显存。连续批处理、量化、prefill/decode分离调度,这套组合拳下来通常能把单位Token成本降一半左右,但降下来之后还有一步不能省——持续的回归评测。任何一次优化改动都要跑一遍功能回归,保证模型输出质量没退化,同时用压测数据验证延迟和吞吐指标。缺了这道工序,优化带来的收益很可能在线上被质量问题全部吃掉。

7. 结束语

梳理完整个推理Infra的结构,我自己的感受是,这个领域的复杂度远比想象中高:它不是单点技术的堆叠,而是调度、显存、网络、容灾、观测、成本多个维度耦合在一起的系统工程。而且有一点和训练Infra很不一样:训练系统每一次优化容错空间很大,推理系统每一次改动都会直接暴露给终端用户,所以上线前的回归验证和监控设计千万不能省。

如果是从零开始搭建或者学习这个方向,我个人的建议是先不要一头扎进复杂的框架源码里,而是从nano-vllm这类小型实现入手,理解请求是怎么被调度、KV Cache是怎么被管理、连续批处理是怎么运作的这几条主线,然后回到vLLM这样的生产级框架里对照验证。实际部署时从一个小模型、两个副本开始,把指标观测和故障恢复跑顺了再往上加规模。推理Infra的很多坑,都是用肉眼盯出来的,不自己压一次测、不自己看着队列深度飙升一次,很难真正理解那些参数设计背后的理由。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/3 16:20:48

网络安全基础与实战:从CIA三要素到防护策略

1. 网络安全基础认知&#xff1a;构建防护思维框架刚接触网络安全的新手常犯的错误&#xff0c;就是直接跳进工具使用和技术细节&#xff0c;却忽略了最基础的防护思维建立。这就好比学武术只练招式不学心法&#xff0c;实战中必然漏洞百出。让我们先搭建正确的认知框架。1.1 安…

作者头像 李华
网站建设 2026/11/3 16:20:02

Shell脚本多行注释全解析:四种方案与避坑指南

1. 多行注释这件事&#xff0c;为什么值得单独拎出来聊写shell脚本的人大概都有过这样的体验&#xff1a;一段逻辑暂时不想执行&#xff0c;又舍不得删&#xff0c;于是习惯性地敲下#&#xff0c;一行一行地加注释符号。三五行的脚本还好&#xff0c;碰上几十行的函数块或者一整…

作者头像 李华
网站建设 2026/11/3 16:19:54

长上下文提效与AI芯片估值:编码场景下的推理成本账本

1. 从一条日报说起&#xff1a;长上下文与AI芯片为什么同时被推上台面9月25日这条AI日报里&#xff0c;两个信息点被放在了一起&#xff1a;Claude Opus 5.5在长上下文处理上的效率提升&#xff0c;以及某AI芯片初创公司估值冲到百亿。表面上看&#xff0c;一个是模型能力迭代&…

作者头像 李华
网站建设 2026/11/3 16:16:31

最小二乘法在时间序列预测中的高效应用与实践

1. 项目背景与核心价值时间序列预测在金融、气象、工业控制等领域有着广泛应用。传统方法如移动平均、指数平滑往往难以捕捉复杂非线性关系&#xff0c;而深度学习又存在计算资源消耗大、解释性差的问题。最小二乘算法(Least Squares, LS)作为一种经典的数学优化方法&#xff0…

作者头像 李华
网站建设 2026/11/3 16:16:22

模型又答错了?别急,六个步骤定位AI推理故障

先跟各位说个我自己的真实经历。一个图像分类项目&#xff0c;验证集上表现良好&#xff0c;上线之后却频繁输出错误类别。团队第一反应全是“模型坏了”&#xff0c;有人开始调学习率&#xff0c;有人准备换backbone&#xff0c;还有人已经在收集数据准备重训。折腾了整整两周…

作者头像 李华