Torch.cuda.empty_cache() 性能非常非常慢

原文:Torch.cuda.empty_cache() 性能非常非常慢答案 - 爱码网

【问题标题】:Torch.cuda.empty_cache() very very slow performanceTorch.cuda.empty_cache() 性能非常非常慢
【发布时间】:2021-05-24 22:03:57
【问题描述】:

当我在单个 GPU 上执行推理批处理循环时,我遇到了性能非常慢的问题。

这种缓慢的行为出现在第一批被处理之后 - 也就是 GPU 已经快满了,需要回收它的内存来接受下一批的时候。

在 原始 GPU 状态 - 性能超快(如预期)。

我希望下面的代码 sn-p 和输出都能简明扼要地说明问题。

为了简洁,我已经从 sn-p 中删除了打印和时间测量

predictions = None

for i, batch in enumerate(self.test_dataloader):

    # if this line is active - the bottleneck after the first batch moves here, rather than below
    # i.e. when i > 0
    # torch.cuda.empty_cache()    

    # HUGE PERFORMANCE HIT HAPPENS HERE - after the first batch
    # i.e. when i > 0
    # obviously tensor.to(device) uses torch.cuda.empty_cache() internally when needed
    # and it is inexplicably SLOW
    batch = tuple(t.to(device) for t in batch)  # to GPU (or CPU) when gpu

    b_input_ids, b_input_mask, b_labels = batch

    with torch.no_grad():
        outputs = model(b_input_ids, token_type_ids=None, attention_mask=b_input_mask)

    logits = outputs[0]
    logits = logits.detach()

    # that doesn't help alleviate the issue
    del outputs

    predictions = logits if predictions is None else torch.cat((predictions, logits), 0)            

    # nor do all of the below - freeing references doesn't help speeding up
    del logits
    del b_input_ids
    del b_input_mask
    del b_labels
    for o in batch:
        del o
    del batch


输出

start empty cache... 0.00082
end empty cache... 1.9e-05
start to device... 3e-06
end to device... 0.001179 - HERE - time is super fast (as expected)
start outputs... 8e-06
end outputs... 0.334536
logits... 6e-06
start detach... 1.7e-05
end detach... 0.004036

start empty cache... 0.335932
end empty cache... 4e-06
start to device... 3e-06
end to device... 173)">16.553849 - HERE - time is ridiculously high - it's 173)">16 seconds to move tensor to GPU
start outputs... 2.3e-05
end outputs... 0.020878
logits... 7e-06
start detach... 1.4e-05
end detach... 0.00036

start empty cache... 0.00082
end empty cache... 6e-06
start to device... 4e-06
end to device... 17.385204 - HERE - time is ridiculously high
start outputs... 2.9e-05
end outputs... 0.021351
logits... 4e-06
start detach... 1.3e-05
end detach... 1.1e-05

...

我是否遗漏了一些明显的东西,或者这是 预期的 GPU 行为?

我在进行复杂编码之前发布这个问题,在我的服务器上可用的几个 GPU 和 CPU 之间进行处理。

提前致谢, 阿尔伯特

编辑

已解决问题是:在 DataLoader 构造函数中 - 我更改了 pin_memory to FalseTrue 导致了问题)。这将.to(device) 的时间缩短了 350%-400%

self.test_dataloader = DataLoader(
            test_dataset,
            sampler=SequentialSampler(test_dataset),
            # batch_size=len(test_dataset)  # AKA - single batch - nope! no mem for that
            batch_size=BATCH_SIZE_AKA_MAX_ROWS_PER_GUESS_TO_FIT_GPU_MEM,
            # tests
            num_workers=8,
            # maybe this is the culprit as suggested by user12750353 in ***
            # pin_memory=True
            pin_memory=False
        )

