tensor torch 构造_PyTorch的Tensor(中)

本文深入探讨了PyTorch中Tensor的autograd机制,详细阐述了如何在前向传播过程中构建autograd信息,包括创建Variable、建立反向计算函数实例、构建Variable实例的grad_fn_成员等。通过示例代码展示了autograd在加法、乘法等操作中的工作原理,为理解反向传播打下基础。
摘要由CSDN通过智能技术生成

背景

在PyTorch的Tensor系列上一篇文章中:Gemfield:PyTorch的Tensor(上)​zhuanlan.zhihu.com865e21b7eada1ec07e5441ec94afecc8.png

Gemfield介绍了一个Tensor的创建过程,特别是在创建一个Tensor的时候,调用栈从Python到C++再回到Python的过程。与此同时,在内存中对应的是一个Variable实例的创建(严格来说,Variable实例的某个field也是Variable实例)。

在本文,Gemfield将介绍PyTorch的Tensor中autograd相关的部分。autograd是PyTorch之所以是神经网络框架的一个重要原因。autograd机制提供了对Tensor上所有操作自动求微分的功能。我们知道,对于一个Variable来说,它的唯一数据成员就是impl_,这个impl_成员是TensorImpl 类型,在初始化阶段impl_会被实例化为一个Variable::Impl的实例(TensorImpl的子类):

Variable --> impl_ = Variable::Impl实例

对于一个Variable的autograd来说,autograd的部分就体现在impl_的autograd_meta_成员上。在初始化阶段,autograd_meta_会被初始化为一个Variable::AutogradMeta的实例(AutogradMetaInterface的子类),或者会被初始化为一个Variable::DifferentiableViewMeta的实例(Variable::AutogradMeta的子类),然后通过Variable的 get_autograd_meta()来访问。实际上,autograd_meta_正是一个Variable是普通tensor还是带autograd功能的tensor的唯一标识:

#1 Variable是个Tensor,没有requires_gradVariable --> impl_ --> autograd_meta_ = None

#2Variable --> impl_ --> autograd_meta_ = Variable::AutogradMeta实例

#3Variable --> impl_ --> autograd_meta_ = Variable::DifferentiableViewMeta实例

而一个Variable::AutogradMeta实例有如下成员,这些成员正是PyTorch autograd系统的中坚:

# Variable::AutogradMeta 和 Variable::DifferentiableViewMeta

Variable grad_;

std::shared_ptr grad_fn_;

std::weak_ptr grad_accumulator_;

VariableVersion version_counter_;

std::vector<:shared_ptr>> hooks_;

bool requires_grad_;

bool is_view_;

uint32_t output_nr_;

# 仅 Variable::DifferentiableViewMeta

Variable base_;

uint32_t attr_version;1,grad_是另外一个Variable,存储当前Variable实例的梯度;

2,grad_fn是个Function的实例,非leaf variables才有。通过Variable的grad_fn()来访问,实际上,PyTorch中就是通过是否grad_fn_ == nullptr来判断一个Variable是否是leaf variable的;

3,grad_accumulator_是个Function的实例,只有leaf variables才有。通过Variable的grad_accumulator()来访问;

4,version_counter_里有个version number;

5,hooks_可以是一组;

6,requires_grad_ 是个flag,表明此Variable实例是否需要grad;

7,is_view_是个flag,表明此Variable实例是否是个view(没有实际存储,基于base的variable);

8,output_nr_是个数字;

9,base_是view的base variable;

10,attr_version是个数字。

我们通过下面这一小段代码来演示下这个能力:

gemfield = torch.ones(2, 2, requires_grad=True)

syszux = gemfield + 2

civilnet = syszux * syszux * 3

gemfieldout = civilnet.mean()

gemfieldout.backward()

特别的,对于在python会话中的每一步操作,gemfield都将映射到内存上类实例中的成员/结构体的变化。

Tensor创建:gemfield = torch.ones(2, 2, requires_grad=True)

我们使用gemfield = torch.ones(2, 2, requires_grad=True) 语句来创建了一个tensor。在https://zhuanlan.zhihu.com/p/54896021一文中已经介绍过了,这个调用会在内存中产生如下一个Variable实例:

