SELF_MatrixMultiply.cpp Source File


Contents


Source Code

#include "SELF_GPU_Macros.h"

// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //
//
// SELF_MatrixMultiply.cpp
//
// Hand-written CUDA/HIP tensor-product contraction kernels that replace the
// cuBLAS/hipBLAS (Xgemm / XgemvStridedBatched) matrix operators previously
// used for spectral interpolation and differentiation.
//
// All operators act on nodal, column-major (Fortran) data. The operator
// matrix A is stored column-major with leading dimension (Nc+1), so that
//     A[k + (Nc+1)*a]
// maps control node k -> target node a. This matches the storage used for
// interp (iMatrix), strong-derivative (dMatrix), DG-derivative (dgMatrix) and
// boundary (bMatrix) matrices constructed in SELF_Lagrange_t.f90.
//
// Every kernel sums its contractions in an explicit, fixed loop order that
// matches the CPU reference algorithms in the *_t.f90 templates. Results may
// therefore differ from the previous BLAS path at the round-off level, but are
// consistent with the CPU backend.
//
// Kernels are grid-strided over the quadrature/target points so that they
// support arbitrary polynomial degree N (control) and M (target) without the
// fixed-thread-count limitations present in some earlier kernels.
//
// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //

#define SELF_MATOP_THREADS 256

// ---------------------------------------------------------------------------
// 1-D operator application:  Af = A^T f
//
//   Af[a,c] = sum_{k=0}^{opAcols-1} A[k + opAcols*a] * f[k + opAcols*c]
//
// with a in [0,opArows), c in [0,ncol). One thread-block handles one column c
// (a single (element,variable) pair); the operator matrix is staged in shared
// memory and reused across all output rows of that column. Covers 1-D grid
// interpolation (A=iMatrix, opArows=M+1), strong/DG derivative (A=dMatrix or
// dgMatrix, opArows=N+1) and boundary interpolation (A=bMatrix, opArows=2).
// ---------------------------------------------------------------------------
__global__ void MatrixOp_1D_gpukernel(real *A, real *f, real *Af, int opArows, int opAcols, int ncol){

  int c = blockIdx.x; // column index (element*variable)
  if( c >= ncol ) return;

  extern __shared__ real sA[]; // opAcols*opArows entries
  for(int idx = threadIdx.x; idx < opAcols*opArows; idx += blockDim.x){
    sA[idx] = A[idx];
  }
  __syncthreads();

  for(int a = threadIdx.x; a < opArows; a += blockDim.x){
    real acc = 0.0;
    for(int k = 0; k < opAcols; k++){
      acc += sA[k + opAcols*a]*f[k + opAcols*c];
    }
    Af[a + opArows*c] = acc;
  }
}

extern "C"
{
  void MatrixOp_1D_gpu(real *A, real *f, real *Af, int opArows, int opAcols, int ncol){
    int nthreads = SELF_MATOP_THREADS;
    size_t smem = (size_t)opArows*opAcols*sizeof(real);
    MatrixOp_1D_gpukernel<<<dim3(ncol,1,1), dim3(nthreads,1,1), smem, 0>>>(A,f,Af,opArows,opAcols,ncol);
  }
}

// ---------------------------------------------------------------------------
// 2-D grid interpolation (tensor product), control degree N -> target degree M
//
//   fInterp[a,b] = sum_{i,j} A[i+(N+1)*a] * A[j+(N+1)*b] * f[i,j]
//
// One block per (element,variable); the interpolation matrix is staged in
// shared memory. Threads grid-stride over the (M+1)^2 target points.
// ---------------------------------------------------------------------------
__global__ void GridInterp_2D_gpukernel(real *A, real *f, real *fInterp, int N, int M, int nel, int nvar){

  int iel = blockIdx.x;
  int ivar = blockIdx.y;
  int nq_in = (N+1)*(N+1);
  int nq_out = (M+1)*(M+1);
  int fbase = nq_in*(iel + nel*ivar);
  int gbase = nq_out*(iel + nel*ivar);

  extern __shared__ real sA[]; // (N+1)*(M+1)
  for(int idx = threadIdx.x; idx < (N+1)*(M+1); idx += blockDim.x){
    sA[idx] = A[idx];
  }
  __syncthreads();

  for(int iq = threadIdx.x; iq < nq_out; iq += blockDim.x){
    int a = iq % (M+1);
    int b = iq/(M+1);
    real acc = 0.0;
    for(int i = 0; i < N+1; i++){
      real tmp = 0.0;
      for(int j = 0; j < N+1; j++){
        tmp += sA[j + (N+1)*b]*f[i + (N+1)*j + fbase];
      }
      acc += sA[i + (N+1)*a]*tmp;
    }
    fInterp[a + (M+1)*b + gbase] = acc;
  }
}

