news 2026/9/8 23:27:34

PyTorch C++ Autograd 梯度计算完全指南:backward 与 grad 函数深入解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PyTorch C++ Autograd 梯度计算完全指南:backward 与 grad 函数深入解析

PyTorch C++ Autograd 梯度计算完全指南:backward 与 grad 函数深入解析

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

梯度计算是 PyTorch C++ 前端中最核心的能力之一。本文将系统讲解 torch::autograd::backward 与 torch::autograd::grad 两个高层梯度计算函数的完整签名、参数语义与底层实现原理,并演示 Tensor 上内置的backward().grad().detach().requires_grad_()等方法在自动微分(Automatic Differentiation)流程中的实际用法。读完本文,你能够在纯 C++ 环境下完成前向图构建、反向传播、梯度清零、高阶导数计算等常见训练与调试任务,并对链式法则背后的 Jacobian-vector product(雅可比向量积)机制形成源码级认知。

梯度计算概览:C++ 前端中的自动微分

PyTorch 为张量计算提供了自动求导能力:它会在 Tensor 上记录所有算子操作,构建一张有向无环的计算图,随后通过反向传播(backpropagation)自动计算图中各节点相对于叶子张量的梯度。与 Python 端的torch.autograd.backward/torch.autograd.grad相对应,纯 C++ 环境(可暴露给 C++ API 与 TorchScript)提供了两个顶层函数:

  • torch::autograd::backward(...):对给定张量执行反向传播,并把梯度累加到叶子张量的.grad中;
  • torch::autograd::grad(...):计算输出关于指定输入的梯度并作为返回值返回,不写回.grad

从源码注释可以看到(torch/csrc/autograd/autograd.cpp),这份 C++ 实现复刻了torch/autograd/__init__.pytorch._C._EngineBase.run_backward中的既有逻辑,是一个不依赖 Python 的纯 C++ 自动微分入口,二者必须长期保持语义一致。

在 Autograd 索引文档 中,官方给出了使用场景定位:训练神经网络(梯度自动计算)、为特殊算子实现自定义反向传播、以及需要细粒度控制梯度计算过程时,都应使用这一套 C++ 自动微分设施。

torch::autograd::backward:向叶子累加梯度

函数签名与完整参数表

backward的完整声明位于 torch/csrc/autograd/autograd.h:

void torch::autograd::backward( const variable_list& tensors, const variable_list& grad_tensors = {}, std::optional<bool> retain_graph = std::nullopt, bool create_graph = false, const variable_list& inputs = {});
参数类型默认值语义
tensorsvariable_list—(必填)需要求导的输出张量,即被微分函数的结果。
grad_tensorsvariable_list{}Jacobian-vector product 中的"向量",通常是对应张量的预计算梯度;长度须与tensors匹配。
retain_graphstd::optional<bool>std::nulloptfalse时,反向计算结束后用于求梯度的计算图会被释放;默认取create_graph的值。
create_graphboolfalsetrue时构造导数图,从而允许计算更高阶导数。
inputsvariable_list{}只针对这些输入把梯度累加到at::Tensor::grad,其余张量被忽略;为空时累加到所有用于计算tensors的叶子张量。

语义细节

backward计算的是给定张量关于图叶子(graph leaves)的梯度之和,整个图用链式法则求导:

  • tensors中存在非标量(数据元素多于一个)且需要梯度的张量,则计算 Jacobian-vector product,此时必须额外提供grad_tensors。它应是长度匹配的序列,保存 JVP 中的"向量",通常是被微分函数输出关于对应张量的梯度;对不需要梯度的张量,可用torch::Tensor()(未定义张量)占位。
  • 该函数把梯度累加到叶子张量上,因此多次调用前通常需要先将.grad清零(对应 Python 中的optimizer.zero_grad()/grad.zero_()语义)。
  • 传入inputs时,梯度只会被累加到这些输入上;当某个输入不是叶子时,当前实现仍会调用其grad_fn(虽然严格来说不必如此),这是官方明确标注的"实现细节",用户不应依赖该行为(参见 autograd.h 中的注释)。

核心示例

#include <torch/torch.h> auto x = torch::randn({2, 2}, torch::requires_grad()); auto y = x * x; auto z = y.sum(); // 计算梯度 z.backward(); std::cout << x.grad() << std::endl; // dz/dx = 2x,被写入 x.grad()

torch::autograd::grad:返回梯度而非累加

grad的声明同样位于 autograd.h:

