본문 바로가기

데이터 사이언스 DataScience/Deep Learning 딥러닝

[TensorFlow] 애플 실리콘 vs Nvidia GPU 성능 비교

Udemy 강의와 회사 스터디를 통해 조금씩 딥러닝 코드를 돌려보고 있는데, 

궁금증이 생겼다. 

 

- 애플 실리콘 노트북과 Nvidia 그래픽카드를 탑재한 데스크톱 중, 어떤 환경에서 Tensorflow 코드가 더 빨리 실행될 것인가?

- 그 차이는 얼마나 될 것인가?

 

유튜브 영상과 구글링 결과를 통해 답은 어느정도 알 수 있었다.

하지만 백문이 불여일타. 백번 듣는 것 보단 한 번 해보는게 좋을 것 같아서 

Mac OS, Windows OS 간 다른 실행환경을 설정하고 테스트 해보는 과정을 거쳤다.

 

 

* 결론부터

- 자체테스트 결과 > Nvidia GPU 데스크톱 환경이 3.5배 정도 빨랐다

- 다른 사람 테스트 결과> 스펙과 모델에 따라 Nvidia GPU가 2~4배 빨랐다.

출처 : https://wandb.ai/tcapelle/apple_m1_pro/reports/Deep-Learning-on-the-M1-Pro-with-Apple-Silicon---VmlldzoxMjQ0NjY3

<Resnet 50 수행 결과>

출처 -&nbsp;https://wandb.ai/tcapelle/apple_m1_pro/reports/Deep-Learning-on-the-M1-Pro-with-Apple-Silicon---VmlldzoxMjQ0NjY3

 

* 테스트 환경

구분 애플 실리콘 맥북 스펙 윈도우 데스크톱 스펙
CPU M1 Max - 10 코어 라이젠 3700x - 8코어
GPU M1 Max - 32 코어 Nvidia RTX 2070 Super (8GB)
RAM 32 GB 16 GB
파워 공급 140W 750W (GPU 최대 215W, CPU TDP 65W 등) 

 

 

* 환경구성

기본적으로 TensorFlow의 가이드 문서를 보면서 환경을 구성했다.

https://www.tensorflow.org/install

 

1) 애플 실리콘

conda 가상환경을 구성하는 방법과, local 환경을 구성하는 방법 중 

local 환경을 구성하는 방법을 선택했다.

 

TensorFlow 가이드 문서를 보며 적절한 버전으로 아래 2가지를 설치했다. 

- python 설치

- pip 통한 tensorflow 설치

 

그런데, 막상 코드를 돌려보니 cpu만 60% 정도 사용하고, gpu는 전혀 사용하지 않았다.

 

그래서 문서를 찾던 중 Metal 이라는 plugin을 설치해야 GPU 를 활용하는 것을 알게됐다.

참고한 문서 - https://developer.apple.com/metal/tensorflow-plugin/

 

- pip 통한 tensorflow-metal 설치

pip install tensorflow-metal

까지 마치고 나니 GPU를 사용했다. 코드 수행 중 GPU 사용률은 약 85%였다.

 

 

2) 윈도우 데스크톱

WSL2를 통한 환경 구성과, local 환경 구성하는 방법 중

local 환경을 구성하는 방법을 선택했다.

 

TensorFlow 가이드 문서를 보며 pip를 통해 TensorFlow를 2.10 버전으로 설치했다.

(이후 버전은 WSL 환경에서만 공식 지원)

 

NVIDIA의 CUDA 아키텍처를 활용하기위해 NVIDIA의 CUDA Toolkit과 cuDNN을 설치했다.

설치 후 코드를 돌렸을때,  아래와 같은 에러메세지가 나오면 실행되지 않았다.

tensorflow/stream_executor/cuda/cuda_dnn.cc:366] Loaded cuDNN version 8300

 

그 이유는 CUDA Toolkit과 cuDNN의 버전이 호환되지 않았기 때문이다.

아래 표에 맞춰 tensorflow 2.10 - 쿠다 11.2 - cuDNN 8.1 으로 버전을 맞춰서 설치하니 문제가 해결됐다.

출처 - https://www.tensorflow.org/install/source_windows

 

* 테스트 결과

CNN을 구성하기위해 CIFAR-10 데이터셋을 활용했다.

8개의 Layer로 구성해 10 epoch를 돌려봤고 1epoch 평균 시간에서 약 3.5배 차이가 났다

구분 애플 실리콘 맥북 윈도우 데스크톱
1 epoch 평균 시간 28초 8초

 

다른 사람들의 테스트 결과에서 fine tuning 한 모델의 경우 격차가 줄어드는 경우가 있어 

다른 모델들에 대해 이해를 높인 후 테스트를 더 해봐야겠다.

 

* 결론

애플 실리콘 환경은 저전력으로 랩탑에서 편하게 딥러닝을 구현/테스트해볼 수 있다는 장점이 있다.

    -> 초보/입문자가 다른 용도와 겸용으로 쓰기에 괜찮은 선택지가 될 수 있다. (쿼드로 RTX 5000 정도의 딥러닝 성능)

 

하지만, 모델을 실제 만드는데 있어 100 epoch 이상의 코드 실행시 Nvidia GPU가 탑재된 데스크톱이 훨씬 빠르다.

     -> 중급이상의 구현에는 GPU 탑재 데스크톱 또는 GPU 서버를 통한 구현 필요

 

 

