news 2026/9/11 14:35:36

PyTorch Profiler 实现原理深度解析:从 RecordFunction 到 Kineto 的 CPU/GPU 全链路采集架构

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PyTorch Profiler 实现原理深度解析:从 RecordFunction 到 Kineto 的 CPU/GPU 全链路采集架构

PyTorch Profiler 实现原理深度解析:从 RecordFunction 到 Kineto 的 CPU/GPU 全链路采集架构

【免费下载链接】pytorchTensors and Dynamic neural networks in Python with strong GPU acceleration项目地址: https://gitcode.com/GitHub_Trending/py/pytorch

torch.profiler是 PyTorch 官方提供的性能剖析工具,它能够记录模型执行过程中的算子耗时、内存分配、Python 调用栈与 GPU 事件,并导出为 Chrome Trace / Perfetto 可读的 JSON 文件。本文以仓库中 torch/csrc/profiler/README.md 为核心骨架,结合 torch/csrc/profiler/collection.h、torch/csrc/profiler/collection.cpp、torch/csrc/autograd/profiler_kineto.cpp 等源码,深入剖析 Profiler 的底层实现:事件从 CPU 算子回调、内存分配、自动求导、Python 栈到 GPU 活动是如何被采集、关联与导出的。读完本文,你将理解 Profiler 的完整数据链路、各采集阶段的内部机制,以及如何基于这些原理正确配置和使用剖析参数。

代码库结构:Profiler 前端、后端与 C++ 采集层的职责划分

Profiler 的实现横跨 Python 与 C++ 两大层次,README 给出了核心文件布局(相对仓库根路径),关键组成如下:

torch/ │ ├── profiler/ # 主 Python 包,包含核心前端逻辑 │ ├── __init__.py # profiler 包的初始化文件 │ ├── profiler.py # 主 Profiler 前端类(profile、schedule、supported_activities 等) │ └── _utils.py # FunctionEvent 工具函数 │ ├── autograd/ # Autograd 包 │ ├── __init__.py # autograd 包初始化文件 │ ├── profiler.py # 主 Profiler 后端类 │ └── profiler_utils.py # FunctionEvent 工具函数 │ ├── csrc/ # C 与 C++ 源码 │ └── profiler/ # Profiler C++ 源码 │ ├── collection.cpp # 主要采集逻辑 │ ├── collection.h # 采集定义 │ ├── kineto_client_interface.cpp # 从 kineto 调用 Profiler 的接口(仅 on-demand 模式) │ ├── kineto_client_interface.h # Client 接口定义 │ ├── kineto_shim.cpp # 从 profiler 调用 kineto 的 shim │ ├── kineto_shim.h # shim 定义 │ ├── util.cpp # 处理 profiler 事件中参数的 utils │ ├── util.h # util 定义 │ └── README.md # 本文所讲解的文档 │ └── autograd/ # Autograd C++ 源码 │ ├── profiler_python.cpp # 主要的 Python 调用栈采集逻辑 │ ├── profiler_python.h # Python 调用栈采集定义 │ ├── profiler_kineto.cpp # 启动采集/kineto 的 Profiler 后端逻辑 │ └── profiler_kineto.h # 启动采集/kineto 的 Profiler 后端定义 │ └── ATen/ # ATen C++ 源码 │ ├── record_function.cpp # RecordFunction 采集逻辑 │ └── record_function.h # RecordFunction 定义

从源码结构可以清晰地看到职责边界:

  • Python 前端(torch/profiler/profiler.py):负责向用户暴露profile()schedule()ProfilerActivity等 API,并把用户的配置(如record_shapeswith_stack)翻译成底层ProfilerConfig
  • Python 后端(torch/autograd/profiler.py):torch.autograd.profiler模块中的profile类,历史上是旧式 Profiler 的入口,现在与 Kineto 后端并存。
  • C++ 采集层torch/csrc/profiler/):实现线程级事件缓冲(ThreadLocalSubqueue)、事件树构建(Result)、Kineto shim 与时钟转换。
  • Autograd C++ 层torch/csrc/autograd/):profiler_kineto.cpp实现回调注册与前后端桥接,profiler_python.cpp实现基于 CPython C API 的调用栈追踪。

RecordFunction:CPU 侧事件插桩的基础设施