torch::autograd::variable_list grad( const variable_list& outputs, const variable_list& inputs, const variable_list& grad_outputs = {}, std::optional<bool> retain_graph = std::nullopt, bool create_graph = false, bool allow_unused = false);
参数类型默认值语义
outputsvariable_list—(必填)被微分函数的输出。
inputsvariable_list—(必填)需要返回梯度的输入;梯度返回而不累加进at::Tensor::grad
grad_outputsvariable_list{}JVP 中的"向量",通常是对应每个输出的梯度;未定义张量可占位,语义同backwardgrad_tensors
retain_graphstd::optional<bool>std::nulloptbackward,默认取create_graph的值。
create_graphboolfalse构造导数图以便计算更高阶导数。
allow_unusedboolfalsefalse时,如果指定了未参与计算outputs的输入(其梯度恒为零),会报错;为true则容忍此情况。

语义要点与示例

grad计算并返回输出关于输入的梯度之和,其中grad_outputs长度应与outputs匹配;某输出若不requires_grad,其梯度可用torch::Tensor()表示。gradbackward最本质的区别在于:它不修改任何张量的.grad,而是把结果以variable_list形式返回,因此非常适合在无需持有"参数"对象的工具函数或分析代码中使用:

#include <torch/torch.h> auto x = torch::randn({2, 2}, torch::requires_grad()); auto y = x * x; auto z = y.sum(); // 用 grad() 针对特定输出-输入对求梯度(不会写回 x.grad()) auto grads = torch::autograd::grad({z}, {x}); std::cout << grads[0] << std::endl; // 2x

Tensor 内置的梯度相关方法

除顶层函数外,torch::Tensor自身还提供一组梯度计算方法。文档在 gradient.md 中给出的用法可扩展如下:

// 启用梯度追踪 auto x = torch::randn({2, 2}).requires_grad_(true); // 检查该张量是否要求梯度 bool needs_grad = x.requires_grad(); // backward 之后访问梯度 auto grad = x.grad(); // 从计算图中分离出来(detach 后共享数据但不参与反向传播) auto x_detached = x.detach();

配合标量输出的便捷反向调用,最常用的组合是:

auto loss = model->forward(input); // 某种标量损失 loss.backward(); // 自动将梯度累加到所有叶子(模型参数) for (auto& p : model->parameters()) p.grad().zero_(); // 下一轮前清零

从实现上看,这些方法最终汇聚到at::Tensor的 autograd hooks。在 torch/csrc/autograd/variable.h 中定义的VariableHooks(实现at::impl::VariableHooksInterface)提供了_backwardrequires_grad_retain_gradretains_gradgrad_fnis_leafis_viewset_data等一系列回调——Tensor::backward().grad().detach().is_leaf()等在 dispatch 层即通过这些 hooks 落到 autograd 运行时。其中Variablerequires_grad()返回requires_grad_ || grad_fn_(variable.h),即一个非叶子中间张量即便其标志位为假,只要存在grad_fn也会被视为参与求导。

在不需要梯度的推理阶段,应使用torch::NoGradGuard关闭梯度记录(对应 Python 的with torch.no_grad()):

torch::NoGradGuard no_grad; // 进入该作用域后不构建计算图 auto result = model->forward(input); // 不计算梯度

源码级原理:backward 与 grad 的内部执行流程

两份高层 API 的实现集中在 torch/csrc/autograd/autograd.cpp,可拆解为三个步骤。

1. _make_grads:构造种子梯度

backwardgrad都会首先调用静态函数_make_grads(autograd.cpp)把用户输入规范化为参与反向的梯度序列,其行为包括:

  • grad_outputs为空时,对每个requires_grad()的输出做两条TORCH_CHECK校验:输出必须为标量output.numel() == 1,否则报"grad can be implicitly created only for scalar outputs"),且数据类型必须是浮点类型(否则报"grad can be computed only for real scalar outputs...");通过后用at::ones_like生成全 1 种子梯度。
  • grad_outputs非空时,先校验num_tensors == num_gradients;随后逐元素处理:未定义的grad_output走与上面相同的隐式创建逻辑;已定义者直接沿用,但若输出为复数,则校验grad_outputoutput的复数属性一致(要求 dtype 匹配)。

这段逻辑解释了为什么对非标量tensors调用backward()(不传grad_tensors)会报错——必须显式给出与输出同形状的grad_tensors作为"向量"。

2. run_backward:把任务交给求导引擎

规范化的梯度随后进入run_backward(autograd.cpp):

  1. 确定根节点(roots):对每个输出调用impl::gradient_edge(output)取其梯度边,并校验该边确有grad_fn,否则报"element ... of tensors does not require grad and does not have a grad_fn"
  2. 确定输出边(output_edges):当显式传入inputs时,为每个输入定位其grad_fn(无则退回try_get_grad_accumulator);backwardaccumulate_grad = true还会对输入调用retain_grad(),以便反向时写入.grad;对计算图中不可达的输入,用Identity节点占位(参见源码中的NOTE [ Autograd Unreachable Input ])。
  3. 调用引擎:以Engine::get_default_engine().execute(roots, grad_outputs, keep_graph, create_graph, accumulate_grad, output_edges)真正执行反向传播。
  4. 未使用输入检查:仅当非空inputsallow_unused = false时,校验grad_inputs[i].defined(),对未参与图的输入报错并提示设置allow_unused = true

