cute(1)Layout基础

鱿鱼圈 Lv4

Ref

1 Layout 基础

对应代码:01_layout_basics.cu 纯 host 代码,不需要 GPU。

核心概念

Layout = Shape + Stride,它是一个函数,把多维坐标映射到一维 offset:

1
offset = layout(i, j) = i * stride_0 + j * stride_1
  • Shape:每个维度的大小,如 (4, 3) 表示 4 行 3 列
  • Stride:每个维度走一步对应的 offset 增量

我们直接从代码来理解,先避开硬核的抽象理解。

代码解析

第 1 节:一维 Layout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ============================================================
// 1. 最简单的 Layout:一维
// ============================================================
{
// Shape = 8,Stride = 1
// 含义:8 个元素,相邻元素 offset 差 1
auto layout = make_layout(Int<8>{}); // 等价于 (8):(1)
printf("=== 1D Layout ===\n");
print(layout);
printf("\n");

// 手动验证:layout(i) 返回 offset
for (int i = 0; i < size(layout); ++i) {
printf(" layout(%d) = %d\n", i, int(layout(i)));
}
printf("\n");
}

1
auto layout = make_layout(Int<8>{});  // (8):(1)
  • Shape = 8,Stride = 1(默认)
  • 含义:8 个元素,相邻元素 offset 差 1
  • layout(i) = i * 1 = i,最简单的连续排列
1
2
3
4
layout(0) = 0
layout(1) = 1
...
layout(7) = 7

注意print_layout 只能用于 rank-2(二维)的 layout。一维 layout 只能用 print() 打印文本形式。

第 2 节:二维 Layout —— Row-Major

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ============================================================
// 2. 二维 Layout:row-major
// ============================================================
{
// Shape = (4, 3),Stride = (3, 1)
// row-major: 行内连续(stride=1),行间跳 3(stride=3)
auto layout = make_layout(make_shape(4, 3), make_stride(3, 1));
printf("=== 2D Row-Major (4,3):(3,1) ===\n");
print_layout(layout);
printf("\n");

// 用二维坐标访问
printf(" layout(0,0) = %d\n", int(layout(0, 0))); // 0*3 + 0*1 = 0
printf(" layout(1,2) = %d\n", int(layout(1, 2))); // 1*3 + 2*1 = 5
printf(" layout(3,2) = %d\n", int(layout(3, 2))); // 3*3 + 2*1 = 11
printf("\n");
}

img

1
auto layout = make_layout(make_shape(4, 3), make_stride(3, 1));
  • (4, 3):(3, 1) = 4 行 3 列,row-major
  • 行内连续:stride=1(第二维,列方向)
  • 行间跳 3:stride=3(第一维,行方向)
1
2
3
4
5
6
7
offset = i * 3 + j * 1

j=0 j=1 j=2
i=0 [ 0 1 2 ]
i=1 [ 3 4 5 ]
i=2 [ 6 7 8 ]
i=3 [ 9 10 11 ]

记忆方法:Row-major = 行内连续 = 第二维 stride 为 1,第一维 stride = 列数。

第 3 节:二维 Layout —— Col-Major

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ============================================================
// 3. 二维 Layout:col-major
// ============================================================
{
// Shape = (4, 3),Stride = (1, 4)
// col-major: 列内连续(stride=1),列间跳 4(stride=4)
auto layout = make_layout(make_shape(4, 3), make_stride(1, 4));
printf("=== 2D Col-Major (4,3):(1,4) ===\n");
print_layout(layout);
printf("\n");

printf(" layout(0,0) = %d\n", int(layout(0, 0))); // 0*1 + 0*4 = 0
printf(" layout(1,2) = %d\n", int(layout(1, 2))); // 1*1 + 2*4 = 9
printf("\n");
}

img

1
auto layout = make_layout(make_shape(4, 3), make_stride(1, 4));
  • (4, 3):(1, 4) = 4 行 3 列,col-major
  • 列内连续:stride=1(第一维,行方向)
  • 列间跳 4:stride=4(第二维,列方向)
1
2
3
4
5
6
7
offset = i * 1 + j * 4

j=0 j=1 j=2
i=0 [ 0 4 8 ]
i=1 [ 1 5 9 ]
i=2 [ 2 6 10 ]
i=3 [ 3 7 11 ]

记忆方法:Col-major = 列内连续 = 第一维 stride 为 1,第二维 stride = 行数。

第 4 节:make_layout 默认 Col-Major

1
2
3
4
5
6
7
8
9
10
11
12
// ============================================================
// 4. make_layout 的便捷用法
// ============================================================
{
// 只给 shape,不给 stride -> 默认 col-major(CuTe 惯例!)
auto layout = make_layout(make_shape(4, 3));
printf("=== Default (col-major): make_layout(make_shape(4,3)) ===\n");
print(layout); // 打印 shape:stride 的文本形式
printf("\n");
print_layout(layout);
printf("\n");
}

img

1
auto layout = make_layout(make_shape(4, 3));  // 不给 stride

CuTe 惯例:只给 shape 不给 stride,默认生成 col-major stride。

这与 C/C++ 的 row-major 习惯不同!使用时要特别注意。

默认 stride 的计算规则是 col-major 前缀积:

  • shape = (4, 3)
  • stride = (1, 4) ← 第一个是 1,第二个是 1×4=4

第 5 节:嵌套 Shape(层级 Layout)

