解决pytorch中Dataloader读取数据太慢的问题

2024-04-28 18:58

本文主要是介绍解决pytorch中Dataloader读取数据太慢的问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、造成的原因
  • 二、查找不匹配的原因
  • 三、解决方法
  • 四、使用方法
  • 后言


前言

最近在使用pytorch框架进行模型训练时遇到一个性能问题,即数据读取的速度远远大于GPU训练的速度,导致整个训练流程中有大部分时间都在等待数据发送到GPU,在资源管理器中呈现出CUDA使用率周期性波动,且大部分时间都是在等待数据加载。


一、造成的原因

其实从前言中就可以知道,造成这样的原因可以理解为:GPU的算力与数据加载速度之间的不匹配

二、查找不匹配的原因

本人使用的GPU为RTX3060,数据集为cifar10,使用的模型为VGG,显然这张显卡对于这个任务来说是绰绰有余的,无论是显存还是算力。
但是几经测试发现,数据从内存送到GPU的速度实在是太慢了,去百度了很久都没有很好的解决。那回到这个问题的本身,既然是数据加载导致的性能差,那问题就出在pytorch的datasetdataloader中。

在dataset中,会将数据从磁盘读入内存中,如果启用了dataloader中的pin_memory,就会让数据常驻内存,同时设置num_workers还能实现多进程读取数据,但即使设置了这些,数据加载速度依然没有质的提升。

博主发现,dataset中的transform是导致性能慢的一个原因,dataset中有个函数为__getitem__,每获取一个数据就会让这个数据过一次transform。相信大家都写过如下的代码:

transform = transforms.Compose([transforms.RandomHorizontalFlip(),transforms.ToTensor(),transforms.Normalize([0.4914, 0.4822, 0.4465], [0.5, 0.5, 0.5])
])

但是这里的ToTensor和Normalize完全没必要没读一次数据都处理一次,可以在数据加载到内存的时候就直接全部处理完,这样每个数据只需要经历一次ToTensor和Normalize,这会大大提高数据读取速度,大家可以自行测试一次ToTensor和Normalize所需要的时间,还是非常大的。

在训练的过程中,相信大家也写过如下代码:

for x, y in dataloader:x, y = x.cuda(), y.cuda()

经过博主测试,将数据发送到GPU也是非常耗时的,那为什么不一次性全部加载到GPU里面呢?当然前提是你的GPU显存够大。

三、解决方法

以上分析可以总结为两点:

  1. 数据的预处理有一部分可以提前对全部数据做一遍;
  2. 如果显存足够,可以将数据全部加载到GPU中。

基于此,我们可以重载类,这里以pytorch自带的cifar10为例:

class CUDACIFAR10(CIFAR10):def __init__(self, root: str, train: bool = True,to_cuda: bool = True,half: bool = False,pre_transform: Optional[Callable] = None,transform: Optional[Callable] = None, target_transform: Optional[Callable] = None, download: bool = False) -> None:super().__init__(root, train, transform, target_transform, download)if pre_transform is not None:self.data = self.data.astype("float32")for index in range(len(self)):"""ToTensor的操作会检查数据类型是否为uint8, 如果是, 则除以255进行归一化, 这里data提前转为float,所以手动除以255."""self.data[index] = pre_transform(self.data[index]/255.0).numpy().transpose((1, 2, 0))self.targets[index] = torch.Tensor([self.targets[index]]).squeeze_().long()if to_cuda:self.targets[index] = self.targets[index].cuda()self.data = torch.Tensor(self.data).permute((0, 3, 1, 2))if half:self.data = self.data.half()if to_cuda:self.data = self.data.cuda()def __getitem__(self, index: int) -> Tuple[Any, Any]:"""Args:index (int): IndexReturns:tuple: (image, target) where target is index of the target class."""img, target = self.data[index], self.targets[index]if self.transform is not None:img = self.transform(img)if self.target_transform is not None:target = self.target_transform(target)return img, target

to_cuda为True就会让数据全部加载到GPU中,后续就不需要写x, y = x.cuda(), y.cuda()了。
pre_transform就是让所有数据提前进行的处理,例如使用ToTensor和Normalize,后续调用时不会再进行这些处理。
transform为后续调用时会进行的处理,一般就是一些随机处理过程。

在博主的测试过程中发现,解决了以上问题后,一个epoch只要2秒就能完成,而平时需要15秒,并且任务管理器中的CUDA几乎全程拉满。唯一的代价就是显存占用更高了,这何尝不是一种空间换时间。

四、使用方法

这里直接粘贴我为这个类写的注释

