pytorch api文档:张量的 .contiguous()方法-返回内存连续的新张量 作者:马育民 • 2026-01-21 19:07 • 阅读:10001 # 介绍 张量的 `.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()` 的必要性: ```python 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()`、某些算子)要求张量内存连续,原因: 1. **`.view()` 的底层限制**:`.view()` 只是“重新解读”内存,无法处理内存顺序与维度遍历顺序不一致的情况,必须依赖连续内存才能正确重塑形状; 2. **性能优化**:连续张量的内存访问是线性的,速度远快于非连续张量(非连续张量会跳着读取内存,触发缓存未命中); 3. **算子兼容性**:部分 CUDA 算子/底层优化仅支持连续张量,非连续张量会导致报错或性能下降。 ### 常见触发张量非连续的操作 以下操作会让张量从连续变为非连续,后续若用 `.view()` 需先 `.contiguous()`: - 维度交换:`.transpose()`、`.permute()`; - 切片/步长:`x[:, ::2]`(隔列取数)、`x[1:]`; - 转置相关:`.t()`、`.mT`; - 其他:`.unfold()`、某些 padding 操作。 ### 什么时候用 不用无脑加,遵循以下原则即可: 1. **必加场景**:对非连续张量调用 `.view()` 前(可先用 `x.is_contiguous()` 判断); 2. **建议加场景**:对非连续张量做大量计算前(提升内存访问效率); 3. **无需加场景**: - 张量本身已连续(如刚创建的张量、`.clone()` 后的张量); - 仅做简单运算(如加减乘除),无需重塑形状。 # 总结 1. `.contiguous()` 用于将非连续张量转为**内存连续**的新张量,是 `.view()` 处理非连续张量的必要前置操作; 2. 非连续张量通常由 `.transpose()`/`.permute()`/切片等操作触发,核心特征是内存顺序与维度遍历顺序不一致; 3. `.contiguous()` 仅在张量非连续时创建内存副本,连续时无开销,使用时优先判断 `is_contiguous()` 可避免不必要的拷贝。 原文出处:http://www.malaoshi.top/show_1GW2dNGybasI.html