清理Tensorflow GPU内存,用于模型执行后

80

我已经训练了3个模型,并且正在运行代码,按顺序加载每个模型的3个检查点并使用它们进行预测。我正在使用GPU。

当第一个模型被加载时,它会预先分配整个GPU内存(这是我用来处理第一批数据的)。但是它完成后不会卸载内存。当加载第二个模型时,即使在使用tf.reset_default_graph()with tf.Graph().as_default()时,第一个模型仍然完全占用了GPU内存,第二个模型就会因为缺乏内存而无法运行。

是否有其他解决方法,而不是使用Python子进程或多进程来解决这个问题(这是我通过谷歌搜索找到的唯一解决方案)?


如果您删除会话(del sess),那么这应该对内存产生与重新启动进程相同的影响。 - Yaroslav Bulatov
1
sess.close()(或使用with将Session作为上下文)也应该可以工作,不是吗? - etarion
1
我希望我能使用 with ... sess: 并尝试过 sess.close()。GPU内存并没有被清除,清除默认图并重新构建它似乎并不起作用。也就是说,即使在模型之间放置10秒的暂停,我也看不到nvidia-smi上GPU内存被清除。这并不一定意味着tensorflow在幕后没有正确处理事情,只是保持其内存分配不变。但我在验证这种推理时遇到了麻烦。 - David Parks
3
nvidia-smi 不能正确报告可供 TensorFlow 使用的内存量。当 TensorFlow 计算释放内存时,它仍会显示为被外部工具所占用,但此内存可供 TensorFlow 中的其他计算使用。 - Yaroslav Bulatov
1
@YaroslavBulatov 我进行了更多的测试,并确认在重置默认图后,tensorflow在第二个和第三个模型上表现如预期。如果您将其发布为答案,我将接受它作为正确答案。看起来这个问题是无关紧要的,但可能经常被问到,所以值得保持开放状态。 - David Parks
10个回答

39

您可以使用 numba 库来释放所有 GPU 内存。

pip install numba 
from numba import cuda 
device = cuda.get_current_device()
device.reset()

这将释放所有内存


11
这会使GPU处于不良状态。 - mradul dubey
20
您能否解释一下“使GPU处于不稳定状态”是什么意思?这并没有告诉我们使用此方法的后果。 - Scott White
6
我猜他的意思是这导致了一个错误:“ .\tensorflow/core/kernels/random_op_gpu.h:232] Non-OK-status: GpuLaunchKernel(FillPhiloxRandomKernelLaunch<Distribution>, num_blocks, block_size, 0, d.stream(), gen, data, size, dist) status: Internal: invalid resource handle”,至少对我来说是这种情况。 - Hagbard
6
也许他所说的“bad state”是指这会导致内核崩溃。在长时间运行的进程中无法进行此操作。 - Jed
是的,这会清除GPU并终止内核。 - tikendraw
不,这并不会杀死Jupyter内核。它只是清除了GPU中的所有内容。 - MikeB2019x

38

2016年6月的一篇git问题(https://github.com/tensorflow/tensorflow/issues/1727)指出存在以下问题:

目前GPUDevice中的内存分配器属于ProcessState,它本质上是一个全局单例。使用GPU的第一个会话初始化它,并在进程关闭时释放自己。

因此,唯一的解决方法是在计算后使用进程并关闭它们。

示例代码:

import tensorflow as tf
import multiprocessing
import numpy as np

def run_tensorflow():

    n_input = 10000
    n_classes = 1000

    # Create model
    def multilayer_perceptron(x, weight):
        # Hidden layer with RELU activation
        layer_1 = tf.matmul(x, weight)
        return layer_1

    # Store layers weight & bias
    weights = tf.Variable(tf.random_normal([n_input, n_classes]))


    x = tf.placeholder("float", [None, n_input])
    y = tf.placeholder("float", [None, n_classes])
    pred = multilayer_perceptron(x, weights)

    cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=pred, labels=y))
    optimizer = tf.train.AdamOptimizer(learning_rate=0.001).minimize(cost)

    init = tf.global_variables_initializer()

    with tf.Session() as sess:
        sess.run(init)

        for i in range(100):
            batch_x = np.random.rand(10, 10000)
            batch_y = np.random.rand(10, 1000)
            sess.run([optimizer, cost], feed_dict={x: batch_x, y: batch_y})

    print "finished doing stuff with tensorflow!"


if __name__ == "__main__":

    # option 1: execute code with extra process
    p = multiprocessing.Process(target=run_tensorflow)
    p.start()
    p.join()

    # wait until user presses enter key
    raw_input()

    # option 2: just execute the function
    run_tensorflow()

    # wait until user presses enter key
    raw_input()

如果你在创建的进程中调用函数run_tensorflow()并关闭该进程(选项1),则会释放内存。如果仅运行run_tensorflow()(选项2),则在函数调用后不会释放内存。


