Accelerate documentation
Profiler
Profiler
Profiler es una herramienta que permite recopilar métricas de rendimiento durante el entrenamiento y la inferencia. La API de administrador de contexto de Profiler se puede utilizar para comprender mejor qué operadores del modelo son los más costosos, examinar sus formas de entrada y trazas de pila, estudiar la actividad de los kernels del dispositivo y visualizar la traza de ejecución. Proporciona información sobre el rendimiento de su modelo, lo que le permite optimizarlo y mejorarlo.
Esta guía explica cómo usar PyTorch Profiler para medir el tiempo y el consumo de memoria de los operadores del modelo y cómo integrar esto con Accelerate. Abordaremos varios casos de uso y proporcionaremos ejemplos para cada uno.
Usar el perfilador para analizar el tiempo de ejecución
Profiler permite verificar qué operadores se llamaron durante la ejecución de un rango de código envuelto con un administrador de contexto de profiler.
Veamos cómo podemos usar el profiler para analizar el tiempo de ejecución:
import torch
import torchvision.models as models
from torch.profiler import profile, record_function, ProfilerActivity
model = models.resnet18()
inputs = torch.randn(5, 3, 224, 224)
with profile(activities=[ProfilerActivity.CPU], record_shapes=True) as prof:
model(inputs)
print(prof.key_averages().table(sort_by="cpu_time_total", row_limit=10))from accelerate import Accelerator, ProfileKwargs
import torch
import torchvision.models as models
model = models.resnet18()
inputs = torch.randn(5, 3, 224, 224)
profile_kwargs = ProfileKwargs(
activities=["cpu"],
record_shapes=True
)
accelerator = Accelerator(cpu=True, kwargs_handlers=[profile_kwargs])
model = accelerator.prepare(model)
with accelerator.profile() as prof:
with torch.no_grad():
model(inputs)
print(prof.key_averages().table(sort_by="cpu_time_total", row_limit=10))La salida de la tabla resultante (omitimos algunas columnas):
--------------------------------- ------------ ------------ ------------ ------------
Name Self CPU CPU total CPU time avg # of Calls
--------------------------------- ------------ ------------ ------------ ------------
aten::conv2d 171.000us 52.260ms 2.613ms 20
aten::convolution 227.000us 52.089ms 2.604ms 20
aten::_convolution 270.000us 51.862ms 2.593ms 20
aten::mkldnn_convolution 51.273ms 51.592ms 2.580ms 20
aten::batch_norm 118.000us 7.059ms 352.950us 20
aten::_batch_norm_impl_index 315.000us 6.941ms 347.050us 20
aten::native_batch_norm 6.305ms 6.599ms 329.950us 20
aten::max_pool2d 40.000us 4.008ms 4.008ms 1
aten::max_pool2d_with_indices 3.968ms 3.968ms 3.968ms 1
aten::add_ 780.000us 780.000us 27.857us 28
--------------------------------- ------------ ------------ ------------ ------------
Self CPU time total: 67.016msPara obtener una granularidad más fina de los resultados e incluir las formas de entrada de los operadores, pase group_by_input_shape=True (nota: esto requiere ejecutar el perfilador con record_shapes=True):
print(prof.key_averages(group_by_input_shape=True).table(sort_by="cpu_time_total", row_limit=10))Usar el perfilador para analizar el consumo de memoria
Profiler también puede mostrar la cantidad de memoria (usada por los tensores del modelo) que se asignó (o liberó) durante la ejecución de los operadores del modelo. Para habilitar la funcionalidad de perfilado de memoria, pase profile_memory=True.
model = models.resnet18()
inputs = torch.randn(5, 3, 224, 224)
with profile(activities=[ProfilerActivity.CPU],
profile_memory=True, record_shapes=True) as prof:
model(inputs)
print(prof.key_averages().table(sort_by="self_cpu_memory_usage", row_limit=10))model = models.resnet18()
inputs = torch.randn(5, 3, 224, 224)
profile_kwargs = ProfileKwargs(
activities=["cpu"],
profile_memory=True,
record_shapes=True
)
accelerator = Accelerator(cpu=True, kwargs_handlers=[profile_kwargs])
model = accelerator.prepare(model)
with accelerator.profile() as prof:
model(inputs)
print(prof.key_averages().table(sort_by="self_cpu_memory_usage", row_limit=10))La salida de la tabla resultante (omitimos algunas columnas):
--------------------------------- ------------ ------------ ------------
Name CPU Mem Self CPU Mem # of Calls
--------------------------------- ------------ ------------ ------------
aten::empty 94.85 Mb 94.85 Mb 205
aten::max_pool2d_with_indices 11.48 Mb 11.48 Mb 1
aten::addmm 19.53 Kb 19.53 Kb 1
aten::mean 10.00 Kb 10.00 Kb 1
aten::empty_strided 492 b 492 b 5
aten::cat 240 b 240 b 6
aten::abs 480 b 240 b 4
aten::masked_select 120 b 112 b 1
aten::ne 61 b 53 b 3
aten::eq 30 b 30 b 1
--------------------------------- ------------ ------------ ------------
Self CPU time total: 69.332msSi necesita uno o dos números agregados para el seguimiento de experimentos en lugar de una tabla completa de operadores, combine el perfilador con las estadísticas de memoria en tiempo de ejecución expuestas por PyTorch. Esto suele ser suficiente para registrar el uso de memoria pico por ejecución:
import torch
from accelerate import Accelerator, PartialState, ProfileKwargs
# Resolve the device type/module (cuda, xpu, ...) instead of hardcoding CUDA
device_type = PartialState().device.type
device_module = getattr(torch, device_type)
def summarize_memory_stats():
return {
"peak_allocated_mb": device_module.max_memory_allocated() / 1024**2,
"peak_reserved_mb": device_module.max_memory_reserved() / 1024**2,
}
profile_kwargs = ProfileKwargs(
activities=["cpu", device_type],
profile_memory=True,
record_shapes=True,
)
accelerator = Accelerator(kwargs_handlers=[profile_kwargs])
device_module.reset_peak_memory_stats()
with accelerator.profile() as prof:
model(inputs)
print(prof.key_averages().table(sort_by=f"self_{device_type}_memory_usage", row_limit=10))
print(summarize_memory_stats())Este patrón mantiene la salida del perfilador disponible para depuración y, al mismo tiempo, produce valores compactos que pueden registrarse en un rastreador de experimentos.
Exportar la traza de Chrome
Puede examinar la secuencia de operadores y kernels de CUDA perfiles en el visor de trazas de Chrome (chrome://tracing):
# Pick the available accelerator (cuda, xpu, ...) instead of hardcoding CUDA
device = torch.accelerator.current_accelerator()
device_activity = getattr(ProfilerActivity, device.type.upper())
model = models.resnet18().to(device)
inputs = torch.randn(5, 3, 224, 224).to(device)
with profile(activities=[ProfilerActivity.CPU, device_activity]) as prof:
model(inputs)
prof.export_chrome_trace("trace.json")model = models.resnet18()
profile_kwargs = ProfileKwargs(
activities=["cpu", PartialState().device.type],
output_trace_dir="trace"
)
accelerator = Accelerator(kwargs_handlers=[profile_kwargs])
model = accelerator.prepare(model)
inputs = torch.randn(5, 3, 224, 224).to(accelerator.device)
with accelerator.profile() as prof:
model(inputs)
# The trace will be saved to the specified directoryUso de Profiler para analizar trabajos de larga duración
Profiler ofrece una API adicional para manejar trabajos de larga duración (como bucles de entrenamiento). Rastrear toda la ejecución puede ser lento y dar lugar a archivos de trazas muy grandes. Para evitarlo, utiliza los argumentos opcionales:
schedule_option: Las opciones de programación permiten controlar cuándo está activo el perfilado. Esto es útil para trabajos de larga duración para evitar recopilar demasiados datos. Las claves disponibles sonwait,warmup,active,repeatyskip_first. El perfilador omitirá los primerosskip_firstpasos, luego esperaráwaitpasos, luego realizará el calentamiento durante los siguienteswarmuppasos, luego realizará la grabación activa durante los siguientesactivepasos y luego repetirá el ciclo comenzando conwaitpasos. El número opcional de ciclos se especifica con el parámetrorepeat; el valor cero significa que los ciclos continuarán hasta que finalice el perfilado.on_trace_ready: especifica una función que toma una referencia al perfilador como entrada y es llamada por el perfilador cada vez que la nueva traza está lista.
Para ilustrar cómo funciona la API, considere el siguiente ejemplo:
from torch.profiler import schedule
my_schedule = schedule(
skip_first=1,
wait=5,
warmup=1,
active=3,
repeat=2
)
def trace_handler(p):
output = p.key_averages().table(sort_by=f"self_{device.type}_time_total", row_limit=10)
print(output)
p.export_chrome_trace("/tmp/trace_" + str(p.step_num) + ".json")
with profile(
activities=[ProfilerActivity.CPU, device_activity],
schedule=my_schedule,
on_trace_ready=trace_handler
) as p:
for idx in range(8):
model(inputs)
p.step()device_type = PartialState().device.type
def trace_handler(p):
output = p.key_averages().table(sort_by=f"self_{device_type}_time_total", row_limit=10)
print(output)
p.export_chrome_trace("/tmp/trace_" + str(p.step_num) + ".json")
profile_kwargs = ProfileKwargs(
activities=["cpu", device_type],
schedule_option={"wait": 5, "warmup": 1, "active": 3, "repeat": 2, "skip_first": 1},
on_trace_ready=trace_handler
)
accelerator = Accelerator(kwargs_handlers=[profile_kwargs])
model = accelerator.prepare(model)
with accelerator.profile() as prof:
for idx in range(8):
model(inputs)
prof.step()FLOPS
Use formula para estimar los FLOPs (operaciones de coma flotante) de operadores específicos (multiplicación de matrices y convolución 2D).
Para medir las operaciones de punto flotante (FLOPS):
with profile(
activities=[ProfilerActivity.CPU, device_activity],
with_flops=True
) as prof:
model(inputs)
print(prof.key_averages().table(sort_by="flops", row_limit=10))profile_kwargs = ProfileKwargs(
with_flops=True
)
accelerator = Accelerator(kwargs_handlers=[profile_kwargs])
with accelerator.profile() as prof:
model(inputs)
print(prof.key_averages().table(sort_by="flops", row_limit=10))La salida de la tabla resultante (omitimos algunas columnas):
------------------------------------------------------- ------------ ------------ ------------
Name Self CPU Self CUDA Total FLOPs
------------------------------------------------------- ------------ ------------ ------------
aten::conv2d 197.000us 0.000us 18135613440.000
aten::addmm 103.000us 17.000us 5120000.000
aten::mul 29.000us 2.000us 30.000
aten::convolution 409.000us 0.000us --
aten::_convolution 253.000us 0.000us --
aten::cudnn_convolution 5.465ms 2.970ms --
cudaEventRecord 138.000us 0.000us --
cudaStreamIsCapturing 43.000us 0.000us --
cudaStreamGetPriority 40.000us 0.000us --
cudaDeviceGetStreamPriorityRange 10.000us 0.000us --
------------------------------------------------------- ------------ ------------ ------------
Self CPU time total: 21.938ms
Self CUDA time total: 4.165msConclusión e información adicional
PyTorch Profiler es una herramienta potente para analizar el rendimiento de tus modelos. Al integrarlo con Accelerate, puedes perfilear tus modelos con facilidad y obtener información sobre su rendimiento, lo que te ayuda a optimizarlos y mejorarlos.
Para obtener información más detallada, consulte la documentación del Profiler de PyTorch.
Update on GitHub