Недостаточно памяти для перезагрузки: torch.OutOfMemoryError: CUDA недостаточно памяти

Я обучаю сквозную модель на видео задаче. В качестве кодировщика я использовал Pytorch ResNet50, а входная форма — (1,seq_length,3,224,224), где seq_length — количество кадров в каждом видео. Например, если видео 1 имеет 1500 кадров, входная форма будет (1,1500,3,224,224), если видео 2 имеет 2000 кадров, входная форма будет (1,2000,3,224,224). Однако когда я передаю входные данные в Resnet, CUDA исчерпает память при прохождении через первый слой свертки в прямом проходе.

Я пробовал:

  1. torch.cuda.empty_cache()
  2. установите pin_memory=True и prefetch_factor=2 в загрузчике данных
  3. установите PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
  4. уменьшение seq_length. Это не работает, так как существенно влияет на производительность, видео представляет собой последовательность данных, уменьшение seq_length нарушит эту структуру.

Есть ли какие-нибудь хаки, которые могут помочь с этой проблемой? Любая помощь приветствуется

Ниже приведено полное сообщение об ошибке

Traceback (most recent call last):
  File "Path/E2E.py", line 143, in <module>
    p_classes1, phase_preds = model1(long_feature)
  File "Path/lib/python3.9/site-packages/torch/nn/modules/module.py", line 1553, in _wrapped_call_impl
    return self._call_impl(*args, **kwargs)
  File "Path/lib/python3.9/site-packages/torch/nn/modules/module.py", line 1562, in _call_impl
    return forward_call(*args, **kwargs)
  File "Path/E2E.py", line 47, in forward
    x = self.resnet_lstm(x)
  File "Path/lib/python3.9/site-packages/torch/nn/modules/module.py", line 1553, in _wrapped_call_impl
    return self._call_impl(*args, **kwargs)
  File "Path/lib/python3.9/site-packages/torch/nn/modules/module.py", line 1562, in _call_impl
    return forward_call(*args, **kwargs)
  File "Path/train_embedding.py", line 226, in forward
    x = self.share.forward(x)
  File "Path/lib/python3.9/site-packages/torch/nn/modules/container.py", line 219, in forward
    input = module(input)
  File "Path/lib/python3.9/site-packages/torch/nn/modules/module.py", line 1553, in _wrapped_call_impl
    return self._call_impl(*args, **kwargs)
  File "Path/lib/python3.9/site-packages/torch/nn/modules/module.py", line 1562, in _call_impl
    return forward_call(*args, **kwargs)
  File "Path/lib/python3.9/site-packages/torch/nn/modules/container.py", line 219, in forward
    input = module(input)
  File "Path/lib/python3.9/site-packages/torch/nn/modules/module.py", line 1553, in _wrapped_call_impl
    return self._call_impl(*args, **kwargs)
  File "Path/lib/python3.9/site-packages/torch/nn/modules/module.py", line 1562, in _call_impl
    return forward_call(*args, **kwargs)
  File "Path/lib/python3.9/site-packages/torchvision/models/resnet.py", line 155, in forward
    out = self.bn3(out)
  File "Path/lib/python3.9/site-packages/torch/nn/modules/module.py", line 1553, in _wrapped_call_impl
    return self._call_impl(*args, **kwargs)
  File "Path/lib/python3.9/site-packages/torch/nn/modules/module.py", line 1562, in _call_impl
    return forward_call(*args, **kwargs)
  File "Path/lib/python3.9/site-packages/torch/nn/modules/batchnorm.py", line 176, in forward
    return F.batch_norm(
  File "Path/lib/python3.9/site-packages/torch/nn/functional.py", line 2512, in batch_norm
    return torch.batch_norm(
torch.OutOfMemoryError: CUDA out of memory. Tried to allocate 614.00 MiB. GPU 0 has a total capacity of 23.65 GiB of which 353.38 MiB is free. Including non-PyTorch memory, this process has 23.28 GiB memory in use. Of the allocated memory 22.84 GiB is allocated by PyTorch, and 3.77 MiB is reserved by PyTorch but unallocated. If reserved but unallocated memory is large try setting PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True to avoid fragmentation.  See documentation for Memory Management  (https://pytorch.org/docs/stable/notes/cuda.html#environment-variables)

Вот код модели Resnet50, слой self.fc существует просто потому, что я хочу иметь возможность загружать предварительно обученную модель.

class resnet_lstm(torch.nn.Module):
    def __init__(self):
        super(resnet_lstm, self).__init__()
        resnet = models.resnet50(pretrained=True)
        self.share = torch.nn.Sequential()
        self.share.add_module("conv1", resnet.conv1)
        self.share.add_module("bn1", resnet.bn1)
        self.share.add_module("relu", resnet.relu)
        self.share.add_module("maxpool", resnet.maxpool)
        self.share.add_module("layer1", resnet.layer1)
        self.share.add_module("layer2", resnet.layer2)
        self.share.add_module("layer3", resnet.layer3)
        self.share.add_module("layer4", resnet.layer4)
        self.share.add_module("avgpool", resnet.avgpool)
        self.fc = nn.Sequential(nn.Linear(2048, 512),
                                nn.ReLU(),
                                nn.Linear(512, 7))

    def forward(self, x):
        x = x.view(-1, 3, 224, 224)
        x = self.share.forward(x)
        x = x.view(1,-1, 2048)
        return x

Вот реализация набора данных, file_paths — это список путей к изображениям длиной seq_length:

class CustomDataset(Dataset):
    def __init__(self, file_paths, file_labels, transform=None,
                 loader=pil_loader):
        self.file_paths = file_paths
        self.file_labels_phase = file_labels
        self.transform = transform
        self.loader = loader

    def __getitem__(self, index):
        img_names_list = self.file_paths[index]
        labels_phase = self.file_labels_phase[index]
        imgs = [self.loader(img_name) for img_name in img_names_list]
        if self.transform is not None:
            imgs = [self.transform(img) for img in imgs]

        return imgs, labels_phase, index

    def __len__(self):
        return len(self.file_paths)

class SeqSampler(RandomSampler):
    def __init__(self, data_source, seed=1):
        super().__init__(data_source)
        self.data_source = data_source
        self.seed = seed

    def __iter__(self):
        if self.seed is not None:
            random.seed(self.seed)
        
        # Generate a list of indices and shuffle them
        indices = list(range(len(self.data_source)))
        random.shuffle(indices)
        return iter(indices)

    def __len__(self):
        return len(self.idx)

train_loaders = DataLoader(
            train_dataset_80,
            batch_size=1,
            sampler=SeqSampler(train_dataset_80),
            num_workers=1,
            pin_memory=True,
        )

Вот как я подаю входные данные, это довольно регулярно:

    for data,labels_phase in tqdm(train_loaders,desc=f"Epoch    {epoch+1}/{max_epochs}"):
        
        long_feature = torch.tensor(np.array(data)).to(device)
        labels_phase = np.asarray(labels_phase).squeeze()

        optimizer1.zero_grad()

        labels_phase = torch.LongTensor(labels_phase).to(device)
        
        #Allocated: 2239.18 MB
        #Cached:    2248.00 MB
        p_classes = model1(long_feature)

Вот вывод nvidia-smi:

+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 545.23.08              Driver Version: 545.23.08    CUDA Version: 12.3     |
|-----------------------------------------+----------------------+----------------------+
| GPU  Name                 Persistence-M | Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp   Perf          Pwr:Usage/Cap |         Memory-Usage | GPU-Util  Compute M. |
|                                         |                      |               MIG M. |
|=========================================+======================+======================|
|   0  NVIDIA RTX A6000               On  | 00000000:25:00.0 Off |                  Off |
| 30%   24C    P8              17W / 300W |     12MiB / 49140MiB |      0%      Default |
|                                         |                      |                  N/A |
+-----------------------------------------+----------------------+----------------------+
                                                                                         
+---------------------------------------------------------------------------------------+
| Processes:                                                                            |
|  GPU   GI   CI        PID   Type   Process name                            GPU Memory |
|        ID   ID                                                             Usage      |
|=======================================================================================|
|    0   N/A  N/A      7388      G   /usr/lib/xorg/Xorg                            4MiB |
+---------------------------------------------------------------------------------------+

Предоставьте базовый код, показывающий, как вы используете RestNet50 для работы с данными формы (1,seq_length,3,224,224). Ренеты — это модели изображений, и они не требуют временного измерения, поэтому мне хотелось бы посмотреть, как вы их адаптируете.

Sachin Hosmani 24.08.2024 11:22

Привет. Спасибо за ответ, надеюсь, с кодом будет понятнее

WillWu 24.08.2024 11:42

Основные вопросы: сколько у вас памяти графического процессора? Вы проверили nvidia-smi, чтобы узнать, сколько памяти доступно? Делаете ли вы в сценарии что-нибудь еще, что может выделять память графического процессора?

nneonneo 24.08.2024 12:02

Я думаю, что памяти еще достаточно (47 ГБ), но позвольте мне проверить, есть ли другие вещи, которые потребляют память.

WillWu 24.08.2024 12:18

Хорошо, я только что проверил: перед тем, как входные данные передаются в модель, выделяется только 2G и кэшируется 2G (как показано в коде), поэтому я не думаю, что другие реализации являются проблемой.

WillWu 24.08.2024 12:43

Написал свои наблюдения в качестве ответа. Будем рады обсудить подробности в комментариях.

Sachin Hosmani 24.08.2024 12:49
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
6
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

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

Когда ты это делаешь

for data,labels_phase in tqdm(train_loaders,desc=f"Epoch {epoch+1}/{max_epochs}"):

Я не знаю, как инициализируется ваш train_loaders, но если он инициализируется с размером пакета b, то когда вы делаете x = x.view(-1, 3, 224, 224) в forward(), вы обрабатываете каждый кадр видео как отдельное изображение, поэтому эффективный размер пакета становится b * seq_length, что может получиться довольно большим.

Я думаю, что это основная причина ваших проблем с памятью. Одним из очевидных решений было бы разбить видео на несколько частей, чтобы seq_length эффективно снижался. Чтобы создать одно вложение для каждого видео, вы можете объединить вложения всех фрагментов (например, усреднив их).

Альтернативные подходы

Ваш подход не только неэффективен с точки зрения памяти, но и не использует временную семантику видео (поскольку вы считаете, что видео имеет огромный набор отдельных несвязанных кадров изображения).

Вместо этого вы можете попробовать:

  1. 3D-свертки: идея состоит в том, чтобы применять свертки не только к пространственным измерениям каждого кадра, но и во времени между кадрами одного и того же видео. Это приведет к лучшему обучению, но не устранит проблемы с памятью, поскольку вам все равно придется полностью загружать каждое видео.
  2. Моделирование последовательностей: использование моделей последовательностей, таких как LSTM, решит обе проблемы (с точки зрения памяти вам не обязательно полностью загружать каждое видео при вызове forward()).

Привет. Спасибо, что ответили мне. Я пробовал разбить видео на несколько частей, например, если у меня есть видео из 1000 кадров, я группирую каждые 100 кадров в одну группу данных, создавая входные данные (1, 100, 3, 224, 224). Но это приведет к значительному снижению производительности. И я только что обновил реализацию своего набора данных в своем посте, если вам интересно.

WillWu 24.08.2024 13:06

О каком спектакле идет речь? Показатели качества модели или производительность памяти? Если первое, вам нужно показать, как вы обучаете модель (с применением функции потерь). То, что вы показали, — это всего лишь прямой проход (он же вывод).

Sachin Hosmani 24.08.2024 14:38

Извините за поздний ответ, проблему я уже решил. Большое спасибо!

WillWu 25.08.2024 08:40

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