Accelerate documentation
Профилировщик
Профилировщик
Profiler — это инструмент, позволяющий собирать метрики производительности во время обучения и инференса. API контекстного менеджера Profiler можно использовать, чтобы лучше понять, какие операторы модели являются наиболее затратными, изучить формы их входных данных и трассы стека, проанализировать активность ядер на устройстве и визуализировать трассу выполнения. Он даёт представление о производительности вашей модели, что позволяет оптимизировать и улучшить её.
В этом руководстве объясняется, как использовать PyTorch Profiler для измерения времени и потребления памяти операторами модели, а также как интегрировать это с Accelerate. Мы рассмотрим различные сценарии использования и приведём примеры для каждого из них.
Использование профайлера для анализа времени выполнения
Profiler позволяет проверить, какие операторы были вызваны во время выполнения участка кода, обёрнутого контекстным менеджером профилировщика.
Рассмотрим, как можно использовать profiler для анализа времени выполнения:
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))Результат вывода таблицы (некоторые столбцы опущены):
--------------------------------- ------------ ------------ ------------ ------------
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.016msЧтобы получить более детальные результаты и включить формы входных данных операторов, передайте group_by_input_shape=True (примечание: для этого необходимо запускать профилировщик с record_shapes=True):
print(prof.key_averages(group_by_input_shape=True).table(sort_by="cpu_time_total", row_limit=10))Использование профайлера для анализа потребления памяти
Профилировщик также может отображать объём памяти (используемой тензорами модели), который был выделен (или освобождён) во время выполнения операторов модели. Чтобы включить профилирование памяти, передайте 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))Результат вывода таблицы (некоторые столбцы опущены):
--------------------------------- ------------ ------------ ------------
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.332msЕсли для отслеживания экспериментов вам нужны один или два сводных показателя, а не полная таблица операторов, объедините профилировщик со статистикой памяти во время выполнения, предоставляемой PyTorch. Этого часто достаточно для записи пикового потребления памяти за каждый запуск:
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())Этот подход сохраняет вывод профилировщика доступным для отладки, одновременно генерируя компактные значения, которые можно записывать в трекер экспериментов.
Экспорт трассировки Chrome
Вы можете изучить последовательность профилируемых операторов и CUDA-ядер в Chrome trace viewer (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 directoryИспользование Profiler для анализа длительных задач
Профайлер предлагает дополнительный API для обработки длительных задач (таких как циклы обучения). Трассировка всего выполнения может быть медленной и приводить к созданию очень больших файлов трассировки. Чтобы избежать этого, используйте необязательные аргументы:
schedule_option: Параметры планирования позволяют управлять тем, когда профилирование активно. Это полезно для длительных задач, чтобы избежать сбора избыточного объёма данных. Доступные ключи:wait,warmup,active,repeatиskip_first. Профилировщик пропускает первыеskip_firstшагов, затем ожидаетwaitшагов, после чего выполняет разгон в течение следующихwarmupшагов, затем ведёт активную запись в течение следующихactiveшагов и повторяет цикл, начиная с шаговwait. Необязательное количество циклов задаётся параметромrepeat; значение ноль означает, что циклы будут продолжаться до завершения профилирования.on_trace_ready: определяет функцию, которая принимает ссылку на профилировщик в качестве входного параметра и вызывается профилировщиком каждый раз, когда новый трассировочный файл готов.
Для иллюстрации работы API рассмотрим следующий пример:
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
Используйте формулу для оценки количества FLOPs (операций с плавающей точкой) для конкретных операторов (умножение матриц и 2D-свёртка).
Для измерения операций с плавающей точкой (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))Результат вывода таблицы (некоторые столбцы опущены):
------------------------------------------------------- ------------ ------------ ------------
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.165msЗаключение и дополнительная информация
PyTorch Profiler — это мощный инструмент для анализа производительности ваших моделей. Интегрировав его с Accelerate, вы сможете легко профилировать модели и получать информацию об их производительности, что поможет оптимизировать и улучшить их.
Для получения более подробной информации обратитесь к документации PyTorch Profiler.
Update on GitHub