GPU负载方式详解

GPU负载方式详解

概述

在深度学习和大模型训练中,GPU资源的高效利用是关键。本文详细介绍了三种主要的GPU负载方式,帮助根据实际需求选择最合适的方案。其中时分复用的方式以Koordinator为主流,而GPU虚拟化和GPU数量分配可以参考英伟达等显卡本身提供的硬件接口实现,比如CUDA

GPU资源管理系统流程图

系统架构概览

graph TB subgraph "用户层" User[用户提交任务] API[API接口] end subgraph "调度层" PriorityQueue[优先级队列管理器] Scheduler[GPU资源调度器] Balancer[资源均衡器] end subgraph "执行层" K8sAPI[Kubernetes API] GPUCluster[GPU集群] Monitoring[监控系统] end User --> API API --> PriorityQueue PriorityQueue --> Scheduler Scheduler --> K8sAPI Balancer --> K8sAPI K8sAPI --> GPUCluster GPUCluster --> Monitoring Monitoring --> Balancer

1. 优先级队列管理流程

1.1 任务提交流程

flowchart TD A[用户提交任务] --> B{解析任务参数} B --> C[提取GPU需求] B --> D[提取内存需求] B --> E[提取优先级] C --> F[验证GPU资源格式] D --> G[验证内存需求] E --> H[确定优先级队列] F --> I{验证通过?} G --> I H --> I I -->|是| J[创建调度请求] I -->|否| K[返回错误信息] J --> L{优先级判断} L -->|高优先级 ≥3| M[加入高优先级队列] L -->|普通优先级 1-2| N[加入普通优先级队列] L -->|低优先级 <1| O[加入低优先级队列] M --> P[记录队列状态] N --> P O --> P P --> Q[触发调度处理]

1.2 队列处理流程

flowchart TD A[队列处理线程启动] --> B[检查高优先级队列] B --> C{高优先级队列有任务?} C -->|是| D[获取最高优先级任务] C -->|否| E[检查普通优先级队列] D --> F[提交给调度器] F --> G{调度成功?} G -->|是| H[记录成功日志] G -->|否| I[降低优先级重新入队] E --> J{普通优先级队列有任务?} J -->|是| K[获取普通优先级任务] J -->|否| L[检查低优先级队列] K --> F L --> M{低优先级队列有任务?} M -->|是| N[获取低优先级任务] M -->|否| O[等待新任务] N --> F O --> P[休眠1秒] P --> B H --> Q[更新队列统计] I --> Q Q --> R[继续处理下一个任务] R --> B

2. GPU资源调度器流程

2.1 调度决策流程

flowchart TD A[接收调度请求] --> B[解析GPU需求] B --> C[解析内存需求] C --> D[解析节点选择器] D --> E[获取集群节点信息] E --> F[过滤可用节点] F --> G[应用节点选择器] G --> H{找到合适节点?} H -->|否| I[返回调度失败] H -->|是| J[计算节点评分] J --> K[应用调度策略] K --> L{选择调度策略} L -->|SPREAD| M[分散调度算法] L -->|PACK| N[紧凑调度算法] L -->|BALANCED| O[均衡调度算法] M --> P[选择负载最低节点] N --> Q[选择资源最集中节点] O --> R[平衡负载和资源利用率] P --> S[验证资源可用性] Q --> S R --> S S --> T{资源验证通过?} T -->|否| U[选择下一个最佳节点] T -->|是| V[分配GPU资源] U --> S V --> W[创建Pod规格] W --> X[提交到Kubernetes] X --> Y[返回调度结果]

2.2 节点评分算法

flowchart TD A[开始节点评分] --> B[获取节点GPU信息] B --> C[计算GPU利用率] C --> D[计算内存利用率] D --> E[计算CPU利用率] E --> F[计算GPU碎片化程度] F --> G[应用调度策略权重] G --> H{调度策略} H -->|SPREAD| I[偏好低利用率节点] H -->|PACK| J[偏好高利用率节点] H -->|BALANCED| K[平衡利用率和碎片化] I --> L[计算分散评分] J --> M[计算紧凑评分] K --> N[计算均衡评分] L --> O[应用权重系数] M --> O N --> O O --> P[GPU利用率权重 40%] P --> Q[内存利用率权重 30%] Q --> R[CPU利用率权重 20%] R --> S[碎片化惩罚权重 10%] S --> T[计算最终评分] T --> U[返回评分结果]

3. 资源均衡器流程

3.1 均衡触发流程

flowchart TD A[均衡器启动] --> B[设置均衡间隔] B --> C[等待均衡周期] C --> D[收集集群状态] D --> E[获取项目组信息] E --> F[计算资源使用率] F --> G[识别资源不平衡] G --> H{发现不平衡?} H -->|否| I[记录均衡检查日志] H -->|是| J[计算均衡阈值] I --> C J --> K{超过均衡阈值?} K -->|否| I K -->|是| L[选择均衡策略] L --> M{均衡类型} M -->|CPU均衡| N[执行CPU资源均衡] M -->|GPU均衡| O[执行GPU资源均衡] M -->|混合均衡| P[执行混合资源均衡] N --> Q[迁移CPU节点] O --> R[迁移GPU节点] P --> S[协调CPU和GPU均衡] Q --> T[更新节点标签] R --> T S --> T T --> U[验证均衡效果] U --> V{均衡成功?} V -->|是| W[记录均衡成功] V -->|否| X[回滚均衡操作] W --> Y[更新均衡统计] X --> Y Y --> C

