More than code

More Than Code
The efficiency of your iteration of reading, practicing and thinking decides your understanding of the world.
  1. 首页
  2. 未分类
  3. 正文

Torch Tensor & Storage

2026年6月22日 55点热度 0人点赞 0条评论

简单看看tensor/stoage相关的实现,为后面的autograd相关的做一下准备

StorageImpl

StorageImpl = 一块连续内存 buffer 的所有者。它只关心三件事:内存在哪(data_ptr_)、有多大(size_bytes_)、谁来分配/释放(allocator_)

  • stride,shape,dtype相关的则是由TensorImpl来负责

注释中:

// NB: storage is supposed to uniquely own a data pointer; e.g.,
// two non-null data pointers alias if and only if they are from
// the same storage.

即data_ptr_相同,那么storage一定是相同的。storage唯一拥有底层的data ptr
在python层,会根据storage是否相同来判断是否是同一份数据。
Version counting也是发生在storage层。所以这个storage层更像是一个功能更多的data buffer

实现上,继承自intrusive_ptr_target,是一个share ptr,用来把share ptr的控制块inline进去

关键的成员变量:

```C++
DataPtr data_ptr_; // 内存本体(指针 + deleter + device)
SymInt size_bytes_; // 字节数(可能是符号)
bool size_bytes_is_heap_allocated_; // size 是不是符号
bool resizable_;
Allocator* allocator_; // 怎么分配/释放
impl::PyObjectSlot pyobj_slot_; // 关联的 Python 对象

<pre><code class="line-numbers">- 存储数据的buffer就是data ptr,以及对应的size\_bytes\_了。因为这一层没有dtype等,所以都是按照bytes来计算的空间

- size\_bytes\_支持SymInt,里面是一个union,可以是一个i64的size,也可以是一个指针,指向一个SymNode。这个等compile的时候再详细研究

- resizable和allocator有一个联动,如果是resizable的话,需要allocator存在,才能通过allocator进行resize
- 当然也需要保证resizable\_为true才行,比如share memory这种,resizable就是false,因为已经可能share给其他人了

- pyobj\_slot\_对应每一个storage impl一个,python层访问Storage对象的时候应该访问的就是这里。

<br>

COW,校验相关

```C++
bool has_mutable_data_ptr_check_; // 慢路径汇总开关
bool throw_on_mutable_data_ptr_;
bool throw_on_immutable_data_ptr_;
bool warn_deprecated_on_mutable_data_ptr_;
MaterializeFn materialize_fn_; // COW 钩子

代码中有几个地方的校验,并不是常态开启的,手动设置后才会开启,应该是对应一些错误的链路。

COW

然后来看下这里COW是怎么做的
cow主要是用来做lazy clone,只有当修改新的tensor的时候才执行拷贝。

api在COW.h的lazy_clone_storage上,主要是通过这个例子来看一下stoage impl中的materialize_fn_是怎么使用的

设计的核心思路就是把原本的data ptr换成一个带引用计数的data ptr。这样就会出现两个storage共享同一个data ptr的情况。此时删除一个storage,另一个还会持有对应的数据。
对应的deleter则也是通过COW这里wrap了一层,保证引用计数为0的时候才能释放

Materialize fn是materialize_cow,会直接操作StorageImpl,修改其中的data ptr。这里分两种情况:

  • 如果refcnt为1,表示当前storage是最后一个引用者,直接把data ptr移动走即可,不需要拷贝

  • 否则的话会通过allocator->clone(),拷贝+复制内存,放到新的data ptr上。

对应到StorageImpl中,就是在调用mutable_ptr的时候,涉及到对数据的修改,就需要触发这里的materialize,复制一份新的出来。就完成了COW

使用的话,主要是torch._lazy_clone(t),好像看起来用的不多。应该是因为平常没有这种复杂的用法,基本上一个tensor变化完了老的tensor就释放掉了。

TensorImpl

