Un laboratorio de gestión de memoria CUDA simple y preciso para Pytorch, consiste en diferentes partes sobre la memoria:
Características:
line_profiler con API simple.%mlrun / %%mlrun Line / Cell Magic comandos.Tabla de contenido
pip install pytorch_memlabpip install git+https://github.com/stonesjtu/pytorch_memlabLos errores fuera de la memoria en Pytorch ocurren con frecuencia, para los recién nacidos y programadores experimentados. Una razón común es que la mayoría de las personas realmente no aprenden la filosofía de gestión de memoria subyacente de Pytorch y GPU. Escribieron la memoria de códigos ineficientes y se quejaron de que Pytorch comió demasiada memoria CUDA.
En este repositorio, voy a compartir algunas herramientas útiles para ayudar a la depuración de OOM, o para inspeccionar el mecanismo subyacente si alguien está interesado.
El Profiler de memoria es una modificación de line_profiler de Python, proporciona la información de uso de la memoria para cada línea de código en la función/método especificado.
import torch
from pytorch_memlab import LineProfiler
def inner ():
torch . nn . Linear ( 100 , 100 ). cuda ()
def outer ():
linear = torch . nn . Linear ( 100 , 100 ). cuda ()
linear2 = torch . nn . Linear ( 100 , 100 ). cuda ()
linear3 = torch . nn . Linear ( 100 , 100 ). cuda ()
work ()Después de que el script termina o se interrumpe por teclado, proporciona la siguiente información de perfil si está en un cuaderno de Jupyter:

