OneFlow源码解析:自动微分机制
撰文 | 郑建华

更新|赵露阳、王迎港

深度学习结构一般经过主动微分(autograd)机制核算梯度并反向传达。本文尝试经过一个简略的比方,粗浅地调查一下OneFlow的autograd的完成机制。

1

主动微分根底

主动微分相关的材料比较多,个人感觉主动微分的原理介绍(mp.weixin.qq.com/s/BwQxmNoSB… )这个系列及其引用的材料对相关背景知识的介绍比较完整明晰。

下面分几种状况对梯度传达的原理做一些直观解说。

1.1 stack网络的梯度传达

以x -> f -> g -> z这个stack网络为例,根据链式法则:

∂z/∂x = ∂z/∂g * ∂g/∂f * ∂f/∂x

实践运行时,在梯度反向传达进程中:

  • z将∂z/∂g传给g。
  • 假如节点g有权重w需求核算梯度,就核算∂z/∂w = ∂z/∂g * ∂g/∂w。
  • g需求核算∂g/∂f,再乘以z传过来的梯度,将成果传给f。g只需求给f传递链式乘积的成果,不需求传递各项明细。
  • 在练习阶段的前向核算时,g需求保存∂g/∂f核算依靠的中心成果、以供反向核算时运用。
  • 其它节点的传达状况依次类推。

1.2 简略graph的梯度传达

以下面这个简略的graph拓扑为例。

OneFlow源码解析:自动微分机制

在持续之前,需求了解一下多元复合函数微分的根本公式。
下图中,u和v都是关于x和y的函数,z是关于u和v的函数。

OneFlow源码解析:自动微分机制

根据这个公式能够知道,z对x的梯度别离沿两条链路传达,z -> u -> x和z -> v -> x,节点x将两个梯度之和作为z对x的梯度。

1.3 杂乱graph的梯度传达

再看一个拓扑略微杂乱点的比方:

OneFlow源码解析:自动微分机制

上图能够视为x -> U -> L,其间U是e -> … -> h的子图。f -> g的子图能够视为V。

关于节点h来说,它需求把梯度传给g和k。对节点e来说,它需求对f和k传来的梯度求和,才是∂L/∂e。这样,L对x的梯度,仍能够按链路拆解,一条链路前后节点间的梯度是乘积联系,传入的多条链路梯度是加和联系。

这篇博客(blog.paperspace.com/pytorch-101… )中有一个简直一样的拓扑图,给出了部分权重参数的梯度公式。

2

autograd中tensor相关的一些根本概念

2.1 叶子节点

OneFlow的autograd文档(docs.oneflow.org/en/master/b… )中介绍了leaf node和root node的概念。只要输出、没有输入的是leaf node,只要输入、没有输出的是root node。

个人了解,假如把weight、bias、data视为核算图的一部分,这些节点便是叶子节点(op不是叶子节点)。尤其是从反向核算图的视角(discuss.pytorch.org/t/what-is-t… )看,这些节点的grad_fn是空,反向传达到这些节点就会停止。

is_leaf和requires_grad有比较密切的联系,但二者又是独立的。PyTorch是这样解说的:(pytorch.org/docs/stable…)

  • requires_grad=false的节点都是叶子节点。比方data。
  • requires_grad=true的节点假如是用户创立的,也是叶子节点。比方weight和bias。
  • 在梯度的反向核算进程中,只要叶子节点的梯度才会被填充。关于非叶子节点,假如要填充梯度信息,需求显式设置retain_grad=true。
  • requires_grad=true才会核算、填充梯度。比方y = relu(x),y是op创立的、不是叶子节点。但假如x需求核算梯度,则y.requires_grad==true。但不需求为y填充梯度。

关于叶子节点这个概念,目前找到的首要是直观描绘,还没看到严格、明晰的界说。也可能是因为用户一般不会直接运用is_leaf(discuss.pytorch.org/t/what-is-t… ),这个概念仅仅在阅览代码的时分才会触及到。