RecordFunction是 Profiler 插桩 CPU 侧事件的核心机制,其定义位于 aten/src/ATen/record_function.h。

它是一个通用的函数调用插桩手段,并非 Profiler 专有:也可以用于其他通用场景,例如 PyTorch 的 大规模部署特性。在 PyTorch 内部,它已被安插在若干关键位置,最典型的是dispatcher中,环绕每一个算子调用(见 aten/src/ATen/core/dispatch/Dispatcher.h)。

RecordFunction的核心设计是回调注册制:用户(或 PyTorch 自身)可以注册回调,每当执行路径遇到一个RecordFunction守卫时,这些回调就会被触发。Profiler 正是利用这一机制来记录每个算子调用的开始与结束时间,以及用户自定义的RecordFunction注释。

从工程角度,RecordFunction机制被刻意设计为低开销,尤其是在没有注册任何回调时。这是因为算子调度路径是训练/推理的热路径,任何额外开销都会被放大。尽管如此,一旦启用回调,依然会引入一定的性能损耗——这解释了为什么 Profiler 只在需要时开启采集。

RecordFunction还提供了 Python 绑定:with torch.profiler.record_function(...)。这是用户在代码中标记模块级事件的常用手段,例如:

import torch with torch.profiler.profile(activities=[torch.profiler.ProfilerActivity.CPU]) as prof: with torch.profiler.record_function("my_custom_module"): x = torch.randn(1024, 1024) y = x.mm(x)

自定义的USER_SCOPE事件与算子事件一样会被采集,并出现在最终的 trace 中。

Autograd 集成:用序列号与前向线程 ID 关联前反向

自动求导引擎负责自动计算梯度。Profiler 从 autograd 引擎记录两类关键信息,用于在 trace 中把反向算子与触发它的前向算子关联起来:

序列号(Sequence Number)

定义位于 aten/src/ATen/SequenceNumber.h。这是一个每线程唯一的索引,分配给前向传播中的每次算子调用。当反向算子被触发时,它会被赋予与其来源前向算子相同的序列号。利用这一点,Profiler 能够完成前向/反向算子的匹配;在 Chrome Trace 中,该功能以"fwd_bwd" flow events的形式呈现。

需要特别注意的是(README 中的脚注):只有输入张量需要梯度的算子调用才会被分配序列号。这意味着纯推理(无梯度需求)场景下序列号关联的价值有限,而在训练场景中该机制是剖析反向传播热点的重要依据。

前向线程 ID(Forward Thread ID)

Autograd 可以在多线程环境中使用。前向线程 ID 表示前向算子执行所在线程的 ID(相关字段可见于 aten/src/ATen/record_function.h 的RecordFunction定义中)。之所以需要它,是因为上述序列号只在单个线程内唯一:不同线程上可能产生相同的序列号,必须用前向线程 ID 加以区分。

在 torch/csrc/profiler/collection.h 的TorchOpBasicFields中可以看到这两个字段的直接体现:

struct TorchOpBasicFields { int64_t sequence_number_{0}; uint64_t forward_tid_{0}; ... };

ExtraFields<EventType::TorchOp>继承TorchOpBasicFields,在采集时随事件一起写入,供后处理阶段构建前反向关联。

Torch 算子采集流程:回调注册、线程子队列与热路径优化

本节描述auto-trace(进程内、同步)模式下 torch 算子的通用采集流程。关于 on-demand(进程外、异步)追踪的细节,可参考 Libkineto 的 README(Kineto 作为第三方子模块被引入)。

回调的注册:profiler_kineto.cpp

当一次 trace 开始时,autograd/profiler 后端会调用 torch/csrc/autograd/profiler_kineto.cpp 来准备、启动或停止采集。在 trace 启动时,定义于该文件中的onFunctionEnteronFunctionExit回调会被注册到RecordFunction机制中。

源码中回调分为两组(torch/csrc/autograd/profiler_kineto.cpp):

  • onFunctionEnterGlobal/onFunctionExitGlobal:全局回调,用于KINETO_ONDEMAND或配置了profile_all_threadsKINETO会话;
  • onFunctionEnterTLS/onFunctionExitTLS:线程本地回调,仅对 trace 启动时存在的线程生效。

