前置 | 基本概念

导学

Infinitensor 课程导学部分笔记 | 原文

张量

定义

一般定义。

struct Tensor {
    DataType data_type;        // 数据类型
    std::vector<size_t> shape; // 形状
    std::vector<uint8_t> data; // 底层数据存储
};

形状

张量的形状对于许多计算来说是具有语义的,这种语义会决定计算的方式。对于基本的张量定义来说,形状和存储方式是一一对应的,所以为了得到需要的张量形状而对张量进行变换的时候就必须同时变换底层的数据表示。这种变换可能导致大量寻址访问,是低效的。

为了解决这个问题,需要把张量的逻辑形状和存储方式分离,变换时尽量只改变逻辑形状,只有在必要的时候才变换存储方式。例如,为张量引入步长(strides)字段:

struct Tensor {
    data_type: DataType,
    shape: Vec<usize>,
    strides: Vec<isize>,
    data: Vec<u8>,
}

步长定义在张量的每一个维度上,表示这一个维度上第 i 个数据和第 i+1 个数据在底层存储上的距离。例如一个 C 二维数组:

T arr[m][n];

它的步长就是 (n, 1),因为在索引在长度为 m 的这个维度上每增加 1,就意味着数据的地址跳过了 n 个元素,用矩阵的语言描述就是矩阵的两行之间隔着一行的长度——也就是列数——那么多个元素。而索引在长度为 n 的这个维度上每增加 1,地址也只增加了 1 个元素。这就是所谓行优先存储的矩阵。

对于 3x4 的二阶张量(矩阵),步长为 [4,1]:

shape:   [     y  x] = 3 x 4
strides: [(n) sy sx] = (12) 4 1
-------------------------------
\------------/    \----------/
| 1  2  3  4 |    |  O → +1  |
| 5  6  7  8 | -> |  ↓       |
| 9 10 11 12 |    | +4       |
/------------\    /----------\

对于 2x3x4 的三阶张量(可能是卷积核),步长为 [12,4,1]:

shape:   [     z  y  x] = 2 x 3 x 4
strides: [(n) sz sy sx] = (24) 12 4 1
-------------------------------------
/ z
*--x ____________       ___________
|   /          /|      /         /|
y  / 13 14 15 16|     /  +12    / |
  /----------/20|    /--/------/  |
  |1  2  3  4|24| -> | O → +1  |  /
  |5  6  7  8| /     | ↓       | /
  |9 10 11 12|/      |+4       |/
  /----------/       /---------/

以转置操作为例,如果按模型结构,某个矩阵运算需要转置矩阵,对于基本的张量表示来说真的需要挪动矩阵中所有的数据。而引入步长之后我需要直接把形状和步长的 4 个数字交换一下就完成了:

原矩阵 转置矩阵
形状 M x N N x M
步长 N × 1 1 × N
posted @ 2025-03-08 03:37  Miya_Official  阅读(31)  评论(0)    收藏  举报