Pytorch底层源码解读(二)libtorch源码浅析

前言

上文我们通过针对性的阅读pytorch源码的框架结构,同时以__init__.py文件为线索探索了pytorch中多个主要类型的实现和功能。在上文中我们曾提到,pytorch框架是一个以python为前端,C++为后端的框架。如果去除掉pytorch中的python前端,那我们就可以得到一个C++的AI框架——libtorch。没有特殊说明时,本文所看源码均是取自libtorch而非pytorch,其中libtorch的源码版本是libtorch-win-shared-with-deps-2.1.0+cpu

一个引子

我们来看看如下代码:

torch::Tensor x = torch::tensor({1.0});

是否觉得很眼熟,其实这就是当我们在python中调用torch.tensor()这个函数时,其在C++后端实际上调用的函数,从这一个例子中我们可以知道,要想真正理解pytorch的底层原理,就需要深入的理解libtorch的源码。

目录结构

和上文我们解析pytorch源码一样,现在我们来看一下libtorch的源码结构,值得注意的是,和pytorch一样,libtorch也分为cpu版本和gpu版本两个版本,因此其目录结构稍有不同。我们主要看cpu版本的include目录以及lib目录,lib目录下存放的是静态链接文件,而include下存放的是头文件,我们在使用libtorch的时候,实际上是引入头文件,最后在编译过程中,由编译器根据头文件去找到路径下的动态链接文件,并在链接期间将其链接进来。

图片1.png

include目录下则是这个样子,可以很容易地发现,这和pytorch的源码结构是几乎一样的。
图片2.png

torch.h解读

torch.h源码很短,只有如下几行,从中我们可以看到它其实是引入了all.h这个头文件,另外还引入了一个extension.h文件。这里这个extension.h文件其实是引入了python.h,当然,由于这里讨论的是无python前端的libtorch版本,因此不需要注意这个。

#pragma once

#include <torch/all.h>

#ifdef TORCH_API_INCLUDE_EXTENSION_H
#include <torch/extension.h>

#endif // defined(TORCH_API_INCLUDE_EXTENSION_H)

在进入all.h后,可以看到如下代码,很明显,这个头文件就是将所有模块下的头文件引入,现阶段我们要关注的只有linalg.h这一个文件。

#pragma once

#if !defined(_MSC_VER) && __cplusplus < 201703L
#error C++17 or later compatible compiler is required to use PyTorch.
#endif

#include <torch/autograd.h>
#include <torch/cuda.h>
#include <torch/data.h>
#include <torch/enum.h>
#include <torch/fft.h>
#include <torch/jit.h>
#include <torch/linalg.h>
#include <torch/mps.h>
#include <torch/nested.h>
#include <torch/nn.h>
#include <torch/optim.h>
#include <torch/serialize.h>
#include <torch/sparse.h>
#include <torch/special.h>
#include <torch/types.h>
#include <torch/utils.h>
#include <torch/version.h>

torch::Tensor浅析

进入linalg.h后我们可以看到如下代码,这个文件提供了相当多的内联函数,主要是一些矩阵运算相关的函数。当然,这个文件最重要的部分其实是将ATen.h引入了torch.h中,记得上文我们说过ATen目录是与Tensor类实现相关的库,也就是说,在这个文件中我们可以找到torch::Tensor的声明。

#pragma once

#include <ATen/ATen.h>

