Я хочу понять, как работает pin_memory в Dataloader.
Согласно документации:
pin_memory (bool, optional) – If True, the data loader will copy tensors into CUDA pinned memory before returning them.
Ниже приведен пример автономного кода.
import torchvision
import torch
print('torch.cuda.is_available()', torch.cuda.is_available())
train_dataset = torchvision.datasets.CIFAR10(root='cifar10_pytorch', download=True, transform=torchvision.transforms.ToTensor())
train_dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=64, pin_memory=True)
x, y = next(iter(train_dataloader))
print('x.device', x.device)
print('y.device', y.device)
Создание следующего вывода:
torch.cuda.is_available() True
x.device cpu
y.device cpu
Но я ожидал чего-то подобного, потому что указал флаг pin_memory=True в Dataloader.
torch.cuda.is_available() True
x.device cuda:0
y.device cuda:0
Также я запускаю тест:
import torchvision
import torch
import time
import numpy as np
pin_memory=True
train_dataset =torchvision.datasets.CIFAR10(root='cifar10_pytorch', download=True, transform=torchvision.transforms.ToTensor())
train_dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=64, pin_memory=pin_memory)
print('pin_memory:', pin_memory)
times = []
n_runs = 10
for i in range(n_runs):
st = time.time()
for bx, by in train_dataloader:
bx, by = bx.cuda(), by.cuda()
times.append(time.time() - st)
print('average time:', np.mean(times))
Я получил следующие результаты.
pin_memory: False
average time: 6.5701503753662
pin_memory: True
average time: 7.0254474401474
Так что pin_memory=True только замедляет работу.
Может кто-нибудь объяснить мне такое поведение?





Документация, возможно, излишне лаконична, учитывая, что используемые термины довольно нишевые. В терминах CUDA закрепленная память означает не память графического процессора, а невыгружаемую память процессора. Преимущества и обоснование предоставлены здесь, но суть в том, что этот флаг позволяет операции x.cuda() (которую вам все еще нужно выполнять, как обычно) избежать одной неявной копии CPU-to-CPU, что делает ее немного более производительной. Кроме того, с закрепленными тензорами памяти вы можете использовать x.cuda(non_blocking=True) для выполнения копирования асинхронно по отношению к хосту. Это может привести к повышению производительности в определенных сценариях, а именно, если ваш код структурирован как
x.cuda(non_blocking=True)x.Поскольку копирование, инициированное в 1., является асинхронным, оно не блокирует выполнение 2. во время выполнения копирования, и, таким образом, оба могут происходить одновременно (что является преимуществом). Поскольку шаг 3. требует, чтобы x уже был скопирован в GPU, он не может быть выполнен, пока 1. не будет завершен, поэтому только 1. и 2. могут перекрываться, а 3. обязательно произойдет позже. Таким образом, продолжительность 2. — это максимальное время, которое вы можете сэкономить с помощью non_blocking=True. Без non_blocking=True ваш процессор будет ожидать завершения передачи, прежде чем продолжить с 2..
Примечание: возможно, шаг 2. может также включать операции с графическим процессором, если они не требуют x - я не уверен, что это правда, и, пожалуйста, не цитируйте меня по этому поводу.
Редактировать: Я полагаю, что вы упускаете суть своего теста. Есть три проблемы с ним
non_blocking=True в своих .cuda() звонках.DataLoader, а это означает, что большая часть работы в любом случае выполняется синхронно в основном потоке, что превосходит затраты на передачу памяти..cuda()), поэтому нет работы, которая может быть перекрыта передачей памяти.Ориентир, более близкий к тому, как pin_memory предназначен для использования, будет
import torchvision, torch, time
import numpy as np
pin_memory = True
batch_size = 1024 # bigger memory transfers to make their cost more noticable
n_workers = 6 # parallel workers to free up the main thread and reduce data decoding overhead
train_dataset =torchvision.datasets.CIFAR10(
root='cifar10_pytorch',
download=True,
transform=torchvision.transforms.ToTensor()
)
train_dataloader = torch.utils.data.DataLoader(
train_dataset,
batch_size=batch_size,
pin_memory=pin_memory,
num_workers=n_workers
)
print('pin_memory:', pin_memory)
times = []
n_runs = 10
def work():
# emulates the CPU work done
time.sleep(0.1)
for i in range(n_runs):
st = time.time()
for bx, by in train_dataloader:
bx, by = bx.cuda(non_blocking=pin_memory), by.cuda(non_blocking=pin_memory)
work()
times.append(time.time() - st)
print('average time:', np.mean(times))
что дает в среднем 5,48 с для моей машины с закреплением памяти и 5,72 с без.
означает ли это дополнительное использование оперативной памяти? Когда мы НЕ должны его использовать? Спасибо
Я не знаю технических подробностей и точных последствий. Я не думаю, что какая-либо дополнительная оперативная память используется, но, поскольку она не может быть выгружена, ОС может быть не в состоянии выгружать вашу программу и OOM в ситуации, из которой она обычно может восстановиться.
Знаете ли вы ожидаемое поведение .to(non_blocking=True), когда pin_memory==False?
Чего я не понимаю, так это того, что операция .cuda идет рука об руку с работой процессора. Как мы можем гарантировать, что x, отправленный .cuda, является обработанным x, а не оригинальным?
Это не дополнительное использование памяти, а блок памяти, который ОС не может перемещать, переключать на диск, если памяти мало, и т. д. Таким образом, это усложняет работу ОС, и существует ограничение на объем памяти, который вы можете использовать. может закрепить.
Я отредактировал свой ответ, чтобы ответить на ваш тест. В следующий раз, пожалуйста, оставьте комментарий, потому что я случайно заметил, что ваш вопрос был обновлен.