gemm

GEMM μ΄λž€?

참고 자료 : https://petewarden.com/2015/04/20/why-gemm-is-at-the-heart-of-deep-learning/

  • General Matrix to Matrix Multiplication

  • 1979년에 λ§Œλ“€μ–΄μ§„ BLAS 라이브러리의 일뢀 μž…λ‹ˆλ‹€.

  • λ‘κ°œμ˜ μž…λ ₯ 행렬을 κ³±ν•΄μ„œ 좜λ ₯을 μ–»λŠ” 방법 μž…λ‹ˆλ‹€.

λ”₯λŸ¬λ‹μ—μ„œ λŒ€λΆ€λΆ„μ˜ 연산은 output = input * weight + bias둜 ν‘œν˜„μ΄ λ©λ‹ˆλ‹€. μ—¬κΈ°μ„œ input, output, weightλ₯Ό ν–‰λ ¬λ‘œ ν‘œν˜„ν•΄μ„œ GEMM을 μ‚¬μš©ν•΄ μ—°μ‚°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Fully Connected Layer

fully connected layerλŠ” μœ„μ™€ 같이 ν‘œν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Convolutional Layer

  • im2col : 3차원 이미지 배열을 2차원 λ°°μ—΄λ‘œ λ³€ν™˜ν•©λ‹ˆλ‹€.

convolutional layerλŠ” μœ„μ™€ 같이 ν‘œν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μœ„ 그림의 κ²½μš°λŠ” strideκ°€ kernel size와 같은 경우λ₯Ό μ˜λ―Έν•©λ‹ˆλ‹€.


gemm.c

gemm

gemm(0,0,m,n,k,1,a,k,b,n,1,c,n);

void gemm(int TA, int TB, int M, int N, int K, float ALPHA,
        float *A, int lda,
        float *B, int ldb,
        float BETA,
        float *C, int ldc)
{
    gemm_cpu(TA,  TB,  M, N, K, ALPHA, A, lda, B, ldb, BETA, C, ldc);
}

ν•¨μˆ˜ 이름: gemm

μž…λ ₯:

  • int TA: ν–‰λ ¬ A의 μ „μΉ˜ μ—¬λΆ€ (0: μ „μΉ˜ν•˜μ§€ μ•ŠμŒ, 1: μ „μΉ˜ν•¨)

  • int TB: ν–‰λ ¬ B의 μ „μΉ˜ μ—¬λΆ€ (0: μ „μΉ˜ν•˜μ§€ μ•ŠμŒ, 1: μ „μΉ˜ν•¨)

  • int M: ν–‰λ ¬ C의 ν–‰μ˜ 수

  • int N: ν–‰λ ¬ C의 μ—΄μ˜ 수

  • int K: ν–‰λ ¬ A의 μ—΄μ˜ 수 (ν–‰λ ¬ B의 ν–‰μ˜ μˆ˜μ™€ κ°™μ•„μ•Ό 함)

  • float ALPHA: 슀칼라 κ°’

  • float *A: ν–‰λ ¬ A의 포인터

  • int lda: ν–‰λ ¬ A의 ν–‰ λ‹¨μœ„ 크기

  • float *B: ν–‰λ ¬ B의 포인터

  • int ldb: ν–‰λ ¬ B의 ν–‰ λ‹¨μœ„ 크기

  • float BETA: 슀칼라 κ°’

  • float *C: ν–‰λ ¬ C의 포인터

  • int ldc: ν–‰λ ¬ C의 ν–‰ λ‹¨μœ„ 크기

λ™μž‘:

  • ν–‰λ ¬-ν–‰λ ¬ κ³±μ…ˆ 연산을 μˆ˜ν–‰ν•¨.