#gemfield

Variable实例 --> Variable::Imple实例 --> tensor data --> TensorImpl实例 --> Storage实例 = [[1., 1.],[1., 1.]]--> autograd_meta --> grad_ (又一个Variable实例) = None--> grad_fn_ (Function实例)= None--> grad_accumulator_ (Function实例)= None--> version_counter_ = 0--> hooks_ len = 0--> requires_grad_ = True--> is_view_ = false--> output_nr_ = 0--> base_ = Not exist

这个gemfield变量就是图中的leaf,为什么呢?因为这是用户直接创建的(不是经过计算得到的),位于图中最“底端/外侧”的位置,没有子节点。这个时候,Tensor gemfield的grad是None,grad_fn是None。output_nr_为0,表明这个Variable是function的第1个输出。

Tensor的简单加法:syszux = gemfield + 2

我们使用 syszux = gemfield + 2 来得到一个新的Tensor,名字为syszux。这个加法嘛,在初始化的时候已经和C++中的THPVariable_add函数绑定上,并注册到Python的torch._C._TensorBase符号上了:

PyMethodDef variable_methods[] = {

{"__add__", (PyCFunction)THPVariable_add, METH_VARARGS | METH_KEYWORDS, NULL},

......

而THPVariable_add的定义如下:

static PyObject * THPVariable_add(PyObject* self_, PyObject* args, PyObject* kwargs)

{

......

return wrap(dispatch_add(self, r.tensor(0), r.scalar(1)));

}

1,scalar to tensor

在这个函数中,首先要将syszux = gemfield + 2 中的2从标量转换为tensor,这个转换逻辑如下:

auto tensor = scalar_to_tensor(scalar);

tensor.unsafeGetTensorImpl()->set_wrapped_number(true);

return autograd::make_variable(tensor);

现在scalar 2已经变成了内存中一个Variable的实例,在add真正执行之前,在内存中已经有2个Variable实例了,分别是gemfield和2:

#gemfield

Variable实例 --> Variable::Imple实例 --> tensor data --> TensorImpl实例 --> Storage实例 = [[1., 1.],[1., 1.]]

--> autograd_meta --> grad_ (又一个Variable实例) = None

--> grad_fn_ (Function实例)= None

--> grad_accumulator_ (Function实例)= None

--> version_counter_ = 0

--> hooks_ len = 0

--> requires_grad_ = True

--> is_view_ = false

--> output_nr_ = 0

--> base_ = Not exist

#scalar 2

Variable实例 --> Variable::Imple实例 --> tensor data --> TensorImpl实例 --> Storage实例 = [2.]

--> autograd_meta --> grad_ (又一个Variable实例) = None

--> grad_fn_ (Function实例)= None

--> grad_accumulator_ (Function实例)= None

--> version_counter_ = 0

--> hooks_ len = 0

--> requires_grad_ = False

--> is_view_ = false

--> output_nr_ = 0

--> base_ = Not exist

不过要注意了,由于gemfield是一个leaf variable,因此在后文的加法运算中,gemfield会被触发Variable::grad_accumulator()调用,这会初始化gemfield的grad_accumulator_成员,因此在那之后,gemfield在内存中的样子就会变为:

#gemfield

Variable实例 --> Variable::Imple实例 --> tensor data --> TensorImpl实例 --> Storage实例 = [[1., 1.],[1., 1.]]

--> autograd_meta --> grad_ (又一个Variable实例) = None

--> grad_fn_ (Function实例)= None

--> grad_accumulator_ (Function实例)= AccumulateGrad实例0x55ca7f304500

--> ......

2,dispatch to add

既然是分发,肯定是为了分发到对应种类的Type上。分发的核心逻辑是:

dispatch_type().add(*this, other, alpha)

dispatch_type是at::Tensor类上的一个方法,根据其impl(TensorImpl类)的id、dtype等信息,推导出Type类型(VariableType)。在这个例子上,根据输入参数的类型,最终分发到torch/csrc/autograd/generated/VariableType.cpp(参考autograd的代码生成)中的VariableType的add方法上:

Tensor VariableType::add(const Tensor & self, const Tensor & other, Scalar alpha) const {

std::shared_ptr grad_fn;

grad_fn = std::shared_ptr(new AddBackward0(), deleteFunction);

grad_fn->set_next_edges(collect_next_edges( self, other ));

grad_fn->alpha = alpha;

auto tmp = ([&]() {

at::AutoNonVariableTypeMode non_var_type_mode(true);

return baseType->add(self_, other_, alpha);

})();

auto result = as_variable(tmp);

set_history(flatten_tensor_args( result ), grad_fn);

return result;

}

匿名函数中的baseType->add最终又调用了如下的调用栈,只能说一个简单的加法在graph设计中也会变得比较复杂:

VariableType::add

|

V

baseType->add(self_, other_, alpha)

|

V

#ATen/TypeDefault.cpp

TypeDefault::add

|

V

#aten/src/ATen/native/BinaryOps.cpp

add(const Tensor& self, const Tensor& other, Scalar alpha)

|

V

#此处依赖初始化阶段的REGISTER_DISPATCH的工作

add_stub

|

V

#aten/src/ATen/native/cpu/BinaryOpsKernel.cpp.AVX2.cpp

add_kernel

|

V

#aten/src/ATen/native/cpu/BinaryOpsKernel.cpp.AVX2.cpp

binary_kernel_vec

|

V

binary_loop

3,构建autograd

3.1 构建grad_fn (AddBackward0)

AddBackward0是加法运算的反向传播算法,构建这个grad_fn实例是通过如下一行代码完成的:

std::shared_ptr grad_fn = std::shared_ptr(new AddBackward0(), deleteFunction);

可以看到,对于加法,对应的grad_fn是AddBackward0。在聊AddBackward0之前,你有必要先了解下PyTorch中的Function继承体系(可以参考 https://zhuanlan.zhihu.com/p/61765561 ),还记得吧:

class AddBackward0 : public TraceableFunction

class TraceableFunction : public Function

gemfield + 2 加法运算完成后,就会创建出来这个AddBackward0(一个Function)实例,并且使用collect_next_edges()搜集gemfield和2的grad_fn或者grad_accumulator。gemfield是leaf variable,因此搜集的就是grad_accumulator(2啥都没有),类型是AccumulateGrad(1个Function的实例),然后再向这个AddBackward0实例上注册:

grad_fn->set_next_edges(collect_next_edges( self, other ));

next edges就是self和other的gradient_edge():gradient_edge()函数返回的是Edge实例(通过Variable的grad_fn_构建)。当set_next_edges调用完成后,一个Function的next_edges_成员(类型为std::vector)就会被初始化。

1,如果一个Variable是内部创建的(通过运算得到,比如syszux变量),那么grad_fn_就是这个Variable的gradient function;

2,如果一个Variable是用户创建的(比如gemfield变量),则grad_fn_就是这个Variable的gradient accumulator,也就是一个AccumulateGrad类(Function子类)的实例。

但不管怎样,Variable的grad_fn_成员在这里终归是要构建成一个Edge实例并以此作为gradient_edge()函数的返回:

Edge gradient_edge() const {

if (const auto& gradient = grad_fn()) {

return Edge(gradient, output_nr());

} else {

return Edge(grad_accumulator(), 0);

}

}

对于leaf Variable来说,grad_fn是null,在这种情况下Variable将使用gradient accumulator, 用来累加输出给这个Variable的梯度。注意只有当Variable的requires_grad = True时才有gradient accumulators。经过构建后的AddBackward0的内存布局如下所示:

#grad_fn AddBackward0, requires_grad == True

Function实例 --> sequence_nr_ (uint64_t) = 0

--> next_edges_ (edge_list) --> std::vector = [(AccumulateGrad实例0x55ca7f304500, 0),(0, 0)]

--> input_metadata_ --> [(type, shape, device)...] = None

--> alpha (Scalar) = 1

--> pre_hooks_ = None

--> post_hooks_ = None

--> anomaly_metadata_ = None

--> apply() --> 使用 AddBackward0 的apply

#grad_fn,requires_grad == False

Function实例 --> None

3.2 构建grad_accumulator_ (AccumulateGrad)

前文已经提到过,在grad_fn调用collect_next_edges去搜集输入参数(Variable实例)的edges时,对于leaf gemfield来说,会触发Variable::grad_accumulator()调用,在一个Variable第一次调用这个API的时候,会去初始化它的grad_accumulator_成员:

result = std::make_shared(Variable(std::move(intrusive_from_this)));

autograd_meta->grad_accumulator_ = result;

这会new一个AccumulateGrad对象,使用UINT64_MAX(也就是18446744073709551615)来初始化Function的sequence_nr_成员。构建完成后,grad_accumulator_(0x55ca7f304500)在内存中看起来是这个样子的:

# 类AccumulateGrad,继承自Function,是一个Function实例

Function实例 --> sequence_nr_ (uint64_t) = UINT64_MAX

--> next_edges_ (edge_list) --> None

--> input_metadata_ --> [(type, shape, device)...] = [(CPUFloatType, [2, 2], cpu)]

--> pre_hooks_ = None

--> post_hooks_ = None

--> anomaly_metadata_ = None

--> apply() --> 使用 AccumulateGrad 的apply

--> variable = gemfield

初始化完毕后,此时gemfield这个Variable的grad_accumulator_已经被赋值为AccumulateGrad实例(0x55ca7f304500)。

4 构建Variable(syszux)

在加法表达式完成之后,内存中也就产生了syszux。刚产生的syszux实例在内存中看起来是这样的:

//syszux

Variable实例 --> Variable::Imple实例 --> tensor data --> TensorImpl实例 --> Storage实例 = [[3., 3.],[3., 3.]]

--> autograd_meta --> grad_ (又一个Variable实例) = None

--> grad_fn_ (Function实例)= None

--> grad_accumulator_ (Function实例)= None

--> version_counter_ = 0

--> hooks_ len = 0

--> requires_grad_ = False

--> is_view_ = false

--> output_nr_ = 0

--> base_ = Not exist

之后会使用set_history(flatten_tensor_args( result ), grad_fn)来设置syszux的gradient_edge,这个函数做了2件事情:

1,AddBackward0实例中的input_metadata_ 追加了Variable syszux的(type, shape, device),追加完成后,syszux信息在input_metadata_中的index就是下文中会用到的output_nr_,此处为0(第一个元素嘛);这样AddBackward0实例在内存中看起来是这样:

//grad_fn->add_input_metadata(variable);grad_fn_ --> input_metadata_ += (variable.type, variable.shape, variable.device) = []

#AddBackward0实例, requires_grad == TrueFunction实例 --> sequence_nr_ (uint64_t) = 0

--> next_edges_ (edge_list) --> std::vector = [(AccumulateGrad实例, 0),(0, 0)]

--> input_metadata_ --> [(type, shape, device)...] = [(CPUFloatType, [2, 2],cpu])]

--> alpha (Scalar) = 1

--> pre_hooks_ = None

--> post_hooks_ = None

--> anomaly_metadata_ = None

--> apply() --> 使用 AddBackward0 的apply

2,syszux的autograd_meta中的grad_fn_ 被赋值成了上述AddBackward0实例,而autograd_meta中的output_nr_被赋值成了上文中的“当前Variable信息在input_metadata_中的index”。这样syszux实例在内存中看起来就是这样:

//as_variable_ref(variable).set_gradient_edge({grad_fn, output_nr});Variable实例 --> Variable::Imple实例 --> tensor data --> TensorImpl实例 --> Storage实例 = [[3., 3.],[3., 3.]]

--> autograd_meta --> grad_ (又一个Variable实例) = None

--> grad_fn_ (Function实例)= AddBackward0实例0x55ca7f872e90(参考上面)

--> grad_accumulator_ (Function实例)= None

--> version_counter_ = 0

--> hooks_ len = 0

--> requires_grad_ = False

--> is_view_ = false

--> output_nr_ = 0

--> base_ = Not exist

完成这一步后,如果你在python会话中打印syszux的grad_fn属性,就会调用THPVariable_get_grad_fn函数,接着调用Variable的grad_fn()函数拿到grad_fn_,然后使用 functionToPyObject()函数从cpp_function_types表中查找对应的Python表示:

>>> syszux.grad_fn

civilnet = syszux * syszux * 3

现在进入civilnet = syszux * syszux * 3这个表达式的解析了,这个表达式执行期间,就会产生两次python object的__mul__(乘法)调用,并且会new一个新的Variable civilnet出来。

1,Python到C++

在初始化阶段,Variable的python绑定已经完成了下面的初始化:

PyMethodDef variable_methods[] = {

{"__add__", (PyCFunction)THPVariable_add, METH_VARARGS | METH_KEYWORDS, NULL},

{"__mul__", (PyCFunction)THPVariable_mul, METH_VARARGS | METH_KEYWORDS, NULL},

......

}

因此这个表达式会导致对C++函数THPVariable_mul的调用,并且是2次:两次乘法。

2,dispatch to mul

和加法类似,这个乘法的调用栈如下所示(其中的一次):

#torch/csrc/autograd/generated/python_variable_methods.cpp

THPVariable_mul

|

V

#torch/csrc/autograd/generated/python_variable_methods_dispatch.h

dispatch_mul

|

V

#aten/src/ATen/core/TensorMethods.h

Tensor::mul

|

V

#需要dispatch type 了

#torch/csrc/autograd/generated/VariableType_4.cpp

Tensor VariableType::mul(const Tensor & self, const Tensor & other)

|

V

#运算符太简单了,扔回到base的default实现

#build/aten/src/ATen/TypeDefault.cpp

Tensor TypeDefault::mul(const Tensor & self, const Tensor & other)

|

V

#aten/src/ATen/native/BinaryOps.cpp

Tensor mul(const Tensor& self, const Tensor& other)

|

V

#aten/src/ATen/native/TensorIterator.cpp

TensorIterator::binary_op

|

V

#此处依赖初始化阶段的REGISTER_DISPATCH的工作

#build/aten/src/ATen/native/cpu/BinaryOpsKernel.cpp.AVX2.cpp(如果使用的是CPU版的pytorch,并且cpu支持AVX2)

void mul_kernel(TensorIterator& iter)

|

V

#aten/src/ATen/native/cpu/Loops.h

void binary_kernel_vec(TensorIterator& iter, func_t op, vec_func_t vop)

如果使用的是CPU版的PyTorch,那么这里的乘法运算最终分发到cpu的native实现上了。

3,构建Autograd信息

当乘法运算分发到VariableType::mul的时候,PyTorch将会在这个函数中构建autograd的信息。

Tensor VariableType::mul(const Tensor & self, const Tensor & other) const {

std::shared_ptr grad_fn;

grad_fn = std::shared_ptr(new MulBackward0(), deleteFunction);

grad_fn->set_next_edges(collect_next_edges( self, other ));

if (grad_fn->should_compute_output(1)) {

grad_fn->self_ = SavedVariable(self, false);

}

if (grad_fn->should_compute_output(0)) {

grad_fn->other_ = SavedVariable(other, false);

}

auto tmp = ([&]() {

at::AutoNonVariableTypeMode non_var_type_mode(true);

return baseType->mul(self_, other_);

})();

auto result = as_variable(tmp);

set_history(flatten_tensor_args( result ), grad_fn);

return result;

}

3.1 构建grad_fn (MulBackward0)

经过表达式 grad_fn = std::shared_ptr(new MulBackward0(), deleteFunction)后,一个Function的实例就产生了,类型为MulBackward0(0x55ca7ebba2a0)。

struct MulBackward0 : public TraceableFunction {

variable_list apply(variable_list&& grads) override;

SavedVariable self_;

SavedVariable other_;

};

和AddBackward0不同的是,MulBackward0还有两个SavedVariable成员。比如使用syszux初始化SavedVariable self_的时候,进行了以下的拷贝:

#从syszux 拷贝到 SavedVariable self_

variable.output_nr() --> output_nr_variable.requires_grad() --> requires_grad_variable.data() --> data_variable.grad_fn() --> grad_fn_variable.version_counter() --> version_counter_version_counter_.current_version() --> saved_version_

在内存中,这个MulBackward0实例的布局如下所示:

#grad_fn MulBackward0, requires_grad == True

Function实例 --> sequence_nr_ (uint64_t) = 1 (每个线程内自增)

--> next_edges_ (edge_list) --> std::vector = None

--> input_metadata_ --> [(type, shape, device)...] = None

--> self_ (SavedVariable) = syszux的浅拷贝

--> other_ (SavedVariable) = syszux的另一个浅拷贝

--> pre_hooks_ = None

--> post_hooks_ = None

--> anomaly_metadata_ = None

--> apply() --> 使用 MulBackward0 的apply

3.2,初始化MulBackward0的next_edges_

前文已经提到过,在grad_fn调用collect_next_edges去搜集输入参数(Variable实例,此处为syszux)的edges时,对于“非leaf”的syszux来说,会触发Variable::grad_fn()调用,这会得到syszux的grad_fn,也就是AddBackward0实例。使用两个syszux的grad_fn组成的edges初始化完MulBackward0实例后,MulBackward0在内存中看起来是这个样子:

#grad_fn MulBackward0, requires_grad == True,0x55ca7ebba2a0

Function实例 --> sequence_nr_ (uint64_t) = 1(每个线程内自增)

--> next_edges_ (edge_list) = [(AddBackward0实例0x55ca7f872e90,0),(AddBackward0实例0x55ca7f872e90,0)]

--> input_metadata_ --> [(type, shape, device)...] = None

--> pre_hooks_ = None

--> post_hooks_ = None

--> anomaly_metadata_ = None

--> apply() --> 使用 AccumulateGrad 的apply

初始化完毕后,此时MulBackward0这个Function(0x55ca7ebba2a0)的next_edges_已经被赋值为syszux中的grad_fn组成的edges。

4 构建Variable(tmp)

在第一次乘法表达式完成之后,内存中也就产生了临时Variable tmp。刚产生的tmp实例在内存中看起来是这样的:

//tmp

Variable实例 --> Variable::Imple实例 --> tensor data --> TensorImpl实例 --> Storage实例 = [[9., 9.],[9., 9.]]

--> autograd_meta --> grad_ (又一个Variable实例) = None

--> grad_fn_ (Function实例)= None

--> grad_accumulator_ (Function实例)= None

--> version_counter_ = 0

--> hooks_ len = 0

--> requires_grad_ = False

--> is_view_ = false

--> output_nr_ = 0

--> base_ = Not exist

之后会使用set_history(flatten_tensor_args( result ), grad_fn)来设置tmp的gradient_edge,这个函数做了2件事情:

1,MulBackward0实例中的input_metadata_ 追加了Variable syszux的(type, shape, device),追加完成后,syszux信息在input_metadata_中的index就是下文中会用到的output_nr_,此处为0(第一个元素嘛);这样MulBackward0实例在内存中看起来是这样:

//grad_fn->add_input_metadata(variable);

grad_fn_ --> input_metadata_ += (variable.type, variable.shape, variable.device) = []

#MulBackward0实例, requires_grad == True

Function实例 --> sequence_nr_ (uint64_t) = 1

--> next_edges_ (edge_list) = [(AddBackward0实例0x55ca7f872e90,0),(AddBackward0实例0x55ca7f872e90,0)]

--> input_metadata_ --> [(type, shape, device)...] = [(CPUFloatType, [2, 2],cpu])]

