
PyTorch Geometric 性能剖析完全指南torch_geometric.profile 模塊源碼級解析【免費(fèi)下載鏈接】pytorch_geometricGraph Neural Network Library for PyTorch項(xiàng)目地址: https://gitcode.com/GitHub_Trending/py/pytorch_geometric導(dǎo)讀訓(xùn)練一個(gè) GNN 模型時(shí)運(yùn)行了多久、顯存峰值多少、哪個(gè)算子最耗時(shí)往往是調(diào)優(yōu)路上最先遇到的問題。PyTorch GeometricPyG在torch_geometric.profile模塊中內(nèi)置了一套完整的性能剖析工具箱從輕量級計(jì)時(shí)器timeit、可返回結(jié)構(gòu)化顯存統(tǒng)計(jì)的profileit裝飾器、逐層剖析的Profiler到生成 Chrome Trace 的torch_profile、函數(shù)級對比基準(zhǔn)benchmark與 NVTX 標(biāo)注工具nvtxit覆蓋了 GNN 訓(xùn)練/推理性能分析的全流程。閱讀本文后你將掌握這套工具的全部 API、底層實(shí)現(xiàn)原理對應(yīng)源碼文件位置以及它們在 PyG 官方 benchmark 腳本中的真實(shí)用法能夠直接在自己的模型訓(xùn)練腳本中落地使用。本文對應(yīng)的文檔入口為 docs/source/modules/profile.rst該文件通過 Sphinx autosummary 自動展開torch_geometric.profile模塊的全部公開成員以下內(nèi)容即圍繞該模塊的源碼與測試展開。一、模塊總覽一張 API 地圖torch_geometric.profile包的公共接口統(tǒng)一在 torch_geometric/profile/init.py 中聲明共 17 個(gè)符號分為四類能力類別公開 API實(shí)現(xiàn)文件訓(xùn)練/推理計(jì)時(shí)與顯存統(tǒng)計(jì)profileit、timeit、get_stats_summary、GPUStats、CUDAStats、GPUStatsSummary、CUDAStatsSummarytorch_geometric/profile/profile.py逐層 Profiler 與 Trace 導(dǎo)出Profiler、torch_profile、xpu_profile、trace_handler、print_time_total、rename_profile_filetorch_geometric/profile/profile.py、torch_geometric/profile/profiler.py模型與數(shù)據(jù)規(guī)模統(tǒng)計(jì)count_parameters、get_model_size、get_data_size、get_cpu_memory_from_gc、get_gpu_memory_from_gc、get_gpu_memory_from_nvidia_smi、get_gpu_memory_from_ipextorch_geometric/profile/utils.py函數(shù)級對比與 NVTX 標(biāo)注benchmark、nvtxittorch_geometric/profile/benchmark.py、torch_geometric/profile/nvtx.py其中GPUStats/CUDAStats/GPUStatsSummary/CUDAStatsSummary是profileit與get_stats_summary返回的數(shù)據(jù)類dataclass也是整個(gè)模塊的數(shù)據(jù)骨架會在下文逐一展開。值得注意的是Profiler類位于profiler.py中但未在包的__init__.py導(dǎo)出需要通過from torch_geometric.profile.profiler import Profiler顯式導(dǎo)入。二、訓(xùn)練運(yùn)行時(shí)與顯存峰值profileit裝飾器2.1 基本用法profileit是一個(gè)裝飾器用于在單次函數(shù)調(diào)用內(nèi)同時(shí)采集 GPU 運(yùn)行耗時(shí)與顯存統(tǒng)計(jì)。它要求被裝飾函數(shù)的第一個(gè)參數(shù)必須是torch.nn.Module并且只能用于cuda與xpu兩種設(shè)備源碼中會顯式拋出AttributeError校驗(yàn)這兩點(diǎn)見 profile.py。from torch_geometric.profile import profileit profileit(cuda) def train(model, optimizer, x, edge_index, y): optimizer.zero_grad() out model(x, edge_index) loss criterion(out, y) loss.backward() optimizer.step() return float(loss) loss, stats train(model, optimizer, x, edge_index, y)返回值是一個(gè)二元組第一個(gè)元素是被裝飾函數(shù)的原始返回值第二個(gè)元素是統(tǒng)計(jì)對象——設(shè)備為cuda時(shí)返回CUDAStats為xpu時(shí)返回GPUStats。2.2 底層實(shí)現(xiàn)鏈路從源碼看profileit的實(shí)現(xiàn)分為三步profile.py推斷設(shè)備 ID遍歷函數(shù)入?yún)⑴c關(guān)鍵字參數(shù)找到第一個(gè)torch.Tensor通過tensor.get_device()推斷 GPU 編號若找不到張量或get_device()返回-1CPU 張量則分別拋出AttributeError與RuntimeError。CUDA 內(nèi)存剖析在 CUDA 設(shè)備上會實(shí)例化pytorch_memlab的LineProfiler并將model.forward加入被追蹤函數(shù)列表line_profiler.add_function(args[0].forward)從而獲得前向傳播逐行/逐算子的顯存占用。計(jì)時(shí)與統(tǒng)計(jì)使用torch_gpu.Event(enable_timingTrue)記錄開始與結(jié)束事件并synchronize()得到秒級耗時(shí)隨后通過read_from_memlab讀取allocated_bytes.all.peak、reserved_bytes.all.peak、active_bytes.all.peak三個(gè)峰值指標(biāo)單位換算為 MB見 profile.py再調(diào)用nvidia-smi查詢該 GPU 的空閑/已用顯存utils.py。2.3 返回的數(shù)據(jù)結(jié)構(gòu)profileit返回的對象是 dataclassprofile.py字段含義所屬類time本次調(diào)用 GPU 耗時(shí)秒GPUStats/CUDAStatsmax_allocated_gpu峰值已分配顯存MBGPUStats/CUDAStatsmax_reserved_gpu峰值已預(yù)留reserved顯存MBGPUStats/CUDAStatsmax_active_gpu峰值活躍顯存MBGPUStats/CUDAStatsnvidia_smi_free_cuda剖析時(shí)該 GPU 的空閑顯存MB來自nvidia-smi僅CUDAStatsnvidia_smi_used_cuda剖析時(shí)該 GPU 的已用顯存MB來自nvidia-smi僅CUDAStats2.4 多次運(yùn)行求匯總get_stats_summary單次統(tǒng)計(jì)易受噪聲影響實(shí)踐中通常多次運(yùn)行后求匯總。get_stats_summary接收一個(gè)GPUStats/CUDAStats列表返回對應(yīng)的GPUStatsSummary/CUDAStatsSummaryprofile.py公共字段time_mean平均耗時(shí)、time_std耗時(shí)標(biāo)準(zhǔn)差、max_allocated_gpu、max_reserved_gpu、max_active_gpu各取列表最大值CUDAStatsSummary額外包含min_nvidia_smi_free_cuda最小空閑顯存與max_nvidia_smi_used_cuda最大已用顯存。下面的用法直接取自 PyG 官方測試 test/profile/test_profile.py用profileit(cuda)裝飾訓(xùn)練函數(shù)跑 5 個(gè) epoch 但只收集后 3 個(gè)前 2 個(gè)作為 warm-up再求匯總from torch_geometric.profile import get_stats_summary, profileit stats_list [] for epoch in range(5): _, stats train(model, data.x, data.edge_index, data.y) if epoch 2: # Warm-up stats_list.append(stats) stats_summary get_stats_summary(stats_list) print(stats_summary.time_mean, stats_summary.time_std, stats_summary.max_allocated_gpu)三、輕量級計(jì)時(shí)器timeit上下文管理器當(dāng)只需要一段代碼的運(yùn)行時(shí)長、不關(guān)心顯存細(xì)節(jié)時(shí)timeit是最輕的選擇。它繼承contextlib.ContextDecorator既可用作with語句也可用作裝飾器profile.pyfrom torch_geometric.profile import timeit torch.no_grad() def test(model, x, edge_index): return model(x, edge_index) with timeit() as t: z test(model, x, edge_index) time t.duration # 秒?yún)?shù)說明logbool默認(rèn)True為False時(shí)不在控制臺打印耗時(shí)avg_time_divisorint默認(rèn)0大于 1 時(shí)將總耗時(shí)除以該值常用于 for 循環(huán)內(nèi)計(jì)算平均耗時(shí)。兩個(gè)實(shí)現(xiàn)細(xì)節(jié)值得注意一是進(jìn)入與退出with塊時(shí)都會在 CUDA 可用時(shí)調(diào)用torch.cuda.synchronize()確保測得的是 GPU 算子真正執(zhí)行完畢的時(shí)間而非排隊(duì)時(shí)間二是退出塊后t.duration屬性才被賦值測試 test_timeit 專門驗(yàn)證了在塊內(nèi)duration不存在、塊外才存在這一行為。此外還提供了reset()方法打印當(dāng)前耗時(shí)并重啟計(jì)時(shí)。四、逐層剖析Profiler類timeit給出整體耗時(shí)而Profiler可以告訴你每一層模塊及每個(gè)算子的耗時(shí)與顯存。它通過遞歸遍歷模型結(jié)構(gòu)_walk_modules會生成(path, is_leaf, module)三元組路徑形如(GCN, conv1, lin)對每個(gè)葉子模塊的forward打上 hook在其內(nèi)部用torch.profiler.profile記錄事件最后匯總成帶縮進(jìn)的分層表格profiler.py。from torch_geometric.profile.profiler import Profiler with Profiler(model, use_cudatorch.cuda.is_available(), profile_memoryTrue) as prof: out model(x, edge_index) # 打印分層剖析表Module / Self CPU total / CPU total / ... / Number of Calls print(prof)構(gòu)造函數(shù)參數(shù)profiler.pymodel待剖析的torch.nn.Moduleenabled默認(rèn)True為False時(shí)整體禁用use_cuda默認(rèn)False是否剖析 CUDA 執(zhí)行profile_memory默認(rèn)False是否同時(shí)剖析顯存paths默認(rèn)None預(yù)定義路徑列表用于只剖析指定子模塊如[GCN, GCN/conv1]為None時(shí)剖析所有葉子模塊。剖析輸出為分層樹狀表每層包含 Self CPU total、CPU total、Self CUDA total、CUDA total、Self CPU Mem、CPU Mem、Self CUDA Mem、CUDA Mem、Number of Calls 等列列的具體展示取決于use_cuda與profile_memory開關(guān)見 profiler.py。實(shí)現(xiàn)中引用了torchprof的分層分組思路且要求 PyTorch 版本不低于 1.8.1見 profiler.py 的版本檢查。退出with塊后所有被替換的forward會被還原不會污染模型本身。五、Chrome Trace 導(dǎo)出torch_profile與xpu_profiletorch_profile是一個(gè)上下文管理器用一行代碼接入 PyTorch 官方 profiler并自動把結(jié)果導(dǎo)出為 Chrome Trace 格式profile.pyfrom torch_geometric.profile import torch_profile with torch_profile(): model(data.x, data.edge_index)其行為要點(diǎn)自動檢測torch.cuda.is_available()決定 activities 是否包含ProfilerActivity.CUDAexport_chrome_traceTrue默認(rèn)時(shí)剖析結(jié)束后調(diào)用trace_handler先打印按self_cuda_time_total無 CUDA 時(shí)按self_cpu_time_total排序的key_averages()表格再把結(jié)果導(dǎo)出為當(dāng)前工作目錄下的timeline.jsonexport_chrome_traceFalse時(shí)僅打印統(tǒng)計(jì)表不導(dǎo)出文件支持可選的csv_data/write_csvprof參數(shù)將 Top 5 最耗時(shí)算子的 SELF CPU %、SELF CPU、CPU TOTAL %、CPU TOTAL、CUDA 對應(yīng)列及調(diào)用次數(shù)寫入 CSV見 save_profile_data。配套的兩個(gè)輔助函數(shù)rename_profile_file(*args)把生成的timeline.json重命名為profile-arg1-arg2....json便于多次剖析時(shí)保留多個(gè) trace 文件測試 test_torch_profile 中驗(yàn)證了重命名后profile-test_profile.json存在print_time_total(p)按耗時(shí)排序打印 profiler 事件表。XPUIntel GPU設(shè)備則使用xpu_profile(export_chrome_traceTrue)基于torch.autograd.profiler_legacy.profile(use_xpuTrue)采集打印按self_xpu_time_total排序的表格并可選導(dǎo)出timeline.jsonprofile.py。該分支在測試 test_xpu_profile 中被覆蓋。六、模型與數(shù)據(jù)規(guī)模統(tǒng)計(jì)utils工具族這類工具解決的是我的模型多大、數(shù)據(jù)占多少內(nèi)存的靜態(tài)規(guī)模問題全部實(shí)現(xiàn)在 torch_geometric/profile/utils.py 中API返回值說明count_parameters(model)int統(tǒng)計(jì)requires_gradTrue的可訓(xùn)練參數(shù)量utils.pyget_model_size(model)int將state_dict保存為臨時(shí).pt文件后取其磁盤字節(jié)數(shù)隨即刪除臨時(shí)文件utils.pyget_data_size(data)int遞歸遍歷Data/HeteroData的 stores按numel * element_size計(jì)算張量理論內(nèi)存占用通過data_ptr()去重避免重復(fù)計(jì)數(shù)SparseTensor按其 CSR 表示計(jì)utils.pyget_cpu_memory_from_gc()int遍歷 Python GC 對象累加所有非 CUDA 張量的字節(jié)數(shù)utils.pyget_gpu_memory_from_gc(device0)int同上但只統(tǒng)計(jì)指定設(shè)備上的張量utils.pyget_gpu_memory_from_nvidia_smi(device0, digits2)(free, used)MB通過nvidia-smi --query-gpumemory.free/used --formatcsv查詢utils.pyget_gpu_memory_from_ipex(device0, digits2)(allocated, reserved, active)MB通過 Intel Extension for PyTorchipex的memory_stats_as_nested_dict讀取 XPU 峰值統(tǒng)計(jì)utils.py注意get_gpu_memory_from_nvidia_smi的返回值是 MiB其換算系數(shù)為 1.0485見medibyte_to_megabyte且源碼注釋提醒nvidia-smi報(bào)告的占用通常高估了本程序?qū)嶋H使用的顯存因?yàn)榘彺骖A(yù)留做精確顯存分析時(shí)優(yōu)先采用pytorch_memlab/ ipex 的峰值統(tǒng)計(jì)。七、函數(shù)級對比基準(zhǔn)benchmarkbenchmark用于在相同輸入上橫向?qū)Ρ榷鄠€(gè)函數(shù)的性能輸出一張由tabulate渲染的表格benchmark.pyfrom torch_geometric.profile import benchmark benchmark( funcs[add], args(torch.randn(10), torch.randn(10)), num_steps1, num_warmups1, backwardTrue, )參數(shù)與行為funcs待對比的函數(shù)列表args可以是統(tǒng)一的參數(shù)元組也可以是每個(gè)函數(shù)一份參數(shù)的列表甚至可傳入生成參數(shù)的函數(shù)用于按不同規(guī)?;鶞?zhǔn)測試num_steps正式計(jì)時(shí)的步數(shù)num_warmups默認(rèn) 10預(yù)熱步數(shù)預(yù)熱階段的耗時(shí)不計(jì)入結(jié)果兩者都必須為正整數(shù)否則拋ValueErrorbackward默認(rèn)False為True時(shí)同時(shí)測量反向傳播耗時(shí)——實(shí)現(xiàn)中會先對輸出求和并backward(out_grad)輸出為 tuple/list/dict 時(shí)自動匯總各張量per_step默認(rèn)False為True時(shí)報(bào)告每步平均耗時(shí)否則報(bào)告總耗時(shí)progress_bar默認(rèn)False使用tqdm顯示進(jìn)度條func_names默認(rèn)None自定義顯示名缺省時(shí)從函數(shù)__name__推斷。無論是否開啟backward每次迭代都會在 CUDA 可用時(shí)torch.cuda.synchronize()并用time.perf_counter()計(jì)時(shí)保證結(jié)果的確定性。輸出表格形如--------------------------------------- | Name | Forward | Backward | Total | |---------------------------------------| | add | 0.0001s | 0.0002s | 0.0003s | ---------------------------------------該行為在測試 test/profile/test_benchmark.py 中通過捕獲標(biāo)準(zhǔn)輸出得到驗(yàn)證。八、NVTX 標(biāo)注nvtxitnvtxit用于為函數(shù)添加 NVTXNVIDIA Tools Extension范圍標(biāo)記方便在 NVIDIA Nsight 等 GPU 剖析工具中按函數(shù)名查看時(shí)間線nvtx.pyfrom torch_geometric.profile import nvtxit nvtxit() def forward(self, x, edge_index): return self.propagate(edge_index, xx) nvtxit(custom_name, n_warmups1, n_iters3) def another_func(...): ...參數(shù)name可選標(biāo)記名稱缺省為被裝飾函數(shù)的__name__n_warmups默認(rèn) 0開始標(biāo)記前的預(yù)熱調(diào)用次數(shù)n_iters可選需要記錄的調(diào)用次數(shù)缺省記錄全部。實(shí)現(xiàn)上nvtxit通過torch.cuda.nvtx.range_push(f{name}_{iters_so_far})/range_pop()包裹函數(shù)體并利用模塊級全局變量CUDA_PROFILE_STARTED配合cudaProfilerStart()/cudaProfilerStop()控制 CUDA profiler 的啟停nvtx.py。非 CUDA 環(huán)境下直接透傳執(zhí)行不做任何標(biāo)記。其 warm-up、命名、迭代次數(shù)等分支行為均在 test/profile/test_nvtx.py 中有系統(tǒng)測試覆蓋。九、實(shí)戰(zhàn)在 PyG 官方 benchmark 中如何組合使用上述工具在 PyG 自帶的基準(zhǔn)測試腳本中被大量組合使用是最佳實(shí)踐參考benchmark/citation/train_eval.py 展示了完整流程常規(guī)訓(xùn)練 epoch 直接調(diào)用train(...)最后一個(gè) run 的最后一個(gè) epoch用with timeit():包裹以獲得穩(wěn)定的單步訓(xùn)練耗時(shí)當(dāng)profilingTrue時(shí)再額外執(zhí)行一次with torch_profile(): train(...)導(dǎo)出timeline.json供 Chrome Tracing 分析。推理路徑run_inference同理且在bf16True時(shí)結(jié)合torch.bfloat16與 AMP autocast 一起剖析。benchmark/citation/gcn.py、benchmark/points/point_cnn.py 等腳本則調(diào)用rename_profile_file(gcn, ...)將每次實(shí)驗(yàn)的 trace 文件重命名歸檔避免覆蓋。benchmark/kernel/main_performance.py、benchmark/loader/neighbor_loader.py 同樣使用了timeit/torch_profile/rename_profile_file組合用于 kernel 級與數(shù)據(jù)加載環(huán)節(jié)的性能剖析。十、小結(jié)與選型建議面對不同的性能分析訴求torch_geometric.profile提供了從粗到細(xì)、從單機(jī)到異構(gòu)設(shè)備的完整工具鏈只需整體耗時(shí) →timeit秒級最輕量需要耗時(shí) 顯存峰值且能接受多跑幾次取匯總 →profileitget_stats_summaryCUDA 依賴pytorch_memlabXPU 依賴 ipex需要定位到具體層/算子的熱點(diǎn) →Profiler逐層剖析表需要可視化時(shí)間線 →torch_profile/xpu_profile導(dǎo)出 Chrome Tracetimeline.json/profile-*.json配合rename_profile_file歸檔需要橫向?qū)Ρ榷鄠€(gè)實(shí)現(xiàn)如不同聚合方式→benchmark需要在 Nsight 中按函數(shù)定位 →nvtxit需要靜態(tài)評估模型/數(shù)據(jù)規(guī)模 →count_parameters、get_model_size、get_data_size及 GC /nvidia-smi/ ipex 內(nèi)存查詢族。結(jié)合 test/profile 目錄下的測試用例可以進(jìn)一步確認(rèn)每個(gè) API 的邊界行為如設(shè)備限制、warm-up 語義、trace 文件命名規(guī)則從而在自己的項(xiàng)目中安全、正確地接入這套剖析工具?!久赓M(fèi)下載鏈接】pytorch_geometricGraph Neural Network Library for PyTorch項(xiàng)目地址: https://gitcode.com/GitHub_Trending/py/pytorch_geometric創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考