μ„€λͺ…:

  • 이 ν•¨μˆ˜λŠ” CPU μƒμ—μ„œ ν–‰λ ¬-ν–‰λ ¬ κ³±μ…ˆ 연산을 μˆ˜ν–‰ν•˜λŠ” ν•¨μˆ˜μ΄λ‹€.

  • gemm_cpu ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜μ—¬ 이 연산을 μˆ˜ν–‰ν•œλ‹€.

  • ν–‰λ ¬ A와 ν–‰λ ¬ B의 크기와 μ „μΉ˜ μ—¬λΆ€, 슀칼라 κ°’ ALPHA와 BETA 등을 μž…λ ₯으둜 λ°›κ³ , μ—°μ‚° 결과인 ν–‰λ ¬ Cλ₯Ό 좜λ ₯으둜 λ°˜ν™˜ν•œλ‹€.

gemm_cpu

void gemm_cpu(int TA, int TB, int M, int N, int K, float ALPHA,
        float *A, int lda,
        float *B, int ldb,
        float BETA,
        float *C, int ldc)
{
    //printf("cpu: %d %d %d %d %d %f %d %d %f %d\n",TA, TB, M, N, K, ALPHA, lda, ldb, BETA, ldc);
    int i, j;
    for(i = 0; i < M; ++i){
        for(j = 0; j < N; ++j){
            C[i*ldc + j] *= BETA;
        }
    }
    if(!TA && !TB)
        gemm_nn(M, N, K, ALPHA, A,lda, B, ldb, C, ldc);
    else if(TA && !TB)
        gemm_tn(M, N, K, ALPHA, A,lda, B, ldb, C, ldc);
    else if(!TA && TB)
        gemm_nt(M, N, K, ALPHA, A,lda, B, ldb, C, ldc);
    else
        gemm_tt(M, N, K, ALPHA, A,lda, B, ldb, C, ldc);
}

ν•¨μˆ˜ 이름: gemm_cpu

μž…λ ₯:

  • int TA: A ν–‰λ ¬μ˜ μ „μΉ˜ μ—¬λΆ€λ₯Ό λ‚˜νƒ€λ‚΄λŠ” ν”Œλž˜κ·Έ

  • int TB: B ν–‰λ ¬μ˜ μ „μΉ˜ μ—¬λΆ€λ₯Ό λ‚˜νƒ€λ‚΄λŠ” ν”Œλž˜κ·Έ

  • int M: C ν–‰λ ¬μ˜ ν–‰ 수

  • int N: C ν–‰λ ¬μ˜ μ—΄ 수

  • int K: A, B ν–‰λ ¬μ—μ„œ κ³΅μœ ν•˜λŠ” μ°¨μ›μ˜ 크기

  • float ALPHA: A, B ν–‰λ ¬μ˜ 곱에 λŒ€ν•œ κ°€μ€‘μΉ˜

  • float *A: A ν–‰λ ¬μ˜ 포인터

  • int lda: A ν–‰λ ¬μ˜ ν–‰ λ‹Ή μ›μ†Œ 수

  • float *B: B ν–‰λ ¬μ˜ 포인터

  • int ldb: B ν–‰λ ¬μ˜ ν–‰ λ‹Ή μ›μ†Œ 수

  • float BETA: C 행렬에 λŒ€ν•œ κ°€μ€‘μΉ˜

  • float *C: C ν–‰λ ¬μ˜ 포인터

  • int ldc: C ν–‰λ ¬μ˜ ν–‰ λ‹Ή μ›μ†Œ 수

λ™μž‘:

  • CPUμ—μ„œ ν–‰λ ¬ κ³±μ…ˆ 연산을 μˆ˜ν–‰ν•œλ‹€.

  • A, B, C μ„Έ 개의 행렬을 인자둜 λ°›κ³ , A와 B의 곱에 κ°€μ€‘μΉ˜ ALPHAλ₯Ό κ³±ν•œ κ²°κ³Όλ₯Ό C 행렬에 λ”ν•œλ‹€.

