Linux内核sched_ext BPF调度器实战——AI训练与推理服务混部场景下的自定义调度策略
问题定义:为什么CFS在AI混部场景下失灵
2025年AI基础设施领域有一个令人头疼的现实:训练任务吃满GPU时,同节点上部署的推理服务P99延迟可以飙升3到5倍。根本原因不在GPU侧,而在CPU调度侧。
Linux CFS调度器的设计目标是"公平"而非"可控"。当一个节点上同时存在以下两类任务时:
- AI训练任务(DataLoader多进程数据预取,核绑定,吞吐优先)
- LLM推理服务(Token生成,延迟敏感,毫秒级SLO)
CFS会将CPU时间平均分配给所有可运行任务。DataLoader进程的高CPU权重会挤压推理进程的调度延迟,TTFN(Time To First Token)从基准的80ms膨胀到200ms以上。更糟的是,CFS对NUMA拓扑无感知,训练任务频繁跨Socket访问推理任务的内存,QPI总线成为隐形瓶颈。
理想的解决方案需要满足三个条件:可以精确控制任务优先级、可以做NUMA感知放置、不修改内核源码可以热加载。Linux 6.12 引入的sched_ext恰好满足这三个条件。
sched_ext架构总览
sched_ext是Linux 6.12引入的BPF调度器框架,允许用户空间通过编写BPF程序完全替换内核调度器逻辑。它的核心设计哲学是"调度策略即代码"——你不需要重新编译内核,只需编译一个BPF程序加载即可。
┌─────────────────────────────────────────────────────────────────┐
│ 用户空间 │
│ scx_rusty scx_lavd scx_layered 你的自定义调度器(scx_ai_co) │
└─────────────────────────────────────────────────────────────────┘
│ BPF skeleton
┌─────────────────────────────────────────────────────────────────┐
│ 内核空间 │
│ sched_ext core ── enqueue_task ── dispatch ── select_cpu │
│ ── task_tick ── set_weight │
└─────────────────────────────────────────────────────────────────┘
│
CPU 0 CPU 1 ... CPU N
生产环境目前有三个成熟BPF调度器可供参考:
- scx_rusty:全局vtime调度,简单高效,适合HPC
- scx_lavd:基于延迟的调度,自带NUMA感知,适合交互式负载
- scx_layered:分层调度,支持按cgroup分组策略
实战中我们发现,对于AI混部场景,这三个调度器都不能开箱解决问题,需要从源码级定制。
设计混部调度器的核心思路
一个生产可用的AI混部调度器需要实现四个机制:
1. 任务分类与标记
推理服务通过独立Pod运行,cgroup路径统一为 /kubepods/inference/*,训练DataLoader使用 /kubepods/training/*。调度器在enqueue_task时检查cgroup归属:
static bool is_inference_task(struct task_struct *p)
{
struct cgroup *cg;
bool result = false;
rcu_read_lock();
cg = task_css(p, cpu_cgrp_id)-

发表评论 取消回复