--> alpha (Scalar) = 1

--> pre_hooks_ = None

--> post_hooks_ = None

--> anomaly_metadata_ = None

--> apply() --> 使用 MulBackward0 的apply

2,Variable tmp的autograd_meta中的grad_fn_ 被赋值成了上述MulBackward0实例,而autograd_meta中的output_nr_被赋值成了上文中的“当前Variable信息在input_metadata_中的index”。这样tmp实例在内存中看起来就是这样:

//as_variable_ref(variable).set_gradient_edge({grad_fn, output_nr});

Variable实例 --> Variable::Imple实例 --> tensor data --> TensorImpl实例 --> Storage实例 = [[9., 9.],[9., 9.]]

--> autograd_meta --> grad_ (又一个Variable实例) = None

--> grad_fn_ (Function实例)= MulBackward0实例0x55ca7ebba2a0(参考上面)

--> grad_accumulator_ (Function实例)= None

--> version_counter_ = 0

--> hooks_ len = 0

--> requires_grad_ = False

--> is_view_ = false

--> output_nr_ = 0

--> base_ = Not exist

5,第二次乘法

syszux * syszux后得到的tmp还要继续进行tmp * 3的运算,经过前面的一次加法(gemfield + 2)和一次乘法(syszux * syszux),我想我们目前已经能总结出规律了。每一次这样的运算,会经历以下的步骤:1,加法/乘法的调用栈,最终派发到某种device的实现上,如果运算输入是个scalar,进行scalar到Variable的构建;

