分体式LLM推理资源分配:基于排队论与SLO约束的动态优化实践 1. 分体式推理到底在解决什么问题第一次看到 SARA 这个工作是在翻近期系统会议论文列表的时候。标题里的几个词——SLO、排队论、分体式推理、资源分配——单拎出来都不新鲜但把它们捏在一起指向的是一个非常具体的工程痛点当 LLM 推理被拆成多个阶段、跑在多组异构资源上时怎么按延迟目标动态分配算力而不是靠拍脑袋或者静态配比。先说清楚“分体式 LLM 推理”是什么。传统做法是把整个模型塞进一张卡或者一组卡里请求进来从头跑到尾。但大模型推理其实分两个性质完全不同的阶段Prefill预填充和Decode解码。Prefill 要把整段 prompt 一次性过一遍是计算密集型GPU 利用率高、吞吐大Decode 是逐 token 生成每一步只算一个 token是访存密集型算力大量闲置但显存带宽吃紧。把这两个阶段混在同一批资源上跑结果就是互相拖累——长 prompt 的 Prefill 一上来正在 Decode 的请求延迟立刻抖动。分体式disaggregated推理的思路很直接把 Prefill 和 Decode 拆到不同的资源池上各跑各的中间通过 KV Cache 传输衔接。这样 Prefill 池可以堆算力、Decode 池可以堆显存带宽各自按自己的瓶颈去优化。这个思路在近两年的系统工作里已经被反复验证有效但随之而来的是一个更难的问题两个池子的资源配比该是多少配多了 PrefillDecode 排队配多了 DecodePrefill 堵住。而且线上流量是波动的白天和深夜的请求长度分布、并发量完全不同。静态配比要么浪费、要么违约。SARA 要解决的就是这件事——用排队论建模把“配多少资源”变成一个可以实时求解的优化问题并且把 SLO 作为约束显式写进去。这篇适合谁看如果你在做 LLM 推理服务的部署、容量规划、或者调度系统尤其是已经在用或者打算用分体式架构的团队这篇的建模思路和参数取舍值得细读。如果你只是调用 API 的应用层开发者理解这套逻辑也能帮你判断“为什么我的请求在高峰期延迟会抖”。2. SARA 的整体设计思路拆解2.1 为什么是排队论而不是简单的阈值控制很多团队第一版做资源调度用的是阈值规则队列长度超过 X 就扩容低于 Y 就缩容。这套东西在传统 Web 服务里够用因为请求的处理时间相对稳定。但 LLM 推理不一样单个请求的处理时间方差极大——一个 50 token 的问答和一个 8000 token 的长文档摘要Prefill 时间能差两个数量级。阈值控制在这种高方差场景下会疯狂震荡刚扩容完一波长请求处理完了队列瞬间清空又开始缩容下一波长请求进来又来不及。排队论的价值在于它不只看“当前队列有多长”而是把到达率、服务率、服务时间分布都纳入进来算出的是稳态下的延迟分布而不是瞬时快照。SARA 用排队模型去估计“在当前资源配比下请求的 P99 延迟大概是多少”然后反推需要多少资源才能把 P99 压到 SLO 以下。这就从“事后补救”变成了“事前预估”。提示排队论模型的前提是到达过程和服务过程相对平稳。如果你的流量是突发型的比如整点秒杀式涌入纯排队模型会有偏差需要叠加突发检测或者预留缓冲。这一点在论文的评估部分也有体现。2.2 分体式架构下资源分配的特殊性单池架构的资源分配是一维的加机器就行。分体式是二维甚至多维的Prefill 池加多少、Decode 池加多少、KV Cache 传输带宽要不要跟着调。而且这三个维度不是独立的——Prefill 池扩容会让更多请求更快进入 Decode 池反而可能把 Decode 池压垮。SARA 把这个耦合关系显式建模了。它的核心洞察是Prefill 和 Decode 两个阶段的 SLO 约束是不同的。Prefill 主要影响的是首 token 延迟TTFTTime To First Token因为用户发出请求后要等 Prefill 跑完才能看到第一个字。Decode 主要影响的是每 token 输出延迟TPOTTime Per Output Token也就是字与字之间的间隔。这两个指标对应两个不同的 SLO需要分别约束。这个拆分非常关键。如果只用一个笼统的“端到端延迟”做约束模型会分不清是该优化 Prefill 还是 Decode资源分配就会失焦。SARA 把 TTFT 和 TPOT 分开约束之后优化目标就清晰了在满足 TTFT SLO 的前提下Prefill 池最少需要多少资源在满足 TPOT SLO 的前提下Decode 池最少需要多少资源。2.3 方案选型的取舍逻辑论文里没有追求“大而全”的调度框架而是聚焦在资源配比这一个点上。这个取舍我认为是对的。分体式推理的调度问题可以拆成好几层请求路由、批处理策略、KV Cache 管理、资源扩缩容。如果一篇论文想全解决大概率每层都浅尝辄止。SARA 只做资源配比但做得很深——把排队模型、SLO 约束、动态求解都打通了。另一个取舍是在线求解 vs 离线查表。排队模型的解析解通常很复杂在线实时求解可能有计算开销。SARA 的做法是把模型简化到可以在毫秒级求解的程度同时保留关键的非线性特征。具体来说它对服务时间分布做了近似处理用更易处理的分布去拟合实际观测到的分布牺牲一点精度换求解速度。这个权衡在实际系统里是合理的——资源调度本身不需要纳秒级精度秒级响应就够了。3. 核心细节解析与实操要点3.1 排队模型的关键参数怎么定SARA 的模型里最核心的几个参数是到达率 λ、Prefill 服务率 μ_p、Decode 服务率 μ_d、以及服务时间的变异系数。这几个参数不是拍出来的而是要从实际系统里测出来。到达率 λ 相对好测统计单位时间内的请求数就行。但要注意到达率要按请求类型分开统计。长 prompt 和短 prompt 的到达模式可能完全不同混在一起算会掩盖掉关键特征。实操中建议至少按 prompt 长度分 2-3 档分别统计到达率。Prefill 服务率 μ_p 的定义是单位时间内能处理多少个 Prefill 请求。这个值取决于模型大小、硬件算力、批处理大小。测量方法是固定一批请求跑 Prefill 阶段记录总耗时除以请求数。但要注意Prefill 服务率会随批大小变化——批越大单请求平均耗时越低因为算力利用率高但排队等待时间可能更长。SARA 的模型里用的是“有效服务率”也就是在最优批大小下的服务率。Decode 服务率 μ_d 更微妙。Decode 是逐 token 的所以服务率的单位应该是“每秒能生成多少 token”而不是“每秒处理多少请求”。这个值主要受显存带宽限制和批大小关系很大——批越大显存带宽利用率越高单 token 生成越快。但批太大会导致单请求的 TPOT 变长因为要等同一批里其他请求的 token 一起算。注意测量服务率时一定要在目标硬件上测不要用理论峰值。实际服务率受显存带宽、KV Cache 命中率、批处理策略影响和理论值可能差 30% 以上。我见过有团队直接用 GPU 的 TFLOPS 去估算结果上线后延迟超标一倍。3.2 SLO 约束怎么转化成资源需求SLO 通常是这样的形式“TTFT 的 P99 小于 500msTPOT 的 P99 小于 50ms”。SARA 要做的是把这个延迟约束翻译成资源数量约束。对于 Prefill 池用排队模型可以算出在到达率 λ 和服务率 μ_p 下如果配 N_p 个 Prefill workerTTFT 的 P99 是多少。然后反解要让 P99 小于 500msN_p 至少是多少。这个反解过程可能没有解析解需要用数值方法比如二分搜索来求。对于 Decode 池逻辑类似但约束是 TPOT 的 P99。这里有个细节Decode 的延迟不仅取决于 Decode 池的资源还取决于 KV Cache 从 Prefill 池传过来的速度。如果传输带宽不够Decode worker 会空等 KV CacheTPOT 就会抖。SARA 的模型里把传输延迟也作为一个参数纳入虽然论文里没有展开讲传输层的优化但建模时考虑到了这个耦合。实操中我建议把 SLO 约束做成可配置的。不同业务对 TTFT 和 TPOT 的敏感度不同——对话类应用对 TTFT 更敏感用户等第一个字不能太久而长文本生成对 TPOT 更敏感字与字之间卡顿很难受。把 SLO 做成配置项可以让同一套调度逻辑适配不同业务。3.3 动态调整的触发条件和步长资源配比不是算一次就完事流量变了要重新算。SARA 的动态调整逻辑是周期性的每隔一个时间窗口比如 30 秒重新测量到达率和服务率重新求解资源需求然后调整。这里有两个关键决策窗口大小和调整步长。窗口太小测量噪声大会导致频繁调整、系统震荡窗口太大响应慢流量突变时来不及调整。论文里用的窗口是几十秒级别这个粒度对大多数线上服务是合适的。调整步长指的是每次调整时资源变化的最大幅度。如果算出来需要从 10 个 worker 加到 20 个是一次性加 10 个还是分几步加一次性加响应快但可能过度调整分步加更平滑但可能跟不上流量变化。SARA 的做法是设置一个最大变化比例比如每次最多调整 50%避免剧烈震荡。提示动态调整一定要有“冷却期”。如果刚调整完就立刻重新评估可能会因为系统还没稳定而做出错误决策。建议在每次调整后等待至少一个窗口时间再允许下一次调整。4. 实操过程与核心环节实现4.1 从零搭建一个分体式推理测试环境要复现 SARA 的思路首先得有一个分体式推理的环境。这里我用最常见的开源方案来举例不涉及任何特定厂商。第一步是部署 Prefill 和 Decode 两个独立的推理服务。可以用同一套推理框架但配置不同的参数。Prefill 服务重点优化计算吞吐批大小可以设大一些Decode 服务重点优化显存带宽利用率批大小要适中避免 TPOT 抖动。第二步是打通 KV Cache 传输。Prefill 跑完之后KV Cache 需要传给 Decode 服务。传输方式可以是共享内存同机、RDMA跨机、或者普通的网络传输。传输延迟直接影响 TTFT 和 TPOT所以这一步的优化很重要。实操中如果 Prefill 和 Decode 在同一台机器上用共享内存最快跨机的话RDMA 比普通 TCP 快一个数量级。第三步是加一个路由层负责把请求分发到 Prefill 池以及把 Prefill 的输出和 KV Cache 转发到 Decode 池。路由层还要收集监控数据到达率、队列长度、TTFT、TPOT 等这些是排队模型的输入。# 示例启动一个简单的 Prefill 服务伪代码具体参数按实际框架调整 python -m inference_server \ --mode prefill \ --model /path/to/model \ --max-batch-size 32 \ --port 8001 # 启动 Decode 服务 python -m inference_server \ --mode decode \ --model /path/to/model \ --max-batch-size 16 \ --port 80024.2 参数测量与模型拟合的完整流程环境搭好之后下一步是测量排队模型需要的参数。我建议按以下流程走第一轮测量服务率。固定批大小发送一批已知长度的请求记录 Prefill 和 Decode 的耗时。Prefill 测的是“处理 N 个 token 需要多少时间”Decode 测的是“生成 N 个 token 需要多少时间”。每个批大小测 3-5 次取平均减少噪声。第二轮测量服务时间分布。服务率是均值但排队模型还需要方差。方法是记录每个请求的服务时间算均值和标准差得到变异系数。如果变异系数接近 1可以用指数分布近似如果远小于 1说明服务时间比较稳定可以用确定性分布近似。第三轮拟合到达过程。统计单位时间内的请求数看是否符合泊松分布。如果请求到达是独立的、随机的泊松分布通常是个好近似。但如果有明显的周期性比如整点高峰需要把周期因素也建模进去。第四轮验证模型。用测出来的参数跑一遍排队模型预测的 TTFT 和 TPOT 和实际测量值对比。如果偏差在 10% 以内模型可用如果偏差大检查是哪个参数测不准或者模型假设不成立。参数测量方法注意事项到达率 λ统计单位时间请求数按 prompt 长度分档统计Prefill 服务率 μ_p固定批大小测耗时在目标硬件上测不用理论值Decode 服务率 μ_d测每秒生成 token 数注意批大小对服务率的影响变异系数服务时间标准差/均值决定用哪种分布近似传输延迟测 KV Cache 传输耗时跨机场景必须测4.3 资源求解器的实现要点有了模型参数接下来要实现资源求解器。输入是当前到达率、SLO 约束输出是 Prefill 和 Decode 各自需要的 worker 数量。求解器的核心是一个数值搜索过程。以 Prefill 为例给定到达率 λ 和 SLOTTFT P99 500ms要找最小的 N_p使得排队模型算出的 TTFT P99 小于 500ms。因为 TTFT 随 N_p 增加而单调下降所以可以用二分搜索。def find_min_prefill_workers(arrival_rate, slo_ttft, mu_p, cv): 二分搜索最小的 Prefill worker 数量 arrival_rate: 到达率 lambda slo_ttft: TTFT 的 SLO 上限秒 mu_p: 单个 worker 的服务率 cv: 服务时间变异系数 lo, hi 1, 1000 # 搜索范围 while lo hi: mid (lo hi) // 2 predicted_ttft queue_model_ttft(arrival_rate, mid, mu_p, cv) if predicted_ttft slo_ttft: hi mid else: lo mid 1 return loDecode 池的求解类似但要注意 Decode 的服务率单位是 token/秒而 SLO 是每 token 延迟需要做单位换算。另外Decode 池的资源需求还和平均输出长度有关——输出越长Decode 池需要处理的 token 越多资源需求越大。注意求解器算出来的资源数量是理论最小值实际部署时要留缓冲。建议在理论值基础上加 10%-20% 的余量应对测量误差和流量波动。我见过有团队直接按理论值配结果流量稍微一抖就违约。5. 常见问题与排查技巧实录5.1 模型预测和实际延迟对不上怎么办这是最常见的问题。模型说 TTFT P99 应该是 400ms实际测出来 700ms。排查思路按以下顺序走先查参数测量是否准确。最常见的原因是服务率测高了。比如测 Prefill 服务率时用的是短 prompt但线上是长 prompt实际服务率低很多。解决办法是按 prompt 长度分档测量用加权平均。再查模型假设是否成立。排队模型通常假设到达过程是泊松的、服务时间是指数分布的。如果实际流量是突发的或者服务时间分布和假设差很远预测就会偏。解决办法是换更合适的分布或者叠加突发检测。最后查系统是否有额外开销。模型可能没考虑到 KV Cache 传输延迟、批处理调度开销、网络抖动等。这些开销在低负载时不明显高负载时会放大。解决办法是在模型里加一个经验性的开销项或者直接在实际系统里测端到端延迟来校准。5.2 动态调整导致系统震荡怎么破震荡的表现是资源数量反复上下跳动延迟也跟着抖。原因通常是调整太频繁或者步长太大。第一个手段是加大窗口。如果窗口是 10 秒改成 30 秒或 60 秒让测量更稳定。代价是响应变慢但对大多数业务来说几十秒的响应延迟是可以接受的。第二个手段是加冷却期。每次调整后强制等待至少一个窗口时间再允许下一次调整。这能防止“调整-测量-再调整”的快速循环。第三个手段是加平滑。不要直接用求解器的输出而是用指数移动平均EMA平滑一下。比如新资源数 0.7 * 旧资源数 0.3 * 求解器输出。这样即使求解器输出有波动实际调整也会平滑很多。第四个手段是设死区。如果求解器算出来的资源需求和当前资源差距小于某个阈值比如 10%就不调整。避免为了微小差异频繁调整。5.3 不同请求长度混在一起怎么处理线上流量通常是长短混合的。长请求的 Prefill 时间长会阻塞短请求导致短请求的 TTFT 超标。SARA 的模型如果只用平均请求长度会掩盖这个问题。实操中有两个解法。一是分队列。把长请求和短请求分到不同的 Prefill 队列各自独立调度。短请求队列优先处理保证 TTFT长请求队列可以容忍更长的等待。二是分档建模。在排队模型里按请求长度分 2-3 档每档单独算资源需求然后加总。这样更精确但模型复杂度也更高。我个人的经验是如果长短请求比例比较稳定分档建模就够了如果比例波动大分队列更稳。分队列的代价是资源利用率可能略低因为两个队列的资源不能互相借用。问题现象可能原因排查手段解决办法模型预测延迟偏低服务率测高了按长度分档重测用加权平均服务率模型预测延迟偏低忽略传输开销测端到端延迟模型加开销项资源频繁震荡窗口太小看调整日志加大窗口冷却期资源频繁震荡步长太大看调整幅度加平滑死区短请求 TTFT 超标长请求阻塞分长度看延迟分队列或分档建模Decode TPOT 抖动KV Cache 传输慢测传输延迟优化传输或加缓冲5.4 几个容易踩的坑坑一用理论算力估算服务率。GPU 的 TFLOPS 是峰值算力实际推理能用到 30%-50% 就不错了。一定要实测。坑二忽略批处理的影响。批大小变了服务率也变。模型里用的服务率要对应实际使用的批大小。坑三SLO 设得太紧。如果 SLO 设成 TTFT P99 100ms资源需求会指数级上升。SLO 要根据业务实际需求设不要盲目追求低延迟。坑四不做压测就上线。排队模型是近似实际系统有各种意外。上线前一定要做压测验证模型预测和实际表现是否一致。坑五忘了监控。动态调整依赖监控数据。如果监控有延迟或者不准调整就会失准。监控系统本身也要纳入容量规划。6. 我对这套思路的实践体会SARA 这套东西最值钱的地方不是某个具体的排队模型公式而是把资源分配从“经验驱动”变成了“模型驱动”。以前配资源靠拍脑袋或者简单阈值现在有了一个可以解释、可以验证、可以迭代的框架。即使你不用它论文里的具体模型这个思路也值得借鉴。我在实际项目里试过类似的思路最大的感受是参数测量比模型选择更重要。排队模型本身有很多变体M/M/1、M/M/c、M/G/1 等等选哪个其实差别没那么大。但如果服务率测不准再好的模型也白搭。所以如果要在团队里推这套东西第一步应该是把监控和测量做扎实而不是急着上模型。另一个体会是动态调整的工程实现比算法本身更难。算法上算出一个资源数量很简单但要在生产系统里安全地扩缩容涉及容器调度、负载均衡、连接迁移、状态同步等一堆工程问题。这些在论文里通常一笔带过但实际落地时才是真正花时间的地方。最后分享一个小技巧如果你们的流量有明显的日周期规律可以先用历史数据离线跑一遍模型算出不同时段的资源需求曲线作为动态调整的初始值。这样系统启动时就有个合理的起点不用从零开始摸索。等运行一段时间、积累够数据之后再逐步切换到完全动态的调整模式。这个过渡策略能显著降低上线初期的风险。