向量与矩阵基础知识与GPU高效计算

220 阅读6分钟

详细深入地解析向量与矩阵的核心运算,并重点阐述GPU为何及如何高效并行化这些操作。


1. 向量运算 (Vector Operations)

向量可以看作是一个一维数组,包含 n 个分量(元素)。在数学和物理中,它通常表示一个有方向和大小的量。

a) 向量加法 (Vector Addition)

  • 定义: 两个相同维度的向量 ab 相加,得到一个新向量 c。新向量的每个分量是 ab 对应分量之和。

    • 公式: ci=ai+bic_i = a_i + b_i
    • 示例: [123]+[456]=[579]\begin{bmatrix} 1 \\ 2 \\ 3 \end{bmatrix} + \begin{bmatrix} 4 \\ 5 \\ 6 \end{bmatrix} = \begin{bmatrix} 5 \\ 7 \\ 9 \end{bmatrix}
  • GPU并行化:

    • 极度并行。每个向量的分量加法完全独立,互不依赖。
    • GPU可以启动大量线程(例如,对于长度为N的向量,就启动N个线程),每个线程只负责计算一个位置(i)的加法 c[i] = a[i] + b[i]
    • 这是 数据并行 (Data Parallelism) 的完美体现:相同的操作应用于不同的数据片段

b) 点积 / 内积 (Dot Product / Inner Product)

  • 定义: 两个相同维度的向量 ab 的点积是一个标量(一个数字)。它是对应分量乘积之和。

    • 公式: ab=i=1naibi\mathbf{a} \cdot \mathbf{b} = \sum_{i=1}^{n} a_i b_i
    • 几何意义: 它衡量了两个向量的“相似度”。如果向量是垂直的,点积为0。它也等于 |a||b|cosθ,其中 θ 是两向量间的夹角。
    • 示例: [123][456]=(14)+(25)+(36)=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
  • GPU并行化:

    • 这个过程分为两步:
      1. 元素级乘法 (Map): 计算每个位置的分量乘积 d[i] = a[i] * b[i]。这一步是高度并行的,与向量加法类似。
      2. 求和归约 (Reduce): 将第一步得到的所有乘积结果 d[0], d[1], ..., d[n-1] 求和。这一步是并行算法中的经典归约(Reduction)模式
    • 归约(Reduce) 不能简单地让所有线程同时加,需要一种树形结构的策略。例如,先让相邻的两个数相加,然后结果再两两相加,如此往复,直到得到一个最终值。GPU可以通过线程块(Thread Block)内的共享内存同步操作来高效地实现这种归约。

c) 叉积 / 外积 (Cross Product / Outer Product)

  • 叉积 (3D向量)

    • 定义: 两个三维向量 ab 的叉积得到一个新的三维向量 c,该向量垂直于 ab 所在的平面。
    • 公式: a×b=[a2b3a3b2a3b1a1b3a1b2a2b1]\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}
    • 几何意义: 其大小等于以 ab 为邻边的平行四边形的面积,方向由右手定则确定。
    • GPU并行化: 计算结果的三个分量可以并行计算,但每个分量的计算本身是独立的标量运算。
  • 外积 (任意维度)

    • 定义: 一个 m 维向量 a 和一个 n 维向量 b 的外积得到一个 m x n 的矩阵 M
    • 公式: Mij=aibjM_{ij} = a_i b_j
    • 示例: [123][45]=[141524253435]=[458101215]\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}
    • GPU并行化: 高度并行。输出矩阵中的每一个元素 M[i][j] 的计算都是独立的。GPU可以启动一个二维网格的线程,每个线程 (i, j) 只计算 M[i][j] = a[i] * b[j]

2. 矩阵运算 (Matrix Operations)

矩阵是一个二维数组,由行和列组成。

a) 矩阵加法 (Matrix Addition)

  • 定义: 两个相同维度(m x n)的矩阵 AB 相加,得到一个新矩阵 C。新矩阵的每个元素是 AB 对应元素之和。

    • 公式: Cij=Aij+BijC_{ij} = A_{ij} + B_{ij}
    • 示例: [1234]+[5678]=[681012]\begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} + \begin{bmatrix} 5 & 6 \\ 7 & 8 \end{bmatrix} = \begin{bmatrix} 6 & 8 \\ 10 & 12 \end{bmatrix}
  • GPU并行化: 与向量加法完全类似,但线程布局是二维的。每个线程 (i, j) 计算一个输出元素 C[i][j] = A[i][j] + B[i][j]完美并行

b) 矩阵转置 (Matrix Transpose)

  • 定义: 将矩阵 A 的行和列互换,得到转置矩阵 A^T

    • 公式: (AT)ij=Aji(A^T)_{ij} = A_{ji}
    • 示例: [123456]T=[142536]\begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{bmatrix}^T = \begin{bmatrix} 1 & 4 \\ 2 & 5 \\ 3 & 6 \end{bmatrix}
  • 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

    • 公式: Cij=k=1nAikBkjC_{ij} = \sum_{k=1}^{n} A_{ik} B_{kj}
    • 解释: 结果矩阵 C 中第 i 行第 j 列的元素,等于矩阵 A 的第 i 行 与 矩阵 B 的第 j 列 的点积
    • 示例: [1234][5678]=[(15+27)(16+28)(35+47)(36+48)]=[19224350]\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}
  • GPU并行化:

    • 天然并行。输出矩阵 C 中的每一个元素 (i, j) 的计算都是独立的!因为每个元素都是一个点积。
    • GPU可以启动一个二维网格的线程(m x p 个线程),每个线程 (i, j) 负责计算一个点积,即计算 Cij=k=1nAikBkjC_{ij} = \sum_{k=1}^{n} A_{ik} B_{kj}
    • 然而,简单的实现性能不高,因为每个线程都需要从全局内存中读取 A 的一行和 B 的一列,访问效率低。
    • 高性能GEMM(通用矩阵乘法)实现(如cuBLAS库中的)使用了极其优化的技术:
      • 分块(Tiling): 将大矩阵分解成小块,放入GPU的共享内存(类似CPU的L1缓存)。这样可以大幅减少访问全局内存(慢)的次数。
      • 寄存器优化: 让每个线程计算多个结果(例如一个2x2的小块),以摊销线程创建和内存读取的开销。
      • 内存访问合并: 确保线程在读取全局内存时是连续的、对齐的,以最大化内存带宽利用率。

上图展示了分块矩阵乘法的思想。每个线程块负责计算结果矩阵C的一个分块。为了计算这个分块,它需要从全局内存中加载矩阵A和B的相应分块到共享内存中,然后所有线程协作利用这些数据块进行计算。这极大地提高了数据复用率和计算效率。

总结:GPU为何擅长这些操作?

  1. 大规模并行架构: GPU拥有成千上万个轻量级计算核心(CUDA Core/Streaming Processor),非常适合执行大量简单的、相同的计算任务。
  2. 独立性与规则性: 向量和矩阵运算(加法、点积的分量乘、矩阵乘法的每个输出元素)通常具有极高的独立性和规则的内存访问模式,可以轻松映射到GPU的线程模型上。
  3. 专用硬件支持: 现代GPU(如NVIDIA的Tensor Core)甚至为特定的矩阵运算(如低精度矩阵乘累加 D = A * B + C)提供了硬件级别的加速,这在深度学习中至关重要。

因此,理解这些运算的数学本质和并行特性,是有效利用GPU进行高速计算的关键。在实际应用中,我们通常直接调用高度优化的库(如cuBLAS),但理解其背后的原理对于调试和优化至关重要。