【CUDA】矩阵乘法(从简单到进阶)

本文介绍CUDA中矩阵乘的实现,包括直接实现和使用共享内存优化的版本,对比了CPU和GPU上的执行时间,展示了共享内存如何提升计算效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在学习CUDA的过程中,免不了要实现矩阵乘,从而对GPU的运作机制以及如何使用share memory有更深的理解。下面是用FORTRAN对简单矩阵乘和利用分块并将数据放入share memory实现矩阵乘的实现。

! this program demonstates various memory optimzation techniques
! applied to a matrix multiplication.

module dimensions_m

  implicit none

  integer, parameter :: TILE_DIM = 16
  integer, parameter :: NUM_REPS = 100
  integer, parameter :: nx = 256, ny = 256


end module dimensions_m

module kernels_matrix
  use dimensions_m
  implicit none
  contains
!实际没有使用,但通过调用它的输出可以看出数据是如何移动的,
!对于share memory时,每个线程需要搬用全局的哪些数据有帮助
  attributes(global) subroutine matrixMulTestKernel(dA,dB,dC)

    real :: dA(ny,nx),dB(ny,nx),dC(ny,nx)
    real,shared :: tmA(TILE_DIM,TILE_DIM),tmB(TILE_DIM,TILE_DIM)
    integer :: x, y, i,j,tx,ty,ti
    real :: tmc


    tx = threadIdx%x
    ty = threadIdx%y
    x = (blockIdx%x-1) * blockDim%x + threadIdx%x
    y = (blockIdx%y-1) * blockDim%y + threadIdx%y
    tmc = 0.0
    i = TILE_DIM
        if(tx <= nx .and. ty <= ny) then
            tmA(tx,ty) = dA(x,ty+i)
            tmB(tx,ty) = dB(tx+i,ty)
            call syncthreads()
            do j = 1,TILE_DIM
                tmc = tmA(tx,ty) !tmA(tx,j) *tmB(j,ty) + tmc
            end do
            call syncthreads()
        end if
    dC(x,y) = tmc
  end subroutine matrixMulTestKernel
! 简单的矩阵乘实现,基本思路是每个线程获得它在整个线程中的位置,
! 然后计算该点对应的结果即可
  attributes(global) subroutine matrixMulKernel(dA,dB,dC)

    real :: dA(ny,nx),dB(ny,nx),dC(ny,nx)
    integer :: x, y, j

    x = (blockIdx%x-1) * blockDim%x + threadIdx%x
    y = (blockIdx%y-1) * blockDim%y + threadIdx%y

    dC(x,y) = 0.0;
    do j = 1,nx
        dC(x,y) = dA(x,j)*dB(j,y) + dC(x,y)
    end do
  end subroutine matrixMulKernel

!共享内存实现版本,首先将矩阵中的一块放入共享内存,并计算出中间结果,
!接着使用循环的方式,计算出来这块的最终结果。将数据放入共享内存,可以减少
!线程从全局内存中取数据的次数,从而提高计算速度
  attributes(global) subroutine matrixMulSharedKernel(dA,dB,dC)

    real :: dA(ny,nx),dB(ny,nx),dC(ny,nx)
    real,shared :: tmA(TILE_DIM,TILE_DIM),tmB(TILE_DIM,TILE_DIM)
    integer :: x, y, i,j,tx,ty,ti
    real :: tmc


    tx = threadIdx%x
    ty = threadIdx%y
    x = (blockIdx%x-1) * blockDim%x + threadIdx%x
    y = (blockIdx%y-1) * blockDim%y + threadIdx%y
    tmc = 0.0
! 通过循环的方式,计算出最终结果
    do i = 0,nx-1,TILE_DIM
        if(tx+i <= nx .and. ty+i <= ny) then
            tmA(tx,ty) = dA(x,ty+i)
            tmB(tx,ty) = dB(tx+i,y)
            call syncthreads()
            do j = 1,TILE_DIM
                tmc = tmA(tx,j)*tmB(j,ty) + tmc
            end do
            call syncthreads()
        end if
    end do
    dC(x,y) = tmc
  end subroutine matrixMulSharedKernel
end module kernels_matrix

program matrixMulMatrix

  use cudafor
  use dimensions_m
  use kernels_matrix

  implicit none

  type (dim3) :: grid, tBlock
  type (cudaEvent) :: startEvent, stopEvent
  type (cudaDeviceProp) :: prop
  real :: time

  real :: hA(nx,ny), hB(nx,ny), hC(ny,nx),gold(ny,nx)
  real, device :: dA(nx,ny), dB(nx,ny), dC(ny,nx)

  integer :: i,ii, j,k, istat,errflag
  integer :: cputime(0:1),timediff,clock_rate,clock_max

  ! check parameters and calculate execution configuration