2,派发到VariableType上时,会顺便进行autograd信息的构建;

2.1,构建一个加法/乘法的反向计算函数实例(比如AddBackward0,MulBackward0);

2.2,初始化反向计算函数实例的next_edges_和其它相关成员,next_edges_成员的值来自前向时候的输入参数,如果输入Variable是leaf的话,则next_edges_来自输入Variable的grad_accumulator_;如果是非leaf的话,则来自Variable的grad_fn_;

2.3,使用步骤3中的Variable实例来初始化反向计算函数实例的input_metadata_,

3,运算后得到新的Variable,使用Variable::Impl进行构建,使用步骤2中的反向计算函数实例初始化该Variable实例的grad_fn_成员。

对于civilnet = tmp * 3的运算(civilnet = syszux * syszux * 3的第二步),上述步骤就是:

1,THPVariable_mul的分发,不再赘述;其间要使用scalar 3构建一个Variable;

2,在调用栈到达VariableType::mul的时候,构建又一个MulBackward0实例(0x55ca7fada2f0),并初始化其next_edges_成员:

#grad_fn MulBackward0, requires_grad == True,0x55ca7fada2f0

Function实例 --> sequence_nr_ (uint64_t) = 2 (每个线程内自增)

--> next_edges_ (edge_list) = [(MulBackward0实例0x55ca7ebba2a0,0),(0,0)]

