☰
分体式LLM推理资源分配:基于排队论的SLO感知调度实践
2026/10/7 5:41:48 网站建设 项目流程

1. 从一次线上事故说起:为什么分体式推理的资源分配这么难

去年下半年,我参与维护的一套大模型推理服务经历了一次挺典型的翻车。白天压测一切正常,P99延迟稳定在2秒出头,结果晚上业务方搞了个活动,请求量翻了不到三倍,整个推理集群直接雪崩——预填充节点(Prefill)的GPU利用率冲到100%,解码节点(Decode)却闲得发慌,队列里的请求越堆越多,超时率从0.5%飙到30%以上。事后复盘,问题根本不在模型本身,也不在硬件不够,而是资源分配策略是静态的:预填充和解码的算力配比写死在配置里,流量结构一变,两边就严重失衡。

这件事让我开始认真研究分体式(Disaggregated)LLM推理架构下的资源调度问题。所谓分体式,就是把传统单体推理里的两个阶段——Prefill(预填充,处理完整输入prompt,计算密集)和Decode(解码,逐token生成,访存密集)——拆到不同的计算资源池上独立部署。这么拆的好处很明显:两个阶段的硬件特性、并行策略、批处理逻辑完全不同,混在一起跑会互相拖累。但拆开之后,新的问题就来了:两边的资源该按什么比例配?流量波动时怎么动态调整?怎么保证端到端的SLO(Service Level Objective,服务等级目标)?

SARA这篇工作(SLO-Aware Resource Allocation)给出的答案是用排队论来建模,把预填充和解码两个阶段分别抽象成排队系统,通过解析模型推导出满足SLO所需的最小资源配置,再结合运行时反馈做动态调整。我读完之后的感受是:这套思路不算特别复杂,但胜在把工程问题数学化了,而且落地路径清晰。下面我就按自己的理解,把这篇论文的核心思路、数学建模过程、实操落地要点,以及我在实际环境中踩过的坑,完整地拆一遍。

2. 分体式推理的架构本质与资源困境

2.1 预填充与解码:两种截然不同的计算模式

要理解SARA为什么这么设计,得先把分体式推理的底层逻辑讲清楚。传统单体推理里,一个请求进来,先做Prefill:把整个输入序列(比如2000个token)一次性送进模型,计算所有位置的注意力,生成KV Cache。这一步是计算密集型的,GPU的算力单元(Tensor Core)基本跑满,显存带宽反而没那么吃紧。然后是Decode:基于KV Cache,每次只生成一个token,循环往复直到遇到终止符。这一步是访存密集型的,每生成一个token都要把整个KV Cache读一遍,算力利用率很低,瓶颈在显存带宽。

把这两个阶段放在同一张卡上跑,会出现什么情况?Prefill的长请求会把Decode的短请求堵在后面,因为Prefill占用算力时间长,Decode虽然单步快但架不住要循环很多次。更麻烦的是,两者的最优批处理策略是冲突的:Prefill适合大batch一次性算完,Decode适合小batch高频迭代。混在一起,调度器左右为难。

分体式架构就是把它们物理隔离:一组GPU专门做Prefill,另一组专门做Decode,中间通过高速网络传输KV Cache。这样一来,每边都可以针对自己的特性做优化——Prefill节点可以用大batch、高并行度;Decode节点可以用连续批处理(Continuous Batching)、PagedAttention等技巧。但代价是:KV Cache的传输成了新的瓶颈,而且两边的资源配比变成了一个需要动态求解的优化问题。

2.2 静态配比的三个致命缺陷

我见过不少团队一开始都是拍脑袋定配比,比如Prefill和Decode按1:2配,或者按1:1配。这种静态策略在流量模式稳定时勉强能用,但一旦遇到以下三种情况就会出问题。

第一种是请求长度分布变化。如果突然来了一批超长prompt的请求(比如RAG场景下检索回来一堆文档),Prefill的计算量会暴增,而Decode的负载不变。这时候Prefill节点成为瓶颈,Decode节点闲置。反过来,如果是一批短prompt但要求生成很长内容的请求(比如写代码、写文章),Decode的负载会远大于Prefill。

第二种是到达率波动。LLM推理服务的请求到达往往不是泊松过程,而是有明显的突发性。活动开始的一瞬间,请求量可能翻好几倍,如果资源不能快速弹性伸缩,队列就会迅速积压。

第三种是SLO的多样性。不同业务对延迟的容忍度不一样:对话类应用可能要求TTFT(Time To First Token)在500ms以内,而离线批量生成任务可能允许几秒的延迟。静态配比无法同时满足多档SLO。