namespace torch {
namespace linalg {

#ifndef DOXYGEN_SHOULD_SKIP_THIS
namespace detail {

inline Tensor cholesky(const Tensor& self) {
  return torch::linalg_cholesky(self);
}
...

进入到ATen.h文件后,我们看到如下代码,其中出现了我们在上一节提到过的两个核心概念,TensorStorage,它们被包含在ATen/Tensor.h以及c10/core/Storage.h中。

#pragma once

#if !defined(_MSC_VER) && __cplusplus < 201703L
#error C++17 or later compatible compiler is required to use ATen.
#endif

#include <ATen/Context.h>
#include <ATen/Device.h>
#include <ATen/DeviceGuard.h>
#include <ATen/DimVector.h>
#include <ATen/Dispatch.h>
#include <ATen/Formatting.h>
#include <ATen/Functions.h>
#include <ATen/NamedTensor.h>
#include <ATen/ScalarOps.h>
#include <ATen/Tensor.h>
#include <ATen/TensorGeometry.h>
#include <ATen/TensorIndexing.h>
#include <ATen/TensorOperators.h>
#include <ATen/Version.h>
#include <ATen/core/ATenGeneral.h>
#include <ATen/core/Generator.h>
#include <ATen/core/Reduction.h>
#include <ATen/core/Scalar.h>
#include <ATen/core/UnsafeFromTH.h>
#include <ATen/core/ivalue.h>
#include <ATen/core/jit_type.h>
#include <c10/core/Allocator.h>
#include <c10/core/InferenceMode.h>
#include <c10/core/Layout.h>
#include <c10/core/Storage.h>
#include <c10/core/TensorOptions.h>
#include <c10/util/Exception.h>

// TODO: try to remove this
// There is some back story, see https://github.com/pytorch/pytorch/issues/48684
#include <ATen/NativeFunctions.h>

进入ATen/Tensor.h中,可以看到该文件其实引入了ATen/core/Tensor.h,因此我们进入该文件,进入后我们可以看到如下代码:

#pragma once

#include <ATen/core/TensorBody.h>
#include <c10/util/Exception.h>

namespace at {
class TORCH_API OptionalTensorRef {
 public:
  OptionalTensorRef() = default;

  ~OptionalTensorRef() {
    ref_.unsafeReleaseTensorImpl();
  }

  OptionalTensorRef(const TensorBase& src)
      : ref_(Tensor::unsafe_borrow_t{}, src) {
    TORCH_INTERNAL_ASSERT_DEBUG_ONLY(src.defined());
  }

  OptionalTensorRef(const OptionalTensorRef& rhs)
      : ref_(Tensor::unsafe_borrow_t{}, rhs.ref_) {}

  OptionalTensorRef& operator=(OptionalTensorRef rhs) {
    std::swap(ref_, rhs.ref_);
    return *this;
  }

  bool has_value() const {
    return ref_.defined();
  }

  const Tensor& getTensorRef() const & {
    return ref_;
  }

  const Tensor& operator*() const & {
    return ref_;
  }

  const Tensor* operator->() const & {
    return &ref_;
  }

  operator bool() const {
    return ref_.defined();
  }

 private:
  Tensor ref_;
};

// Use to convert a TensorBase (that may be undefined) to an at::Tensor
// without bumping refcount.
class TORCH_API TensorRef {
 public:
  ~TensorRef() {
    ref_.unsafeReleaseTensorImpl();
  }

  TensorRef(const TensorBase& src)
      : ref_(Tensor::unsafe_borrow_t{}, src) {}

  const Tensor& operator*() const & {
    return ref_;
  }
 private:
  Tensor ref_;
};

template <typename T>
auto Tensor::register_hook(T&& hook) const -> Tensor::hook_return_void_t<T> {
  // Return the grad argument in case of a hook with void return type to have an
  // std::function with Tensor return type
  static_assert(std::is_same<decltype(hook(Tensor())), void>::value,
                "Expected hook to return void");
  return _register_hook([fn=std::forward<T>(hook)](const TensorBase& grad_base) {
    TensorRef grad(grad_base);
    fn(*grad);
    return Tensor();
  });
}

template <typename T>
auto Tensor::register_hook(T&& hook) const -> Tensor::hook_return_var_t<T> {
  return _register_hook([fn=std::forward<T>(hook)](const TensorBase& grad_base) {
    TensorRef grad(grad_base);
    Tensor ret = fn(*grad);
    return TensorBase(std::move(ret));
  });
}

} // namespace at

这段代码其实主要是实现了ATen/core/TensorBody.h中声明的部分类型,同时,最重要的torch::Tensor类型也是在ATen/core/TensorBody.h这个文件中声明的,因此我们进入这个文件。该文件中第92行到1458行为torch::Tensor的定义。从中我们可以看到,该类继承于TensorBase类,同时没有子类,也就是说在libtorch中,我们不能使用类似于pytorch中的torch.FloatTensor()这类函数初始化一个Tensor对象。这个现象在上文中其实有所体现,因为这些类型是在python中进行派生的。

class TORCH_API Tensor: public TensorBase {
 protected:
  // Create a Tensor with a +0 reference count. Special care must be
  // taken to avoid decrementing this reference count at destruction
  // time. Intended to support MaybeOwnedTraits<Tensor>.
  explicit Tensor(unsafe_borrow_t, const TensorBase& rhs): TensorBase(unsafe_borrow_t{}, rhs) {}
  friend MaybeOwnedTraits<Tensor>;
  friend OptionalTensorRef;
  friend TensorRef;

