概述

Tensor是PyTorch的核心数据结构,承载着所有数值计算和深度学习操作。本文将深入剖析Tensor的完整实现架构,从底层的Storage存储系统到高层的Python接口,揭示PyTorch如何实现高效的张量操作和内存管理。

1. Tensor架构层次

1.1 核心组件关系

PyTorch的Tensor系统采用多层架构设计:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
┌─────────────────────────────────────────┐
│           Python torch.Tensor           │ ← Python用户接口
├─────────────────────────────────────────┤
│          TensorBase C++ 接口            │ ← C++基础接口
├─────────────────────────────────────────┤
│           TensorImpl 核心               │ ← 张量实现核心
├─────────────────────────────────────────┤
│            Storage 存储层               │ ← 数据存储管理
├─────────────────────────────────────────┤
│          StorageImpl 存储实现           │ ← 底层存储实现
├─────────────────────────────────────────┤
│            DataPtr 数据指针             │ ← 内存数据指针
└─────────────────────────────────────────┘

1.2 Tensor系统架构图

graph TB subgraph "Tensor 完整架构" subgraph "Python接口层" PT[torch.Tensor] TS[torch.Storage] TD[torch.dtype] TL[torch.layout] end subgraph "C++基础层" TB[TensorBase] TO[TensorOptions] TA[TensorAccessor] end subgraph "核心实现层" TI[TensorImpl] SS[SizesAndStrides] DK[DispatchKeySet] SM[SymbolicMeta] end subgraph "存储管理层" ST[Storage] SI[StorageImpl] DP[DataPtr] AL[Allocator] end subgraph "内存后端" CPU[CPU Allocator] CUDA[CUDA Allocator] CUSTOM[Custom Allocator] end subgraph "数据类型系统" SC[ScalarType] TM[TypeMeta] DT[Device] MF[MemoryFormat] end end %% 连接关系 PT --> TB PT --> TS TB --> TI TI --> ST TI --> SS TI --> DK TI --> SM ST --> SI SI --> DP DP --> AL AL --> CPU AL --> CUDA AL --> CUSTOM TI --> SC TI --> DT TI --> MF SC --> TM TO --> SC TO --> DT TO --> MF TO --> TL style TI fill:#e1f5fe style ST fill:#f3e5f5 style AL fill:#e8f5e8 style SC fill:#fff3e0

2. TensorImpl 核心数据结构

2.1 TensorImpl 完整定义

TensorImpl是Tensor的核心实现,包含了张量的所有元数据:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
class C10_API TensorImpl : public c10::intrusive_ptr_target {
 private:
  // 1. 存储相关
  Storage storage_;                    // 底层数据存储
  
  // 2. 形状和步长信息  
  c10::impl::SizesAndStrides sizes_and_strides_;  // 尺寸和步长
  SymInt storage_offset_;              // 存储偏移量
  
  // 3. 类型和设备信息
  ScalarType dtype_;                   // 数据类型(float32、int64等)
  Device device_;                      // 设备信息(CPU、CUDA等)
  
  // 4. 分发和优化相关
  DispatchKeySet key_set_;             // 分发键集合
  MemoryFormat memory_format_;         // 内存格式(连续、NHWC等)
  
  // 5. 梯度和版本管理
  std::unique_ptr<c10::impl::PyObjectSlot> pyobj_slot_;  // Python对象槽
  c10::impl::NamedTensorMetaInterface* named_tensor_meta_; // 命名张量元数据
  c10::VariableVersion version_counter_;  // 版本计数器
  c10::impl::AutogradMetaInterface* autograd_meta_;  // 自动微分元数据

 public:
  // 构造函数 - 创建具有给定存储和选项的张量
  TensorImpl(
      Storage&& storage,
      DispatchKeySet key_set,
      const caffe2::TypeMeta& data_type
  );
  
  // 获取底层存储
  const Storage& storage() const { return storage_; }
  
  // 获取张量维度
  int64_t dim() const { return sizes_and_strides_.size(); }
  
  // 获取形状信息
  IntArrayRef sizes() const {
    return IntArrayRef{
        sizes_and_strides_.sizes_data(), 
        sizes_and_strides_.size()
    };
  }
  
  // 获取步长信息
  IntArrayRef strides() const {
    return IntArrayRef{
        sizes_and_strides_.strides_data(),
        sizes_and_strides_.size()
    };
  }
  
  // 获取元素总数
  int64_t numel() const {
    return c10::size_to_dim_(dim(), sizes());
  }
  
  // 检查张量是否连续
  bool is_contiguous(at::MemoryFormat memory_format = at::MemoryFormat::Contiguous) const;
  
  // 调整张量大小
  virtual void resize_(IntArrayRef size, std::optional<IntArrayRef> stride = std::nullopt);
  
  // 其他核心方法...
};

TensorImpl关键字段详解

  1. storage_: 指向底层数据存储的Storage对象
  2. sizes_and_strides_: 高效存储张量形状和步长的数据结构
  3. storage_offset_: 在存储中的字节偏移量,支持视图操作
  4. key_set_: 分发键集合,决定操作如何分发到具体后端
  5. dtype_: 张量的数据类型,如float32、int64等
  6. device_: 张量所在的设备,如CPU、CUDA:0等

2.2 SizesAndStrides 优化结构

PyTorch使用专门的数据结构来高效存储张量的形状和步长信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
namespace c10::impl {

class SizesAndStrides {
 private:
  // 小张量优化:直接在对象内存储而非堆分配
  static constexpr size_t kInlineStorageSize = 5;
  
  // 联合体优化内存使用
  union {
    int64_t inline_storage_[kInlineStorageSize];  // 内联存储
    int64_t* heap_storage_;                       // 堆存储指针
  };
  
  uint8_t size_;           // 维度数量
  bool is_heap_allocated_; // 是否使用堆分配

 public:
  // 构造函数
  explicit SizesAndStrides(size_t size) : size_(size) {
    if (C10_LIKELY(size <= kInlineStorageSize)) {
      // 小张量使用栈内存,性能更好
      is_heap_allocated_ = false;
    } else {
      // 大张量使用堆内存
      is_heap_allocated_ = true;
      heap_storage_ = static_cast<int64_t*>(
          std::malloc(sizeof(int64_t) * size * 2)  // size + strides
      );
    }
  }
  
  // 获取形状数据指针
  const int64_t* sizes_data() const noexcept {
    return is_heap_allocated_ ? heap_storage_ : inline_storage_;
  }
  
  // 获取步长数据指针  
  const int64_t* strides_data() const noexcept {
    const int64_t* base = sizes_data();
    return base + size_;  // 步长紧跟在形状之后
  }
  
  // 设置形状信息
  void set_sizes(IntArrayRef newSizes) {
    TORCH_CHECK(newSizes.size() == size_);
    std::copy(newSizes.begin(), newSizes.end(), sizes_data_non_const());
  }
};

} // namespace c10::impl

SizesAndStrides设计亮点

  • 小对象优化: 5维以内的张量直接在对象内存储,避免堆分配
  • 内存局部性: 形状和步长数据连续存储,提升缓存命中率
  • 零开销抽象: 提供统一接口,编译器可完全内联优化

3. Storage存储系统深度解析

3.1 Storage核心架构

Storage是PyTorch中管理原始数据的抽象,实现了引用计数的内存管理:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
namespace c10 {

// Storage 接口类 - 提供统一的存储访问接口
struct C10_API Storage {
 private:
  c10::intrusive_ptr<StorageImpl> storage_impl_;  // 指向具体实现
  