3.2 资源迁移流程

flowchart TD A[开始资源迁移] --> B[分析源项目组] B --> C[分析目标项目组] C --> D[计算迁移需求] D --> E[选择迁移节点] E --> F{节点选择策略} F -->|最低利用率| G[选择使用率最低节点] F -->|最少活跃任务| H[选择任务最少节点] F -->|最佳性能| I[选择性能最佳节点] G --> J[验证节点可迁移性] H --> J I --> J J --> K{节点可迁移?} K -->|否| L[选择下一个候选节点] K -->|是| M[准备迁移操作] L --> J M --> N[创建迁移计划] N --> O[执行节点标签更新] O --> P{标签更新成功?} P -->|否| Q[记录迁移失败] P -->|是| R[验证资源重新分配] Q --> S[回滚标签更改] R --> T{资源分配正确?} T -->|否| U[回滚迁移操作] T -->|是| V[完成迁移] S --> W[记录回滚日志] U --> W V --> X[更新项目组资源统计] W --> Y[返回迁移结果] X --> Y

4. 系统集成流程

4.1 完整任务处理流程

flowchart TD A[用户提交任务] --> B[API接收请求] B --> C[验证请求参数] C --> D[创建优先级任务] D --> E[加入优先级队列] E --> F[队列处理器获取任务] F --> G[提交给GPU调度器] G --> H[调度器分析资源需求] H --> I[获取集群状态] I --> J[应用调度策略] J --> K[选择最佳节点] K --> L{调度成功?} L -->|否| M[任务重新入队] L -->|是| N[创建Pod] M --> F N --> O[提交到Kubernetes] O --> P[监控Pod状态] P --> Q{Pod启动成功?} Q -->|否| R[记录失败日志] Q -->|是| S[更新资源统计] R --> T[清理失败资源] S --> U[触发资源均衡检查] T --> V[返回错误信息] U --> W[检查资源平衡] W --> X{需要均衡?} X -->|是| Y[执行资源均衡] X -->|否| Z[继续监控] Y --> AA[迁移资源] Z --> BB[任务完成] AA --> BB

4.2 监控和反馈流程

flowchart TD A[系统运行] --> B[收集性能指标] B --> C[监控队列状态] C --> D[监控资源利用率] D --> E[监控调度延迟] E --> F[分析系统性能] F --> G{性能指标异常?} G -->|否| H[继续监控] G -->|是| I[触发告警] H --> B I --> J[发送告警通知] J --> K[记录性能问题] K --> L[分析问题原因] L --> M{需要调整?} M -->|否| N[记录分析结果] M -->|是| O[执行自动调整] N --> P[更新监控配置] O --> Q[调整调度参数] Q --> R[调整队列配置] R --> S[调整均衡策略] P --> T[验证调整效果] S --> T T --> U{调整成功?} U -->|是| V[记录调整成功] U -->|否| W[回滚调整] V --> X[更新系统配置] W --> Y[记录调整失败] X --> B Y --> B

5. 关键业务场景

5.1 高优先级任务处理

flowchart TD A[高优先级任务提交] --> B[进入高优先级队列] B --> C[立即触发处理] C --> D[抢占低优先级资源] D --> E[快速调度到最佳节点] E --> F[优先获得GPU资源] F --> G[监控执行状态] G --> H[任务完成]

5.2 资源竞争处理

flowchart TD A[资源竞争发生] --> B[检查优先级] B --> C{优先级相同?} C -->|是| D[按提交时间排序] C -->|否| E[高优先级优先] D --> F[FIFO处理] E --> G[优先级处理] F --> H[等待资源释放] G --> I[抢占低优先级任务] H --> J[资源可用时调度] I --> K[立即调度] J --> L[任务执行] K --> L

5.3 故障恢复流程

flowchart TD A[检测到故障] --> B[分析故障类型] B --> C{故障类型} C -->|节点故障| D[迁移节点任务] C -->|调度器故障| E[重启调度器] C -->|队列故障| F[重建队列] D --> G[重新调度任务] E --> H[恢复调度服务] F --> I[恢复队列处理] G --> J[验证恢复效果] H --> J I --> J J --> K{恢复成功?} K -->|是| L[继续正常服务] K -->|否| M[启动备用方案] L --> N[记录恢复成功] M --> O[启用降级模式] N --> P[更新监控状态] O --> P

6. 性能指标和监控

6.1 关键性能指标

  • 调度延迟: 从任务提交到调度完成的时间

  • 队列积压: 各优先级队列中的待处理任务数

  • 资源利用率: GPU、CPU、内存的使用率

  • 均衡效果: 项目组间资源分布的均衡程度

  • 故障恢复时间: 从故障检测到恢复的时间

6.2 监控告警

  • 高优先级队列积压: 超过阈值时告警

  • 调度延迟过高: 超过预期时间时告警

  • 资源利用率异常: 过高或过低时告警

  • 均衡失败: 资源均衡操作失败时告警

  • 系统故障: 关键组件故障时告警

GPU感知调度器

