[内存泄漏][PyTorch](create_graph=True)

1. 内存泄漏定义

  内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

2. 问题发现背景

  在使用深度学习求解PDE时,由于经常需要计算高阶导数,在pytorch框架下写的代码需要用到torch.autograd.grad(create_graph=True)或者torch.backward(create_graph=True)这个参数,然后发现了这个内存泄漏的问题。如果要保存计算图用来计算高阶导数,那么其所占的内存不会被释放,会一直占用。也就是如果设置create_graph=True,那么其保存的计算图所占的内存只有在程序运行结束时才会释放,这样导致了一个问题,即如果在循环中需要保存计算图,例如每个循环都需要计算一次黑塞矩阵,那么这个内存占用就会越来越多,最终导致out of memory报错。
在这里插入图片描述

3. github中pytorch源码关于这个问题的讨论

  官网中关于这个问题的讨论见https://github.com/pytorch/pytorch/issues/7343,这里提出的内存泄漏的例子如下:

import torch
import gc

_ = torch.randn(1, device='cuda')
del _
torch.cuda.synchronize()
gc.collect()
print(torch.cuda.memory_allocated())
x = torch.randn(1, device='cuda', requires_grad=True)
y = x.tanh()
y.backward(torch.ones_like(y), create_graph=True)
del x, y
torch.cuda.synchronize()
gc.collect()
print(torch.cuda.memory_allocated())

在这里插入图片描述
可以看到虽然删除了变量,依然造成了内存泄漏。这里红色的警告就是关于这个内存泄漏的问题。

UserWarning: Using backward() with create_graph=True will create a reference cycle between
the parameter and its gradient which can cause a memory leak. We recommend using autograd.grad 
when creating the graph to avoid this. If you have to use this function, make sure to reset 
the .grad fields of your parameters to None after use to break the cycle and avoid the leak. 
(Triggered internally at C:\cb\pytorch_1000000000000\work\torch\csrc\autograd\engine.cpp:1000.)
allow_unreachable=True, accumulate_grad=True) 
# Calls into the C++ engine to run the backward pass

看这个UserWarning,提示我们使用torch.autograd.grad()函数可以避免这个梯度泄漏,然后对代码进行改动:

import torch
import gc
from torch.autograd import grad

_ = torch.randn(1, device='cuda')
del _
torch.cuda.synchronize()
gc.collect()
print(torch.cuda.memory_allocated())
x = torch.randn(1, device='cuda', requires_grad=True)
y = x.tanh()
z = grad(y, x, retain_graph=True, create_graph=True)
# y.backward(torch.ones_like(y), create_graph=True)
del x, y, z
torch.cuda.synchronize()
gc.collect()
print(torch.cuda.memory_allocated())

在这里插入图片描述
结果显示没有梯度泄漏。进一步,我们求一下二阶导数:

import torch
import gc
from torch.autograd import grad

_ = torch.randn(1, device='cuda')
del _
torch.cuda.synchronize()
gc.collect()
print(torch.cuda.memory_allocated())
x = torch.randn(1, device='cuda', requires_grad=True)
y = x.tanh()
z = grad(y, x, retain_graph=True, create_graph=True)
print(torch.cuda.memory_allocated())
q = grad(z, x)
del x, y, z, q
torch.cuda.synchronize()
gc.collect()
print(torch.cuda.memory_allocated())

在这里插入图片描述
结果也没有内存泄漏。但是,如果我们不删除结果二阶导数q,这样是出于如果写在一个函数中,需要将q作为return值返回的情况。

import torch
import gc
from torch.autograd import grad

_ = torch.randn(1, device='cuda')
del _
torch.cuda.synchronize()
gc.collect()
print(torch.cuda.memory_allocated())
x = torch.randn(1, device='cuda', requires_grad=True)
y = x.tanh()
z = grad(y, x, retain_graph=True, create_graph=True)
print(torch.cuda.memory_allocated())
q = grad(z, x)
del x, y, z
torch.cuda.synchronize()
gc.collect()
print(torch.cuda.memory_allocated())

