第一部分:GPU的物理本质与计算原理
要理解GPU如何分配和计算,首先要明白它到底是什么。
1. GPU是什么?------软硬一体的复杂系统
- 物理层(硬件): GPU是一块集成了数百亿个晶体管 的硅片。这些晶体管组成了最基本的逻辑门电路 (与门、或门、非门)就是通过0和1进行计算的,进而构建出能够执行加减乘除的算术逻辑单元(ALU) 。在NVIDIA的架构中,成千上万个这样的ALU被称为CUDA核心,它们是并行计算的基石。
- 软件层(接口): 在Linux操作系统看来,GPU被抽象为设备文件 ,如
/dev/nvidia0。这并非存储数据的普通文件,而是一个**"传送门"**。当程序向这个文件写入指令时,操作系统内核中的驱动程序会截获请求,将数据和命令通过PCIe总线搬运到GPU的显存(VRAM)中,从而启动硬件计算。
2. 0和1如何变成复杂计算?------硬件算法揭秘
GPU的计算过程,就是电流在精心设计的晶体管电路中流动的过程。不同的数学运算,对应着不同的硬件实现策略。
表格
| 运算类型 | 核心思想 | 硬件实现原理 | 形象比喻 |
|---|---|---|---|
| 加减法 | 逻辑门组合 | 通过加法器电路(由半加器、全加器串联而成)直接处理二进制位,产生和与进位。 | 数豆子:最基础、最直接的物理动作。 |
| 乘除法 | 移位与累加/减 | 乘法 :将二进制数左移(相当于乘以2的幂)并根据乘数位是否为1进行累加。 除法:通过右移(除以2的幂)和减法来实现。 | 搭积木:通过错位叠加,高效完成复杂运算。 |
| 平方 (x²) | 特化的乘法 | 直接调用GPU中的乘法器,将同一个数据x同时送入两个输入端口(A和B),计算A×B。 |
背口诀:一步到位,速度与普通乘法无异。 |
| 开根号 (√x) | 迭代逼近 | 主要使用牛顿迭代法 。硬件电路会先猜一个初始值,然后反复套用公式 x_new = 0.5 * (x_old + S / x_old) 进行修正,直到结果收敛到足够精确。 比如: 1. 瞎猜 ( x0x0 ): 假设猜 10。 2. 第一轮迭代: * 10+25/10=10+2.5=12.510+25/10=10+2.5=12.5 * 12.5/2=6.2512.5/2=6.25 (离 5 近多了!) 3. 第二轮迭代: * 6.25+25/6.25≈6.25+4=10.256.25+25/6.25≈6.25+4=10.25 * 10.25/2=5.12510.25/2=5.125 (非常接近了!) 4. 第三轮迭代: * 5.125+25/5.125≈5.125+4.878=10.0035.125+25/5.125≈5.125+4.878=10.003 * 10.003/2=5.001510.003/2=5.0015 (误差极小,停止!) |
猜谜语:先猜一个大概,再根据提示不断修正,直至猜中。 |
| 对数/三角函数 | 查表与插值 | 利用特殊函数单元(SFU) 。首先通过区间缩减 (如利用log(M * 2^E) = log(M) + E)将任意输入x缩小到一个固定范围(如1到2之间)。然后,用输入值的高位比特作为地址 ,在固化的只读存储器(ROM)查找表 中获取一个近似值。最后,利用低位比特进行线性插值,得到高精度的最终结果。会有存储器存储(例子:0.00001~999999的平方、开根、log等计算的表用来直接查询出结果) |
查字典:不现场推导,而是直接查阅预先准备好的"答案小抄",并用微调确保精确。 |
第二部分:GPU资源的分配与调度
理解了GPU如何计算,我们再来看如何管理和分配这些宝贵的计算资源。
1. 单机分配:从手动到自动
在没有K8s的独立服务器上,GPU分配主要依赖手动或脚本。
- 静态分配(手动指定): 通过环境变量
CUDA_VISIBLE_DEVICES,用户可以强制程序使用指定的GPU。例如,export CUDA_VISIBLE_DEVICES=0会让程序只能看到并使用0号GPU。这种方法简单直接,但容易因人为疏忽导致资源冲突。 - 动态分配(脚本/调度器): 更高级的方式是编写脚本,调用
nvidia-smi查询GPU显存使用情况,自动将任务分配给空闲的GPU。在高性能计算领域,Slurm等专业调度器会维护一个任务队列,根据资源空闲情况自动分配,实现更高效的批处理。
2. K8s集群调度:自动化与智能化的管理
K8s将GPU管理提升到了集群级别,实现了资源的池化和自动化调度。
- 核心组件:NVIDIA Device Plugin
- 发现与上报: 该插件运行在每个节点上,负责扫描
/dev/nvidia*设备文件,并向K8s API Server上报:"本节点拥有N个nvidia.com/gpu资源"。 - 注册与等待: K8s将这些资源信息记录下来,使其成为可被调度的资源池的一部分。
- 这个插件会把显卡打成标签,变成可以
nodeSelector选择
- 发现与上报: 该插件运行在每个节点上,负责扫描
- 调度流程:
- 用户声明: 用户在Pod的YAML文件中通过
resources.limits声明需要多少GPU,并通过nodeSelector指定GPU型号(如nvidia.com/gpu.product: NVIDIA-A100)。 - 调度器匹配: K8s调度器会遍历所有节点,寻找满足资源请求和标签约束的节点。
- 绑定与挂载: 找到合适节点后,调度器将Pod绑定到该节点。节点上的Kubelet会调用Device Plugin,获取具体的GPU设备文件路径,并将其挂载到容器中。
- 用户声明: 用户在Pod的YAML文件中通过
- 高级分配策略:
-
同构卡分配: 当一个节点有多张相同GPU时,K8s默认采用Binpack(打包) 策略,优先填满一张卡再使用下一张,以减少资源碎片。也可配置为**Spread(分散)**策略,将任务均匀分布,提高容灾能力。比如我的请求需要6g,通过上面静态分割成2g.10gb,就可以通过K8S YAML指定给他分配3个
-
异构卡分配: 通过
nodeSelector和自动打上的标签,K8s可以轻松区分A100、T4等不同型号的GPU,确保训练任务分配到高性能卡,推理任务分配到经济型卡。 -
MIG(多实例GPU): 对于A100/H100等高端卡,可以在硬件层面将其切割成多个拥有独立显存和计算单元的"小卡"(如
1g.10gb)。K8s会将这些切片识别为独立的资源(如nvidia.com/mig-1g.10gb),实现更细粒度的分配和硬件级隔离。apiVersion: v1
kind: Pod
metadata:
name: ai-training-pod
spec:1. 告诉 K8s:把我调度到有 A100 的节点上
nodeSelector:
nvidia.com/gpu.product: A100-SXM4-40GB-MIG-2g.10gb #选择插件的标签
containers:- name: gpu-container
image: my-ai-image
resources:
limits:
# 2. 申请数量:我要 7 个这种特定型号的切片
nvidia.com/mig-2g.10gb: 7
- name: gpu-container
-
第三部分:大模型时代的并行计算策略
当模型规模远超单卡显存,且需要服务海量并发请求时,就需要更复杂的并行策略。
1. 模型并行:解决"装不下"的问题
- 张量并行(Tensor Parallelism, TP):
- 原理: 将模型中的单个大矩阵乘法操作(如
Q, K, V矩阵)拆分成多个小矩阵,分配到不同GPU上并行计算。 - 过程: 输入数据被广播到所有参与TP的GPU上,各GPU计算自己负责的部分,然后通过高速互联(如NVLink)进行结果聚合(All-Reduce)。
- 优点: 能有效降低单层计算延迟,是处理超大规模模型的核心技术。
- 原理: 将模型中的单个大矩阵乘法操作(如
- 流水线并行(Pipeline Parallelism, PP):
- 原理: 将模型按层(Layer)切分,不同GPU负责不同层的计算,形成一条处理流水线。
- 过程: 数据在GPU间依次传递,GPU 0处理完前几层后传给GPU 1,同时GPU 0可以开始处理下一个数据批次。
- 优点: 通信开销相对较小,但可能存在"气泡"(即部分GPU空闲等待)问题。
2. 数据并行:解决"服务不过来"的问题
- 原理: 这是最直接的扩展方式。将完整的模型副本加载到多个GPU(或GPU组)上。
- 过程: incoming的用户请求被负载均衡器分发到不同的模型副本上,实现并发处理。
- 优点: 线性提升系统吞吐量,是K8s中通过增加Pod副本数即可实现的经典模式。
3. 混合并行:终极解决方案
现代超大规模模型(如DeepSeek-R1)的训练和推理,几乎都采用混合并行策略。例如,使用8张GPU通过TP组成一个"逻辑GPU"来承载一个巨大的模型,然后在K8s中部署多个这样的"逻辑GPU"副本(数据并行)来服务海量用户。这种方式完美结合了模型并行和数据并行的优势,是应对AI大模型挑战的终极武器。