详细深入地解析向量与矩阵的核心运算,并重点阐述GPU为何及如何高效并行化这些操作。
1. 向量运算 (Vector Operations)
向量可以看作是一个一维数组,包含 n 个分量(元素)。在数学和物理中,它通常表示一个有方向和大小的量。
a) 向量加法 (Vector Addition)
-
定义: 两个相同维度的向量 a 和 b 相加,得到一个新向量 c。新向量的每个分量是 a 和 b 对应分量之和。
- 公式:
- 示例:
-
GPU并行化:
- 极度并行。每个向量的分量加法完全独立,互不依赖。
- GPU可以启动大量线程(例如,对于长度为N的向量,就启动N个线程),每个线程只负责计算一个位置(
i)的加法c[i] = a[i] + b[i]。 - 这是 数据并行 (Data Parallelism) 的完美体现:相同的操作应用于不同的数据片段。
b) 点积 / 内积 (Dot Product / Inner Product)
-
定义: 两个相同维度的向量 a 和 b 的点积是一个标量(一个数字)。它是对应分量乘积之和。
- 公式:
- 几何意义: 它衡量了两个向量的“相似度”。如果向量是垂直的,点积为0。它也等于
|a||b|cosθ,其中 θ 是两向量间的夹角。 - 示例:
-
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 所在的平面。
- 公式:
- 几何意义: 其大小等于以 a 和 b 为邻边的平行四边形的面积,方向由右手定则确定。
- GPU并行化: 计算结果的三个分量可以并行计算,但每个分量的计算本身是独立的标量运算。
-
外积 (任意维度)
- 定义: 一个
m维向量 a 和一个n维向量 b 的外积得到一个m x n的矩阵 M。 - 公式:
- 示例:
- 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 对应元素之和。- 公式:
- 示例:
-
GPU并行化: 与向量加法完全类似,但线程布局是二维的。每个线程
(i, j)计算一个输出元素C[i][j] = A[i][j] + B[i][j]。完美并行。
b) 矩阵转置 (Matrix Transpose)
-
定义: 将矩阵 A 的行和列互换,得到转置矩阵 A^T。
- 公式:
- 示例:
-
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。- 公式:
- 解释: 结果矩阵 C 中第
i行第j列的元素,等于矩阵 A 的第i行 与 矩阵 B 的第j列 的点积。 - 示例:
-
GPU并行化:
- 天然并行。输出矩阵 C 中的每一个元素
(i, j)的计算都是独立的!因为每个元素都是一个点积。 - GPU可以启动一个二维网格的线程(
m x p个线程),每个线程(i, j)负责计算一个点积,即计算 。 - 然而,简单的实现性能不高,因为每个线程都需要从全局内存中读取 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),但理解其背后的原理对于调试和优化至关重要。