Tuple定义
定义
如果\(V\)是一个集合,那么由\(V\)中元素组成的有限长度有序列表就被叫做一个元组(tuple)
\(x_i \in V, 1\le i \le m\)。
\(X\)的长度为
我们最感兴趣的是\(V = \mathbb{Z}\),也就是由整数组成的tuple。当\(X\)是整数组成的tuple的时候,我们定义其size为:
下面是一些整数组成的tuple的例子
示例
定义
如果\(X = (x_1,\cdots, x_m)\), \(Y = (y_1, \cdots, y_n)\)都是tuples,它们的连接(concatenation)定义为:
例如 \(X = (2,3), Y=(1,4)\), 那么
定义
如果\(X, Y\)都是tuple,当存在另外一个tuple \(X'\),使得
我们称\(X\)整除\(Y\)。
注
如果\(n\)是一个正整数,那么我们记
如果\(S = (s_1, \cdots, s_m)\)是一个tuple,那么我们记
也就是\((x_1, \cdots, x_m), 0\le x_i < s_i\)的集合。
示例
如果\(S = (3, 2)\),那么
嵌套Tuple
定义
一个profile P满足下面条件之一: - \(P = *\) - \(P = (P_1, \cdots, P_r)\),其中\(P_1, \cdots, P_r\)都是profile(\(r\geq 0\),允许空tuple)。
示例
下面是一些profile的示例:
定义
假设\(P\)是一个profile,我们有
示例
定义
\(P\)是一个rank为\(r\)的profile,ith mode of P(\(1\le i < r\))定义为
示例
例如\(P = (*, (*, *))\),我们有
定义
\(V\)是一个集合,由\(V\)中的元素组成的nested tuple 的\(X\)是由一个tuple \(X^\text{b}\)和一个profile \(P\)组成 - \(X^{\text{b}} = (x_1, \cdots, x_m)\), \(x_i \in V, \forall i \in [1, m]\),是一个tuple,称为flattening of X; - \(\text{prof}(X) = P\),叫做profile of X。
把\(V\)中元素组成的所有的嵌套tuples记作\(\text{Nest}(V)\)。
示例
定义
假设\(X\)是由\(V\)中元素组成的nested tuple,那么 - rank of X
- length of X
- depth of X
- 如果\(V = \mathbb{Z}\), size of X
从数学 Tuple 到 CuTe 代码
cute中tuple.hpp包含了Tupe相关的声明,省略构造及访问函数体;eso::ESO_t 是库内部用于紧凑存储元素的基类:
template <class... T>
struct tuple : eso::ESO_t<T...> {
CUTE_HOST_DEVICE constexpr tuple();
CUTE_HOST_DEVICE constexpr tuple(T const&... t);
};
template <> struct tuple<> {};
template <class... T>
CUTE_HOST_DEVICE constexpr tuple<T...> make_tuple(T const&... t);
template <size_t I, class... T>
CUTE_HOST_DEVICE constexpr decltype(auto) get(tuple<T...> const& t) noexcept;
template <size_t I, class... T>
CUTE_HOST_DEVICE constexpr decltype(auto) get(tuple<T...>& t) noexcept;
template <size_t I, class... T>
CUTE_HOST_DEVICE constexpr decltype(auto) get(tuple<T...>&& t) noexcept;
rank, depth, size函数在int_tuple.hpp中:
template <int... Is, class IntTuple>
CUTE_HOST_DEVICE constexpr auto rank(IntTuple const& t);
template <int... Is, class IntTuple>
CUTE_HOST_DEVICE constexpr auto depth(IntTuple const& t);
template <int... Is, class IntTuple>
CUTE_HOST_DEVICE constexpr auto size(IntTuple const& a);
flatten函数定义在tupe_algorithms.hpp中,它会把嵌套的Tupe展开
template <class T>
CUTE_HOST_DEVICE constexpr
auto flatten(T const& t)
{
if constexpr (is_tuple<T>::value) {
if constexpr (is_flat<T>::value) { // Shortcut for perf
return t;
} else {
return filter_tuple(t, [](auto const& a) { return flatten_to_tuple(a); });
}
} else {
return t;
}
CUTE_GCC_UNREACHABLE;
}
其中传入的T是nested tuple。
真实使用中,我们比较少直接使用tuple,而是使用tupe的别名,最经常使用的有
- Shape
- Stride
- Coord
- Tile
- Step
它们定义在layout.hpp文件里面:
template <class... Shapes> using Shape = cute::tuple<Shapes...>;
template <class... Strides> using Stride = cute::tuple<Strides...>;
template <class... Coords> using Coord = cute::tuple<Coords...>;
template <class... Layouts> using Tile = cute::tuple<Layouts...>;
template <class... Strides> using Step = cute::tuple<Strides...>;
同一文件里面还定义了生产它们的工厂函数:
template <class... Ts>
CUTE_HOST_DEVICE constexpr Shape<Ts...> make_shape(Ts const&... t);
template <class... Ts>
CUTE_HOST_DEVICE constexpr Stride<Ts...> make_stride(Ts const&... t);
template <class... Ts>
CUTE_HOST_DEVICE constexpr Coord<Ts...> make_coord(Ts const&... t);
template <class... Ts>
CUTE_HOST_DEVICE constexpr Tile<Ts...> make_tile(Ts const&... t);
template <class... Ts>
CUTE_HOST_DEVICE constexpr Step<Ts...> make_step(Ts const&... t);
这些工厂函数都返回由实参类型组成的 Tuple,用不同名字表达用途:
-
Shape表示一个tensor的形状; -
Stride表示一个tensor的stride; -
Coord表达坐标; -
Tile组织各方向的 tiler; -
Step表达各方向的选择或顺序。
现在将前面介绍的静态整数与普通整数放在一起:
#include <cute/tensor.hpp>
using namespace cute;
int n = 5;
auto x = make_tuple(_2{}, make_tuple(_3{}, n));
// 数学表达:(2,(3,5)),其中最后一个 5 是运行时值
auto first = get<0>(x); // Int<2>
auto inner = get<1>(x); // (Int<3>, int)
int last = get<1>(get<1>(x)); // 5
auto s = make_shape(_2{}, make_shape(_3{}, n));
auto d = make_stride(_1{}, make_stride(_2{}, _6{}));
make_tuple、make_shape、make_stride 都能建立嵌套整数结构;后两者用名字表达“这组数用于形状”或“这组数用于步长”。get<I> 取第 I 个顶层 mode,C++ 下标从 0 开始,它不会自动把嵌套 Tuple 展平,因此取得最里面的 n 需要先进入第二个 mode。
Shape 和 Stride 中,哪些值写成静态整数
同一个数学形状 (4,8) 可以有三种代码表达:
int m = 4, n = 8;
auto dynamic_shape = make_shape(m, n); // (4,8)
auto static_shape = make_shape(_4{}, _8{}); // (_4,_8)
auto mixed_shape = make_shape(_4{}, n); // (_4,8)
它们当前的数值形状相同,但前者的两个值由对象保存,中间两个值由类型确定,最后一种只把第一个值放进类型。Tuple 的层次和元素个数在这里仍由类型确定;“动态 shape”并不表示可以在运行时随意增加一个维度。
实际使用时,全局矩阵的 M/N/K 常由调用者传入,而一个 kernel 固定使用的 tile 大小以及连续维的步长 1 常写成静态整数。例如,上一章的列优先矩阵可以这样准备形状和步长:
int m = 4, n = 8;
auto shape = make_shape(m, n); // (4,8)
auto stride = make_stride(_1{}, m); // (_1,4)
坐标 (i,j) 对应的元素偏移仍是 i + j*m。第一维每次前进一个元素,这个 1 与矩阵尺寸无关,适合用 _1{} 表达;第二维的步长由运行时传入的 m 决定。
后面将 shape 和 stride 交给 make_layout,就得到完整 Layout。
固定的 tile 大小适合静态表达,需要在不同调用间变化的矩阵尺寸适合动态表达。两种信息可以存在于同一个 Tuple 中;具体硬件操作若要求某些维度或步长静态已知,再按该操作的约束选择类型。