μ„€λͺ…:

  • gemm_cpu ν•¨μˆ˜λŠ” CPUμ—μ„œ ν–‰λ ¬ κ³±μ…ˆ 연산을 μˆ˜ν–‰ν•œλ‹€.

  • 이 ν•¨μˆ˜λŠ” A, B, C μ„Έ 개의 포인터와 λ‹€μ–‘ν•œ 인자λ₯Ό λ°›μ•„μ„œ, ν–‰λ ¬ κ³±μ…ˆ μ—°μ‚° κ²°κ³Όλ₯Ό C 행렬에 μ €μž₯ν•œλ‹€.

  • ν•¨μˆ˜ λ‚΄λΆ€μ—μ„œλŠ” TA와 TB 인자λ₯Ό μ‚¬μš©ν•˜μ—¬ A와 B 행렬이 μ „μΉ˜λ˜μ–΄ μžˆλŠ”μ§€ μ—¬λΆ€λ₯Ό ν™•μΈν•˜κ³ , 이에 따라 gemm_nn, gemm_tn, gemm_nt, gemm_tt ν•¨μˆ˜ 쀑 ν•˜λ‚˜λ₯Ό ν˜ΈμΆœν•œλ‹€.

  • 이 ν•¨μˆ˜λ“€μ€ λ‹€μ–‘ν•œ ν–‰λ ¬ κ³±μ…ˆ μ—°μ‚° 방법을 κ΅¬ν˜„ν•˜κ³  μžˆλ‹€.

  • λ”°λΌμ„œ gemm_cpu ν•¨μˆ˜λŠ” 이λ₯Ό μ΄μš©ν•˜μ—¬ μž…λ ₯으둜 받은 ν–‰λ ¬ A, B의 곱에 κ°€μ€‘μΉ˜ ALPHAλ₯Ό κ³±ν•œ κ²°κ³Όλ₯Ό C 행렬에 λ”ν•œλ‹€.

  • 이 λ•Œ BETA 인자λ₯Ό μ‚¬μš©ν•˜μ—¬ 기쑴의 C ν–‰λ ¬ 값에 λŒ€ν•œ κ°€μ€‘μΉ˜λ₯Ό μ‘°μ ˆν•  수 μžˆλ‹€.

gemm_nn

void gemm_nn(int M, int N, int K, float ALPHA,
        float *A, int lda,
        float *B, int ldb,
        float *C, int ldc)
{
    int i,j,k;
    #pragma omp parallel for
    for(i = 0; i < M; ++i){
        for(k = 0; k < K; ++k){
            register float A_PART = ALPHA*A[i*lda+k];
            for(j = 0; j < N; ++j){
                C[i*ldc+j] += A_PART*B[k*ldb+j];
            }
        }
    }
}

ν•¨μˆ˜ 이름: gemm_nn

μž…λ ₯:

  • M: ν–‰λ ¬ A의 ν–‰μ˜ 개수

  • N: ν–‰λ ¬ B의 μ—΄μ˜ 개수

  • K: ν–‰λ ¬ A의 μ—΄μ˜ 개수 λ˜λŠ” ν–‰λ ¬ B의 ν–‰μ˜ 개수

  • ALPHA: ν–‰λ ¬ A와 ν–‰λ ¬ B의 κ³±μ…ˆ 결과에 κ³±ν•΄μ§€λŠ” 슀칼라 κ°’

  • A: 크기 M x K의 ν–‰λ ¬ A

  • lda: ν–‰λ ¬ A의 leading dimension

  • B: 크기 K x N의 ν–‰λ ¬ B

  • ldb: ν–‰λ ¬ B의 leading dimension

  • C: 크기 M x N의 ν–‰λ ¬ C