o la siguiente información si está en una terminal de solo texto:
## outer
active_bytes reserved_bytes line code
all all
peak peak
0.00B 0.00B 7 def outer():
40.00K 2.00M 8 linear = torch.nn.Linear(100, 100).cuda()
80.00K 2.00M 9 linear2 = torch.nn.Linear(100, 100).cuda()
120.00K 2.00M 10 inner()
## inner
active_bytes reserved_bytes line code
all all
peak peak
80.00K 2.00M 4 def inner():
120.00K 2.00M 5 torch.nn.Linear(100, 100).cuda()
Una explicación de lo que significa cada columna se puede encontrar en la documentación de la antorcha. El nombre de cualquier campo de memory_stats() se puede pasar a display() para ver la estadística correspondiente.
Si usa el decorador profile , las estadísticas de memoria se recopilan durante múltiples ejecuciones y solo la máxima se muestra al final. También proporcionamos una API más flexible llamada profile_every que imprime la información de memoria cada n veces de la ejecución de funciones. Simplemente puede reemplazar @profile con @profile_every(1) para imprimir el uso de memoria para cada ejecución.
El @profile y @profile_every también se pueden mezclar para obtener más control de la granularidad de depuración.
class Net ( torch . nn . Module ):
def __init__ ( self ):
super (). __init__ ()
@ profile
def forward ( self , inp ):
#do_somethingset_target_gpu . La selección de GPU es a nivel mundial, lo que significa que debe recordar en qué GPU está perfilando durante todo el proceso: import torch
from pytorch_memlab import profile , set_target_gpu
@ profile
def func ():
net1 = torch . nn . Linear ( 1024 , 1024 ). cuda ( 0 )
set_target_gpu ( 1 )
net2 = torch . nn . Linear ( 1024 , 1024 ). cuda ( 1 )
set_target_gpu ( 0 )
net3 = torch . nn . Linear ( 1024 , 1024 ). cuda ( 0 )
func () Se pueden encontrar más muestras en test/test_line_profiler.py
Asegúrese de que haya instalado IPython o haya instalado pytorch-memlab con pip install pytorch-memlab[ipython] .
Primero, cargue la extensión:
% load_ext pytorch_memlab Esto hace que el %mlrun y %%mlrun línea/mágica de celda esté disponible para su uso. Por ejemplo, en una nueva celda, ejecute lo siguiente para perfilar una celda completa
% % mlrun - f func
import torch
from pytorch_memlab import profile , set_target_gpu
def func ():
net1 = torch . nn . Linear ( 1024 , 1024 ). cuda ( 0 )
set_target_gpu ( 1 )
net2 = torch . nn . Linear ( 1024 , 1024 ). cuda ( 1 )
set_target_gpu ( 0 )
net3 = torch . nn . Linear ( 1024 , 1024 ). cuda ( 0 ) O puede invocar el Profiler para una sola declaración sobre a través de la magia de celdas %mlrun .
import torch
from pytorch_memlab import profile , set_target_gpu
def func ( input_size ):
net1 = torch . nn . Linear ( input_size , 1024 ). cuda ( 0 )
% mlrun - f func func ( 2048 ) Ver %mlrun? para obtener ayuda sobre qué argumentos son compatibles. Puede establecer el dispositivo GPU en perfil, volcar los resultados de perfil en un archivo y devolver el objeto LineProfiler para la inspección posterior al perfil.
Obtenga más información revisando el cuaderno de demostración de Jupyter
Como el perfilador de memoria solo proporciona la información general de uso de la memoria por líneas, memoria de memoria puede obtener una información de uso de memoria más de bajo nivel.
Memory Reporter itera todos los objetos Tensor y obtiene el objeto subyacente UntypedStorage (previamente Storage ) para obtener el uso real de la memoria en lugar del Tensor.size de superficie.size.
Ver UntypedStorage para obtener información detallada
import torch
from pytorch_memlab import MemReporter
linear = torch . nn . Linear ( 1024 , 1024 ). cuda ()
reporter = MemReporter ()
reporter . report ()Salidas:
Element type Size Used MEM
-------------------------------------------------------------------------------
Storage on cuda:0
Parameter0 (1024, 1024) 4.00M
Parameter1 (1024,) 4.00K
-------------------------------------------------------------------------------
Total Tensors: 1049600 Used Memory: 4.00M
The allocated memory on cuda:0: 4.00M
-------------------------------------------------------------------------------
import torch
from pytorch_memlab import MemReporter
linear = torch . nn . Linear ( 1024 , 1024 ). cuda ()
inp = torch . Tensor ( 512 , 1024 ). cuda ()
# pass in a model to automatically infer the tensor names
reporter = MemReporter ( linear )
out = linear ( inp ). mean ()
print ( '========= before backward =========' )
reporter . report ()
out . backward ()
print ( '========= after backward =========' )
reporter . report ()Salidas:
========= before backward =========
Element type Size Used MEM
-------------------------------------------------------------------------------
Storage on cuda:0
weight (1024, 1024) 4.00M
bias (1024,) 4.00K
Tensor0 (512, 1024) 2.00M
Tensor1 (1,) 512.00B
-------------------------------------------------------------------------------
Total Tensors: 1573889 Used Memory: 6.00M
The allocated memory on cuda:0: 6.00M
-------------------------------------------------------------------------------
========= after backward =========
Element type Size Used MEM
-------------------------------------------------------------------------------
Storage on cuda:0
weight (1024, 1024) 4.00M
weight.grad (1024, 1024) 4.00M
bias (1024,) 4.00K
bias.grad (1024,) 4.00K
Tensor0 (512, 1024) 2.00M
Tensor1 (1,) 512.00B
-------------------------------------------------------------------------------
Total Tensors: 2623489 Used Memory: 10.01M
The allocated memory on cuda:0: 10.01M
-------------------------------------------------------------------------------
import torch
from pytorch_memlab import MemReporter
linear = torch . nn . Linear ( 1024 , 1024 ). cuda ()
linear2 = torch . nn . Linear ( 1024 , 1024 ). cuda ()
linear2 . weight = linear . weight
container = torch . nn . Sequential (
linear , linear2
)
inp = torch . Tensor ( 512 , 1024 ). cuda ()
# pass in a model to automatically infer the tensor names
out = container ( inp ). mean ()
out . backward ()
# verbose shows how storage is shared across multiple Tensors
reporter = MemReporter ( container )
reporter . report ( verbose = True )Salidas:
Element type Size Used MEM
-------------------------------------------------------------------------------
Storage on cuda:0
0.weight (1024, 1024) 4.00M
0.weight.grad (1024, 1024) 4.00M
0.bias (1024,) 4.00K
0.bias.grad (1024,) 4.00K
1.bias (1024,) 4.00K
1.bias.grad (1024,) 4.00K
Tensor0 (512, 1024) 2.00M
Tensor1 (1,) 512.00B
-------------------------------------------------------------------------------
Total Tensors: 2625537 Used Memory: 10.02M
The allocated memory on cuda:0: 10.02M
-------------------------------------------------------------------------------
import torch
from pytorch_memlab import MemReporter
lstm = torch . nn . LSTM ( 1024 , 1024 ). cuda ()
reporter = MemReporter ( lstm )
reporter . report ( verbose = True )
inp = torch . Tensor ( 10 , 10 , 1024 ). cuda ()
out , _ = lstm ( inp )
out . mean (). backward ()
reporter . report ( verbose = True ) Como se muestra a continuación, el (->) indica la reutilización de las mismas salidas de fondo de almacenamiento:
Element type Size Used MEM
-------------------------------------------------------------------------------
Storage on cuda:0
weight_ih_l0 (4096, 1024) 32.03M
weight_hh_l0(->weight_ih_l0) (4096, 1024) 0.00B
bias_ih_l0(->weight_ih_l0) (4096,) 0.00B
bias_hh_l0(->weight_ih_l0) (4096,) 0.00B
Tensor0 (10, 10, 1024) 400.00K
-------------------------------------------------------------------------------
Total Tensors: 8499200 Used Memory: 32.42M
The allocated memory on cuda:0: 32.52M
Memory differs due to the matrix alignment
-------------------------------------------------------------------------------
Element type Size Used MEM
-------------------------------------------------------------------------------
Storage on cuda:0
weight_ih_l0 (4096, 1024) 32.03M
weight_ih_l0.grad (4096, 1024) 32.03M
weight_hh_l0(->weight_ih_l0) (4096, 1024) 0.00B
weight_hh_l0.grad(->weight_ih_l0.grad) (4096, 1024) 0.00B
bias_ih_l0(->weight_ih_l0) (4096,) 0.00B
bias_ih_l0.grad(->weight_ih_l0.grad) (4096,) 0.00B
bias_hh_l0(->weight_ih_l0) (4096,) 0.00B
bias_hh_l0.grad(->weight_ih_l0.grad) (4096,) 0.00B
Tensor0 (10, 10, 1024) 400.00K
Tensor1 (10, 10, 1024) 400.00K
Tensor2 (1, 10, 1024) 40.00K
Tensor3 (1, 10, 1024) 40.00K
-------------------------------------------------------------------------------
Total Tensors: 17018880 Used Memory: 64.92M
The allocated memory on cuda:0: 65.11M
Memory differs due to the matrix alignment
-------------------------------------------------------------------------------
AVISO:
Cuando se reenvía con
grad_mode=True, Pytorch mantiene buffers de tensor para futuras propagaciones de retroceso, en el nivel C. Por lo tanto, estos buffers no serán administrados o recolectados por Pytorch. Pero si almacena estos resultados intermedios como variables de Python, se informarán.
También puede filtrar el dispositivo para informar al pasar argumentos adicionales: report(device=torch.device(0))
Un ejemplo fallido debido a los buffers de tensor lateral C de Pytorch
En el siguiente ejemplo, se crea un búfer temperador en inp * (inp + 2) para almacenar tanto inp como inp + 2 , desafortunadamente Python solo conoce la existencia de INP, por lo que tenemos la memoria de 2 m perdidas, que es el mismo tamaño de tensor inp .
import torch
from pytorch_memlab import MemReporter
linear = torch . nn . Linear ( 1024 , 1024 ). cuda ()
inp = torch . Tensor ( 512 , 1024 ). cuda ()
# pass in a model to automatically infer the tensor names
reporter = MemReporter ( linear )
out = linear ( inp * ( inp + 2 )). mean ()
reporter . report ()Salidas:
Element type Size Used MEM
-------------------------------------------------------------------------------
Storage on cuda:0
weight (1024, 1024) 4.00M
bias (1024,) 4.00K
Tensor0 (512, 1024) 2.00M
Tensor1 (1,) 512.00B
-------------------------------------------------------------------------------
Total Tensors: 1573889 Used Memory: 6.00M
The allocated memory on cuda:0: 8.00M
Memory differs due to the matrix alignment or invisible gradient buffer tensors
-------------------------------------------------------------------------------
A veces a las personas les gustaría evitar su tarea de ejecución, pero no desea guardar el punto de control y luego cargar, en realidad todo lo que necesitan son los recursos de la GPU (generalmente los recursos de la CPU y la memoria de la CPU siempre se repuestos en los clústeres de GPU), por lo que puede mover todos sus trabajos de GPU a CPU y luego detener su tarea hasta que una señal de reinicio se dispare, en lugar de ahorrar y cargar puntos de cheques y bootstrappents de scration.
Todavía desarrollando ..... pero puedes divertirte con:
from pytorch_memlab import Courtesy
iamcourtesy = Courtesy ()
for i in range ( num_iteration ):
if something_happens :
iamcourtesy . yield_memory ()
wait_for_restart_signal ()
iamcourtesy . restore ()Memory_Reporter , los tensores intermedios no están cubiertos correctamente, por lo que es posible que desee insertar dichos lógicos de cortesía después backward o antes forward .Sufrí mucho depuración de un extraño uso de la memoria durante mis 3 años de desarrollar modelos eficientes de aprendizaje profundo y, por supuesto, aprendí mucho de la gran comunidad de código abierto.
DataFrame.drop para pandas 1.5+ MemReporter (#24)MemReporter line_profiler no encontrado