python 复制代码
class GPUScheduler:
    """GPU感知的资源调度器"""
    
    def __init__(self, k8s_client, gpu_parser, memory_guard):
        self.k8s_client = k8s_client
        self.gpu_parser = gpu_parser
        self.memory_guard = memory_guard
        self.logger = logging.getLogger(__name__)
        
        # 调度配置
        self.scheduling_policy = SchedulingPolicy.BALANCED
        self.gpu_fragmentation_threshold = 0.3  # GPU碎片化阈值
        
        # 调度队列
        self.pending_requests: List[SchedulingRequest] = []
        self.scheduling_lock = threading.Lock()
        
    def schedule_pod(self, request: SchedulingRequest) -> Optional[str]:
        """调度Pod到合适的节点"""
        try:
            # 解析GPU需求
            gpu_info = self.gpu_parser.parse_gpu_resource(request.gpu_requirement)
            
            # 验证 显存 需求
            if not self.memory_guard.validate_memory_requirement(
                request.gpu_requirement, 
                request.memory_requirement
            ):
                self.logger.warning(f"Pod {request.pod_name} GPU显存需求不满足")
                return None
            
            # 查找合适的节点
            suitable_nodes = self._find_suitable_nodes(request, gpu_info)
            
            if not suitable_nodes:
                self.logger.warning(f"未找到满足需求的节点: {request.pod_name}")
                return None
            
            # 根据调度策略选择最佳节点
            selected_node = self._select_best_node(suitable_nodes, gpu_info)
            
            self.logger.info(f"为Pod {request.pod_name} 选择节点: {selected_node}")
            return selected_node
            
        except Exception as e:
            self.logger.error(f"调度Pod失败: {e}")
            return None
    
    def _find_suitable_nodes(self, request: SchedulingRequest, gpu_info) -> List[Dict]:
        """查找满足需求的节点"""
        suitable_nodes = []
        nodes = self.k8s_client.get_nodes()
        
        for node in nodes:
            if not node['ready'] or not node['schedulable']:
                continue
            
            # 检查节点选择器
            if request.node_selector:
                if not all(
                    node['labels'].get(k) == v 
                    for k, v in request.node_selector.items()
                ):
                    continue
            
            # 检查GPU类型匹配
            if gpu_info.gpu_type:
                node_gpu_type = node['labels'].get('gpu-type')
                if node_gpu_type != gpu_info.gpu_type:
                    continue
            
            # 检查GPU资源可用性
            if gpu_info.gpu_num > 0:
                available_gpus = self._get_available_gpus(node)
                if available_gpus < gpu_info.gpu_num:
                    continue
            
            # 计算节点评分
            score = self._calculate_node_score(node, gpu_info)
            suitable_nodes.append({
                'node': node,
                'score': score,
                'available_gpus': self._get_available_gpus(node)
            })
        
        return suitable_nodes
    
    def _get_available_gpus(self, node: Dict) -> float:
        """获取节点可用GPU数量"""
        total_gpus = 0
        used_gpus = 0
        
        # 统计总GPU数量
        for gpu_type, gpu_info in node.get('gpu_info', {}).items():
            total_gpus += gpu_info.get('allocatable', 0)
        
        # 统计已使用GPU数量
        pods = self.k8s_client.get_pods()
        for pod in pods:
            if pod.get('node_name') == node['name']:
                for resource_name, count in pod.get('gpu_resources', {}).items():
                    used_gpus += float(count)
        
        return total_gpus - used_gpus
    
    def _calculate_node_score(self, node: Dict, gpu_info) -> float:
        """计算节点评分"""
        score = 0.0
        
        # GPU利用率评分
        gpu_utilization = self._get_gpu_utilization(node)
        if self.scheduling_policy == SchedulingPolicy.SPREAD:
            score += (1.0 - gpu_utilization) * 40  # 偏好低利用率节点
        elif self.scheduling_policy == SchedulingPolicy.PACK:
            score += gpu_utilization * 40  # 偏好高利用率节点
        else:  # BALANCED
            score += (0.5 - abs(gpu_utilization - 0.5)) * 40
        
        # 内存利用率评分
        memory_utilization = node.get('used_memory', 0) / node.get('memory_allocatable', 1)
        score += (1.0 - memory_utilization) * 30
        
        # CPU利用率评分
        cpu_utilization = node.get('used_cpu', 0) / node.get('cpu_allocatable', 1)
        score += (1.0 - cpu_utilization) * 20
        
        # GPU碎片化惩罚
        fragmentation_penalty = self._calculate_fragmentation_penalty(node, gpu_info)
        score -= fragmentation_penalty * 10
        
        return score
    
    def _get_gpu_utilization(self, node: Dict) -> float:
        """获取节点GPU利用率"""
        total_gpus = 0
        used_gpus = 0
        
        for gpu_type, gpu_info in node.get('gpu_info', {}).items():
            total_gpus += gpu_info.get('allocatable', 0)
            used_gpus += gpu_info.get('allocatable', 0) - gpu_info.get('available', 0)
        
        return used_gpus / max(total_gpus, 1)
    
    def _calculate_fragmentation_penalty(self, node: Dict, gpu_info) -> float:
        """计算GPU碎片化惩罚"""
        if gpu_info.gpu_num < 1:
            return 0.0  # 虚拟GPU不考虑碎片化
        
        available_gpus = self._get_available_gpus(node)
        required_gpus = gpu_info.gpu_num
        
        # 如果剩余GPU数量小于阈值,增加惩罚
        remaining_after_allocation = available_gpus - required_gpus
        if 0 < remaining_after_allocation < self.gpu_fragmentation_threshold:
            return 1.0
        
        return 0.0
    
    def _select_best_node(self, suitable_nodes: List[Dict], gpu_info) -> str:
        """选择最佳节点"""
        if not suitable_nodes:
            return None
        
        # 按评分排序
        suitable_nodes.sort(key=lambda x: x['score'], reverse=True)
        
        # 返回评分最高的节点
        return suitable_nodes[0]['node']['name']
    
    def add_scheduling_request(self, request: SchedulingRequest):
        """添加调度请求到队列"""
        with self.scheduling_lock:
            self.pending_requests.append(request)
            # 按优先级排序
            self.pending_requests.sort(key=lambda x: x.priority, reverse=True)
    
    def process_pending_requests(self):
        """处理待调度请求"""
        with self.scheduling_lock:
            processed_requests = []
            
            for request in self.pending_requests[:]:
                selected_node = self.schedule_pod(request)
                if selected_node:
                    self.logger.info(f"成功调度Pod {request.pod_name} 到节点 {selected_node}")
                    processed_requests.append(request)
                else:
                    self.logger.warning(f"暂时无法调度Pod {request.pod_name}")
            
            # 移除已处理的请求
            for request in processed_requests:
                self.pending_requests.remove(request)

