In case data is stored in host memory in row major order and we wish to perform matrix multiplication and retrieve the data back in row major order, the below code does that
#include <stdio.h>
#include <cuda_runtime.h>
#include <cublas_v2.h>
#include <assert.h>
#define ROWM 4
#define COLM 3
#define COLN 5
#define cudaCheckErrors(msg) \
do { \
cudaError_t __err = cudaGetLastError(); \
if (__err != cudaSuccess) { \
fprintf(stderr, "Fatal error: %s (%s at %s:%d)\n", \
msg, cudaGetErrorString(__err), \
__FILE__, __LINE__); \
fprintf(stderr, "*** FAILED - ABORTING\n"); \
exit(1); \
} \
} while (0)
void printArrayS(float *ptr, int rows, int cols, char mode, char *name)
{
printf("%s\n", name);
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
if (mode == 'N') /* Normal mode */
{
if (ptr[i * cols + j] >= 0)
printf(" %3.6f ", ptr[i * cols + j]);
else
printf("%3.6f ", ptr[i * cols + j]);
}
else /* Transpose mode */
{
if (ptr[j * rows + i] >= 0)
printf("%3.6f ", ptr[j * rows + i]);
else
printf("%3.6f ", ptr[j * rows + i]);
}
}
printf("\n");
}
}
typedef float mytype;
// Pi = Mi x Ni
// pr = P rows = M rows
// pc = P cols = N cols
// mc = M cols = N rows
void GPU_Multi(mytype **M, mytype **N, mytype **P,
size_t pr, size_t pc, size_t mc,
size_t num_mat, mytype alpha, mytype beta)
{
#define NUM_MAT 2
mytype *devM[NUM_MAT];
mytype *devN[NUM_MAT];
mytype *devP[NUM_MAT];
size_t p_size = sizeof(mytype) * pr * pc;
size_t m_size = sizeof(mytype) * pr * mc;
size_t n_size = sizeof(mytype) * mc * pc;
const mytype **d_Marray, **d_Narray;
mytype **d_Parray;
cublasHandle_t myhandle;
cublasStatus_t cublas_result;
for (int i = 0; i < NUM_MAT; i++)
{
cudaMalloc((void **)&devM[i], m_size);
cudaMalloc((void **)&devN[i], n_size);
cudaMalloc((void **)&devP[i], p_size);
}
cudaMalloc((void **)&d_Marray, NUM_MAT * sizeof(mytype *));
cudaMalloc((void **)&d_Narray, NUM_MAT * sizeof(mytype *));
cudaMalloc((void **)&d_Parray, NUM_MAT * sizeof(mytype *));
cudaCheckErrors("cudaMalloc fail");
for (int i = 0; i < NUM_MAT; i++) {
cudaMemcpy(devM[i], M[i], m_size, cudaMemcpyHostToDevice);
cudaMemcpy(devN[i], N[i], n_size, cudaMemcpyHostToDevice);
cudaMemcpy(devP[i], P[i], p_size, cudaMemcpyHostToDevice);
}
cudaMemcpy(d_Marray, devM, NUM_MAT * sizeof(mytype *), cudaMemcpyHostToDevice);
cudaMemcpy(d_Narray, devN, NUM_MAT * sizeof(mytype *), cudaMemcpyHostToDevice);
cudaMemcpy(d_Parray, devP, NUM_MAT * sizeof(mytype *), cudaMemcpyHostToDevice);
cudaCheckErrors("cudaMemcpy H2D fail");
cublas_result = cublasCreate(&myhandle);
assert(cublas_result == CUBLAS_STATUS_SUCCESS);
// change to cublasDgemmBatched for double
cublas_result = cublasSgemmBatched(myhandle, CUBLAS_OP_N, CUBLAS_OP_N
, pc, pr, mc
, &alpha, d_Narray, pc, d_Marray, mc
, &beta, d_Parray, pc
, NUM_MAT);
assert(cublas_result == CUBLAS_STATUS_SUCCESS);
for (int i = 0; i < NUM_MAT; i++)
{
cudaMemcpy(P[i], devP[i], p_size, cudaMemcpyDeviceToHost);
cudaFree(devM[i]);
cudaFree(devN[i]);
cudaFree(devP[i]);
}
cudaFree(d_Marray);
cudaFree(d_Narray);
cudaFree(d_Parray);
cudaCheckErrors("cudaMemcpy D2H fail");
}
int main() {
mytype h_M1[ROWM][COLM], h_M2[ROWM][COLM];
mytype h_N1[COLM][COLN], h_N2[COLM][COLN];
mytype h_P1[ROWM][COLN], h_P2[ROWM][COLN];
mytype *h_Marray[2], *h_Narray[2], *h_Parray[2];
for (int i = 0; i < ROWM; i++)
for (int j = 0; j < COLM; j++) {
h_M1[i][j] = (i + j) * 1.0f; h_M2[i][j] = (i - j) * 2.0f;
}
for (int i = 0; i < COLM; i++)
for (int j = 0; j < COLN; j++) {
h_N1[i][j] = (i + j) * 1.0f; h_N2[i][j] = (i - j) * 1.0f;
}
for (int i = 0; i < ROWM; i++)
for (int j = 0; j < COLN; j++) {
h_P1[i][j] = 0.0f; h_P2[i][j] = 0.0f;
}
printArrayS((float *)h_M1, ROWM, COLM, 'N', "h_M1");
printArrayS((float *)h_N1, COLM, COLN, 'N', "h_N1");
printArrayS((float *)h_M2, ROWM, COLM, 'N', "h_M2");
printArrayS((float *)h_N2, COLM, COLN, 'N', "h_N2");
h_Marray[0] = &(h_M1[0][0]);
h_Marray[1] = &(h_M2[0][0]);
h_Narray[0] = &(h_N1[0][0]);
h_Narray[1] = &(h_N2[0][0]);
h_Parray[0] = &(h_P1[0][0]);
h_Parray[1] = &(h_P2[0][0]);
GPU_Multi(h_Marray, h_Narray, h_Parray, ROWM, COLN, COLM, 2, 1.0f, 0.0f);
printArrayS((float *)h_P1, ROWM, COLN, 'N', "h_P1");
printArrayS((float *)h_P2, ROWM, COLN, 'N', "h_P2");
return 0;
}
Result
h_M1
0.000000 1.000000 2.000000
1.000000 2.000000 3.000000
2.000000 3.000000 4.000000
3.000000 4.000000 5.000000
h_N1
0.000000 1.000000 2.000000 3.000000 4.000000
1.000000 2.000000 3.000000 4.000000 5.000000
2.000000 3.000000 4.000000 5.000000 6.000000
h_M2
0.000000 -2.000000 -4.000000
2.000000 0.000000 -2.000000
4.000000 2.000000 0.000000
6.000000 4.000000 2.000000
h_N2
0.000000 -1.000000 -2.000000 -3.000000 -4.000000
1.000000 0.000000 -1.000000 -2.000000 -3.000000
2.000000 1.000000 0.000000 -1.000000 -2.000000
h_P1
5.000000 8.000000 11.000000 14.000000 17.000000
8.000000 14.000000 20.000000 26.000000 32.000000
11.000000 20.000000 29.000000 38.000000 47.000000
14.000000 26.000000 38.000000 50.000000 62.000000
h_P2
-10.000000 -4.000000 2.000000 8.000000 14.000000
-4.000000 -4.000000 -4.000000 -4.000000 -4.000000
2.000000 -4.000000 -10.000000 -16.000000 -22.000000
8.000000 -4.000000 -16.000000 -28.000000 -40.000000
3.5, then, for your10,4x4matrices case, you could create a grid of10threads, each performing acuBLAScall from within a kernel. Otherwise, you should loop overcuBLAScalls from the host. - Vitality