 public:
  // 默认构造函数 - 创建空存储
  Storage() = default;
  
  // 从StorageImpl构造
  Storage(c10::intrusive_ptr<StorageImpl> ptr) 
      : storage_impl_(std::move(ptr)) {}
  
  // 使用字节大小创建存储
  Storage(
      use_byte_size_t /*use_byte_size*/,
      const SymInt& size_bytes,
      Allocator* allocator = nullptr,
      bool resizable = false
  ) : storage_impl_(c10::make_intrusive<StorageImpl>(
          StorageImpl::use_byte_size_t(),
          size_bytes,
          allocator,
          resizable)) {}
  
  // 使用预分配内存创建存储  
  Storage(
      use_byte_size_t /*use_byte_size*/,
      size_t size_bytes,
      at::DataPtr data_ptr,
      at::Allocator* allocator = nullptr,
      bool resizable = false
  );
  
  // 核心访问方法
  void* mutable_data() const { return storage_impl_->mutable_data(); }
  const void* data() const { return storage_impl_->data(); }
  
  SymInt nbytes() const { return storage_impl_->nbytes(); }
  Device device() const { return storage_impl_->device(); }
  
  bool resizable() const { return storage_impl_->resizable(); }
  Allocator* allocator() { return storage_impl_->allocator(); }
  
  // 引用计数管理
  bool unique() const { return storage_impl_.use_count() == 1; }
  int use_count() const { return storage_impl_.use_count(); }
};

} // namespace c10

3.2 StorageImpl详细实现

StorageImpl是存储的具体实现,管理实际的内存分配和生命周期:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
struct C10_API StorageImpl : public c10::intrusive_ptr_target {
 private:
  // 核心数据成员
  DataPtr data_ptr_;                    // 数据指针(包含删除器)
  SymInt size_bytes_;                   // 字节大小(支持符号整数)
  bool size_bytes_is_heap_allocated_;   // 大小是否堆分配
  bool resizable_;                      // 是否可调整大小
  bool received_cuda_;                  // 是否接收过CUDA数据
  Allocator* allocator_;                // 内存分配器
  
  // 扩展元数据(用于错误处理等)
  std::unique_ptr<StorageExtraMeta> extra_meta_;
  
 public:
  // 构造函数 - 使用字节大小和数据指针
  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"
      );
    }
    refresh_has_data_ptr_check();
  }
  
  // 获取原始数据指针
  void* data() {
    if (!has_data_ptr_check_) [[unlikely]] {
      check_data_ptr();
    }
    return data_ptr_.get();
  }
  
  // 获取字节大小
  SymInt nbytes() const {
    return size_bytes_;
  }
  
  // 调整存储大小
  void resize_(const SymInt& new_size_bytes) {
    TORCH_CHECK(resizable(), "Trying to resize storage that is not resizable");
    
    if (new_size_bytes == 0) {
      // 大小为0时释放内存但保留分配器
      data_ptr_ = allocator_->allocate(0);
    } else {
      // 重新分配内存
      DataPtr new_data = allocator_->allocate(new_size_bytes.as_int_unchecked());
      
      // 如果有旧数据,拷贝到新内存(取较小值)
      if (data_ptr_) {
        auto old_size = size_bytes_.as_int_unchecked();
        auto copy_size = std::min(old_size, new_size_bytes.as_int_unchecked());
        if (copy_size > 0) {
          std::memcpy(new_data.get(), data_ptr_.get(), copy_size);
        }
      }
      
      data_ptr_ = std::move(new_data);
    }
    
    size_bytes_ = new_size_bytes;
    refresh_has_data_ptr_check();
  }
  
  // 设备信息
  Device device() const {
    return data_ptr_.device();
  }
  
  // 分配器信息
  Allocator* allocator() {
    return allocator_;
  }
  
 private:
  // 检查数据指针有效性
  void check_data_ptr() {
    if (extra_meta_ && extra_meta_->custom_data_ptr_error_msg_) {
      TORCH_CHECK(false, extra_meta_->custom_data_ptr_error_msg_.value());
    }
    if (!data_ptr_) {
      throwNullDataPtrError();
    }
  }
  
  // 刷新数据指针检查状态
  void refresh_has_data_ptr_check() {
    has_data_ptr_check_ = (!extra_meta_ || !extra_meta_->custom_data_ptr_error_msg_) && data_ptr_;
  }
};

StorageImpl关键特性

  1. 引用计数管理: 继承自intrusive_ptr_target,自动管理生命周期
  2. 符号整数支持: size_bytes_支持符号整数,用于动态形状
  3. 可调整大小: 支持运行时调整存储大小,用于动态张量
  4. 设备感知: 通过DataPtr管理不同设备的内存
  5. 错误处理: 通过extra_meta_提供定制化错误信息

4. DataPtr和内存分配器

4.1 DataPtr智能指针

DataPtr是PyTorch中管理设备内存的智能指针:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
namespace at {

// DataPtr - 设备感知的智能指针
class DataPtr {
 private:
  void* ptr_;                    // 原始指针
  std::unique_ptr<void, DeleterFnPtr> ctx_;  // 删除器上下文
  Device device_;                // 设备信息
  
 public:
  // 构造函数
  DataPtr(void* data, Device device) 
      : ptr_(data), device_(device) {}
      
  DataPtr(void* data, void* ctx, DeleterFnPtr deleter, Device device)
      : ptr_(data), 
        ctx_(ctx, deleter),
        device_(device) {}
  
  // 移动构造和赋值
  DataPtr(DataPtr&& other) noexcept
      : ptr_(other.ptr_), 
        ctx_(std::move(other.ctx_)), 
        device_(other.device_) {
    other.ptr_ = nullptr;
  }
  
  // 获取原始指针
  void* get() const { return ptr_; }
  
  // 释放所有权
  void* release() noexcept {
    ctx_.release();
    auto result = ptr_;
    ptr_ = nullptr;
    return result;
  }
  
  // 获取设备信息
  Device device() const { return device_; }
  
  // 重置指针
  void clear() {
    ctx_.reset();
    ptr_ = nullptr;
  }
  
  // 类型转换
  template<typename T>
  T* get() const { return static_cast<T*>(ptr_); }
  
  // 布尔转换
  explicit operator bool() const { return ptr_ != nullptr; }
};

} // namespace at

4.2 内存分配器体系

PyTorch提供了多种内存分配器以适应不同的使用场景:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
namespace c10 {

// 抽象分配器接口
class C10_API Allocator {
 public:
  virtual ~Allocator() = default;
  
  // 分配内存
  virtual DataPtr allocate(size_t n) const = 0;
  
  // 获取删除器函数指针
  virtual DeleterFnPtr raw_deleter() const { 
    return nullptr; 
  }
  
  // 复制数据(可选优化)
  virtual void copy_data(void* dest, const void* src, std::size_t count) const {
    std::memcpy(dest, src, count);
  }
};

// CPU默认分配器
class DefaultCPUAllocator : public Allocator {
 public:
  DataPtr allocate(size_t nbytes) const override {
    void* data = aligned_alloc(gAlignment, nbytes);
    if (!data) {
      TORCH_CHECK(false, "DefaultCPUAllocator: can't allocate memory");
    }
    return {data, data, &free, kCPU};
  }
  
