https://datamasters.co.kr/33
이번 포스팅에서는 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)를 자주 본다면, 이 포스트가 도움 될거라 믿는다.
왜 이러는 걸까
우선 이는 잘못된 것이 아니라는 점 분명히 밝혀두고 시작한다.
By default, TensorFlow maps nearly all of the GPU memory of all GPUs (subject toCUDA_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가 항상 여러장이진 않기 때문
os.environ
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에, 2개의 프로세스가 약 40%의 메모리를 할당 받아 올라갔다.
위의 tensorflow 옵션들은, tensorflow를 backend로 사용하는 Keras에도 동일하게 적용 되므로 1부만 읽어도 tensorflow와 keras 모두 gpu를 컨트롤 할 수 있다. 그렇지만, 이 정도로는 gpu를 똑똑하게 썼다기 보다는 이제서야 출발점에 왔다고 할 수 있다.
2부에서는 위의 옵션들과 함께 Keras를 어떻게 써야 정말 똑똑한 것인지에 대해 다룬다.