介绍
张量的 .stride() 方法,是理解张量内存布局(连续/非连续)的“底层钥匙”——直接告诉我们:遍历张量时,每跳过一个维度的元素,需要在内存中移动多少步(元素个数)。
作用
返回一个元组,元组中第 i 个元素表示“遍历张量第 i 维时,每移动一个位置,需要在内存中跳过的元素数量”(步长)。
元组长度 = 张量的维度数(比如2维张量返回2个值,3维返回3个值);
步长的单位是“元素个数”(不是字节),PyTorch 会自动根据元素类型(如 float32=4字节)换算成实际内存字节数;
- 该方法是只读检测,不修改张量,无任何内存开销。
语法
Tensor.stride() → Tuple[int, ...]
例子
# 2维张量:shape [2, 3],内存中存储为 [1,2,3,4,5,6]
x = torch.tensor([[1, 2, 3],
[4, 5, 6]])
print("张量shape:", x.shape) # (2, 3)
print("张量stride:", x.stride()) # (3, 1)
对 stride=(3,1) 的解读:
维度1(列维度)步长=1:要从第0列跳到第1列(如1→2),只需在内存中往后跳
1个元素;维度0(行维度)步长=3:要从第0行(
[1,2,3])跳到第1行([4,5,6]),需要在内存中往后跳3个元素(从1→2→3→4,共3步);
这是连续张量的典型步长:满足 前一维步长 = 后一维步长 × 后一维大小,如下:
3 = 1 × 3 // 维度0步长 = 维度1步长 × 维度1大小
例子:步长与张量内存的关系
通过不同场景的示例,直观理解步长的含义,以及它如何决定张量是否连续:
示例1:基础2维张量(连续)
x = torch.tensor([[1, 2, 3], [4, 5, 6]])
print("=== 基础2维连续张量 ===")
print("shape:", x.shape) # (2, 3)
print("stride:", x.stride()) # (3, 1)
print("是否连续:", x.is_contiguous()) # True
# 验证遍历逻辑:按维度遍历的每个元素,对应内存中的位置
# 遍历顺序:(0,0)→(0,1)→(0,2)→(1,0)→(1,1)→(1,2)
# 内存位置:0→1→2→3→4→5(步长3和1完美匹配)
indices = [(0,0), (0,1), (0,2), (1,0), (1,1), (1,2)]
memory_pos = [i * x.stride()[0] + j * x.stride()[1] for i,j in indices]
print("遍历元素的内存位置:", memory_pos) # [0,1,2,3,4,5]
示例2:转置后的张量(非连续)
x_trans = x.transpose(0, 1) # 交换行和列,shape变为(3,2)
print("\n=== 转置后的非连续张量 ===")
print("shape:", x_trans.shape) # (3, 2)
print("stride:", x_trans.stride()) # (1, 3)
print("是否连续:", x_trans.is_contiguous()) # False
# 验证遍历逻辑:按新维度遍历,内存位置跳着走
# 遍历顺序:(0,0)→(0,1)→(1,0)→(1,1)→(2,0)→(2,1)
# 内存位置:0→3→1→4→2→5(步长1和3导致跳着读)
indices_trans = [(0,0), (0,1), (1,0), (1,1), (2,0), (2,1)]
memory_pos_trans = [i * x_trans.stride()[0] + j * x_trans.stride()[1] for i,j in indices_trans]
print("遍历元素的内存位置:", memory_pos_trans) # [0,3,1,4,2,5]
示例3:3维张量的步长(连续 vs 非连续)
# 3维连续张量:shape [2,3,4]
x_3d = torch.randn(2, 3, 4)
print("\n=== 3维连续张量 ===")
print("shape:", x_3d.shape) # (2, 3, 4)
print("stride:", x_3d.stride()) # (12, 4, 1)
# 验证连续规则:12=4×3,4=1×4 → 满足
# permute交换维度1和2,变为非连续
x_3d_perm = x_3d.permute(0, 2, 1)
print("\n=== 3维permute后非连续张量 ===")
print("shape:", x_3d_perm.shape) # (2, 4, 3)
print("stride:", x_3d_perm.stride()) # (12, 1, 4)
# 验证连续规则:12≠1×4 → 不满足,非连续
示例4:切片操作导致的步长变化(非连续)
# 隔列取数:步长改变,变为非连续
x_slice = x[:, ::2] # shape (2,2),取第0、2列
print("\n=== 切片后的非连续张量 ===")
print("shape:", x_slice.shape) # (2, 2)
print("stride:", x_slice.stride()) # (3, 2)
print("是否连续:", x_slice.is_contiguous()) # False
应用场景
判断张量是否连续(底层逻辑)
.is_contiguous() 的本质就是检查步长是否满足「连续规则」:
- 对 N 维张量,从最后一维往前推:
第i维步长 = 第i+1维步长 × 第i+1维大小; - 最后一维的步长必须为 1(因为遍历最后一维是逐个元素读)。
比如:
- 连续张量
x.stride()=(3,1):3=1×3,最后一维步长=1 → 满足; - 非连续张量
x_trans.stride()=(1,3):1≠3×2,最后一维步长=3≠1 → 不满足。
解释 .view() 报错的原因
.view() 要求张量内存连续,本质是要求步长符合连续规则——非连续张量的步长混乱,无法按新形状“线性解读”内存,因此报错。
优化内存访问效率
步长越小,内存访问越连续,计算效率越高:
- 连续张量的步长是“紧凑的”(最后一维步长=1,前一维步长=后一维大小×后一维步长);
- 非连续张量的步长会导致“内存跳跃”,缓存命中率低,计算速度慢。
注意
步长与内存共享:
transpose()/permute()/切片等操作只会修改步长(解读规则),不会改变内存中元素的存储顺序,因此和原张量共享内存;.contiguous()会重置步长:非连续张量调用.contiguous()后,会重新排列内存,步长恢复为连续规则(如x_trans.contiguous().stride()变为(2,1));- 步长与张量类型无关:无论 float32、int64 还是 CUDA 张量,步长的计算逻辑完全一致(单位都是“元素个数”,不是字节);
- 空张量/标量的步长:标量(0维张量)的
.stride()返回空元组,空张量的步长根据形状适配(如torch.empty(2,0).stride()返回(0,1))。
总结
.stride()返回张量各维度的遍历步长,是理解张量内存布局的核心指标;- 连续张量的步长满足「前一维步长 = 后一维步长 × 后一维大小」,最后一维步长=1;
- 非连续张量(转置/切片/permute)的步长打破上述规则,导致内存访问跳跃,
.view()报错; - 步长是
.is_contiguous()和.contiguous()的底层判断依据,掌握步长就能彻底理解张量的内存逻辑。