介绍
张量的 .contiguous() 方法,是解决 张量内存不连续 问题的核心
连续张量和非连续张量
首先要理解张量在内存中的存储逻辑:
- PyTorch 张量的元素在内存中是一维线性存储的,“维度”只是对这段线性内存的“解读方式”;
- 连续张量:元素的内存顺序与按维度遍历(如按行)的顺序完全一致(比如
[2,3]的张量,内存中是1,2,3,4,5,6,按行遍历也是这个顺序); - 非连续张量:元素的内存顺序与维度遍历顺序不一致(如转置后的张量,维度遍历是
1,4,2,5,3,6,但内存中还是1,2,3,4,5,6)。
.contiguous() 作用
返回一个与原张量 数据相同、维度相同,但内存连续 的新张量(若原张量已连续,则返回自身,无额外拷贝)。
关键特征
- 若原张量已连续:返回原张量(无拷贝,高效);
- 若原张量非连续:创建新的连续内存副本(数据与原张量一致,不再共享内存)。
语法
Tensor.contiguous(memory_format=torch.contiguous_format) → Tensor
参数:
memory_format可选,默认torch.contiguous_format(行优先连续),还支持torch.channels_last(通道优先,用于CV优化);
例子
结合 .transpose() 和 .view(),演示 .contiguous() 的必要性:
import torch
# 1. 初始张量:连续的
x = torch.tensor([[1, 2, 3],
[4, 5, 6]])
print("原张量是否连续:", x.is_contiguous()) # True
print("原张量内存顺序(展平):", x.view(-1)) # [1,2,3,4,5,6]
# 2. 转置后:非连续张量
x_trans = x.transpose(0, 1)
print("\n转置后是否连续:", x_trans.is_contiguous()) # False
# x_trans.view(-1) # 直接调用view会报错!报错信息:RuntimeError: view size is not compatible with input tensor's size and stride
# 3. 用.contiguous()整理内存:变为连续张量
x_trans_contig = x_trans.contiguous()
print("contiguous后是否连续:", x_trans_contig.is_contiguous()) # True
print("contiguous后view展平:", x_trans_contig.view(-1)) # [1,4,2,5,3,6]
# 4. 内存拷贝验证:contiguous()后不再共享内存
x_trans_contig[0, 0] = 100
print("\n修改contiguous后的张量:")
print(x_trans_contig) # [[100,4],[2,5],[3,6]]
print("原转置张量(未变):")
print(x_trans) # [[1,4],[2,5],[3,6]]
print("原始张量(未变):")
print(x) # [[1,2,3],[4,5,6]]
# 5. 已连续张量调用contiguous():返回自身(无拷贝)
x_contig = x.contiguous()
print("\n原张量与contiguous后张量是否同一对象:", x is x_contig) # True
输出核心特征:
- 转置后的张量非连续,直接用
.view()会报错,必须先.contiguous(); .contiguous()会创建内存副本(非连续张量时),新张量与原张量不再共享内存;- 已连续的张量调用
.contiguous()无开销,直接返回自身。
为什么需要 .contiguous()
PyTorch 的很多核心操作(如 .view()、某些算子)要求张量内存连续,原因:
.view()的底层限制:.view()只是“重新解读”内存,无法处理内存顺序与维度遍历顺序不一致的情况,必须依赖连续内存才能正确重塑形状;- 性能优化:连续张量的内存访问是线性的,速度远快于非连续张量(非连续张量会跳着读取内存,触发缓存未命中);
- 算子兼容性:部分 CUDA 算子/底层优化仅支持连续张量,非连续张量会导致报错或性能下降。
常见触发张量非连续的操作
以下操作会让张量从连续变为非连续,后续若用 .view() 需先 .contiguous():
- 维度交换:
.transpose()、.permute(); - 切片/步长:
x[:, ::2](隔列取数)、x[1:]; - 转置相关:
.t()、.mT; - 其他:
.unfold()、某些 padding 操作。
什么时候用
不用无脑加,遵循以下原则即可:
- 必加场景:对非连续张量调用
.view()前(可先用x.is_contiguous()判断); - 建议加场景:对非连续张量做大量计算前(提升内存访问效率);
- 无需加场景:
- 张量本身已连续(如刚创建的张量、
.clone()后的张量); - 仅做简单运算(如加减乘除),无需重塑形状。
- 张量本身已连续(如刚创建的张量、
总结
.contiguous()用于将非连续张量转为内存连续的新张量,是.view()处理非连续张量的必要前置操作;- 非连续张量通常由
.transpose()/.permute()/切片等操作触发,核心特征是内存顺序与维度遍历顺序不一致; .contiguous()仅在张量非连续时创建内存副本,连续时无开销,使用时优先判断is_contiguous()可避免不必要的拷贝。