Nvidia作为GPU王者,无论是生产学习游戏都占据半壁江山,尤其是AI时代的爆发,让Nvidia的生意更是如日中天。下面对Nvidia开放的一些免费工具进行总结,学会怎么使用会让我们更好的发挥Nvidia系列产品的性能。
(1)apex
APEX是英伟达开源的,完美支持PyTorch框架,用于改变数据格式来减小模型显存占用的工具。其中最有价值的是amp(Automatic Mixed Precision),将模型的大部分操作都用Float16数据类型测试,一些特别操作仍然使用Float32。并且用户仅仅通过三行代码即可完美将自己的训练代码迁移到该模型。实验证明,使用Float16作为大部分操作的数据类型,并没有降低参数,在一些实验中,反而由于可以增大Batch size,带来精度上的提升,以及训练速度上的提升
》下载apex库
网址 GitHub - NVIDIA/apex: A PyTorch Extension: Tools for easy mixed precision and distributed training in Pytorch,下载到本地文件夹。解压后进入到apex的目录安装依赖。在执行命令;
cd apex-master #进入apex目录
pip install -r requirements.txt
》安装apex库
依赖安装完后,打开cmd,cd进入到刚刚下载完的apex-master路径下,运行:
python setup.py install
》使用方式
在混合精度训练上,Apex 的封装十分优雅。直接使用 amp.initialize 包装模型和优化器,apex 就会自动帮助我们管理模型参数和优化器的精度了,根据精度需求不同可以传入其他配置参数。
from apex import amp
model, optimizer = amp.initialize(model, optimizer, opt_level='O1')
其中 opt_level 为精度的优化设置,O0(第一个字母是大写字母O):
O0:纯FP32训练,可以作为accuracy的baseline;
O1:混合精度训练(推荐使用),根据黑白名单自动决定使用FP16(GEMM, 卷积)还是FP32(Softmax)进行计算。
O2:“几乎FP16”混合精度训练,不存在黑白名单,除了Batch norm,几乎都是用FP16计算。
O3:纯FP16训练,很不稳定,但是可以作为speed的baselin
》并行训练
Apex也实现了并行训练模型的转换方式,改动并不大,主要是优化了NCCL的通信,因此代码和 torch.distributed 保持一致,换一下调用的API即可:
from apex import amp
from apex.parallel import DistributedDataParallel
model, optimizer = amp.initialize(model, optimizer, opt_level='O1')
model = DistributedDataParallel(model, delay_allreduce=True)
####反向传播时需要调用 amp.scale_loss,用于根据loss值自动对精度进行缩放
with amp.scale_loss(loss, optimizer) as scaled_loss:
scaled_loss.backward()
》同步BN
Apex为我们实现了同步BN,用于解决单GPU的minibatch太小导致BN在训练时不收敛的问题。
from apex.parallel import convert_syncbn_model
from apex.parallel import DistributedDataParallel
### 注意顺序:三个顺序不能错
model = convert_syncbn_model(UNet3d(n_channels=1, n_classes=1)).to(device)
model, optimizer = amp.initialize(model, optimizer, opt_level='O1')
model = DistributedDataParallel(model, delay_allreduce=True)
(2)runx
Runx是NVIDIA开源的一款深度学习实验管理工具,开源于2020年三月,是个很新的开源库。其功能是帮助深度学习研究者自动执行一些常见任务,如超参数扫描、输出日志记录、运行唯一目录创建、实验总结等操作,这是考虑到深度学习一个实验就要几个小时、几天甚至几个月,如果没有及时进行保存和记录会是一件很低效的事情。
》安装
- pip快速安装
pip install runx
- 源码安装
git clone https://github.com/NVIDIA/runx
cd runx
python setup.py install --user
》模块介绍
Runx主要有三个模块,分别是runx、logx和sumx。这些模块设计之初,期待被一起使用,但如果想只使用runx也是可以的,注意的是,sumx必须配合logx进行使用。
- runx
使用简单的YAML文件配置实验参数,runx会扫描这个文件并逐个执行。runx主要是实验运行的配置扫描,并逐次执行,例如有个train的Python脚本,需要学习率和学习器两个参数,只需要配置如下一个名为sweep.yml的YAML文件即可。
cmd: 'python train.py'
hparams:
lr: [0.01, 0.02]
solver: ['sgd', 'adam']
在命令行通过python -m runx.runx sweep.yml,之后会逐次自动执行下面的命令。它的执行参数是互相组合得到的。该模块对作业系统支持较好,当使用集群时,可以方便的进行任务提交。
python train.py --lr 0.01 --solver sgd
python train.py --lr 0.01 --solver adam
python train.py --lr 0.02 --solver sgd
python train.py --lr 0.02 --solver adam
- logx
保存metrics、messages、checkpint以及tensorboard记录文件。该模块是后续sumx模块必要的步骤,将metric传给logx才能进行用sumx进行实验分析。同时,logx还支持标准输出的本地化、方便地tensorboard记录已经快速的模型保存。
使用该模块首先需要进行导入,得到logx对象后需要先进行初始化,初始化包含下面的参数。
logx.initialize(
logdir=None, # 日志存储目录,不存在会自动创建
coolname=False, # 是否在logdir下生成一个独有的目录
hparams=None, # 配合runx使用,超参数都存下来
tensorboard=False, # 是否写入tensorboard文件,无需手动writer
no_timestamp=False, # 是否不启用时间戳命名
global_rank=0, # 分布式训练防止多输出
eager_flush=True # 打开后即使tensorboard写入过快也不会丢失
)
初始化之后就可以正常使用logx的几个主要功能了。
logx.msg("content")
将所有的print语句换成上面的函数即可,这样所有的标准输出都会保存在logdir目录下的一个logging.log文件中,方便后续查看。
logx.metric(phase='val', metrics=metrics, epoch=epoch)
对传入的字典metrics中的数据进行保存记录,若开启了tensorboard则自动add_scalar。phase表示不同的阶段,可选train和val,epoch表示全局轮次。
logx.save_model(
save_dict, # 保存的checkpoint字典, 里面可以有epoch,state_dict等
metric, # 评估指标,logx会维护一系列的该指标值,若变好则保存新模型
epoch, # 当前轮次
higher_better=True, # 是否更高更好,如准确率
delete_old=True # 是否删除旧模型
)
- sumx
对训练结果进行总结。用于归总多次试验运行的结果,依赖于logx产生的metric文件,避免读取tensorboard文件增加分析速度。
如执行python -m runx.sumx sweep可得如下分析结果。
lr solver acc epoch epoch_time
run4 0.02 adam 99.1 10 5:21
run3 0.02 sgd 99.0 10 5:02
run1 0.01 sgd 98.2 10 5:40
run2 0.01 adam 98.1 10 5:25
下面的代码演示训练模型拟合一元二次函数的例子。
import torch
import numpy as np
import torch.nn as nn
import torch.optim as optim
from runx.logx import logx
# dataset
x = torch.arange(16*10).view(16, 10).float()
x = x + torch.rand_like(x) / 10
y = x ** 2 + 1
# model
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.fc1 = nn.Linear(1, 10)
self.fc2 = nn.Linear(10, 1)
def forward(self, x):
x = self.fc1(x)
x = nn.functional.relu(x)
x = self.fc2(x)
return x
# log
logx.initialize("./logs/", coolname=True, tensorboard=True)
model = Model()
model.cuda()
criterion = nn.MSELoss()
optimizer = optim.SGD(model.parameters(), lr=0.001)
best_loss = np.inf
for epocn in range(10):
losses = 0.0 # epochs training loss
losses_valid = 0.0 # epoch validation loss
for step, (x_, y_) in enumerate(zip(x, y)):
# data
x_, y_ = x_.cuda().view(10, 1), y_.cuda().view(10, 1)
x_train, y_train = x_[:5], y_[:5]
x_val, y_val = x_[5:], y_[5:]
# forward
out = model(x_train)
# loss
loss_ = criterion(out, y_train)
losses += loss_
# backward
optimizer.zero_grad()
loss_.backward()
optimizer.step()
# valid
with torch.no_grad():
pred = model(x_val)
valid_loss = criterion(pred, y_val)
losses_valid += valid_loss
metrics = {
'loss': losses / (step + 1),
}
metrics_valid = {
'loss': losses_valid / (step+1)
}
logx.metric('train', metrics, epocn)
logx.metric('val', metrics_valid, epocn)
if losses_valid < best_loss:
best_loss = losses_valid
save_dict = {
'state_dict': model.state_dict()
}
logx.save_model(save_dict, best_loss, epocn, higher_better=False, delete_old=True)
logx.msg("epoch {}, train loss {:.6f}, valid loss {:.6f}".format(epocn, losses / (step+1), losses_valid / (step+1)))
这段训练代码执行后会生成如下结果,包含模型checkpoint,tensorboard文件,标准输出日志(logging.log)以及metric记录。打开TensorBoard服务,可视化训练过程,结果如下,可以看到,logx已经自动记录了tensorboard文件。
(3)NVDashboard
NVDashboard 是一个开源包,用于在交互式 Jupyter Lab 环境中实时可视化 NVIDIA GPU 指标。 NVDashboard 是所有 GPU 用户监控系统资源的好方法。然而,它对于 NVIDIA 的 GPU 加速数据科学软件库开源套件 RAPIDS 的用户来说尤其有价值。鉴于现代数据科学算法的计算强度,在许多情况下 GPU 可以提供改变游戏规则的工作流加速。为了达到最佳性能,底层软件有效利用系统资源是绝对关键的。尽管加速库(如 cuDNN 和 RAPIDS)专门设计用于在性能优化方面进行繁重的工作,但对于开发人员和最终用户来说,验证他们的软件是否确实按预期利用了 GPU 资源可能非常有用。虽然这可以通过 nvidia-smi 等命令行工具来完成,但许多专业数据科学家更喜欢使用交互式 Jupyter 笔记本进行日常模型和工作流开发。
NVDashboard 使 Jupyter notebook用户能够在他们用于开发的同一交互式环境中可视化系统硬件指标。支持的指标包括:
- GPU 计算利用率
- GPU 内存消耗
- PCIe 吞吐量
- NVLink 吞吐量
该软件包基于基于 Python 的仪表板服务器构建,支持 Bokeh 可视化库实时显示和更新图形。一个额外的 Jupyter-Lab 扩展将这些仪表板作为可移动窗口嵌入到交互式环境中。大多数 GPU 指标都是通过 PyNVML 收集的,PyNVML 是一个开源 Python 包,由 NVIDIA 管理库 (NVML) 的包装器组成。出于这个原因,可以修改/扩展可用的仪表板以显示可通过 NVML 访问的任何可查询的 GPU 指标。
nvdashboard 包在 PyPI 上可用,它由两个基本组件组成:
- Bokeh Server:服务器组件利用出色的 Bokeh 可视化库来实时显示和更新 GPU 诊断仪表板。使用 PyNVML 访问所需的硬件指标,PyNVML 是一个开源 python 包,由 NVIDIA 管理库 (NVML) 的包装器组成。出于这个原因,可以修改/扩展 NVDashboard 以显示任何可通过 NVML 访问的可查询 GPU 指标,并且可以从 Python 轻松访问。
- Jupyter-Lab 扩展:Jupyter-Lab 扩展将 GPU 诊断仪表板作为可移动窗口嵌入到交互式 Jupyter-Lab 环境中。
$ pip install jupyterlab-nvdashboard
# If you are using Jupyter Lab 2 you will also need to run
$ jupyter labextension install jupyterlab-nvdashboard
(4)registry机制
简单来说registry可以提供字符串到python中类的映射,registry能让开发者输入相应的类名和参数,就能获得一个初始化好的类。常见的具体使用步骤如下:
- 创建注册表
首先创建一个目录,随意取个名字code,在code下再创建一个目录,名为registry,在这个目录下创建一个名为builder的python文件,代码如下:
from mmcv import Registry
from mmcv.utils.registry import build_from_cfg
CLASS=Registry("class_test")
在这里我们创建了一个名为class_test的注册表,其中Registry的参数如下
name (str): 注册表的名字
build_func(func, optional):构造类接口的函数,简单来说就是用来后续处理获取初始化类的函数,
mmcv提供了一个build_from_cfg的函数,只需要建立一个字典,然后type=需要调用的类名,然后正常的参数的键值对输入就可以得到一个用自己配置参数获取的初始化的类了。当然也可以自己写。
parent (Registry, optional): 父注册表,如果没有指定build_func,就调用父注册表里面的,当然父注册表里面的接口也是可以继承的
- 注册表添加类
然后在registry目录中创建一个名为class_test的python文件,代码如下:
from .builder import CLASS
@CLASS.register_module()
class test(object):
def init(self,n):
self.n=n
def printf(self):
print(self.n)
我们在这里定义了一个类,@CLASS.register_module()的作用是将我们这个类放入CLASS注册表里面,只要在@CLASS.register_module()下写入一个类就可以将这个类放入我们创建的注册表里面了。
然后在registry中创建一个__init__.py文件,将我们的registry变成一个python包,这是必要的。代码如下:
from .builder import CLASS
from .class_test import test
all=["CLASS","test"]
- 测试
这样我们的注册表就创建完成了,赶紧实验以下,在code目录下创建一个test python文件,代码如下:
from Registry import CLASS
cfg = {"type":'test',"n":10}
test=CLASS.build(cfg)
test.printf()
在这里我们通过build方法创建好了一个初始化好了的test类,然后调用printf方法打印。
(5)Nsight
Nsight是NVIDIA面相开发者提供的开发工具套件,能提供深入的跟踪、调试、评测和分析,以优化跨 NVIDIA GPU和CPU的复杂计算应用程序。Nsight主要包含Nsight System、Nsight Compute、Nsight Graphics三部分。nsight文档
- Nsight System
所有与NVIDIA GPU相关的程序开发都可以从Nsight System开始以确定最大的优化机会。Nsight System给开发者一个系统级别的应用程序性能的可视化分析。开发人员可以优化瓶颈,以便在任意数量或大小的CPU和GPU之间实现高效扩展。详情可访问NVIDIA官网。
- Nsight Compute
Nsight Compute是一个CUDA应用程序的交互式kernel分析器。它通过用户接口和命令行工具的形式提供了详细的性能分析度量和API调试。Nsight Compute还提供了定制化的和数据驱动的用户接口和度量集合,可以使用分析脚本对这些界面和度量集合进行扩展,以获得后处理的结果。详情可访问NVIDIA官网。
- Nsight Graphics
Nsight Graphics是一个用于调试、评测和分析Microsoft Windows和Linux上的图形应用程序。它允许您优化基于Direct3D 11, Direct3D 12, DirectX,Raytracing 1.1, OpenGL,Vulkan和KHR Vulkan Ray Tracing Extension的应程序的性能。详情可访问NVIDIA官网。
以手写数字数据库MNIST作为训练数据集,使用PyTorch框架进行神经网络训练。通过Nsight System对训练过程进行性能分析,进而找到性能瓶颈,指导优化训练过程。
1、下载训练所需的数据集和脚本 数据集采用MNIST,训练脚本我们采用该位置的PyTorch代码,基于单块NVIDIA Volta GPU我们将完成多batches和epochs的训练。一次完整的迭代过程包括如下几个方面:
- 从磁盘载入数据
- 数据加载到GPU设备端
- 前向传播
- 反向传播
运行以下命令,可完成一次训练,本次实践以bcc.gn3.c10m80.1v100-32g实例为例,约花费90s左右。
2、利用Nsight system进行性能分析
(可选)开发者可通过添加NVIDIA Tools Extension(NVTX);来对应用程序逻辑上的时间线进行注释,帮助分析人员理解应用算法的上下文逻辑,在main.py中的训练代码部分增加nvtx函数如下: 通过Nsight System的命令行方式,生成分析结果文件:
nsys profile –t cuda,osrt,nvtx –o baseline –w true python main.py
在这个例子中,采用的参数解释如下:
- -t 后面跟定的参数是我们要追踪的API,即需要CUDA API,OS runtime API以及NVTX API
- -o 给定的是输出的文件名称
- -w 后面表明是或否要在命令行中同时输出结果
- python main.py为程序的执行命令
将导出的baseline输出文件下载到本地,并拖拽到本地的Nsight System窗口即可获取性能结果展示,示例如下:
可以看到,通过产生的分析文件,我们发现训练的过程中,有很大一段时间GPU都处于空闲状态。
3、性能优化 可以发现,GPU空闲状态主要是因为CPU在处理数据加载耗时过多。我们可以对收据加载进行优化,优化的方式如下: 原有的数据加载采用的是一个CPU worker 线程:
kwargs = {'num_workers': 1, 'pin_memory': True} if use_cuda else {}
我们可以将num_workers的数量调大,这里我们调整为8
kwargs = {'num_workers': 8, 'pin_memory': True} if use_cuda else {}
重新按照步骤2执行,可观察到如下结果:
可以发现,经过优化之后,数据加载的时间明显缩短了。整体的训练时间也从原来的90s变成了21s,获得了显著的加速效果。