--> input_metadata_ --> [(type, shape, device)...] = [(CPUFloatType, [2, 2],cpu])]

--> self_ (SavedVariable) = tmp的浅拷贝

--> other_ (SavedVariable) = 3的浅拷贝

--> pre_hooks_ = None

--> post_hooks_ = None

--> anomaly_metadata_ = None

--> apply() --> 使用 MulBackward0 的apply

注意sequence_nr_又自增了1。

3,构建Variable civilnet:

//as_variable_ref(variable).set_gradient_edge({grad_fn, output_nr});

Variable实例 --> Variable::Imple实例 --> tensor data --> TensorImpl实例 --> Storage实例 = [[27., 27.],[27., 27.]]

--> autograd_meta --> grad_ (又一个Variable实例) = None

--> grad_fn_ (Function实例)= MulBackward0实例0x55ca7fada2f0(参考上面)

--> grad_accumulator_ (Function实例)= None

--> version_counter_ = 0

--> hooks_ len = 0

--> requires_grad_ = False

--> is_view_ = false

--> output_nr_ = 0

--> base_ = Not exist

gemfieldout = civilnet.mean()

步骤其实类似了:

1,python调用到C++的THPVariable_mean的调用:

static PyObject * THPVariable_mean(PyObject* self_, PyObject* args, PyObject* kwargs)

