
8 块 A100 全部占满,16 个推理服务排队等待,3 个微调任务卡在队列里一动不动,还有 2 个数据处理管道已经超时 20 分钟。
你盯着监控面板上一片飘红的 GPU 利用率曲线,陷入了沉思:明明有 8 张卡,为什么还是不够用?
答案很简单:不是卡不够,是调度不够聪明。
这就像一条 8 车道的高速公路,如果所有车都挤在最左侧车道,右边 7 条道空空荡荡,通行效率还不如单车道。
AI 任务调度框架,就是算力高速公路上的"交通指挥官"。
你可能会想:任务调度不是老问题吗?K8s 的 Scheduler、YARN、Mesos,哪个不能调度?
能调度,但调不好 AI 任务。 原因有三:
AI 任务对资源的需求,跨度大得离谱:
任务类型 | 典型时长 | GPU 需求 | 显存需求 | 延迟要求 |
|---|---|---|---|---|
模型训练 | 小时~天 | 1~8 卡 | 40~160GB | 无 |
在线推理 | 毫秒~秒 | 0.5~2 卡 | 8~80GB | <100ms |
模型微调 | 分钟~小时 | 1~4 卡 | 16~80GB | 低 |
数据处理 | 秒~分钟 | 0 卡(CPU) | 2~16GB | 无 |
训练任务要占满 8 卡跑好几天,推理任务只要半张卡但必须秒级响应。
用同一套调度策略处理它们,就像用同一把钥匙开所有的锁。
一个典型的 AI 工作流长这样:
数据采集 → 数据清洗 → 特征工程 → 模型训练 → 模型评估 → 模型注册 → 在线部署
每一步都依赖上一步的输出,中间任何一步失败都要回滚重试。而且有些步骤可以并行(比如多种特征工程方案同时跑),有些必须串行。
这不是简单的"先进先出",而是有向无环图(DAG)的编排问题。
一组数据:企业级 GPU 集群平均利用率仅 5% 左右。
你没看错,95% 的算力在被浪费。一块 A100 8 万块,一个 8 卡节点 64 万,利用率 5% 意味着你花 64 万买了 3.2 万的有效算力。
调度不当,烧的是真金白银。
一个成熟的 AI 任务调度框架,至少需要 5 个核心组件:

调度引擎是整个框架的大脑,负责决定"哪个任务分配给哪个节点"。
它的核心决策逻辑分三层:
第一层:资源匹配
任务声明自己需要什么(2 张 GPU、32GB 显存、NVLink 互联),调度器从节点池中筛选出满足条件的候选节点。
第二层:策略决策
在候选节点中,按策略选最优:
第三层:亲和性优化
让任务尽量跑到"离数据近"的节点上。训练数据在节点 A 的本地 SSD 上,就别把任务调度到节点 B,光传数据就要半小时。
资源管理器是框架的账本,实时记录每个节点的"家底":
关键挑战是 GPU 资源的精细化调度。传统的"整卡分配"太粗放,一块 80GB 的 A100 只跑一个 4GB 的小模型,浪费 95% 显存。
现代框架支持 GPU 虚拟化和 分时复用:
1 # Ray 的 GPU 资源声明
2 @ray.remote(num_gpus=0.5) # 只用半张卡
3 class InferenceModel:
4 def __init__(self, model_path):
5 self.model = load_model(model_path)
6
7 @ray.remote(num_gpus=4) # 用 4 张卡
8 def train_model(config):
9 return distributed_train(config)推理服务用半张卡,训练任务用 4 张卡,同一集群各取所需。
编排器负责定义任务的执行顺序和依赖关系。
主流的编排模型有两种:
DAG 模式(Airflow 的选择)
用有向无环图定义任务依赖,每条边代表"完成 A 才能开始 B":
1 # Airflow DAG 定义
2 with DAG("ai_pipeline", schedule_interval="@daily") as dag:
3 collect = PythonOperator(task_id="collect", ...)
4 clean = PythonOperator(task_id="clean", ...)
5 train = PythonOperator(task_id="train", ...)
6 deploy = PythonOperator(task_id="deploy", ...)
7
8 collect >> clean >> train >> deploy # 串行依赖优点是语义清晰,缺点是表达力有限(不能有循环,不能动态生成子任务)。
Graph 模式(Dify / LangGraph 的选择)
允许循环、分支、动态节点,适合 Agent 场景下的复杂编排:
1 # LangGraph 的条件循环
2 graph.add_node("reason", agent_reason)
3 graph.add_node("act", agent_act)
4 graph.add_conditional_edges(
5 "reason",
6 should_continue,
7 {"continue": "act", "end": END}
8 )
9 graph.add_edge("act", "reason") # 循环:推理→行动→再推理Agent 可能需要"推理-行动-再推理"好几轮才能完成任务,DAG 表达不了这种循环,Graph 才行。
消息队列是框架的邮局,负责任务的分发和结果收集。
核心模式是 生产者-消费者:
1 # Celery 的经典用法
2 @app.task(queue="gpu_training")
3 def train_model(config):
4 model = create_model(config)
5 return model.train()
6
7 @app.task(queue="cpu_pipeline")
8 def process_data(path):
9 return clean_and_featurize(path)不同类型的任务进不同的队列,GPU 队列由 GPU 节点消费,CPU 队列由 CPU 节点消费。
选型参考:
消息中间件 | 吞吐量 | 延迟 | 适用场景 |
|---|---|---|---|
Redis | 万级/秒 | <1ms | 小规模、低延迟 |
RabbitMQ | 万级/秒 | ~ms | 中等规模、可靠投递 |
Kafka | 十万级/秒 | ~10ms | 大规模、高吞吐 |
ZeroMQ | 百万级/秒 | <0.1ms | 极低延迟、定制场景 |
监控是框架的眼睛,容错是框架的保险。
一个生产级的调度框架必须做到:
实时监控:任务状态、资源利用率、队列深度、延迟分布,全部可视化。
超时熔断:任务执行超过阈值自动终止,释放资源。训练任务跑了 72 小时还没收敛?大概率是配置错了,别浪费算力。
失败重试:网络抖动导致任务失败?自动重试 3 次,指数退避。
节点故障转移:Worker 节点挂了?未完成的任务自动回退到队列,由健康节点接管。
状态持久化:调度器自己挂了怎么办?所有状态写入数据库(Redis/MySQL),重启后恢复到故障前的状态。
理论讲完了,来看看主流框架怎么做的。