在torch中我们可以访问tensor的shape属性和stride方法(注意方法调用需要使用带括号形式,即stride())来获取对应的信息。我们不难发现,上面的shape和stride描述其限制了Tensor的每一个轴只能有一个stride值,也就是说,整个tensor在某一个维度上的连续性关系是不能变的,更形象地描述则为:Tensor不可以分块。我们将这样轴的连续性不可变更,体现为Tensor不可以分块的描述称为单调Tensor描述。而当我们处理复杂的Tensor计算问题时,尤其是如NVidia硬件引入的指令计算时,这种表示是不充分的。由此则引入了有层次的Tensor描述,即Heriarchy Layout。简单地理解,有层次的Tensor(或者Layout)就是以原有的单调Tensor所描述的小块作为基础单元,将其组成Tensor。这样原有的小块是Tensor,小块作为单元的外部组织也是Tensor,实现了Tensor套Tensor,这就是所谓的有层级的Tensor了。而其坐标到实际物理位置的映射关系则就是其Layout,Tensor有了层次,Layout也就有层次了。

这里引用reed博士的图片

img

shape和stride给出了Tensor的逻辑空间描述和映射到物理空间时的间隔描述,当根据逻辑空间的位置获得物理空间位置时,依然和传统的Tensor计算规则一样,采用coordinate和stride点积即可,只不过坐标也采用heriarchy表达即可。

通过以上两个有层次Tensor的shape和stride的分解和组合,我们可以完成有层次的Tensor的组合,其突破了传统的Tensor轴只能有一个stride的限制,可以表达更丰富的Tensor(有层次的Tensor)。

总结一下,shape的表示规则可以这样理解:

((内部行数,外部行数1, 外部行数2,…),(内部列数,外部列数1,外部列数2,…))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// ============================================================
// 5. 嵌套 Shape(层级 Layout)
// ============================================================
{
// Shape = ((2, 3), 4) 是一个嵌套 shape
// 第一维由 (2, 3) 组成(逻辑上 2*3=6 个位置),第二维 4
// CuTe 会自动计算 col-major stride
auto layout = make_layout(make_shape(make_shape(2, 3), 4));
printf("=== Nested Shape ((2,3), 4) ===\n");
print(layout);
printf("\n");
print_layout(layout);
printf("\n");

// stride 的结构必须和 shape 一一对应:
// shape = ((2, 3), 4)
// stride = ((1, 2), 6) <-- 第一维嵌套,stride 也必须嵌套
auto layout1 = make_layout(make_shape(make_shape(2, 3), 4),
make_stride(make_stride(2, 2), 3));
printf("=== Nested Shape ((2,3), 4) stride((1,2), 6) ===\n");
print(layout1);
printf("\n");
print_layout(layout1);
printf("\n");
}

img

1
auto layout = make_layout(make_shape(make_shape(2, 3), 4));

Shape = ((2, 3), 4),第一维由 (2, 3) 两个子维度组成(逻辑上 2×3=6 个位置),第二维 4。

关键规则:stride 的结构必须和 shape 一一对应。

1
2
shape  = ((2, 3),   4)
stride = ((1, 2), 6) ← 嵌套 shape 必须配嵌套 stride

常见错误

1
2
3
4
5
// 错误!shape 是嵌套的 ((2,3), 4),stride 不能是 flat 的 (s1, s2)
make_layout(make_shape(make_shape(2, 3), 4), make_stride(2, 3)); // 编译错误

// 正确:stride 也嵌套
make_layout(make_shape(make_shape(2, 3), 4), make_stride(make_stride(2, 2), 3));

另一个陷阱:C++ 中 (2, 3) 是逗号表达式,结果是 3 而不是 tuple。必须用 make_shape(2, 3) 来创建嵌套结构。

第 6 节:Layout 的常用属性

1
auto layout = make_layout(make_shape(4, 8), make_stride(8, 1));
函数 含义 示例值
shape(layout) 每维大小 (4, 8)
stride(layout) 每维步长 (8, 1)
size(layout) 总元素数 = 各维大小之积 32
rank(layout) 维度数 2
cosize(layout) 覆盖的地址空间大小 = max_offset + 1 32

size vs cosize 的区别

1
2
3
4
auto sparse = make_layout(make_shape(2, 2), make_stride(1, 8));
// 实际访问的 offset: 0, 1, 8, 9
// size = 4(4 个元素)
// cosize = 10(max_offset=9, +1=10)

当 stride 不紧凑时,cosize > size,中间有"空洞"。

第 7 节:一维索引 vs 多维索引

Layout 可以用一维 index 访问,CuTe 会按 col-major 展开:

1
2
3
4
auto layout = make_layout(make_shape(3, 4), make_stride(4, 1));

// layout(i) 等价于 layout(i % shape<0>, i / shape<0>)
// 即 layout(i) = layout(i % 3, i / 3)

展开顺序是先填满第一维,再进入第二维

1
2
3
4
5
6
layout(0) = layout(0,0) = 0
layout(1) = layout(1,0) = 4
layout(2) = layout(2,0) = 8
layout(3) = layout(0,1) = 1 ← 第一维填满,进入第二维
layout(4) = layout(1,1) = 5
...

注意:一维展开顺序始终是 col-major,与 layout 本身是 row-major 还是 col-major 无关。这是 CuTe 的固定约定。

易错点总结

问题 解决方法
make_layout(shape) 默认是什么 Col-major,不是 row-major
(2, 3) 在 C++ 中是什么 逗号表达式,值为 3。要用 make_shape(2, 3)
嵌套 shape 配什么 stride stride 结构必须和 shape 一一对应
print_layout 报错 只支持 rank-2 layout,一维用 print()
一维 index 的展开顺序 始终 col-major:先填第一维
  • 标题: cute(1)Layout基础
  • 作者: 鱿鱼圈
  • 创建于 : 2026-06-06 22:13:32
  • 更新于 : 2026-06-14 23:33:01
  • 链接: https://yuyanqi.com/2026/06/06/cute(1)Layout基础/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论