【发布时间】: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... 16.553849 - HERE - time is ridiculously high - it's 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 False(True 导致了问题)。这将.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 stackoverflow
# 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 上会有如此大的不同?