  DeleterFnPtr raw_deleter() const override {
    return &free;
  }
};

// CPU缓存分配器 - 减少分配/释放开销
class CPUCachingAllocator : public Allocator {
 private:
  // 内存块结构
  struct Block {
    void* ptr;
    size_t size;
    bool in_use;
  };
  
  mutable std::mutex mutex_;
  mutable std::vector<Block> cached_blocks_;
  
 public:
  DataPtr allocate(size_t nbytes) const override {
    std::lock_guard<std::mutex> lock(mutex_);
    
    // 1. 尝试从缓存中找到合适的块
    auto it = std::find_if(cached_blocks_.begin(), cached_blocks_.end(),
        [nbytes](const Block& block) {
          return !block.in_use && block.size >= nbytes;
        });
    
    if (it != cached_blocks_.end()) {
      // 找到缓存块,标记为使用中
      it->in_use = true;
      return {it->ptr, this, &CPUCachingAllocator::free_cached, kCPU};
    }
    
    // 2. 没有合适的缓存,分配新内存
    void* ptr = aligned_alloc(gAlignment, nbytes);
    if (!ptr) {
      TORCH_CHECK(false, "CPUCachingAllocator: allocation failed");
    }
    
    // 添加到缓存中
    cached_blocks_.push_back({ptr, nbytes, true});
    
    return {ptr, this, &CPUCachingAllocator::free_cached, kCPU};
  }
  
  // 释放内存到缓存
  static void free_cached(void* ctx, void* ptr) {
    auto* allocator = static_cast<CPUCachingAllocator*>(ctx);
    std::lock_guard<std::mutex> lock(allocator->mutex_);
    
    auto it = std::find_if(allocator->cached_blocks_.begin(), 
                          allocator->cached_blocks_.end(),
        [ptr](const Block& block) {
          return block.ptr == ptr;
        });
    
    if (it != allocator->cached_blocks_.end()) {
      it->in_use = false;  // 标记为可用,不实际释放
    }
  }
};

} // namespace c10

分配器设计特点

  1. 接口统一: 所有分配器实现相同的Allocator接口
  2. 设备感知: DataPtr包含设备信息,支持跨设备内存管理
  3. 性能优化: 缓存分配器减少系统调用开销
  4. 可扩展: 支持自定义分配器,如内存池、GPU分配器等

5. 张量视图机制

5.1 视图创建原理

PyTorch的视图机制允许多个张量共享同一份数据,实现零拷贝操作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// 视图创建的核心逻辑
Tensor TensorImpl::create_view(
    IntArrayRef size,
    IntArrayRef stride, 
    int64_t storage_offset
) {
  // 1. 创建新的TensorImpl,但共享Storage
  auto impl = c10::make_intrusive<TensorImpl>(
      Storage(storage()),           // 共享底层存储
      key_set(),                   // 继承分发键
      dtype()                      // 继承数据类型
  );
  
  // 2. 设置新的形状和步长
  impl->set_sizes_and_strides(size, stride);
  impl->set_storage_offset(storage_offset);
  
  // 3. 继承其他属性
  impl->set_device(device());
  impl->set_layout(layout());
  
  // 4. 返回包装的Tensor对象
  return at::detail::make_tensor<TensorImpl>(std::move(impl));
}

// 常见视图操作的实现
namespace at::native {

// 切片操作 - tensor[start:end]
Tensor slice(const Tensor& self, int64_t dim, int64_t start, int64_t end, int64_t step) {
  // 计算新的形状
  auto sizes = self.sizes().vec();
  sizes[dim] = (end - start + step - 1) / step;
  
  // 计算新的步长
  auto strides = self.strides().vec(); 
  strides[dim] *= step;
  
  // 计算存储偏移
  auto offset = self.storage_offset() + start * self.stride(dim);
  
  // 创建视图
  return self.as_strided(sizes, strides, offset);
}

// 转置操作 - tensor.t()  
Tensor transpose(const Tensor& self, int64_t dim0, int64_t dim1) {
  auto sizes = self.sizes().vec();
  auto strides = self.strides().vec();
  
  // 交换指定维度的尺寸和步长
  std::swap(sizes[dim0], sizes[dim1]);
  std::swap(strides[dim0], strides[dim1]);
  
  // 创建视图(共享存储,不同形状)
  return self.as_strided(sizes, strides, self.storage_offset());
}

// reshape操作 - 改变形状
Tensor reshape(const Tensor& self, IntArrayRef shape) {
  // 1. 检查元素总数是否匹配
  auto numel = c10::multiply_integers(shape);
  TORCH_CHECK(numel == self.numel(), "reshape: incompatible shape");
  
  // 2. 如果可能,尝试创建视图
  auto stride = compute_stride_for_reshape(self, shape);
  if (stride.has_value()) {
    return self.as_strided(shape, *stride, self.storage_offset());
  }
  
  // 3. 否则,需要拷贝数据并重新排列
  return self.contiguous().view(shape);
}

} // namespace at::native

5.2 步长计算与内存布局

步长(stride)是PyTorch实现高效视图的核心概念:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
namespace c10 {

// 计算连续张量的默认步长
std::vector<int64_t> compute_contiguous_strides(IntArrayRef sizes) {
  std::vector<int64_t> strides(sizes.size());
  
  // 从最后一维开始,步长为1
  strides.back() = 1;
  
  // 向前计算每一维的步长
  for (int i = static_cast<int>(sizes.size()) - 2; i >= 0; --i) {
    strides[i] = strides[i + 1] * sizes[i + 1];
  }
  
  return strides;
}

// 示例:形状为[2,3,4]的张量
// sizes = [2, 3, 4]
// strides = [12, 4, 1]  // 12=3*4, 4=4*1, 1
// 元素[i,j,k]在内存中的位置 = i*12 + j*4 + k*1

// 计算NHWC格式的步长(通道优先)
std::vector<int64_t> compute_channels_last_strides(IntArrayRef sizes) {
  TORCH_CHECK(sizes.size() == 4, "channels_last only supports 4D tensors");
  
  auto N = sizes[0], C = sizes[1], H = sizes[2], W = sizes[3];
  
  // NHWC布局:步长为[H*W*C, 1, W*C, C]
  return {H * W * C, 1, W * C, C};
}

// 检查张量是否连续
bool is_contiguous_with_format(
    IntArrayRef sizes,
    IntArrayRef strides,
    MemoryFormat memory_format
) {
  switch (memory_format) {
    case MemoryFormat::Contiguous: {
      auto expected_strides = compute_contiguous_strides(sizes);
      return strides == expected_strides;
    }
    
    case MemoryFormat::ChannelsLast: {
      if (sizes.size() != 4) return false;
      auto expected_strides = compute_channels_last_strides(sizes);
      return strides == expected_strides;
    }
    
    default:
      return false;
  }
}

} // namespace c10

视图机制的关键优势

  1. 零拷贝: 视图操作不复制数据,只改变元数据
  2. 内存高效: 多个视图共享同一存储,节省内存
  3. 操作灵活: 支持切片、转置、reshape等多种变换
  4. 性能优异: 视图创建时间复杂度O(1)

6. 张量操作的关键路径

6.1 连续性检查与优化

