详细深入地解析向量与矩阵的核心运算,并重点阐述GPU为何及如何高效并行化这些操作。
1. 向量运算 (Vector Operations)
向量可以看作是一个一维数组,包含 n
个分量(元素)。在数学和物理中,它通常表示一个有方向和大小的量。
a) 向量加法 (Vector Addition)
-
定义 : 两个相同维度的向量 a 和 b 相加,得到一个新向量 c 。新向量的每个分量是 a 和 b 对应分量之和。
- 公式 : <math xmlns="http://www.w3.org/1998/Math/MathML"> c i = a i + b i c_i = a_i + b_i </math>ci=ai+bi
- 示例 : <math xmlns="http://www.w3.org/1998/Math/MathML"> [ 1 2 3 ] + [ 4 5 6 ] = [ 5 7 9 ] \begin{bmatrix} 1 \\ 2 \\ 3 \end{bmatrix} + \begin{bmatrix} 4 \\ 5 \\ 6 \end{bmatrix} = \begin{bmatrix} 5 \\ 7 \\ 9 \end{bmatrix} </math> 123 + 456 = 579
-
GPU并行化:
- 极度并行 。每个向量的分量加法完全独立,互不依赖。
- GPU可以启动大量线程(例如,对于长度为N的向量,就启动N个线程),每个线程只负责计算一个位置(
i
)的加法c[i] = a[i] + b[i]
。 - 这是 数据并行 (Data Parallelism) 的完美体现:相同的操作 应用于不同的数据片段。
b) 点积 / 内积 (Dot Product / Inner Product)
-
定义 : 两个相同维度的向量 a 和 b 的点积是一个标量(一个数字)。它是对应分量乘积之和。
- 公式 : <math xmlns="http://www.w3.org/1998/Math/MathML"> a ⋅ b = ∑ i = 1 n a i b i \mathbf{a} \cdot \mathbf{b} = \sum_{i=1}^{n} a_i b_i </math>a⋅b=∑i=1naibi
- 几何意义 : 它衡量了两个向量的"相似度"。如果向量是垂直的,点积为0。它也等于
|a||b|cosθ
,其中 θ 是两向量间的夹角。 - 示例 : <math xmlns="http://www.w3.org/1998/Math/MathML"> [ 1 2 3 ] ⋅ [ 4 5 6 ] = ( 1 ∗ 4 ) + ( 2 ∗ 5 ) + ( 3 ∗ 6 ) = 4 + 10 + 18 = 32 \begin{bmatrix} 1 & 2 & 3 \end{bmatrix} \cdot \begin{bmatrix} 4 & 5 & 6 \end{bmatrix} = (1*4) + (2*5) + (3*6) = 4 + 10 + 18 = 32 </math>[123]⋅[456]=(1∗4)+(2∗5)+(3∗6)=4+10+18=32
-
GPU并行化:
- 这个过程分为两步:
- 元素级乘法 (Map) : 计算每个位置的分量乘积
d[i] = a[i] * b[i]
。这一步是高度并行的,与向量加法类似。 - 求和归约 (Reduce) : 将第一步得到的所有乘积结果
d[0], d[1], ..., d[n-1]
求和。这一步是并行算法中的经典归约(Reduction)模式。
- 元素级乘法 (Map) : 计算每个位置的分量乘积
- 归约(Reduce) 不能简单地让所有线程同时加,需要一种树形结构的策略。例如,先让相邻的两个数相加,然后结果再两两相加,如此往复,直到得到一个最终值。GPU可以通过线程块(Thread Block)内的共享内存 和同步操作来高效地实现这种归约。
- 这个过程分为两步:
c) 叉积 / 外积 (Cross Product / Outer Product)
-
叉积 (3D向量)
- 定义 : 两个三维向量 a 和 b 的叉积得到一个新的三维向量 c ,该向量垂直于 a 和 b 所在的平面。
- 公式 : <math xmlns="http://www.w3.org/1998/Math/MathML"> a × b = [ a 2 b 3 − a 3 b 2 a 3 b 1 − a 1 b 3 a 1 b 2 − a 2 b 1 ] \mathbf{a} \times \mathbf{b} = \begin{bmatrix} a_2b_3 - a_3b_2 \\ a_3b_1 - a_1b_3 \\ a_1b_2 - a_2b_1 \end{bmatrix} </math>a×b= a2b3−a3b2a3b1−a1b3a1b2−a2b1
- 几何意义 : 其大小等于以 a 和 b 为邻边的平行四边形的面积,方向由右手定则确定。
- GPU并行化: 计算结果的三个分量可以并行计算,但每个分量的计算本身是独立的标量运算。
-
外积 (任意维度)
- 定义 : 一个
m
维向量 a 和一个n
维向量 b 的外积得到一个m x n
的矩阵 M。 - 公式 : <math xmlns="http://www.w3.org/1998/Math/MathML"> M i j = a i b j M_{ij} = a_i b_j </math>Mij=aibj
- 示例 : <math xmlns="http://www.w3.org/1998/Math/MathML"> [ 1 2 3 ] ⊗ [ 4 5 ] = [ 1 ∗ 4 1 ∗ 5 2 ∗ 4 2 ∗ 5 3 ∗ 4 3 ∗ 5 ] = [ 4 5 8 10 12 15 ] \begin{bmatrix} 1 \\ 2 \\ 3 \end{bmatrix} \otimes \begin{bmatrix} 4 & 5 \end{bmatrix} = \begin{bmatrix} 1*4 & 1*5 \\ 2*4 & 2*5 \\ 3*4 & 3*5 \end{bmatrix} = \begin{bmatrix} 4 & 5 \\ 8 & 10 \\ 12 & 15 \end{bmatrix} </math> 123 ⊗[45]= 1∗42∗43∗41∗52∗53∗5 = 481251015
- GPU并行化 : 高度并行 。输出矩阵中的每一个元素
M[i][j]
的计算都是独立的 。GPU可以启动一个二维网格的线程,每个线程(i, j)
只计算M[i][j] = a[i] * b[j]
。
- 定义 : 一个
2. 矩阵运算 (Matrix Operations)
矩阵是一个二维数组,由行和列组成。
a) 矩阵加法 (Matrix Addition)
-
定义 : 两个相同维度(
m x n
)的矩阵 A 和 B 相加,得到一个新矩阵 C 。新矩阵的每个元素是 A 和 B 对应元素之和。- 公式 : <math xmlns="http://www.w3.org/1998/Math/MathML"> C i j = A i j + B i j C_{ij} = A_{ij} + B_{ij} </math>Cij=Aij+Bij
- 示例 : <math xmlns="http://www.w3.org/1998/Math/MathML"> [ 1 2 3 4 ] + [ 5 6 7 8 ] = [ 6 8 10 12 ] \begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} + \begin{bmatrix} 5 & 6 \\ 7 & 8 \end{bmatrix} = \begin{bmatrix} 6 & 8 \\ 10 & 12 \end{bmatrix} </math>[1324]+[5768]=[610812]
-
GPU并行化 : 与向量加法完全类似,但线程布局是二维的。每个线程
(i, j)
计算一个输出元素C[i][j] = A[i][j] + B[i][j]
。完美并行。
b) 矩阵转置 (Matrix Transpose)
-
定义 : 将矩阵 A 的行和列互换,得到转置矩阵 A^T。
- 公式 : <math xmlns="http://www.w3.org/1998/Math/MathML"> ( A T ) i j = A j i (A^T){ij} = A{ji} </math>(AT)ij=Aji
- 示例 : <math xmlns="http://www.w3.org/1998/Math/MathML"> [ 1 2 3 4 5 6 ] T = [ 1 4 2 5 3 6 ] \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix}^T = \begin{bmatrix} 1 & 4 \\ 2 & 5 \\ 3 & 6 \end{bmatrix} </math>[142536]T= 123456
-
GPU并行化 : 高度并行 。输出矩阵中的每一个元素
(i, j)
的计算都是独立的 。每个线程(i, j)
负责T[i][j] = A[j][i]
。然而,这里需要注意内存访问模式 。从A
中读取是(j, i)
,是非连续的访问,可能导致性能下降。高级的GPU转置算法会利用共享内存来合并全局内存访问。
c) 矩阵乘法 (Matrix Multiplication)
-
定义 : 一个
m x n
的矩阵 A 和一个n x p
的矩阵 B 相乘,得到一个m x p
的矩阵 C。- 公式 : <math xmlns="http://www.w3.org/1998/Math/MathML"> C i j = ∑ k = 1 n A i k B k j C_{ij} = \sum_{k=1}^{n} A_{ik} B_{kj} </math>Cij=∑k=1nAikBkj
- 解释 : 结果矩阵 C 中第
i
行第j
列的元素,等于矩阵 A 的第i
行 与 矩阵 B 的第j
列 的点积。 - 示例 : <math xmlns="http://www.w3.org/1998/Math/MathML"> [ 1 2 3 4 ] [ 5 6 7 8 ] = [ ( 1 ∗ 5 + 2 ∗ 7 ) ( 1 ∗ 6 + 2 ∗ 8 ) ( 3 ∗ 5 + 4 ∗ 7 ) ( 3 ∗ 6 + 4 ∗ 8 ) ] = [ 19 22 43 50 ] \begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} \begin{bmatrix} 5 & 6 \\ 7 & 8 \end{bmatrix} = \begin{bmatrix} (1*5 + 2*7) & (1*6 + 2*8) \\ (3*5 + 4*7) & (3*6 + 4*8) \end{bmatrix} = \begin{bmatrix} 19 & 22 \\ 43 & 50 \end{bmatrix} </math>[1324][5768]=[(1∗5+2∗7)(3∗5+4∗7)(1∗6+2∗8)(3∗6+4∗8)]=[19432250]
-
GPU并行化:
- 天然并行 。输出矩阵 C 中的每一个元素
(i, j)
的计算都是独立的!因为每个元素都是一个点积。 - GPU可以启动一个二维网格的线程(
m x p
个线程),每个线程(i, j)
负责计算一个点积,即计算 <math xmlns="http://www.w3.org/1998/Math/MathML"> C i j = ∑ k = 1 n A i k B k j C_{ij} = \sum_{k=1}^{n} A_{ik} B_{kj} </math>Cij=∑k=1nAikBkj。 - 然而,简单的实现性能不高,因为每个线程都需要从全局内存中读取 A 的一行和 B 的一列,访问效率低。
- 高性能GEMM(通用矩阵乘法)实现 (如cuBLAS库中的)使用了极其优化的技术:
- 分块(Tiling) : 将大矩阵分解成小块,放入GPU的共享内存(类似CPU的L1缓存)。这样可以大幅减少访问全局内存(慢)的次数。
- 寄存器优化: 让每个线程计算多个结果(例如一个2x2的小块),以摊销线程创建和内存读取的开销。
- 内存访问合并: 确保线程在读取全局内存时是连续的、对齐的,以最大化内存带宽利用率。
- 天然并行 。输出矩阵 C 中的每一个元素
上图展示了分块矩阵乘法的思想。每个线程块负责计算结果矩阵C的一个分块。为了计算这个分块,它需要从全局内存中加载矩阵A和B的相应分块到共享内存中,然后所有线程协作利用这些数据块进行计算。这极大地提高了数据复用率和计算效率。
总结:GPU为何擅长这些操作?
- 大规模并行架构: GPU拥有成千上万个轻量级计算核心(CUDA Core/Streaming Processor),非常适合执行大量简单的、相同的计算任务。
- 独立性与规则性: 向量和矩阵运算(加法、点积的分量乘、矩阵乘法的每个输出元素)通常具有极高的独立性和规则的内存访问模式,可以轻松映射到GPU的线程模型上。
- 专用硬件支持 : 现代GPU(如NVIDIA的Tensor Core)甚至为特定的矩阵运算(如低精度矩阵乘累加
D = A * B + C
)提供了硬件级别的加速,这在深度学习中至关重要。
因此,理解这些运算的数学本质和并行特性,是有效利用GPU进行高速计算的关键。在实际应用中,我们通常直接调用高度优化的库(如cuBLAS),但理解其背后的原理对于调试和优化至关重要。