资源均衡器

python 复制代码
 # resource_balancer.py
import logging
import time
import threading
from typing import Dict, List, Tuple
from dataclasses import dataclass
from datetime import datetime, timedelta

@dataclass
class ResourceUsage:
    """资源使用情况"""
    cpu_usage: float
    memory_usage: float
    gpu_usage: float
    node_count: int

class ResourceBalancer:
    """资源均衡器"""
    
    def __init__(self, k8s_client, scheduler):
        self.k8s_client = k8s_client
        self.scheduler = scheduler
        self.logger = logging.getLogger(__name__)
        
        # 均衡配置
        self.balance_threshold = 0.2  # 20%的使用率差异触发均衡
        self.min_nodes_for_balance = 3  # 最少3个节点才参与均衡
        self.balance_interval = 300  # 5分钟检查一次
        
        # 均衡状态
        self.last_balance_time = datetime.now()
        self.balancing_enabled = True
        self.balance_thread = None
        
    def start_balancing(self):
        """启动资源均衡"""
        if self.balance_thread and self.balance_thread.is_alive():
            return
        
        self.balancing_enabled = True
        self.balance_thread = threading.Thread(target=self._balance_loop, daemon=True)
        self.balance_thread.start()
        self.logger.info("资源均衡器已启动")
    
    def stop_balancing(self):
        """停止资源均衡"""
        self.balancing_enabled = False
        if self.balance_thread:
            self.balance_thread.join()
        self.logger.info("资源均衡器已停止")
    
    def _balance_loop(self):
        """均衡循环"""
        while self.balancing_enabled:
            try:
                # 检查是否需要均衡
                if self._should_trigger_balance():
                    self._perform_balance()
                
                time.sleep(self.balance_interval)
            except Exception as e:
                self.logger.error(f"资源均衡错误: {e}")
                time.sleep(60)  # 出错后等待1分钟再重试
    
    def _should_trigger_balance(self) -> bool:
        """检查是否应该触发均衡"""
        # 检查时间间隔
        if datetime.now() - self.last_balance_time < timedelta(seconds=self.balance_interval):
            return False
        
        # 检查是否有挂起的Pod
        pending_pods = self._get_pending_pods()
        if pending_pods:
            self.logger.info(f"发现{len(pending_pods)}个挂起的Pod,触发资源均衡")
            return True
        
        # 检查资源使用率差异
        org_usage = self._get_organization_usage()
        if self._has_significant_imbalance(org_usage):
            self.logger.info("检测到资源使用率不均衡,触发资源均衡")
            return True
        
        return False
    
    def _get_pending_pods(self) -> List[Dict]:
        """获取挂起的Pod"""
        pending_pods = []
        
        for namespace in ['jupyter', 'pipeline', 'automl', 'service']:
            pods = self.k8s_client.get_pods(namespace=namespace)
            for pod in pods:
                if pod['status'] == 'Pending':
                    # 检查挂起时间
                    pending_time = datetime.now() - pod['start_time']
                    if pending_time.total_seconds() > 300:  # 挂起超过5分钟
                        pending_pods.append(pod)
        
        return pending_pods
    
    def _get_organization_usage(self) -> Dict[str, ResourceUsage]:
        """获取各项目组的资源使用情况"""
        org_usage = {}
        nodes = self.k8s_client.get_nodes()
        
        for node in nodes:
            org = node['labels'].get('org', 'public')
            
            if org not in org_usage:
                org_usage[org] = ResourceUsage(
                    cpu_usage=0.0,
                    memory_usage=0.0,
                    gpu_usage=0.0,
                    node_count=0
                )
            
            # 只统计训练节点
            if node['labels'].get('train', 'false') == 'true':
                org_usage[org].node_count += 1
                
                # 计算资源使用率
                cpu_total = node.get('cpu_allocatable', 0)
                memory_total = node.get('memory_allocatable', 0)
                
                if cpu_total > 0:
                    cpu_used = node.get('used_cpu', 0)
                    org_usage[org].cpu_usage += cpu_used / cpu_total
                
                if memory_total > 0:
                    memory_used = node.get('used_memory', 0)
                    org_usage[org].memory_usage += memory_used / memory_total
                
                # GPU使用率
                gpu_total = sum(info.get('allocatable', 0) for info in node.get('gpu_info', {}).values())
                if gpu_total > 0:
                    gpu_used = sum(info.get('allocatable', 0) - info.get('available', 0) 
                                 for info in node.get('gpu_info', {}).values())
                    org_usage[org].gpu_usage += gpu_used / gpu_total
        
        # 计算平均使用率
        for org in org_usage:
            if org_usage[org].node_count > 0:
                org_usage[org].cpu_usage /= org_usage[org].node_count
                org_usage[org].memory_usage /= org_usage[org].node_count
                org_usage[org].gpu_usage /= org_usage[org].node_count
        
        return org_usage
    
    def _has_significant_imbalance(self, org_usage: Dict[str, ResourceUsage]) -> bool:
        """检查是否存在显著的资源不均衡"""
        if len(org_usage) < 2:
            return False
        
        # 检查CPU使用率差异
        cpu_usages = [usage.cpu_usage for usage in org_usage.values() if usage.node_count >= self.min_nodes_for_balance]
        if len(cpu_usages) >= 2:
            cpu_diff = max(cpu_usages) - min(cpu_usages)
            if cpu_diff > self.balance_threshold:
                return True
        
        # 检查GPU使用率差异
        gpu_usages = [usage.gpu_usage for usage in org_usage.values() if usage.node_count >= self.min_nodes_for_balance]
        if len(gpu_usages) >= 2:
            gpu_diff = max(gpu_usages) - min(gpu_usages)
            if gpu_diff > self.balance_threshold:
                return True
        
        return False
    
    def _perform_balance(self):
        """执行资源均衡"""
        try:
            org_usage = self._get_organization_usage()
            
            # 找出CPU使用率最高和最低的项目组
            cpu_orgs = [(org, usage.cpu_usage) for org, usage in org_usage.items() 
                       if usage.node_count >= self.min_nodes_for_balance]
            
            if len(cpu_orgs) >= 2:
                cpu_orgs.sort(key=lambda x: x[1])
                min_cpu_org, min_cpu_usage = cpu_orgs[0]
                max_cpu_org, max_cpu_usage = cpu_orgs[-1]
                
                if max_cpu_usage - min_cpu_usage > self.balance_threshold:
                    self._balance_cpu_resources(min_cpu_org, max_cpu_org)
            
            # 找出GPU使用率最高和最低的项目组
            gpu_orgs = [(org, usage.gpu_usage) for org, usage in org_usage.items() 
                       if usage.node_count >= self.min_nodes_for_balance]
            
            if len(gpu_orgs) >= 2:
                gpu_orgs.sort(key=lambda x: x[1])
                min_gpu_org, min_gpu_usage = gpu_orgs[0]
                max_gpu_org, max_gpu_usage = gpu_orgs[-1]
                
                if max_gpu_usage - min_gpu_usage > self.balance_threshold:
                    self._balance_gpu_resources(min_gpu_org, max_gpu_org)
            
            self.last_balance_time = datetime.now()
            
        except Exception as e:
            self.logger.error(f"执行资源均衡失败: {e}")
    
    def _balance_cpu_resources(self, source_org: str, target_org: str):
        """均衡CPU资源"""
        try:
            # 获取源项目组中CPU使用率最低的节点
            source_nodes = self._get_org_cpu_nodes(source_org)
            if not source_nodes:
                return
            
            # 选择使用率最低的节点进行迁移
            source_nodes.sort(key=lambda x: x[1])  # 按使用率排序
            node_to_migrate = source_nodes[0][0]
            
            # 迁移节点标签
            success = self.k8s_client.label_node([node_to_migrate], labels={"org": target_org})
            
            if success:
                self.logger.info(f"成功将CPU节点 {node_to_migrate} 从项目组 {source_org} 迁移到 {target_org}")
            else:
                self.logger.error(f"迁移CPU节点 {node_to_migrate} 失败")
                
        except Exception as e:
            self.logger.error(f"均衡CPU资源失败: {e}")
    
    def _balance_gpu_resources(self, source_org: str, target_org: str):
        """均衡GPU资源"""
        try:
            # 获取源项目组中GPU使用率最低的节点
            source_nodes = self._get_org_gpu_nodes(source_org)
            if not source_nodes:
                return
            
            # 选择使用率最低的节点进行迁移
            source_nodes.sort(key=lambda x: x[1])  # 按使用率排序
            node_to_migrate = source_nodes[0][0]
            
            # 迁移节点标签
            success = self.k8s_client.label_node([node_to_migrate], labels={"org": target_org})
            
            if success:
                self.logger.info(f"成功将GPU节点 {node_to_migrate} 从项目组 {source_org} 迁移到 {target_org}")
            else:
                self.logger.error(f"迁移GPU节点 {node_to_migrate} 失败")
                
        except Exception as e:
            self.logger.error(f"均衡GPU资源失败: {e}")
    
    def _get_org_cpu_nodes(self, org: str) -> List[Tuple[str, float]]:
        """获取项目组的CPU节点及其使用率"""
        nodes = self.k8s_client.get_nodes()
        org_nodes = []
        
        for node in nodes:
            if (node['labels'].get('org') == org and 
                node['labels'].get('cpu', 'false') == 'true'):
                
                cpu_usage = node.get('used_cpu', 0) / max(node.get('cpu_allocatable', 1), 1)
                org_nodes.append((node['name'], cpu_usage))
        
        return org_nodes
    
    def _get_org_gpu_nodes(self, org: str) -> List[Tuple[str, float]]:
        """获取项目组的GPU节点及其使用率"""
        nodes = self.k8s_client.get_nodes()
        org_nodes = []
        
        for node in nodes:
            if (node['labels'].get('org') == org and 
                node['labels'].get('gpu', 'false') == 'true'):
                
                gpu_total = sum(info.get('allocatable', 0) for info in node.get('gpu_info', {}).values())
                gpu_used = sum(info.get('allocatable', 0) - info.get('available', 0) 
                             for info in node.get('gpu_info', {}).values())
                gpu_usage = gpu_used / max(gpu_total, 1)
                org_nodes.append((node['name'], gpu_usage))
        
        return org_nodes