λ™μž‘:

  • ν–‰λ ¬ A와 Bλ₯Ό κ³±ν•˜μ—¬ ν–‰λ ¬ Cλ₯Ό κ³„μ‚°ν•˜λŠ” General Matrix Multiply(GEMM) 연산을 μˆ˜ν–‰ν•œλ‹€.

  • i, k, j μ„Έ 개의 for 루프λ₯Ό μ‚¬μš©ν•˜μ—¬ ν–‰λ ¬ C의 각 μš”μ†Œλ₯Ό κ³„μ‚°ν•œλ‹€.

  • i λ£¨ν”„μ—μ„œλŠ” ν–‰λ ¬ A의 각 행을 μˆœνšŒν•˜λ©°, k λ£¨ν”„μ—μ„œλŠ” ν–‰λ ¬ A의 각 μ—΄κ³Ό ν–‰λ ¬ B의 각 행을 μˆœνšŒν•˜λ©°, j λ£¨ν”„μ—μ„œλŠ” ν–‰λ ¬ B의 각 열을 μˆœνšŒν•˜λ©° ν–‰λ ¬ C의 각 μš”μ†Œλ₯Ό κ³„μ‚°ν•œλ‹€.

  • OpenMPλ₯Ό μ‚¬μš©ν•˜μ—¬ 병렬 μ²˜λ¦¬ν•œλ‹€.

μ„€λͺ…:

  • General Matrix Multiply(GEMM) 연산은 인곡 μ‹ κ²½λ§μ—μ„œ κ°€μž₯ 많이 μ‚¬μš©λ˜λŠ” μ—°μ‚° 쀑 ν•˜λ‚˜μ΄λ‹€.

  • GEMM 연산을 μˆ˜ν–‰ν•˜λŠ” 방법은 μ—¬λŸ¬ κ°€μ§€κ°€ 있으며, 이 ν•¨μˆ˜μ—μ„œλŠ” A 행렬을 μˆœνšŒν•˜λ©΄μ„œ A와 B의 곱을 κ³„μ‚°ν•œλ‹€.

  • OpenMPλŠ” λ©€ν‹°μ½”μ–΄ CPUμ—μ„œ 병렬 처리λ₯Ό μˆ˜ν–‰ν•  수 μžˆλŠ” 라이브러리둜, 이λ₯Ό μ‚¬μš©ν•˜μ—¬ μ„±λŠ₯을 ν–₯μƒμ‹œν‚¨λ‹€.

gemm_nt

void gemm_nt(int M, int N, int K, float ALPHA,
        float *A, int lda,
        float *B, int ldb,
        float *C, int ldc)
{
    int i,j,k;
    #pragma omp parallel for
    for(i = 0; i < M; ++i){
        for(j = 0; j < N; ++j){
            register float sum = 0;
            for(k = 0; k < K; ++k){
                sum += ALPHA*A[i*lda+k]*B[j*ldb + k];
            }
            C[i*ldc+j] += sum;
        }
    }
}

ν•¨μˆ˜ 이름: gemm_nt

μž…λ ₯:

  • M: A ν–‰λ ¬μ˜ ν–‰ 개수

  • N: B ν–‰λ ¬μ˜ μ—΄ 개수

  • K: A ν–‰λ ¬μ˜ μ—΄ 개수 (λ™μ‹œμ— B ν–‰λ ¬μ˜ ν–‰ 개수)

  • ALPHA: A와 B ν–‰λ ¬μ˜ κ³±μ…ˆ 결과에 κ³±ν•΄μ§ˆ 슀칼라 κ°’

  • *A: A ν–‰λ ¬μ˜ 포인터

  • lda: A ν–‰λ ¬μ˜ ν–‰ λ‹Ή μ›μ†Œ 개수

  • *B: B ν–‰λ ¬μ˜ 포인터

  • ldb: B ν–‰λ ¬μ˜ ν–‰ λ‹Ή μ›μ†Œ 개수

  • *C: C ν–‰λ ¬μ˜ 포인터

  • ldc: C ν–‰λ ¬μ˜ ν–‰ λ‹Ή μ›μ†Œ 개수

