Питорч. Как работает pin_memory в Dataloader?

Я хочу понять, как работает 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 только замедляет работу. Может кто-нибудь объяснить мне такое поведение?

Я отредактировал свой ответ, чтобы ответить на ваш тест. В следующий раз, пожалуйста, оставьте комментарий, потому что я случайно заметил, что ваш вопрос был обновлен.

Jatentaki 08.04.2019 17:17
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
30
1
22 775
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Документация, возможно, излишне лаконична, учитывая, что используемые термины довольно нишевые. В терминах CUDA закрепленная память означает не память графического процессора, а невыгружаемую память процессора. Преимущества и обоснование предоставлены здесь, но суть в том, что этот флаг позволяет операции x.cuda() (которую вам все еще нужно выполнять, как обычно) избежать одной неявной копии CPU-to-CPU, что делает ее немного более производительной. Кроме того, с закрепленными тензорами памяти вы можете использовать x.cuda(non_blocking=True) для выполнения копирования асинхронно по отношению к хосту. Это может привести к повышению производительности в определенных сценариях, а именно, если ваш код структурирован как

  1. x.cuda(non_blocking=True)
  2. выполнять некоторые операции ЦП
  3. выполнять операции графического процессора, используя x.

Поскольку копирование, инициированное в 1., является асинхронным, оно не блокирует выполнение 2. во время выполнения копирования, и, таким образом, оба могут происходить одновременно (что является преимуществом). Поскольку шаг 3. требует, чтобы x уже был скопирован в GPU, он не может быть выполнен, пока 1. не будет завершен, поэтому только 1. и 2. могут перекрываться, а 3. обязательно произойдет позже. Таким образом, продолжительность 2. — это максимальное время, которое вы можете сэкономить с помощью non_blocking=True. Без non_blocking=True ваш процессор будет ожидать завершения передачи, прежде чем продолжить с 2..

Примечание: возможно, шаг 2. может также включать операции с графическим процессором, если они не требуют x - я не уверен, что это правда, и, пожалуйста, не цитируйте меня по этому поводу.

Редактировать: Я полагаю, что вы упускаете суть своего теста. Есть три проблемы с ним

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

означает ли это дополнительное использование оперативной памяти? Когда мы НЕ должны его использовать? Спасибо

Shihab Shahriar Khan 08.04.2019 15:59

Я не знаю технических подробностей и точных последствий. Я не думаю, что какая-либо дополнительная оперативная память используется, но, поскольку она не может быть выгружена, ОС может быть не в состоянии выгружать вашу программу и OOM в ситуации, из которой она обычно может восстановиться.

Jatentaki 08.04.2019 16:09

Знаете ли вы ожидаемое поведение .to(non_blocking=True), когда pin_memory==False?

user27182 18.09.2019 13:03

Чего я не понимаю, так это того, что операция .cuda идет рука об руку с работой процессора. Как мы можем гарантировать, что x, отправленный .cuda, является обработанным x, а не оригинальным?

R. Zhu 10.02.2020 03:07

Это не дополнительное использование памяти, а блок памяти, который ОС не может перемещать, переключать на диск, если памяти мало, и т. д. Таким образом, это усложняет работу ОС, и существует ограничение на объем памяти, который вы можете использовать. может закрепить.

Christian Hudon 18.08.2020 17:06

Другие вопросы по теме