 public:
  Tensor() = default;
  // This constructor should not be used by end users and is an implementation
  // detail invoked by autogenerated code.
  explicit Tensor(
      c10::intrusive_ptr<TensorImpl, UndefinedTensorImpl> tensor_impl)
      : TensorBase(std::move(tensor_impl)) {}
  Tensor(const Tensor &tensor) = default;
  Tensor(Tensor &&tensor) = default;
...

torch::Storage浅析

现在我们来看看Storage类,抛去那些繁琐的查找,我们看到c10/core/Storage.h头文件的内容,该文件主要是Storage类的定义。

#pragma once

#include <c10/core/StorageImpl.h>

namespace c10 {

struct C10_API Storage {
 public:
  struct use_byte_size_t {};

  Storage() = default;
  Storage(c10::intrusive_ptr<StorageImpl> ptr)
      : storage_impl_(std::move(ptr)) {}

  // Allocates memory buffer using given allocator and creates a storage with it
  Storage(
      use_byte_size_t /*use_byte_size*/,
      SymInt size_bytes,
      Allocator* allocator = nullptr,
      bool resizable = false)
      : storage_impl_(c10::make_intrusive<StorageImpl>(
            StorageImpl::use_byte_size_t(),
            std::move(size_bytes),
            allocator,
            resizable)) {}

  // Creates storage with pre-allocated memory buffer. Allocator is given for
  // potential future reallocations, however it can be nullptr if the storage
  // is non-resizable
  Storage(
      use_byte_size_t /*use_byte_size*/,
      size_t size_bytes,
      at::DataPtr data_ptr,
      at::Allocator* allocator = nullptr,
      bool resizable = false)
      : storage_impl_(c10::make_intrusive<StorageImpl>(
            StorageImpl::use_byte_size_t(),
            size_bytes,
            std::move(data_ptr),
            allocator,
            resizable)) {}
...

在这部分,我们主要粗略地看了torch::Tensor以及torch::Storage的定义和部分实现,在继续翻看源码之前,我们在中间插入一些理论上的部分,这个部分主要和pytorch的设计理念有关。

Tensor原理介绍

我们先来学习下Tensor的实现原理,即官方在实现Tensor的过程中遵循了怎样的思想。Tensor 是PyTorch的核心数据结构,它是包含若干个标量(标量可以是各种数据类型如浮点型、整形等)的n-维的数据结构。我们可以认为tensor包含了数据和元数据(metadata),元数据用来描述tensor的大小、其包含内部数据的类型、存储的位置(CPU内存或是CUDA显存),而数据则是tensor真正的物理存储的数据。简单来说,我们对一个Tensor对象进行操作的时候,比如切分,resize等操作,实际上并不会改变这个Tensor对象在物理上的存储位置,而是通过操作metadata来改变这个Tensor对象的“逻辑表示”。

tensor原理.png

元数据metadata中有我们已经熟知的一些属性,如device,sizes,dtype,同样的也存在layoutstrides这些我们以前并未了解过的属性。
我们先来解释strides步长的概念,首先我们在上方已经提到,操作Tensor对象实际上是操作metadata的过程,例如执行以下代码:

import torch
a = torch.tensor([[1, 2],[3, 4]])
print(a[1, 0])

我们可以很容易的知道这段代码打印的结果是3,因为这符合我们的编程直觉和习惯。那么,当我们对a这个变量进行索引的时候,其底层究竟是怎么做的呢。根据我们在上文所讲述的,一个Tensor对象分为数据和元数据,数据其实是连续分配在某设备device上的,而元数据中的strides可以实现索引与数据物理位置的一一映射,具体我们看下方这张图:

stride原理.png

Tensor是一个数学概念。当用计算机表示数学概念的时候,通常我们需要定义一种物理存储方式。最常见的表示方式是将Tensor中的每个元素按照次序连续的在内存中铺开,将每一行写到相应内存位置里。如上图所示,假设tensor包含的是32位的整数,因此每个整数占据一块物理内存,每个整数的地址都和上下相邻整数相差4个字节。为了记住tensor的实际维度,我们需要将tensor的维度大小记录在额外的元数据中。假设我想要访问位于tensor [1, 0]位置处的元素,如何将这个逻辑地址转化到物理内存的地址上呢?步长就是用来解决这样的问题:当我们根据下标索引查找tensor中的任意元素时,将某维度的下标索引和对应的步长相乘,然后将所有维度乘积相加就可以了。在上图中我将第一维(行)标为红色,第二维(列)标为蓝色,因此你能够在计算中方便的观察下标和步长的对应关系。求和返回了一个0维的标量2,而内存中地址偏移量为2的位置正好储存了元素3。
到目前为止,细心的读者应该已经可以意识到,为什么一个Storage对象可以对应多个Tensor对象了。很显然,对于上面所举的例子,其中物理位置就是由Storage对象进行管理的,而逻辑表示则是由Tensor对象进行管理,通过不同的strides,sizes,我们可以很轻松地实现将一个Storage对象映射到多个Tensor对象中去。

Tensor源码解读

TensorBase类

在开始讲这个类之前,我们先来看一下core\TensorBase.h引入的头文件,可以看到当中有我们之前提到的一些熟悉的面孔,像Layout.hStorage.h等。

#include <c10/core/Device.h>
#include <c10/core/Layout.h>
#include <c10/core/MemoryFormat.h>
#include <c10/core/ScalarType.h>
#include <c10/core/ScalarTypeToTypeMeta.h>
#include <c10/core/Storage.h>
#include <c10/core/SymIntArrayRef.h>
#include <c10/core/TensorImpl.h>
#include <c10/core/TensorOptions.h>
#include <c10/core/UndefinedTensorImpl.h>
#include <c10/core/WrapDimMinimal.h>
#include <c10/util/Exception.h>
#include <c10/util/ExclusivelyOwned.h>
#include <c10/util/ExclusivelyOwnedTensorTraits.h>
#include <c10/util/MaybeOwned.h>
#include <c10/util/Optional.h>
#include <c10/util/intrusive_ptr.h>

#include <ATen/core/NamedTensor.h>
#include <ATen/core/QuantizerBase.h>
#include <ATen/core/TensorAccessor.h>
#include <ATen/StorageUtils.h>

我们在ATen\core\TensorBase.h中可以看到TensorBase类的声明和部分实现,这是所有Tensor类的基类,本文不去细究该类每一个成员函数的作用,而是从宏观的角度介绍这个类。

class TORCH_API TensorBase {
 public:
  struct unsafe_borrow_t { explicit unsafe_borrow_t() = default; };