我写了一个小型可重用包装器,使用与此答案相同的技巧。然而,性能下降非常严重,这对于小型计算(即在小型数据集上进行推理)是可以接受的,但在任何其他情况下都不实用。我认为这必须是由于进程间通信和来回传递大型numpy对象所致。 - Ben Usman
我可以在加载Keras模型后使用这个来释放GPU内存吗? - Austin
1
相关且重要的是,multiprocessing.Process在Windows上默认使用spawn,但在*nix系统上使用fork。如果您发现在单独的进程中运行的模型无法使用GPU,即在检查跨平台兼容性时tf.test.is_gpu_availableFalse,则可以使用multiprocessing.get_context('spawn')强制选择状态。 spawn可用于Windows、Linux和MacOS。有关上下文的更多信息,请参见此处 - mradul dubey
这是一个非常出色的答案。 - Foivos Ts
那么如何让Jupyter Notebook支持多进程呢? - huang
2
我无法让它工作,也无法使用Ben Usman列出的小型可重用包装器。问题在于如果使用“spawn”,parallel_wrapper无法进行picklable处理,如果使用“fork”,该进程将会卡住。令人难以置信的是,在TF经过近4年发展后,这仍然是一个严重的问题。是否有人知道一个好的解决方案? - Jed

9

我使用 numba 来释放 GPU。但是在 TensorFlow 中,我找不到有效的方法。

import tensorflow as tf
from numba import cuda

a = tf.constant([1.0,2.0,3.0],shape=[3],name='a')
b = tf.constant([1.0,2.0,3.0],shape=[3],name='b')
with tf.device('/gpu:1'):
    c = a+b

TF_CONFIG = tf.ConfigProto(
gpu_options=tf.GPUOptions(per_process_gpu_memory_fraction=0.1),
  allow_soft_placement=True)

sess = tf.Session(config=TF_CONFIG)
sess.run(tf.global_variables_initializer())
i=1
while(i<1000):
        i=i+1
        print(sess.run(c))

sess.close() # if don't use numba,the gpu can't be released
cuda.select_device(1)
cuda.close()
with tf.device('/gpu:1'):
    c = a+b

TF_CONFIG = tf.ConfigProto(
gpu_options=tf.GPUOptions(per_process_gpu_memory_fraction=0.5),
  allow_soft_placement=True)

sess = tf.Session(config=TF_CONFIG)

sess.run(tf.global_variables_initializer())
while(1):
        print(sess.run(c))

4
在运行 cuda.close(1) 后,尝试运行 tf.Session() 时,代码报错并提示 tensorflow.python.framework.errors_impl.InternalError: Failed to create session. 前面还有一句 Failed precondition: Failed to memcopy into scratch buffer for device 0 - guillefix
这会让TensorFlow处于糟糕的状态。 - Neil G
1
我试图找到一些从Kaggle笔记本释放GPU内存的东西,因为在利用基于tensorflow-gpu的推理进行特征工程之后,我需要在GPU上运行XGBoost,这个方法非常好。Numba预装了,我只需要使用del model_object gc.collect() from numba import cuda cuda.select_device(0)cuda.close() - Deepak Sadulla

7

我刚刚通过垃圾收集器解决了一个OOM错误。

import gc
gc.collect()

model.evaluate(x1, y1)
gc.collect()

model.evaluate(x2, y2)
gc.collect()

根据Yaroslav Bulatov在他们的回答中所说(即tf在对象销毁时释放GPU内存),我推断可能只是垃圾回收器还没有运行。强制进行垃圾回收使我摆脱了问题,所以这可能是一个好方法。


1
这对我来说简直就像魔法一样有效! - nklsla

5
现在有两种方法来解决迭代式训练模型,或者如果您使用未来的多进程池来进行模型训练,那么使用池中的进程不会被杀死,即使未来已经完成。您可以在训练过程中应用两种方法释放GPU内存,同时希望保留主进程。
  1. 调用子进程运行模型训练。当单个阶段的训练完成后,子进程将退出并释放内存。很容易获得返回值。
  2. 调用multiprocessing.Process(p)来运行模型训练(p.start),p.join将指示进程退出并释放内存。
这是一个辅助函数,使用multiprocess.Process可以打开一个新进程来运行您编写的Python函数,并返回值,而不是使用Subprocess。
# open a new process to run function
def process_run(func, *args):
    def wrapper_func(queue, *args):
        try:
            logger.info('run with process id: {}'.format(os.getpid()))
            result = func(*args)
            error = None
        except Exception:
            result = None
            ex_type, ex_value, tb = sys.exc_info()
            error = ex_type, ex_value,''.join(traceback.format_tb(tb))
        queue.put((result, error))

    def process(*args):
        queue = Queue()
        p = Process(target = wrapper_func, args = [queue] + list(args))
        p.start()
        result, error = queue.get()
        p.join()
        return result, error  

    result, error = process(*args)
    return result, error

