日期:2026-10-21
很多团队早期都是训练集群和推理集群分开建的,看着各管各的挺清楚,真跑起来才发现两边都在浪费。我们接的一家客户就是这样:白天推理高峰,训练那边占着一堆卡闲着;深夜训练批跑起来,推理要是来个突发流量,又抢不到资源。两边利用率都上不去,财务看账单直皱眉。
当时他们的痛点很典型。训练任务一提交就占满整卡,跑完才释放,中间空窗期别人用不上。推理服务为了稳,常驻预留一批卡,低峰时也是空转。两套资源池像两口井,水不通。更麻烦的是,训练团队和推理团队各自为政,谁也不想把自己的卡让出去,协调会开成辩论赛。
我们给的方案是训推一体:把训练推理放进同一个资源池,靠调度把空闲和突发错开。但这事说起来轻巧,做起来全是坑,尤其是抢占和隔离,弄不好训练和推理一起塌。
场景围绕统一池展开。训练任务提交后不再独占,而是进入优先级队列,推理的高峰请求可以临时抢占低优先级的训练批次。训练这边要配合做检查点,保证被抢了能从上次进度续上,不丢成果。
推理侧设守护额度,保证核心业务的最低实例数,哪怕训练满负荷也不会把推理挤到零。调度器按实时负载在两者间动态腾挪,闲时训练多占、忙时推理优先。
另外我们做了资源画像,按任务类型打标签,调度时据此分配最合适的卡型和并行度,避免大材小用。同时给每个团队一个可视化面板,能看到自己任务在池里的位置和等待原因,减少"我的卡去哪了"的质疑。
首个难点是抢占不能打断长训练。最早我们直接抢占,结果一个跑了二十小时的任务被推理高峰打断,从头重来,团队当场炸锅。后来强制训练任务周期性打检查点,抢占只发生在检查点边界,最差丢几分钟进度。这一步是项目能否推进的分水岭,没有它根本推不动,我们当时差点在一次事故后 rollback。
另一个难点是推理守护额度的设定。守护设太高,训练没资源;设太低,突发流量顶不住。我们用历史推理流量算峰值,取一个偏保守的守护值,再留一层弹性缓冲给真正的尖峰。这里调参调了不止一轮,因为业务峰值会漂移,每个大促季都得重新看一遍。
再一个难点是优先级定义的公平。不能让推理永远压训练,否则模型迭代停摆。我们按业务等级和任务时效分了三档,训练里接近截止的也升档,避免被无限期挤压。调度逻辑得让两边的人都能接受,否则谁都不信这个池子,私下又去申请独占卡。
第四个难点是抢占后的碎片。频繁抢占会留下碎卡,新任务凑不齐连续显存。我们加了碎片整理和任务装箱,尽量把小需求拼到同一张卡上,并设了碎卡回收的后台任务,闲时自动归并。
案例片段(已脱敏): 训推一体调度与抢占的配置(参数为示意值):
yaml pool: total_gpus: 64 inference_guard_min: 16 elastic_buffer: 4 preempt: enabled: true only_at_checkpoint: true checkpoint_interval_min: 10 priority_tiers: - {name: infer_core, weight: 100} - {name: train_deadline_near, weight: 80} - {name: train_normal, weight: 40}
运行两个月的数据(均为示意值,非审计口径)。GPU 整体利用率从约百分之四十二提升到百分之六十八,闲时不再空转,账单压力肉眼可见地降。训练平均等待时延反而降了,因为池子大了、碎片被盘活,小任务不用排队等整卡。
推理首字延迟在突发时段基本平稳,没再出现被训练挤垮的情况。抢占成功率维持在九成以上,绝大多数抢占发生在检查点边界,几乎不丢进度。团队反馈最明显的是:模型迭代没因为资源共享而变慢,反而因为空闲卡被用起来,实验排队时间短了。
还有个意外收获:两个团队的关系缓和了。以前抢卡是零和博弈,现在看的是同一块面板、同一套规则,谁占多少一目了然,扯皮少了一大半。技术治理有时候也是在治理协作,这点我们以前没料到,算是个意外收获,两个团队从抢卡到看同一块面板,协作成本降得比预期多。
这个项目最贵的教训就是抢占必须配合检查点,否则再好的调度理念也落不了地。我们当时差点在一次长训练被打断后放弃方案,是检查点兜底才救回来,这个经验后来被写进了所有训练任务的接入规范。
守护额度别拍脑袋,要用真实峰值反推,并留弹性。优先级分层要让双方都觉得公平,调度器不是技术物件,是团队信任的载体。碎片整理容易被忽视,但它决定了资源池能不能真的拼出连续算力。
训推一体不是把两个池子合并那么简单,而是把抢占、守护、分层、整理这四件事一起设计,缺一不可。后来一个做多租户 GPU 服务的客户也对这思路感兴趣,我们把它搬过去时,核心逻辑基本能直接用,只是守护额度与优先级分层的比例要按他家的业务节奏重调,调完跑得一样稳。