检查cuda_pytorch_nvidia安装情况脚本
测试版本
1 | import os |
通用版本
1 | import os |
改动总结:
- 在
pytorch_cuda_info
函数中,无论检测到多少 GPU 都会打印 GPU 名称。 - 其他部分代码保持不变,确保可以在多 GPU 或单 GPU 情况下正常运行。
以后如果要在其他脚本中加入类似的逻辑,只需在调用 GPU 相关的地方进行以下调整:
- 使用
torch.cuda.device_count()
检查 GPU 数量。 - 在需要多个 GPU 并行运算时,使用
nn.DataParallel()
包装模型。 - 确保在任何情况下都打印出每个 GPU 的名称。
多GPU并行计算性能版本
一、pytorch下的GPU并行计算,举例两个标准方法
1. torch.multiprocessing
(mp)
- 优点:
- 支持多进程并行,可以在多个 GPU 上并行处理不同的数据。
- 每个进程有独立的 Python 解释器,这意味着每个进程可以独立地加载模型和数据,避免了全局解释器锁(GIL)的影响。
- 可以利用 NVIDIA NCCL 后端实现高效的跨 GPU 通信。
- 缺点:
- 启动和管理多个进程的开销可能相对较高,特别是在小规模任务中。
- 需要处理进程间的通信,可能会增加代码的复杂性。
2. Data Parallelism数据并行 (ddp)
- 使用
torch.nn.DataParallel
或torch.nn.parallel.DistributedDataParallel
实现数据并行。 - 优点:
DataParallel
在多个 GPU 上并行计算每个 mini-batch 的损失和梯度,使用简单。DistributedDataParallel
更加高效,适合大规模分布式训练,减少了跨 GPU 的通信开销。
- 缺点:
- 在小型模型或小批量数据时,可能不会看到显著的性能提升。
- 在某些情况下,
DataParallel
的效率较低,因为它在主 GPU 上聚合梯度,可能成为瓶颈。
二、mp-精简逻辑
1 | import torch |
**setup_ddp
**:设置分布式处理环境。
rank在多个方法中传递并标识,在的计算方法调用 setup_ddp 来进行环境设置,mp.spawn会自动生成一个rank到 计算方法中。这样就能确保每个进程都能正确地进行分布式计算。
1 | import os |
mp.spawn
是 PyTorch 的多进程模块提供的一个函数,用于方便地启动多个子进程(workers)。这个函数会为每个子进程调用指定的目标函数,并传递相应的参数。
args=(world_size, elapsed_times) + args
:
这个参数定义了要传递给
computation_func
的参数。它由几个部分组成:
- **
world_size
**:当前可用的 GPU 数量,作为第一个参数传递给computation_func
。 - **
elapsed_times
**:共享的列表,用于存储每个进程的计算耗时,也是作为第二个参数传递。 - **
+ args
**:args
是一个可变参数列表,代表在调用start_multiprocessing
时传递给函数的任何额外参数。这些参数会被附加到world_size
和elapsed_times
之后一起传递给computation_func
。
- **
nprocs=world_size
:
- 这个参数指定要启动的子进程数量。通常,
world_size
是可用 GPU 的数量,所以这个参数表示我们希望在每个可用的 GPU 上启动一个进程,从而实现并行处理。
join=True
:
- 这个参数控制
mp.spawn
的行为。当设置为True
时,主进程会等待所有的子进程完成后再继续执行后续代码。如果设置为False
,主进程将不会等待子进程的完成。 - 一般来说,建议设置为
True
,以确保主进程在所有子进程完成后再进行后续处理,比如计算总耗时等。
三、mp-可用代码
1 | import subprocess |
四、ddp精简逻辑
以下是精简的 DDP 结构示例:
1 | import os |
模块化的逻辑说明
- **
setup
和cleanup
**:用于初始化和销毁分布式进程组。 - **
create_model
**:定义模型结构,直接替换成你自己的模型。 - **
create_dataloader
**:创建数据加载器,使用DistributedSampler
确保每个进程获得数据的不同部分。 - **
train
**:封装训练逻辑。模型、数据、损失函数和优化器定义在此函数中。训练代码逻辑直接可以替换。
这个结构方便修改模型和数据加载逻辑,同时保留 DDP 的核心初始化和训练逻辑。
以下是 DDP 在代码中的核心环节,以及实现效果的关键代码讲解:
1. 初始化分布式进程 (setup
函数)
1 | def setup(rank, world_size): |
- **
MASTER_ADDR
和MASTER_PORT
**:指定主进程的地址和端口,以便所有 GPU 进程能通过这个主节点进行通信。这里设置为本地通信(localhost
),适合单机多卡情况。 - **
dist.init_process_group
**:初始化分布式进程组,指定使用"nccl"
后端(针对 GPU 的高效通信框架)。这个过程会在每个 GPU 上创建一个进程,参与模型的同步。
作用:在 DDP 中,必须在每个 GPU 进程上初始化一个分布式进程组。
nccl
能更高效地处理 GPU 间通信。
2. 模型的分布式封装 (DDP
包装模型)
1 | device = torch.device(f"cuda:{rank}") |
- **
to(device)
**:将模型移到指定的 GPU 上,确保每个进程在特定的 GPU(由rank
指定)上执行。 - **
DDP(model, device_ids=[rank])
**:通过DDP
封装模型,以便在训练过程中自动处理梯度同步。每次反向传播后,DDP 会自动在每个 GPU 进程之间同步梯度。
作用:封装后的模型会在每个 GPU 之间自动同步参数。每次
backward()
调用时,DDP 会收集所有 GPU 的梯度并计算平均值,再将平均后的梯度分发到每个 GPU,确保模型参数在所有设备上一致。
3. 数据并行化 (DistributedSampler
)
1 | sampler = DistributedSampler(dataset, num_replicas=world_size, rank=rank) |
- **
DistributedSampler
**:数据加载器的采样器,确保每个 GPU 进程读取数据集的不同部分,避免数据重复。 - **
num_replicas=world_size
**:指定进程数量,保证数据集分配给所有进程。 - **
rank=rank
**:指定当前进程的 GPU ID,使每个 GPU 加载不同的样本。
作用:确保每个进程处理数据集的不同部分,从而充分利用多 GPU 资源。同时避免多 GPU 处理相同数据的重复计算。
4. 自动梯度同步和参数更新
1 | for inputs, labels in dataloader: |
- **
loss.backward()
**:反向传播时,DDP 会自动将各 GPU 进程的梯度同步并计算平均值。每个 GPU 上的模型参数通过梯度更新保持一致。 - **
optimizer.step()
**:在所有进程中执行参数更新,确保下一步前参数保持同步。
作用:在反向传播的同时完成梯度同步,确保各个 GPU 进程的模型更新一致,实现真正的分布式数据并行。
5. 结束分布式训练 (cleanup
)
1 | def cleanup(): |
- **
dist.destroy_process_group()
**:销毁进程组,释放 GPU 资源,结束分布式训练。
作用:在训练完成后清理资源,避免 GPU 资源占用或潜在的内存泄漏。
一个我经常用的例子
这个是做 环境部署和GPU性能检测的。
ddp5_env_check.py
他会检测 驱动 torch conda等情况,并用此脚本文件夹中图片做多GPU测试。
1 | import os |
start_ddp.sh
平时运行它、修改它就好了。
1 |
|
以上 例子 需要说明的是 ddp 方式 来做 多进程的 GPU 操作,是不存在主进程的,实际是 用 rank0 所在的进程兼职了主进程,它一边运行着自己的GPU训练一边兼职了主进程的活。所以 我们上面写的 统计 总共用时的写法 是不准确的,事实上也只会 体现出 rank0的 运行时间,但是 我没有想看那么精确 也就没有改了。