尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

PyTorch Storage 深度指南:从 UntypedStorage 到 TypedStorage 的存储体系全解析

PyTorch Storage 深度指南:从 UntypedStorage 到 TypedStorage 的存储体系全解析 PyTorch Storage 深度指南从 UntypedStorage 到 TypedStorage 的存储体系全解析【免费下载链接】pytorchTensors and Dynamic neural networks in Python with strong GPU acceleration项目地址: https://gitcode.com/GitHub_Trending/py/pytorch导读本文基于 PyTorch 官方文档 docs/source/storage.md 与仓库源码 torch/storage.py系统讲解 PyTorch 中 Tensor 底层存储Storage的核心概念与使用方式。你将理解 Tensor 的五个组成要素Storage、dtype、shape、stride、offset如何协同定义数据布局掌握UntypedStorage这一现代推荐的存储 API 的访问、共享与修改方式并理清已废弃的TypedStorage系列类的历史沿革与替换路径。本文面向希望深入理解 PyTorch 内存模型、序列化行为与视图机制的开发者。Tensor 的五要素Storage 与元数据的关系PyTorch 中的一个常规 Tensor 是结构 数据的统一体由以下五个组成部分共同定义组成要素说明StorageTensor 的实际数据以连续的、一维的字节数组形式存放dtype张量元素的数据类型如torch.float32、torch.int64shape元组表示张量在每个维度上的大小stride在每一维度上从一个元素移动到下一个元素所需的步长offset张量数据在 storage 中的起始位置新创建的张量通常为 0这些要素的分工非常明确Storage 保存真正的内容数据其余四者全部属于描述数据如何被解释的元数据。dtype决定每个元素占用多少字节例如torch.float32为 4 字节shape与stride决定多维逻辑索引如何映射到一维物理偏移而offset则允许同一个 Storage 的不同区域被不同张量复用。从源码结构看这一Storage 承载字节、元数据解释字节的设计贯穿始终在 torch/storage.py 中_StorageBase同时实现了size()返回nbytes()、element_size()、data_ptr()等底层接口见 torch/storage.py而 torch/_tensor.py 中的Tensor.storage()与Tensor._typed_storage()则负责把张量与存储绑定起来。Untyped Storage API现代推荐的存储接口什么是 UntypedStoragetorch.UntypedStorage是一个连续的、一维的元素数组它的长度等于张量所占的字节数因此它不关心也不记录任何数据类型信息——未类型化即由此而来。它是 Tensor 的底层数据容器。在 PyTorch 中使用常规构造器创建的张量例如torch.zeros、torch.zeros_like或torch.Tensor.new_zeros通常都会产生张量与存储一一对应的结果张量有多大其 storage 就有多少字节二者一一对应。共享 Storage视图的本质然而一个 Storage 是允许被多个 Tensor 共享的。例如通过Tensor.view得到的视图通过部分而非全部索引方式如整数索引、切片得到的子张量这些视图与原始张量指向同一个底层 Storage。这正是 PyTorch 视图机制高效的根本原因——视图不复制数据只复制元数据shape、stride、offset。这一共享关系在序列化torch.save/torch.load过程中会被完整保留序列化和反序列化共享同一个 Storage 的多个张量时还原后的张量仍然指向同一个 Storage。文档还给出一个有趣的性能结论反序列化多个指向同一 Storage 的张量比反序列化多个相互独立的张量更快因为共享的数据只需要写入一次。访问 Storageuntyped_storage() 与 data_ptr()张量的 Storage 通过Tensor.untyped_storage()方法访问返回类型为torch.UntypedStorage。识别存储是否共享的关键手段是UntypedStorage.data_ptr()方法——它是 Storage 的唯一标识符。在常规场景下两个数据共享同一个 Storage 的张量其storage.data_ptr()相同。但需要注意两个例外Tensor 的 data 与 grad 各自持有独立的 Storage一个带grad的张量实际包含两块数据每一块都需要自己的data_ptr()不保证Tensor.data_ptr()与UntypedStorage.data_ptr()相等——这是一个常见误区二者是两个独立对象的指针不能假设它们一致。在存储级别改变 Tensor 的数据Untyped Storage 与构建在其上的 Tensor 相对独立。实际含义是不同 dtype、不同 shape 的 Tensor 可以指向同一个 Storage而且一个 Tensor 的底层 Storage 可以被整体替换。文档给出了如下示例 t torch.ones(3) s0 t.untyped_storage() s0 0 0 128 63 0 0 128 63 0 0 128 63 [torch.storage.UntypedStorage(devicecpu) of size 12] s1 s0.clone() s1.fill_(0) 0 0 0 0 0 0 0 0 0 0 0 0 [torch.storage.UntypedStorage(devicecpu) of size 12] # Fill the tensor with a zeroed storage t.set_(s1, storage_offsett.storage_offset(), stridet.stride(), sizet.size()) tensor([0., 0., 0.])逐行解读这个示例torch.ones(3)产生的 float32 张量占用 12 字节3 个元素 × 4 字节因此untyped_storage()打印出 12 个字节128 63正是 float32 值 1.0 的小端字节表示。随后克隆 Storage 并用fill_(0)清零最后通过Tensor.set_()把新 Storage 连同原有的 offset、stride、size 一起挂接到t上张量内容随即变为全 0。警告如上所示直接修改 Tensor 的 Storage不是推荐做法。这种底层操作仅用于教学演示帮助理解张量与存储之间的关系。在实际代码中使用标准的Tensor.clone()、Tensor.fill_()等方法更安全、更高效。Storage 操作属于 low-level API使用有风险并且 Tensor 层面已有的对应 API 应优先于 Storage 层面的同名 API 使用。UntypedStorage 的常用属性与方法除data_ptr之外UntypedStorage 还提供以下常用属性从源码 torch/storage.py 与_StorageBase基类可以确认成员说明filename若 Storage 指向磁盘上的文件即通过from_file(sharedTrue)创建返回文件名字符串否则返回None见 torch/storage.pydevice返回 Storage 所在的torch.deviceis_cuda判断 Storage 是否位于 CUDA 设备device.type cudacopy_就地拷贝数据fill_就地填充数据pin_memory将 CPU Storage 转移到锁页内存pinned memory用于加速主机到 GPU 的异步拷贝clone返回副本内部实现为type(self)(self.nbytes(), deviceself.device).copy_(self)见 torch/storage.pytolist返回包含 Storage 元素的 Python 列表cpu/cuda/mps/hpu设备迁移方法返回对应设备上的副本nbytes/size/element_size字节数、元素数、单元素字节数resize_就地调整 Storage 大小from_file类方法从文件创建存储sharedTrue时使用 mmap 共享映射值得一提的底层细节是share_memory_与from_file(sharedTrue)的区别源码文档注释有明确说明见 torch/storage.pyshare_memory_使用 POSIXshm_open(3)创建共享内存对象并在 mmap 后调用shm_unlink(3)确保无进程引用时自动释放from_file(sharedTrue)使用open(2)打开用户指定文件并 mmap不会 unlink 文件文件持久存在直到用户主动删除两者都通过mmap(2)MAP_SHARED把文件/对象映射进虚拟地址空间。共享内存中的 Storage不能被 resize。特殊场景grad 的 Storage、meta 设备与 FakeTensorTensor 与梯度各自持有 Storage前面提到带有非None的grad属性的 Tensor 实际上包含两块数据。此时Tensor.untyped_storage()返回的是Tensor.data属性的 Storage梯度的 Storage 需要通过tensor.grad.untyped_storage()获取。 t torch.zeros(3, requires_gradTrue) t.sum().backward() assert list(t.untyped_storage()) [0] * 12 # the storage of the tensor is just 0s assert list(t.grad.untyped_storage()) ! [0] * 12 # the storage of the gradient isnt这个断言示例验证了张量本体的 Storage 仍为全 0而反向传播后梯度 Storage 中已写入非零的梯度值。没有常规 Storage 的张量文档明确指出存在两类特殊张量它们没有典型意义上的 Storage甚至完全没有 Storagemeta设备上的张量用于形状推断shape inference不持有真实数据。在 torch/storage.py 中可以看到UntypedStorage.__getitem__对 meta 设备直接抛出NotImplementedError(Not available for meta device type)其__repr__也只显示省略号加元信息。FakeTensorPyTorch 编译器如 torch.compile / torch.fx内部使用的工具思想与 meta 设备类似只携带元数据不携带实际数据用于在不执行真实计算的前提下进行图分析。此外Tensor 子类subclass或类张量对象也可能表现出异常行为。文档强调一般场景并不需要开发者直接在 Storage 层面操作——绝大多数情况下Tensor 层的 API 已经足够且更安全。遗留的 Typed Storage API历史沿革与迁移路径警告出于历史原因PyTorch 曾经使用类型化存储类typed storage它们现在已被弃用、应当避免使用。以下内容仅为帮助读者在遇到旧代码时能够理解。除torch.UntypedStorage之外的所有存储类都将在未来被移除届时torch.UntypedStorage将是唯一保留的存储类。torch.Storage 是默认 dtype 的别名torch.Storage是与默认数据类型对应的存储类的别名。例如当默认数据类型为torch.float时由torch.get_default_dtype()决定torch.Storage解析为torch.FloatStorage。Storage 类从未真正实例化的门面torch.typeStorage与torch.cuda.typeStorage系列类如torch.FloatStorage、torch.IntStorage等实际上永远不会被直接实例化。调用它们的构造函数时实际创建的是一个携带对应torch.dtype与torch.device的torch.TypedStorage对象。从源码 torch/storage.py 可以看到这些遗留类的__new__会校验参数组合无参 /(int size)/(Sequence data)/(*, UntypedStorage wrap_storage)通过模块名如torch.cuda推断设备见_get_device_from_moduletorch/storage.py然后内部委托给TypedStorage。它们拥有与TypedStorage相同的全部类方法。TypedStorage 的结构UntypedStorage dtypetorch.TypedStorage是一个连续的、一维的、元素类型为特定torch.dtype的数组。它可以使用任意torch.dtype内部数据会被按照该 dtype 恰当解释。关键的结构事实是TypedStorage内部包含一个UntypedStorage数据以未类型化的字节数组形式存放在后者中构造时通过wrap_storage包装见 torch/storage.py每一个 strided 的torch.Tensor都包含一个TypedStorage其中存放了该张量视图view的全部数据新 dtype 的存储不再映射到遗留typeStorage类而是直接序列化为UntypedStoragetorch.dtype的组合。源码中的_dtype_to_storage_type_maptorch/storage.py注释明确说明该映射仅用于与旧版本 PyTorch 的向后/向前兼容BC/FC不再向其中添加新的 dtype。遗留 API 的弃用信号使用TypedStorage相关 API 会触发弃用警告UserWarning提示文本建议To access UntypedStorage directly, use tensor.untyped_storage() instead of tensor.storage()见 torch/storage.py。对应地在 torch/_tensor.py 中Tensor.storage()的 docstring 也标注了同样的弃用说明并建议改用Tensor.untyped_storage()内部的Tensor._typed_storage()仅用于避免在内部路径重复触发警告。遗留 Storage 类的完整清单文档末尾通过 autodoc 列出了所有遗留的typeStorage类可作为排查旧代码时的参照存储类对应 dtypetorch.DoubleStoragetorch.doubletorch.FloatStoragetorch.floattorch.HalfStoragetorch.halftorch.LongStoragetorch.longtorch.IntStoragetorch.inttorch.ShortStoragetorch.int16torch.CharStoragetorch.int8torch.ByteStoragetorch.uint8torch.BoolStoragetorch.booltorch.BFloat16Storagetorch.bfloat16torch.ComplexDoubleStoragetorch.cdoubletorch.ComplexFloatStoragetorch.cfloattorch.QUInt8Storagetorch.quint8torch.QInt8Storagetorch.qint8torch.QInt32Storagetorch.qint32torch.QUInt4x2Storagetorch.quint4x2torch.QUInt2x4Storagetorch.quint2x4完整的 dtype 到存储类映射关系可以在 torch/storage.py 的_dtype_to_storage_type_map中逐一核对。实践建议与总结结合文档与源码可以给出如下实践准则优先使用 Tensor 层 API创建、复制、填充数据应使用torch.zeros、Tensor.clone、Tensor.fill_等标准方法避免直接操作 Storage只读场景下使用untyped_storage()当需要理解张量的内存布局、判断两个张量是否共享底层数据时通过Tensor.untyped_storage()与UntypedStorage.data_ptr()进行只读检查是安全且推荐的用法正视共享语义理解 view、切片共享 Storage 的行为能帮助你预判序列化共享关系会被保留且反序列化更快与原地操作可能意外修改共享数据的其他视图的行为避免任何TypedStorage/typeStorageAPI它们已弃用并将在未来移除迁移到UntypedStorage是唯一可持续的方向警惕 meta 设备与 FakeTensor它们没有真实数据任何依赖实际内容的 Storage 操作在编译与形状推断场景下都会失效。理解 PyTorch 的存储体系本质上是理解字节与解释的分离UntypedStorage是纯粹的字节容器而 dtype、shape、stride、offset 决定了这些字节如何被组织成有意义的张量。掌握这一模型将帮助你在内存优化、自定义算子、序列化调试与编译器技术栈如 torch.compile中做出更准确的判断。【免费下载链接】pytorchTensors and Dynamic neural networks in Python with strong GPU acceleration项目地址: https://gitcode.com/GitHub_Trending/py/pytorch创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表