{

......

return wrap(dispatch_mean(self));

}

调用栈如下:

#torch/csrc/autograd/generated/python_variable_methods.cpp

static PyObject * THPVariable_mean(PyObject* self_, PyObject* args, PyObject* kwargs)|V#aten/src/ATen/core/TensorMethods.h

Tensor Tensor::mean()|V#torch/csrc/autograd/generated/VariableType_2.cpp

Tensor VariableType::mean(const Tensor & self)|V#build/aten/src/ATen/TypeDefault.cpp

Tensor TypeDefault::mean(const Tensor & self)|V#aten/src/ATen/native/ReduceOps.cpp

Tensor mean(const Tensor &self)|V#aten/src/ATen/native/ReduceOps.cpp

static inline Tensor mean(const Tensor &self, optional dtype)|V#aten/src/ATen/native/ReduceOps.cpp

static inline Tensor mean(const Tensor &self, IntArrayRef dim, bool keepdim, optional dtype)|V#aten/src/ATen/native/ReduceOps.cpp

Tensor &mean_out(Tensor &result, const Tensor &self, IntArrayRef dim,bool keepdim, optional opt_dtype)|Vat::sum_out(result, self, dim, keepdim, dtype).div_(dim_prod);|V#build/aten/src/ATen/TypeDefault.cpp