优先级任务队列

python 复制代码
# priority_queue.py
import heapq
import threading
from typing import List, Optional, Any
from dataclasses import dataclass, field
from datetime import datetime
import logging

@dataclass
class PriorityTask:
    """优先级任务"""
    priority: int
    timestamp: datetime
    task_id: str
    task_data: Any
    
    def __lt__(self, other):
        # 优先级高的先执行(数字越大优先级越高)
        if self.priority != other.priority:
            return self.priority > other.priority
        # 优先级相同时,时间早的先执行
        return self.timestamp < other.timestamp

class PriorityQueue:
    """线程安全的优先级队列"""
    
    def __init__(self):
        self._queue: List[PriorityTask] = []
        self._lock = threading.Lock()
        self._condition = threading.Condition(self._lock)
        self.logger = logging.getLogger(__name__)
    
    def put(self, priority: int, task_id: str, task_data: Any) -> bool:
        """添加任务到队列"""
        try:
            task = PriorityTask(
                priority=priority,
                timestamp=datetime.now(),
                task_id=task_id,
                task_data=task_data
            )
            
            with self._condition:
                heapq.heappush(self._queue, task)
                self._condition.notify()
                self.logger.debug(f"添加任务到队列: {task_id}, 优先级: {priority}")
                return True
                
        except Exception as e:
            self.logger.error(f"添加任务失败: {e}")
            return False
    
    def get(self, timeout: Optional[float] = None) -> Optional[PriorityTask]:
        """从队列获取任务"""
        with self._condition:
            while not self._queue:
                if not self._condition.wait(timeout):
                    return None
            
            task = heapq.heappop(self._queue)
            self.logger.debug(f"从队列获取任务: {task.task_id}")
            return task
    
    def peek(self) -> Optional[PriorityTask]:
        """查看队列顶部任务但不移除"""
        with self._lock:
            return self._queue[0] if self._queue else None
    
    def size(self) -> int:
        """获取队列大小"""
        with self._lock:
            return len(self._queue)
    
    def empty(self) -> bool:
        """检查队列是否为空"""
        with self._lock:
            return len(self._queue) == 0
    
    def clear(self):
        """清空队列"""
        with self._condition:
            self._queue.clear()
            self.logger.info("队列已清空")