 protected:
  // Create a Tensor with a +0 reference count. Special care must be
  // taken to avoid decrementing this reference count at destruction
  // time. Intended to support MaybeOwnedTraits<Tensor>.
  explicit TensorBase(unsafe_borrow_t, const TensorBase& rhs)
      : impl_(c10::intrusive_ptr<at::TensorImpl, UndefinedTensorImpl>::reclaim(rhs.impl_.get())) {}
  friend MaybeOwnedTraits<TensorBase>;
...

首先,对于部分C++了解不多的读者可能不清楚在class和类名TensorBase之间的TORCH_API是什么。其实TORCH_API是一个宏,我们知道宏是在程序的预编译时期起作用的,自然这里的TORCH_API也一定是由于某种需要在预编译时期进行处理的需求而出现的。我们再来看到开发者在contributing.md中说到的一段话:

Symbols are NOT exported by default on Windows; instead, you have to explicitly mark a symbol as exported/imported in a header file with __declspec(dllexport) / __declspec(dllimport). We have codified this pattern into a set of macros which follow the convention *_API, e.g., TORCH_API inside Caffe2, Aten and Torch. (Every separate shared library needs a unique macro name, because symbol visibility is on a per shared library basis. See c10/macros/Macros.h for more details.)

其实这里也就说明了为什么要使用TORCH_API的原因了,因为pytorch是以C、C++为后端,python为前端的框架。想要在python中使用C++,就不免需要用到动态链接库,即将C++的代码导出为.dll.so格式,而在windows上这些函数不能直接导出,必须在头文件中使用__declspec(dllexport)/__declsspec(dllimport)显式地将符号标记为导出/导入,而标记方式就是在类前加上这些宏。因此官方采用了一套*_API的宏用以编码,TORCH_API就是其中一种。
去查找TORCH_API这个宏,不难发现其实该宏的定义来自于C10_IMPORT这个宏。

#define TORCH_API C10_IMPORT

C10_IMPORT这个宏又定义于__declspec(dllimport),这也印证了前文所述。

#define C10_IMPORT __declspec(dllimport)

除了TORCH_API以外,这里还有个常用的宏函数TORCH_CHECK用于对数据合法性进行检查。

intrusive_ptr_target类

TensorBase.h文件的剩下部分都是对TensorBase类的成员函数进行定义,几乎每一个成员函数都用到了一个成员变量impl_,同时这些用到该成员变量的成员函数基本都可以认为是作用在这个成员变量上实现的,找到该文件的第890行,这里给出了该变量的定义:

protected:
  void enforce_invariants();
  c10::intrusive_ptr<TensorImpl, UndefinedTensorImpl> impl_;

在这里,对这个成员变量而言,我们需要注意两个部分,一是它的数据类型,是个典型的模板类,二是它的泛型声明为了TensorImplUndefinedTensorImpl两个类型。
我们首先来看TensorImpl是什么,实际上在TensorBody.h中有这么一段注释,这段注释位于75到91行,它的大意可以理解为,Tensor本质上是一个采用引用计数的对象,多个Tensor可以同时指向同一个TensorImpl。也就是说TensorBase这个类本质上是通过引用计数的方式指向TensorImpl对象来实现底层操作的,即TensorBase可以理解为对TensorImpl的进一步封装。当然,这样的设计思想在各种工程项目中也随处可见。

// Tensor is a "generic" object holding a pointer to the underlying TensorImpl object, which
// has an embedded reference count. In this way, Tensor is similar to boost::intrusive_ptr.
//
// For example:
//
// void func(Tensor a) {
//   Tensor b = a;
//   ...
// }
//
// In this example, when we say Tensor b = a, we are creating a new object that points to the
// same underlying TensorImpl, and bumps its reference count. When b goes out of scope, the
// destructor decrements the reference count by calling release() on the TensorImpl it points to.
// The existing constructors, operator overloads, etc. take care to implement the correct semantics.
//
// Note that Tensor can also be NULL, i.e. it is not associated with any underlying TensorImpl, and
// special care must be taken to handle this.

另一方面,它的类型是c10::intrusive_ptr,而这是pytorch框架最基础,最核心的数据结构代码,我们先来看一下官方是怎么描述的:

/**
 * intrusive_ptr<T> is an alternative to shared_ptr<T> that has better
 * performance because it does the refcounting intrusively
 * (i.e. in a member of the object itself).
 * Your class T needs to inherit from intrusive_ptr_target to allow it to be
 * used in an intrusive_ptr<T>. Your class's constructor should not allow
 *`this` to escape to other threads or create an intrusive_ptr from `this`.
 */

我们去寻找这个类型的定义,会发现在c10/util/intrusive_ptr.h这个文件下,第54到90行有如下代码,从这段代码可以知道c10::intrusive_ptrintrusive_ptr_target的友元类,除此之外,还有个值得注意的友元类weak_intrusive_ptr。实际上,PyTorch中使用intrusive_ptr来管理TensorStorage的引用计数,其中引用分为强引用和弱引用(弱引用为了解决循环引用问题),对应的类名 intrusive_ptrweak_intrusive_ptr

class C10_API intrusive_ptr_target {
  // Note [Weak references for intrusive refcounting]
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  // Here's the scheme:
  //
  //  - refcount == number of strong references to the object
  //    weakcount == number of weak references to the object,
  //      plus one more if refcount > 0
  //    An invariant: refcount > 0  =>  weakcount > 0
  //
  //  - c10::StorageImpl stays live as long as there are any strong
  //    or weak pointers to it (weakcount > 0, since strong
  //    references count as a +1 to weakcount)
  //
  //  - finalizers are called and data_ptr is deallocated when refcount == 0
  //
  //  - Once refcount == 0, it can never again be > 0 (the transition
  //    from > 0 to == 0 is monotonic)
  //
  //  - When you access c10::StorageImpl via a weak pointer, you must
  //    atomically increment the use count, if it is greater than 0.
  //    If it is not, you must report that the storage is dead.
  //
  mutable std::atomic<size_t> refcount_;
  mutable std::atomic<size_t> weakcount_;

