更新于:2024年5月27日09:47:01
经过了漫长的排查,使用tracemalloc也并不能找到哪里内存泄漏,最后只能通过给出的错误去反思,然后再凭感觉去猜测错误所在位置:
所报的错误是:
Too many open files, Cannot allocate memory (12)
所以就猜测应该是打开的文件太多导致的,可是为什么会导致打开的文件太多呢?唯一与打开文件相关的代码在datasets中的init函数中的3D数据读取中,于是去看到底是哪里的问题:
我是首先使用csv存储大量的已经读取好的tensor,然后将它们在init中读取为一个list, 每一行为一个512维度的tensor。问题就出在了:
threeD_data_list = get_dataset(threeD_data_path)
解决:
将上述代码改为下面的方式,也就是说使用numpy.array将list转为array
self.threeD_graph_data = np.array(torch.stack(threeD_data_list))
我主要是参考了https://zhuanlan.zhihu.com/p/401073320的评论所说的
为什么会内存泄漏?
这个问题出在了init中存储的是一个list, 然后我们在getitem中取出这个list中的元素,最后在collate函数中将这一个批次的所有数据(这个批次的数据会存储为list)转为tensor。问题就出在了list与tensor的转换过程中,这个过程会有内存泄漏。
所以应该在init中将list转为array,从而避免内存泄漏
默认的collate函数:
# step3 collate_fn 转tensor
# site-packages/torch/utils/data/_utils/collate.py:
def default_collate(batch):elem = batch[0]elem_type = type(elem)elif elem_type.__module__ == 'numpy':elif elem.shape == (): # scalarsreturn torch.as_tensor(batch)elif isinstance(elem, float):return torch.tensor(batch, dtype=torch.float64)elif isinstance(elem, int_classes):return torch.tensor(batch)elif isinstance(elem, container_abcs.Sequence):transposed = zip(*batch)return [default_collate(samples) for samples in transposed]
如何排查哪段代码存在内存泄漏?
使用内存检查工具tracemalloc模块,具体用法看:https://zhuanlan.zhihu.com/p/80689571#%E5%BC%95%E7%94%A8%E8%AE%A1%E6%95%B0
一般是使用下面这段代码,看看是否出现激增情况:
内存泄漏:
import numpy as np
import tracemalloc
tracemalloc.start()n = 10
l = []for i in range(n):snapshot1 = tracemalloc.take_snapshot()array_large = np.random.choice(1000, size=(700, 700))array_small = array_large[:5, :5]l.append(array_small)snapshot2 = tracemalloc.take_snapshot()top_stats = snapshot2.compare_to(snapshot1, 'lineno')stat = top_stats[0]print("%s memory blocks: %.1f KiB" % (stat.count, stat.size / 1024))for line in stat.traceback.format():print(line)
输出:发现逐渐增长,那么是错误的。
3 memory blocks: 1914.2 KiB
File "<ipython-input-10-4097e1dc518b>", line 11
array_large = np.random.choice(1000, size=(700, 700))
5 memory blocks: 3828.4 KiB
File "<ipython-input-10-4097e1dc518b>", line 11
array_large = np.random.choice(1000, size=(700, 700))
7 memory blocks: 5742.5 KiB
File "<ipython-input-10-4097e1dc518b>", line 11
array_large = np.random.choice(1000, size=(700, 700))
9 memory blocks: 7656.6 KiB
File "<ipython-input-10-4097e1dc518b>", line 11
array_large = np.random.choice(1000, size=(700, 700))
11 memory blocks: 9570.8 KiB
File "<ipython-input-10-4097e1dc518b>", line 11
array_large = np.random.choice(1000, size=(700, 700))
14 memory blocks: 11484.9 KiB
File "<ipython-input-10-4097e1dc518b>", line 11
array_large = np.random.choice(1000, size=(700, 700))
内存未泄漏:
import numpy as np
import tracemalloc
tracemalloc.start()n = 10
l = []for i in range(n):snapshot1 = tracemalloc.take_snapshot()array_large = np.random.choice(1000, size=(700, 700))array_small = array_large[:5, :5].copy()l.append(array_small)snapshot2 = tracemalloc.take_snapshot()top_stats = snapshot2.compare_to(snapshot1, 'lineno')stat = top_stats[0]print("%s memory blocks: %.1f KiB" % (stat.count, stat.size / 1024))for line in stat.traceback.format():print(line)
输出:较为平稳,那么是没有内存泄漏的
8 memory blocks: 3829.4 KiB
File "/mnt/d/Pycharm_workspace/test/MDT/66.py", line 11
array_large = np.random.choice(1000, size=(700, 700))
5 memory blocks: 0.6 KiB
File "/mnt/d/Pycharm_workspace/test/MDT/66.py", line 12
array_small = array_large[:5, :5].copy()
41 memory blocks: 1.9 KiB
File "/home/mapengsen/anaconda3/envs/MDT/lib/python3.8/tracemalloc.py", line 185
self._frames = tuple(reversed(frames))
40 memory blocks: 1.9 KiB
File "/home/mapengsen/anaconda3/envs/MDT/lib/python3.8/tracemalloc.py", line 185
self._frames = tuple(reversed(frames))
43 memory blocks: 2.0 KiB
File "/home/mapengsen/anaconda3/envs/MDT/lib/python3.8/tracemalloc.py", line 185
self._frames = tuple(reversed(frames))
87 memory blocks: 4.1 KiB
File "/home/mapengsen/anaconda3/envs/MDT/lib/python3.8/tracemalloc.py", line 185
self._frames = tuple(reversed(frames))
83 memory blocks: 3.9 KiB
File "/home/mapengsen/anaconda3/envs/MDT/lib/python3.8/tracemalloc.py", line 185
self._frames = tuple(reversed(frames))
54 memory blocks: 2.5 KiB
File "/home/mapengsen/anaconda3/envs/MDT/lib/python3.8/tracemalloc.py", line 185
self._frames = tuple(reversed(frames))
52 memory blocks: 2.4 KiB
File "/home/mapengsen/anaconda3/envs/MDT/lib/python3.8/tracemalloc.py", line 185
self._frames = tuple(reversed(frames))
82 memory blocks: 3.8 KiB
File "/home/mapengsen/anaconda3/envs/MDT/lib/python3.8/tracemalloc.py", line 185
self._frames = tuple(reversed(frames))
可能的原因一(loss.item()):
解决pytorch训练的过程中内存一直增加的问题_pytorch训练过程中,内存一直增长-CSDN博客
可能的原因二(numpy切片相关):
看numpy下内存泄露的例子:https://zhuanlan.zhihu.com/p/338232671
可能的原因三(dataloader相关):
又重新整了一下内存泄漏的问题,看到dataloader内存泄漏主要是来源于getitem()中,这里返回了array类型的数据,从而导致collect函数形成了list of np scalars,从而导致内存泄漏,参考于:
https://zhuanlan.zhihu.com/p/338232671
解决:
那么问题的解决就不应该是像下面说的转为什么array类型啥的,应该是修改最后getitem()返回的数据类型:
这主要是因为__getitem__
返回的是一个numpy类型的值,在dataloader
中会组成一个list of np scalars,并调用torch.tensor
,从而引起内存泄露。要解决这个问题,只需要让__getitem__
返回一个python内置的数据类型,例如在最后返回XX.tolist()....(实测了一下,在最后返回的array中,让它转为tensor类型也是可以的:torch.tensor(xxx))
注意:一般将array转list时,最好用tolist()
彻底转换过来,如果只是用list(array)
依然不能存,因为里面每个元素还是numpy中定义的类型。
注意:如果你看到内存刚开始会增加,先不要着急,看看最后是否趋于平稳,如果趋于平稳,那么就没问题
更新于2024年5月26日08:27:08:
以下内容可能是错误的:
DataLoader num_workers > 0 causes CPU memory from parent process to be replicated in all worker processes · Issue #13246 · pytorch/pytorch · GitHub
【Pytorch排坑记】:1.内存泄漏 | TianHongZXY
例子:
from torch.utils.data import Dataset, DataLoader
import numpy as np
import torchclass DataIter(Dataset):def __init__(self):# 不会导致内存一直增加# self.data_np = np.array([x for x in range(24000000)])# 会导致内存一直增加self.data = [x for x in range(24000000)]def __len__(self):return len(self.data)def __getitem__(self, idx):data = self.data[idx]data = np.array([data], dtype=np.int64)return torch.tensor(data)train_data = DataIter()
train_loader = DataLoader(train_data, batch_size=300,shuffle=True,drop_last=True,pin_memory=False,num_workers=6)for i, item in enumerate(train_loader):if i % 1000 == 0:print(i)
解决办法1:
在dataset中,将init中的数据类型换成下面的形式:
dict --> pandas
lists --> numpy arrays
也就是说,如果你的init函数中使用了列表,那么你需要将这个list转为np.array形式(同时指定dtype类型),
1)list包含的不是tensor类型:
可以直接np.array():
numpy_array = np.array(your_list, dtype=np.XXX)
2)list包含的是tensor类型:
例如你的list包含了N个tensor,那么你应该将list中的每个tensor转为numpy形式,然后再np.array():
numpy_list = [tensor.numpy() for tensor in your_tensor_list]numpy_array = np.array(numpy_list, dtype=np.float32)
你也可以先使用torch.stack(),然后再 np.array():
stacked_tensor = torch.stack(your_tensor_list)numpy_array = np.array(stacked_tensor, dtype=np.float32)
不可以直接:
numpy_array = np.array(your_tensor_list, dtype=np.float32)
解决办法2(推荐):
可以在getitem中设置深拷贝,然后init中此时就无需设置np.array这种复杂操作
class DataIter(Dataset):def __init__(self):# 不会导致内存一直增加# self.data = np.array([x for x in range(24000000)],dtype=np.str_)# self.data = np.array([x for x in range(24000000)],dtype=np.int64)# 会导致内存一直增加self.data = [x for x in range(24000000)]def __len__(self):return len(self.data)def __getitem__(self, idx):data = copy.deepcopy(self.data[idx])# data = np.array([data], dtype=np.int64)return torch.tensor(data)
PyTorch训练模型,内存泄露问题解决_python程序内存泄露改num workers-CSDN博客
备注:np.array()的dtype类型都有什么?
注意:不要使用np.object
类型,否则内存会一直增加,没有用处
详细看:https://gist.github.com/mprostock/2850f3cd465155689052f0fa3a177a50
-
整数类型
np.int8
: 8-bit 整数np.int16
: 16-bit 整数np.int32
: 32-bit 整数np.int64
: 64-bit 整数
-
无符号整数类型
np.uint8
: 8-bit 无符号整数np.uint16
: 16-bit 无符号整数np.uint32
: 32-bit 无符号整数np.uint64
: 64-bit 无符号整数
-
浮点数类型
np.float16
: 16-bit 半精度浮点数np.float32
: 32-bit 单精度浮点数np.float64
: 64-bit 双精度浮点数
-
复数类型
np.complex64
: 32-bit 实部和虚部的复数np.complex128
: 64-bit 实部和虚部的复数
-
布尔类型
np.bool_
: 布尔类型
-
对象类型
np.object_
: 通用对象类型
-
字符串类型
np.str_
: 固定长度的字符串类型np.unicode_
: 固定长度的 Unicode 类型
不将list设置为np.array的可能后果
错误一:
[06:55:34.417285] RuntimeError: unable to mmap 2048 bytes from file </torch_106078_2225082816_70220>: Cannot allocate memory (12)
错误二:
OSError: [Errno 24] Too many open files