做游戏AI的都知道,算力永远是绕不开的话题。我们团队在腾讯超算中心维护一套支撑游戏AI的GPU资源调度平台,每天几千个任务跑在上面,有大规模的强化学习训练,也有在线推理的实时请求,还有各种数据预处理和模型评测任务。作为架构师,我花了大半年时间打磨这套AI资源调度系统,踩了无数坑,也沉淀了不少方法论。这篇博文不聊虚的,就把调度系统的设计思路、实操细节、常见故障排查,以及我个人的架构师成长经验,一次性讲透。
1. 项目背景与场景定位:游戏AI为什么需要专门的算力调度
1.1 游戏AI工作负载的典型画像
先说结论:游戏AI的算力需求,跟互联网常规的微服务完全不同。微服务看重QPS、P99时延,而游戏AI,尤其是强化学习类的任务,是典型的高吞吐、长时驻、潮汐式的算力消耗。
我把它拆成四类核心负载:
-
训练任务:大规模强化学习(RL)、模仿学习、生成式模型(如游戏关卡生成、NPC对话)的训练过程。这类任务通常是长时驻的,一个训练任务跑几天到几周很常见。资源需求大,动不动就要占用几十甚至上百张GPU卡。而且为了加速,往往需要多机多卡并行,对网络拓扑很敏感。
-
推理任务:游戏内的实时AI,比如Boss行为决策、NPC对话、队友Bot,还有游戏AI自动播(自动对战演示)这类场景。推理任务对时延要求极高,通常需要在几十毫秒内返回结果。它的特点是单次请求算力消耗小,但请求量巨大,峰值波动剧烈,属于典型的在线服务。
-
数据与仿真任务:强化学习需要大量的环境交互数据,游戏AI尤其依赖仿真环境。比如《王者荣耀》类MOBA游戏的AI训练,需要模拟海量的对局环境,每个样本都是一次完整的游戏模拟。这类任务算力消耗不小,而且大部分可以断点续跑,属于可弹性伸缩的离线批处理任务。
-
开发调试与评测任务:算法工程师在写代码、调参时,需要小规模验证;模型训练完需要跑评测集、做A/B测试。这类任务是短时、零散的,但很频繁,对调度器的响应速度要求高。
| 负载类型 | GPU需求特征 | 时延要求 | 弹性特征 | 典型占比 |
|---|---|---|---|---|
| 训练任务 | 多卡、大显存、长驻 | 不敏感 | 弱,重启成本高 | 40% |
| 推理任务 | 单卡、中显存、高并发 | 极敏感 | 强,需快速扩缩 | 30% |
| 数据仿真 | 中量、高吞吐 | 不敏感 | 强,可弹性 | 20% |
| 开发调试 | 零星单卡 | 中等 | 强,短时 | 10% |
这四类负载混在同一个集群里,如果没有一套好的AI资源调度系统,后果就是灾难性的:训练任务把GPU占满,推理任务排队等卡,QPS一上来就直接超时;或者推理任务为了稳定预留了大量资源,结果训练任务没卡可用,整个集群利用率低得可怜。
1.2 算力规模与业务目标
我们平台管理的规模,大致是:训练集群3000+张GPU卡,推理集群500+张卡。集群物理上分布在同一个超算中心,用高速RDMA网络互连,每个节点8张A100或H800。
架构师在这个项目里要解决的核心问题,说白了就三个:
-
利用率:GPU是成本大头,一张A100的价格摆在那儿,利用率上不去就是纯烧钱。我们的目标是整体利用率从60%拉到85%以上。
-
排队效率:训练任务从提交到真正跑起来的时间,要尽量短。以前没有调度系统时,算法同学手动找卡、临时要资源,一个任务排队几小时很常见。我们的目标是让任务在5分钟内完成调度上卡。
-
SLA保障:推理任务不能因为资源被抢就超时,训练任务的优先级也要有区分。高优任务要能吃到资源,低优任务不能把集群塞死。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 调度系统整体设计与技术选型
2.1 分层调度架构:为什么不用裸K8s
最开始有个很自然的想法:直接用Kubernetes,加个GPU插件,再装个Volcano,是不是就完事了?调研之后发现,这个方案在小规模集群(比如几十张卡)没问题,但到了几千张卡的规模,问题就很多:
- 调度器性能瓶颈:K8s原生的调度器调度一个Pod要几秒钟,几千个任务排队的时候,调度效率跟不上。
- 对GPU拓扑不感知:K8s不知道哪几张卡在同一个NVLink域里,8卡训练任务可能被调度到物理上跨节点的卡上,通信性能直接崩。
- 优先级抢占能力弱:原生的Pod优先级、抢占机制,在长时驻训练任务面前不够用,容易把集群搞得很碎片化。
- 分时调度、显存共享这些能力,需要大量二次开发。
所以,我们的整体设计采用了轻量级的两层调度模型,这是借鉴Mesos思想后结合业务做的改良。
第一层是全局资源管理器(RM)。它管理整集群的GPU资源视图,维护每张卡的状态(空闲、占用、预留),对外提供API接收任务提交,并按照队列、配额、优先级进行全局调度决策。
第二层是节点级执行器(Agent)。每个计算节点上部署一个Agent,负责接收RM下发的指令,在本地执行容器的启动、停止、资源隔离,并回报节点的实时状态。
两层之间通过消息队列异步通信。这样设计的核心好处是:全局调度只做"决策",不直接操作容器,避免了大集群下的一致性开销;节点级执行器可以独立升级,不需要全局重启。这个结构跟实际项目中的一致性、扩展性需求是对应的。
2.2 核心模块拆解:队列、优先级与配额
调度系统的核心,可以理解为三个模块的合力:
队列管理:把整个集群资源按照业务线拆分。比如MOBA游戏AI团队一个队列,吃鸡AI团队一个队列,平台公共队列一个队列。每个队列有配额(比如MOBA队列最多占集群50%的资源),防止某一个团队的任务把整个集群占满。队列还分规格,比如训练队列、推理队列、debug队列,不同规格对应的调度策略不同。
优先级:同一队列内,任务可以有优先级。比如线上紧急的bug修复训练任务,优先级高于日常的实验任务。调度器在做资源分配时,严格按优先级从高到低分配,高优任务可以抢占低优任务的资源。
配额与弹性:配额是硬上限,但配额不是静态的。空闲的配额可以被其他队列借用,这就是"弹性"的体现。比如MOBA团队晚上没有任务,那它们队列的空闲资源可以被其他团队的训练任务临时借用,一旦MOBA有新任务提交,借用资源的任务会被挂起或迁移。
这三个模块组合起来,才算把"多团队共享一个超算集群"这件事跑起来。不然各个团队各自建集群,那成本就爆炸了。
2.3 技术选型的取舍方案
当时对比了三个方案,我最后的选择如下:
| 方案 | 优点 | 缺点 | 结论 |
|---|---|---|---|
| 原生K8s + Volcano | 生态好、社区活跃、上手快 | 大规模调度性能瓶颈、二次开发成本高 | 适合300卡以下的中小型集群 |
| 自研调度器(最终方案) | 完全可控、深度适配业务 | 开发周期长、需要专业团队 | 适合我们几千卡的超算中心 |
| Slurm | 经典HPC调度器,稳定老练 | 容器支持弱、微服务化程度低 | 用于传统科学计算,游戏AI不太合适 |
说实话,如果团队规模不大,我建议直接用K8s+Volcano起步,把业务验证通了再考虑自研。我们是因为确实到了几千卡的规模,才决定投入资源自研调度器。
提示:自研调度器这件事,架构师一定要把"ROI"想清楚。如果集群规模1000卡以下,大概率K8s生态是最好的选择;超过2000卡,且业务场景复杂,才值得考虑自研。盲目自研是架构师最容易犯的毛病之一。
3. 关键机制实现:从任务排队到GPU共享
3.1 GPU资源抽象与共享方案
游戏AI集群里,GPU卡是最核心的资源。但"一张卡"不是一个绝对的单位,怎么切分、怎么共享,直接决定了调度系统的灵活性。
我们的做法是提供两级GPU资源抽象:
-
物理卡独占:一个任务独占一整张或多张物理卡,适用于训练任务。独占模式的优势是性能稳定,没有干扰,坏处是浪费。比如一个推理任务只需要2GB显存,却独占了一张40GB显存的A100,那38GB就浪费了。
-
GPU共享与切分:我们实现了显存粒度的切分和算力粒度的切分。显存切分就是把一张卡的显存分成几份,比如A100的40GB,可以切成2份20GB,分别给两个推理任务用。算力切分则是限制任务对GPU计算单元的使用比例,比如限制任务最多用50%的SM。
具体到调度,我们会根据任务的GPU请求规格去做匹配。一个推理任务声明需要gpu_mem=8GB, gpu_mem_percent=10%,调度器就会在有空闲资源的物理卡上分配一个共享切片。下面是一个调度请求的简化JSON示例:
json复制{
"task_id": "task_20240915_001",
"job_type": "inference",
"queue": "game_ai_infer",
"priority": 50,
"resource_requests": {
"gpu_count": 1,
"gpu_model": "A100",
"gpu_mem_per_card": "8GB",
"gpu_cal_percent": 10,
"cpu_cores": 4,
"memory": "16GB"
},
"scheduling_policy": {
"shareable": true,
"preemptible": false
}
}
3.2 拓扑感知与亲和性调度
游戏AI的强化学习训练,大多是多卡并行的,而且通信模式分两类:一类是AllReduce通信,比如PPO算法更新策略网络;另一类是数据并行时的梯度同步。不管是哪一类,对GPU之间的通信带宽都极其敏感。
张卡互联时,同一节点内用NVLink,带宽600GB/s没问题;跨节点用RDMA网络,也要尽量保证都在同一个交换域内,减少跨交换机的跳数。调度器如果不感知这些,把8卡任务拆到4个节点上,每个节点2张卡,那训练效率可能直接掉一半。
我们的实现思路是在调度器内部维护一个拓扑信息模型:
- 每个节点维护一张GPU互连矩阵,记录卡与卡之间的带宽和连接类型(NVLink/PCIe/RDMA)。
- 调度器在做多卡任务分配时,优先在同一个拓扑域内分配。比如8卡任务,优先找一台8卡全部空闲的节点;找不到,就找两台各自有4张卡互连良好的节点。
- 即使是共享GPU的任务,也要尽量把同一批任务的切片放在同一节点,减少跨节点通信。
这个机制上线后,8卡任务的训练吞吐提升了大概30%。这里补充一个关键点:拓扑感知调度要尽量做成启发式的,不能做成精确的线性规划求解,否则几千卡下求解太慢,调度性能扛不住。我们使用的是一种贪心启发式,结合历史的成功率经验,在效果和性能之间取了平衡。
3.3 优先级、抢占与资源配额
有了队列、有了拓扑,最后就是抢占机制。这是很多调度系统的痛点,处理不好就会出现"饿死"、"死锁"、资源碎片化。
我们的设计是:
- 优先级分为10个等级,1最低,10最高。高优任务可以抢占低优任务,但低优任务不能反向抢占。
- 抢占分两种:预占用(preempt) 和 驱逐(evict)。预占用是指高优任务提交后,调度器判定需要腾位置,主动将低优任务挂起,把资源让给高优任务;驱逐是指直接杀掉低优任务。开发调试类任务被驱逐的概率最大,但训练任务很少被驱逐,因为重启成本太高。
- 防饿死设计:每个队列内部的资源分配采用带权轮询,保证即使队列内部有低优先级的任务,也不会被完全饿死。比如30分钟内如果低优任务没有拿到任何资源,系统会自动标记为"饥饿",调度器临时提升其优先级,确保它能运行起来。
- 超卖与回收:共享GPU切片支持超卖,比如一张40GB的卡,可以按显存需求分配给50GB的任务请求(超卖率20%~30%),因为实际运行中这些任务不会同时打满显存。但超卖必须配合实时监控,一旦节点内存水位过高,就要触发回收策略,把低优任务的共享切片挂起或迁移。
这条机制是调度系统的"最后一公里"。没有抢占,谈调度就是在耍流氓。但抢占也不能乱来,防饿死、超卖回收这些配套机制必须跟上。
4. 实操复盘:一次游戏AI训练任务的调度优化记录
4.1 场景:大模型强化学习训练任务
以我们最近支持的一个MOBA游戏AI升级训练为例。这个任务的目标是让AI学会更复杂的团队配合策略,模型规模大,需要大规模并行采样和训练。
先看资源预估。我们给的配置是:训练任务使用128张A100显卡,运行时间预计3天;需要支持高频的模型参数同步,所以要求128张卡尽量分布在不超过16个节点内(每个节点8卡),且节点之间网络延迟要低;训练数据实时产生,需要大内存支撑数据缓冲;同时训练过程要稳定,需要备份几个快照,防止训练中断。
按照这个需求,调度器需要做三件事:确认集群有没有128张卡的空闲资源;找到16个符合拓扑要求的节点;确保这128张卡属于同一个租户队列且未被其他任务抢占。
4.2 调度策略配置与实施
具体的调度策略配置是这样的:
- 队列选择:训练任务提交到
game_ai_train队列,这个队列的配额上限是集群总资源的50%,当前集群利用率82%,其中该队列实际占用30%,有足够配额。 - 优先级设置:该任务设置为优先级8(高优先级),因为这是正式训练,不是实验。
- 拓扑限制:在任务描述中,声明
node_group_id=topo_16nodes_a100,调度器会优先选择满足该拓扑的节点组合。 - GPU共享设置:训练任务不共享,申请整卡资源,
shareable=false。
调度器执行流程大致是:
- 接收任务请求,校验配额和优先级。
- 扫描集群空闲资源,筛选出满足128卡的候选节点集合。
- 在候选集合内,按拓扑域距离排序,优先选择同一机架、同一交换域下的节点。
- 预分配资源,向节点Agent下发启动指令。
- Agent启动容器,挂载GPU,安装配置,完成后回报状态。
- 调度器将任务状态更新为"运行中"。
整个调度耗时约3秒,从任务提交到真正开始训练,大概40秒(包含镜像拉取和数据缓存)。
4.3 效果与优化迭代
这个任务上线后的效果:
| 指标 | 优化前(无调度系统) | 优化后 |
|---|---|---|
| 任务排队时间 | 2小时~1天 | 3分钟 |
| GPU集群整体利用率 | 62% | 88% |
| 训练任务平均完成时长 | 4.2天 | 3.1天 |
排队时间大幅缩短,直接原因是调度器能做到在秒级扫描几千卡的资源视图并快速决策。利用率上升的收益来自两个方面:一是共享GPU让推理任务把空闲算力用起来了,二是超卖机制把显存水分挤掉了。
不过这里也有个教训:第一次把调度器上到全量集群时,出现过一次"调度风暴"。原因是某个团队一次性提交了1000多个推理任务,调度器的决策队列瞬间积压,导致所有任务一起排队。后来我们加了一个核心改造:调度请求缓冲池 + 限流机制,单队列每秒最多处理50个调度请求,超过的请求进入缓冲区,避免系统被突发流量击垮。
5. 踩坑记录与疑难排查
5.1 常见故障与排查方法
调度的坑,很多不是一眼能看出来的。我整理了最典型的几类:
| 问题现象 | 根本原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 任务一直处于"待调度"状态 | 调度器死锁:高优任务等待低优任务释放,而低优任务又因为配额不足无法部署 | 查看调度事件队列,检查是否有互相等待的环状依赖 | 增加超时中断机制,10分钟未调度成功的任务自动挂起队列,重新排队 |
| GPU利用率不高,但任务在排队 | 资源碎片化:大量显存被切分成小块,无法满足整卡任务的申请 | 用资源拓扑图查看每张卡的分配状态,看碎片率 | 增加"资源整理"机制,定期将共享切片任务迁移腾挪出整卡空间 |
| 某节点GPU温度过高 | 共享GPU任务的算力限制没有生效,多个任务同时打满SM | 查看节点Agent的算力监控曲线 | 修复算力切分模块的cgroup配置,限制单任务SM利用率 |
| 几个训练任务互相影响,吞吐下降 | 网络拓扑感知没有生效,任务被分配到跨交换机的高延迟路径 | 查看调度器日志,确认任务实际部署的节点拓扑 | 强化拓扑约束条件,对不满足条件的节点组合直接排除 |
5.2 框架层与调度层配合的技巧
调度器不是万能的。很多时候,调度问题需要框架层和调度层协同解决,才能真正跑顺。
维度一:断点续训与秒级恢复。游戏AI训练跑三天,中间如果因为优先级抢占或节点故障中断,那代价太高了。我们的做法是,在与NCCL和代码框架的配合上,训练框架周期性保存checkpoint(比如每10分钟一次),调度器在资源紧张时优先挂起可以快速恢复的任务,而不是直接杀掉。恢复时,调度器会保留上一次的节点和网络分配,确保断点续训的高效性。
维度二:显存OOM的处理。共享切片的任务最容易OOM。如果某个共享任务的显存使用超过了分配量,调度器不会立即杀掉它,而是尝试给它一个"显存扩容"时间,如果节点还有剩余显存,就动态调整切分;如果没有,才触发驱逐。这个过程里面,框架层的OOM报错信息要能及时反馈到调度器,形成联动。
维度三:模型并行与数据并行的资源请求差异。模型并行任务要求GPU之间高带宽,这需要调度器保证在同一节点或同一NVLink域;数据并行任务跨节点通信要求相对宽松。所以调度器要能够读取任务声明的并行模式,不同模式不同的拓扑绑定期望。
5.3 可观测性设计
最后一条踩坑经验:调度系统的可观测性,一定要从一开始就设计进去,而不是等出问题才补。
我们给调度器增加了三层可观测性:
-
Metrics监控:调度队列长度、调度耗时、任务启动耗时、GPU利用率、抢占次数、驱逐次数等关键指标,全部打入Prometheus,配置了告警规则。比如"调度耗时超过10秒"、"驱逐次数超过100次/小时"都会触发告警。
-
事件追踪:每个任务从提交到结束的全生命周期,会在调度器中产生一条完整的状态轨迹,包括:提交->排队->预分配->启动->运行->完成/失败,每一步都有时间戳和原因。出问题时,直接查任务的完整轨迹,不用猜。
-
审计日志:谁提交了什么任务、谁调整了队列配额、谁手动触发了驱逐,全部记录到审计日志,留存90天。有一次线上问题,就是因为某位同事临时把队列配额调大了3倍,导致了资源争抢,审计日志帮我们快速定位了责任人。
注意:可观测性不是监控面板做得好看就完了,关键是要把"调度决策路径"记录下来。比如调度器为什么选择了节点A而不是节点B,这个决策原因如果不记录,后面很难排查调度不均衡的问题。我们后来给每个调度行为加了一个
reason字段,记录决策依据(如"满足拓扑约束"、"优先级最高"等),排查效率提升了一大截。
6. 从系统设计到个人成长:架构师如何从项目中沉淀能力
6.1 这个调度项目锻炼的核心能力
很多同学问过我,架构师到底在调度的项目里做什么?我的体会有三个核心能力的锤炼。
第一是需求分析能力。接到"支持游戏AI应用"这个需求时,不能上来就是"我要设计一个调度系统"。要先分析游戏AI各类业务的负载特征,跟算法团队深聊,把"训练任务为什么需要128卡""推理任务为什么允许被驱逐但不能被抢占"这些问题一个个摸清楚。需求分析做不好,后面架构再漂亮也是空中楼阁,因为你的架构跟业务实际是脱节的。
第二是架构权衡能力。比如自研调度器 vs 开源方案,当时的取舍标准是集群规模、团队投入、业务确定性。再比如调度算法,用启发式贪心而不是精确最优化。做架构设计一定要在多个维度上权衡,任何"最优解"的说法都是不专业的表现。
第三是容量规划能力。超算中心的AI资源调度,是一个典型的容量管理问题。多少任务会并发提交、每类任务的资源规格分布、未来半年业务增长的预判,这些都要在调度器的配额设计和集群扩容计划中提前安排。容量规划做不好,调度器性能再好,也会因为资源不够而变成"巧妇难为无米之炊"。
6.2 和软考系统架构师的映射
这个话题插进来,是因为我在准备软考系统架构师考试时,发现很多知识点在实际项目中都能找到一一对应的映射,现在反过来看,对整个知识体系的掌握也更深了。这里给也正在备考的同学一些经验。
软考系统架构师的架构风格考点,在我这个项目里就是两层调度架构的应用。单体调度、两层调度、共享状态调度,这些你从教科书上看理解不深,但你真正实现过一套两层调度后,再回头看这些概念,就特别有体感。考试论文写作时,可以把实际项目的技术选型作为案例支撑,用书面的语言去组织表达。
质量属性考点,也是跟调度系统强相关。比如可用性(调度器挂了怎么办,我们的方案是无状态设计,调度器可以随时横向扩容,挂了自动重启)、性能(调度决策耗时控制在100ms以内)、安全性(调度API的鉴权、审计)、可维护性(模块解耦,Agent可独立升级)。这些质量属性的权衡和设计,写进论文里,比空谈理论有说服力得多。
另外,软考的高级架构师论文题目,比如"论系统设计方法"、"论软件架构风格",这些题目都可以拿调度系统作为案例来写,因为调度系统是兼具技术深度和业务复杂度的题材,而且数据、现象、效果都很具体。我在考前把项目整理成了几个论文模板,考试时套用起来非常顺手。
6.3 架构师在AI资源调度领域的成长路线建议
最后分享一下,如果你想往AI基础设施方向深耕,做游戏AI资源调度这类项目,成长路线上我的建议是:
- 先吃透底层技术:GPU架构、NVLink/RDMA网络、容器运行时的GPU隔离原理,这些是最硬的底子。不懂底层,就没有调度层面的发言权。
- 再掌握调度理论:从单机调度到分布式调度,从K8s到Volcano、Yarn、Slurm,多对比,理解各自的设计哲学。不用每个都精通,但要能说出各自的适用边界。
- 多做实战项目:调度器这种系统,理论跟实践差距极大,只有真正在几千卡集群上踩过坑,才知道什么叫做"纸上得来终觉浅"。
- 保持对AI业务的理解:调度器本质是服务业务的,不懂游戏AI的训练流程、推理时延要求、模型迭代节奏,做出来的调度系统就是空中楼阁。经常和算法团队聊,比多读十篇技术文章更有用。
- 沉淀方法论:做完一个项目,一定要把为什么这样做、当时有哪些可选方案、最终怎么取舍写下来。这种复盘出来,才真正变成你自己的架构能力。
答了这么多,还是那句老话:做基础设施的架构师,最怕的是沉迷于技术的精妙,而忘了为什么出发。AI资源调度这件事,最终的目标永远是让业务跑得更好、更省、更稳定。把这句记住,方向就不会偏。