λ™μž‘:

  • ν–‰λ ¬ A와 Bλ₯Ό κ³±ν•œ ν›„, C 행렬에 λ”ν•΄μ£ΌλŠ” 연산을 μˆ˜ν–‰ν•œλ‹€.

  • A와 B 행렬을 κ³±ν•˜κΈ° μœ„ν•΄ AλŠ” κ·ΈλŒ€λ‘œ, BλŠ” μ „μΉ˜(transpose)된 ν˜•νƒœλ‘œ μ‚¬μš©λœλ‹€.

  • A의 i번째 ν–‰κ³Ό B의 j번째 열을 κ³±ν•œ 값을 C의 i번째 ν–‰ j번째 열에 λˆ„μ ν•˜μ—¬ 더해쀀닀.

μ„€λͺ…:

  • 이 ν•¨μˆ˜λŠ” B 행렬이 μ „μΉ˜λœ ν˜•νƒœλ‘œ μž…λ ₯으둜 λ“€μ–΄μ˜¬ λ•Œ A와 Bλ₯Ό κ³±ν•œ ν›„ C 행렬에 λ”ν•΄μ£ΌλŠ” 연산을 μˆ˜ν–‰ν•œλ‹€.

  • ν•¨μˆ˜ λ‚΄λΆ€μ—μ„œλŠ” OpenMPλ₯Ό μ΄μš©ν•˜μ—¬ 병렬 처리λ₯Ό μˆ˜ν–‰ν•˜λ©°, i, j, k μ„Έ 개의 for 루프λ₯Ό μ΄μš©ν•˜μ—¬ ν–‰λ ¬μ˜ μ›μ†Œ κ³±μ…ˆ 및 λ§μ…ˆ 연산을 μˆ˜ν–‰ν•œλ‹€.

gemm_tn

void gemm_tn(int M, int N, int K, float ALPHA,
        float *A, int lda,
        float *B, int ldb,
        float *C, int ldc)
{
    int i,j,k;
    #pragma omp parallel for
    for(i = 0; i < M; ++i){
        for(k = 0; k < K; ++k){
            register float A_PART = ALPHA*A[k*lda+i];
            for(j = 0; j < N; ++j){
                C[i*ldc+j] += A_PART*B[k*ldb+j];
            }
        }
    }
}

ν•¨μˆ˜ 이름: gemm_tn

μž…λ ₯:

  • int M: ν–‰λ ¬ A의 ν–‰μ˜ 수

  • int N: ν–‰λ ¬ B의 μ—΄μ˜ 수

  • int K: ν–‰λ ¬ A의 μ—΄μ˜ 수 λ˜λŠ” ν–‰λ ¬ B의 ν–‰μ˜ 수

  • float ALPHA: κ³±ν•΄μ§€λŠ” μƒμˆ˜

  • float *A: ν–‰λ ¬ A의 데이터 포인터

  • int lda: ν–‰λ ¬ A의 ν–‰ 간격

  • float *B: ν–‰λ ¬ B의 데이터 포인터

  • int ldb: ν–‰λ ¬ B의 ν–‰ 간격

  • float *C: 좜λ ₯ ν–‰λ ¬ C의 데이터 포인터

  • int ldc: 좜λ ₯ ν–‰λ ¬ C의 ν–‰ 간격

λ™μž‘:

  • ν–‰λ ¬ A와 B의 μ „μΉ˜ν–‰λ ¬μΈ AT와 BTλ₯Ό κ³±ν•˜κ³  ALPHAλ₯Ό κ³±ν•œ 값을 좜λ ₯ ν–‰λ ¬ C에 λ”ν•œλ‹€.

μ„€λͺ…:

  • 이 ν•¨μˆ˜λŠ” ν–‰λ ¬ A와 B의 μ „μΉ˜ν–‰λ ¬μΈ AT와 BTλ₯Ό κ³±ν•œ κ²°κ³Όλ₯Ό 좜λ ₯ν•˜λŠ” ν•¨μˆ˜μ΄λ‹€.

  • μ΄λ•Œ ALPHAλ₯Ό κ³±ν•œ 값이 좜λ ₯ ν–‰λ ¬ C에 더해진닀.

  • λ‚΄λΆ€μ μœΌλ‘œλŠ” OpenMPλ₯Ό μ‚¬μš©ν•˜μ—¬ 병렬 처리λ₯Ό μˆ˜ν–‰ν•œλ‹€.

