FlexPipe:让LLM推理流水线在碎片化GPU集群中动态换挡

发布时间:2026/10/19 10:43:42
FlexPipe:让LLM推理流水线在碎片化GPU集群中动态换挡 让LLM推理流水线在碎片化GPU集群中动态换挡——这个念头是我在一次“深夜救火”时被逼出来的。当时集群里剩了三张规格不完全相同的计算卡正好是上一个批处理作业释放出来的。线上一个7B模型的推理服务却按老配置要求“同规格四卡全互连拓扑”调度器在碎片里翻来覆去找不到合适组合三张卡就那么白白空着。那晚我意识到很多LLM推理流水线的性能问题不是模型不行而是没在碎片化GPU集群里动态换挡。FlexPipe就是我们后来做的调度与执行层。它不改造推理模型本身也不强制统一并行策略而是让同一条LLM推理流水线能在一卡、两卡、四卡甚至“两卡一个拓扑、单卡另一个节点”的混合组合之间平滑切换。换句话说它把“作业必须申请一整组同规格GPU”这件事彻底改成了“资源与流水线不断协商挡位”。这篇文章会把FlexPipe背后的思路、挡位建模、切换链路和实测结果完整展开也聊聊只有跑过真实碎片集群才会踩到的坑。适合正在做推理服务、平台调度或者想把剩余GPU资源利用起来的同学。1. 固定流水线在碎片化集群上为什么注定低效1.1 调度器眼里的“剩卡”和推理作业眼里的“硬需求”是两套语言调度器看资源的方式和推理框架看资源的方式差别极大。调度器拿到的是显存大小、算力、卡间拓扑、NUMA归属它会说某个节点有三张可用卡0号卡被其他作业占用了一部分显存1号卡完全空闲2号卡完全空闲但1号和2号之间的互联带宽不错0号和它们之间却隔了一道慢速互联。推理作业的配置往往很“硬”。配置文件里写的是“申请4张同规格GPU、张量并行度4、卡间必须全互连”如果做不到就整个作业无法启动。这类硬约束一旦写进部署模板调度器就只能做二选一的判断要么找到一整组完全符合条件的卡要么什么都不跑。碎片化集群的问题本质不是缺资源而是资源以调度器无法理解的形式存在。这种语义错配在在线推理场景里更明显。离线训练可以等资源任务提交上去队列排半小时无所谓。但推理服务有SLO等不起。用户请求已经到网关模型却还没分配好卡那只能超时返回错误。很多平台同学抱怨“集群明明有卡服务却一直起不来”很大程度上不是调度算法不行而是“作业必须固定并行度”这个前提把大量可用碎片直接排除了。1.2 固定并行度不仅浪费碎片还让负载波动无解LLM推理和离线训练还有一个本质区别它不是单一计算形态。一次请求会先进入prefill阶段对全部输入token做并行计算这个阶段算力敏感随后进入decode阶段逐token生成输出这个阶段对显存带宽和KV Cache容量更敏感。经典的流水线并行会把模型的不同层放到不同卡上但如果请求形态变化剧烈固定四卡流水线就会出现“prefill时四卡都很忙decode时某些卡明显空闲”的情况。由于配置固定空闲的卡不能借给别的服务波峰波谷被叠成双重浪费。FlexPipe的起点是把这个前提改掉调度器不需要提供同规格四卡只要告诉执行层“现在有几张卡、分别是什么规格、卡间拓扑如何、能维持多久”执行层自己决定用哪个挡位。这样资源碎片和负载波动就不再是两件不可调和的矛盾而成为需要动态平衡的两个变量。2. FlexPipe的挡位建模先算清楚每个挡位能跑多快2.1 定义候选挡位并行度不是越高越好在FlexPipe里“挡位”不只是并行度而是包括并行策略、权重精度、批量大小、KV Cache承载策略的一组完整配置。以我们平时接触较多的7B模型为例在碎片集群上预置了下面几种挡位。不要把这组数据当成通用基准它的意义在于说明不同挡位之间存在明显的吞吐与延迟差异而最优解完全取决于当前资源组合。挡位并行配置权重精度Prefill吞吐近似Decode吞吐近似适用资源形态1单卡INT8850 tokens/s180 tokens/s只有一两张独立卡2双卡张量并行INT81500 tokens/s320 tokens/s两张卡互联良好3四卡流水线并行INT81400 tokens/s280 tokens/s四卡同节点或有高速互联4双卡流水线双卡张量FP161800 tokens/s300 tokens/s混合拓扑跨慢速互联为什么不全用张量并行因为张量并行对卡间带宽极其敏感。每个transformer层的前向计算里都有大量all-reduce通信如果卡间是慢速互联通信时间会直接超过计算时间挡位值再好看也没用。流水线并行对带宽要求低一些但它的代价是单个请求要依次经过多张卡整卡空闲时间更多只有在批量足够大时效率才上来。碎片集群里经常出现的组合是两张高速互联的卡加一张独立卡。固定策略会让三张卡强行跑一个三路并行结果高速互联的卡被慢速链路拖住实际吞吐还不如“两张卡跑张量并行另一张卡单跑”。FlexPipe会把这两种情况都建成独立挡位执行时按实时资源状态选择。2.2 挡位表怎么测一小时实测胜过一天估算挡位表不是写在配置文件里的静态数字而是通过一个基准测量器跑出来的。我们做了个叫gear_bench的小工具对每个候选挡位做固定压力测试控制输入长度和输出长度记录prefill吞吐、decode吞吐、p90延迟以及从冷启动到Ready的时间。这个做法可能看起来简单但非常必要。曾经我们尝试用理论公式去估算不同并行度的吞吐结果和实测差了将近一倍。原因是碎片集群里有太多理论模型覆盖不到的因素共享带宽、CPU绑定、显存带宽竞争、持久化缓存命中率、NCCL通信调度波动。一小时实际测量能把这些因素全部吸收进挡位表之后调度器只需要查表。示意代码如下def measure_gear(model_id, parallel_plan, precision, batch_sizes): engine build_engine(model_id, parallel_plan, precision) results {} for bs in batch_sizes: trace run_trace( engine, input_len512, output_len128, num_requests200, batch_sizebs ) results[bs] { prefill_tput: trace.prefill_throughput, decode_tput: trace.decode_throughput, p90_latency: trace.p90_latency, peak_memory: trace.peak_memory, } return results这里有一条很实际的经验不要只测“最优批量”而是把批量从一个很小的值扫到显存允许的最大值。因为在线服务场景里请求是持续到达的预填充阶段可能需要小批量低延迟解码阶段可能希望大批量高吞吐。挡位表里真正有用的不是一两个性能峰值而是不同批量下的完整曲线。动态换挡时FlexPipe会同时查“当前批量”和“目标批量”两行数据避免换过去之后才发现性能不增反降。3. 动态换挡的执行链路状态冻结、拓扑迁移与流量切换3.1 把权重迁移和KV Cache重分片分离换挡和变速箱换挡一样核心问题是“动起来的时候不能熄火”。LLM推理服务器从挡位1切到挡位2需要解决两件事模型权重重新分布到多张卡以及请求过程中产生的KV Cache按新拓扑重分片。权重迁移相对简单因为它不是在线状态只需要把已经加载的权重从显存里重新切分。可KV Cache是活跃请求的状态不能随便丢。如果为了换挡把一个生成了300个token的请求断掉用户感受到的就是服务中断。FlexPipe把这两个动作分开了。第一步把新流量切到新挡位实例这个实例可以先做权重加载和预热第二步旧实例继续服务存量请求等这批请求自然跑完或者等到可接受的超时阈值第三步把仍然存活的存量请求的KV Cache按新拓扑做一次同步重分片再迁移到新实例。这个过程被称为“优雅换挡”。def switch_gear(from_gear, to_gear, live_requests): new_engine warm_up(to_gear) switch_traffic(new_engine) deadline now() 30 while live_requests and now() deadline: request live_requests.popleft() if request.cache_size migrate_threshold: request.shutdown(retry) else: new_engine.inherit_request(request, from_gear) live_requests.append(request) drain_and_release(from_gear)把权重迁移和KV Cache重分片分开最直接的好处是换挡对客户端的影响被限制在一个很小的窗口里。如果网络条件好很多存量请求根本不会感知到自己被换到了另一组卡上。只有那些已经生成很长的请求由于迁移代价大于重算代价才选择让客户端重试。这个阈值怎么定我们实践下来生成token数少于64的请求直接迁移KV Cache通常不划算因为重算一次prefill更快生成token数大于256的请求则尽量迁移因为重算一次会让用户等太久。3.2 换挡触发条件不能只看卡空闲要看服务等级目标很多人听到动态换挡第一反应是“调度器发现卡空出来就换过去”。这个思路在稳定集群里勉强能用在碎片集群里会灾难性地抖动。因为空闲卡的出现频率很高如果每次都触发换挡服务会一直处于迁移状态吞吐不升反降。FlexPipe真正用的触发信号不是“卡是否空闲”而是服务等级目标是否面临风险。我们同时监控三类信号请求队列积压深度、p99延迟是否逼近SLO、当前挡位的资源利用率是否长期过低。只有当这些指标同时满足条件才允许发起换挡。伪代码是这样的def should_shift(state): queue_pressure state.request_queue_depth / state.queue_capacity latency_pressure state.p99_latency / state.latency_slo underutilized state.gpu_utilization 0.35 if queue_pressure 0.7 and latency_pressure 0.8: return up, pick_best_gear(state) if underutilized and queue_pressure 0.3 and latency_pressure 0.5: return down, pick_cheapest_gear(state) return hold, None重点在于“连续判定”。我们不会因为某一次采样超标就立刻换挡而是要求指标连续五次采样都处于触发区间才真正发起迁移。这样做的原因是碎片集群里的瞬时抖动非常多某次网络打满或者另一租户抢带宽都可能让延迟短暂升高但过十秒钟就恢复。如果每次都换挡系统会在几个挡位之间反复横跳。3.3 调度算法贪心加预演回退当多个候选挡位都满足条件时FlexPipe用的是一个带预演的贪心策略。它会先算每个候选挡位的收益收益定义很简单目标挡位在当前批量下的有效吞吐减去当前挡位的有效吞吐再扣除迁移成本和风险系数。风险系数来自拓扑差异性比如从单卡换到双卡张量并行因为要把权重切成两半并建立通信组风险系数是0.2如果从双卡换到四卡流水线涉及跨节点迁移风险系数会调高到0.5。计算出得分最高的挡位后还会做一次“预演”。预演不是在全部流量上做而是在一组空闲实例上先起一个目标挡位加载同一份权重跑一小段真实流量验证它的延迟和吞吐能达到挡位表里的预期值。如果预演失败就自动回退到当前挡位并把该组合加入一个短暂的失败黑名单。这个机制让我们少踩了很多次“挡位表看着没问题换过去就崩”的坑。4. 实测当集群只剩“两种半”卡FlexPipe把碎片变成可用吞吐4.1 模拟集群与基线设定为了验证FlexPipe的真实收益我们搭了一套模拟集群四张规格略有差异的卡0号卡24GB显存1号和2号卡在同一个节点且互联良好3号卡在另一个节点但通过慢速链路接入。测试前1号卡还被另一个任务占用了一半显存所以在任何一个时间点系统都无法拼出“同规格四卡全互连”的理想资源。基线方案是传统的固定四卡流水线必须等待1号卡完全释放再等3号卡能满足拓扑要求整个过程耗时约40分钟。这个方案在等待期间没有营收调度器反复尝试申请资源每次失败后都会把作业重新放回队列。FlexPipe方案则不同。启动后不到2分钟调度器识别出0号、1号、2号三张卡的实际可用情况1号和2号之间互联良好组成挡位2服务主流量0号剩余显存较大但与其他卡互联一般组成挡位1服务低优先级请求。随后当1号卡的占用任务结束系统再动态把挡位2升级成挡位3。4.2 关键结果碎片利用率与延迟的取舍指标固定四卡FlexPipe自适应服务可用时间40分钟后2分钟内等待期间吞吐0310 tokens/s稳定后平均吞吐240 tokens/s310 tokens/sp99延迟资源不足时直接超时始终在SLO以内碎片GPU利用率0%等待期间78%提升不是来自模型本身的优化而是来自“不让资源空转”。固定方案在等待期间完全无法服务FlexPipe却先把三张可用卡变成两个独立挡位立刻承担了流量。等资源条件变好后再通过优雅换挡升级到更高挡位整个过程中流量没有出现长时间中断。有一个细节值得注意FlexPipe的吞吐并没有因为“碎片化利用”而降低多少。原因是它选择把高速互联的卡组成张量并行组避免慢速链路拖累主要请求而慢速链路单独跑一些离线或者低优先级请求。这比强行把三张卡绑在一起跑三路并行要快得多。我们做过对照实验如果强制三卡并行吞吐反而只有260 tokens/s因为慢速互联的通信开销把优势吃掉了。4.3 实测中的三个坑第一个坑拓扑感知不能只看“是否同节点”。模拟集群里1号和2号卡本来互联良好但测试期间另一个租户在这两张卡之间打满带宽导致换挡前探测到的有效带宽只有原来的40%。结果挡位2启动后不但没有达到表里的1500 tokens/s反而比单卡还慢。我们后来在每次换挡前增加了一次10毫秒的实时带宽探测并把探测结果存到挡位选择器里作为通信代价的修正系数。第二个坑KV Cache迁移时要处理“隐性断点”。一开始我们采用优雅换挡让旧实例继续服务存量请求但某个请求的KV Cache分布在新旧两组卡上切换后新实例无法直接继续生成。折腾了几次之后我们调整了策略如果请求的生成token数超过256先把KV Cache按目标拓扑重新分片再一次性迁移如果少于64则直接让客户端重试。这个阈值要按实际集群带宽调整不要照抄。第三个坑冷却期一定要比迁移时间长。我们最开始把冷却期设成60秒结果一次换挡还没彻底完成新的触发信号又来了系统立刻准备下一次换挡造成严重的震荡。后来把所有挡位切换都加了180秒冷却期并且用历史记录做防抖一旦某个挡位在最近十分钟内失败过两次就直接忽略它。这个规则非常暴力但在碎片集群里很管用。5. 进阶玩法挡位不一定只有并行度5.1 把批量大小、投机解码和缓存策略都做成“挡”变量FlexPipe做到后面我们发现“挡位”这个概念完全可以超出并行度本身。批量大小是一个天然的可换挡变量请求少时用单请求小批量保持低延迟请求多时切换到大批量换取高吞吐。投机解码的长度也可以做成挡位变量当目标模型和服务模型都空闲时使用更激进的投机长度当集群负载升高时动态缩短投机长度减少无效推理。更实用的扩展是把KV Cache的持久化策略纳入换挡。碎片集群里经常出现一张卡显存大但算力一般另一张卡算力强但显存较小。FlexPipe可以让显存大的卡承担更多KV Cache旁路存储算力强的卡专注计算。这样“挡位”从并行配置扩展到了“存储和计算分离”的组合资源的利用维度又多了一层。5.2 在没有私有调度器的环境下自建适配层很多人以为做动态换挡必须掌控整套集群调度器。其实不需要。FlexPipe在设计上只依赖一个很朴素的接口能查询当前可用卡列表能在确定节点上启动新实例。我们在一套通用容器调度环境里做过适配方式是增加一个辅助Pod定期扫描节点资源状态把结果写进共享配置中心FlexPipe再从中读取候选资源。这样既有通用调度器的资源隔离能力又有FlexPipe自己的换挡决策能力。适配层的价值还在于可观测性。每次换挡都会留下记录时间、当前挡位、目标挡位、触发原因、迁移耗时、迁移后的性能表现。这些记录在普通推理框架里是完全没有的但对排查线上问题非常重要。我们可以很清楚地看到某次p99延迟升高到底是模型变慢还是后台资源竞争导致换挡失败。最后再分享一个经验挡位表一定要做成可配置的不要硬编码。FlexPipe运行一个季度之后我们发现不同业务模型的“最优挡位”差异非常大有的模型适合单卡INT8有的必须用FP16保持精度有的在四卡流水线下有明显加速有的则因为通信开销几乎无提升。把挡位表放到配置中心之后新增模型只需要跑一次gear_bench就能上线后面所有决策都变得非常轻量。碎片化GPU集群不会消失真正需要学会的是让推理流水线在资源不那么完美的时候仍然能找到最合适的那一挡。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询