  template <typename T, typename NullType>
  friend class intrusive_ptr;
  friend inline void raw::intrusive_ptr::incref(intrusive_ptr_target* self);

  template <typename T, typename NullType>
  friend class weak_intrusive_ptr;
  friend inline void raw::weak_intrusive_ptr::incref(
      intrusive_ptr_target* self);

  template <typename T>
  friend struct ExclusivelyOwnedTensorTraits;

当然,不仅如此,除了intrusive_ptrweak_intrusive_ptrintrusive_ptr_target的友元类之外,还可以知道的是,TensorImplUndefinedTensorImpl均是intrusive_ptr_target的子类,从这里我们也可以看出intrusive_ptr_target是pytorch得以实现的极为底层的核心数据结构。

TensorImpl类

现在我们来看一下TensorImpl类是如何组织的,这部分定义位于c10/core/TensorImpl.h的497到3060行,在这段代码里,我们可以看到一些上文提到过的概念,比如TensorImpl在初始化的时候其实传入了一个Storage对象,同时TensorImpl也出现了上文提到过的sizes。由于TensorBase本质上是对TesorImpl的引用,那么Tensor也就是对TensorImpl的引用,所有当我们创建torch::Tensor类型时,实际上是执行了这段代码。

struct C10_API TensorImpl : public c10::intrusive_ptr_target {
  TensorImpl() = delete;
  ~TensorImpl() override;
  // Note [Enum ImplType]
  // This enum is temporary. In the followup refactor we should
  // think about how to specialize TensorImpl creation for view
  // tensors. Currently we only special case its key_set_ but
  // there's also potential to share version_counter_ directly
  // without creating first and then override in as_view.
  enum ImplType { VIEW };