PyTorch在张量操作中大量使用连续性检查来优化性能。基于网上优秀的源码分析文章总结,连续性检查是张量操作的关键优化点:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
namespace at {

// 连续性检查的核心实现(来源:PyTorch源码剖析)
bool TensorBase::is_contiguous_or_false(MemoryFormat memory_format) const {
  if (impl_->is_contiguous_default()) {
    // 快速路径:如果已知是默认连续的
    return memory_format == MemoryFormat::Contiguous;
  }
  
  // 详细检查连续性
  return impl_->is_contiguous_memory_format(memory_format);
}

// 高效的连续化操作
TensorBase TensorBase::contiguous(MemoryFormat memory_format) const {
  if (is_contiguous_or_false(memory_format)) {
    // 已经连续,直接返回(避免不必要的拷贝)
    return *this;
  } else {
    // 需要重新排列内存布局
    return __dispatch_contiguous(memory_format);
  }
}

// expect_contiguous:性能优化的连续性检查
c10::MaybeOwned<TensorBase> TensorBase::expect_contiguous(
    MemoryFormat memory_format) const & {
  
  if (is_contiguous_or_false(memory_format)) {
    // 已经连续,返回借用的引用(避免引用计数开销)
    return c10::MaybeOwned<TensorBase>::borrowed(*this);
  } else {
    // 需要创建连续版本,返回拥有的副本
    return c10::MaybeOwned<TensorBase>::owned(__dispatch_contiguous(memory_format));
  }
}

} // namespace at

连续性优化的关键点(参考多篇源码分析文章):

  1. 快速路径检查: 通过缓存的连续性标志避免重复计算
  2. 借用语义: expect_contiguous使用MaybeOwned避免不必要的引用计数操作
  3. 内存格式感知: 支持NCHW、NHWC等多种内存布局的连续性检查
  4. 惰性计算: 只有在真正需要时才执行昂贵的内存重排操作

6.2 stride计算的数学原理

根据网上深入的源码分析,stride(步长)计算是张量视图操作的数学基础:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
namespace c10 {

// stride计算的完整数学原理(基于源码分析总结)
class StrideCalculator {
 public:
  // 计算连续张量的步长
  static std::vector<int64_t> contiguous_strides(IntArrayRef sizes) {
    /*
     * 连续张量的步长计算公式:
     * stride[i] = ∏(j=i+1 to n-1) size[j]
     * 
     * 例如:shape=[2,3,4]
     * stride[2] = 1                    // 最后一维步长为1
     * stride[1] = size[2] = 4          // 倒数第二维
     * stride[0] = size[1]*size[2] = 12 // 第一维
     */
    
    const int64_t ndim = sizes.size();
    if (ndim == 0) return {};
    
    std::vector<int64_t> strides(ndim);
    strides[ndim - 1] = 1;  // 最后一维步长为1
    
    // 从后向前计算步长
    for (int64_t i = ndim - 2; i >= 0; --i) {
      strides[i] = strides[i + 1] * sizes[i + 1];
    }
    
    return strides;
  }
  
  // 计算通道最后格式(NHWC)的步长
  static std::vector<int64_t> channels_last_strides(IntArrayRef sizes) {
    /*
     * NHWC格式的步长计算:
     * - N(batch): stride = H * W * C
     * - H(height): stride = W * C  
     * - W(width): stride = C
     * - C(channel): stride = 1
     */
    
    TORCH_CHECK(sizes.size() == 4, "channels_last requires 4D tensor");
    
    const int64_t N = sizes[0], C = sizes[1], H = sizes[2], W = sizes[3];
    return {H * W * C, 1, W * C, C};
  }
  
  // 计算3D通道最后格式的步长
  static std::vector<int64_t> channels_last_3d_strides(IntArrayRef sizes) {
    /*
     * NDHWC格式的步长计算:
     * - N: stride = D * H * W * C
     * - D: stride = H * W * C
     * - H: stride = W * C
     * - W: stride = C
     * - C: stride = 1
     */
    
    TORCH_CHECK(sizes.size() == 5, "channels_last_3d requires 5D tensor");
    
    const int64_t N = sizes[0], C = sizes[1], D = sizes[2], H = sizes[3], W = sizes[4];
    return {D * H * W * C, 1, H * W * C, W * C, C};
  }
  
  // 检查步长是否有效(基于网上分析的边界情况处理)
  static bool are_strides_valid(IntArrayRef sizes, IntArrayRef strides) {
    if (sizes.size() != strides.size()) return false;
    
    // 检查步长的数学约束
    for (size_t i = 0; i < sizes.size(); ++i) {
      // 大小为0或1的维度,步长可以是任意值
      if (sizes[i] <= 1) continue;
      
      // 步长不能为负数
      if (strides[i] < 0) return false;
      
      // 步长为0时,该维度大小必须为1
      if (strides[i] == 0 && sizes[i] > 1) return false;
    }
    
    return true;
  }
};

} // namespace c10

6.3 元素访问机制

基于深入的源码分析,PyTorch提供了多种高效的张量元素访问方式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
namespace at {

// TensorAccessor - 提供类型安全的多维数组访问
template<typename T, size_t N, template<typename U> class PtrTraits = DefaultPtrTraits>
class TensorAccessor {
 private:
  T* data_;                     // 数据指针
  int64_t sizes_[N];           // 各维大小
  int64_t strides_[N];         // 各维步长
  
 public:
  TensorAccessor(T* data, IntArrayRef sizes, IntArrayRef strides) 
      : data_(data) {
    TORCH_CHECK(sizes.size() == N && strides.size() == N);
    std::copy(sizes.begin(), sizes.end(), sizes_);
    std::copy(strides.begin(), strides.end(), strides_);
  }
  
  // 多维访问操作符
  template<typename... Args>
  T& operator()(Args... indices) {
    static_assert(sizeof...(Args) == N, "Wrong number of indices");
    return data_[compute_offset(indices...)];
  }
  
  // 单维访问(递归返回低维Accessor)
  TensorAccessor<T, N-1, PtrTraits> operator[](int64_t i) {
    return TensorAccessor<T, N-1, PtrTraits>(
        data_ + i * strides_[0],
        sizes_ + 1,
        strides_ + 1
    );
  }
  
 private:
  // 计算线性偏移
  template<typename... Args>
  int64_t compute_offset(int64_t first, Args... rest) {
    return first * strides_[N - sizeof...(Args) - 1] + 
           compute_offset_impl<sizeof...(Args)>(rest...);
  }
  
  template<size_t remaining, typename... Args>
  int64_t compute_offset_impl(int64_t first, Args... rest) {
    if constexpr (remaining == 1) {
      return first * strides_[N - 1];
    } else {
      return first * strides_[N - remaining] + 
             compute_offset_impl<remaining - 1>(rest...);
    }
  }
};

// 使用示例
void example_tensor_access() {
  auto tensor = torch::rand({3, 4, 5});
  
  // 获取类型安全的访问器
  auto accessor = tensor.accessor<float, 3>();
  
  // 直接多维访问
  float value = accessor(1, 2, 3);  // 等价于 tensor[1][2][3]
  
  // 层次化访问
  auto slice = accessor[1];         // 得到2D accessor
  float value2 = slice(2, 3);       // 等价于 tensor[1][2][3]
}

} // namespace at

6.2 张量创建流程详解

