计算 Python 代码的内存和模型显存消耗的小技巧

liftword1个月前 (03-20)技术文章11

了解Python代码的内存消耗是每一个开发人员都必须要解决的问题,这个问题不仅在我们使用pandas读取和处理CSV文件的时候非常重要,在我们使用GPU训练的时候还需要规划GPU的内存使用。尤其是我们在白嫖使用kaggle和colab时显得更为重要。

本篇文章我们将介绍两个 Python 库 memory_profiler和Pytorch-Memory-Utils这两个库可以帮助我们了解内存和显存的消耗。

memory_profiler

pip install memory_profiler#Load its magic function
%load_ext memory_profiler
from memory_profiler import profile

memory_profiler可以完成以下的工作:

1、查找一行的内存消耗

我们只需要在代码的前面加上魔法函数 %memit

%memit x = 10+5
#Output
peak memory: 54.01 MiB, increment: 0.27 MiB

这里,峰值内存(peak memory)是运行此代码的进程消耗的内存。增量只是由于添加这行代码而需要/消耗的内存。同样的逻辑也适用于以下其他的显示。

2、查找函数的内存消耗

在调用函数的行的开头添加魔法函数。

def addition():
    a = [1] * (10 ** 1)
    b = [2] * (3 * 10 ** 2)
    sum = a+b
    return sum

%memit addition()
#Output
peak memory: 36.36 MiB, increment: 0.01 MiB

3、逐行查找函数的内存消耗

如果需要记录函数中每一行的内存使用,我们可以使用@profile 装饰器。 但是@profile 仅适用于在单独模块中定义的函数,因此我们将首先使用 %%file 创建一个名为 demo.py 的简单模块,其中包含我们的函数

%%file demo.py
from memory_profiler import profile

@profile
def addition():
    a = [1] * (10 ** 1)
    b = [2] * (3 * 10 ** 2)
    sum = a+b
    return sum

现在,我们可以调用该函数

from demo import addition

%memit addition()

#Output
Line #    Mem usage    Increment   Line Contents
================================================
     2     36.4 MiB     36.4 MiB   @profile
     3                             def addition():
     4     36.4 MiB      0.0 MiB       a = [1] * (10 ** 1)
     5   3851.1 MiB   3814.7 MiB       b = [2] * (3 * 10 ** 2)
     6   7665.9 MiB   3814.8 MiB       sum = a+b
     7   7665.9 MiB      0.0 MiB       return sum
peak memory: 7665.88 MiB, increment: 7629.52 MiB

4、完整python脚本的内存消耗

这个这个方法不能再 notebook 中使用。我们必须创建 python 脚本并通过命令行运行它。

#create script.py
import time

@profile
def function1():
    n = 100000
    a = [1] * n
    time.sleep(1)
    return a
    
@profile
def function2():
    n = 200000
    b = [1] * n
    time.sleep(1)
    return b

if __name__ == "__main__":
    function1()
    function2()

之后运行脚本并查看

#On command line
mprof run script.py
#To generate plot 
mprof plot

我们可以看到内存消耗与时间的关系图

@profile装饰器没有必要放在函数前面,如果我们不保留它,我们不会看到函数级内存消耗,但我们会看到整个脚本的内存消耗

Pytorch-Memory-Utils

通过Pytorch-Memory-Utils工具,我们在使用显存的代码中间插入检测函数,这样就可以输出在当前行代码时所占用的显存。这个对于我们计算模型的GPU显存占用是非常方便的,通过计算显存占用,我们才能够最大化训练的batch size,保证训练的最优速度。

import torch
import inspect

from torchvision import models
from gpu_mem_track import MemTracker  # 引用显存跟踪代码

device = torch.device('cuda:0')

frame = inspect.currentframe()     
gpu_tracker = MemTracker(frame)      # 创建显存检测对象

gpu_tracker.track()                  # 开始检测
cnn = models.vgg19(pretrained=True).to(device)  # 导入VGG19模型并且将数据转到显存中
gpu_tracker.track()

