当前位置:   article > 正文

Torch.cuda.empty_cache() 性能非常非常慢_torch.cuda.empty_cache()损耗性能

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 中删除了打印和时间测量

  1. predictions = None
  2. for i, batch in enumerate(self.test_dataloader):
  3. # if this line is active - the bottleneck after the first batch moves here, rather than below
  4. # i.e. when i > 0
  5. # torch.cuda.empty_cache()
  6. # HUGE PERFORMANCE HIT HAPPENS HERE - after the first batch
  7. # i.e. when i > 0
  8. # obviously tensor.to(device) uses torch.cuda.empty_cache() internally when needed
  9. # and it is inexplicably SLOW
  10. batch = tuple(t.to(device) for t in batch) # to GPU (or CPU) when gpu
  11. b_input_ids, b_input_mask, b_labels = batch
  12. with torch.no_grad():
  13. outputs = model(b_input_ids, token_type_ids=None, attention_mask=b_input_mask)
  14. logits = outputs[0]
  15. logits = logits.detach()
  16. # that doesn't help alleviate the issue
  17. del outputs
  18. predictions = logits if predictions is None else torch.cat((predictions, logits), 0)
  19. # nor do all of the below - freeing references doesn't help speeding up
  20. del logits
  21. del b_input_ids
  22. del b_input_mask
  23. del b_labels
  24. for o in batch:
  25. del o
  26. del batch

输出

  1. start empty cache... 0.00082
  2. end empty cache... 1.9e-05
  3. start to device... 3e-06
  4. end to device... 0.001179 - HERE - time is super fast (as expected)
  5. start outputs... 8e-06
  6. end outputs... 0.334536
  7. logits... 6e-06
  8. start detach... 1.7e-05
  9. end detach... 0.004036
  10. start empty cache... 0.335932
  11. end empty cache... 4e-06
  12. start to device... 3e-06
  13. end to device... 173)">16.553849 - HERE - time is ridiculously high - it's 173)">16 seconds to move tensor to GPU
  14. start outputs... 2.3e-05
  15. end outputs... 0.020878
  16. logits... 7e-06
  17. start detach... 1.4e-05
  18. end detach... 0.00036
  19. start empty cache... 0.00082
  20. end empty cache... 6e-06
  21. start to device... 4e-06
  22. end to device... 17.385204 - HERE - time is ridiculously high
  23. start outputs... 2.9e-05
  24. end outputs... 0.021351
  25. logits... 4e-06
  26. start detach... 1.3e-05
  27. end detach... 1.1e-05
  28. ...

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

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

提前致谢, 阿尔伯特

编辑

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

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

【问题讨论】:

  • 正如您在其他地方已经确认的那样,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】:

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

还要注意

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

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

编辑 1

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

只是为了说明我的意思

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

将批次创建为元组

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

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

将批次创建为列表

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

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

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

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

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

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

要检测的代码是这样的

  1. for i, batch in enumerate(self.test_dataloader):
  2. # torch.cuda.empty_cache()
  3. # torch.synchronize() # if empty_cache is used
  4. # start timer for copy
  5. batch = tuple(t.to(device) for t in batch) # to GPU (or CPU) when gpu
  6. torch.cuda.synchronize()
  7. # stop timer for copy
  8. b_input_ids, b_input_mask, b_labels = batch
  9. # start timer for inference
  10. with torch.no_grad():
  11. outputs = model(b_input_ids, token_type_ids=None, attention_mask=b_input_mask)
  12. torch.cuda.synchronize()
  13. # stop timer for inference
  14. logits = outputs[0]
  15. logits = logits.detach()
  16. # 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)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/127367
推荐阅读
相关标签
  

闽ICP备14008679号