* CNN 실행 코드

   - 수행결과 정확도가 높진 않다 (70%) -> 튜닝 필요 

import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf

from keras.datasets import cifar10
from keras.utils import to_categorical

from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Conv2D
from keras.layers import MaxPooling2D
from keras.layers import Flatten
from keras.layers import Dropout

import cv2


(x_train, y_train), (x_test, y_test) = cifar10.load_data()

print(type(x_train))
print(x_train.shape)
print(type(x_train[0,0,0,0]))

print(type(y_train))
print(y_train.shape)
print(type(y_train[0,0]))

print(x_test.shape)

# 한 번 training dataset의 10개 이미지 보기
scale_factor = 4.0 # 2배 확대

# colab에서 하는 경우 
    #from google.colab.patches import cv2_imshow
    #cv2_imshow(resized_image)
#for i in range(10):
#    resized_image = cv2.resize(x_train[i], None, fx=scale_factor, fy=scale_factor)
#    cv2_imshow(resized_image)
    
# Loss의 스케일 조정을 위해 0 ~ 255 -> 0 ~ 1 범위로 만들어줍니다
X_train = x_train.astype('float32') / 255.0
X_test = x_test.astype('float32') / 255.0

# One-Hot Encoding (숫자 -> One Hot Vector)
#colab에서 하는 경우
#from keras.utils import np_utils
# np_utils
Y_train = to_categorical(y_train)
Y_test = to_categorical(y_test)

print('Y_train : ', Y_train.shape)
Y_train[:10]

# 모델 구성하기

# 순차적 모델을 만들기위한 Sequential() 함수
model = Sequential(name='CIFAR10_CNN')

# 32*32*3 입력
# 3*3*3 필터 32개 => 32*32*32 출력
# 파라미터 수는 '필터셀수+편향 * 필터 개수'=(3*3*3 + 1)*32
model.add(Conv2D(filters=32, kernel_size=(3,3), padding='same', activation='relu',
                 input_shape=X_train[0].shape))

# 3*3*32 필터 32개 => 32*32*32 출력
model.add(Conv2D(filters=32, kernel_size=(3,3), padding='same',activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2))) # 16*16*32

# 3*3*32 필터 64개 => 16*16*64
model.add(Conv2D(filters=64, kernel_size=(3,3), padding='same',activation='relu'))

# 3*3*64 필터 64개 => 16*16*64 
model.add(Conv2D(filters=64, kernel_size=(3,3), padding='same',activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2))) # 8*8*64

# 3*3*64 필터 128개 => 8*8*128
model.add(Conv2D(filters=128, kernel_size=(3,3), padding='same',activation='relu'))

# 3*3*128 필터 128개 => 8*8*64 
model.add(Conv2D(filters=128, kernel_size=(3,3), padding='same',activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2))) # 4*4*128

# FCN 입력을 위한 Flattening
model.add(Flatten()) # 4*4*128 = 2048

model.add(Dense(512, activation='relu'))
model.add(Dropout(0.2))

model.add(Dense(10, activation='softmax'))

# 모델 정보 조회
model.summary()

model.compile(loss='categorical_crossentropy', optimizer='rmsprop', metrics=['accuracy'])

# 모델 학습 (GPU)
with tf.device('/GPU:0'):
    hist=model.fit(X_train, Y_train, epochs=2, batch_size=32, validation_split=0.2, verbose=1)

# Training Loss vs Validation Loss 비교를 위한 그래프 그리기
plt.plot(hist.history['loss'], 'y', label='train loss')
plt.plot(hist.history['val_loss'], 'r', label='val loss')

# y축 범위 설정
plt.ylim([0.0, 2.5])

# 각 축의 이름 정하기
plt.xlabel('epoch')
plt.ylabel('loss')

# 각 그래프의 설명 위치 설정 후 표시
plt.legend(loc='upper left')
plt.show()

# Training Accuracy VS Validation Accuracy
plt.plot(hist.history['accuracy'], 'b', label='train acc')
plt.plot(hist.history['val_accuracy'], 'r', label='val acc')

# y축 범위 설정
plt.ylim([0.2, 1.0])

# 각 축의 이름 정하기
plt.xlabel('epoch')
plt.ylabel('accuracy')

# 각 그래프의 설명 위치 설정 후 표시
plt.legend(loc='upper left')
plt.show()

loss_and_acc = model.evaluate(X_test, Y_test, batch_size=32)

print('Test set Loss and Accuracy')
print(loss_and_acc)

## 70% -> filter, CNN Layer, Epoch/Batch변경

labels = ['비행기','자동차','새','고양이','사슴','개','개구리','말','배','트럭']

print.rcParams["figure.figsize"] = (2,2)

# Test Set 의 10개 맞추기
for i in range(300,310):
    output = model.predict(X_test[i].reshape(1, 32, 32, 3))

    # 이미지 출력
    plt.imshow(X_test[i].reshapre(32,32,3))

    #np.argmax()가 labels의 인덱스가 되어 labels 배열에 있는 문자열을 출력합니다
    print('예측:' + labels[np.argmax(output)] + ' / 정답: '+labels[np.argmax(Y_test[i])])
    plt.show()
반응형