You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

视频任务中ResNet50编码器CUDA内存不足问题求助

视频任务端到端模型训练的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内存不足的问题。

已尝试的解决方法

  • 调用torch.cuda.empty_cache()清理显存
  • 在DataLoader中设置pin_memory=True和prefetch_factor=2优化数据加载
  • 设置环境变量PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True缓解显存碎片
  • 减小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 |
+---------------------------------------------------------------------------------------+

可行的解决方法

1. 分批次处理视频帧

将单视频的所有帧拆分为多个小批次处理,避免一次性加载全部帧占用过多显存,处理完成后拼接结果:

def forward(self, x):
    batch_size, seq_len, c, h, w = x.shape
    # 自定义子批次大小,根据显存调整
    chunk_size = 128
    features = []
    for i in range(0, seq_len, chunk_size):
        chunk = x[:, i:i+chunk_size, :, :, :].view(-1, c, h, w)
        feat = self.share(chunk)
        features.append(feat.view(batch_size, -1, 2048))
    x = torch.cat(features, dim=1)
    return x

2. 启用混合精度训练

通过torch.cuda.amp将部分张量从FP32转为FP16,大幅降低显存占用,同时几乎不影响模型性能:

from torch.cuda.amp import GradScaler, autocast

scaler = GradScaler()

# 训练循环内修改
optimizer1.zero_grad()
with autocast():
    p_classes = model1(long_feature)
    loss = ... # 替换为你的损失计算逻辑
scaler.scale(loss).backward()
scaler.step(optimizer1)
scaler.update()

3. 修改BatchNorm层行为

ResNet默认的BatchNorm会对所有视频帧计算全局均值方差,既占用显存也不符合视频时序特性。可替换为LayerNorm:

# 在resnet_lstm的__init__中添加替换逻辑
def replace_bn_with_ln(module):
    for name, child in module.named_children():
        if isinstance(child, nn.BatchNorm2d):
            setattr(module, name, nn.LayerNorm(child.num_features))
        else:
            replace_bn_with_ln(child)

replace_bn_with_ln(self.share)

4. 冻结ResNet部分层

若无需微调整个ResNet,可冻结前几层参数,减少反向传播时的显存开销:

# 在resnet_lstm的__init__中添加冻结逻辑
for param in self.share.conv1.parameters():
    param.requires_grad = False
for param in self.share.bn1.parameters():
    param.requires_grad = False
for param in self.share.layer1.parameters():
    param.requires_grad = False

可根据任务需求调整冻结层数。

5. 优化数据加载流程

避免从列表转numpy再转tensor的冗余步骤,直接在数据集内返回tensor:

# 修改CustomDataset的__getitem__方法
def __getitem__(self, index):
    img_names_list = self.file_paths[index]
    labels_phase = self.file_labels_phase[index]
    imgs = []
    for img_name in img_names_list:
        img = self.loader(img_name)
        if self.transform is not None:
            img = self.transform(img)
        imgs.append(img)
    imgs = torch.stack(imgs)
    return imgs, labels_phase, index

训练循环内直接使用:

long_feature = data.to(device)

6. 使用梯度累积

通过多次前向传播后统一更新参数,模拟大批次训练效果,同时保持单批次显存占用:

accumulation_steps = 4  # 累积4次梯度再更新参数

for step, (data,labels_phase) in enumerate(tqdm(train_loaders,desc=f"Epoch    {epoch+1}/{max_epochs}")):
    long_feature = data.to(device)
    labels_phase = torch.LongTensor(np.asarray(labels_phase).squeeze()).to(device)
    
    with autocast():
        p_classes = model1(long_feature)
        loss = ... # 替换为你的损失计算逻辑
    
    loss = loss / accumulation_steps
    scaler.scale(loss).backward()
    
    if (step + 1) % accumulation_steps == 0:
        scaler.step(optimizer1)
        scaler.update()
        optimizer1.zero_grad()

内容的提问来源于stack exchange,提问作者Will Wu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 09:04:51