ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

分布式推理如何支撑大模型服务:llm-d六项关键技术详解

分布式推理如何支撑大模型服务:llm-d六项关键技术详解

做在线大模型服务的朋友,最近应该都被同一个问题卡过脖子:模型越来越大,单卡根本放不下,更别说要支撑真实业务流量。我去年在某公司搭建一套内部智能助手时,就踩过类似的坑——把一个开源大模型部署到一台8卡机器上,结果GPU利用率勉强到三成,每秒只能处理几十个请求,延迟还忽高忽低,完全扛不住日常调用。后来我把目光转向分布式推理方案,才慢慢摸清了里面的门道。今天想结合llm-d这个项目,聊聊分布式推理到底是怎么支撑起大模型服务的,重点拆解它背后起支撑作用的六项关键技术。这篇文章适合正在做大模型服务部署、或者准备上分布式方案的工程师参考,不管你是刚入门还是已经踩了不少坑,应该都能找到有用的东西。

1. 为什么单机跑不动大模型服务:先理解问题再选方案

很多人的第一反应是"既然单卡放不下,那就多插几张卡不就行了"。话是没错,但没那么简单。分布式推理不是把模型拆开扔到多张GPU上就结束了,它要解决的是三个层面的矛盾:显存放不下、算力用不满、延迟控不住。这三件事互相纠缠,只解决其中一个,另外两个照样会拖垮服务。

1.1 显存天花板:模型权重只是其中一个开销

先算一笔账。一个700亿参数的模型,用FP16精度存储权重,大约需要140GB显存;就算用INT8量化,也还要70GB左右。而当前主流的单块商用加速卡,显存普遍在24GB到80GB之间,单卡放不下几乎是一定的。

但很多人容易忽略的是,权重只是开销的一部分。推理过程中还有三大块隐性显存消耗:一是激活值(activation),就是每一层前向计算产生的中间张量;二是KV Cache,也就是注意力机制里缓存的Key和Value向量;三是算子计算时需要的临时缓冲区,比如做Softmax、LayerNorm时中间结果占用的空间。这三块加起来,在大并发场景下往往比权重本身还要多一倍到两倍。

我见过不少人测试阶段单卡推理没问题,一上生产就爆显存,原因就是测试时batch size设成1,根本没把KV Cache和激活值算进去。所以做分布式推理,第一步不是想着怎么切模型,而是先搞清楚显存到底被谁占了。

1.2 算力与延迟的双重矛盾

大模型推理有一个非常特殊的性质:它是自回归解码,也就是一个token一个token往外蹦。单个请求的时延核心是单趟前向计算时间,这决定了首token返回的速度;但如果多个请求同时进来,GPU的矩阵乘法又是典型的吞吐友好型计算,塞进来的请求越多,单位时间处理的token总数越高。

问题就出在这里:在单机上,想要高吞吐,就得把请求攒成大batch一起算;但这样先来的请求得排队等后面的人,时延就压不住。想要低时延,就得让请求尽可能快地进入计算,但batch小了,GPU又吃不饱,吞吐跟着掉。这两头你只能顾一头,单机方案怎么也平衡不好。

分布式推理的一个核心价值,就是把"吞吐优先"和"时延敏感"这两类需求拆开,通过多节点的协同调度来做动态平衡。计算压力分摊到多个设备上之后,单个请求的排队时间大幅缩短,同时整体GPU利用率还能维持在高位。

1.3 分布式训练和分布式推理是两码事

不少人把分布式推理想成"分布式训练的低配版",这是个很大的误区。训练阶段是数据并行切数据集,每个GPU算完梯度再做同步更新,它对时延不敏感,哪怕多等几轮也没关系。但推理服务是面向线上的,它要保证的是每个请求的端到端时延和系统整体的吞吐量。

训练可以隔一段时间保存一次checkpoint,节点挂了重启、恢复就行。推理服务挂一个节点,用户立刻就能感知到,请求直接失败。训练的数据分布基本固定,但推理的流量是动态的,白天峰值晚上低谷,甚至某个瞬间突然涌进来一批热点请求。这些差异意味着分布式推理在设计上必须更关注调度灵活性、容错能力和动态扩缩容,这些都不是直接复用训练框架就能搞定的。

