【CUDA】C2 矩阵计算

一、课程目标 00:04

● 理论目标: 进一步认识GPU并行原理
● 技能目标:
○ 实现基本并行的矩阵乘法
○ 实现进阶版矩阵乘法
二、内容回顾 00:15

  1. GPU并行基础概念

    ● 核函数: 设备端运行的函数,使用global关键字修饰
    ● 线程组织:
    ○ 主机端通过<<<…>>>语法传递线程组织方式
    ○ 线程块(block)和网格(grid)的索引可以是三维的
  2. CUDA编程三步骤

    ● Part1:
    ○ 主机端申请显存
    ○ 内存内容拷贝到显存
    ● Part2:
    ○ 构建核函数在设备端运行
    ● Part3:
    ○ 设备端内容回传到主机端
  3. 线程配置优化

    ● 线程块大小: 使用256个线程构成每个线程块
    ● 网格大小: 计算ceil(n/256)作为网格维度
    ● 三维索引:
    ○ dim3 DimGrid(n/256,1,1)
    ○ dim3 DimBlock(256,1,1)
    三、矩阵乘法基础
  4. 矩阵乘法原理

    ● 维度要求:
    ○ 矩阵A维度为m×km×km×k
    ○ 矩阵B维度为k×nk×nk×n
    ○ 结果矩阵C维度为m×nm×nm×n
    ● 计算规则:
    ○ Cij=∑k=1nAik×BkjC_{ij} = \sum_{k=1}^{n} A_{ik} × B_{kj}Cij=k=1∑nAik×Bkj
    ○ 即A的第i行与B的第j列对应元素相乘后累加
  5. GPU并行优势
    ● 适合并行原因:
    ○ 矩阵乘法中各元素计算相互独立
    ○ 可充分利用GPU的并行计算能力
    ○ 相比向量加法能更显著体现GPU优势
    四、矩阵乘法 01:39
  6. 矩阵乘法的CPU实现 02:42

    ● 算法实现:
    ○ 定义三个矩阵AAA、BBB、CCC在内存中的存储空间
    ○ 使用三重嵌套循环:
    ■ 外层循环iii遍历AAA的每一行(000到M−1M-1M−1)
    ■ 中层循环jjj遍历BBB的每一列(000到N−1N-1N−1)
    ■ 内层循环kkk进行元素级运算(000到K−1K-1K−1)
    ● 核心计算:C(i,j)=C(i,j)+A(i,k)×B(k,j)C(i,j) = C(i,j) + A(i,k) \times B(k,j)C(i,j)=C(i,j)+A(i,k)×B(k,j)
    ● 时间复杂度:O(M×N×K)O(M \times N \times K)O(M×N×K)
    ○ 当MMM、NNN、KKK相近时,可简化为O(n3)O(n^3)O(n3)
    ○ 相比向量加法O(n)O(n)O(n),矩阵乘法更能体现GPU优势
  7. 矩阵乘法的GPU实现

    ● 实现步骤:
    ○ 在CPU内存定义AcpuA_{cpu}Acpu、BcpuB_{cpu}Bcpu、CcpuC_{cpu}Ccpu
    ○ 在GPU内存定义AgpuA_{gpu}Agpu、BgpuB_{gpu}Bgpu、CgpuC_{gpu}Cgpu
    ○ 内存拷贝:Acpu→AgpuA_{cpu} \rightarrow A_{gpu}Acpu→Agpu,Bcpu→BgpuB_{cpu} \rightarrow B_{gpu}Bcpu→Bgpu
    ○ 设置线程块维度:dim3 dimBlock(16,16)
    ○ 设置网格维度:dim3 dimGrid(N/dimBlock.x, M/dimBlock.y)
    ○ 调用核函数:matrixMul<<<dimGrid,dimBlock>>>(A_gpu,B_gpu,C_gpu,K)
    ○ 结果回传:Cgpu→CcpuC_{gpu} \rightarrow C_{cpu}Cgpu→Ccpu
    1)GPU适用性分析
    ● 缓存特性:
    ○ GPU缓存小,适合缓存访问简单的计算
    ○ 矩阵乘法内存访问模式简单,满足要求
    ● 控制复杂度:
    ○ GPU缺乏复杂分支预测单元
    ○ 矩阵乘法无复杂分支判断,控制简单
    ● 计算特性:
    ○ GPU有大量简单计算单元
    ○ 矩阵乘法仅需乘加操作,计算密集
    ● 并行性:
    ○ GPU采用SIMD架构(单指令多数据)
    ○ 矩阵乘法中CCC的各元素计算相互独立,并行度高
  8. 矩阵乘法的GPU实现 03:31
    1)GPU实现三步骤 05:12

    ● 步骤1:内存定义与拷贝
    ○ 在主机端定义矩阵A、B、C的内存地址
    ○ 在设备端定义对应的显存地址A_gpu、B_gpu、C_gpu
    ○ 使用memcpy函数实现主机内存到显存的互相拷贝
    ● 步骤2:核函数书写
    ○ 核心计算部分,采用并行计算方式
    ○ 使用dim3 dimBlock(16,16)定义二维线程块
    ○ 使用dim3 dimGrid(N/dimBlock.x,M/dimBlock.y)定义线程块组合体维度
    ● 步骤3:结果回传与释放
    ○ 将计算结果从设备端显存拷贝回主机端内存
    ○ 释放主机端和设备端的内存资源
    2)矩阵乘法GPU核心核函数 06:08
    ● 线程块与线程索引 06:20

    ○ 线程组织方式
    ■ 每个线程处理矩阵C中的一个元素
    ■ 采用二维索引的线程块和线程块组合体
    ■ 使用16×16的二维线程块组织方式
    ○ 索引计算
    ■ 行索引:i = blockIdx.y * blockDim.y + threadIdx.y
    ■ 列索引:j = blockIdx.x * blockDim.x + threadIdx.x
    ● 核函数参数与计算过程 06:41
    ○ 参数说明
    ■ 输入矩阵A_gpu(m×k)、B_gpu(k×n)
    ■ 输出矩阵C_gpu(m×n)
    ■ 参数K表示矩阵A的列数和矩阵B的行数
    ○ 计算过程
    ■ 初始化累加器accu=0
    ■ 循环k=0到K-1:
    ● accu += A_gpu(i,k) * B_gpu(k,j)
    ■ 将结果存入C_gpu(i,j) = accu
    ● 内存访问与效率考量 07:23

    ○ 访问复杂度分析
    ■ 矩阵A的每个元素被读取n次(C的列数)
    ■ 矩阵B的每个元素被读取m次(C的行数)
    ■ 总访问次数:2mnk次(每次乘法需读取A和B各一次)
    ○ 性能瓶颈
    ■ 全局内存访问时间占比大
    ■ 计算复杂度主要来自内存访问而非计算本身
    ● 共享内存的概念与优势 09:03

    ○ 基本概念
    ■ 线程块内的共享存储空间
    ■ 可由线程块内所有线程共同访问
    ■ 读写速度远快于全局内存(约100倍)
    ○ 优化原理
    ■ 将频繁访问的数据放入共享内存
    ■ 减少全局内存的重复读取次数
    ■ 利用共享内存低延迟的优势
    ● 共享内存的申请方式 11:11

    ○ 静态申请
    ■ 编译时确定共享内存大小
    ■ 使用__shared__关键字声明
    ■ 示例:shared int s[64]
    ○ 动态申请
    ■ 运行时确定共享内存大小
    ■ 使用extern shared__关键字声明
    ■ 调用核函数时需传入共享内存大小参数
    ○ 使用限制
    ■ 共享内存大小仅几十KB
    ■ 过度使用会降低程序并行性
    ● 共享内存的使用注意事项 12:51
    ○ 使用步骤
    ■ 从全局内存拷贝数据到共享内存
    ■ 注意数据边界处理
    ■ 使用同步函数确保数据一致性
    ○ 边界问题
    ■ 需明确哪些数据需要拷贝
    ■ 避免不同线程访问冲突
    ■ 特别注意边界条件的处理
    ● 线程同步函数_syncthreads() 13:36

    ○ 功能作用
    ■ CUDA内置的块内线程同步函数
    ■ 确保共享内存数据的一致性
    ■ 防止读写时序错乱
    ○ 使用规范
    ■ 尽量使用简单的同步结构
    ■ 避免复杂分支条件下的同步
    ■ 确保所有需要同步的线程都能到达同步点
    ○ 错误示例
    ■ 不同条件分支下分别同步
    ■ 可能导致部分线程读取未同步数据
    ■ 计算结果可能出现错误
    ● 静态申请共享内存示例 16:17

    ○ 实现代码
    ○ 调用方式
    ■ 与普通核函数调用相同
    ■ 示例:staticReverse<<<1,n>>>(d_d, n)
    ● 动态申请共享内存示例 16:50

    ○ 实现代码
    ○ 调用方式
    ■ 需要额外指定共享内存大小
    ■ 示例:dynamicReverse<<<1,n,n*sizeof(int)>>>(d_d, n)
    3)使用共享内存复用全局内存数据 18:40
    ● 基础版矩阵乘法效率不高原因分析 18:58

    ○ 重复读取问题:在计算C=ABC=ABC=AB时,矩阵A的每行需要被读取nnn次,矩阵B的每列需要被读取mmm次,导致全局内存访问次数过多。
    ○ 单元素计算瓶颈:每次只计算CCC中的一个元素cijc
    {ij}cij,需要从全局内存读取A的第iii行和B的第jjj列各kkk次,共2k2k2k次全局内存访问。
    ○ 改进方向:
    ■ 将频繁读取的数据放入共享内存
    ■ 每次计算多个CCC的元素(矩阵块)
    ● 平铺矩阵乘法 20:27

    ○ 核心思想:将矩阵乘法分解为多个阶段,每个阶段集中处理数据的一个子集(tile)。
    ○ 分块计算原理:
    ■ 计算CCC中一个block_size×block_sizeblock_size \times block_sizeblock_size×block_size的块需要对应AAA的行块和BBB的列块
    ■ 将大矩阵乘法分解为多个小矩阵块相乘再累加:Cblock=Ablue×Bblue+Aorange×Borange+…C
    {block} = A_{blue} \times B_{blue} + A_{orange} \times B_{orange} + …Cblock=Ablue×Bblue+Aorange×Borange+…
    ○ 内存访问优化:
    ■ 原始方法需要block_size2×2kblock_size^2 \times 2kblock_size2×2k次全局内存访问
    ■ 平铺方法只需2k×block_size2k \times block_size2k×block_size次全局内存访问
    ■ 理论加速比为block_sizeblock_sizeblock_size倍(实际略低)
    ○ 平铺矩阵乘法核函数 25:59

    ■ 共享内存声明:
    ■ 关键步骤:
    ● 将全局内存数据加载到共享内存
    ● 使用__syncthreads()确保所有线程完成加载
    ● 从共享内存读取数据进行计算
    ● 再次同步确保计算完成
    ■ 索引计算:
    ● 行索引:i = blockIdx.y * blockDim.y + threadIdx.y
    ● 列索引:j = blockIdx.x * blockDim.x + threadIdx.x
    ■ 循环结构:
    ● 外层循环遍历所有tile(K/block_sizeK/block_sizeK/block_size次)
    ● 内层循环计算单个tile的乘积累加
    ■ 同步要求:每次共享内存加载后必须同步
    ● 性能分析
    ○ 全局内存访问次数:
    ■ 原始矩阵乘法:2mnk2mnk2mnk次
    ■ 平铺矩阵算法:2mnk/block_size2mnk/block_size2mnk/block_size次
    ○ 实际加速比:
    ■ 理论最大加速比:block_sizeblock_sizeblock_size倍
    ■ 实际加速比略低(考虑同步和共享内存访问开销)
    ○ 优化关键:
    ■ 合理选择block_sizeblock_sizeblock_size值
    ■ 平衡共享内存使用和计算并行度
    五、矩阵乘法文档实现 30:16
  9. CPU矩阵乘法实现 30:27

    ● 矩阵维度:
    ○ A矩阵:M×KM×KM×K
    ○ B矩阵:K×NK×NK×N
    ○ C矩阵:M×NM×NM×N
    ● 实现原理:
    ○ 使用三重循环结构
    ○ 外层两重循环遍历C矩阵每个元素
    ○ 内层循环进行KKK次乘加运算
    ○ 时间复杂度为O(M×N×K)O(M×N×K)O(M×N×K)

    ● 函数参数:
    ○ array_A, array_B, array_C:三个矩阵的内存地址
    ○ M_p, K_p, N_p:对应矩阵维度
    ● 核心算法:
  10. GPU基础版矩阵乘法 31:02

    ● 实现特点:
    ○ 使用二维线程结构
    ○ 每个线程计算C矩阵一个元素
    ○ 线程索引计算:
    ● 存在问题:
    ○ 重复加载相同数据到全局内存
    ○ 单线程仅计算一个元素,效率较低
  11. GPU共享内存矩阵乘法优化 31:43

    ● 优化措施:
    ○ 使用共享内存减少全局内存访问
    ○ 采用平铺矩阵算法
    ○ 定义共享内存:
    ● 实现流程:
    ○ 计算线程对应的行列索引
    ○ 循环处理每个矩阵块
    ○ 将数据从全局内存拷贝到共享内存
    ○ 执行同步操作__syncthreads()
    ○ 计算矩阵块乘积
    ○ 再次同步后写入结果

    ● 边界处理:
    ○ 超出矩阵范围的元素置为0
    ○ 条件判断:
  12. 主函数与编译 32:51

    ● 主要流程:
    ○ 定义矩阵维度:M=512M=512M=512,K=512K=512K=512,N=512N=512N=512
    ○ 内存分配与初始化
    ○ CPU矩阵乘法计时
    ○ GPU内存分配与数据传输
    ○ GPU核函数调用与计时

    ● 编译指令:
  13. 运行时间与性能比较 33:53

    ● 实测性能:
    ○ CPU版本:0.78秒
    ○ GPU基础版:0.08秒
    ○ GPU共享内存版:0.006秒
    ● 性能分析:
    ○ 内存拷贝和同步操作影响实际加速比
    ○ 建议单独测量核函数执行时间
    六、作业 34:40

    ● 作业1:
    ○ 测试不同矩阵尺寸(64×64, 512×512)下三种实现的运行时间
    ○ 填写性能对比表格
    ● 作业2:
    ○ 研究BLOCK_SIZE取值对性能的影响
    ○ 寻找共享内存使用与并行度的最佳平衡点
    七、知识小结