回调的注册范围由ExperimentalConfig决定,分为两种模式:

  • Global(全局):回调注册到执行期间的所有线程。
  • Local(本地):回调仅注册到trace 开始时刻已存在的线程上。
auto recordFunctionCallback = at::RecordFunctionCallback(onFunctionEnterGlobal, onFunctionExitGlobal) .needsInputs(state_ptr->config().report_input_shapes) .scopes(scopes);

needsInputs(config.report_input_shapes)表明:只有当用户开启record_shapes时,回调才需要算子输入信息,否则采集开销更小。

线程子队列与begin_op

onFunctionEnter内部,Profiler 会为每个线程创建一个ThreadLocalSubqueue实例(见 torch/csrc/profiler/collection.h),确保每个 CPU 算子都与它实际执行的线程关联。当一个 torch 算子进入时,Profiler 调用定义在collection.cppbegin_op来记录必要信息。

ThreadLocalSubqueue的设计值得关注:

  • 内部使用AppendOnlyList(只追加、块大小为 512)作为事件存储,避免每次算子都分配独立 vector;
  • 每种事件类型对应独立的存储区:torch_ops_(算子事件)、allocations_(内存分配)、backend_events_(后端事件)、vulkan_events_ooms_(OOM)、py_calls_(Python 调用);
  • 算子事件的correlation idEventBlock按块起始 ID + 块内偏移计算,无需额外分配(torch/csrc/profiler/collection.cpp)。

README 特别强调:begin_op被刻意设计得非常轻量,因为它处于 profiling 的 "hot path"(热路径)上,过高的额外开销会扭曲 profile 结果、降低其参考价值。因此回调期间只收集最必要的信息,大部分逻辑都推迟到后处理阶段完成。这也是InputOutputEncoder存在的原因——它在采集期把算子输入的形状、dtype 编码进连续的AppendOnlyList,避免为每个算子创建 vector,而在后处理时才解码还原(见 torch/csrc/profiler/collection.cpp)。

全局会话的并发安全

对于全局回调,profiler_kineto.cpp中还有一个GlobalCallbackSession协调机制(torch/csrc/profiler/kineto_profiler.cpp):由于全局回调会在任意线程上触发,而disableProfiler()可能在另一个线程上销毁 profiler 状态,因此每个回调通过enter()/exit()只在极短的临界区(getGlobal()+RecordQueue访问)内持有 in-flight 计数;teardown 时drain()关闭会话并等待所有 in-flight 回调退出,避免 use-after-free。退出回调还会校验session_generation_,丢弃跨会话悬空的退出事件。

内存分配事件采集:绕过 RecordFunction 的瞬时事件

与有起止时刻的算子事件不同,内存分配事件被表示为cpu_instant_event(零持续时间)。因此,分配事件不经过RecordFunction,而是由reportMemoryUsage直接调用emplace_allocation_event,把事件入队到对应的ThreadLocalSubqueue

在 torch/csrc/autograd/profiler_kineto.cpp 中可以看到:

void reportMemoryUsage( void* ptr, int64_t alloc_size, size_t total_allocated, size_t total_reserved, c10::Device device) override { if (config_.profile_memory && !config_.disabled()) { recordQueue.getSubqueue()->emplace_allocation_event( c10::getApproximateTime(), ptr, alloc_size, total_allocated, total_reserved, device.type(), device.index()); } }

同样地,OOM(内存不足)事件通过reportOutOfMemoryemplace_ooms_event进入队列。RawAllocation结构(torch/csrc/profiler/collection.h)记录了分配指针、大小、累计分配量/预留量以及设备信息,并被static_assert(std::is_trivial_v<RawAllocation>)强制保持平凡类型以提升性能。

在 Python 前端,对应的开关是profile()profile_memory参数(torch/profiler/profiler.py):

