Документация, возможно, слишком лаконична, учитывая, что используемые термины довольно нишевые. В терминах CUDA закрепленная память означает не память графического процессора, а память невыгружаемого процессора. Преимущества и обоснование предоставляются здесь , но суть его в том, что этот флаг позволяет операции x.cuda()
(которую вам все равно придется выполнять как обычно), чтобы избежать одной неявной копии ЦП, что делает его немного более производительным. Кроме того, с закрепленными тензорами памяти вы можете использовать x.cuda(non_blocking=True)
для асинхронного копирования по отношению к хосту. Это может привести к повышению производительности в определенных сценариях, а именно, если ваш код структурирован как
x.cuda(non_blocking=True)
- выполняет некоторые операции с процессором
- выполняет операции с графическим процессором, используя
x
.
Поскольку копия, инициированная в 1.
, является асинхронной, она не блокирует 2.
от продолжения, пока идет копирование, и, таким образом, оба могут происходить рядом (что является усилением). Поскольку для шага 3.
требуется, чтобы x
уже был скопирован в графический процессор, его невозможно выполнить до тех пор, пока не будет завершено 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 с без.