- 使用pytorch自带的CIFAR10时, 每读取一个数据都会调用一次transforms, 其中ToTensor()和Normalize()会消耗巨大的时间如果你的数据集非常的大, 那么一个epoch将会花费非常多的时间用于读取数据, 如果还要将数据送入GPU, 那么时间将会继续增加。- 一般的写法如下:for epoch in range(epochs):for x, y in dataloader:x, y = x.cuda(), y.cuda()如果你的数据集很大并且GPU算力很强, 那么读取数据并发送的GPU将会成为性能瓶颈。- CUDACIFAR10是专门针对pytorch的CIFAR10进行优化的, 使用的前提是你的显存足够的大, 至少8GB, 且读取数据已经是性能瓶颈。CUDACIFAR10的参数与CIFAR10非常相似, 新增的参数为:to_cuda: bool, 是否将数据集常驻GPU, default=Truehalf: bool, 进一步降低数据所占据的显存, 在混合精度训练时使用, 否则可能存在意外(例如梯度值overflow)pre_transform: 传入一个transforms, 如果不为None, 则会在初始化数据时直接对所有数据进行对应的转换, 在后续调用时将不会使用pre_transform. 可以将ToTensor()和Normalize()作为pre_transform, 这会大幅度减少读取时间.- CUDACIFAR10的用法如下:pre_transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize([0.4914, 0.4822, 0.4465], [0.5, 0.5, 0.5])])dataset = CUDACIFAR10(..., to_cuda=True, pre_transform=pre_transform, ...)dataloader = Dataloader(dataset, ..., pin_memory=False, num_workers=0, ...)...for epoch in range(epochs):for x, y in dataloader:# 不需要写x, y = x.cuda(), y.cuda(), 除非to_cuda=False...- 使用CUDACIFAR10需要注意如果启用了to_cuda, 那么Dataloader不能启用pin_memory, pin_memory是将数据常驻内存, 这会产生冲突.同时num_workers=0.- 如果参数to_cuda=False, pre_transform=None, 那么该类与CIFAR10用法完全一致.

后言

本文写作仓促,可能有部分错误,欢迎大家的批评与指正。

这篇关于解决pytorch中Dataloader读取数据太慢的问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/943997

相关文章

IDEA和GIT关于文件中LF和CRLF问题及解决

《IDEA和GIT关于文件中LF和CRLF问题及解决》文章总结:因IDEA默认使用CRLF换行符导致Shell脚本在Linux运行报错,需在编辑器和Git中统一为LF,通过调整Git的core.aut... 目录问题描述问题思考解决过程总结问题描述项目软件安装shell脚本上git仓库管理,但拉取后,上l

解决docker目录内存不足扩容处理方案

《解决docker目录内存不足扩容处理方案》文章介绍了Docker存储目录迁移方法:因系统盘空间不足,需将Docker数据迁移到更大磁盘(如/home/docker),通过修改daemon.json配... 目录1、查看服务器所有磁盘的使用情况2、查看docker镜像和容器存储目录的空间大小3、停止dock

idea npm install很慢问题及解决(nodejs)

《ideanpminstall很慢问题及解决(nodejs)》npm安装速度慢可通过配置国内镜像源(如淘宝)、清理缓存及切换工具解决,建议设置全局镜像(npmconfigsetregistryht... 目录idea npm install很慢(nodejs)配置国内镜像源清理缓存总结idea npm in

pycharm跑python项目易出错的问题总结

《pycharm跑python项目易出错的问题总结》:本文主要介绍pycharm跑python项目易出错问题的相关资料,当你在PyCharm中运行Python程序时遇到报错,可以按照以下步骤进行排... 1. 一定不要在pycharm终端里面创建环境安装别人的项目子模块等,有可能出现的问题就是你不报错都安装

idea突然报错Malformed \uxxxx encoding问题及解决

《idea突然报错Malformeduxxxxencoding问题及解决》Maven项目在切换Git分支时报错,提示project元素为描述符根元素,解决方法:删除Maven仓库中的resolv... 目www.chinasem.cn录问题解决方式总结问题idea 上的 maven China编程项目突然报错,是

在Ubuntu上打不开GitHub的完整解决方法

《在Ubuntu上打不开GitHub的完整解决方法》当你满心欢喜打开Ubuntu准备推送代码时,突然发现终端里的gitpush卡成狗,浏览器里的GitHub页面直接变成Whoathere!警告页面... 目录一、那些年我们遇到的"红色惊叹号"二、三大症状快速诊断症状1:浏览器直接无法访问症状2:终端操作异常

mybatis直接执行完整sql及踩坑解决

《mybatis直接执行完整sql及踩坑解决》MyBatis可通过select标签执行动态SQL,DQL用ListLinkedHashMap接收结果,DML用int处理,注意防御SQL注入,优先使用#... 目录myBATiFBNZQs直接执行完整sql及踩坑select语句采用count、insert、u

Python爬虫HTTPS使用requests,httpx,aiohttp实战中的证书异步等问题

《Python爬虫HTTPS使用requests,httpx,aiohttp实战中的证书异步等问题》在爬虫工程里,“HTTPS”是绕不开的话题,HTTPS为传输加密提供保护,同时也给爬虫带来证书校验、... 目录一、核心问题与优先级检查(先问三件事)二、基础示例:requests 与证书处理三、高并发选型:

前端导出Excel文件出现乱码或文件损坏问题的解决办法

《前端导出Excel文件出现乱码或文件损坏问题的解决办法》在现代网页应用程序中,前端有时需要与后端进行数据交互,包括下载文件,:本文主要介绍前端导出Excel文件出现乱码或文件损坏问题的解决办法,... 目录1. 检查后端返回的数据格式2. 前端正确处理二进制数据方案 1:直接下载(推荐)方案 2:手动构造

MyBatis Plus大数据量查询慢原因分析及解决

《MyBatisPlus大数据量查询慢原因分析及解决》大数据量查询慢常因全表扫描、分页不当、索引缺失、内存占用高及ORM开销,优化措施包括分页查询、流式读取、SQL优化、批处理、多数据源、结果集二次... 目录大数据量查询慢的常见原因优化方案高级方案配置调优监控与诊断总结大数据量查询慢的常见原因MyBAT