2. llm-d 六项关键技术全景拆解:给自己列一张技术地图

说到llm-d这个名字,我当时看到某个工业界团队对它的技术解析时,第一反应是"这不就是传统分布式系统那套思路套在了LLM推理上吗"。但仔细看完,发现它的巧妙之处在于:针对大模型推理的特性,把调度、存储、通信、并行这些通用技术做了非常务实的取舍。下面这六项就是我梳理出来的核心,每一项解决的具体痛点都不太一样。

2.1 第一项:连续批处理与动态请求合并

传统的大模型推理框架,大多采用静态批处理:攒够一批请求,一起做前向计算,等到这一批所有请求都生成完了,才把GPU空出来给下一批。这个方式在并发量小的时候问题不大,但一旦请求长短不一,浪费就非常明显。

举个生活化的例子:你去银行排队,窗口每次只服务一组人,必须等这一组全部办完才放下一组进来。结果前面一个办贷款花了四十分钟,后面只想取个零钱的也干等着,窗口的资源其实一直没闲着,但后面的人就是进不来。

连续批处理(continuous batching)的做法是换一种流程:按"token级"去调度,每个请求在生成完某一个token之后,如果它的句子结束了,就立刻把位置让给新进来的请求。也就是说,GPU始终在处理所有活跃请求中最新那一个token的生成,而不是傻等某个慢请求结束。这个改动对吞吐量的提升非常明显,尤其是长短请求混合的真实业务场景。

llm-d在这块做得比较细的地方在于,它不只是简单地把请求塞进一个池子里,而是实现了请求级别的动态合并:根据当前各请求的生成长度、剩余预估轮数、优先级权重来计算哪些请求应该放进当前这个batch,哪些需要暂时让位。这一步本质上是一个优化问题,目标是让GPU在当前条件下跑得最满,同时不让任何请求的等待时间超限。

2.2 第二项:调度策略:优先级队列与抢占机制

在线服务是有SLO(服务等级目标)的。比如你承诺首token延迟(TTFT)不超过200毫秒,每分钟token生成数(TPOT)不低于某个值;如果不能保障这些指标,上层业务就会出问题。不同请求的优先级还不一样:用户在聊天框里等着的请求,和后台离线分析数据的请求,体验要求完全不同。

llm-d的做法是把请求分成不同优先级队列,调度器动态分配GPU资源。这里面的难点在于:如果一味让高优先级请求插队,低优先级的批量任务就可能被无限期饿死。所以它的调度器不是简单的前后顺序,而是带权重的动态调整——低优先级任务在高优先级空闲时也能抢到算力,只是在高峰期适当让路。

更进阶一点的是抢占机制。有些请求已经生成了一半,KV Cache占着显存,如果这时来了一个超高优先级的任务,直接把低优先级请求杀掉、释放显存,代价太大——因为重新跑一遍意味着前面生成的token全部白费。llm-d的处理方式是"换出"而不是"杀掉":把低优先级请求的KV Cache从显存搬到CPU内存,等资源空下来再挪回来继续生成。这个机制的做法,和操作系统里进程的换页调度非常像,只不过被换进换出的对象变成了神经网络的中间状态。

2.3 第三项:KV Cache 的分布式管理与换出策略

KV Cache是大模型推理里最容易被低估的显存消耗来源。它的大小可以这样估算:每个token需要保存 2 × 层数 × 注意力头数 × 每头维度 个值,假设你有几十层、每层几十个头,每个头的维度还有几十到上百,那每多一个token,就要增加几MB甚至十几MB的显存占用。上下文越长、并发越高,KV Cache膨胀得越凶,甚至可以占到大模型总显存的一半以上。

llm-d对KV Cache的处理,核心思路是分片和换出。分片是指把KV Cache的存储和计算任务分散到多个节点上,每个节点只维护其中一部分token的Key和Value;解码时,需要哪个位置的KV块,就通过网络从对应节点取过来。这样做的优点是显存压力不会集中在某一台机器上。

换出策略则更关键:GPU显存不够时,把哪些KV块挪走?挪到哪?llm-d采用了一种基于"近期最少使用"思路的近似策略——但它不只看访问频率,还结合了请求的剩余长度预估。如果一个请求预计马上要结束,那它的KV Cache优先级就会降低,可以提前换出;反过来,一个长对话请求还在高频生成中,它的KV块要尽量留在显存里。