在这里插入图片描述
可以看到,这还是会导致一部分内存泄漏。这里记录一下这个问题,有读者有遇到相同问题欢迎讨论。

### PyTorch 的 `autograd.grad` 函数详解 #### 一、`autograd.grad` 基本功能 PyTorch 提供了强大的自动微分机制,其中 `torch.autograd.grad` 是用于计算张量梯度的核心工具之一。它允许用户针对输入张量精确地获取输出张量的梯度值[^2]。 该函数的主要作用是对指定的张量执行反向传播操作,并返回对应的梯度结果。其基本形式如下: ```python import torch from torch import autograd x = torch.tensor([1.0, 2.0], requires_grad=True) y = x ** 2 grads = autograd.grad(outputs=y, inputs=x, grad_outputs=torch.ones_like(y)) print(grads[0]) # 输出梯度 [2., 4.] 对应于 d(x^2)/dx ``` 上述代码展示了如何利用 `autograd.grad` 计算简单表达式的梯度[^3]。 --- #### 二、参数解析 以下是 `autograd.grad` 的主要参数及其含义: - **outputs**: 需要对其求导的目标张量。 - **inputs**: 输入张量列表或单个张量,表示需要计算哪些变量相对于目标张量的变化率。 - **grad_outputs**: 可选参数,默认为 None。当 output 不是一个标量时,此参数用来定义权重矩阵以组合多个梯度方向。 - **retain_graph**: 是否保留计算图以便后续重复使用。 - **create_graph**: 控制是否创建动态计算图支持高阶导数计算。 - **allow_unused**: 如果某些 input 并未参与 outputs 的计算,则不会抛出错误而是返回 None。 特别需要注意的是,在处理复杂模型或者涉及多维数据的情况下,合理配置这些选项能够显著提升性能并简化调试过程。 --- #### 三、`create_graph=True` 的场景及意义 当设置 `create_graph=True` 时,意味着当前的梯度计算会被记录到计算图中,从而使得我们可以进一步对其进行更高阶次的操作——比如再次取导来获得 Hessian 矩阵等信息[^1]。 下面的例子演示了如何借助这一特性实现二阶偏导数的计算: ```python x = torch.tensor([3.0], requires_grad=True) # 定义原始函数 f(x)=x^2 y = x ** 2 first_order_gradient = autograd.grad( outputs=y, inputs=x, grad_outputs=torch.ones_like(y), create_graph=True )[0] second_order_gradient = autograd.grad( outputs=first_order_gradient, inputs=x, grad_outputs=torch.ones_like(first_order_gradient) )[0] print(f"First order gradient: {first_order_gradient}") # 应打印 'tensor([6.], ...)' print(f"Second order gradient: {second_order_gradient}") # 应打印 'tensor([2.], ...)' ``` 这里可以看到,第一次调用 `autograd.grad` 得到了关于 \(f'(x)\),第二次则得到了\(f''(x)\)。如果没有开启 `create_graph` ,那么尝试访问第二层梯度将会失败因为缺少必要的中间节点信息被销毁掉了。 --- #### 四、实际应用案例分析 假设我们有一个更复杂的损失函数 L(w,b),希望研究 w 和 b 各自的影响程度以及相互关系的话,就可以采用这种方式逐步深入挖掘潜在规律。例如在线性回归问题里调整学习速率策略时候经常需要用到此类技术手段辅助决策制定。 另外值得注意的一点是在训练神经网络期间如果频繁切换正向/逆向模式可能会带来额外开销因此建议尽可能集中完成所有必要步骤后再统一释放资源减少不必要的内存占用情况发生几率提高整体效率表现水平达到预期效果最大化目的为止结束整个流程才算真正意义上的成功实践经历分享完毕谢谢大家耐心阅读本文内容希望能给大家带来一定启发帮助共同进步成长成为更好的自己加油吧朋友们! ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值