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资源的最优利用