class ResourceSchedulingQueue:
    """资源调度队列管理器"""
    
    def __init__(self, scheduler):
        self.scheduler = scheduler
        self.high_priority_queue = PriorityQueue()
        self.normal_priority_queue = PriorityQueue()
        self.low_priority_queue = PriorityQueue()
        self.logger = logging.getLogger(__name__)
        
        # 队列处理线程
        self.processing_enabled = True
        self.processing_threads = []
        
    def start_processing(self, num_workers: int = 3):
        """启动队列处理线程"""
        self.processing_enabled = True
        
        for i in range(num_workers):
            thread = threading.Thread(
                target=self._process_queue_worker,
                args=(f"worker-{i}",),
                daemon=True
            )
            thread.start()
            self.processing_threads.append(thread)
        
        self.logger.info(f"启动了 {num_workers} 个队列处理线程")
    
    def stop_processing(self):
        """停止队列处理"""
        self.processing_enabled = False
        self.logger.info("停止队列处理")
    
    def add_scheduling_request(self, request, priority: int = 1):
        """添加调度请求"""
        task_id = f"{request.namespace}-{request.pod_name}"
        
        if priority >= 3:
            self.high_priority_queue.put(priority, task_id, request)
        elif priority >= 1:
            self.normal_priority_queue.put(priority, task_id, request)
        else:
            self.low_priority_queue.put(priority, task_id, request)
        
        self.logger.info(f"添加调度请求: {task_id}, 优先级: {priority}")
    
    def _process_queue_worker(self, worker_name: str):
        """队列处理工作线程"""
        self.logger.info(f"队列处理线程 {worker_name} 已启动")
        
        while self.processing_enabled:
            try:
                # 按优先级顺序处理队列
                task = None
                
                # 优先处理高优先级队列
                if not self.high_priority_queue.empty():
                    task = self.high_priority_queue.get(timeout=1.0)
                # 然后处理普通优先级队列
                elif not self.normal_priority_queue.empty():
                    task = self.normal_priority_queue.get(timeout=1.0)
                # 最后处理低优先级队列
                elif not self.low_priority_queue.empty():
                    task = self.low_priority_queue.get(timeout=1.0)
                
                if task:
                    self._process_scheduling_task(worker_name, task)
                
            except Exception as e:
                self.logger.error(f"队列处理线程 {worker_name} 错误: {e}")
        
        self.logger.info(f"队列处理线程 {worker_name} 已停止")
    
    def _process_scheduling_task(self, worker_name: str, task: PriorityTask):
        """处理调度任务"""
        try:
            request = task.task_data
            self.logger.info(f"{worker_name} 开始处理调度任务: {task.task_id}")
            
            # 执行调度
            selected_node = self.scheduler.schedule_pod(request)
            
            if selected_node:
                self.logger.info(f"{worker_name} 成功调度 {task.task_id} 到节点 {selected_node}")
            else:
                # 调度失败,重新加入队列(降低优先级)
                new_priority = max(task.priority - 1, 0)
                self.add_scheduling_request(request, new_priority)
                self.logger.warning(f"{worker_name} 调度失败,重新加入队列: {task.task_id}")
                
        except Exception as e:
            self.logger.error(f"{worker_name} 处理调度任务失败: {e}")
    
    def get_queue_status(self) -> Dict[str, int]:
        """获取队列状态"""
        return {
            'high_priority': self.high_priority_queue.size(),
            'normal_priority': self.normal_priority_queue.size(),
            'low_priority': self.low_priority_queue.size(),
            'total': (self.high_priority_queue.size() + 
                     self.normal_priority_queue.size() + 
                     self.low_priority_queue.size())
        }