extern "C"
{
  void GridInterp_2D_gpu(real *A, real *f, real *fInterp, int N, int M, int nvar, int nel){
    size_t smem = (size_t)(N+1)*(M+1)*sizeof(real);
    GridInterp_2D_gpukernel<<<dim3(nel,nvar,1), dim3(SELF_MATOP_THREADS,1,1), smem, 0>>>(A,f,fInterp,N,M,nel,nvar);
  }
}

// ---------------------------------------------------------------------------
// 3-D grid interpolation (tensor product), control degree N -> target degree M
//
//   fInterp[a,b,c] = sum_{i,j,k} A[i+(N+1)*a]*A[j+(N+1)*b]*A[k+(N+1)*c]*f[i,j,k]
// ---------------------------------------------------------------------------
__global__ void GridInterp_3D_gpukernel(real *A, real *f, real *fInterp, int N, int M, int nel, int nvar){

  int iel = blockIdx.x;
  int ivar = blockIdx.y;
  int nq_in = (N+1)*(N+1)*(N+1);
  int nq_out = (M+1)*(M+1)*(M+1);
  int fbase = nq_in*(iel + nel*ivar);
  int gbase = nq_out*(iel + nel*ivar);

  extern __shared__ real sA[]; // (N+1)*(M+1)
  for(int idx = threadIdx.x; idx < (N+1)*(M+1); idx += blockDim.x){
    sA[idx] = A[idx];
  }
  __syncthreads();

  for(int iq = threadIdx.x; iq < nq_out; iq += blockDim.x){
    int a = iq % (M+1);
    int b = (iq/(M+1)) % (M+1);
    int c = iq/(M+1)/(M+1);
    real acc = 0.0;
    for(int k = 0; k < N+1; k++){
      real tk = 0.0;
      for(int j = 0; j < N+1; j++){
        real tj = 0.0;
        for(int i = 0; i < N+1; i++){
          tj += sA[i + (N+1)*a]*f[i + (N+1)*(j + (N+1)*k) + fbase];
        }
        tk += sA[j + (N+1)*b]*tj;
      }
      acc += sA[k + (N+1)*c]*tk;
    }
    fInterp[a + (M+1)*(b + (M+1)*c) + gbase] = acc;
  }
}

extern "C"
{
  void GridInterp_3D_gpu(real *A, real *f, real *fInterp, int N, int M, int nvar, int nel){
    size_t smem = (size_t)(N+1)*(M+1)*sizeof(real);
    GridInterp_3D_gpukernel<<<dim3(nel,nvar,1), dim3(SELF_MATOP_THREADS,1,1), smem, 0>>>(A,f,fInterp,N,M,nel,nvar);
  }
}