然后可以发现程序运行过程中的显存变化(第一行是载入前的显存,最后一行是载入后的显存):

At __main__ : line 13                        Total Used Memory:472.2  Mb

+ | 1 * Size:(128, 64, 3, 3)      | Memory: 0.2949 M | 
+ | 1 * Size:(256, 128, 3, 3)     | Memory: 1.1796 M | 
+ | 1 * Size:(64, 64, 3, 3)       | Memory: 0.1474 M | 
+ | 2 * Size:(4096,)              | Memory: 0.0327 M | 
+ | 1 * Size:(512, 256, 3, 3)     | Memory: 4.7185 M | 
+ | 2 * Size:(128,)               | Memory: 0.0010 M | 
+ | 1 * Size:(1000, 4096)         | Memory: 16.384 M | 
+ | 6 * Size:(512,)               | Memory: 0.0122 M | 
+ | 1 * Size:(64, 3, 3, 3)        | Memory: 0.0069 M | 
+ | 1 * Size:(4096, 25088)        | Memory: 411.04 M | 
+ | 1 * Size:(4096, 4096)         | Memory: 67.108 M | 
+ | 5 * Size:(512, 512, 3, 3)     | Memory: 47.185 M | 
+ | 2 * Size:(64,)                | Memory: 0.0005 M | 
+ | 3 * Size:(256,)               | Memory: 0.0030 M | 
+ | 1 * Size:(128, 128, 3, 3)     | Memory: 0.5898 M | 
+ | 2 * Size:(256, 256, 3, 3)     | Memory: 4.7185 M | 
+ | 1 * Size:(1000,)              | Memory: 0.004 M | 

At __main__ : line 15                        Total Used Memory:1387.5 Mb

通过上面的报告,很容易发现一个问题。

首先我们知道VGG19所有层的权重大小加起来大约是548M(这个数值来源于Pytorch官方提供的VGG19权重文件大小),我们将上面报告打印的Tensor-Memory也都加起来算下来也差不多551.8Mb。但是,我们算了两次打印的显存实际占用中:1387.5 – 472.2 = 915.3 MB

Pytorch在开始运行程序时需要额外的显存开销,这种额外的显存开销与我们实际使用的模型权重显存大小无关。 这个额外的显存Pytorch的开发者也对此进行说明了,这部分释放后的显存可以用,只不过不在Nvidia-smi中显示罢了,所以我们无需关注。



相关文章

分享一个文件整理使用Python小脚本(删除相同文件减小内存占用)

写在前面:脚本作用:在目标文件夹中筛选出大小相同的文件,并打印在屏幕上,这样可以快速找出相同文件,释放硬盘内存。前期部署:安装python并做好配置环境变量(基础)先上代码:import osfrom...

python如何进行内存管理

关于首先python进行内存管理就不得不提到Python解释器在何种情况下会释放变量的内存。Python引用了内存计数这一简单的计数来控制。当一个变量的引用计数为0的时候,就会被解释器回收。当然在交互...

一文掌握Python内存管理

Python中的内存是由Python内存管理器(Python memory manager)来管理的,它涉及到一个包含所有Python对象和数据结构的私有堆(heap)。Python内存管理器有不同的...

喂!你这个python的内存是不是爆了?喏,拿这个去测试一下看看

你是否曾经为python程序的内存占用问题头疼不已?是否觉得内存泄漏、对象占用过多内存等问题难以排查?别急,今天我要介绍一个神器——pympler,让你轻松搞定python内存分析!01什么是pymp...

面试必备:Python内存管理机制(建议收藏)

什么是内存管理器(what)Python作为一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言,与大多数编程语言不同,Python中的变量无需事先申明,变量无需指定类型,程序员无需关心内存管...

面试必备 | Python 垃圾回收机制

众所周知,Python 是一门面向对象语言,在 Python 的世界一切皆对象。所以一切变量的本质都是对象的一个指针而已。Python 运行过程中会不停的创建各种变量,而这些变量是需要存储在内存中的,...