torch.profiler.profile( activities=[torch.profiler.ProfilerActivity.CPU], profile_memory=True, # 跟踪张量内存分配/释放 )

Kineto 集成:多架构事件采集、预热与导出

Kineto 是 Profiler 获取 GPU 及其他加速器事件的关键抽象层。它作为第三方子模块(third_party/kineto/目录)被引入,通过与 CUPTI 等库交互来接收 GPU 与加速器事件,再转发给前端 Profiler。

预热(warmup)的原因

Kineto 需要时间"prepare"(也称 "warmup")这些第三方模块,以避免初始化例程扭曲 profile 结果。理论上可以在作业启动时就完成预热,但让 CUPTI 这类重量级库持续运行会带来显著的不必要开销,因此预热被安排在正式 trace 之前的短暂窗口内完成。

双向桥接与后处理

如前所述,profiler_kineto.cpp在后台调用合适的 profiler 阶段,同时它还会调用kineto_shim.cpp,后者触发 Kineto 中的对应例程。当一次 trace 完成后,Kineto 收集的所有事件会被转发给 Profiler,原因有两个:

  1. 合并所有数据并完成 Profiler 事件与 Kineto 事件之间的后处理(例如把 Kineto 事件嵌入Result事件树,见 torch/csrc/profiler/collection.h 的ExtraFields<EventType::Kineto>,其中Flow结构用于将 Kineto 的 flow 事件映射进 profiler 树);
  2. 把这些事件作为FunctionEvents转发给 Python 前端,供prof.key_averages()prof.table()等 API 使用。

文件导出

集成的最后一步是文件导出。所有事件收集并后处理完成后,它们可以导出为 JSON 文件,供PerfettoChrome Tracer可视化。这一步通过调用 Kineto 的ActivityTraceInterface::save完成,把所有事件信息写入磁盘。

在 Python 侧,tensorboard_trace_handler会自动生成 trace 目录:

from torch.profiler import profile, ProfilerActivity, tensorboard_trace_handler with profile( activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA], on_trace_ready=tensorboard_trace_handler("./log/resnet18"), ) as prof: model(x) # 输出的 JSON 文件位于 ./log/resnet18/ 目录下

Python 栈追踪:基于 CPython 剖析 API 的实现

当在 profiler 中设置with_stack=True时,Python 栈追踪器会使用PythonTracerBase中定义的make函数生成,具体实现位于profiler_python.cpp(torch/csrc/autograd/profiler_python.cpp)。

使用PyEval_SetProfile追踪执行事件

为了剖析调用栈,实现使用PyEval_SetProfile来追踪并处理 Python 程序中的各种执行事件(对应源码中recordPyCall/recordCCall的实现与PyTrace_*分支,见 torch/csrc/autograd/profiler_python.cpp)。它针对以下具体场景做出响应:

CPython 事件处理函数采集内容
PyTrace_CALLrecordPyCall记录每次 Python 函数调用,捕获后续分析所需的关键细节
PyTrace_C_CALLrecordCCall记录对 C 函数的调用,包括相关参数,提供程序执行流的完整视图
PyTrace_RETURN记录 Python 函数的退出时间,实现精确的函数执行时长测量
PyTrace_C_RETURNPyTrace_C_EXCEPTION记录 C 函数的退出时间(无论正常结束还是因异常结束),确保所有执行路径都被统计

RecordQueue/ThreadLocalSubqueue中,Python 调用事件被以(TraceKey, approx_time_t)对的形式存入py_calls_(torch/csrc/profiler/collection.h),后处理阶段通过匹配入口与出口来构建完整的调用事件。

注意事项(README 原文):对于 Python 3.12.0–3.12.4,CPython 中存在一个 bug,需要使用sys.monitoring作为 workaround。这意味着不同 Python 小版本下 Python 栈追踪的底层实现路径可能不同。

Python 前端的对应参数为with_stack(torch/profiler/profiler.py):

