Accelerate documentation
Profiler
Profiler
Profiler là công cụ cho phép thu thập các chỉ số hiệu năng trong quá trình huấn luyện và suy luận. API trình quản lý ngữ cảnh của Profiler có thể được sử dụng để hiểu rõ hơn về các toán tử mô hình nào tốn kém nhất, kiểm tra hình dạng đầu vào và vết chồng xếp (stack trace) của chúng, nghiên cứu hoạt động của kernel trên thiết bị, và trực quan hóa vết thực thi. Công cụ này cung cấp thông tin chi tiết về hiệu năng của mô hình, giúp bạn tối ưu và cải thiện nó.
Hướng dẫn này giải thích cách sử dụng PyTorch Profiler để đo lường thời gian và mức tiêu thụ bộ nhớ của các toán tử trong mô hình, cũng như cách tích hợp công cụ này với Accelerate. Chúng ta sẽ xem xét các trường hợp sử dụng khác nhau và cung cấp ví dụ cho từng trường hợp.
Sử dụng profiler để phân tích thời gian thực thi
Profiler cho phép kiểm tra các toán tử nào đã được gọi trong quá trình thực thi của một phạm vi mã được bọc bởi trình quản lý ngữ cảnh profiler.
Hãy xem cách chúng ta có thể sử dụng profiler để phân tích thời gian thực thi:
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))Bảng kết quả (bỏ qua một số cột):
--------------------------------- ------------ ------------ ------------ ------------
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Để có kết quả chi tiết hơn và bao gồm hình dạng đầu vào của toán tử, hãy truyền group_by_input_shape=True (lưu ý: điều này yêu cầu chạy trình phân tích với record_shapes=True):
print(prof.key_averages(group_by_input_shape=True).table(sort_by="cpu_time_total", row_limit=10))Sử dụng profiler để phân tích mức tiêu thụ bộ nhớ
Profiler cũng có thể hiển thị lượng bộ nhớ (được sử dụng bởi các tensor của mô hình) đã được cấp phát (hoặc giải phóng) trong quá trình thực thi các toán tử của mô hình. Để bật tính năng theo dõi bộ nhớ, hãy truyền 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))Bảng kết quả (bỏ qua một số cột):
--------------------------------- ------------ ------------ ------------
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.332msNếu bạn chỉ cần một hoặc hai con số tổng hợp để theo dõi thí nghiệm thay vì một bảng toán tử đầy đủ, hãy kết hợp trình phân tích (profiler) với các thống kê bộ nhớ thời gian chạy do PyTorch cung cấp. Điều này thường là đủ để ghi lại mức sử dụng bộ nhớ cao nhất cho mỗi lần chạy:
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())Mẫu này giữ cho đầu ra của trình phân tích (profiler) luôn sẵn sàng để gỡ lỗi, đồng thời tạo ra các giá trị gọn gàng có thể được ghi log vào trình theo dõi thí nghiệm.
Xuất trace chrome
Bạn có thể xem xét trình tự các toán tử và kernel CUDA được profile trong trình xem trace của 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 directorySử dụng Profiler để Phân Tích Các Công Việc Chạy Lâu
Profiler cung cấp một API bổ sung để xử lý các tác vụ chạy lâu (chẳng hạn như vòng lặp huấn luyện). Việc theo dõi toàn bộ quá trình thực thi có thể gây chậm và dẫn đến các tệp trace rất lớn. Để tránh điều này, hãy sử dụng các đối số tùy chọn:
schedule_option: Các tùy chọn lập lịch cho phép bạn kiểm soát thời điểm profiling được kích hoạt. Điều này hữu ích cho các tác vụ chạy dài để tránh thu thập quá nhiều dữ liệu. Các khóa khả dụng làwait,warmup,active,repeatvàskip_first. Trình profiler sẽ bỏ quaskip_firstbước đầu tiên, sau đó chờ trongwaitbước, tiếp theo thực hiện warmup trongwarmupbước kế tiếp, sau đó ghi nhận hoạt động trongactivebước tiếp theo và lặp lại chu kỳ bắt đầu bằngwaitbước. Số chu kỳ tùy chọn được chỉ định bằng tham sốrepeat, giá trị bằng không có nghĩa là các chu kỳ sẽ tiếp tục cho đến khi profiling kết thúc.on_trace_ready: chỉ định một hàm nhận tham chiếu đến profiler làm đầu vào và được profiler gọi mỗi khi trace mới sẵn sàng.
Để minh họa cách API hoạt động, hãy xem xét ví dụ sau:
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
Sử dụng công thức để ước tính FLOPs (phép toán số dấu phẩy động) của các toán tử cụ thể (nhân ma trận và tích chập 2D).
Để đo các phép toán số dấu phẩy động (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))Bảng kết quả (bỏ qua một số cột):
------------------------------------------------------- ------------ ------------ ------------
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.165msKết luận và Thông tin thêm
PyTorch Profiler là một công cụ mạnh mẽ để phân tích hiệu suất của các mô hình của bạn. Bằng cách tích hợp nó với Accelerate, bạn có thể dễ dàng tạo hồ sơ (profile) cho các mô hình và thu được những hiểu biết về hiệu suất của chúng, giúp bạn tối ưu hóa và cải thiện chúng.
Để biết thêm thông tin chi tiết, hãy tham khảo tài liệu PyTorch Profiler.
Update on GitHub