3、资源抽象模型:GPU资源的量化方式
聊到GPU集群调度,我第一个想说的就是资源抽象。你想想看,一台机器上插了8张A100,每张卡又有显存、算力、NVLink带宽……这些东西怎么让调度器理解?
说白了,我们需要一套统一的量化语言。把物理资源翻译成调度器能懂的“数字”。
3.1 GPU资源的三个维度
我个人习惯把GPU资源拆成三个维度来看:显存、算力、拓扑。缺一个都不行。
3.1.1 显存(Memory)
显存是最直观的资源。多少GB就是多少GB。但这里有个坑——显存碎片化。
我曾经遇到过一个问题:一张40GB的A100,任务只申请了32GB,但调度器报“显存不足”。查了半天,原来是之前跑的任务释放后留下了大量碎片,连续32GB分配不出来。
3.1.2 算力(Compute)
算力量化比显存复杂。你不能简单说“这张卡有312 TFLOPS”。为什么?因为不同精度下算力差异巨大。
| 精度 | A100 峰值算力 | 典型场景 |
|---|---|---|
| FP64 | 9.7 TFLOPS | 科学计算 |
| FP32 | 19.5 TFLOPS | 传统深度学习 |
| FP16 | 312 TFLOPS | 混合精度训练 |
| INT8 | 624 TOPS | 推理 |
我建议在资源模型中,把算力按精度类型分开声明。比如:compute.fp16 = 312。这样调度器才能做精确匹配。
3.1.3 拓扑(Topology)
这是最容易忽略的一点。两张卡虽然都在同一台机器上,但它们之间的通信带宽可能差10倍。
举个例子:
- 同一NVSwitch域内的卡:600 GB/s
- 跨NVSwitch域但同节点:200 GB/s
- 跨节点:50 GB/s
我习惯用距离矩阵来表示拓扑。每张卡一个ID,卡与卡之间的带宽就是矩阵里的值。调度器做任务分配时,优先把通信密集的pair放到高带宽的卡上。
3.2 节点资源统一视图
单卡资源说清楚了,但调度器面对的是整个节点。一个节点上有CPU、内存、GPU、网卡……这些东西得揉成一个统一视图。
我见过不少团队,GPU调度和CPU调度是两套系统。结果呢?GPU等CPU数据,CPU等GPU算完,互相死等。
正确的做法是:把节点抽象成一个资源池。
// 节点资源统一视图示例
Node {
cpu: 128 cores
memory: 1024 GB
gpus: [
{ id: 0, memory: 40GB, compute: {fp16: 312}, topology: {...} },
{ id: 1, memory: 40GB, compute: {fp16: 312}, topology: {...} },
// ... 共8张卡
]
network: { bandwidth: 100Gbps, latency: 2us }
}
这样调度器在做决策时,可以同时考虑CPU和GPU的需求。比如一个数据预处理任务,需要4个CPU核+16GB内存+1张GPU。统一视图下,一次查询就能搞定。
3.3 资源预留与超分
这是调度里最考验经验的地方。资源预留和超分,说白了就是“确定性”和“利用率”之间的博弈。
3.3.1 资源预留(Reservation)
有些任务必须保证资源。比如一个训练任务,你给它8张卡,它就要8张卡的全部显存。少1GB都跑不起来。
我建议对这类任务做硬预留。调度器在分配时,直接把资源锁死,其他任务不能抢占。
// 硬预留示例
Reservation {
task_id: "train-job-001",
gpu_ids: [0,1,2,3],
memory: 160GB, // 4张卡 * 40GB
duration: "2h",
type: "exclusive" // 独占模式
}
3.3.2 资源超分(Overcommitment)
但如果你所有任务都做硬预留,集群利用率会很低。我见过不少集群,平均利用率不到30%。
这时候就需要超分。超分的核心逻辑是:假设不是所有任务都会同时用满资源。
举个例子:
- 10个推理任务,每个申请2GB显存
- 但实际峰值可能只用1.2GB
- 调度器可以按1:1.5的比例超分,20GB的物理显存分配30GB的虚拟显存
我现在的做法是:动态超分。调度器实时监控每个任务的实际资源使用量,如果发现某个任务接近申请值,就提前触发资源回收或任务迁移。
3.4 知识体系总览
说了这么多,我画了一张图帮你梳理一下。这张图展示了GPU资源量化的完整逻辑:
这张图把整个逻辑串起来了。从三个维度的量化,到统一视图,再到调度策略的选择。你可以在实际项目中对照着看。
嗯,资源量化这块就聊这么多。记住三个关键词:显存粒度、算力精度、拓扑距离。把这三点抓牢了,后面的调度策略才能落地。
想要更多资料,联系可可