这里有个细节需要注意:KV Cache的换出不要落到磁盘上。磁盘IO延迟是毫秒到十毫秒级别,而一次token生成可能只要几十微秒,一旦换出到磁盘,这个请求的延迟直接爆表。好点的方案是显存和CPU内存之间的二级缓存,再辅以内存与磁盘的异步整合。实际部署中,宁可少配点GPU并发,也要给CPU内存留足空间,因为换出策略的收益高度依赖这块缓冲区。

2.4 第四项:高吞吐通信与解耦的数据路径

多机多卡协同,必然涉及中间结果的传输。张量并行(Tensor Parallelism)时,每一层的前向计算都需要跨卡同步一次中间结果;KV Cache分片后,每次解码也都要拉取远端数据块。通信效率上不去,GPU再快也白搭。

llm-d在通信层面的一个亮点是"数据路径解耦"。通俗地说,就是把搬运数据这件事从GPU主计算流程里剥出来,让通信过程与计算过程重叠。它通过额外的通信线程或专用硬件队列来管理数据传输,GPU的核心计算单元不用干等着数据到位,而是利用异步机制一边算一边收。

实际配置时,机内高速互联和跨机RDMA网络的选择直接影响效果。机内互联适合张量并行这种高频小数据量的同步,跨机RDMA则适合KV Cache分片这种大数据量的按需拉取。我推荐的做法是:单节点内用尽可能高的互联带宽来做张量并行,跨节点之间的流量尽量压到最低,宁可多开几个数据并行副本,也不要让跨节点通信成为瓶颈。

2.5 第五项:张量并行与流水线并行的取舍

分布式推理有两大类切分模型的方式。张量并行把每一层权重横切成多份,放在多张卡上,大家一起算同一层;流水线并行则按层切分,第一张卡算完前几层,把中间结果传给下一张卡继续算后面的层。

从延迟角度看,张量并行每层只有一次通信,但通信量很大——隐层维度有几千甚至上万,每个数值都要跨卡同步;流水线并行通信量小,但GPU利用率受限于流水线气泡,也就是某些卡在等待上游结果时空闲的时间。在线服务对延迟敏感,所以llm-d优先推荐张量并行,而且尽量把张量并行的范围控制在单节点内,依靠高速互联来消化通信开销。

如果单节点放不下怎么办?一个常见的配置方案是"单节点内TP + 跨节点纯数据并行"。也就是每个节点内的多张卡联合跑一个模型副本,多个节点各跑一份副本,请求按负载均摊到不同副本上。这种做法牺牲了一些显存效率,但极大降低了跨节点通信量,整体吞吐反而更好。

我顺手整理过两种并行方式的对比:

维度张量并行流水线并行
权重切分方式按层内维度横切按层纵向分段
通信频率每层一次每段边界一次
单次通信量大小
跨机友好度较差,依赖高速互联较好,可跨机部署
气泡开销无有不小比例
在线推理推荐度强烈推荐仅作为补充手段

2.6 第六项:弹性扩缩容与故障隔离

服务上线之后,最怕两件事:流量突增撑不住,节点故障拖垮全部。弹性扩缩容这件事,训练场景里往往不太敏感,因为训练任务跑完就结束;但推理服务是常驻的,它需要根据实时流量动态调整实例数量。llm-d在架构设计上把控制面和数据面分离,有一套独立的控制器负责实例的注册、健康检查、流量再分配。扩缩容时,控制器先把新实例的模型加载完成,再逐步把流量导过去;缩容时则先摘流量、等存量请求处理完、再回收资源。

故障隔离这块,llm-d的机制是"请求级别复制"。常规做法是每个请求只发给一个实例处理,但某些高价值业务,它会同时发给两个副本,哪个先返回就用哪个,另一个直接废弃。代价是算力翻倍,但换来了极高的可用性。绝大多数场景不需要做到这一步,它更常见的是"节点故障后的快速重放":控制器通过心跳机制每几秒检查一次各节点状态,一旦发现某节点超时,就把分配给它的未完成请求重新排到其他节点上重跑。