SARA的核心贡献,就是把这三种情况统一到一个排队论框架里,用数学工具求出在给定SLO约束下的最优资源配比。

2.3 排队论为什么适合这个问题

你可能会问:为什么非要用排队论?直接用强化学习或者启发式规则不行吗?我的理解是,排队论的优势在于可解释性和可求解性。强化学习虽然灵活,但训练成本高、收敛慢,而且在生产环境里很难保证稳定性——你不敢让一个RL agent直接控制几千张GPU的分配。启发式规则(比如队列长度超过阈值就扩容)则缺乏理论保证,参数调起来全靠经验。

排队论把系统抽象成“到达过程+服务过程+队列规则”,可以用M/M/c、M/G/1等模型求出平均等待时间、队列长度、超时概率等指标。对于分体式推理,Prefill和Decode可以分别建模成两个串联的排队系统,端到端延迟就是两段延迟之和加上KV Cache传输时间。有了这个解析模型,就可以反过来求:在给定到达率λ和SLO约束下,需要多少台Prefill节点和多少台Decode节点。

当然,实际系统比教科书里的排队模型复杂得多——服务时间不是指数分布,批处理会改变服务率,KV Cache传输有带宽限制。SARA的做法是做合理的近似,然后用运行时数据校准模型参数。这种“解析模型+在线校准”的思路,我觉得是这篇论文最值得借鉴的地方。

3. SARA的核心机制:排队论建模与SLO约束求解

3.1 系统抽象:两级串联排队网络

SARA把整个推理服务抽象成一个两级串联的排队网络。第一级是Prefill队列,请求到达后先在这里排队,等待被分配到某个Prefill实例上执行。执行完成后,请求进入第二级Decode队列,同时KV Cache被传输到Decode节点。Decode阶段会持续多个时间步(每个token一步),直到生成结束。

这里有个关键细节:Prefill的服务时间取决于输入序列长度,Decode的服务时间取决于输出序列长度。两者都是随机变量,不能简单用固定值。SARA假设输入长度和输出长度分别服从某种分布(论文里用的是经验分布),然后通过排队论公式计算各阶段的平均延迟。

端到端延迟可以写成:

E2E_Latency = W_prefill + S_prefill + T_transfer + W_decode + S_decode

其中W是排队等待时间,S是服务时间,T_transfer是KV Cache传输时间。SLO约束通常是对E2E_Latency的某个百分位数(比如P99)提出上限。

3.2 服务时间建模:从token长度到GPU时间

排队论模型要能用,前提是服务时间估计得准。SARA在这块做了比较细的建模。对于Prefill阶段,服务时间大致与输入序列长度的平方成正比(因为注意力机制是O(n²)),但实际中还受batch size、并行策略、GPU型号影响。论文里用了一个线性回归模型来拟合:给定序列长度和batch size,预测Prefill耗时。

Decode阶段的服务时间则与输出序列长度线性相关,但每步的耗时还取决于当前batch里有多少个活跃序列(因为要读所有序列的KV Cache)。SARA用了一个迭代公式来估计Decode的总时间:每生成一个token,更新一次活跃序列集合,累加每步耗时。

我在实际环境里验证过这个思路,发现拟合误差主要来自两个方面:一是GPU的DVFS(动态调频)导致同样计算量耗时波动;二是KV Cache传输的延迟在跨节点时不稳定。SARA的处理方式是用滑动窗口在线更新模型参数,这个后面会细说。

3.3 SLO约束下的资源求解:一个带约束的优化问题

有了延迟模型,资源分配问题就可以形式化为:在满足SLO约束的前提下,最小化总资源成本。设Prefill节点数为N_p,Decode节点数为N_d,总成本C = c_p * N_p + c_d * N_d(c_p和c_d是单位成本)。约束是P99延迟 ≤ SLO_target。

SARA的求解思路是:先固定N_p,求出满足Prefill阶段SLO所需的最小N_p;再固定N_d,求出满足Decode阶段SLO所需的最小N_d;然后通过迭代调整,找到全局最优。由于两个阶段的延迟是耦合的(Prefill的输出速率影响Decode的到达率),所以不能完全解耦,需要用不动点迭代。

论文里给了一个具体的求解算法,大致步骤如下:

  1. 初始化N_p和N_d为保守估计值。
  2. 根据当前N_p计算Prefill阶段的输出速率(即Decode的到达率)。
  3. 根据到达率和SLO约束,计算所需的N_d。
  4. 根据N_d反推Decode阶段能承受的最大到达率,再调整N_p。
  5. 重复2-4直到收敛。

这个算法在数学上收敛性有保证(因为是单调映射),实际跑起来通常几轮就收敛了。

