PyTorch 中通过类 torch.utils.data.DataLoader 提供给模型训练数据集。该类中有一个参数 pin_memory 表示是否采用锁页内存。那什么是锁页内存,它有什么用?本篇对其进行介绍,以帮助大家了解其中的原理,设置合适的参数值,加快模型的训练。
什么是锁页内存 我们都知道计算机除了 CPU 进行计算外,还离不开内存,因为内存中数据读取、交换比较快,所以,计算机程序运行时,将中间数据存放到内存中将大大提高程序的运行速度。但内存相对比较昂贵,因此,计算机的内存往往容量有限,在 Linux 中,常见有虚拟内存(通过 htop 命令可以查看 Swp 就是虚拟内存),其是在硬盘的每个区域划分出来的模拟内存的一块区域,在内存空间不足时,可以作为临时的内存空间使用,但虚拟内存毕竟是在硬盘上,数据的读取速度明显比内存慢。
在内存中,数据的存储分为两类,一类是锁页,一类不是锁页。锁页内存存放的数据在任何情况下都不会与虚拟内存进行数据交换。而不锁页内存当内存不足时,数据会存放在虚拟内置中。值得一提的是,GPU 的显存全部都是锁页内存。
设置 pin_memory 了解了什么是锁页内存,在编写代码时,就可以有针对性的设置 torch.utils.data.DataLoader 中的参数 pin_memory 。
当计算机的内存充足时,可设置 pin_memory=True ;如果为 True,数据加载器将在返回之前将张量复制到 CUDA 固定内存中。
当计算集的内存不足时,需设置 pin_memory=False .
在 PyTorch 中,因为 pin_memory 与电脑硬件有关,开发者不能确保每一个深度学习研究员都有高端的计算设备,因此,pin_memory 的默认值为 False.
如果不是很清楚是否应该设置 pin_memory 为 True,可以通过如下的代码测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 import multiprocessingimport timeuse_cuda = torch.cuda.is_available() core_number = multiprocessing.cpu_count() batch_size = 64 best_num_worker = [0 , 0 ] best_time = [99999999 , 99999999 ] print ("cpu_count =" , core_number) def loading_time (num_workers, pin_memory ): kwargs = {"num_workers" : num_workers, "pin_memory" : pin_memory} if use_cuda else {} train_loader = torch.utils.data.DataLoader( train_dataset, batch_size=args.batch_size, shuffle=(train_sampler is None ), sampler=train_sampler, **kwargs ) start = time.time() for epoch in range (4 ): for batch_idx, (data, target) in enumerate (train_loader): if batch_idx == 15 : break end = time.time() print ("Used {} second with num_workers = {}" .format (end - start, num_workers)) return end - start for pin_memory in [False , True ]: print ("While pin_memory =" , pin_memory) for num_workers in range (0 , core_number * 2 + 1 , 4 ): current_time = loading_time(num_workers, pin_memory) if current_time < best_time[pin_memory]: best_time[pin_memory] = current_time best_num_worker[pin_memory] = num_workers else : if best_num_worker[pin_memory] == 0 : the_range = [] else : the_range = list ( range (best_num_worker[pin_memory] - 3 , best_num_worker[pin_memory]) ) for num_workers in the_range + list ( range (best_num_worker[pin_memory] + 1 , best_num_worker[pin_memory] + 4 ) ): current_time = loading_time(num_workers, pin_memory) if current_time < best_time[pin_memory]: best_time[pin_memory] = current_time best_num_worker[pin_memory] = num_workers break if best_time[0 ] < best_time[1 ]: print ("Best num_workers =" , best_num_worker[0 ], "with pin_memory = False" ) else : print ("Best num_workers =" , best_num_worker[1 ], "with pin_memory = True" ) return
将这些代码放到:GitHub-pytorch-examples-imagenet-main.py 中的 main_worker 函数 中的下面位置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 train_loader = torch.utils.data.DataLoader( train_dataset, batch_size=args.batch_size, shuffle=(train_sampler is None ), num_workers=args.workers, pin_memory=True , sampler=train_sampler) ---------------- here ---------------- val_loader = torch.utils.data.DataLoader( datasets.ImageFolder(valdir, transforms.Compose([ transforms.Resize(256 ), transforms.CenterCrop(224 ), transforms.ToTensor(), normalize, ])), batch_size=args.batch_size, shuffle=False , num_workers=args.workers, pin_memory=True )
同时,下载了 [ImageNet] 数据集(包含有 train, val),训练方法参见:GitHub-pytorch-examples-imagenet .
从中选择最优的 pin_memory 和 num_workers 组合。
参考文献
pytorch创建data.DataLoader时,参数pin_memory的理解
将Pytorch训练速度提高10%
TORCH.UTILS.DATA