// ---------------------------------------------------------------------------
// 2-D scalar gradient (strong form), control degree N -> N.
//
//   df[i,j,0] = sum_a A[a+(N+1)*i] * f[a,j]   (d/dxi^1)
//   df[i,j,1] = sum_a A[a+(N+1)*j] * f[i,a]   (d/dxi^2)
//
// The two directional derivatives are written to separate direction slots
// (VE_2D layout). Used for scalar gradients (A=dMatrix) and, with an effective
// variable count of 2*nvar, vector gradients.
// ---------------------------------------------------------------------------
__global__ void ScalarGradient_2D_gpukernel(real *A, real *f, real *df, int N, int nel, int nvar){

  int iel = blockIdx.x;
  int ivar = blockIdx.y;
  int nq = (N+1)*(N+1);
  int base = nq*(iel + nel*ivar);

  extern __shared__ real sA[]; // (N+1)*(N+1)
  for(int idx = threadIdx.x; idx < (N+1)*(N+1); idx += blockDim.x){
    sA[idx] = A[idx];
  }
  __syncthreads();

  for(int iq = threadIdx.x; iq < nq; iq += blockDim.x){
    int i = iq % (N+1);
    int j = iq/(N+1);
    real d1 = 0.0;
    real d2 = 0.0;
    for(int a = 0; a < N+1; a++){
      d1 += sA[a + (N+1)*i]*f[a + (N+1)*j + base];
      d2 += sA[a + (N+1)*j]*f[i + (N+1)*a + base];
    }
    // VE_2D_INDEX(i,j,iel,ivar,idir,N,nel,nvar)
    df[VE_2D_INDEX(i,j,iel,ivar,0,N,nel,nvar)] = d1;
    df[VE_2D_INDEX(i,j,iel,ivar,1,N,nel,nvar)] = d2;
  }
}

extern "C"
{
  void ScalarGradient_2D_gpu(real *A, real *f, real *df, int N, int nvar, int nel){
    size_t smem = (size_t)(N+1)*(N+1)*sizeof(real);
    ScalarGradient_2D_gpukernel<<<dim3(nel,nvar,1), dim3(SELF_MATOP_THREADS,1,1), smem, 0>>>(A,f,df,N,nel,nvar);
  }
}

// ---------------------------------------------------------------------------
// 3-D scalar gradient (strong form), control degree N -> N.
//
//   df[i,j,k,0] = sum_a A[a+(N+1)*i]*f[a,j,k]
//   df[i,j,k,1] = sum_a A[a+(N+1)*j]*f[i,a,k]
//   df[i,j,k,2] = sum_a A[a+(N+1)*k]*f[i,j,a]
// ---------------------------------------------------------------------------
__global__ void ScalarGradient_3D_gpukernel(real *A, real *f, real *df, int N, int nel, int nvar){

  int iel = blockIdx.x;
  int ivar = blockIdx.y;
  int nq = (N+1)*(N+1)*(N+1);
  int base = nq*(iel + nel*ivar);

  extern __shared__ real sA[]; // (N+1)*(N+1)
  for(int idx = threadIdx.x; idx < (N+1)*(N+1); idx += blockDim.x){
    sA[idx] = A[idx];
  }
  __syncthreads();

  for(int iq = threadIdx.x; iq < nq; iq += blockDim.x){
    int i = iq % (N+1);
    int j = (iq/(N+1)) % (N+1);
    int k = iq/(N+1)/(N+1);
    real d1 = 0.0;
    real d2 = 0.0;
    real d3 = 0.0;
    for(int a = 0; a < N+1; a++){
      d1 += sA[a + (N+1)*i]*f[a + (N+1)*(j + (N+1)*k) + base];
      d2 += sA[a + (N+1)*j]*f[i + (N+1)*(a + (N+1)*k) + base];
      d3 += sA[a + (N+1)*k]*f[i + (N+1)*(j + (N+1)*a) + base];
    }
    df[VE_3D_INDEX(i,j,k,iel,ivar,0,N,nel,nvar)] = d1;
    df[VE_3D_INDEX(i,j,k,iel,ivar,1,N,nel,nvar)] = d2;
    df[VE_3D_INDEX(i,j,k,iel,ivar,2,N,nel,nvar)] = d3;
  }
}

extern "C"
{
  void ScalarGradient_3D_gpu(real *A, real *f, real *df, int N, int nvar, int nel){
    size_t smem = (size_t)(N+1)*(N+1)*sizeof(real);
    ScalarGradient_3D_gpukernel<<<dim3(nel,nvar,1), dim3(SELF_MATOP_THREADS,1,1), smem, 0>>>(A,f,df,N,nel,nvar);
  }
}