这里需要注意的是,大模型推理不是普通的无状态服务,请求重放会导致KV Cache重建,代价不小。所以故障检测不能太激进——频繁误判会把正常节点上的请求全打散重跑,反而拖垮整体。实操中我会把超时阈值放宽到正常处理时长的3到5倍,宁可让少数请求等待,也不能因为误判引发雪崩。

3. 实操过程与核心环节实现:从设计到压测一次走通

理论说再多,落地才是关键。这一节我会以一套典型的分布式推理服务为例,讲讲硬件选型、框架配置、压测流程这些实操环节怎么做。这套方案不是唯一解,但至少能帮你少走弯路。

3.1 硬件与网络选型:先定拓扑再定配置

我的习惯是先画拓扑图,再定具体参数。推理集群的核心资源是三个:GPU算力、显存容量、网络带宽。这三者的平衡点取决于你的业务负载特征——是并发高但每个请求短,还是并发不高但上下文很长。

以我实际搭过的服务为例,业务以中型开源模型为基础,单机8卡放不下,所以我选了2机8卡。机内走高速互联用于张量并行,跨机走低速但吞吐稳定的以太网络承载数据并行。这个拓扑有个好处:把跨机通信量压到最低,网络越简单,出问题的概率越小。

另一个容易被忽视的是CPU内存。前面提到KV Cache换出策略依赖CPU内存做二级缓存,所以我给每台机器配了比常规推荐更大的内存容量。这个钱不能省,它是KV Cache换出策略能否生效的前提。

如果你预算充裕,建议直接上更高带宽的跨机网络,比如RDMA或RoCE。这时候可以更大胆地做跨节点TP,模型切分的灵活性高很多。但网络设备成本也高,小团队要先掂量业务量级,不要为了技术尝鲜盲目上全套高速网络。

3.2 框架配置思路:关键参数怎么定

主流的开源推理框架现在基本都支持连续批处理、张量并行、KV Cache容量设置这些能力。以我用的配置为例,核心参数和背后逻辑如下:

  • 张量并行度:设为单节点GPU数。这样可以充分利用机内高速互联,避免跨节点通信。
  • 数据并行副本数:设为节点数。每个节点独立处理一批请求,天然实现负载均衡。
  • 最大批处理token数:这个要和显存动态协调。它决定了连续批处理时GPU能吃多满,设太小GPU空转,设太大则容易显存溢出。
  • KV Cache容量上限:建议显存预留50%~60%给KV Cache。因为并发生成场景下,KV Cache的增速比激活值快得多,空间不足是生产环境最常见的故障源。
  • 请求级别超时时间:建议设为正常处理时长的3到5倍,避免误判触发不必要的重放。

配置好后,先跑一个冒烟测试,确认模型能正常回答几轮对话,再进入正式的压测环节。冒烟测试还有一个作用:把常见配置参数固化到版本管理的配置模板里,后续每次变更都能追溯。

3.3 测试流程与关键指标:压测不是跑一遍就完事

性能测试是上线前必须做的事情,但很多人压测方式不对。不是随便发一堆请求看总耗时就行,要分层看指标。我一般按四个维度记录:

  • TTFT(首token延迟):反映了用户体验的第一印象,也是最敏感的指标。
  • TPOT(单token生成时间):反映稳定输出速度,这个值决定了对话的流畅度。
  • 端到端延迟:完整生成一句话的耗时。
  • P99尾延迟:相比平均值,尾延迟更能暴露系统稳定性问题。

压测工具我会选择能模拟真实流量特征的方式:混合长短请求,让一部分请求快速结束、一部分保持长对话,模拟生产环境中的真实分布。每个并发级别跑10分钟以上,看指标是否稳定。

跑完压测,还有一个容易被忽略的步骤:记录不同并发下的GPU利用率曲线和显存水位。如果并发上去了但利用率没跟着上去,说明调度或通信有瓶颈;如果显存占用到瓶颈但利用率还不高,大概率是KV Cache或batch设置不合理。这些数据才是后续调优的依据。

4. 常见问题与排查技巧实录:把这些坑提前帮你踩掉

