Keras, Tensorflow에서 GPU 똑똑하게 사용하기 - 1부

이번 포스팅에서는 Keras와 Tensorflow에서 GPU를 더 똑똑하게 사용하는 방법에 대해 알아보자.


케라스 (와 당연히 텐서플로우)를 사용한다면, GPU도 높은 확률로 사용 중일 것 이다.


근데 이놈의 텐서플로우는 default로 (2장 이상의 GPU를 사용한다면 모든) GPU의 메모리를 배정받으면서 시작되는데, 이 경우 파이썬 프로세스를 하나만 실행하기만 해도 GPU 메모리가 허덕이는 경우가 태반이다.


하는일은 하나도 없고 (util : 0%) 메모리는 95%를 먹고계신 Tensorflow




ResourceExhaustedError (see above for traceback): OOM when allocating tensor with shape[10000,32,28,28]
[[Node: conv1/Conv2D = Conv2D[T=DT_FLOAT, data_format="NHWC", padding="SAME", strides=[1, 1, 1, 1], 
use_cudnn_on_gpu=true, _device="/job:localhost/replica:0/task:0/device:GPU:0"](reshape/Reshape, conv1/Variable/read)]]

OOM (Out of Memory)를 자주 본다면, 이 포스트가 도움 될거라 믿는다.


왜 이러는 걸까


우선 이는 잘못된 것이 아니라는 점 분명히 밝혀두고 시작한다.


Tensorflow는 이에 대한 이유로 다음과 같이 설명하고 있다:
By default, TensorFlow maps nearly all of the GPU memory of all GPUs (subject to CUDA_VISIBLE_DEVICES) visible to the process. This is done to more efficiently use the relatively precious GPU memory resources on the devices by reducing memory fragmentation.

요약 하자면 메모리 조각화를 막기 위해 중요자원인 GPU(들)의 메모리들을 일단 다 배정 해놓고 본다는 것이다.


물론 좋은 취지지만, 연구나 개발하다보면 답답할 수가 있는데, 이에 대한 여러 해결책 (이라쓰고 우회책이라 읽는다)을 여러분께 제시 해 본다.


참고로, 아래의 여러 해결책들은 조합하여 같이 사용할 수 있다. 필요에 따라 활용하도록 하자.

해결책 0. GPU가 여러장일때 일부분만 사용하기


이 해결책이 0번인 이유는, GPU가 항상 여러장이진 않기 때문


우선 본인의 컴퓨터나 서버에 GPU가 2장 이상 존재한다고 가정하자. (gpu 0, gpu 1)
Keras나 Tensorflow를 실행시키면 (model을 생성하거나, instance를 생성하거나이다 단순히 import tensorflow 만으로는 배정되지 않는다) GPU 2장의 메모리가 가득 차게 되는데, 아래와 같이 아예 쓰지 못하게 하거나, 특정 GPU를 지정하여 사용하게 할 수 있다.

os.environ

Tensorflow는 gpu를 지정할 때, 몇 장이나 가지고 있는지 조회해보고 배정하게 되는데, 이 조회 자체를 막아버리는 방식이다.
import os

# GPU를 아예 못 보게 하려면:
os.environ["CUDA_VISIBLE_DEVICES"]=''
# GPU 0만 보게 하려면:
os.environ["CUDA_VISIBLE_DEVICES"]='0'
# GPU 1만 보게 하려면:
os.environ["CUDA_VISIBLE_DEVICES"]='0'
# GPU 0과 1을 보게 하려면:
os.environ["CUDA_VISIBLE_DEVICES"]='0,1'

주의 할 것은 2개 이상일때 하나의 ' ' 안에 모두 들어가 있어야 한다 ('0', '1' 라고 입력시 에러)


with tf.device

이하는 Tensorflow가 제시하는 방식인데, 문제는 with문으로 계속 지정해줘야 해서 필자는 그닥 추천하지 않는 방식이다

import tensorflow as tf

# CPU만 사용하려면:
with tf.device('/cpu:0'):
    a = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[2, 3], name='a')
    b = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[3, 2], name='b')
    c = tf.matmul(a, b)
# GPU 0만 사용하려면:
with tf.device('/device:GPU:0'):
    a = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[2, 3], name='a')
    b = tf.constant([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], shape=[3, 2], name='b')
    c = tf.matmul(a, b)
# Creates a session with log_device_placement set to True.
sess = tf.Session(config=tf.ConfigProto(log_device_placement=True))
# Runs the op.
print(sess.run(c))


