3. Tensor与显存:存储格式、连续性与生命周期
聊到显存优化,Tensor 是绕不开的核心对象。说白了,你训练模型时,显存里装的就是 Tensor。我刚开始做深度学习时,总觉得 Tensor 就是个多维数组,后来踩了不少坑才明白——它的存储方式、内存布局、生命周期,每一个细节都直接影响显存占用。
3.1 Tensor 的存储格式
Tensor 在底层由两部分组成:元数据(形状、步长、数据类型等)和实际数据(存储在连续内存块中)。元数据很小,真正吃显存的是数据部分。
举个例子,一个形状为 (3, 224, 224) 的 float32 Tensor:
import torch
x = torch.randn(3, 224, 224)
print(x.element_size()) # 4 bytes (float32)
print(x.numel()) # 3*224*224 = 150528
print(x.storage().nbytes()) # 150528 * 4 = 602112 bytes
嗯,这里要注意:storage() 才是数据真正存放的地方。Tensor 本身只是一个“视图”,它通过 stride(步长)来索引 storage 中的数据。
核心概念:Tensor 的显存占用 = 元素个数 × 每个元素字节数。但实际中,由于对齐和碎片,可能会略高一些。
我在项目中遇到过一个问题:两个 Tensor 形状相同,但显存占用却不一样。后来发现,其中一个 Tensor 是另一个的切片,它们共享了同一个 storage。这引出了下一个话题——连续与非连续张量。
3.2 连续与非连续张量
连续张量,指的是数据在内存中按行优先顺序紧密排列。非连续张量则相反,数据在内存中可能“跳着”存放。
为什么会这样?你想想看,当你对 Tensor 做转置、切片、部分索引时,PyTorch 并不会真的复制数据,而是创建一个新的“视图”,修改 stride 和 offset。这样省了显存,但带来了非连续的问题。
import torch
x = torch.randn(3, 4)
y = x.T # 转置,非连续
print(x.is_contiguous()) # True
print(y.is_contiguous()) # False
非连续张量有什么影响?
- 运算效率低:很多底层操作(如矩阵乘法)要求输入是连续的,非连续时会触发隐式
contiguous()调用,复制数据。 - 显存可能翻倍:如果你不小心调用了
.contiguous(),PyTorch 会分配一块新内存,把数据拷贝过去。原本共享的 storage 就变成了两份。
避坑指南:我曾经在写一个数据预处理 pipeline 时,反复对 Tensor 做切片和转置,最后调用了 .contiguous(),结果显存直接翻倍。排查了半天才发现是这里的问题。建议:尽量在数据加载阶段就保证连续性,避免在训练循环中频繁触发隐式拷贝。
如何判断一个 Tensor 是否连续?用 is_contiguous()。如何强制连续?用 contiguous()。但记住,contiguous() 只在非连续时才复制,连续时返回自身。
3.3 张量生命周期与显存回收
Tensor 的生命周期,说白了就是它从创建到被销毁的过程。Python 的垃圾回收机制(引用计数 + 循环检测)负责回收不再使用的 Tensor,释放显存。
但这里有个坑:Python 的垃圾回收不是即时的。即使你删除了一个 Tensor 的引用,显存也不会立刻释放,而是等到下一次 GC 循环或显存压力触发时才会回收。
import torch
import gc
x = torch.randn(1000, 1000) # 占用约 4MB
del x # 删除引用
gc.collect() # 手动触发垃圾回收
我个人习惯在关键节点手动调用 gc.collect() 和 torch.cuda.empty_cache()。但要注意,empty_cache() 只是清空缓存,不会释放正在使用的显存。
小技巧:在训练循环中,如果你创建了大量临时 Tensor(比如中间特征图),可以用 with torch.no_grad(): 包裹,避免构建计算图,减少显存占用。同时,尽量复用 Tensor 而不是反复创建新对象。
还有一个容易被忽略的点:计算图中的中间变量。当你执行 loss.backward() 时,PyTorch 会保留所有中间激活值用于反向传播。这些中间变量直到反向传播完成后才会被释放。所以,如果你的网络很深,显存占用会非常大。
我记得有一次训练一个 50 层的 ResNet,显存直接爆了。后来用 checkpoint 技术(梯度检查点),以时间换空间,才把模型塞进 8GB 显存里。
3.4 知识体系总览
下面这张图总结了 Tensor 与显存的核心逻辑:
这张图把三个核心知识点串在了一起。你从存储格式入手,理解 Tensor 到底怎么占显存;然后关注连续性,避免不必要的拷贝;最后管理好生命周期,及时释放不再需要的资源。这三步走下来,显存优化就有了基础。
一句话总结:Tensor 的显存管理,本质上就是搞清楚“数据存在哪、怎么存、什么时候该删”。
想要更多资料,联系可可