Ray 是为 AI 而生的,OpenAI 的训练底层就是 Ray。
核心设计理念:让分布式代码写起来像单机代码。
1 import ray
2
3 ray.init()
4
5 @ray.remote(num_gpus=2)
6 class Trainer:
7 def train(self, config):
8 # 这段代码自动在远程 GPU 节点上执行
9 return train_model(config)
10
11 # 启动 4 个并行训练
12 trainers = [Trainer.remote() for _ inrange(4)]
13 futures = [t.train.remote(config) for t in trainers]
14 results = ray.get(futures)你在本地写 t.train.remote(config),Ray 自动帮你把任务调度到集群中有 GPU 的节点执行。
Ray 的架构分两层:
Ray 最强的地方是 GPU 调度。它能感知每个节点的 GPU 型号、显存大小、NVLink 拓扑,把任务调度到最合适的节点。
适合场景:大规模 GPU 集群、模型训练、在线推理混合部署。
Celery 是 Python 生态的老牌调度框架,2010 年发布,已经稳定运行 15 年。
它的设计哲学很朴素:任务推到队列,Worker 拉取执行。
1 from celery import Celery, chain, group, chord
2
3 app = Celery('tasks', broker='redis://localhost:6379')
4
5 @app.task
6 def process_image(img_path):
7 return detect_objects(img_path)
8
9 # 批量并行处理
10 job = group(process_image.s(f"img_{i}.jpg") for i inrange(100))
11 result = job.apply_async()Celery 的 Canvas 原语支持复杂的工作流编排:
适合场景:CPU 密集型批处理、数据处理管道、定时任务。
不适合场景:GPU 资源调度(Celery 对 GPU 一无所知)、低延迟在线推理。
Airflow 是数据工程领域的调度之王,2014 年由 Airbnb 开源,现在 Apache 基金会顶级项目。
核心概念只有一个:一切皆 DAG。
1 from airflow import DAG
2 from airflow.operators.python import PythonOperator
3
4 with DAG("ml_pipeline", schedule_interval="0 8 * * *") as dag:
5 extract = PythonOperator(task_id="extract", ...)
6 validate = PythonOperator(task_id="validate", ...)
7 train = PythonOperator(task_id="train", ...)
8 evaluate = PythonOperator(task_id="evaluate", ...)
9 deploy = PythonOperator(task_id="deploy", ...)
10
11 extract -> validate -> [train, evaluate] -> deployAirflow 的 4 种 Executor 决定了调度能力:
Executor | 调度范围 | 适用规模 |
|---|---|---|
SequentialExecutor | 单机串行 | 开发测试 |
LocalExecutor | 单机并行 | 小规模 |
CeleryExecutor | 分布式(基于 Celery) | 中大规模 |
KubernetesExecutor | K8s 原生 | 大规模、云原生 |
适合场景:数据管道、ML Pipeline 编排、定时调度。
不适合场景:实时推理、GPU 资源精细调度(需要配合 K8s Device Plugin)。
框架是骨架,算法是灵魂。来看看调度决策背后的数学。