  /**
   * Construct a 1-dim 0-size tensor backed by the given storage.
   */
  TensorImpl(
      Storage&& storage,
      DispatchKeySet,
      const caffe2::TypeMeta data_type);

  // See Note [Enum ImplType]
  TensorImpl(
      ImplType,
      Storage&& storage,
      DispatchKeySet,
      const caffe2::TypeMeta data_type);
...
  TensorImpl(const TensorImpl&) = delete;
  TensorImpl& operator=(const TensorImpl&) = delete;
  TensorImpl(TensorImpl&&) = delete;
  TensorImpl& operator=(TensorImpl&&) = delete;
...
 public:
  /**
   * Return a reference to the sizes of this tensor.  This reference remains
   * valid as long as the tensor is live and not resized.
   */
  IntArrayRef sizes() const {
    if (C10_UNLIKELY(matches_policy(SizesStridesPolicy::CustomSizes))) {
      return sizes_custom();
    }
    return sizes_and_strides_.sizes_arrayref();
  }
...

StorageImpl类

StorageImpl类位于c10/core/StorageImpl.h中,它也是intrusive_ptr_target的子类,它的构造函数里有不少pytorch中的核心概念,如Allocator,resizable等,这些概念这篇文章不做讨论,将在后续的系列谈及。

struct C10_API StorageImpl : public c10::intrusive_ptr_target {
 public:
  struct use_byte_size_t {};

  StorageImpl(
      use_byte_size_t /*use_byte_size*/,
      SymInt size_bytes,
      at::DataPtr data_ptr,
      at::Allocator* allocator,
      bool resizable)
      : data_ptr_(std::move(data_ptr)),
        size_bytes_(std::move(size_bytes)),
        size_bytes_is_heap_allocated_(size_bytes_.is_heap_allocated()),
        resizable_(resizable),
        received_cuda_(false),
        allocator_(allocator) {
    if (resizable) {
      TORCH_INTERNAL_ASSERT(
          allocator_, "For resizable storage, allocator must be provided");
    }
  }

  StorageImpl(
      use_byte_size_t /*use_byte_size*/,
      const SymInt& size_bytes,
      at::Allocator* allocator,
      bool resizable)
      : StorageImpl(
            use_byte_size_t(),
            size_bytes,
            size_bytes.is_heap_allocated()
                ? allocator->allocate(0)
                : allocator->allocate(size_bytes.as_int_unchecked()),
            allocator,
            resizable) {}

  StorageImpl& operator=(StorageImpl&& other) = delete;
  StorageImpl& operator=(const StorageImpl&) = delete;
  StorageImpl() = delete;
  StorageImpl(StorageImpl&& other) = delete;
  StorageImpl(const StorageImpl&) = delete;
  ~StorageImpl() override = default;
...

总结,本文主要讲述了Tensor的源码实现机理,以及StorageTensor的关系,下一篇文章将会讲述pytorch是如何实现C++与python绑定的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,172评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,346评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,788评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,299评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,409评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,467评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,476评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,262评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,699评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,994评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,167评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,499评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,149评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,387评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,028评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,055评论 2 352

推荐阅读更多精彩内容