Tensor & TypeDefault::sum_out(Tensor & out, const Tensor & self, IntArrayRef dim, bool keepdim, ScalarType dtype)|V#aten/src/ATen/native/ReduceOps.cpp

Tensor& sum_out(Tensor& result, const Tensor& self, IntArrayRef dim, bool keepdim, ScalarType dtype)|V#aten/src/ATen/native/ReduceOps.cpp

Tensor& sum_out(Tensor& result, const Tensor& self, IntArrayRef dim,bool keepdim, optional opt_dtype)|Vsum_stub ,依赖初始化阶段的REGISTER_DISPATCH的工作

2,构建autograd

在调用栈到达 VariableType::mean时,开始顺便构建autograd的信息。主要是构建一个op的反向计算函数实例:MeanBackward0实例(0x55ca7eb358b0)。MeanBackward0类型定义如下:

struct MeanBackward0 : public TraceableFunction {

variable_list apply(variable_list&& grads) override;

std::vector self_sizes;

int64_t self_numel = 0;

};

多了self_sizes和self_numel成员。构建完成后,MeanBackward0实例在内存中看起来如下所示:

#grad_fn MeanBackward0, requires_grad == True,0x55ca7eb358b0

Function实例 --> sequence_nr_ (uint64_t) = 3 (每个线程内自增)

