现在把大模型训练和推理真正跑进生产环境的人,应该都有一种很直观的感受:数据的活好干,算力的活难干。AllData 这类数据中台把元数据、数据同步、数据质量、数据服务都管得井井有条,但到了 GPU/CPU/内存/磁盘这些异构算力资源这一层,绝大多数团队还在靠"问一圈、盯一下、抢一抢"的方式过日子。我做了一次集成尝试,在 AllData 数据中台里接入了开源项目 Crater,目的就一个:把训练和推理场景下的算力资源当作一套统一资产来调度,少做点人肉运维,多留点时间做正事。
这篇文章不打算讲太多概念,重点是把为什么这样设计、实际怎么集成、踩了哪些坑、最后效果如何讲透。适合正在做 AI 平台建设、或者准备在数据中台里接入算力管理能力的团队参考。
1. 数据中台与算力平台之间的那堵墙:为什么AllData需要Crater
先说背景。AllData 本身是一套开源的一站式数据中台,覆盖数据集成、数据开发、数据治理、数据服务这些能力。我们内部用了一段时间之后,发现一个很尴尬的断层:数据链路已经自动化了,但模型训练和推理依赖的算力资源仍然是纯手工管理。数据开发在中台上提数、调度、监控都顺滑,算法工程师却在另一个世界里"抢机器、抢显存、抢磁盘空间"。
1.1 数据侧很成熟,算力侧却在"各玩各的"
最典型的状态就是:数据开发用中台界面跑数,算法工程师用 SSH 登到 GPU 服务器上敲命令,两边各有一套信息体系。中台上能看到数据血缘、任务依赖、质量报告,却看不到任何一台 GPU 服务器的实时状态;算力侧只有少数几个"消息灵通"的同事知道哪台机器还剩多少显存、哪个节点磁盘又要满了。资源信息完全靠口口相传,分配情况没有任何记录,更谈不上审计和复盘。
这种割裂带来的问题在故障时尤其明显。比如某个推理服务突然 F 了,第一反应不是看调度日志,而是到处问"谁在用那台卡"。人找人、找机器、找责任人,十分钟能定位的问题可能要折腾半小时。数据中台本身有非常成熟的元数据管理思路,完全可以借用同一套方法论去管理算力资源,只是缺少一个能纳管异构设备的执行层。
crater 的定位正好补齐这一层。它负责把分散在不同物理节点上的 GPU、CPU、内存、磁盘统一上报、统一分配、统一回收,让资源信息像数据资产一样"看得见、管得住"。上层业务不需要知道具体跑在哪台机器上,只要提交资源申请,由调度器负责安排。这也符合数据中台一贯的抽象思路:底层细节屏蔽掉,把能力以服务的方式暴露出来。
1.2 "训练用一台、推理用一台"的资源割裂浪费
在没有统一调度之前,很多团队为了防止训练任务影响线上推理,会选择物理隔离:训练任务用一组机器,推理服务用另一组机器。这种做法看起来安全,实际上资源利用率非常糟糕。
训练任务的特点是重负载、长时间、夜间为主,白天大部分时候模型在等待数据、等待调参;推理服务则相反,白天的业务高峰才是它的主战场,夜间请求量大幅下降。两边各占一摊,就意味着夜晚训练靠那组机器硬扛,白天空闲也没法借给推理;白天推理那组机器高负荷,夜间又全部闲着。资源池被人为切成了两半,任何一侧出现瞬时的流量高峰,另一侧的闲置资源也帮不上忙。更别说不同团队的 GPU 型号还不同,有的机器显存大、有的机器 CPU 强,没有统一调度时很难按需匹配。
我称这个问题为"算力孤岛"。AllData 已经把数据资产统一了起来,算力资产没有理由继续隔离。Crater 的引入彻底改变了这个局面:训练和推理任务提交到同一个资源池,由调度器根据任务类型、优先级、资源需求动态分配。高峰期可以把资源倾斜给推理,低峰期再把卡让给训练跑 batch 任务,一套池子两套场景,这才是我理解的"训推一体化"。
2. Crater为异构算力管理带来了什么:一套"资源底座"
Crater 不是一个单一模块,而是一套完整的异构算力资源管理框架。它做的事情可以从两个层面理解:一个是资源抽象层,解决"有多少资源、怎么描述资源"的问题;另一个是调度执行层,解决"任务来了放哪里、怎么放最合理"的问题。这两层配合起来,平台才有能力对上提供算力服务。
2.1 异构资源统一抽象
Crater 在每个物理节点上部署一个轻量代理,代理负责采集节点上的 GPU 型号、显存总量与已用量、GPU 利用率、CPU 核数、内存容量、磁盘容量和 IO 指标,然后统一上报给中心调度服务。所有节点的资源信息经过标准化之后,形成一套统一的资源描述符。上层应用看到的不是一张"机器清单",而是一个可动态分割的算力资源池,你只需要声明"我要多少 G 显存、多少个 CPU 核、多少内存",调度器会从池子里挑出最合适的分片。
这个抽象思路非常重要。举一个不一定很恰当但很好理解的例子:以前申请机器就像租一套整租的房子,不管一个人住还是五个人住,整套都归你;Crater 的资源抽象则把这套房拆成房间、工位、公共区域,按实际占用分配。对数据中台侧来说,资源不再绑定到某一台物理机,而是变成一种可以动态发放和回收的配额。这也让后续做多租户管理、成本核算、任务隔离有了唯一的数据来源。
2.2 GPU/CPU/内存/磁盘的细粒度调度
早期很多内部平台调度粒度很粗,要么按整卡分配,要么按整机分配。大模型场景下这种粗粒度非常浪费,尤其是推理场景,一个 7B 参数的模型量化之后可能只需要十几 G 显存,按整卡分就白白浪费了剩余部分。Crater 支持细粒度的多维资源匹配,显存可以按 GB 申请,CPU 按核数申请,内存和磁盘按容量申请,调度器在多维约束下做组合匹配。
实际的调度匹配逻辑不复杂,核心是这么几步:任务提交时会带一份资源需求描述,包括显存大小、CPU 核数、内存、磁盘、是否必须使用某种 GPU 型号、是否独占节点等等;调度器先筛选出所有满足硬性条件的节点,再通过打分函数给每个候选节点排序;打分时会考虑节点剩余资源的碎片程度、是否已经有同样的模型权重缓存、当前节点的负载热点、网络拓扑位置等因素,最终选出得分最高的放置方案。
我对比过两种落地方式:一种是所有 GPU 机器都纳入自建的资源调度系统,另一种是用 Kubernetes 做底层编排,上层再封装一层调度。最终选了基于类似 Crater 思路独立出来的调度服务,原因是它把配额、调度、资源上报、分配记录都做完整了,团队不需要从零维护一套复杂的调度器,只需要把业务层和它对接起来即可。这个决策在后续开发中确实省了大量工时,因为我们没有陷入调度算法本身的深坑,而是把精力花在了业务适配和数据打通上。
3. 在AllData中落地Crater:我实际完成的集成路径
接入这件事听起来简单,实际做起来要拆成好几步。我按项目推进的时间线,把关键环节和决策点交代一下。
3.1 从资源接入到统一门户
第一步是把所有可用的异构算力节点纳入 Crater 的纳管范围。这一步没有太多技术难度,主要工作是给每台机器安装代理组件、配置上报地址、确认采集指标能够被中心服务识别。真正花时间的反而是资源信息梳理:团队里很多 GPU 服务器是过去半年陆续采购的,型号杂、驱动版本不同、显存大小参差不齐。如果这些信息不整理清楚,后续调度非常容易因为资源标签不完整导致任务无法匹配。
资源接入之后,我在 AllData 数据中台的服务层封装了一个"算力资源门户"。这个门户做了三件事:一是展示全集群的资源总量和实时使用率,GPU/CPU/内存/磁盘分维度看;二是提供资源申请入口,使用者提交规格需求,系统自动生成工单并提交给调度器;三是展示历史分配记录和任务资源拓扑,方便追溯"这个任务到底用了哪些资源"。这套门户的价值不在于多炫,而在于所有操作都有记录,不再是口头化的"借一下卡"。
实际开发时,门户和后端服务的交互协议上我做了两个决定。第一,任务规格使用统一的资源描述 JSON,结构类似:
{ "task_id": "train_20240716_001", "task_type": "training", "priority": "high", "resources": { "gpu": {"count": 4, "memory_gb": 80, "model": "A100"}, "cpu": {"cores": 32}, "memory": {"size_gb": 256}, "disk": {"size_gb": 200} }, "duration": "01:00:00", "image": "registry.internal/deeplearning/pytorch:2.1-cu12.1" }字段含义很直白,tack_type 用来区分训练还是推理,priority 决定是否参与排队和抢占,resources 声明硬性需求。第二个决定是调度器只认这套声明式描述,不关心任务的具体内容。这让 Crater 侧保持完全通用,后续扩展任何新框架都不需要改动调度逻辑。
3.2 调度器的核心工作流程
调度器的工作流程是典型的"校验-匹配-绑定-发放"四步。任务提交后先做配额校验,检查这个租户当前已分配的资源加上本次申请是否超出配额上限;如果超了就直接排队,并给出预计可调度时间。配额校验通过后进入节点匹配,这个过程会读取所有在线节点的最新状态,筛选出满足资源条件且标签匹配的节点列表。
节点筛选完成以后,调度器按打分策略对候选节点排序。我这边打分的权重主要看三点:节点剩余资源与申请资源的拟合度,拟合度越高分越高,避免大材小用;节点是否已有相同模型权重缓存,有缓存的任务优先调度过去,能省掉大量加载时间;节点的实时负载,避开 CPU 已经跑满或磁盘 IO 接近瓶颈的机器。最终得分最高的节点被选中,调度器生成一条资源分配记录,并通知节点代理执行环境准备。
环境准备这一步很多人会忽略,但实际很容易出问题。节点代理需要在分配好的资源分片上完成环境隔离,比如用容器或虚拟化方式把 GPU 显存、CPU 核、内存隔离出来,同时挂载训练数据和模型权重目录。我最初踩的坑就是环境准备没有做超时控制,模型镜像下载慢的时候任务一直处于"准备中"状态,调度器无法区分是正常下载还是卡死。后来在分配记录里加了一个状态机,把"Preparing、Ready、Running、Finished、Failed"几个状态在数据库里全部落地,配合超时阈值,才算真正搞清楚每个任务卡在哪个环节。
3.3 适配层与接口设计
集成过程中我花时间最多的地方,其实是 AllData 数据中台与 Crater 之间的适配层。当时有两个方案可以选择:一个是在中台里直接调用 Crater 的调度接口,逻辑上最直接,但中台业务代码会和调度器强耦合;另一个是在中间加一个适配层,中台只依赖适配层提供的接口,由适配层负责翻译和转换。
我选了后者。理由很简单:数据中台的迭代频率远高于算力调度平台,如果两边直接耦合,任何一方的接口变更都可能引发连锁故障。适配层做成独立服务之后,中台侧只需要关心任务提交和状态查询,其他事情全部由适配层兜住。
适配层的主要接口我设计成五个:资源申请、资源释放、任务状态查询、资源使用率查询、配额变更。所有接口返回统一格式,业务侧不用感知调度器的内部状态流转。实际运行下来,这个设计的好处很快体现出来。有一次 Crater 升级调度接口,我们只需要在适配层做兼容,AllData 本身的业务代码一行没动。如果当时直接硬编码调用,估计又要经历一次半夜紧急修复。
4. 训推一体化的关键机制:一套池子,两套场景
"训推一体化"这个词很多文章都在提,但落到实际平台里,核心问题无非是两个:训练任务和推理服务怎么共享同一套资源而不互相干扰?模型版本更新时,算力资源怎么平滑切换?
4.1 训练任务与推理服务的调度冲突与解耦
共享资源池必然遇到一个矛盾:训练任务对资源的需求是"大而稳定",推理服务对资源的需求是"小而频繁"。如果一视同仁地调度,训练任务很可能长时间占住大量 GPU,推理服务只能在一旁饿肚子。Crater 的解决思路不是物理隔离,而是通过优先级和可抢占机制来协调。
实际配置里,我把推理服务设为高优先级、低超卖倍率,保障在线请求不受影响;训练任务设置为中低优先级,允许在资源不足时排队。同时训练任务本身做了 checkpoint 机制,每次调度重新开始时可以从最近一个检查点继续,而不是从头跑。这样即便训练任务被推理高峰期挤掉,代价也只是回退几十分钟,而不是浪费一整天的训练进度。
除了优先级,我还限制了推理服务的单任务资源上限。比如一个在线推理服务最多申请一张卡的一半显存,一旦超过这个阈值就必须横向扩容而不是纵向加资源。这样设计是为了避免某个推理服务异常时把整个节点的显存吃满,拖垮同节点的其他任务。细粒度配额+优先级抢占,这两条规则加在一起,才真正实现了"池子共用、场景解耦"。
4.2 模型版本切换与资源热迁移
集成前我们上线新模型版本的方式比较原始:新模型起一套新服务,流量切过来之后老服务再删。这个流程在主机数量少的时候还能忍受,模型多了以后非常浪费。训练 side 每两天出一个更好精度的版本,每次都要重新申请资源、拉起服务、切流量、释放旧资源,一套流程走下来至少半小时,期间还容易出错。
接入 Crater 之后,模型版本切换变得非常轻量。新版本模型的权重文件先同步到共享存储,调度器只需要在已分配的资源分片上更新容器镜像和权重加载路径,业务服务通过内部探针感知到新版本就绪后自动做平滑切换。整个过程不需要额外申请新资源,也不需要释放旧资源,资源和模型之间彻底解耦。
这套机制在推理服务频繁更新的场景下尤其好用。配合共享存储上的多版本权重管理,我们可以实现一天发布五个版本而资源零额外消耗。发布动作变成了"更新资源分片上的模型版本标签+重启推理进程",而不是"重新分配一台新机器"。这个体验上的差异,日常维护过推理集群的工程师一定懂。
5. 实操中的难点排查与避坑经验
集成过程中踩了不少坑,挑三个最有代表性的写出来,给后来者省点时间。
5.1 显存分配不均匀导致的利用率黑洞
刚上线时遇到一个非常反直觉的现象:集群显存总量看起来还有富余,但新任务一直调度不上去。排查发现,问题出在显存碎片。早期版本的调度策略是优先填满节点——第一个任务申请 40G,第二个任务申请 40G,第三个任务申请 10G,看起来每台机器都分配得很满,但剩余的都是碎片化的小段显存,无法满足一个需要大显存的新任务。
定位问题的过程很有意思。一开始我盯着调度器日志看,没发现任何异常,所有节点状态都正常,任务就是匹配不到合适节点。后来把每个节点的剩余显存分布拉出来看,才发现大量"剩余 2G、4G"这类零头。这个坑教会我一件事:调度不能只看总量,一定要看连续可用量。后来我在打分策略里加入了一个"碎片惩罚因子",优先把新任务调度到剩余显存最连续、与申请值最匹配的节点,这个现象才基本消失。
5.2 大数据量模型的磁盘带宽瓶颈
第二个坑更隐蔽。某个推理任务在调度完成后,从节点进入到模型可响应之间花了将近十分钟,远超预期。刚开始以为是网络传输慢,排查网络后发现带宽根本没打满。后来查到节点代理的日志,发现时间全部耗在从共享存储读取模型权重文件上。
现在很多大模型的权重文件动辄几十 GB,多个节点同时加载同一个模型时,共享存储的带宽会成为瓶颈。如果调度器不加干预,模型更新后所有节点一窝蜂地去拉新权重,磁盘 IO 直接被打满,任务互相拖慢。我的解决办法是两层:第一,在共享存储前加一层本地磁盘缓存,节点首次加载后将权重文件缓存到本地;第二,调度器对"同模型、同时间点"的加载请求做错峰处理,在任务状态机里增加一个"依赖资源预热"阶段,模型文件在后台预热完成后再正式拉起推理进程。
5.3 多租户配额与资源抢占的边界
第三个问题是多租户场景下最常见的——有人申请了资源却不用。某个团队为了确保训练任务能随时跑,一次性申请了大量 GPU 配额并长期持有,但实际使用率不到三成。其他团队的任务因为配额不足只能排队,资源就这么白白浪费了。
这个问题的本质是配额管理和实际使用率脱节。Crater 的调度器负责分配的合理性,但"分配了到底用没用"需要平台侧自己监测。我在 AllData 中台做了个定时巡检任务,定期比对每个租户的已分配资源和实际使用率,超过一定阈值就自动回收空闲资源,同时发通知给资源持有者。另外还设计了一个"空闲资源临时借用"机制:任务可以在配额不足时借用空闲资源池,但被借用资源一旦有更高优先级任务申请,立即释放。
这套机制上线后,集群整体利用率提高了大概三成,而且没有引发任何跨团队冲突。关键在于规则要提前透明化:所有租户在同一个使用规范下申请、使用、释放资源,遇到抢占时有明确的优先级界定,而不是等冲突发生了再互相扯皮。
6. 我的经验与几个可复用的小技巧
接入 Crater 这几个月,我最大的体会是:算力调度平台不是做不出来,而是很多团队低估了"资源管理"这件事本身的工作量。GPU 纳管、调度算法、配额控制都只是底座,真正让平台跑起来、让业务愿意用的是那些细节:资源信息可视、分配记录可查、配额规则透明、失败状态可追踪。
如果你也在做类似的集成,有几个小技巧可以直接拿去用。第一,调度器落实"声明式资源描述",任务只告诉平台要什么,不告诉平台怎么跑,这会让上层业务保持最大的灵活性。第二,资源分配记录一定要落库,哪怕只是一个简单的任务资源映射表,排查故障时能节省大量时间。第三,模型权重缓存和存储带宽规划要提前做,不要等到几十 G 的权重并发加载时才想起来。第四,多租户配额一定要配上使用率巡检,否则资源会被"占而不用"的团队囤积,迟早引发矛盾。
我个人的习惯是每两周复盘一次资源利用率曲线,把高峰、低峰和调度器决策记录对照着看。很多问题在曲线上早就有苗头,只是当时没注意。这套 AllData + Crater 的架构上线之后,我这边接到的问题从"谁能帮我看看机器"变成了"这个任务怎么调度更合理",方向对了。后续还可以做的是把算力资源成本分账数据也接入中台,让每个团队能看到自己用了多少算力、产生了多少成本,这一步走完,平台的闭环就真正完整了。