【问题讨论】:

  • 正如您在其他地方已经确认的那样,cudaFree() 的成本相当高,大约是cudaMalloc() 成本的数量级。在绝对意义上,我在各种硬件和软件环境中观察到每个cudaFree() 需要 2 到 10 微秒。 cudaFree() 的速度慢已经有很多年了。没有实质性改变的事实暗示无法改变的潜在技术问题。解决方法是避免频繁分配和取消分配 GPU 内存,并尽可能保留和重用现有分配。

  • 谢谢,所以我的直觉是对的 - 我无法避免取消分配,因为 GPU 在第一轮后已满,并且 .to(device) 进行了取消分配或empty_cache()内部。所以我想这是弄脏我的手的编码时间 - 跟踪 GPU 是否空闲或 empty_cache() 正在进行中,并在 empty_cache() 时间在 CPU 上进行计算......任何想法如果这样的解决方案/库可能已经存在?

  • 我不使用 PyTorch,也不明白它何时以及为什么用 empty_cache() 刷新缓存。我会假设 PyTorch 开发人员意识到GPU 内存分配的缓慢速度和取消分配 并相应地构建了他们的代码。从通用编程的角度来看,在应用程序的单次运行中应该不需要刷新缓存。下面的答案似乎表明相同。也许您可以探索相关的配置设置?考虑使用 PyTorch 支持渠道(可能是邮件列表或论坛)寻求帮助。

  • 我认为我的用例并不特别。我只需要对相对大量的输入进行实时推断(分类)——有时是几千,有时是几十万。平均20K。所以我除了刷新缓存之外别无他法,因为我只有一个 GPU(因为我不是 Google 或 Facebook,拥有无限的资源并且没有 16 或 32 个现代 GPU 来并行运行推理)。

  • 只是出于好奇——从 C/C++ 的角度来看——为什么 malloc 或者相反——释放分配的内存要花很长时间?我几乎可以肯定(不是驱动程序低级专家)——在普通 RAM 芯片上情况并非如此。为什么在 GPU 上会有如此大的不同?

标签: pytorch gpu inference


【解决方案1】:

如果您正确清除了对先前分配的变量的引用,则不应要求您清除缓存。缓存就像是免费的,是您的脚本可以用于新变量的内存。

还要注意

a = torch.zeros(10**9, dtype=torch.float)
a = torch.zeros(10**9, dtype=torch.float)

需要 8GB 内存,即使 a 使用 4GB(1B 个元素,每个元素 4 个字节)。这是因为torch.zeros 会在a 之前的内容被释放之前分配内存。这可能会在更大范围内发生在您的模型中,具体取决于它的实现方式。

编辑 1

一件可疑的事情是,您一次将一个示例加载到 GPU 中。

只是为了说明我的意思

import torch
device = 'cuda'
batch = torch.zeros((4500, 10));

将批次创建为元组

batch_gpu = tuple(t.to(device) for t in batch) 
torch.cuda.synchronize()

每个循环 254 毫秒 ± 36 毫秒(平均值 ± 标准偏差。7 次运行,每个循环 1 个)

将批次创建为列表

batch_gpu = list(t.to(device) for t in batch) 
torch.cuda.synchronize()

每个循环 235 毫秒 ± 3.74 毫秒(平均值 ± 标准偏差,7 次运行,每个循环 1 个)

batch_gpu = batch.to(device)
torch.cuda.synchronize()

每个循环 115 µs ± 2.9 µs(7 次运行的平均值 ± 标准偏差,每次 10000 个循环)

在此示例中,一次复制一个示例要快 2000 倍。

请注意,GPU 与 CPU 异步工作。因此,您可以继续调用将在操作完成之前返回的函数。为了进行有意义的测量,您可以致电synchronize 明确时间界限。

要检测的代码是这样的

for i, batch in enumerate(self.test_dataloader):

    # torch.cuda.empty_cache()    
    # torch.synchronize() # if empty_cache is used
    

    # start timer for copy
    batch = tuple(t.to(device) for t in batch)  # to GPU (or CPU) when gpu
    torch.cuda.synchronize()
    # stop timer for copy

    b_input_ids, b_input_mask, b_labels = batch

    # start timer for inference
    with torch.no_grad():
        outputs = model(b_input_ids, token_type_ids=None, attention_mask=b_input_mask)
    torch.cuda.synchronize()
    # stop timer for inference


    logits = outputs[0]
    logits = logits.detach()
    # if you copy outputs to CPU it will be synchronized

【讨论】:

  • 我已经编辑了我的原始帖子 - 示例 sn-p 包括删除所有可能的引用 - 这没有效果(如预期的那样)

  • 你的批次是什么类型的??

  • 它是文本(标题,例如 Nike 女式跑鞋),使用拥抱脸转换器 encode_plus() 编码。我的模型在 bert large uncased 上进行了微调。它是一个 NLP 分类器,只有不到 5K 个类。我用于训练和推理的原始笔记本是这样的:mccormickml.com/2019/07/22/BERT-fine-tuning/…

  • 我需要一些时间来准备一个 - 因为我无法在我正在使用的那个中透露一些凭据和专有内容。我有它会发布

  • 已解决 - user12750353 你是对的 - 我在为公共笔记本准备项目时发现了罪魁祸首。问题是:在 DataLoader 构造函数中 - 我更改了 pin_memory to False (True 导致了问题)。这将.to(device) 的时间缩短了 350%-400%。再次感谢您的合作。以下是笔记本:colab.research.google.com/drive/…。我会告诉教程的作者,我的代码是基于推理问题的 (Chris McCormick)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值