张量创建涉及多个步骤,从Python接口到底层内存分配:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 张量创建的完整流程
namespace torch {

// Python API: torch.tensor([1, 2, 3])
Tensor tensor_from_data(py::object data, py::object dtype_obj, py::object device_obj) {
  // 1. 解析Python参数
  auto dtype = dtype_obj.is_none() ? infer_dtype(data) : to_scalar_type(dtype_obj);
  auto device = device_obj.is_none() ? Device(kCPU) : to_device(device_obj);
  
  // 2. 解析数据结构,推断形状
  auto parsed = parse_python_scalars_and_tensors(data);
  auto sizes = parsed.sizes;
  auto scalar_list = parsed.scalars;
  
  // 3. 计算总元素数
  auto numel = c10::multiply_integers(sizes);
  
  // 4. 分配存储空间
  auto dtype_size = scalarTypeToTypeMeta(dtype).itemsize();
  auto nbytes = numel * dtype_size;
  
  auto allocator = GetAllocator(device.type());
  auto storage = c10::make_intrusive<c10::StorageImpl>(
      c10::StorageImpl::use_byte_size_t(),
      nbytes,
      allocator->allocate(nbytes),
      allocator,
      /*resizable=*/true
  );
  
  // 5. 创建TensorImpl
  auto tensor_impl = c10::make_intrusive<c10::TensorImpl>(
      c10::Storage(storage),
      compute_dispatch_key_set(dtype, device),
      scalarTypeToTypeMeta(dtype)
  );
  
  // 6. 设置形状信息
  tensor_impl->set_sizes_and_strides(sizes, compute_contiguous_strides(sizes));
  
  // 7. 填充数据
  fill_tensor_from_scalars(tensor_impl.get(), scalar_list, dtype);
  
  // 8. 返回包装的Tensor对象
  return at::detail::make_tensor<c10::TensorImpl>(std::move(tensor_impl));
}

// 数据填充函数
void fill_tensor_from_scalars(c10::TensorImpl* impl, 
                             const std::vector<at::Scalar>& scalars, 
                             c10::ScalarType dtype) {
  auto data_ptr = impl->storage().mutable_data();
  
  // 根据数据类型进行特化处理
  AT_DISPATCH_ALL_TYPES_AND_COMPLEX_AND3(
      c10::kHalf, c10::kBFloat16, c10::kBool, dtype, "fill_from_scalars", [&] {
        auto typed_data = static_cast<scalar_t*>(data_ptr);
        for (size_t i = 0; i < scalars.size(); ++i) {
          typed_data[i] = scalars[i].to<scalar_t>();
        }
      });
}

} // namespace torch

6.3 算子分发流程

当调用张量操作时,PyTorch会通过复杂的分发机制找到对应的实现:

sequenceDiagram participant User as Python用户 participant Tensor as torch.Tensor participant Dispatch as 分发器 participant Backend as 后端实现 participant AutoGrad as 自动微分 participant Memory as 内存管理 Note over User,Memory: 张量操作的完整分发流程 User->>Tensor: tensor.add(other) Tensor->>Dispatch: 调用ATen分发器 Dispatch->>Dispatch: 1. 计算分发键集合 Note right of Dispatch: 基于设备类型、dtype、
是否需要梯度等 Dispatch->>Dispatch: 2. 查找算子实现 Note right of Dispatch: 按优先级查找:
AutogradCPU > CPU
AutogradCUDA > CUDA alt 需要自动微分 Dispatch->>AutoGrad: 调用Autograd实现 AutoGrad->>AutoGrad: 记录操作到计算图 AutoGrad->>Backend: 调用实际计算函数 Backend->>Memory: 分配输出张量 Memory->>Backend: 返回张量存储 Backend->>Backend: 执行具体算法 Backend->>AutoGrad: 返回结果 AutoGrad->>AutoGrad: 设置grad_fn AutoGrad->>Dispatch: 返回结果张量 else 纯推理模式 Dispatch->>Backend: 直接调用后端实现 Backend->>Memory: 分配输出张量 Memory->>Backend: 返回张量存储 Backend->>Backend: 执行具体算法 Backend->>Dispatch: 返回结果 end Dispatch->>Tensor: 包装为Tensor对象 Tensor->>User: 返回结果

7. 内存优化技术

7.1 写时拷贝(Copy-on-Write)

PyTorch实现了写时拷贝机制来优化内存使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
namespace c10::impl {

// COW(Copy-on-Write)上下文
class COWDeleterContext {
 private:
  std::shared_ptr<void> original_data_;  // 原始数据的共享指针
  size_t original_size_;                 // 原始数据大小
  Device device_;                        // 设备信息
  
 public:
  COWDeleterContext(std::shared_ptr<void> data, size_t size, Device device)
      : original_data_(std::move(data)), original_size_(size), device_(device) {}
  
  // 当需要修改数据时,执行实际拷贝
  static void deleter_function(void* ctx, void* data_ptr) {
    auto* cow_ctx = static_cast<COWDeleterContext*>(ctx);
    
    // 如果是唯一引用,直接释放
    if (cow_ctx->original_data_.use_count() == 1) {
      // 正常释放逻辑
      GetAllocator(cow_ctx->device_.type())->raw_deleter()(data_ptr);
    }
    
    delete cow_ctx;
  }
  
  // 检查是否需要拷贝
  bool needs_cow() const {
    return original_data_.use_count() > 1;
  }
  
  // 执行写时拷贝
  DataPtr make_cow_copy() {
    auto allocator = GetAllocator(device_.type());
    auto new_data = allocator->allocate(original_size_);
    
    // 拷贝原始数据
    std::memcpy(new_data.get(), original_data_.get(), original_size_);
    
    return new_data;
  }
};

} // namespace c10::impl

7.2 CUDA缓存分配器详解

基于网上深入的CUDA内存管理分析,PyTorch的CUDA缓存分配器是GPU内存优化的核心:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
namespace c10::cuda::CUDACachingAllocator {

// CUDA缓存分配器的核心数据结构(源码深度分析)
struct Block {
  size_t size;                    // 块大小
  size_t requested_size;          // 实际请求大小
  void* ptr;                      // 数据指针
  cudaStream_t stream;            // 关联的CUDA流
  bool allocated;                 // 是否已分配
  bool active;                    // 是否活跃使用
  c10::DeviceIndex device;        // 设备索引
  
  // 内存池相关
  MempoolId_t owner_private_pool_id;  // 私有池ID
  
  // 分割和合并支持
  Block* prev;                    // 前一个块(用于合并)
  Block* next;                    // 后一个块(用于合并)
  
  // 流相关的引用计数
  std::unordered_set<c10::cuda::CUDAStream> stream_uses;
  
  // 垃圾回收计数器
  int32_t gc_counter;
  
  // 构造函数
  Block(
      size_t size,
      size_t requested_size, 
      void* ptr,
      cudaStream_t stream,
      c10::DeviceIndex device)
      : size(size),
        requested_size(requested_size),
        ptr(ptr),
        stream(stream),
        allocated(true),
        active(true),
        device(device),
        prev(nullptr),
        next(nullptr),
        gc_counter(0) {}
};

// 内存段管理(源码分析:大块内存的管理策略)
struct Segment {
  c10::DeviceIndex device;        // 设备索引
  size_t address;                 // 内存地址
  size_t total_size;              // 总大小
  size_t allocated_size;          // 已分配大小
  size_t active_size;             // 活跃使用大小
  cudaStream_t stream;            // 创建时的流
  bool is_large;                  // 是否为大段
  bool is_expandable;             // 是否可扩展
  
  std::vector<Block*> blocks;     // 包含的块列表
  
