模異構 GPU 資源碎片治理:基于拓撲親和與 Binpack 算法的調度優(yōu)化)
大規(guī)模異構 GPU 資源碎片治理基于拓撲親和與 Binpack 算法的調度優(yōu)化在管理上百節(jié)點、包含 A100、H800、L40 及國產異構加速卡的混合算力集群時最令人頭疼的問題往往不是算力絕對總量的不足而是“顯存碎片化”與“拓撲不匹配”。經常出現的情況是集群總空閑顯存高達上千 GB但當一個需要 8 卡 NVLink 全互聯的 70B 大模型任務提交上來時卻沒有任何一臺物理機能夠滿足連續(xù)的 8 卡拓撲需求。原因在于之前的零散小任務隨意分布在各個節(jié)點上把原本連續(xù)的 NUMA / NVLink 節(jié)點打得支離破碎。為了解決異構算力碎片化問題我們自研并落地了基于 Kube-Scheduler 框架的拓撲感知裝箱Binpack調度插件。graph TD subgraph 待調度任務隊列 TaskA[70B分布式推理 申請8卡互聯] TaskB[LoRA微調 申請2卡] TaskC[Embedding 單卡切片] end Scheduler[拓撲感知調度器] TaskA -- Scheduler TaskB -- Scheduler TaskC -- Scheduler subgraph 節(jié)點拓撲與打分決策 Scheduler -- Filter[拓撲過濾器: 校驗NVLink/PCIe Switch連通性] Filter -- Score[Binpack 打分器: 優(yōu)先填充已有碎片節(jié)點] Score -- BestFit[選定最佳節(jié)點: 保留完整整機給大任務] end BestFit -- Node1[節(jié)點 1: 已滿載] BestFit -- Node2[節(jié)點 2: 填充輕量任務] BestFit -- Node3[節(jié)點 3: 預留完整 8 卡拓撲]1. 物理拓撲感知從單卡數量到通信帶寬矩陣在傳統(tǒng)的 Kubernetes 默認調度器中GPU 只是一個無差別的標量資源如nvidia.com/gpu: 2。調度器只要看到節(jié)點剩余 GPU 數量大于等于 2就會把 Pod 調度過去。但在真實硬件中卡 0 和卡 1 可能通過高速 NVLink 直連帶寬達到 600GB/s而卡 0 和卡 7 可能跨越了 CPU NUMA 節(jié)點通信只能走高延遲的 PCIe 總線帶寬驟降至 32GB/s。如果讓分布式張量并行Tensor Parallel跨越 NUMA 節(jié)點運行模型推理延遲會直接增加 3 倍以上。我們在調度插件中維護了節(jié)點硬件的物理拓撲掩碼Topology Maskpackage scheduler import ( context fmt v1 k8s.io/api/core/v1 k8s.io/kubernetes/pkg/scheduler/framework ) type GPUTopologyPlugin struct { handle framework.Handle } const PluginName GPUTopologyBinpack func (p *GPUTopologyPlugin) Name() string { return PluginName } // Filter 階段不僅檢查卡數更校驗是否存在滿足通信帶寬的連續(xù)卡組合 func (p *GPUTopologyPlugin) Filter(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status { reqGPUs : getRequestedGPUs(pod) if reqGPUs 1 { return framework.NewStatus(framework.Success) } node : nodeInfo.Node() topologyData, exists : node.Annotations[topology.gpu.internal/matrix] if !exists { return framework.NewStatus(framework.Unschedulable, 節(jié)點缺少 GPU 拓撲元數據) } if !hasContiguousTopology(topologyData, reqGPUs) { return framework.NewStatus(framework.Unschedulable, fmt.Sprintf(節(jié)點無滿足帶寬的 %d 卡連續(xù)拓撲, reqGPUs)) } return framework.NewStatus(framework.Success) } // Score 階段采用 Binpack 策略傾向于將碎任務擠入同一個 NUMA 節(jié)點盡量空出整機 func (p *GPUTopologyPlugin) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) { nodeInfo, err : p.handle.SnapshotSharedLister().NodeInfos().Get(nodeName) if err ! nil { return 0, framework.NewStatus(framework.Error, err.Error()) } usedGPUs, totalGPUs : getNodeGPUUsage(nodeInfo) if totalGPUs 0 { return 0, framework.NewStatus(framework.Success) } // 計算裝箱分數已有使用率越高打分越高加速填滿單機 utilization : float64(usedGPUs) / float64(totalGPUs) score : int64(utilization * 100) return score, framework.NewStatus(framework.Success) }2. 碎片主動整理與低優(yōu)先級任務搶占靜態(tài)調度只能保證任務入場時的合理性但隨著不同任務生命周期的異步結束集群不可避免地會再次產生碎片。為了動態(tài)消除碎片平臺引入了周期性碎片整理與智能重排控制器apiVersion: config.k8s.io/v1beta3 kind: KubeSchedulerConfiguration leaderElection: leaderElect: true profiles: - schedulerName: gpu-topology-scheduler plugins: filter: enabled: - name: GPUTopologyBinpack score: enabled: - name: GPUTopologyBinpack weight: 100 reserve: enabled: - name: GPUTopologyBinpack控制器每隔 15 分鐘掃描一次全集群。當發(fā)現某臺 8 卡機器上只運行了一個單卡低優(yōu)先級批處理任務而其他節(jié)點有能力容納該任務時控制器會自動向該任務發(fā)送安全遷移信號優(yōu)雅終止并在另一節(jié)點重建從而將整臺 8 卡機器釋放出來為后續(xù)的高優(yōu)先級大模型任務保留黃金算力槽位。3. 生產調優(yōu)成效與避坑總結通過拓撲感知與裝箱調度的結合我們在生產環(huán)境中取得了顯著的收益集群整卡可用率提升8 卡連通任務的調度成功率從 61% 提升至 94%消除了“有空閑卡卻調不動大任務”的窘境。算力裝箱率顯著提高在混合運行微調、輕量推理與批量計算的集群中整體算力碎片率下降了近 40%。避免過早調度鎖定在 Score 階段必須同時結合 CPU、內存與 RDMA 網絡的分配情況防止因 GPU 綁定成功但宿主機網絡帶寬打滿造成的性能次生災害。