이와 같이 우선 가지고 있는 하드웨어에 제한을 거는 방식이 있는데, 이렇게 할 경우 tensorflow가 모든 gpu의 메모리를 잡아먹진 않지만, 지정한 gpu는 여전히 메모리가 허덕이고 있는 것을 볼 수 있다. 따라서 필자는 os.environ과 함께 다음의 방법들을 적절히 섞어서 사용하는 것을 추천한다.


해결책 1. tensorflow.ConfigProto().gpu_options


본 해결책은 tensorflow 기반의 해결책인 만큼, 당연히 Keras에서도 사용할 수 있다. 후술할 Keras 전용 코드 참조


Tensorflow에서는 gpu에의 메모리 지정 방식을 바꿀 수 있는 2개의 옵션을 제공한다. 함께 알아 보도록 하자. 공식홈페이지 링크 [새 창]

allow_growth

import tensorflow as tf

config = tf.ConfigProto()
config.gpu_options.allow_growth = True
session = tf.Session(config=config)
...

해당 옵션을 True로 설정할 경우, 아래와 같이 GPU의 메모리가 전부 할당되지 않고, 아주 적은 비율만 할당되어 시작해서, 프로세스의 메모리 수요에 따라 증가하게 된다. 


메모리가 15GB에서 300~500MB로 굉장히 줄어 든 모습



프로세스가 얼마나 메모리를 사용할지는 모르지만 적어도 GPU 메모리를 모두 사용하고 있는 상황을 피할 때 유용한 옵션이라 할 수 있다.

주의할 점은, 이 옵션은 메모리의 증식만 가능하다는 것. 연산이 끝나고 메모리가 필요없는 상황이라고 해서 할당된 메모리를 반납하지 않는다. 

Tensorflow 측에서는 그럴 경우 더 심한 메모리 파편화를 일으킬 수도 있다고 하니 판단은 독자의 몫.

Note that we do not release memory, since that can lead to even worse memory fragmentation. 


per_process_gpu_memory_fraction

import tensorflow as tf

config = tf.ConfigProto()
config.gpu_options.per_process_gpu_memory_fraction = 0.4
session = tf.Session(config=config)
...
해당 옵션은 원하는 gpu에 어느 비율의 메모리를 할당할지 먼저 결정하는 것으로, 프로세스가 어느정도의 메모리를 사용할지 알고 있다면, 작업 전에 미리 지정하여 메모리를 아낄 수 있다. 약 16기가의 메모리에서 40%에 위의 기본 메모리를 더해 각각 6954, 6855이 배정된 모습.



allow_growth per_process_gpu_memory_fraction 모두 사용할 수도 있으나, 추천하진 않는데, 메모리 파편화도 파편화지만 굳이 프로세스의 예상 메모리 사용량을 아는 상태에서 allow_growth를 사용할 이유도 없을 것이다.

또한, 두 옵션 중 하나를 사용할 경우, 메모리가 전부 할당되지 않으므로 추가적인 프로세스를 gpu에 할당할 수 있다. 본인의 gpu가 1장이라도 2개 이상의 프로세스를 돌릴 수 있는게 최고의 장점. 그렇지만 allow_growth로 2개 (또는 그 이상)의 프로세스를 실행시킬 경우, 한 프로세스 (특히 먼저 시작된 놈이)가 메모리를 쭈~욱 잡아 먹는 광경을 볼  수 있다.

그러므로, 2개 이상의 프로세스를 돌리려고 할 때는 per_process_gpu_memory_fraction을 사용하자. 필자의 경험상 모든 프로세스의 fraction 값의 합이 1.0 미만이 되는 것이 안정적으로 구동하는데 도움이 되었다.

하나의 GPU에, 2개의 프로세스가 약 40%의 메모리를 할당 받아 올라갔다.


위의 tensorflow 옵션들은, tensorflow를 backend로 사용하는 Keras에도 동일하게 적용 되므로 1부만 읽어도 tensorflow와 keras 모두 gpu를 컨트롤 할 수 있다. 그렇지만, 이 정도로는 gpu를 똑똑하게 썼다기 보다는 이제서야 출발점에 왔다고 할 수 있다. 


2부에서는 위의 옵션들과 함께 Keras를 어떻게 써야  정말 똑똑한 것인지에 대해 다룬다.


Keras, Tensorflow에서 GPU 똑똑하게 사용하기 - 2부에서 계속


그래도 이해가 안된다면? QnA 게시판 바로가기

COMMENT WRITE

  1. 글 잘봤습니다!! 그런데 케라스로 하는 해결부분이 없네요 아쉽습니다 ㅠ

  2. comment profile
    쉬운대박정리 2019.07.04 16:52

    정말 쉽게 잘써진 글이네요 정말 감사합니다. 케라스 글도 읽으러 갈게요

  3. tensorflow이 2.x로 업데이트되면서 session도 안되서 쥬니어는 슬픕니다..