基本概念
张量(Tensor):
1.基本定义
struct Tensor<T> {
shape: Vec<usize>,
data: Vec<T>,
}
张量的形状(Shape)
shape
是一个向量,表示张量每个维度的大小。例如:
- 一维张量(向量)的
shape
是[n]
,其中n
是向量的长度。 - 二维张量(矩阵)的
shape
是[m, n]
,其中m
是行数,n
是列数。 - 三维张量的
shape
是[p, m, n]
,其中p
是层数,m
是行数,n
是列数。
张量的维数(Rank)
张量的维数(或称为 rank
)是指张量的 shape
向量的长度。例如:
- 向量
[n]
是一维张量,其维数是 1。 - 矩阵
[m, n]
是二维张量,其维数是 2。 - 三维张量
[p, m, n]
的维数是 3。
data
data
是一个包含张量所有元素的扁平化向量。例如:
- 对于形状为
[4]
的一维张量,data
可能是[1, 2, 3, 4]
。 - 对于形状为
[2, 3]
的二维张量,data
可能是[1, 2, 3, 4, 5, 6]
,表示一个 2x3 的矩阵:1 2 3 4 5 6
- 对于形状为
[2, 2, 2]
的三维张量,data
可能是[1, 2, 3, 4, 5, 6, 7, 8]
,表示一个 2x2x2 的立方体结构。
偏移量(Offset)
offset
是数据的偏移量,用于处理子张量的情况。例如,如果一个张量是从另一个张量的中间部分截取的,偏移量就可以指向数据的起始位置。这样我们可以避免数据的复制,只需改变偏移量即可创建新的子张量。
- 示例: 假设我们有一个一维张量,数据为
[1, 2, 3, 4, 5]
。如果我们只想创建一个包含[3, 4, 5]
的子张量,可以设置offset
为2
。
长度(Length)
length
表示张量中的元素总数。这个字段可以帮助我们快速获取张量的大小,并在需要时进行验证或其他操作。
- 示例: 对于一个二维张量,形状为
[2, 3]
,即包含 2 行 3 列,总共有 6 个元素。此时length
就是6
。
2.方法
reshape
方法
reshape
方法用于重新解释张量的形状,同时保持其总大小不变。
代码示例:
// 重新解释张量的形状,同时保持总大小不变
pub fn reshape(&mut self, new_shape: &Vec<usize>) -> &mut Self {
let new_length: usize = new_shape.iter().product();
if new_length != self.length {
let old_shape = self.shape.clone();
panic!("New shape {new_shape:?} does not match tensor of {old_shape:?}");
}
self.shape = new_shape.clone();
self
}
功能和使用场景:
- 功能:
reshape
方法改变张量的形状,而不改变其数据。它只改变shape
字段的值。 - 使用场景:当你需要以不同的维度和形状来查看或操作张量,但数据本身并没有改变时使用。例如,将一个
1x6
的张量重新解释为2x3
或3x2
。
slice
方法
slice
方法用于创建一个新的子张量,该子张量包含原张量中的一部分数据。
代码示例:
pub fn slice(&self, start: usize, shape: &Vec<usize>) -> Self {
let new_length: usize = shape.iter().product();
assert!(self.offset + start + new_length <= self.length);
Tensor {
data: self.data.clone(),
shape: shape.clone(),
offset: self.offset + start,
length: new_length,
}
}
功能和使用场景:
- 功能:
slice
方法根据给定的起始位置和新的形状,创建一个新的张量实例。这个新的张量实例共享原始数据,但通过offset
字段指向数据的子集。 - 使用场景:当你需要从原始张量中提取出一个子张量进行操作时使用。例如,从一个
4x4
的张量中提取出一个2x2
的子张量。
大模型的几个关键算子
1.SiLU函数
(1)Sigmoid函数
其函数图像如下:
(2)SiLU 函数逐元素操作(Element-wise Operation)
- 逐元素操作:这是指对张量中的每一个元素单独进行操作,而不是对整个张量进行整体操作。例如,如果有两个张量
x
和y
,对每一个元素x[i]
和y[i]
,你需要执行y[i] = sigmoid(x[i]) * x[i] * y[i]
。
(3)作业定义
1.定义:
2.代码:
pub fn sigmoid(x: f32) -> f32{
1.0 / (1.0 + (-x).exp())
}
// y = sigmoid(x) * x * y
// hint: this is an element-wise operation
pub fn silu(y: &mut Tensor<f32>, x: &Tensor<f32>) {
let len = y.size();
assert!(len == x.size());
let y_data = unsafe { y.data_mut() };
let x_data = x.data();
// 逐元素计算
for i in 0..len {
y_data[i] = y_data[i] * x_data[i] * sigmoid(x_data[i]);
}
}
2.RMS Normalization 实现
定义:
-
参数说明:
x_i
:位置i
处的输入张量或向量。w
:与x_i
长度相同的一维权重向量,用于逐元素相乘。- ϵ:一个小常数,防止除零错误。
y_i
:位置i
处的归一化输出张量或向量。- N 是输入向量中元素的个数
代码:
pub fn rms_norm(y: &mut Tensor<f32>, x: &Tensor<f32>, w: &Tensor<f32>, epsilon: f32) {
let x_len = x.size();
let w_len = w.size();
let x_silce_num = x_len / w_len;
let y_data = unsafe { y.data_mut() };
let w_data = w.data();
for i in 0..x_silce_num{
let slice = x.slice(w_len*i, &vec![w_len]); // 创建一个更长生命周期的值
let x_slice = slice.data();
let sum_of_squares: f32 = x_slice.iter().map(|&xj| xj * xj).sum();
let rms = (sum_of_squares / w_len as f32 + epsilon).sqrt();
for j in 0..w_len{
y_data[w_len*i + j] = x_slice[j] * w_data[j] / rms;
}
}
}
3. 算子:矩阵乘
1.转置定义
在多维数组或张量的操作中,转置(Transpose)是指交换某些维度上的数据排列。不同于简单的二维矩阵转置,多维数组的转置可以涉及多个维度的重新排列。以下是几种常见的转置定义:</