// ---------------------------------------------------------------------------
// 2-D vector divergence (strong or DG form), control degree N -> N.
//
//   df[i,j] = sum_a A[a+(N+1)*i]*f[a,j,dir=0]
//           + sum_a A[a+(N+1)*j]*f[i,a,dir=1]
//
// f is a vector field laid out with a trailing direction index (VE_2D); the
// scalar result df uses SC_2D layout. Used for the mapped (DG-)gradient, where
// f is the contravariant-weighted field jas. A=dMatrix (strong) or dgMatrix.
// ---------------------------------------------------------------------------
__global__ void VectorDivergence_2D_gpukernel(real *A, real *f, real *df, int N, int nel, int nvar){

  int iel = blockIdx.x;
  int ivar = blockIdx.y;
  int nq = (N+1)*(N+1);

  extern __shared__ real sA[]; // (N+1)*(N+1)
  for(int idx = threadIdx.x; idx < (N+1)*(N+1); idx += blockDim.x){
    sA[idx] = A[idx];
  }
  __syncthreads();

  for(int iq = threadIdx.x; iq < nq; iq += blockDim.x){
    int i = iq % (N+1);
    int j = iq/(N+1);
    real acc = 0.0;
    for(int a = 0; a < N+1; a++){
      acc += sA[a + (N+1)*i]*f[VE_2D_INDEX(a,j,iel,ivar,0,N,nel,nvar)]
           + sA[a + (N+1)*j]*f[VE_2D_INDEX(i,a,iel,ivar,1,N,nel,nvar)];
    }
    df[SC_2D_INDEX(i,j,iel,ivar,N,nel)] = acc;
  }
}

extern "C"
{
  void VectorDivergence_2D_gpu(real *A, real *f, real *df, int N, int nvar, int nel){
    size_t smem = (size_t)(N+1)*(N+1)*sizeof(real);
    VectorDivergence_2D_gpukernel<<<dim3(nel,nvar,1), dim3(SELF_MATOP_THREADS,1,1), smem, 0>>>(A,f,df,N,nel,nvar);
  }
}

// ---------------------------------------------------------------------------
// 3-D vector divergence (strong or DG form), control degree N -> N.
//
//   df[i,j,k] = sum_a A[a+(N+1)*i]*f[a,j,k,dir=0]
//             + sum_a A[a+(N+1)*j]*f[i,a,k,dir=1]
//             + sum_a A[a+(N+1)*k]*f[i,j,a,dir=2]
// ---------------------------------------------------------------------------
__global__ void VectorDivergence_3D_gpukernel(real *A, real *f, real *df, int N, int nel, int nvar){

  int iel = blockIdx.x;
  int ivar = blockIdx.y;
  int nq = (N+1)*(N+1)*(N+1);

  extern __shared__ real sA[]; // (N+1)*(N+1)
  for(int idx = threadIdx.x; idx < (N+1)*(N+1); idx += blockDim.x){
    sA[idx] = A[idx];
  }
  __syncthreads();

  for(int iq = threadIdx.x; iq < nq; iq += blockDim.x){
    int i = iq % (N+1);
    int j = (iq/(N+1)) % (N+1);
    int k = iq/(N+1)/(N+1);
    real acc = 0.0;
    for(int a = 0; a < N+1; a++){
      acc += sA[a + (N+1)*i]*f[VE_3D_INDEX(a,j,k,iel,ivar,0,N,nel,nvar)]
           + sA[a + (N+1)*j]*f[VE_3D_INDEX(i,a,k,iel,ivar,1,N,nel,nvar)]
           + sA[a + (N+1)*k]*f[VE_3D_INDEX(i,j,a,iel,ivar,2,N,nel,nvar)];
    }
    df[SC_3D_INDEX(i,j,k,iel,ivar,N,nel)] = acc;
  }
}

extern "C"
{
  void VectorDivergence_3D_gpu(real *A, real *f, real *df, int N, int nvar, int nel){
    size_t smem = (size_t)(N+1)*(N+1)*sizeof(real);
    VectorDivergence_3D_gpukernel<<<dim3(nel,nvar,1), dim3(SELF_MATOP_THREADS,1,1), smem, 0>>>(A,f,df,N,nel,nvar);
  }
}