三种GPU负载方式

1. GPU数量分配 (最简单直接)

通俗理解: 就像给每个任务分配独立的电脑一样,每个任务独占一个或多个完整的GPU。

实现方式:

ini 复制代码
# 分配2个完整的GPU
gpu_resource = "2(V100)"  # 2个V100 GPU
gpu_resource = "1"         # 1个默认GPU
gpu_resource = "4(A100)"   # 4个A100 GPU

特点:

  • 简单直接:一个GPU就是一个完整的计算单元
  • 性能稳定:不会相互干扰,性能可预测
  • 兼容性好:所有GPU都支持,无需特殊配置
  • 资源利用率低:GPU可能空闲,浪费资源
  • 成本较高:每个任务独占GPU,成本高

适用场景:

  • 大型模型训练(如GPT、BERT、大语言模型)
  • 对性能要求极高的应用
  • 预算充足的环境
  • 需要稳定性能的生产环境

代码示例:

yaml 复制代码
# 在Kubernetes中配置
resources:
  requests:
    nvidia.com/gpu: 2
  limits:
    nvidia.com/gpu: 2

2. GPU虚拟化 (灵活高效)

通俗理解: 就像把一台高性能电脑分成多个小电脑,每个小电脑都能独立工作,共享物理GPU资源。

实现方式:

ini 复制代码
# 分配部分GPU资源
gpu_resource = "0.5"           # 半个GPU算力
gpu_resource = "0.25"          # 四分之一GPU算力
gpu_resource = "8G,0.5"        # 8GB显存 + 0.5算力
gpu_resource = "16G,0.75"      # 16GB显存 + 0.75算力

特点:

  • 资源利用率高:多个任务可以共享一个GPU
  • 成本低:按需分配,按实际使用付费
  • 灵活性强:可以精确控制算力和显存
  • 支持多任务:一个GPU可以同时运行多个任务
  • ⚠️ 需要技术支持:需要GPU虚拟化技术(如NVIDIA MPS、vGPU)
  • ⚠️ 性能可能不稳定:任务间可能相互影响

技术实现:

  • NVIDIA MPS (Multi-Process Service) :软件层面的GPU虚拟化
  • NVIDIA vGPU:硬件层面的GPU虚拟化
  • 容器化GPU:通过Docker/Kubernetes实现资源隔离

适用场景:

  • 中小型训练任务
  • 推理服务部署
  • 开发测试环境
  • 多用户共享GPU资源

代码示例:

python 复制代码
# 使用我们的GPU解析器
from src.gpu_parser import GPUResourceParser

parser = GPUResourceParser()
gpu_info = parser.parse_gpu_resource("8G,0.5")
print(f"显存: {gpu_info.memory_gb}GB")
print(f"算力比例: {gpu_info.compute_ratio}")

3. GPU时分复用 (智能调度)

通俗理解: 就像时间片轮转,GPU在不同任务之间快速切换,让每个任务都能"感觉"到独占GPU,但实际上是通过智能调度实现的。

实现方式:

ini 复制代码
# 通过调度器实现时分复用
from src.gpu_scheduler import GPUScheduler, SchedulingRequest

scheduler = GPUScheduler(k8s_client, gpu_parser, memory_guard)

# 创建调度请求
request = SchedulingRequest(
    pod_name='training-job-1',
    namespace='default',
    gpu_requirement='1(V100)',
    memory_requirement=16.0,
    priority=2  # 高优先级
)