  // 查找可用的块
  Block* find_available_block(size_t requested_size) {
    for (auto* block : blocks) {
      if (!block->allocated && block->size >= requested_size) {
        return block;
      }
    }
    return nullptr;
  }
  
  // 分割块
  Block* split_block(Block* block, size_t size) {
    if (block->size <= size) {
      return block;  // 无需分割
    }
    
    // 创建新块用于剩余空间
    size_t remaining_size = block->size - size;
    void* remaining_ptr = static_cast<char*>(block->ptr) + size;
    
    auto* new_block = new Block(
        remaining_size,
        0,  // requested_size为0表示这是分割产生的块
        remaining_ptr,
        block->stream,
        block->device
    );
    
    new_block->allocated = false;
    new_block->active = false;
    
    // 更新原块大小
    block->size = size;
    
    // 插入到块列表中
    blocks.insert(
        std::find(blocks.begin(), blocks.end(), block) + 1,
        new_block
    );
    
    return block;
  }
  
  // 合并相邻的空闲块
  void merge_free_blocks() {
    for (size_t i = 0; i < blocks.size() - 1; ++i) {
      auto* current = blocks[i];
      auto* next = blocks[i + 1];
      
      if (!current->allocated && !next->allocated) {
        // 检查是否相邻
        if (static_cast<char*>(current->ptr) + current->size == next->ptr) {
          // 合并块
          current->size += next->size;
          
          // 移除下一个块
          blocks.erase(blocks.begin() + i + 1);
          delete next;
          
          // 重新检查当前位置
          --i;
        }
      }
    }
  }
};

// 缓存分配器的核心算法(深度源码分析)
class CachingAllocatorImpl {
 private:
  // 设备特定的分配器状态
  struct DeviceAllocatorState {
    // 分大小范围的空闲块列表
    std::map<size_t, std::set<Block*>> large_free_blocks;    // 大块 (>1MB)
    std::map<size_t, std::set<Block*>> small_free_blocks;    // 小块 (<=1MB)
    
    // 活跃块列表
    std::unordered_set<Block*> active_blocks;
    
    // 内存段列表
    std::vector<std::unique_ptr<Segment>> segments;
    
    // 统计信息
    size_t allocated_size = 0;
    size_t reserved_size = 0;
    size_t peak_allocated_size = 0;
    size_t peak_reserved_size = 0;
    
    // 垃圾回收相关
    std::atomic<size_t> garbage_collect_threshold{1024 * 1024 * 1024};  // 1GB
  };
  
  // 每个设备的状态
  std::vector<DeviceAllocatorState> device_allocator_states_;
  
  // 全局锁
  mutable std::recursive_mutex mutex_;
  
 public:
  // 分配内存的核心算法
  DataPtr allocate(size_t requested_size) {
    // 对齐大小到最近的512字节边界(GPU内存对齐优化)
    size_t size = round_size(requested_size);
    
    auto device = c10::cuda::current_device();
    std::lock_guard<std::recursive_mutex> lock(mutex_);
    
    auto& state = device_allocator_states_[device];
    
    // 1. 尝试从空闲块中分配
    Block* block = find_free_block(state, size);
    if (block) {
      return allocate_from_block(block, requested_size);
    }
    
    // 2. 尝试垃圾回收
    bool freed_memory = garbage_collect_cached_blocks(state);
    if (freed_memory) {
      block = find_free_block(state, size);
      if (block) {
        return allocate_from_block(block, requested_size);
      }
    }
    
    // 3. 分配新的内存段
    return allocate_new_segment(state, size, requested_size);
  }
  
  // 释放内存(实际加入缓存)
  void deallocate(void* ptr) {
    if (!ptr) return;
    
    std::lock_guard<std::recursive_mutex> lock(mutex_);
    
    auto* block = find_allocated_block(ptr);
    if (!block) {
      throw std::runtime_error("Invalid memory pointer for deallocation");
    }
    
    // 标记为空闲但保留在缓存中
    block->allocated = false;
    block->active = false;
    
    // 将块加入到空闲列表
    auto device = block->device;
    auto& state = device_allocator_states_[device];
    
    if (block->size >= kLargeBuffer) {
      state.large_free_blocks[block->size].insert(block);
    } else {
      state.small_free_blocks[block->size].insert(block);
    }
    
    // 尝试与相邻块合并
    try_merge_blocks(block);
  }
  
 private:
  // 查找合适的空闲块
  Block* find_free_block(DeviceAllocatorState& state, size_t size) {
    // 根据大小选择合适的空闲列表
    auto& free_blocks = (size >= kLargeBuffer) ? 
        state.large_free_blocks : state.small_free_blocks;
    
    // 查找最小的合适块(最佳适配算法)
    auto it = free_blocks.lower_bound(size);
    if (it != free_blocks.end() && !it->second.empty()) {
      Block* block = *it->second.begin();
      it->second.erase(it->second.begin());
      
      // 如果块太大,考虑分割
      if (block->size > size * 2) {
        split_block(block, size);
      }
      
      return block;
    }
    
    return nullptr;
  }
  
  // 垃圾回收算法
  bool garbage_collect_cached_blocks(DeviceAllocatorState& state) {
    size_t freed_bytes = 0;
    
    // 收集过期的空闲块
    auto collect_from_map = [&](auto& free_map) {
      for (auto it = free_map.begin(); it != free_map.end();) {
        auto& block_set = it->second;
        
        for (auto block_it = block_set.begin(); block_it != block_set.end();) {
          auto* block = *block_it;
          
          // 检查块是否可以安全释放
          if (can_release_block(block)) {
            freed_bytes += block->size;
            
            // 实际释放GPU内存
            C10_CUDA_CHECK(cudaFree(block->ptr));
            
            block_it = block_set.erase(block_it);
            delete block;
          } else {
            ++block_it;
          }
        }
        
        if (block_set.empty()) {
          it = free_map.erase(it);
        } else {
          ++it;
        }
      }
    };
    
    // 先收集小块,再收集大块
    collect_from_map(state.small_free_blocks);
    collect_from_map(state.large_free_blocks);
    
    state.reserved_size -= freed_bytes;
    
    return freed_bytes > 0;
  }
  
  // 检查块是否可以释放
  bool can_release_block(Block* block) {
    // 检查是否有活跃的流引用
    for (const auto& stream : block->stream_uses) {
      if (!stream.query()) {
        return false;  // 流还在使用中
      }
    }
    
    // 检查垃圾回收计数器
    return block->gc_counter > 3;  // 经过3次GC周期后可释放
  }
  
  // 内存大小对齐(GPU优化)
  size_t round_size(size_t size) {
    // 小于512字节:对齐到最近的8字节
    if (size < 512) {
      return ((size + 7) / 8) * 8;
    }
    // 大于等于512字节:对齐到最近的512字节
    else {
      return ((size + 511) / 512) * 512;
    }
  }
};

} // namespace c10::cuda::CUDACachingAllocator

CUDA内存管理的优化策略(综合多篇技术分析):

  1. 分级缓存: 大块(>1MB)和小块分别管理,提高查找效率
  2. 最佳适配: 寻找大小最接近的空闲块,减少内存碎片
  3. 延迟释放: 块释放后保留在缓存中,避免频繁的cudaMalloc/cudaFree
  4. 流感知: 考虑CUDA流的同步状态,确保内存访问安全
  5. 垃圾回收: 定期清理长期未使用的内存块