gemm_tt

void gemm_tt(int M, int N, int K, float ALPHA,
        float *A, int lda,
        float *B, int ldb,
        float *C, int ldc)
{
    int i,j,k;
    #pragma omp parallel for
    for(i = 0; i < M; ++i){
        for(j = 0; j < N; ++j){
            register float sum = 0;
            for(k = 0; k < K; ++k){
                sum += ALPHA*A[i+k*lda]*B[k+j*ldb];
            }
            C[i*ldc+j] += sum;
        }
    }
}

ν•¨μˆ˜ 이름: gemm_tt

μž…λ ₯:

  • int M: ν–‰λ ¬ C의 ν–‰ 개수

  • int N: ν–‰λ ¬ C의 μ—΄ 개수

  • int K: ν–‰λ ¬ A의 μ—΄ 개수 (ν–‰λ ¬ B의 ν–‰ 개수)

  • float ALPHA: 슀칼라 κ°’

  • float *A: M x K 크기의 ν–‰λ ¬ A

  • int lda: ν–‰λ ¬ A의 μ—΄ 개수

  • float *B: K x N 크기의 ν–‰λ ¬ B

  • int ldb: ν–‰λ ¬ B의 μ—΄ 개수

  • float *C: M x N 크기의 ν–‰λ ¬ C

  • int ldc: ν–‰λ ¬ C의 μ—΄ 개수

λ™μž‘:

  • 두 개의 ν–‰λ ¬ A와 Bλ₯Ό κ³±ν•œ κ²°κ³Όλ₯Ό ν–‰λ ¬ C에 λˆ„μ ν•œλ‹€.

  • A와 BλŠ” μ „μΉ˜(transpose)λ˜μ–΄ μžˆλ‹€κ³  κ°€μ •ν•œλ‹€.

  • OpenMPλ₯Ό μ‚¬μš©ν•˜μ—¬ λ³‘λ ¬μ²˜λ¦¬ν•œλ‹€.

μ„€λͺ…:

  • 일반적으둜 ν–‰λ ¬ κ³±μ…ˆ μ—°μ‚°μ—μ„œλŠ” A x B와 B x AλŠ” λ‹€λ₯΄λ‹€. κ·ΈλŸ¬λ‚˜ gemm_tt ν•¨μˆ˜μ—μ„œλŠ” A와 B λͺ¨λ‘ μ „μΉ˜λœ μƒνƒœμ—μ„œ κ³±μ…ˆμ„ μˆ˜ν–‰ν•˜κΈ° λ•Œλ¬Έμ— A^T x B^T = (B x A)^T와 같은 κ²°κ³Όλ₯Ό μ–»λŠ”λ‹€.

  • i, j, k의 μˆœμ„œλ‘œ 3쀑 for 루프λ₯Ό μˆ˜ν–‰ν•˜λ©°, 각각 C[ildc+j], A[i+klda], B[k+j*ldb]의 값을 μ°Έμ‘°ν•œλ‹€.

  • 각각의 C[i*ldc+j]의 값을 κ³„μ‚°ν•˜κΈ° μœ„ν•΄ sum λ³€μˆ˜λ₯Ό μ‚¬μš©ν•˜μ—¬ λˆ„μ  합을 κ³„μ‚°ν•œλ‹€.

  • OpenMPλ₯Ό μ‚¬μš©ν•˜μ—¬ λ³‘λ ¬μ²˜λ¦¬ν•˜μ—¬ μ„±λŠ₯을 ν–₯μƒμ‹œν‚¨λ‹€.

Last updated

Was this helpful?