3. backward 与 grad 的分流

两个公开函数(autograd.cpp)的差别在run_backward的调用参数上一目了然:

  • backwardretain_graph缺省时回退为create_graph;调用run_backward(..., /*allow_unused=*/true, /*accumulate_grad=*/true),把梯度累加到叶子。
  • grad:同样回退retain_graph,但以allow_unused的显式传入值、accumulate_grad = false调用,因此梯度只返回、不累加

参数选型的工程要点

结合文档与上述实现,可归纳出以下工程性结论(默认值语义均有源码依据):

  1. 非标量输出的 JVP 规则:只要tensors/outputs中有非标量且requires_grad()的张量,就必须按序提供与其等长的grad_tensors/grad_outputs;对不参与求导的输出可用torch::Tensor()占位,_make_grads会为其自动回退到隐式创建分支。
  2. retain_graphcreate_graph的联动:二者缺省时retain_graphcreate_graph的值。由于默认create_graph = false,意味着常规一次反向后计算图即被释放;如需多次backward()(例如共享中间结果的多个损失),才显式传retain_graph = true——官方提醒,大多数情况下应通过把中间结果作为输入单独调用grad()等更高效的方式规避,而不是无脑保留整张图。
  3. inputs参数与内存效率backward传入inputs可把反向"裁剪"到仅关注这些输入的梯度(其余张量被忽略,仅在计算路径上被回溯),run_backward只会为其构造 output edges。
  4. 清空梯度backward采用累加语义,训练循环中应在每轮迭代前对.grad清零(例如param.grad().zero_(),语义对应 Python 训练循环的optimizer.zero_grad())。
  5. 高阶导数:把create_graphtrue会构造导数图,从而支持二阶及以上的导数计算(double backward),代价是额外的图内存开销。
  6. 推理模式:非训练场景务必用torch::NoGradGuard(或等价机制)关闭自动微分图的构建,避免不必要的内存与算力开销。

参考与延伸阅读

  • 本主题原文:docs/cpp/source/api/autograd/gradient.md
  • Autograd 模块总览与使用时机:docs/cpp/source/api/autograd/index.md
  • 函数声明与逐参数文档:torch/csrc/autograd/autograd.h
  • 纯 C++ 实现(_make_grads/run_backward/backward/grad):torch/csrc/autograd/autograd.cpp
  • Tensor 方法背后的 autograd hooks(VariableHooks):torch/csrc/autograd/variable.h
  • 自定义反向传播与求导模式扩展:docs/cpp/source/api/autograd/custom_functions.md、docs/cpp/source/api/autograd/modes.md

【免费下载链接】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/8 23:24:57

uncorr. ECC 显示2是什么意思?服务器内存告警排查全流程

我接手过不少说新不新、说老不老的服务器&#xff0c;最怕的不是性能不够&#xff0c;而是安静跑着的机器突然被一条硬件告警打断。有一回巡检&#xff0c;BMC的事件日志里躺着一行字&#xff1a;Uncorrectable ECC Error, DIMM_A2, Event Count 2。当时看到“Uncorrectable”这…

作者头像 李华
网站建设 2026/9/8 23:24:46

散射中心提取程序解析:从雷达回波到目标特征的关键技术

简介&#xff1a;面向雷达目标识别与成像应用&#xff0c;散射中心提取程序是一套基于MATLAB的信号处理工具包&#xff0c;聚焦从目标回波信号中提取散射中心&#xff0c;可用于雷达成像、目标特征分析与识别&#xff0c;适合雷达信号处理方向的研究者、工程师及高年级学生使用…

作者头像 李华
网站建设 2026/9/8 23:24:06

Drawio 启动屏白屏?5 分钟定位与修复的完整指南

Drawio 启动屏白屏&#xff1f;5 分钟定位与修复的完整指南 【免费下载链接】drawio-desktop Official electron build of draw.io 项目地址: https://gitcode.com/GitHub_Trending/dr/drawio-desktop 双击 Drawio 图标&#xff0c;屏幕转圈十几秒&#xff0c;最后只剩一…

作者头像 李华
网站建设 2026/9/8 23:23:52

Session First还是Agent First?AI产品设计范式的本质与选型指南

1. 两个热词背后的真正分歧最近和几拨做 AI 产品的朋友聊天&#xff0c;发现大家手里都在悄悄押注两条不同的路线&#xff1a;一边是坚持 Session First&#xff0c;把对话窗口当作产品的主战场&#xff1b;另一边是All in Agent First&#xff0c;觉得未来的应用就应该是一堆自…

作者头像 李华