乐趣区

关于深度学习:使用上下文装饰器调试Pytorch的内存泄漏问题

装璜器是 python 上下文管理器的特定实现。本片文章将通过一个 pytorch GPU 调试的示例来阐明如何应用它们。尽管它可能不适用于所有状况,但我它们却是十分有用。

调试内存透露问题

有很多办法能够调试内存透露。本文将展现一种辨认代码中有问题的行的有用办法。该办法能够有助于以简洁的形式找到具体的地位。

逐行手动调试

如果遇到问题,一种经典的且罕用的办法是应用调试器逐行查看,比方上面的例子:

  • 在搜索引擎查找无关如何计算 pytorch 中所有张量总数的代码片段,比方:tensor-counter-snippet
  • 在代码中设置断点
  • 应用 tensor-counter-snippet 来取得张量的总数统计
  • 应用调试器执行下一步操作
  • 从新运行 tensor-counter-snippet,并查看张量计数是否减少
  • 反复下面的步骤

    它能够工作,但这样的操作光听起来来就很麻烦。咱们能够将其封装成一个函数,这样能够在须要的时候调用,这样简直不须要批改现有的代码,所以就引出了咱们要介绍装璜器的性能。

Python 装璜器

装璜器能够包装在代码的任意局部。这里咱们通过装璜器来实现查看是否有额定的张量,除此以外咱们还须要一个计数器,因为须要在执行之前和之后计算张量的数量。该模式如下所示:

 def memleak_wrapper(func):
     def wrap(*args, **kwargs):
         print("num tensors start is ...")
         out = func(*args, **kwargs)
         print("num tensors end is ...")
         return out
     return [email protected]_wrapper
 def function_to_debug(x):
     print(f"put line(s) of code here. Input is {x}")
     out = x + 10
     return outout = function_to_debug(x=1000)
 print(f"out is {out}")
 
 #输出相似这样
 #num tensors start is ...
 #put line(s) of code here. Input is 1000
 #num tensors end is ...
 #outis 1010

要运行这个代码,咱们就须要将要查看的代码行放入函数 (function_to_debug)。然而这不是最好的,因为咱们还须要手动插入很多代码。另外就是如果代码块生成的变量多于一个,还须要寻找额定的解决方案来应用这些上游变量。

上下文装璜器

为了解决下面问题,咱们的能够应用上下文管理器来代替函数装璜器。上下文管理器最宽泛应用的示例是应用

with

语句实例化上下文。以前最常见的就是:

 with open("file") as f:
  …

应用 Python 的 contextlib 库,Python 用户能够轻松地本人创立上下文管理器。所以本文中咱们将应用 ContextDecorator 实现在下面尝试应用 decorator 所做的工作。因为它但更容易开发,也更容易应用:

 from contextlib import ContextDecorator
 
 class check_memory_leak_context(ContextDecorator):
     def __enter__(self):
         print('Starting')
         return self
 
     def __exit__(self, *exc):
         print('Finishing')
         return False

ContextDecorator 有 2 个办法:enter() 和 exit(),当咱们进入或退出上下文时会调用它们。__exit__ 中的 *exc 参数代表任何传入的异样。

当初咱们来应用它解决下面说的问题。

应用 ContextDecorator 找出内存透露

因为要计算张量的总数,所以咱们将计算过程封装成一个函数 get_n_tensors(),这样能够在上下文开始和完结时来计算张量数量:

 class check_memory_leak_context(ContextDecorator):
    def __enter__(self):
        self.start = get_n_tensors()
        return self    def __exit__(self, *exc):
         self.end = get_n_tensors()
         increase = self.end — self.start
         
         if increase > 0:
              print(f”num tensors increased with"\
                    f"{self.end — self.start} !”)
         else:
              print(”no added tensors”)
         return False

如果有减少,则将其打印到控制台。

get_n_tensor()应用垃圾收集器(gc),是为 pytorch 定制的,但能够很容易地批改为其余的库:

 import gc
 def get_n_tensors():
     tensors= []
     for obj in gc.get_objects():
     try:
         if (torch.is_tensor(obj) or
         (hasattr(obj,‘data’) and
         torch.is_tensor(obj.data))):
             tensors.append(obj)
      except:
          pass
      return len(tensors)

当初就能够应用了,咱们对任何一行 (或块) 代码应用这个上下文:

 x = arbitrary_operation(x)
 ...
 with check_memory_leak_context():
     y = x[0].permute(1, 2, 0).cpu().detach().numpy()
     x = some_harmless_operation()
 ...
 x = another_arbitrary_operation(x)

如果上下文润饰器包装的行内创立了一个新的张量,它就会打印进去。

总结

这是一个十分好的代码片段,你能够在开发过程中把它放在一个独自的文件中,上面是本文的残缺代码:

https://avoid.overfit.cn/post/40d81e2235d345ed9f25d2221af7cbcf

最初心愿这篇小文章能让你理解什么是上下文管理器,如何应用上下文装璜器,以及如何将它们利用于调试 pytorch。

作者:MarkTension

退出移动版