下面的材料能够供进一步参阅:

  • What is the purpose of is_leaf? (discuss.pytorch.org/t/what-is-t…)
  • 叶子节点和tensor的requires_grad参数(zhuanlan.zhihu.com/p/85506092

2.2 tensor detach

Tensor的detach办法(github.com/Oneflow-Inc… )会创立一个新的tensor,新tensor的属性中

  • requires_grad = false

  • is_leaf = true

detach的意思是从grad的反向核算图中把tensor分离出来。新的tensor与本来的目标共享存储,但不参加反向图的拓扑结构。原有目标的requires_grad属性不变。

比方下面的代码,修改一个目标的数据,另一个目标的数据也会改变。

import oneflow as flow
y = flow.Tensor([1, 2, 3])
x = y.detach()
x[0] = 4
assert(y[0] == 4)

3

示例代码

本文经过如下代码来调查OneFlow的autograd机制。

import oneflow as flow
# y is scalar
x = flow.tensor([-1.0, 2.0], requires_grad=True)
y = flow.relu(x).sum()
y.backward()
print(x.grad)
# y is not scalar
x = flow.tensor([-1.0, 2.0], requires_grad=True)
y = flow.relu(x)
y.backward(flow.Tensor([1, 1]))
print(x.grad)

y.backward办法有两种接口:

  • 假如y是一个标量(比方loss),不需求传递任何参数。
  • 假如y是一个向量,需求传入一个与y的shape共同的向量作为参数。

为什么会有这种差异呢?下面几篇参阅材料中对这个问题做了比较详细的解说。简略的说:

  • 假如函数的输出是向量,在反向传达的进程中会形成梯度tensor shape的维度胀大,完成杂乱、性能差。
  • 假如函数的输出是标量,反向传达梯度tensor的shape与参数变量的shape共同,不会出现维度胀大,更容易完成。
  • 关于向量版别的backward,能够设想存在某个loss函数,backward的参数是loss传达到y这儿的梯度。因为前后节点间的梯度是乘积联系,所以用ones替代这个设想的梯度,这样核算成果x.grad便是y对x的梯度。

后续将以y.backward(flow.Tensor([1, 1]))为例调查一下autograd的机制。其反向图只要x <- y这一步。

参阅材料

  • 主动求梯度 (tangshusen.me/Dive-into-D…
  • PyTorch 的 backward 为什么有一个 grad_variables 参数?(zhuanlan.zhihu.com/p/29923090

3.1 梯度成果的存储

Tensor的grad属性(github.com/Oneflow-Inc… ),在读取值时调用的是acc_grad()办法(acc应该是accumulate的缩写)。这样就知道梯度实践存储在哪里,读代码时能够要点重视相关部分。

调用流程如下:

OneFlow源码解析:自动微分机制

注:图片中的MirroredTensor在最新源码中,现已更名为LocalTensor,其实是一样的。

4

autograd相关的类图联系

下图展示了autograd相关类的联系

OneFlow源码解析:自动微分机制

在看autograd代码之前,能够参照这个类图,了解其间的结构和联系,有助于了解代码中各个部分的效果。

在eager形式下,用户经过op的组合逐渐构建出前向核算图。在履行前向核算的进程中,引擎会为autograd需求的反向核算图记载必要的信息,在调用backward办法时履行这个反向核算图。

对照上面的类图

站在tensor的视角

  • 前向op输出一个tensor y,即TensorIf <- ReluFunctor这部分。
  • 从y能够找到反向核算图实践履行梯度核算的类,即TensorIf -> FunctionNode ReLU这个链路。
  1. FunctionNode的backward_fn_包含了OpExprGradClosure。它只担任核算其时节点的梯度。
  2. ReLU是履行梯度核算的类,它会调用ReluGradFunctor这个op来履行梯度核算。

站在反向图存储的视角

  • 反向图相关的信息在FunctionNode中保存。
  • 反向核算图的root是tensor(比方y或loss)的grad_fn_node_变量。
  • FunctionNode的next_functions_表明反向图的下流节点,其时节点把梯度成果传给这些下流节点。这些FunctionNode的衔接就构成了反向图的拓扑结构。
  • tensor的梯度存储途径是TensorImpl.AutogradMeta.acc_grad_
  • AutogradMeta.current_grad_是反向图上游传递到其时节点的梯度合计。假如tensor t输入给op u和v,那么u和v反传的梯度会累加到current_grad_。current应该表明到其时正在核算时的累加和。
  • FunctionNode尽管并不持有tensor实例,但它持有tensor的AutogradMeta成员变量指针。

根据上述relu的比方中的节点y

  1. output_meta_data_即y.autograd_meta_
  2. input_meta_data_即x.autograd_meta_
  3. 所以FunctionNode能获取到上下流的梯度数据并进行读写
  • AutoGradCaptureState能够存储一些梯度核算需求的状况信息,比方核算relu的梯度时需求用到它的前向输出成果y。

站在反向图履行的视角

  • GraphTask担任反向图的履行。
  • FunctionNode只保存必要的数据。
  • GraphTask根据这些数据,自己结构遍历需求的数据结构,遍历所有节点、履行梯度核算。

5

前向核算进程中为autograd所做的准备

反向图的履行进程是数据驱动的,数据的存储结构和内容决议了履行的详细动作。

以下评论只针对eager形式。lazy形式下,反向图的构建是多轮优化passes的一部分(github.com/Oneflow-Inc… )。

之前在评论Op、Kernel与解说器(mp.weixin.qq.com/s/gXH7HZ9cF…) 时现已了解Interpreter的效果。仅仅其时要点重视op的履行,疏忽了grad相关的内容。

GetInterpreter(github.com/Oneflow-Inc… )回来的其实是一个AutogradInterpreter目标(github.com/Oneflow-Inc… ),在它的Apply办法中(github.com/Oneflow-Inc… ),调用内嵌Interpreter的一起,也会记载grad核算需求的信息。

AutogradInterpreter::Apply的首要流程如下:

OneFlow源码解析:自动微分机制

Apply的第一步会先核算requires_grad。只要op的任一输入的requires_grad为true,op的输出的requires_grad也为true(github.com/Oneflow-Inc… )(前提是输出的数据类型支持梯度)。y的requires_grad便是在这儿决议的。

比方y = relu(x),假如数据类型支持梯度,y.requires_grad就等于x.requires_grad。

然后会调用内嵌的解说器internal_履行相关核算。在调用内嵌解说器期间,会暂时禁止梯度形式,比方有些op可能会嵌套、多次调用解说器(ReluGradFunctor也会经过解说器履行),这些都不需求梯度逻辑。

需求说明的是,结构x时不会履行grad相关的逻辑,因为inputs的requires_grad都是false,x的requires_grad是在结构的终究才设置的(github.com/Oneflow-Inc… )。

下面要点看一下几个中心函数的逻辑细节。

5.1 梯度闭包的构建

前面临类图的说明中现已提到,OpExprGradClosure只担任其时节点的梯度核算。

GetOrCreateOpGradClosure函数(github.com/Oneflow-Inc… )的中心代码如下:

template<>
Maybe<OpExprGradClosure> BuiltinOpExprImpl<UserOpConf>::GetOrCreateOpGradClosure() const {
if (!op_grad_func_.get()) {
    ...
    op_grad_func_.reset(NewObj<std::string, OpExprGradFunctionIf>(proto().op_type_name()));
    JUST(op_grad_func_->Init(*this));
  }
return std::make_shared<OpExprGradClosure>(op_grad_func_);
}

NewObj会调用AutoRegistrationFactory(github.com/Oneflow-Inc… )获取预先注册的工厂、创立目标。之前在评论Op指令在虚拟机中的履行(mp.weixin.qq.com/s/r5LOoEh-Q…) 时也看到过类似的注册机制。

这儿op_type_name的值是relu,在代码中查找”relu”,能够找到注册ReLU的宏(github.com/Oneflow-Inc… )。宏展开后的代码如下:

static AutoRegistrationFactory<std::string, OpExprGradFunctionIf>::CreatorRegisterTypeg_registry_var4("relu", ([]() { return new ReLU; }));

所以实践回来的目标是ReLU(github.com/Oneflow-Inc… )。其Init函数是个空操作。

OpExprGradClosure仅仅简略的把ReLU存下来供backward履行时调用。整个调用流程如下:

OneFlow源码解析:自动微分机制

5.2 捕获梯度核算需求的数据

调用流程如下:

OneFlow源码解析:自动微分机制

Capture函数(github.com/Oneflow-Inc… )的效果便是为后续的梯度核算保存必要的数据。

需求留意的是,OpExprGradFunction::CaptureIf(github.com/Oneflow-Inc… )中保存的是detach的tensor。这些tensor与本来的tensor共享数据;能够读写梯度数据,但不会参加反向图的拓扑结构。

这个函数把Interpreter传过来的op的detached outputs传给ReLU::Capture(github.com/Oneflow-Inc… )(便是relu的前向输出y),ReLU::Capture就把output[0]存到ReLUCaptureState的saved_tensors_中(github.com/Oneflow-Inc… )。因为关于relu来说,根据y就能够核算梯度。

5.3 保存反向图结构信息

AutogradInterpreter::Apply中会结构一个lambada表达式backward_fn(github.com/Oneflow-Inc… ),其间心逻辑只要一行grad_closure->Apply。

这个lambda的首要效果便是捕获grad_closure这个智能指针。lambda表达式终究会作为FunctionNode的backward_fn_变量。这样才有类图中FunctionNode到OpExprGradClosure这条线,才能从FunctionNode找到closue、履行节点的梯度核算。

GetThreadLocalAutogradEngine()->AddNode这个函数(github.com/Oneflow-Inc… )很要害,AddNode的首要任务(github.com/Oneflow-Inc… )是为inputs和outputs创立FunctionNode、并保存反向图遍历需求的数据。其输入参数中的inputs/outputs,是前向核算的op的inputs/outputs。关于relu来说,inputs便是x,outputs便是y。

在上述示例代码中,关于x,因为它是叶子节点、也需求梯度,在AddAccumulateFunctionNode会将grad_fn_node设置为一个空操作的函数(github.com/Oneflow-Inc… )。之所以是空操作,是因为叶子节点只需求存储梯度、不需求自己核算梯度;它所需求的梯度核算成果会由反向图的上游节点保存到x.autograd_meta_中。

之后会为y结构GraphFunctionNode并形成节点衔接(github.com/Oneflow-Inc… )、并保存到grad_fn_node(github.com/Oneflow-Inc… )。需求留意的是,这儿的backward_fn便是AutogradInterpreter::Apply中的lambda表达式(github.com/Oneflow-Inc… )。

需求留意的是,AddBackwardFuncPtr中的inputs/outputs是针对op而言,GraphFunctionNode结构函数中同名变量的是针对FunctionNode而言,二者的含义和指向的目标是不一样的。

结构完成后,x和y的grad_fn_node_字段数据内容如下:

x.grad_fn_node_

name_: accumulate_grad
next_functions_:input_meta_data_:output_meta_data_: size=1,x.autograd_meta_,requires_grad=true,is_leaf=true
output_tensor_infos_: 对应x, relu前向op的input
backward_fn_: 空函数,AddAccumulateFunctionNode中界说的

y.grad_fn_node_

name_: relu_backward
next_functions_: size=1, x.grad_fn_node, 空操作, AddAccumulateFunctionNode中结构的GraphFunctionNode
input_meta_data_: x.autograd_meta_, requires_grad=true, is_leaf=true
output_meta_data_: size=1, y.autograd_meta_, requires_grad=false, is_leaf=false
output_tensor_infos_: 对应y, relu前向op的output
backward_fn_: AutogradInterpreter::Apply中界说的lambda函数

backward便是根据这些数据,从roots出发,完成反向图的遍历。

6

backward的进口

在《OneFlow源码阅览4:tensor类型系统与local tensor》(segmentfault.com/a/119000004… )中提到过,Tensor类在Python端经过一层包装,经过Python机制为Tensor类注册一些办法,backward便是包装的办法之一。

相关的源代码文件如下

  • python/oneflow/framework/tensor.py
  • python/oneflow/autograd/init.py
  • oneflow/python/oneflow/autograd/autograd.py
  • oneflow/api/python/autograd/autograd.cpp

C++的调用流程如下:

OneFlow源码解析:自动微分机制

这儿重复一下本文运用的示例代码:

import oneflow as flow
x = flow.tensor([-1.0, 2.0], requires_grad=True)
y = flow.relu(x)
y.backward(flow.Tensor([1, 1]))
print(x.grad)

上述示例代码履行时,Backward(github.com/Oneflow-Inc… )的首要参数的值如下:

  • outputs: y, relu输出的tensor
  • out_grads: [1, 1]

CheckAndInitOutGrads(github.com/Oneflow-Inc… )回来的是loss经过其时op、传到其时节点的梯度。其部分逻辑便是第3节评论的

  • 假如y是一个向量,backward必须传入一个与y的shape共同的向量(github.com/Oneflow-Inc… )。
  • 假如y是一个标量,backward不要参数,结构会主动结构一个全1的tensor(github.com/Oneflow-Inc… )。

7

autograd.grad

通常,我们都会经过tensor.backward或autograd.backward触发梯度核算和反向传达,但偶尔也会用到autograd.grad(oneflow.readthedocs.io/en/master/g… )这个接口。autograd.grad和autograd.backward很类似,不同之处首要在于:

  • autograd.backward以outputs(Tensor)作为起点,核算每一个叶子节点的梯度,并且梯度可累积,且保存于对应inputs(Tensor)的tensor.grad上。

  • 而autograd.grad 接口则是从指定的 outputs为起点,以指定的 inputs为结尾核算梯度,并按 inputs 参数的顺序回来一个由inputs相对应的grads构成的TensorTuple。且梯度是直接获得的,不在inputs的tensor.grad中累积。

因为autograd.grad就只履行后向核算图中的一部分,在OneFlow 静态图形式下(lazy mode)TaskGraph 核算入度时就需求做一次剪枝,把不需求核算的结点去掉(参阅 TaskGraph::ComputeDependenciesAndPruneNode(github.com/Oneflow-Inc…) 接口),一起记载每个 inputs 序号,在 FunctionNode::Apply (github.com/Oneflow-Inc… )履行后,把需求保存的 grad 及时捕获,终究回来给用户。

8

反向核算中GraphAutogradEngine的调用流程

反向图核算的流程分析能够结合3类信息

  • 流程代码
  • 上述x和y的grad_fn_node_的值
  • 类图以及类之间的联系

RunBackwardAndSaveGrads4LeafTensor(github.com/Oneflow-Inc… )函数的几个参数是:

  • outputs: relu的输出y
  • out_grads: 用户自己结构的ones [1, 1]

8.1 反向传递过来的梯度的累加

RunBackwardAndSaveGrads4LeafTensor(github.com/Oneflow-Inc… )函数中,PushPartialTensor(github.com/Oneflow-Inc… )的效果便是将loss传过来的梯度累加到autograd_meta_.current_grad_.acc_tensor_。第4节中提到,TensorArg.acc_tensor_存储的便是loss传过来的梯度的合计。这便是roots(即y)接收到的梯度,要么是结构主动创立的ones,要么是用户提供的梯度(通常也是ones)。

这行代码的逻辑能够用如下伪码表明

outputs[i].impl_.autograd_meta_.current_grad_.acc_tensor_ += out_grads[i]

8.2 反向图核算任务的结构与履行

FunctionNode仅仅记载了反向图的根底信息。RunBackwardAndSaveGrads4LeafTensor中会再结构一个GraphTask目标来表明一次反向核算任务。

  • GraphTask的结构函数(github.com/Oneflow-Inc… )首要是初始化反向图的roots_节点,并将图中各个节点的依靠计数dependencies_置为0。根据示例代码,roots_便是y(通常是loss)。
  • ComputeDependencies(github.com/Oneflow-Inc… )会对反向图进行深度优先遍历、核算图中各个节点的依靠计数。
  • GraphTask::Apply(github.com/Oneflow-Inc… )中完成了反向图的遍历逻辑(传入的save_grad_for_leaf参数是true)。当FunctionNode的依靠为0时,节点才会被放入履行行列(github.com/Oneflow-Inc… ),后续会对反向图履行按拓扑序遍历。FunctionNode::Apply履行时,它的依靠都履行结束了。GraphTack::Apply这个函数中,触及梯度核算逻辑首要包含两部分:
  1. 调用node->Apply履行单个节点的梯度核算(github.com/Oneflow-Inc… )
  2. 调用node->AccGrad4LeafTensor存储算好的梯度(github.com/Oneflow-Inc…)

8.3 节点的梯度核算

FunctionNode::Apply中(github.com/Oneflow-Inc… ),处理output_meta_data_的for循环(github.com/Oneflow-Inc… )的中心逻辑能够用如下伪码表明:

acc_tensor = output_meta_data_[i].current_grad_.acc_tensor_
if (acc_tensor != nullptr) {
  output_grads[i] = acc_tensor_
} else {
  output_grads[i] = zeros()
}

从中能够看出来,output_grads的效果便是复制上游传过来的梯度数据(指针),作为backward_fn_的参数。

后面能够看到,backward_fn(github.com/Oneflow-Inc… )的中心逻辑是:

// d(y)表明其时节点对y的梯度,比方relu对其输出y的梯度。
input_grads = d(y) * output_grads

input_grads便是其时节点传给下流节点的梯度,调用backward_fn时会对它进行赋值

处理input_meta_data的for循环的中心逻辑(github.com/Oneflow-Inc… )能够用如下伪码表明。本质便是将其时节点传给下流节点的梯度,累加到下流节点的current_grad上,从而完成梯度的传达。假如tensor输入给多个op,每个op的梯度会加起来。

input_meta_data_[i].current_grad_.acc_tensor_ += input_grads[i]

8.3.1 梯度核算的履行:backward_fn

以下只考虑前述示例的root节点的履行。也便是y对应的FunctionNode。关于y来说,backward_fn便是AutogradInterpreter::Apply中界说的lambda表达式(github.com/Oneflow-Inc… )。关于relu来说,履行进程如下:

OneFlow源码解析:自动微分机制

之前在5.1节现已承认,OpExprGradClosure::impl_便是ReLU(github.com/Oneflow-Inc… )。
如前所述,backward_fn的参数中,output_grads是上游传过来的梯度数据,backward_fn需求核算relu的梯度,二者的乘积赋值给in_grads。这些参数会一直传递到ReLU::Apply(github.com/Oneflow-Inc… )。

functional::ReluGrad(github.com/Oneflow-Inc… )的Functor姓名是ReluGrad。对应的Functor是ReluGradFunctor(github.com/Oneflow-Inc… )(命名空间是oneflow::one::functional::impl)。

ReluGradFunctor之后,是根据Primitive kernel完成的核算逻辑。 ReluGradFunctor中对应op姓名是”relu_grad”,这个relu_grad的注册被包在一个宏界说(github.com/Oneflow-Inc… )中,实践上会回来一个BinaryPrimitiveKernel,这是一种稍显特殊的根据Primitive的kernel,其详细为ep::primitive下的一种BroadcastElementwiseBinary工厂(github.com/Oneflow-Inc… ),其对应的cpu和cuda注册别离位于:

  • oneflow/core/ep/cpu/primitive/broadcast_elementwise_binary.cpp
  • oneflow/core/ep/cuda/primitive/broadcast_elementwise_binary.cu

终究完成位于binary_functor.h(github.com/Oneflow-Inc… ):

template<DeviceType device, typename Src, typename Dst>
struct BinaryFunctor<device, BinaryOp::kReluBackwardWithDyY, Src, Dst> {
  OF_DEVICE_FUNC BinaryFunctor(Scalar attr0, Scalar attr1) {}
  OF_DEVICE_FUNC Dst operator()(Src dy, Src y) const {
    return static_cast<Dst>((y <= static_cast<Src>(0.0)) ? static_cast<Src>(0.0) : dy);
  }
};

至此,完成了梯度核算的逻辑。

8.4 梯度的存储

FunctionNode::Apply履行结束后,GraphTask::Apply调用FunctionNode::AccGrad4LeafTensor(github.com/Oneflow-Inc… )为叶子节点复制梯度数据。
在上述比方中,因为y不是叶子节点,处理到y.grad_fn_node_时不会进行本质处理。关于x,会调用CopyOrAccGrad(github.com/Oneflow-Inc… ),这个函数逻辑的伪码形式如下

autograd_meta.acc_grad_ += autograd_meta.current_grad_

autograd_meta.acc_grad_便是Python端读到的x的梯度。

8.5 暂时梯度的开释机制

上述第5.点中,描绘了前向图构建进程中现已存放了对应的FunctionNode以及前向op所对应的反向backward_fn,实践求梯度、反向传达时,这一个个 backward_fn串联起来构成了反向核算图拓扑,关于其间的每个节点,backward_fn中都能够表明为output_grads、inputs/outputs(可选) -> inputs_grads的一个函数。

其间output_grads 便是链式法则中上游核算的累计梯度,其时节点backward_fn核算完成后,该节点的output_grads就不会再被运用到,从而变成了暂时梯度。之后会调用 FunctionNode->ReleaseOutTensorArgs()(github.com/Oneflow-Inc…) 来及时开释该暂时梯度。

参阅材料

  • oneflow master(github.com/Oneflow-Inc…)
  • OneFlow学习笔记:Autograd解析(mp.weixin.qq.com/s/6zm4xRpRk…)
  • OneFlow: AUTOGRAD(docs.oneflow.org/en/master/b…)
  • 主动微分的原理介绍(mp.weixin.qq.com/s/BwQxmNoSB…)
  • 主动求梯度(tangshusen.me/Dive-into-D…)
  • PyTorch 的 backward 为什么有一个 grad_variables 参数?(zhuanlan.zhihu.com/p/29923090)
  • PyTorch 101, Part 1: Understanding Graphs, Automatic Differentiation and Autograd(blog.paperspace.com/pytorch-101…)

欢迎下载体验 OneFlow v0.8.0 最新版别: github.com/Oneflow-Inc…