Недостаточно памяти для перезагрузки: 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 |
+---------------------------------------------------------------------------------------+

🤔 А знаете ли вы, что...
Синтаксис Python известен своей простотой и читаемостью.


50
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()).