7.3 内存池优化

基于网上的性能优化分析,PyTorch使用内存池来减少内存分配开销:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
namespace c10 {

// 内存池分配器
class MemoryPool {
 private:
  struct Block {
    void* ptr;
    size_t size;
    bool is_free;
    Block* next;
    Block* prev;
  };
  
  // 不同大小的空闲列表
  static constexpr size_t kNumBuckets = 32;
  Block* free_lists_[kNumBuckets];
  
  mutable std::mutex mutex_;
  Allocator* underlying_allocator_;
  size_t peak_allocated_;
  size_t current_allocated_;
  
 public:
  MemoryPool(Allocator* allocator) 
      : underlying_allocator_(allocator),
        peak_allocated_(0),
        current_allocated_(0) {
    std::fill(std::begin(free_lists_), std::end(free_lists_), nullptr);
  }
  
  // 分配内存
  DataPtr allocate(size_t size) {
    std::lock_guard<std::mutex> lock(mutex_);
    
    // 1. 计算桶索引
    size_t bucket = size_to_bucket(size);
    
    // 2. 尝试从对应桶中获取
    if (auto* block = pop_free_block(bucket, size)) {
      block->is_free = false;
      current_allocated_ += block->size;
      return DataPtr(block->ptr, block, &MemoryPool::deallocate_block, kCPU);
    }
    
    // 3. 没有合适的块,分配新的
    auto underlying_data = underlying_allocator_->allocate(size);
    
    auto* block = new Block{
        .ptr = underlying_data.release(),
        .size = size,
        .is_free = false,
        .next = nullptr,
        .prev = nullptr
    };
    
    current_allocated_ += size;
    peak_allocated_ = std::max(peak_allocated_, current_allocated_);
    
    return DataPtr(block->ptr, block, &MemoryPool::deallocate_block, kCPU);
  }
  
  // 释放内存(实际放回池中)
  static void deallocate_block(void* ctx, void* ptr) {
    auto* block = static_cast<Block*>(ctx);
    auto* pool = get_thread_local_pool();  // 获取线程本地池
    
    std::lock_guard<std::mutex> lock(pool->mutex_);
    
    block->is_free = true;
    pool->current_allocated_ -= block->size;
    
    // 将块放回合适的桶中
    size_t bucket = size_to_bucket(block->size);
    push_free_block(bucket, block);
  }
  
 private:
  // 大小到桶的映射
  size_t size_to_bucket(size_t size) {
    // 使用对数分桶:32, 64, 128, 256, ...
    if (size <= 32) return 0;
    return std::min(static_cast<size_t>(31), 
                   static_cast<size_t>(32 - __builtin_clzll(size - 1)));
  }
  
  // 从桶中弹出合适的块
  Block* pop_free_block(size_t bucket, size_t requested_size) {
    // 先从精确匹配的桶开始查找
    for (size_t b = bucket; b < kNumBuckets; ++b) {
      Block* current = free_lists_[b];
      Block* prev = nullptr;
      
      while (current) {
        if (current->size >= requested_size) {
          // 找到合适的块,从链表中移除
          if (prev) {
            prev->next = current->next;
          } else {
            free_lists_[b] = current->next;
          }
          if (current->next) {
            current->next->prev = prev;
          }
          return current;
        }
        prev = current;
        current = current->next;
      }
    }
    return nullptr;
  }
  
  // 将块放入桶中
  void push_free_block(size_t bucket, Block* block) {
    block->next = free_lists_[bucket];
    block->prev = nullptr;
    if (free_lists_[bucket]) {
      free_lists_[bucket]->prev = block;
    }
    free_lists_[bucket] = block;
  }
};

} // namespace c10

8. 张量序列化与反序列化

8.1 序列化机制

PyTorch提供了高效的张量序列化机制:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
namespace torch::serialize {

// 张量序列化数据结构
struct TensorData {
  std::vector<int64_t> sizes;           // 形状信息
  std::vector<int64_t> strides;         // 步长信息
  c10::ScalarType dtype;                // 数据类型
  c10::Device device;                   // 设备信息
  bool requires_grad;                   // 是否需要梯度
  std::vector<uint8_t> data;            // 原始数据
};

// 序列化张量到字节流
void serialize_tensor(const at::Tensor& tensor, BinaryOutputArchive& archive) {
  // 1. 序列化元数据
  archive(tensor.sizes().vec());
  archive(tensor.strides().vec());
  archive(static_cast<int>(tensor.dtype().toScalarType()));
  archive(static_cast<int>(tensor.device().type()));
  archive(tensor.requires_grad());
  
  // 2. 序列化数据
  auto tensor_cpu = tensor.to(at::kCPU);  // 确保在CPU上
  auto data_ptr = tensor_cpu.data_ptr<uint8_t>();
  auto nbytes = tensor_cpu.nbytes();
  
  archive(nbytes);
  archive.saveBinary(data_ptr, nbytes);
}

// 从字节流反序列化张量
at::Tensor deserialize_tensor(BinaryInputArchive& archive) {
  // 1. 读取元数据
  std::vector<int64_t> sizes, strides;
  int dtype_int, device_type_int;
  bool requires_grad;
  
  archive(sizes);
  archive(strides);  
  archive(dtype_int);
  archive(device_type_int);
  archive(requires_grad);
  
  auto dtype = static_cast<c10::ScalarType>(dtype_int);
  auto device_type = static_cast<c10::DeviceType>(device_type_int);
  
  // 2. 读取数据
  size_t nbytes;
  archive(nbytes);
  
  std::vector<uint8_t> data(nbytes);
  archive.loadBinary(data.data(), nbytes);
  
  // 3. 重建张量
  auto options = at::TensorOptions()
      .dtype(dtype)
      .device(device_type)
      .requires_grad(requires_grad);
  
  auto tensor = at::from_blob(data.data(), sizes, strides, options).clone();
  
  return tensor;
}

} // namespace torch::serialize

9. 性能优化最佳实践

9.1 内存访问优化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 高效的张量操作模式

# 1. 避免不必要的拷贝
# 错误:创建了临时张量
result = tensor1 + tensor2 * tensor3

# 正确:使用原地操作
result = tensor1.clone()
result.add_(tensor2 * tensor3)

# 更好:使用torch.addcmul原地操作
result = tensor1.clone()
torch.addcmul(result, tensor2, tensor3, out=result)

# 2. 利用向量化操作
# 错误:逐元素循环
result = torch.empty_like(tensor)
for i in range(tensor.size(0)):
    for j in range(tensor.size(1)):
        result[i, j] = torch.sin(tensor[i, j])

# 正确:向量化操作
result = torch.sin(tensor)

# 3. 预分配输出张量
# 错误:每次都创建新张量
outputs = []
for input_batch in data_loader:
    output = model(input_batch)
    outputs.append(output)

# 正确:预分配并复用
batch_size = data_loader.batch_size
output_shape = (batch_size, num_classes)
pre_allocated = torch.empty(output_shape)

outputs = []
for input_batch in data_loader:
    model(input_batch, out=pre_allocated)
    outputs.append(pre_allocated.clone())

