资讯动态

FlexPipe:碎片化GPU集群下的LLM推理动态并行策略换挡

发布时间:2026/10/17 11:34:09 来源:尧图企业网站定制
1. 所谓碎片化GPU集群先说清楚我面对的资源形态如果你在某个公司管过一阵子大模型推理集群大概率遇到过这个场景明明集群里堆着不少卡可线上推理服务的延迟曲线就是不稳定白天高峰期还能勉强稳住一到半夜来个超长文档分析请求单条延迟直接翻三倍。查了一圈发现根本不是模型写错了而是你手里那批边角料GPU集群在跑LLM推理流水线的时候快慢节奏完全没法统一。我这次要分享的FlexPipe项目就是专门处理这个问题的。它做的事情一句话就能讲明白让LLM推理流水线在碎片化GPU集群里根据实时负载自动切换并行策略和计算节奏——就像开车时变速器根据路况自动换挡一样。模型本身不用改服务接口不用动变化的只是运行时调度逻辑。先说说碎片化集群到底是个什么形态。绝大多数公司不是一次性采购整批同规格GPU的而是跟着项目逐步买卡今天加两张A100 80G明天补几块V100 32G再往后可能还有闲置出来的RTX 3090。这些卡用几年下来就分布在不同的机柜里网络也不统一有的机器走的是高速交换网络有的还在用万兆以太网。你真要拿这批机器跑一个大模型推理服务第一个感受就是速度忽快忽慢。第二个感受是显存极度不规整。同一台机器上可能插着两张80G的卡和四张24G的卡你想把模型切开放到多张卡上切分方式必须迁就最小公分母。更麻烦的是线上不止一个服务在跑有的服务占掉了某张卡的一半显存剩下的另一半又不够放一个完整的模型层。这种物理和逻辑上的双重碎片化是定制化调度系统存在的根本原因。第三个特点是慢邻居问题。推理流水线通常是多节点协同的一个请求的计算图被拆到不同节点上执行任何一段慢的链路都会拖慢整体。集群里只要有一两台机器网络性能偏弱或者CPU预处理能力跟不上整个推理管道都会出现周期性卡顿。你从监控面板上看GPU平均利用率可能还挺高但单看每一条请求的端到端延迟会发现尾巴很长P99完全没法看。FlexPipe的设计目标就是把这些碎片用调度层的智能弥补掉。它的核心思想不是把硬件换成好的而是让推理流水线的执行方式去主动适应硬件现状。项目做下来我的体会是推理引擎本身再快也快不过调度策略和硬件形态之间的匹配度。这一块如果匹配得好哪怕全是老卡照样能跑出让人满意的服务质量。2. 挡位到底是什么六个并行策略的换挡逻辑FlexPipe里最核心的概念就是挡位Gear。我当初做技术方案评审时拿汽车变速器给团队打了个比方MLP和Attention的计算强度完全不同长请求和短请求对显存和带宽的需求也完全不同推理引擎应该像变速箱一样根据路况自动匹配扭矩和速度而不是一脚油门踩到底。在具体实现里一个挡位就是一组完整的并行策略配置决定了当前这个时刻模型的计算图如何被切分到物理设备上以及批处理调度应该如何运作。我们一共定义了六个挡位从低到高对应不同的并行程度和调度方式。1挡是最简单的整卡数据并行DP每张卡独立处理不同请求卡与卡之间几乎不需要通信。这种模式适合并发很高、单个请求比较短的流量比如高频的文本分类、向量化请求。它的优点是没有通信开销缺点是单请求能用的显存和算力上限就是单卡处理超长序列时会力不从心。2挡和3挡是张量并行TP的不同切分尺度。2挡把线性层沿隐藏维度切到2张卡上3挡切得更细。这种模式适合单张卡放不下完整模型参数、但请求还不是特别长的场景。TP的好处是单请求的延迟低坏处是每过一个算子都要做一次全归约通信对节点内带宽要求很高。4挡是序列并行SP本质上是TP的增强版把LayerNorm和Dropout这些按序列维度执行的操作也摊到多卡上。它主要解决长上下文场景下激活显存爆炸的问题把KV Cache和激活值分散到多卡同时保持算力集中。5挡是流水线并行PP主要用在一个节点放不下完整模型的场景。把模型按层切成几段每段放到不同的卡甚至不同的节点上段与段之间通过微批调度流水化执行。它牺牲了一部分单请求延迟换来了整机吞吐的扩展性。6挡是混合并行也就是TP、SP、PP、DP同时启用适合超大规模模型全集群部署的极端场景。这个挡位的调度开销最大换挡的成本也最高所以只在高负载持续很久的时候才会触发。两个相邻挡位之间的跨度越大换挡动作消耗的时间越长。1挡切到2挡可能只要几十毫秒因为只需要调整数据加载和通信组5挡切到6挡可能要重新划分模型层和KV缓存布局耗时以秒计。所以FlexPipe的调度器在做决策时必须同时考虑目标挡位的理论性能和换挡本身的成本否则就会出现在一个挡位上刚跑1分钟又切回去的开销浪费。这里想强调一点挡位切换并不是改动模型代码而是运行时行为的变化。模型权重不变计算图结构在逻辑上也一样变的只是谁来算哪一块、算完怎么聚合。这也是为什么FlexPipe能做成一个相对独立的调度层而不是深埋在某个推理框架内部的绑定逻辑。3. 换挡决策链路调度器怎么判断该换了光定义好六个挡位没有用关键问题是调度器用什么信号判断当前该在哪个挡位上跑这个决策链路我花了将近三周时间打磨核心就一个字稳。方案再复杂如果决策抖动频繁带来的开销会比不换挡还大。先说采集层。调度器需要实时监听四类信号GPU利用率、显存余量、节点间通信延迟、请求队列长度。GPU利用率反映算力是否饱和显存余量决定能不能承载更激进的并行切分通信延迟暴露了慢链路问题队列长度则间接表达了当前吞吐是否跟得上。然后是判定层。我们把判定逻辑整理成三个方向的评估算力瓶颈评估、显存瓶颈评估、通信瓶颈评估。算力瓶颈很好理解就是GPU计算单元跑满了但队列还在积压显存瓶颈是模型加KV缓存快把显存撑爆了再往下就要OOM通信瓶颈是算力没满但卡在等待远端数据回来的时间占比很高这时候盲目加并行度只会更慢。这里我放一段简化版的决策代码是我们调度器的核心函数真实实现比这个复杂得多但逻辑主线可以看得很清楚def decide_gear(metrics: NodeMetrics) - int: 根据实时指标决定目标挡位 # 1. 先看显存健康度这是硬约束 if metrics.mem_pressure 0.85: # 显存紧张降低并行度减少激活值和KV缓存碎片 return min(current_gear, 3) # 2. 看队列积压和GPU利用率 if metrics.queue_depth 50 and metrics.gpu_util 0.6: # 队列堆积但算力没吃满说明并行度不够 # 但还要检查通信是否健康 if metrics.comm_health 0.8: return min(current_gear 1, 6) # 3. 通信质量差时不升挡反而需要降挡减少通信开销 if metrics.comm_health 0.4: return max(current_gear - 1, 1) return current_gear这个函数的判断逻辑虽然简单但它体现了几个我们后来验证有效的原则显存是硬约束必须排在最高优先级通信健康度是软约束只在它足够好时才升级挡位降挡的触发条件比升挡更宽松因为降挡通常更安全。执行层方面换挡过程被分成预演和生效两个阶段。预演阶段只是计算新挡位下的显存预算和通信拓扑检查当前空闲显存是否足够完成切换生效阶段才会真正调整并行组、重新分配KV缓存块、改变数据加载的分发策略。这样设计是为了避免切换做到一半发现显存不够导致服务大面积失败。为了让决策不抖我们给每个挡位设置了一个最小驻留时间。比如2挡一旦生效至少跑满90秒才允许再次换挡。这个数字是实测调出来的太短了挡位间频繁跳变系统一直处于重配状态吞吐反而下降太长了又反应迟钝遇到流量突刺时调度跟不上。90秒刚好卡在两者的平衡点上。最后还有一个兜底机制——如果某个节点的判定结果与全局趋势冲突以全局为准。举个例子某个慢节点自己的指标显示该升挡但全局通信延迟正在恶化这时调度器会忽略该节点的升挡请求甚至反过来降挡。这套机制本质上承认了一件事碎片化集群内部存在严重不平等局部最优不等于全局最优。4. 从零做一个FlexPipe架构拆解与踩过的坑整个FlexPipe系统分三个组件调度器、观测器、执行器。观测器负责采集数据调度器负责做决策执行器负责把决策落到推理引擎上。三个组件完全解耦通过本地IPC通信。这样设计的最大好处是观测器和执行器可以随时重启调度器不会跟着挂掉。观测器写起来不难但有些细节比较烦。GPU利用率如果直接读全局值会被平均骗了必须拆到每个SM层面去采样然后看分布——峰值高但平均值低说明计算时断时续像是被通信卡住了平均值高但峰值不高说明真的在持续计算。显存监控更麻烦不仅要看总量还要看空闲块的连续性因为不连续的空闲内存根本没法给大张量用。调度器用的是Python实现写起来最容易反而是执行器最需要细心。我最初在执行器上踩了个大坑调整并行策略时直接重建了通信组导致正在进行的请求全部断掉线上服务直接雪崩。后来改成两阶段切换才解决问题先准备好所有新拓扑的资源确认无误再在整个容器组上做一次性切换切换期间用双缓冲机制承接请求。这里放一段执行器调整并行策略的代码骨架展示了如何在实际中控制切换的安全性def apply_gear(target_gear: int): 安全换挡两阶段切换避免请求中断 # 阶段一预演与资源预分配 plan compute_resource_plan(target_gear) for rank in range(world_size): show show_cuda_mem_allocated(rank) if plan.required_mem[rank] show plan.available_mem[rank]: raise GearSwitchError(f节点{rank}显存不足) # 阶段二原子切换 with gear_lock: # 全局锁防止观测器误报当前挡位 # 先停掉新请求的进入等待存量请求完成 barrier_only_for_new_requests() # 更新并行组和通信拓扑 update_parallel_groups(plan.group_spec) # 重建KV Cache分片布局 rebuild_cache_slices(plan.cache_layout) # 恢复请求入口发布新挡位状态 resume_request_entry()两阶段切换的关键在于先停入、再切、再放行。停入阶段只拒绝新请求已经跑到一半的请求继续用旧拓扑执行完。等待存量请求的时间窗口通常不超过几百毫秒对整体QPS影响很小但对稳定性的提升是质的改变。还有一个实际工程问题值得单独讲分布式环境里每个节点都有自己的本地调度器但它们之间如何达成一致我们最终的方案是选一个Leader节点做全局决策其余节点只上报状态和执行命令。Leader挂了怎么办我们用简单的竞选机制备选节点在Leader心跳超时后自动接管。这套方案不算新但在碎片化集群里足够可靠而且实现成本低没有引入额外系统组件。5. 实测效果换挡前后到底差了多少理论写得再漂亮也要拿到真实负载上遛一遛。我们的测试环境是一套8节点的混合集群配置如下节点组GPU型号单卡显存数量互联方式前4节点A10080GB每节点8卡高速互联中间2节点V10032GB每节点8卡高速互联最后2节点RTX 309024GB每节点8卡万兆以太网说实话这批机器的网络是典型的新老混搭前6个节点之间走高速网络后2个节点只能用受限带宽。从资源角度讲这就是教科书式的碎片化集群。我们选取了一组典型的混合流量做测试60%短请求平均256 token输入、64 token输出30%中长请求平均1024 token输入、256 token输出10%超长请求平均4096 token输入、512 token输出。跑的是7B参数模型峰值并发120路。对照组是固定并行策略FlexPipe组是开启动态换挡。测试结果说实话让我有点意外收益比预想的大指标固定配置基线FlexPipe提升比例平均延迟480 ms310 ms35%改善P99延迟2100 ms980 ms53%改善GPU平均利用率41%67%26个点提升吞吐量850 req/s1280 req/s50%提升最明显的改善在P99延迟上。固定配置的问题是一把钥匙开所有锁短请求多的时候高并行模式白白浪费通信资源超长请求多的时候低并行模式又卡在单卡算力瓶颈上。FlexPipe在短请求高峰期自动落到2挡甚至1挡在超长请求大量涌入时逐步升到4挡甚至5挡每条请求都能找到相对合适的执行节奏。还有一个细节值得分享开启FlexPipe后集群里那两台千兆网络节点的表现也变好了。原因不复杂——调度器检测到它们通信健康度差就自动把它们的挡位限制在低并行模式让它们专注于处理短请求不再勉强参与长请求的跨节点协同。这相当于给慢节点减负反而让整体吞吐上去了。代价当然也有。换挡期间会有短暂的QPS波动大概持续两三百毫秒调度器通过双缓冲尽量消隐掉了。如果业务对极端稳定性有硬性要求可以在配置里关闭某些挡位切换或者把换挡窗口拉长让系统用更平滑的方式慢慢过渡。这是FlexPipe的取舍大部分场景下多花两三百毫秒换50%的吞吐提升很划算。6. 上层业务怎么适配让应用的感知降到零做这一类系统一个很容易犯的错是把调度逻辑设计得和推理引擎绑定太紧。FlexPipe从一开始就把目标定为对上层业务透明让接入方基本无感。API层保持原有接口不变请求进来还是同样的格式返回也还是同样的格式变化的只是内部执行路径。具体怎么做我们在执行器上做了一个名为VirtualEngine的封装层对外暴露和普通推理引擎完全一致的接口对内负责挡位切换和资源调度。业务方不用关心当前请求实际在哪几张卡上跑也不用处理任何并行相关的参数只要传进来一个请求对象就行。这个封装层的核心代码就几行但价值很大因为它在易用和可控之间找到了一个平衡点。配置方面也要做到动态化。模型参数、并行度、缓存大小、换挡策略全部通过外部配置文件下发跑着的服务不用重启。比如某天晚上大促流量预期翻倍运维只需要在配置中心把FlexPipe的峰值策略调成激进升挡调度器就会自动在接下来几分钟内把挡位往上升。这个能力在线上服务里非常省事。还有一类场景是请求级别的隔离某个租户的请求延迟敏感另一个租户的请求吞吐敏感。FlexPipe支持按请求属性做路由分类让不同租户的请求走到不同的挡位策略池里。这个功能在混合负载时帮了大忙因为我们发现一刀切挡位策略在高并发时根本满足不了不同业务的差异化需求。因为我坚持“感知不到换挡的系统才是好系统”所以FlexPipe的接入成本被压得很低。我们有一个从来没碰过这个系统的同学照着文档配好环境后10分钟就把一个现有的推理服务接入进来了。那一刻我觉得这个项目的方向走对了调度系统的成功标准不是功能多而是让人感觉不到它的存在。7. 碎片化集群里换挡的意外状况几条真实踩坑记录前面说的都是顺利的情况但实际调试阶段踩了不止一个坑。挑三个影响最大的记录在这里给准备做类似系统的同学当参考。第一个坑是慢节点拖累全局挡位。系统刚上线时我们发现每次全局升挡后总会有一两个节点出现显存溢出或者请求超时。定位后发现原因很简单——那两台3090节点通信带宽不够升挡后它们承担的跨节点通信量暴增直接把链路打爆。解决办法是增加一个节点信任度机制每个节点会记录自己历史上的通信失败率和带宽实测值低于阈值的节点禁止参与高挡位只能留在低挡位池里独立运行。调度器再也不会被单点拖垮。第二个坑是挡位抖动。最初的最小驻留时间设的是30秒结果在流量波动比较剧烈的场景下系统频繁在2挡和3挡之间来回横跳。每次换挡都会带来一次通信组的重配虽然我们做了预演不至于挂服务但请求延迟确实波动了。后来把最小驻留时间调到了90秒同时在决策函数里加了一个冷却周期一次换挡后的30秒内禁止发起反向换挡抖动问题基本消失。第三个坑藏在显存碎片里。大模型推理服务跑久了显存会出现一种奇怪的现象总量很充足但可用的大块连续内存很少。这是因为KV Cache和临时激活值反复申请释放把显存切成了很多碎片。FlexPipe换挡时如果申请大张量失败就会误判成显存不足。后来我们在执行器里加了一个显存碎片整理模块在挡位切换的低峰期自动整理空闲块并且把KV Cache的分配改成预分片模式从源头减少碎片产生。整理一次大概耗时一两秒完全放在低峰期执行不影响线上。除了这三个坑还有一个经验想多说一句换挡逻辑千万不要写成越复杂越好。有一段时间我在决策函数里加了十几条规则试图覆盖所有可能的边缘情况结果系统的行为变得极难预测出了问题都不好定位。后来我砍掉了三分之二的规则回到显存硬约束优先、通信健康度做软约束、队列深度做参考这三个核心原则行为一下子变得可预期了。调度系统最怕的不是功能少而是行为不可解释。现在这个项目在内部已经跑了大半年我的整体感受是碎片化GPU集群不是一个将就着用的困境它只是需要一套不一样的调度思维。FlexPipe证明了只要你愿意在调度层多花功夫老卡混卡也一样能跑出稳定的服务。如果让我再重新做一次我可能会在生产环境里更早地引入节点信任度机制因为慢节点对全局的影响真的比想象中还要大。最后分享一个小实操经验挡位定义不是越细越好六个挡位在我们这套环境里刚好够用再多就会出现切换成本大于收益的边际效应。上线前最好先用历史流量回放一遍看看不同负载模式下挡位分布是否合理这一步能帮你省下大量在线调参的时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