FIFO(先来先服务)
最简单的策略,任务按提交时间排队。
优点:实现简单,吞吐量高。缺点:大任务阻塞小任务,一个训练任务占 8 卡 3 天,后面所有推理请求全卡住。
优先级调度
每个任务带优先级标签,调度器优先选高优任务。
问题:优先级反转。高优任务等低优任务释放资源,低优任务又等更高优任务释放,形成死锁。解决方案是 优先级继承:低优任务临时提升优先级,尽快释放资源。
公平调度(Fair Scheduler)
把资源按"池"分配,每个团队/项目一个池,池内 FIFO,池间公平。
核心算法是 最大最小公平(Max-Min Fairness):
这样既不会饿死小团队,也不会浪费大团队的空闲资源。
AI 场景最核心的调度优化:让任务跑到最合适的节点上。
1 # 伪代码:资源感知调度决策
2 def schedule(task, cluster):
3 candidates = []
4 for node in cluster.nodes:
5 if node.matches(task.requirements): # 资源匹配
6 score = 0
7 score += affinity_score(task, node) # 数据亲和性
8 score -= fragmentation_risk(task, node) # 碎片化风险
9 score += gpu_topology_fit(task, node) # GPU 拓扑适配
10 candidates.append((node, score))
11
12 return max(candidates, key=lambda x: x[1])[0]三个关键评分维度:
数据亲和性:任务的数据在哪个节点的本地存储上?让任务"就地"执行,省去网络传输。
碎片化风险:把一个需要 4 卡的任务调度到一个有 5 卡空闲的节点,只剩 1 卡碎片,大概率浪费。应该找刚好有 4 卡空闲的节点。
GPU 拓扑适配:8 卡训练任务最好调度到同一个 NVLink 域内的 8 张卡上。跨 NVLink 通信延迟翻 10 倍,训练速度直接腰斩。
抢占:高优任务来了,资源不够?踢掉低优任务。
被踢掉的任务怎么办?Checkpoint 保存状态,放回队列等下次调度,恢复执行。
回填:队列里有个大任务需要 8 卡,但目前只有 6 卡空闲,2 卡被小任务占着。等不等?
不等。把后面需要 2 卡的小任务提前调度到空闲的 6 卡中,充分利用资源。这叫 回填调度。
所有调度框架都逃不开一个根本问题:调度决策由谁做?

一个 Master 节点负责所有调度决策,Worker 节点只管执行。
Ray、Airflow、K8s 都是中心化架构。
优点:
缺点:
每个节点自己做调度决策,通过协商达成一致。
基于 Multi-Agent 的分布式调度是新兴方向:
优点:
缺点:
生产环境几乎都选混合架构:
这就像公司管理:总部定战略(全局调度),分部管执行(本地调度),既统一又灵活。
说了这么多,到底怎么选?
一句话决策树:
1 你的任务需要 GPU 吗?
2 ├── 是 → 任务类型?
3 │ ├── 训练/微调 → Ray(GPU 原生调度)
4 │ └── 在线推理 → Ray Serve(低延迟服务)
5 └── 否 → 需要复杂工作流编排吗?
6 ├── 是 → 需要循环/动态分支吗?
7 │ ├── 是 → LangGraph / Dify(Graph 编排)
8 │ └── 否 → Airflow(DAG 编排)
9 └── 否 → Celery(简单任务队列)当然,真实环境往往是混合使用:
各司其职,通过 API 和消息队列串联。
AI 任务调度不是一个"锦上添花"的功能,而是算力效率的关键杠杆。
5% 的 GPU 利用率意味着 95% 的钱打了水漂。一个优秀的调度框架,能把利用率拉到 60% 以上,相当于免费多了 12 倍算力。
在算力比黄金还贵的时代,调度才是最大的算力。
— 完 —