分布式推理的麻烦往往不在原理上,而在系统性的疑难杂症上。下面我按问题类型整理了一份速查表,再单独讲三个我自己踩过、解决起来特别费劲的坑。

4.1 问题速查表

现象可能原因排查手段解决方案
GPU利用率持续偏低连续批处理未生效或batch太小查看batch负载和调度器日志调大最大批处理token数,检查请求到达率
显存OOMKV Cache预留太大或并发过高查看显存监控、KV Cache占用曲线调低KV Cache上限,减少并发,启用换出
首token延迟抖动大调度优先级设置不合理查看请求队列等待时间分布优化优先级队列权重、引入抢占机制
单节点卡顿、其他节点闲置负载均衡策略失效查看各节点请求分配情况检查控制器的健康检查和流量分配逻辑
跨机吞吐上不去通信路径瓶颈或过度TP用通信监控工具观察网络占用重新规划TP/DP比例,减少跨机TP流量
长对话中途变慢KV Cache频繁换出查看换出统计和CPU内存占用加大CPU缓冲区或限制上下文长度

4.2 我踩过的三个坑

第一个坑是KV Cache预留空间不足。最初我把KV Cache上限设得比较保守,想着反正有换出机制兜底,结果线上长文本请求一多,频繁触发换出,每个请求的延迟从原来的几百毫秒飙升到好几秒。后来我把显存给KV Cache的比例调大,同时把CPU内存缓冲加大一倍,问题才缓解。这个教训是:换出机制是兜底方案,不是常态路径,不要指望频繁用它来掩盖显存规划失误。

第二个坑是张量并行度设太高,跨机通信直接过载。某次我把TP从4调到8,结果模型没变快,反而整体吞吐掉了三成。排查发现,TP每层通信量翻倍,跨机网络成了瓶颈。后来改成"单机TP + 跨机DP"的结构,吞吐立刻回来了。做并行策略调整时,一定要同步观察网络吞吐,不能被单卡计算速度的提升蒙蔽。

第三个坑是压测只看平均延迟。某次压测平均延迟漂亮得很,上线后用户却反馈"有时快有时慢"。后来一查P99延迟,比平均高出一个数量级。原因是压测时用的请求长度过于均匀,系统的临界资源问题没有被放大。从那之后,我的压测脚本里一定会混入长尾请求,并且每轮压测都同时记录P50、P95、P99三档数据。

4.3 监控与调优的循环方法

排障是临时的,监控才是长期的。我建议至少建立三块监控面板:一是GPU资源面板,看利用率、显存水位、温度;二是服务性能面板,看TTFT、TPOT、P99、吞吐量的实时曲线;三是调度运行面板,看请求排队时长、KV Cache命中率、换出次数、批处理大小分布。

调优的时候要有纪律:一次只调一个变量。比如想验证KV Cache上限的影响,就只改这一个参数,压测跑一轮;想验证调度策略,就保持其他配置不动,一轮只换一个策略。把每轮结果记录成表格,长期积累下来,你就能建立一套自己服务的"参数-性能"经验库。这个库比任何调优文档都管用。

5. 一些个人的实操体会

分布式推理做了这么久,我最大的体会是:它不是把模型拆开扔到多张卡上那么简单。六个关键技术,每个单拎出来都是分布式系统领域的经典问题——调度、存储、通信、容错——只是这里解决的是"大模型推理"这个具体场景下的版本。llm-d这类项目最有价值的地方,是帮你把复杂问题分成了清晰的模块,每一个模块都有明确的优化目标,排查问题时你可以按图索骥,而不是在黑盒里瞎猜。

对刚上手的团队,我的建议是先做减法:并发不高、模型不大的话,单机多卡加张量并行就够用。不要一上来就搞复杂的多机分布式,更不要迷信全功能框架。等业务量起来了,再去逐步引入连续批处理、KV Cache换出、弹性和故障隔离这些高级能力。

最后分享一个小习惯:把压测脚本和性能基准表一直留着。每次变更配置,就完整跑一轮压测,把结果记进表格。时间久了,你对自己系统的"脾气"会非常清楚,再遇到性能波动,不用查日志都能猜个八九不离十。分布式推理调试,说到底拼的是对系统状态的熟悉程度,而这一切都要靠日常一点一滴的记录和积累。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进