3.4 在线校准:让模型跟上真实系统

解析模型再漂亮,如果参数不准也是白搭。SARA设计了一个在线校准模块,每隔一段时间(比如30秒)采集一次运行时指标:实际到达率、实际服务时间、实际队列长度、实际超时率。然后用这些数据去修正模型参数。

具体来说,服务时间模型的系数用递归最小二乘法(RLS)更新,到达率用指数移动平均(EMA)平滑。如果发现模型预测的延迟和实际延迟偏差超过阈值(比如20%),就触发一次重新求解。

我在自己的环境里实现过类似的校准逻辑,有个经验:校准频率不能太高,否则会被噪声带偏;也不能太低,否则跟不上流量变化。30秒是个比较平衡的值,但具体要看你的流量波动周期。如果业务有明显的分钟级突发,可以缩短到10秒;如果是小时级变化,60秒也够用。

4. 实操落地:从论文到生产环境的完整路径

4.1 环境准备与依赖组件

要把SARA的思路落地,你需要一套支持分体式推理的基础设施。我假设你已经有了以下组件:

  • 推理引擎:支持Prefill和Decode分离部署的框架,比如vLLM的disaggregated模式,或者TensorRT-LLM的分离式部署。
  • KV Cache传输层:通常是RDMA网络或者高速以太网,需要支持零拷贝传输。
  • 监控系统:Prometheus + Grafana,采集GPU利用率、队列长度、延迟分位数等指标。
  • 调度器:Kubernetes或者自研调度器,支持动态调整Pod数量。

如果这些还没有,建议先把基础的分体式部署跑通,再考虑上SARA的调度逻辑。我见过一些团队一上来就想搞智能调度,结果基础设施不稳定,调度的效果根本测不出来。

4.2 关键参数采集与预处理

SARA模型需要以下几类输入参数:

参数类别具体指标采集方式更新频率
到达过程请求到达率λ入口网关计数每秒
输入分布prompt长度分布请求日志解析每30秒
输出分布生成长度分布响应日志解析每30秒
Prefill服务时间不同长度/batch下的耗时GPU计时器每30秒
Decode服务时间每token耗时GPU计时器每30秒
传输延迟KV Cache传输耗时网络监控每10秒
SLO指标P99 TTFT、P99 TPOT延迟直方图每秒

采集的时候要注意:延迟指标要用直方图而不是平均值,因为SLO通常看的是尾部延迟。Prometheus的histogram类型可以满足这个需求。

4.3 求解器的实现与调参

我用Python实现过SARA的求解器,核心逻辑大概200行代码。这里给一个简化版的伪代码:

def solve_resources(lambda_rate, input_dist, output_dist, slo_target): # 初始化 N_p = initial_guess_prefill(lambda_rate) N_d = initial_guess_decode(lambda_rate) for iteration in range(max_iter): # 计算Prefill阶段延迟 prefill_delay = queueing_model_prefill(N_p, lambda_rate, input_dist) # Prefill输出速率即Decode到达率 decode_arrival = lambda_rate # 假设无丢弃 # 计算Decode阶段延迟 decode_delay = queueing_model_decode(N_d, decode_arrival, output_dist) # 端到端延迟 e2e_delay = prefill_delay + transfer_delay + decode_delay # 检查SLO if e2e_delay <= slo_target: # 尝试减少资源 N_p, N_d = try_reduce(N_p, N_d) else: # 增加资源 N_p, N_d = try_increase(N_p, N_d) if converged(N_p, N_d): break return N_p, N_d

调参方面,有几个经验值可以参考:迭代次数上限设20次足够,通常5-8次就收敛;资源调整的步长初始可以设大一点(比如10%),接近最优时缩小到2%;如果连续几轮在某个值附近震荡,就取平均值作为最终结果。

4.4 动态调整的安全机制

生产环境里,任何自动调整都需要有安全兜底。我总结了三条必须加的机制:

第一条是调整速率限制。每次调整的资源量不能超过当前总量的20%,否则可能引起震荡。比如当前有100个Prefill节点,一次最多加到120或减到80。

第二条是冷却期。每次调整后,至少等待一个完整的监控周期(比如60秒)才能再次调整。这是为了防止频繁调整导致系统不稳定。

第三条是人工覆盖开关。当系统检测到异常(比如延迟突然飙升但队列不长),自动切换到保守策略,并告警通知人工介入。我遇到过一种情况:模型预测需要扩容,但实际上是因为某个节点挂了导致延迟升高,这时候扩容反而浪费资源。

5. 常见问题与排查技巧实录

5.1 模型预测偏差大怎么办

这是最常见的问题。你按照SARA算出来的资源配置部署下去,发现实际延迟和预测差很远。排查思路如下:

首先检查服务时间模型是否准确。拿一批已知长度的请求,单独测Prefill和Decode的耗时,和模型预测对比。如果偏差超过30%,说明模型需要重新拟合。常见原因是GPU型号变了、batch size策略变了、或者推理引擎版本升级了。

其次检查到达率统计是否有误。有时候网关统计的到达率包含了被限流的请求,实际进入系统的没那么多。或者统计窗口太长,把突发流量平滑掉了。

最后检查KV Cache传输延迟。这个容易被忽略。如果Prefill和Decode跨机房部署,传输延迟可能达到几十毫秒,在端到端延迟里占比很高。SARA的模型里这一项是单独建模的,但如果你的监控没覆盖,就会导致预测偏乐观。

5.2 资源震荡怎么破

震荡的表现是:Prefill节点数一会儿加一会儿减,Decode节点数也跟着反向调整,系统始终稳定不下来。根本原因是两个阶段的调整策略耦合太紧。

我的解法是引入非对称调整:Prefill的调整周期设为Decode的两倍。因为Prefill的服务时间通常更长,对流量变化的响应本来就慢一些。让Decode先调整,等它稳定了再调Prefill,可以打破震荡循环。

另一个技巧是加死区。如果计算出的最优资源和当前资源的差异小于5%,就不调整。这能过滤掉很多微小的波动。

5.3 SLO达标但成本居高不下

有时候SLO是满足了,但资源用量比预期高很多。这时候要检查SLO目标是否设得太紧。比如P99 TTFT要求200ms,但实际业务能容忍500ms,那你就白白多配了一倍资源。建议和业务方对齐SLO,区分“必须满足”和“尽力而为”两档。

还有一个可能是排队模型过于保守。M/M/c模型假设服务时间服从指数分布,但实际的服务时间分布可能更集中(方差更小),这意味着实际排队延迟比模型预测的低。可以改用M/G/c近似或者用实测数据校准。

5.4 突发流量下的快速扩容

SARA的求解器需要几十秒到几分钟才能算出新的资源配置,但突发流量可能几秒内就来了。这时候需要快速响应通道:当队列长度超过阈值时,直接触发预设的扩容策略(比如翻倍),不等求解器。等流量稳定后,再让求解器接管,做精细化调整。

这个快速通道的阈值怎么定?我的经验是:当Prefill队列长度超过当前节点数乘以平均服务时间的2倍时,触发扩容。Decode队列类似,但阈值可以设得更宽松,因为Decode的队列消化速度更快。

5.5 常见问题速查表

问题现象可能原因排查方法解决措施
预测延迟远低于实际服务时间模型过时对比实测与预测重新拟合模型
预测延迟远高于实际到达率统计偏高检查网关限流修正到达率
资源频繁震荡调整步长过大查看调整日志加死区、降步长
SLO达标但成本高SLO过紧或模型保守与业务对齐SLO放宽SLO或换模型
突发时扩容不及时求解器太慢测量求解耗时加快速通道
Decode节点闲置Prefill成为瓶颈查看两阶段利用率增加Prefill节点
Prefill节点闲置Decode成为瓶颈查看两阶段利用率增加Decode节点

6. 我对这套方案的理解与扩展思考

SARA这篇论文最打动我的地方,是它没有追求花哨的算法,而是用经典的排队论解决了一个实际的工程问题。现在很多系统调度的工作动不动就上强化学习、上神经网络,但实际落地时往往因为不稳定、不可解释而被放弃。排队论虽然老,但它的假设清晰、求解可验证、结果可解释,在生产环境里反而更容易被接受。

当然,SARA也不是万能的。它的模型假设了请求之间相互独立,但实际上LLM推理的请求可能有依赖关系(比如多轮对话)。它的服务时间模型是离线拟合的,对于新出现的请求模式可能不准。它的求解器是集中式的,在超大规模集群上可能有性能瓶颈。这些都是可以继续改进的方向。

我在自己的环境里做了一些扩展,比如把SLO约束从单目标改成多目标(同时约束TTFT和TPOT),把资源类型从单一GPU扩展到异构GPU(比如A100和H100混部)。这些扩展的数学推导更复杂,但核心思路还是SARA那一套:建模、求解、校准。

最后分享一个我在实操中总结的小技巧:把SARA的求解结果和实际调整动作都记录下来,定期做回溯分析。你会发现有些调整是有效的,有些是无效的,有些甚至是反效果的。把这些案例整理成规则,可以逐步减少对求解器的依赖,形成一套更稳定的调度策略。毕竟,再好的数学模型,也要靠实际数据来验证和打磨。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询