知识点核心内容考试重点/易混淆点难度系数
GPU与CPU基础结构对比GPU缓存小、计算单元多,适合并行计算;CPU适合复杂分支判断GPU适用场景判断标准(内存访问频率、控制复杂度等)⭐⭐
CUDA编程模型核函数(__global__修饰)、线程块与网格组织(三维索引)核函数参数传递(线程块/网格维度定义)⭐⭐⭐
矩阵乘法基础算法时间复杂度O(mnk),元素计算独立性强,适合GPU并行行列匹配限制(a列数=b行数)⭐⭐
基础版GPU矩阵乘法每个线程计算一个c[i][j],全局内存重复读取(2mnk次)性能瓶颈(全局内存访问延迟500周期)⭐⭐⭐
共享内存优化静态/动态申请(__shared__关键字)、平铺矩阵分块计算边界同步问题(__syncthreads()使用时机)⭐⭐⭐⭐
平铺矩阵乘法分块计算(block_size²),全局内存访问降至2mnk/block_sizeblock_size选择平衡(共享内存大小限制 vs 并行效率)⭐⭐⭐⭐
实践对比CPU耗时0.78s vs GPU基础版0.08s vs 共享内存版0.006s显存拷贝时间影响(需排除cudaMemcpy干扰)⭐⭐⭐
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ray Song

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值