# 系统自动在多个任务间分配GPU时间
selected_node = scheduler.schedule_pod(request)

特点:

  • 资源利用率最高:GPU几乎不空闲,最大化利用
  • 支持优先级调度:重要任务优先获得资源
  • 动态负载均衡:自动调整分配,平衡负载
  • 智能调度:基于多种因素选择最佳节点
  • ⚠️ 实现复杂:需要智能调度算法和监控系统
  • ⚠️ 可能有性能开销:调度和切换的开销

调度策略:

  • 分散调度 (SPREAD) :将任务分散到多个节点,避免资源集中
  • 紧凑调度 (PACK) :将任务集中到少数节点,提高资源利用率
  • 均衡调度 (BALANCED) :在分散和紧凑之间找到平衡

适用场景:

  • 多用户共享GPU集群
  • 任务类型多样(训练、推理、开发)
  • 资源紧张需要最大化利用率
  • 需要动态负载均衡的环境

代码示例:

ini 复制代码
# 配置调度策略
scheduler.scheduling_policy = SchedulingPolicy.BALANCED

# 添加调度请求到队列
scheduler.add_scheduling_request(request)

# 处理待调度请求
scheduler.process_pending_requests()

三种方式对比表

特性 GPU数量分配 GPU虚拟化 GPU时分复用
资源利用率
实现复杂度 简单 中等 复杂
性能稳定性
成本
兼容性 最好 中等
灵活性
适用场景 大型训练 中小型任务 多用户环境

实际应用建议

选择GPU数量分配当:

  • 🎯 训练大型模型(如GPT、BERT、大语言模型)
  • 🎯 对性能要求极高,不能容忍任何干扰
  • 🎯 预算充足,可以承担GPU独占成本
  • 🎯 需要稳定性能的生产环境

选择GPU虚拟化当:

  • 🎯 有多个中小型训练任务
  • 🎯 需要精确控制GPU算力和显存
  • 🎯 使用支持虚拟化的GPU(如NVIDIA T4、A100)
  • 🎯 开发测试环境,需要快速迭代

选择时分复用当:

  • 🎯 多用户共享GPU集群
  • 🎯 任务类型多样(训练、推理、开发混合)
  • 🎯 资源紧张,需要最大化利用率
  • 🎯 需要动态负载均衡和优先级调度

技术实现细节

GPU资源解析器

训练系统支持灵活的GPU资源配置:

bash 复制代码
# 支持的格式
"2"              # 2个完整GPU
"0.5"            # 0.5个GPU(虚拟化)
"2(V100)"        # 2个V100 GPU
"1(nvidia,V100)" # 1个NVIDIA V100
"8G,0.5"         # 8GB显存 + 0.5算力
"16G,0.75"       # 16GB显存 + 0.75算力

显存保障机制

ini 复制代码
# 验证GPU显存是否满足需求
memory_guard = GPUMemoryGuard(k8s_client, gpu_parser)
is_valid = memory_guard.validate_memory_requirement("1(V100)", 16.0)

智能调度算法

ini 复制代码
# 节点评分算法考虑因素
score = (
    gpu_utilization_score * 0.4 +      # GPU利用率
    memory_utilization_score * 0.3 +   # 内存利用率
    cpu_utilization_score * 0.2 +      # CPU利用率
    fragmentation_penalty * 0.1         # 碎片化惩罚
)

最佳实践

1. 混合使用策略

  • 生产环境:使用GPU数量分配确保稳定性
  • 开发环境:使用GPU虚拟化提高效率
  • 共享集群:使用时分复用最大化利用率

2. 监控和优化

  • 实时监控GPU利用率
  • 根据使用情况动态调整策略
  • 定期分析资源使用模式

3. 成本控制

  • 根据任务重要性选择合适的方式
  • 利用GPU虚拟化降低小任务成本
  • 通过时分复用提高整体利用率

总结

GPU负载的三种方式各有优势,选择时需要综合考虑:

  • 任务特性(大小、重要性、性能要求)
  • 资源约束(预算、GPU数量、技术能力)
  • 使用场景(生产、开发、测试)

我们的GPU资源管理器支持这三种方式,可以根据实际需求灵活配置,实现GPU资源的最优利用

相关推荐
Starriers3 小时前
AI - Java AI - LangChain4J 实战
人工智能·后端
用户51681661458413 小时前
使用全能电子地图下载器MapTileDownloader 制作瓦片图层详细过程
前端·后端
NickBi3 小时前
龙芯 LoongArch64编译es7.17.20
后端·elasticsearch
掘金者阿豪3 小时前
金仓数据库KingbaseES与MyBatis-Plus整合实践:电商系统开发实战
java·后端
ONE_Gua3 小时前
Wireshark常用过滤规则
前端·后端·数据挖掘
ZhengEnCi3 小时前
SQL统计查询入门宝典-COUNT-GROUP-BY技术解析
后端·sql
苏打水com3 小时前
企业级数据库实操手册:从架构部署到安全运维的落地指南
数据库·后端
老杨说LLM3 小时前
《看不懂算我输!十分钟大白话理解MCP是什么》
后端
muchan923 小时前
这会不会引起编程范式的变革?
前端·后端·编程语言