9.2 内存格式优化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 使用合适的内存格式提升性能
namespace optimization {

// 卷积操作优化:使用NHWC格式
torch::Tensor optimize_conv_input(const torch::Tensor& input) {
  // 检查是否已经是channels_last格式
  if (input.is_contiguous(at::MemoryFormat::ChannelsLast)) {
    return input;
  }
  
  // 转换为channels_last格式(对卷积操作更友好)
  return input.to(at::MemoryFormat::ChannelsLast);
}

// 线性层优化:确保连续内存布局
torch::Tensor optimize_linear_input(const torch::Tensor& input) {
  if (input.is_contiguous()) {
    return input;
  }
  
  // 创建连续版本
  return input.contiguous();
}

// 批处理优化:使用合适的数据类型
torch::Tensor optimize_for_inference(const torch::Tensor& input) {
  // 推理时可以使用半精度浮点
  if (input.dtype() == torch::kFloat32) {
    return input.to(torch::kFloat16);
  }
  
  return input;
}

} // namespace optimization

10. 调试和性能分析

10.1 内存使用分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import torch
import torch.profiler

# 内存使用监控
def analyze_memory_usage():
    """分析张量内存使用模式"""
    
    # 1. 基本内存信息
    if torch.cuda.is_available():
        print(f"GPU总内存: {torch.cuda.get_device_properties(0).total_memory / 1e9:.2f} GB")
        print(f"GPU已分配: {torch.cuda.memory_allocated() / 1e9:.2f} GB") 
        print(f"GPU缓存: {torch.cuda.memory_reserved() / 1e9:.2f} GB")
    
    # 2. 详细内存统计
    if torch.cuda.is_available():
        print(torch.cuda.memory_summary())
    
    # 3. 内存泄漏检测
    torch.cuda.memory._record_memory_history(enabled=True)
    
    # ... 执行可能泄漏的代码 ...
    
    # 输出内存历史
    torch.cuda.memory._dump_snapshot("memory_snapshot.pickle")

# 性能profiling
def profile_tensor_operations():
    """性能分析张量操作"""
    
    def model_step():
        x = torch.randn(1024, 1024, device='cuda')
        y = torch.randn(1024, 1024, device='cuda')
        
        # 矩阵乘法
        z = torch.mm(x, y)
        
        # 元素级操作
        w = torch.relu(z)
        
        # 规约操作
        result = torch.sum(w)
        
        return result
    
    # 使用PyTorch Profiler
    with torch.profiler.profile(
        activities=[
            torch.profiler.ProfilerActivity.CPU,
            torch.profiler.ProfilerActivity.CUDA,
        ],
        record_shapes=True,
        profile_memory=True,
        with_stack=True
    ) as prof:
        for _ in range(10):
            model_step()
    
    # 输出性能报告
    print(prof.key_averages().table(sort_by="cuda_time_total", row_limit=10))
    
    # 导出Chrome跟踪文件
    prof.export_chrome_trace("trace.json")

10.2 张量诊断工具

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
namespace torch::debug {

// 张量诊断信息
struct TensorDiagnostics {
  // 基本信息
  std::vector<int64_t> sizes;
  std::vector<int64_t> strides;
  c10::ScalarType dtype;
  c10::Device device;
  bool is_contiguous;
  bool requires_grad;
  
  // 内存信息
  size_t element_size;
  size_t storage_nbytes;
  size_t tensor_nbytes;
  void* data_ptr;
  int storage_use_count;
  
  // 计算图信息
  bool has_grad_fn;
  std::string grad_fn_name;
  int grad_fn_next_edges;
};

// 生成诊断报告
TensorDiagnostics diagnose_tensor(const at::Tensor& tensor) {
  TensorDiagnostics diag;
  
  // 基本信息
  diag.sizes = tensor.sizes().vec();
  diag.strides = tensor.strides().vec();
  diag.dtype = tensor.dtype().toScalarType();
  diag.device = tensor.device();
  diag.is_contiguous = tensor.is_contiguous();
  diag.requires_grad = tensor.requires_grad();
  
  // 内存信息
  diag.element_size = tensor.element_size();
  diag.storage_nbytes = tensor.storage().nbytes();
  diag.tensor_nbytes = tensor.numel() * tensor.element_size();
  diag.data_ptr = tensor.data_ptr();
  diag.storage_use_count = tensor.storage().use_count();
  
  // 自动微分信息
  if (auto grad_fn = tensor.grad_fn()) {
    diag.has_grad_fn = true;
    diag.grad_fn_name = grad_fn->name();
    diag.grad_fn_next_edges = grad_fn->next_edges().size();
  } else {
    diag.has_grad_fn = false;
  }
  
  return diag;
}

// 打印诊断信息
void print_tensor_diagnostics(const at::Tensor& tensor) {
  auto diag = diagnose_tensor(tensor);
  
  std::cout << "=== Tensor Diagnostics ===\n";
  std::cout << "Shape: [";
  for (size_t i = 0; i < diag.sizes.size(); ++i) {
    if (i > 0) std::cout << ", ";
    std::cout << diag.sizes[i];
  }
  std::cout << "]\n";
  
  std::cout << "Strides: [";
  for (size_t i = 0; i < diag.strides.size(); ++i) {
    if (i > 0) std::cout << ", ";
    std::cout << diag.strides[i];
  }
  std::cout << "]\n";
  
  std::cout << "Dtype: " << c10::toString(diag.dtype) << "\n";
  std::cout << "Device: " << diag.device << "\n";
  std::cout << "Contiguous: " << (diag.is_contiguous ? "Yes" : "No") << "\n";
  std::cout << "Requires grad: " << (diag.requires_grad ? "Yes" : "No") << "\n";
  
  std::cout << "Element size: " << diag.element_size << " bytes\n";
  std::cout << "Storage size: " << diag.storage_nbytes << " bytes\n";
  std::cout << "Tensor size: " << diag.tensor_nbytes << " bytes\n";
  std::cout << "Data pointer: " << diag.data_ptr << "\n";
  std::cout << "Storage refs: " << diag.storage_use_count << "\n";
  
  if (diag.has_grad_fn) {
    std::cout << "Grad function: " << diag.grad_fn_name << "\n";
    std::cout << "Next edges: " << diag.grad_fn_next_edges << "\n";
  }
  
  std::cout << "========================\n";
}

} // namespace torch::debug

总结

PyTorch的Tensor系统通过精心设计的多层架构,实现了高性能和易用性的完美平衡。从底层的Storage存储管理到高层的Python接口,每一个组件都承担着明确的职责:

核心优势

  1. 高效的内存管理: 引用计数、写时拷贝、内存池等技术确保内存使用的高效性
  2. 灵活的视图机制: 零拷贝的视图操作支持各种张量变换,提升操作效率
  3. 优化的数据结构: SizesAndStrides等数据结构针对小张量进行优化,减少内存分配
  4. 设备透明性: 统一的接口支持CPU、CUDA等多种后端,用户无需关心底层细节
  5. 类型安全: TensorAccessor等工具提供类型安全的元素访问方式

设计哲学体现

  • 性能优先: 大量的优化技术确保张量操作的高效执行
  • 用户友好: Python接口隐藏复杂性,提供直观的操作方式
  • 可扩展性: 模块化设计支持自定义分配器、数据类型等扩展
  • 调试支持: 丰富的诊断和分析工具帮助开发者优化代码

通过深入理解Tensor的实现机制,我们能够更好地使用PyTorch进行深度学习开发,编写出高效、可靠的代码。同时,这些设计思想也为我们自己开发高性能计算库提供了宝贵的参考。


创建时间: 2025年01月06日

本文由 tommie blog 原创发布