我不确定我理解您所谈论的两种方法之间的区别。它们对我来说都只是“使用多进程”。而且已经有一个关于它更详细的不错的答案了。链接 - Igor
在我的理解中,“多进程”和“子进程”,它们都会生成新的进程来处理GPU运行并释放,但是它们的操作方式不同。 - liviaerxin
1
应该注意的是,这里使用的是multiprocessing.Queue而不是queue.Queue。 - Michael Malak

4

我正在考虑在Jupyter Notebook中选择哪种选项更好。即使深度学习应用程序已经完成,Jupyter Notebook仍会永久占用GPU内存。这通常会导致GPU风扇错误,是一个大问题。在这种情况下,我必须定期重置nvidia_uvm并重新启动Linux系统。我总结出以下两个选项可以消除GPU风扇错误的问题,但想知道哪个更好。

环境:

  • CUDA 11.0
  • cuDNN 8.0.1
  • TensorFlow 2.2
  • Keras 2.4.3
  • Jupyter Notebook 6.0.3
  • Miniconda 4.8.3
  • Ubuntu 18.04 LTS

第一种选项

将以下代码放在最后一个单元格中。应用程序运行时,内核立即结束。但它并不太优雅。Jupyter会弹出一个消息,表示内核已死亡。

import os
 
pid = os.getpid()
!kill -9 $pid

选择项

以下代码也可以用于结束Jupyter Notebook的内核。我不知道numba是否安全。Nvidia更喜欢“0” GPU,这是个人开发者(而不是服务器管理员)最常用的GPU。但是,Neil G和mradul dubey都得到了这样的反应:这会导致GPU处于糟糕的状态。

from numba import cuda

cuda.select_device(0)
cuda.close()

看起来第二个选项更优雅。有人能确认哪个是最好的选择吗?

备注:

在Anaconda环境下,通过直接执行“$ python abc.py”来自动释放GPU内存并不是问题。但是,我有时需要使用Jyputer Notebook来处理.ipynb应用程序。


我的测试表明numba是更好的选择。然而,用户需要使用pip install numba而不是conda install -c numba mumba或sudo apt-get install python-3 numba。conda install...存在内部冲突,sudo apt-get install...无法使用。 - Mike Chen
3
如果在执行了cuda.close()后运行任何代码,则会出现“无法同步CUDA流:CUDA_ERROR_INVALID_HANDLE:无效的资源句柄”错误,是否有方法可以清除CUDA内存而不出现此错误? - Hemanth Kollipara
我已经使用了第一种选项几次,对我来说效果很好。谢谢。我在Tensorflow 2.4上进行了测试。 - mastDrinkNimbuPani

2
张量分配的GPU内存在张量不再需要时(在.run调用结束之前)立即释放(返回到TensorFlow内存池)。变量分配的GPU内存在变量容器被销毁时释放。对于DirectSession(即,sess=tf.Session("")),在会话关闭或显式重置时释放(添加在62c159ff中)。

11
目前,TensorFlow 仍然无法通过 sess.Close() 或 tf.Session() as sess: 后立即释放 GPU 内存。请您考虑上述评论更新您的回答。 - Fedor Chervinskii
@yaroslav-bulatov在您的评论中提到nvidia-smi不能正确显示GPU上的内存。我尝试了tf.reset_default_graph(),然后重建了以前的图形,但是我遇到了OOM错误,这表明nvidia-smi正确地显示了内存。有什么想法吗? - Diego Aguado
@DiegoAgher 我的意思是nvidia-smi可能显示可用内存为0,但仍有足够的内存供TensorFlow使用。原因是TensorFlow接管了内存管理。 - Yaroslav Bulatov
1
@yaroslavBulatov,如果TensorFlow池仍然存在,并且在重新构建图形时仍然出现OOM错误,您会如何释放GPU上的空间? - Diego Aguado
它会自动释放。TensorFlow中的OOM错误通常是由于模型过大引起的。 - Yaroslav Bulatov
1
我也遇到了OOM错误,似乎是由于变量没有被释放所致。例如,模型会运行和训练多次,但在重新分配变量(而不改变总大小)后,可能会出现OOM错误。关闭Spyder并重新打开是我唯一的解决方法。 - Fosa

1
我在使用for循环为不同的参数训练模型时,训练了120个模型后出现了错误。之后,如果我不杀死内核,甚至不能训练一个简单的模型。 在构建模型之前,我通过添加以下行来解决我的问题:

tf.keras.backend.clear_session()

请参见https://www.tensorflow.org/api_docs/python/tf/keras/backend/clear_session

0
我认为,Oliver Wilken的用户(2017年6月30日8:30)是最好的解决方案。 但在tensorflow 2中,不再使用会话,而是使用即时执行。 所以你只需要在run_tensorflow(...)函数中导入所有的tensorflow和keras相关内容。

0
为了释放我的资源,我使用以下代码:
import os, signal

os.kill(os.getpid(), signal.SIGKILL)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接