with torch.profiler.profile( activities=[torch.profiler.ProfilerActivity.CPU], with_stack=True, # 记录算子的源码信息(文件名与行号) with_modules=True, # 记录模块层级(含函数名) ) as prof: ...

需要说明的是,README 与本仓库当前代码均提示with_modules已被标记为弃用(deprecated),未来的版本可能移除;而旧式的with_flops参数在 eager 模式下已不起作用,建议以with_stack=Truerecord_shapes=True组合来获得可归因的堆栈与形状信息。

时钟对齐:TSC 周期与 Unix 纳秒之间的转换

在创建时间戳时,Profiler 会根据系统环境选择最有效的时钟。大多数 Linux 系统的默认选择是TSC,它以 CPU 周期(cycles)的形式记录时间。为了把这个时间转换为纳秒级 Unix 时间,Profiler 会创建一个时钟转换器(clock converter)

如果 profiler 中包含了 Kineto,这个转换器也会被传入 Kineto,以确保两端时间轴对齐——这正是 trace 中 CPU 事件与 GPU 事件能够精确排布在统一时间线上的基础。

在源码中可以看到这一机制的直接体现(torch/csrc/autograd/profiler_kineto.cpp):

auto converter = clockConverter.makeConverter(); #ifdef USE_KINETO libkineto::get_time_converter() = converter; #endif auto records_and_trace = recordQueue.getRecords(std::move(converter), startTime, end_time);

c10::ApproximateClockToUnixTimeConverterclockConverter的类型)负责建立 TSC 计数与 Unix 时间的映射;getRecords在把各线程子队列中的原始近似时间事件物化为Result时,统一使用该转换器换算为纳秒时间戳。相关类型定义位于 c10/util/ApproximateClock.h。

总结:一次完整 trace 的端到端数据流

综合以上各节,一次torch.profiler.profile(...)调用的完整数据流可以归纳为:

  1. 启动:Python 前端profile()解析参数并调用后端,profiler_kineto.cpp创建KinetoThreadLocalStateRecordQueue,注册onFunctionEnter/Exit回调(全局或线程本地);
  2. 采集:每个算子经 dispatcher 触发RecordFunction回调 →begin_op在对应线程的ThreadLocalSubqueue中写入轻量事件;分配与 OOM 事件绕过RecordFunction直接入队;Python 栈事件由PyEval_SetProfile驱动;GPU 事件由 Kineto 通过 CUPTI 等收集;
  3. 结束finalizeTrace()停止队列、构建时钟转换器(并同步给 Kineto),getRecords()把原始事件物化为Result树,与 Kineto 事件合并、完成后处理(前反向匹配、栈/模块/形状解码、未完成事件补记结束时间);
  4. 导出:通过ActivityTraceInterface::save写出 JSON,供 Chrome Trace / Perfetto 可视化,或转成FunctionEvents供 Python 侧key_averages()等 API 做表格统计。

理解这条链路后,你将能更有针对性地使用 Profiler:用record_shapes观察算子输入形状、用with_stack定位调用来源、用profile_memory追踪显存分配,并通过 Chrome Trace 中的 "fwd_bwd" flow 事件分析前反向传播的时间占比。

【免费下载链接】pytorchTensors and Dynamic neural networks in Python with strong GPU acceleration项目地址: https://gitcode.com/GitHub_Trending/py/pytorch

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 14:32:41

SolidWorks高级测量与倒角技术实战指南

1. SolidWorks测量功能实战解析作为机械设计领域的黄金标准工具&#xff0c;SolidWorks的测量功能远不止简单的尺寸读取。在最近参与的医疗器械外壳项目中&#xff0c;我深刻体会到精准测量的重要性——0.1mm的误差就可能导致装配干涉。按下键盘上的"M"键调出测量工具…

作者头像 李华
网站建设 2026/9/11 14:30:49

AI灵感生成工具实战指南:破解日常创意卡点

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 14:29:53

OpenClaw实战:Windows 11上从零部署AI智能体并接入Skills全攻略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 14:28:38

黏菌算法优化GRNN在时间序列预测中的应用

1. 黏菌优化算法与GRNN的跨界组合价值在时间序列预测领域&#xff0c;传统统计方法与现代机器学习模型各有优劣。广义回归神经网络(GRNN)作为径向基函数网络的一种变体&#xff0c;凭借其单次学习、无需迭代训练的特性&#xff0c;在实时预测场景中展现出独特优势。但GRNN的预测…

作者头像 李华
网站建设 2026/9/11 14:27:28

现在口碑好的AI写作辅助网站有哪些品牌?学生党亲测反馈

每到期末、毕业答辩、课题申报阶段&#xff0c;很多学生都会陷入论文写作的困境&#xff1a;选题毫无头绪、大纲搭建逻辑混乱、正文撰写耗时长、参考文献格式出错、查重重复率偏高、AIGC检测告警、本校论文排版标准复杂。纯人工从零开始撰写、反复修改格式和降重&#xff0c;不…

作者头像 李华