tensor impl的几个作用:

  • 负责管如何看待一块数据buffer,处理shape/stride/dtype等信息

  • autograd相关,autograd相关的元信息存在这一层

  • Dispatch key set,用于做dispatch

  • 还有一些自定义逻辑的处理,custom layout等,这块暂时先不看

先看解释数据相关的,核心的成员:

成员 含义
Storage storage_ 指向底层数据
SizesAndStrides sizes_and_strides_ sizes 和 strides 打包在一起 |\
注意这里的单位是element,而非bytes
int64_t storage_offset_ 在 storage 里的起始偏移(元素数,非字节)
int64_t numel_ 元素总数 = sizes 连乘,冗余缓存,避免每次重算
caffe2::TypeMeta data_type_ dtype |\
是一个uint16
std::optional<Device> device_opt_ device |\
包含一个device type |\
和一个device index

SizesAndStrides是把size和stride打包到一起,对应每一个维度的size和stride。有一个inline的优化类似inline vector一样。就是维度小于5的时候,size和stride会inline到对象里面,大于5的时候会单独分配一个buffer出来保存这个数组。

autograd相关的字段:

```C++
// This pointer points to an AutogradMeta struct that stores autograd-specific
// fields (such as grad_ / grad_fn_ / grad_accumulator_)...
std::unique_ptr autograd_meta_ = nullptr;
c10::VariableVersion version_counter_;

- `autograd_meta_`:存 `grad_` / `grad_fn_` 等。优化点:不需要梯度的张量这里是 `nullptr`,省内存。

- `version_counter_`它和 storage 共享版本号,in-place 会 bump 它。
- 用来给autograd使用,追踪是否发生了inplace的修改,避免影响autograd的语意

- 在view/reshape等view类操作的时候,base tensor和新的tensor会共享version\_counter\_,里面是一个share\_ptr<atomic int>。

<br>

看一下这里version counter的bump链路:

```Python
x.add_(1)
│ 经 Dispatcher 按 DispatchKeySet 分发,先命中 ADInplaceOrView key
▼
ADInplaceOrView::add_ ← 代码生成的 kernel(见下)
│ ① 设 guard,redispatch 到真正改数据的 kernel(CPU/CUDA)
│ ② increment_version(self)
▼
torch::autograd::increment_version(self) # VariableTypeUtils.h:158
▼
torch::autograd::impl::bump_version(self) # variable.cpp:363
▼
self.unsafeGetTensorImpl()->bump_version() # TensorImpl.h:2224
▼
version_counter_.bump() # TensorImpl.h:2225
▼
++version_counter_->version_ (atomic<uint32_t>) # TensorImpl.h:408-417
</code></pre>

在torch/csrc/autograd/generated/ADInplaceOrViewTypeEverything.cpp中有生成好的kernel代码,编译就可以看到:

```C++
at::Tensor & add__Tensor(c10::DispatchKeySet ks, at::Tensor & self, const at::Tensor & other, const at::Scalar & alpha) {
{
at::AutoDispatchBelowADInplaceOrView guard;
at::_ops::add__Tensor::redispatch(ks & c10::after_ADInplaceOrView_keyset, self, other, alpha);
}
increment_version(self);
return self;
}
```

可以看到这里是先redispatch,把InplaceOrView这个dispatch key去掉了,进入到下一层的计算中。
计算结束后做increment_version,就是到上面的调用链路图,最后是bump version

还有一个需要注意的点是单位:

  • size/stride的单位是element

  • storage相关的是bytes

所以as_strided/view(dtype)的接口是分开的,因为dtype的变化影响的是stride/size如何parse到对应的storage offset。

标签: 暂无
最后更新:2026年6月22日

sheep

think again

点赞
< 上一篇
下一篇 >

文章评论

取消回复

COPYRIGHT © 2021 heavensheep.xyz. ALL RIGHTS RESERVED.

THEME KRATOS MADE BY VTROIS