--> next_edges_ (edge_list) = [(MulBackward0实例0x55ca7fada2f0,0)]

--> input_metadata_ --> [(type, shape, device)...] = [(CPUFloatType|[]|cpu])]

--> self_sizes (std::vector) = (2, 2)

--> self_numel = 4

--> pre_hooks_ = None

--> post_hooks_ = None

--> anomaly_metadata_ = None

--> apply() --> 使用 MulBackward0 的apply

注意sequence_nr_的值又自增了1,另外就是input_metadata_中的shape为空。

3,构建Variable gemfieldout

gemfieldout在内存中的布局如下所示:

//as_variable_ref(variable).set_gradient_edge({grad_fn, output_nr});

Variable实例 --> Variable::Imple实例 --> tensor data --> TensorImpl实例 --> Storage实例 = (27,)

--> autograd_meta --> grad_ (又一个Variable实例) = None

--> grad_fn_ (Function实例)= MeanBackward0实例0x55ca7eb358b0(参考上面)

--> grad_accumulator_ (Function实例)= None

--> version_counter_ = 0

--> hooks_ len = 0

--> requires_grad_ = False

--> is_view_ = false

--> output_nr_ = 0

--> base_ = Not exist

总结

本文《PyTorch的Tensor(中)》主要介绍了Tensor的autograd部分,具体来说,就是在前向运算中,autograd的信息是如何构建和联系在一起的:1,op的调用栈,最终派发到某种device的实现上,如果运算输入是个scalar,进行scalar到Variable的构建;

2,派发到VariableType上时,会顺便进行autograd信息的构建;

2.1,构建一个op的反向计算函数实例(比如AddBackward0,MulBackward0);

2.2,初始化反向计算函数实例的next_edges_和其它相关成员,next_edges_成员的值来自前向时候的输入参数,如果输入Variable是leaf的话,则next_edges_来自输入Variable的grad_accumulator_;如果是非leaf的话,则来自Variable的grad_fn_;

2.3,使用步骤3中的Variable实例来初始化反向计算函数实例的input_metadata_,

3,运算后得到新的Variable,使用Variable::Impl进行构建,使用步骤2中的反向计算函数实例初始化该Variable实例的grad_fn_成员。

而在下一篇文章《PyTorch的Tensor(下)》中,将主要介绍在backward的时候,Tensor中的autograd部分是怎么运行的。不像这篇文章中的Variable实例,那个时候其grad_成员将不再是None了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值