!  if (mod(nx, TILE_DIM) /= 0 &
!       .or. mod(ny, TILE_DIM) /= 0) then
!     write(*,*) 'nx and ny must be a multiple of TILE_DIM'
!     stop
!  end if
!

  grid = dim3(nx/TILE_DIM, ny/TILE_DIM, 1)
  tBlock = dim3(TILE_DIM, TILE_DIM, 1)

  ! write parameters

  i = cudaGetDeviceProperties(prop, 0)
  write(*,"(/,'Device Name: ',a)") trim(prop%name)
  write(*,"('Compute Capability: ',i0,'.',i0)") &
       prop%major, prop%minor


  write(*,*)
  write(*,"('Matrix size:', i5, i5, ',  &
        Tile size:', i3, i3)") &
       nx, ny, TILE_DIM, TILE_DIM

  write(*,"('grid:', i4,i4,i4, ',   tBlock:', i4,i4,i4)") &
       grid%x, grid%y, grid%z, tBlock%x, tBlock%y, tBlock%z

  ! initialize data

  ! host

  do j = 1, ny
     do i = 1, nx
        hA(i,j) = i-1
        hB(i,j) = j-1
     enddo
  enddo
! 获得cpu计算的结果
  call system_clock(cputime(0),clock_rate,clock_max)
  do j = 1, ny
     do i = 1, nx
        gold(i,j) = 0
        do k = 1, nx
            gold(i,j) = hA(i,k) * hB(k,j) + gold(i,j)
        end do
     enddo
  enddo
  call system_clock(cputime(1),clock_rate,clock_max)
  timediff = cputime(1) - cputime(0)
       write(*,'(a20,$)') '*** CpuTime(ms):'
       write(*,'(F20.2)') real(timediff)*1000/real(clock_rate)
!  do i = 1, nx
!      do j = 1, ny
!         write(*,"(F10.3,$)") hA(i,j)
!      end do
!      write(*,*)
!  end do
!  write(*,*)
!  do i = 1, nx
!      do j = 1, ny
!         write(*,"(F10.3,$)") hB(i,j)
!      end do
!      write(*,*)
!  end do

  ! device

  dB = hB
  dA = hA

  ! events for timing
  !golbal memory 全局内存实现方式
  write(*,'(a30)') '*** global memory results ***'
  istat = cudaEventCreate(startEvent)
  istat = cudaEventCreate(stopEvent)

  istat = cudaEventRecord(startEvent, 0)
  do i=1, NUM_REPS
     call matrixMulKernel<<<grid, tBlock>>>(dA,dB,dC)
  end do
  istat = cudaEventRecord(stopEvent, 0)
  istat = cudaEventSynchronize(stopEvent)
  istat = cudaEventElapsedTime(time, startEvent, stopEvent)

  hC = dC
  call postprocess(gold,hC,time)

  ! share memory 共享内存实现方式
  write(*,'(a30)') '*** share memory results ***'
  istat = cudaEventCreate(startEvent)
  istat = cudaEventCreate(stopEvent)

  istat = cudaEventRecord(startEvent, 0)
  do i=1, NUM_REPS
     call matrixMulSharedKernel<<<grid, tBlock>>>(dA,dB,dC)
  end do
  istat = cudaEventRecord(stopEvent, 0)
  istat = cudaEventSynchronize(stopEvent)
  istat = cudaEventElapsedTime(time, startEvent, stopEvent)

  hC = dC
  call postprocess(gold,hC,time)

  istat = cudaEventDestroy(startEvent)
  istat = cudaEventDestroy(stopEvent)


! 结果验证函数
contains
  subroutine postprocess(ref, res, t)
    real, intent(in) :: ref(:,:), res(:,:), t
    if (all(res == ref)) then
       write(*,'(a20)') '*** Test Passed ***'
       write(*,'(a20,$)') '*** ElapsedTime(ms):'
       write(*,'(f20.2)') (t/NUM_REPS)
    else
       write(*,'(a20)') '*** Failed ***'
    end if
  end subroutine postprocess


end program matrixMulMatrix

 

<think>嗯,用户想了解矩阵乘法中的算法优化。首先,我得回忆一下矩阵乘法的基础知识。矩阵乘法是计算机科学和数学中的基础操作,广泛应用于图形学、机器学习等领域。标准的矩阵乘法算法时间复杂度是O(),但随着矩阵规模增大,这个复杂度变得很高,所以需要优化。 接下来,我得考虑常见的优化方法。比如分块算法(Block Matrix Multiplication),把大矩阵分成小块,利用缓存局部性提高效率。可能用户听说过Strassen算法,它通过分治策略将复杂度降到O(n^2.81),但实际应用中常数因子较大,可能适用于大矩阵。 还有循环展开、SIMD指令、多线程并行等方法。硬件层面的优化,比如使用GPU加速,尤其是深度学习中的矩阵运算。此外,内存访问模式也很重要,按行访问还是列访问会影响性能,可能需要调整循环顺序。 用户可能是程序员或者学生,想了解如何优化矩阵乘法的性能。可能实际应用在编程项目或算法学习中。需要分层次讲解,从算法到硬件优化,可能还要提到一些实际库的实现,比如BLAS、OpenBLAS或者CUDA中的优化。 需要检查是否覆盖了主要优化策略,确保术语准确,比如分块、Strassen、并行计算、内存布局等。同时,要避免过于深入数学证明,保持实用导向。可能还要提到实际中的权衡,比如Strassen算法虽然理论复杂度低,但常数大,可能在小矩阵上不如标准算法快。 最后,总结各种方法的适用场景,帮助用户根据具体情况选择优化策略。确保回答结构清晰,步骤分明,容易理解。</think>矩阵乘法是计算机科学中的核心操作,其优化对科学计算、深度学习等领域至关重要。以下从基础到进阶逐步解析优化思路: 一、标准算法分析(时间复杂度 $O(n^3)$) $$\text{对于 } C = A \times B,\ c_{ij} = \sum_{k=1}^{n} a_{ik}b_{kj}$$ 三重循环结构: ```c for i from 0 to n-1 for j from 0 to n-1 for k from 0 to n-1 C[i][j] += A[i][k] * B[k][j] ``` 二、基础优化策略 1. **循环顺序调整**(提升缓存命中率) - 将最内层循环改为连续内存访问: ```c for i from 0 to n-1 for k from 0 to n-1 for j from 0 to n-1 C[i][j] += A[i][k] * B[k][j] // B按行访问 ``` 2. **分块技术(Blocking)** - 将矩阵划分为大小为 $B \times B$ 的子块: $$C_{block} = \sum A_{block} \times B_{block}$$ - 典型块大小选择与CPU缓存匹配(如L1 Cache 32KB时选 $B=64$) 三、高级算法优化 1. **Strassen算法**(分治法优化) - 时间复杂度:$O(n^{\log_2 7}) \approx O(n^{2.807})$ - 核心思想:通过7次子矩阵乘法替代8次标准乘法 $$\begin{cases} M_1 = (A_{11}+A_{22})(B_{11}+B_{22}) \\ M_2 = (A_{21}+A_{22})B_{11} \\ ... \\ C_{11} = M_1 + M_4 - M_5 + M_7 \end{cases}$$ 2. **Coppersmith-Winograd算法**(理论最优) - 时间复杂度:$O(n^{2.376})$(常数项过大,实际应用受限) 四、硬件级优化 1. **SIMD向量化** - 使用AVX/NEON指令集并行处理多个浮点运算 - 示例:同时计算4个float(AVX2)或8个float(AVX512) 2. **多线程并行** - OpenMP实现线程级并行: ```c #pragma omp parallel for for (int i = 0; i < n; i++) { // 计算第i行 } ``` 3. **GPU加速** - 使用CUDA实现大规模并行: ```cpp __global__ void matmul_kernel(float *A, float *B, float *C, int n) { int row = blockIdx.y * blockDim.y + threadIdx.y; int col = blockIdx.x * blockDim.x + threadIdx.x; if (row < n && col < n) { float sum = 0; for (int k = 0; k < n; k++) { sum += A[row*n + k] * B[k*n + col]; } C[row*n + col] = sum; } } ``` 五、内存布局优化 1. **数据对齐**(64字节对齐适应缓存行) 2. **矩阵转置存储**(将B矩阵转置为按列存储) 3. **结构体数组转数组结构体**(AOS转SOA) 六、实际工程实践 1. **BLAS库优化** - OpenBLAS:使用汇编级优化针对不同CPU架构 - Intel MKL:利用AVX-512指令集 2. **自动调优技术** - 使用AutoTVM等工具自动选择最佳分块策略 典型性能对比($n=4096$): | 方法 | 执行时间 | 加速比 | |---------------|-----------|--------| | 原始三重循环 | 120s | 1x | | 分块+SIMD | 8.2s | 14.6x | | OpenBLAS | 0.75s | 160x | | GPU(V100) | 0.012s | 10,000x| 选择策略建议: 1. 小矩阵(n<64):SIMD+分块优化 2. 中型矩阵(64<n<2048):多线程+缓存优化 3. 大型矩阵:使用GPU加速或分布式计算
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值