2017년 7월 24일 월요일

Minsky 서버에서 NVIDIA DIGITS로 MNIST LeNet training 해보기

이번 posting 내용은 아래 URL에 있는 것을 IBM ppc64le 아키텍처 위에서 제공되는 IBM PowerAI tool에 포함된 digits로 구현해본 것입니다.

https://github.com/NVIDIA/DIGITS/blob/master/docs/GettingStarted.md

DIGITS는 NVIDIA에서 내놓은 오픈소스 기반의 딥 러닝 toolkit입니다.  DIGITS에 대해서 NVIDIA가 소개해놓은 글 ( https://developer.nvidia.com/digits )을 읽어보면 대체 이게 뭐라는 것인지 명확하지는 않습니다.  아래와 같이 그냥 좋은 말만 나와 있거든요.  사실 이런 경향은 IBM을 포함한 모든 벤더들이 다 보여주고 있습니다.

The NVIDIA Deep Learning GPU Training System (DIGITS) puts the power of deep learning into the hands of engineers and data scientists. DIGITS can be used to rapidly train the highly accurate deep neural network (DNNs) for image classification, segmentation and object detection tasks.

결론적으로, DIGITS는 caffe나 torch와 같은 딥 러닝용 framework 자체는 아니고, caffe나 torch를 이용하여 딥 러닝을 수행할 때 좀 더 쉽고 빠르게 할 수 있게 해주는 유용한 웹 기반의 tool입니다.  아마 당장 나오는 질문이 '그럼 tensorflow는 안 되나요'라는 것일텐데, 2017년 7월 24일 현재 당장은 안 됩니다.  그러나 NVIDIA도 DIGITS에 tensorflow 지원을 추가하려고 하고 있고, 이번 달에 나온다고 합니다.

여기서는 caffe를 이용해서 가장 간단한 딥 러닝 training인 MNIST dataset에 대한 LeNet 신경망 training을 DIGITS를 통해 해보도록 하겠습니다.

nvidia-caffe와 digits의 설치는 IBM PowerAI toolkit에서 제공되는 것을 그대로 사용하겠습니다.   그 설치 방법에 대해서는 지난번 posting ( http://hwengineer.blogspot.kr/2017/05/minsky-cuda-powerai-tuning.html )을 참조하시기 바랍니다.

먼저, 그냥 caffe를 이용하여 MNIST dataset에 대한 LeNet 신경망 training을 하는 방법을 봐야 하는데, 이에 대해서는 역시 지난번 posting ( http://hwengineer.blogspot.kr/2017/06/mnist-gpu-cpu-deep-learning.html )에 잘 나와 있으니 그를 참조하시기 바랍니다.

오늘 사례는 GPU가 없는 환경에서 digits를 수행해야 합니다.

시작하기 전에 DIGITS 관련 환경변수를 setup해야 합니다.  DIGITS는 위에서 언급한 것처럼 caffe와 torch를 사용하는 것이기 때문에, "apt-get install digits" 명령만 내려도 자동으로 caffe-nv와 torch를 함께 설치합니다.  마찬가지로, DIGITS를 사용하기 위해 PowerAI에서 제공하는 환경변수 setup script인 /opt/DL/digits/bin/digits-activate를 수행해도, caffe-nv와 torch 관련 환경변수들이 함께 setup 됩니다.   먼저 기본 상태의 PATH 관련 환경 변수를 확인하겠습니다.

u0017496@sys-88165:~$ env | grep PATH
PATH=/home/u0017496/bin:/home/u0017496/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

이제 PowerAI에서 제공하는 환경변수 setup script인 /opt/DL/digits/bin/digits-activate를 수행하겠습니다.  앞에 sh 또는 .을 함께 입력하셔야 한다는 것에 유의하십시요.

u0017496@sys-88165:~$ . /opt/DL/digits/bin/digits-activate

이제 다시 환경변수를 확인하면, openblas와 nccl, cuda 등은 물론이고, torch를 위한 LUA 관련 PATH들도 자동 설정된 것을 보실 수 있습니다.

u0017496@sys-88165:~$ env | grep PATH
LD_LIBRARY_PATH=/opt/DL/openblas/lib:/opt/DL/nccl/lib:/opt/DL/caffe-nv/lib:/usr/local/cuda-8.0/lib64:/opt/DL/torch/lib:/opt/DL/torch/lib/lua/5.1
PATH=/home/u0017496/bin:/home/u0017496/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/DL/caffe-nv/bin:/opt/DL/torch/bin:/opt/DL/digits
LUA_PATH=/home/u0017496/.luarocks/share/lua/5.1/?.lua;/home/u0017496/.luarocks/share/lua/5.1/?/init.lua;/opt/DL/torch/share/lua/5.1/?.lua;/opt/DL/torch/share/lua/5.1/?/init.lua;
LUA_CPATH=/home/u0017496/.luarocks/lib/lua/5.1/?.so;/opt/DL/torch/lib/lua/5.1/?.so;
DYLD_LIBRARY_PATH=/opt/DL/torch/lib:/opt/DL/torch/lib/lua/5.1
PYTHONPATH=/opt/DL/caffe-nv/python:/opt/DL/digits

이제 python을 이용하여 홈 디렉토리 밑에 mnist dataset을 download 받겠습니다.  이때 digits의 download_data 모듈을 유용하게 사용할 수 있습니다.

u0017496@sys-88165:~$ python -m digits.download_data mnist ~/mnist
Downloading url=http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz ...
Downloading url=http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz ...
Downloading url=http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz ...
Downloading url=http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz ...
Uncompressing file=train-images-idx3-ubyte.gz ...
Uncompressing file=train-labels-idx1-ubyte.gz ...
Uncompressing file=t10k-images-idx3-ubyte.gz ...
Uncompressing file=t10k-labels-idx1-ubyte.gz ...
Reading labels from /home/u0017496/mnist/train-labels.bin ...
Reading images from /home/u0017496/mnist/train-images.bin ...
Reading labels from /home/u0017496/mnist/test-labels.bin ...
Reading images from /home/u0017496/mnist/test-images.bin ...
Dataset directory is created successfully at '/home/u0017496/mnist'
Done after 24.0719361305 seconds.

이 python 명령 한줄이면 mnist dataset이 모두 자동으로 다 준비된 것입니다.  아래에서 보시다시피 ~/mnist/train 밑에 train data가 들어가 있는 것을 확인하실 수 있습니다.  ~/mnist/test 밑의 data는 이름과는 달리 test가 아니라 validation을 위해 사용될 것입니다.

u0017496@sys-88165:~$ cd mnist

u0017496@sys-88165:~/mnist$ pwd
/home/u0017496/mnist

u0017496@sys-88165:~/mnist$ ls
t10k-images-idx3-ubyte.gz  test-images.bin  train-images.bin            train-labels-idx1-ubyte.gz
t10k-labels-idx1-ubyte.gz  test-labels.bin  train-images-idx3-ubyte.gz
test                       train            train-labels.bin

u0017496@sys-88165:~/mnist$ cd train

u0017496@sys-88165:~/mnist/train$ ls
0  1  2  3  4  5  6  7  8  9  labels.txt  train.txt


이제 digits-server를 수행합니다.  이는 shell cript로 되어 있으며, daemon화 되어 있지는 않으므로 nohup ~ & 구문을 써서 구동하시는 것이 좋습니다.

u0017496@sys-88165:~$ /opt/DL/digits/digits-devserver &
  ___ ___ ___ ___ _____ ___
 |   \_ _/ __|_ _|_   _/ __|
 | |) | | (_ || |  | | \__ \
 |___/___\___|___| |_| |___/ 5.0.0

/usr/lib/python2.7/dist-packages/matplotlib/font_manager.py:273: UserWarning: Matplotlib is building the font cache using fc-list. This may take a moment.
  warnings.warn('Matplotlib is building the font cache using fc-list. This may take a moment.')
NVIDIA: no NVIDIA devices found
cudaRuntimeGetVersion() failed with error #30
2017-07-20 02:33:14 [INFO ] Loaded 0 jobs.

위에서 보시다시피 no NVIDIA devices found라고 error/warning이 있습니다만, 이는 제가 쓰는 이 시스템에 GPU가 없어서 그런 것이니 일단 무시하십시요.


이제 웹 브라우저를 이용해서 http://서버IP주소:5000/ 로 접속하십시요.   그러면 아래와 같이 'Models'라는 메뉴 화면에 접속하게 됩니다.



여기서 아래와 같이 Datasets > New Dataset > Images > Classification 순으로 click 하면 login page로 접속하게 됩니다.  사용자 id를 넣으라고 나올텐데, 이는 사용하시는 OS user명을 넣으시면 됩니다.



이제 아래와 같은 New Image Classification Dataset 메뉴에 들어왔습니다.  여기서 training images에는 위에서 보신 mnist train 디렉토리를 아래와 같이 입력하시고, image type은 Grayscale로, image size는 28 x 28로 변경하십시요.  또 'separate images validation folder'에 check 표시를 클릭해주시면 추가 메뉴가 나오는데, 거기에는 위에서 언급한 대로 mnist test 디렉토리를 입력해주시면 됩니다.  Backend DB는 LMDB로 지정하시고, dataset 이름을 적절한 것으로 지정하십시요.  여기서는 MNIST로 정하겠습니다.




그리고나서 이제 바닥의 파란색 Create 버튼을 click하시면 됩니다.  그러면 우측 상태 메뉴에서 Job Status가 running으로 나오는 것을 보실 수 있습니다.   아래 그림에서는 'Create DB (train)'이 현재 8% 진행되었고 전체적으로는 5분 34초 걸릴 것이라고 예상되어 있는 것을 보실 수 있습니다.  여기서 DB라 함은 LMDB를 말하는 것입니다.  LMDB는 Lightning Memory – Mapped Database라는 것으로서, 간단히 말하면 jpeg 등의 작은 파일로 된 data들을 담는 key-value store이며, 일반 DBMS는 아니지만 고속의 성능을 내기 때문에 이런 딥 러닝 training dataset을 저장하는데 자주 사용됩니다.



이렇게 LMDB가 생성되고 있는데, 그 data는 어떤 규칙에 의해서 만들어지고 있는 것일까요 ?  그건 오른쪽의 스크롤바를 내려 더 아래쪽으로 내려가보시면 볼 수 있습니다.  보시면 "Input File"이라는 항목에 train.txt라는 text file이 보이지요.  거기에는 각 png file 이름과 그에 따른 label이 정의되어 있고, 그에 따라 LMDB가 생성되고 있습니다.  
그 밑에는 그 카테고리, 즉 label에 해당하는 image file들의 숫자가 그래프로 표시되어 있고, 그 그래프 밑에는 'Explore DB'라는 버튼이 보입니다.



이 'Explore DB'라는 버튼을 클릭해보면 아래와 같이 0~9의 숫자마다 어떤 손글씨 그림이 들어있는지를 눈으로 보실 수 있습니다.



LMDB 생성 규칙을 담은 train.txt라는 link를 클릭해보면, 어떤 png file이 어느 숫자로 labeling되어 있는지 보실 수 있습니다.   아래 화면에서는 다 0으로 labeling 되어 있습니다.


좌측 상단의 DIGITS를 click하여 다시 홈페이지로 복귀합니다.   이제 MNIST 라는 dataset을 보실 수 있습니다.


다음으로는 model을 생성하겠습니다.   다음과 같이 Model tab에서 오른쪽 중단의 Images > Classification을 택합니다.


Dataset으로는 방금 생성한 MNIST를 선택합니다.



(여기서부터는 K80이 2장 장착된 Firestone 장비를 구했기 때문에 그것 기준으로 작성했습니다.)

더 밑으로 내려보면 아래와 같이 어떤 신경망을 이용해 training을 할 것인지 정하게 되어 있습니다.  여기서는 LeNet을 선택합니다.


그 밑으로는 몇개의 GPU를 사용하여 training할지 숫자를 적거나, 또는 현재 이 시스템에 장착된 GPU의 종류 및 개수를 보여주며 그 중 어느것을 사용할지 특정하여 선택하게 되어 있습니다.  여기서는 0번과 1번의 2개를 선택하겠습니다.  이렇게 해서 training될 모델의 이름은 LeNet으로 정하겠습니다.


'Create' 버튼을 클릭하면 training이 시작됩니다.   (위에서와는 달리 GPU가 장착된 서버이므로) 이젠 training 속도가 매우 빨라서 3분도 채 안 걸릴 것이라는 예상이 나오는 것을 보실 수 있습니다.


스크롤바를 밑으로 내려보면 accuracy와 loss가 실시간 graph로 보여집니다.  가로축은 epoch, 즉 전체 dataset을 몇번 반복해서 training했는지의 횟수인데, accuracy가 첫 epoch에서 이미 98%에 도달한 이후 더 개선이 없는 것을 보실 수 있습니다.   즉, 이런 경우는 여러번 반복 training하는 것이 큰 의미는 없습니다.  아무튼 여기서는 default인 30-epoch 반복하여 training합니다.


더 아래로 내리면 learning rate가 step size에 따라 계단식으로 줄어드는 것을 보실 수 있습니다.  또한 Caffe process ID 및 그것의 CPU 및 메모리 사용량도 보여줍니다.


아래 시점에서는 이미 19-epoch 반복 training이 끝났고, 그 snapshot이 저장되었기 때문에 'Trained Models'의 'Select Model' 항목에 Epoch #19가 display 되고 있는 것입니다.  Training이 원래대로 30회 반복되고 나면 저 숫자가 30까지 display 될 것입니다.



자, 30회 반복되어 training이 끝났습니다.  이제 trained model을 가지고 inference를 해볼 수 있습니다.  아래의 'Test a single image' 항목에서 Image Path에 실제 png file의 full path 이름을 입력합니다.






입력 후 'Classify One' 버튼을 누르면 새 tab이 생기면서 그 결과가 보여집니다.


여기서는 기존 training dataset 중 하나를 넣은 것이므로, 당연히 100% 정확한 답을 맞춥니다.


이번에는 다음과 같이 8과 1이라는 숫자의 이미지의 URL 주소를 가진 text 파일 'URL1.txt'를 upload하여 분류하도록 해보겠습니다.

http://www.clker.com/cliparts/V/1/Y/3/j/Z/blue-number-1-md.png
https://upload.wikimedia.org/wikipedia/commons/c/cf/NShw_8.png

이 URL에서 가리키는 URL은 각각 다음과 같습니다.





저 URL 주소를 가진 'URL1.txt'를 아래와 같이 'Upload Images List' 항목에 입력하고 'Classify Many'를 클릭합니다.


그 결과는 아래와 같습니다.   즉, 1과 8 대신 4와 9라고 답한 것이지요.  확실히 더 많은 image로 더 많은 training을 거쳐야 제대로 된 답을 내놓을 수 있습니다.


그렇다면 아래처럼 2글자로 된 이미지에는 뭐라고 답을 할까요 ?  참고로 이 LeNet이라는 모델은 하나의 숫자에 대해서만 training이 되어 있습니다.


이번에는 아래처럼 직접 image file을 upload하여 'Classify One' 버튼을 눌러 보겠습니다.  여기서는 특히 'Show visualizations and statistics' 체크 박스도 체크해보겠습니다.


결과는 아래와 같습니다.  3과 8에서 고민하다가 결국 3을 택한 것을 보실 수 있습니다.


그 아래로 내려보면 'Show visualizations and statistics'에 체크를 한 것 때문에 다양한 비주얼라이제이션과 통계치를 보실 수 있습니다.



digits에는 더욱 많은 기능들이 있으니 차츰 더 알아가 보시기 바랍니다.

Minsky 서버에서 ImageNet Contest 2012 data로 Caffe AlexNet training 해보기

# 먼저, ILSVRC2012 data를 고성능 filesystem(NVMe SSD 또는 ESS 등)에 download 받습니다

b6p318za@p10a109:~/nvme/ilsvrc2012$ wget http://www.image-net.org/challenges/LSVRC/2012/nnoupb/ILSVRC2012_img_train.tar  # 138GB
b6p318za@p10a109:~/nvme/ilsvrc2012$ wget http://www.image-net.org/challenges/LSVRC/2012/nnoupb/ILSVRC2012_img_train_t3.tar  # 728MB
b6p318za@p10a109:~/nvme/ilsvrc2012$ wget http://www.image-net.org/challenges/LSVRC/2012/nnoupb/ILSVRC2012_img_val.tar   #  6.3GB
b6p318za@p10a109:~/nvme/ilsvrc2012$ wget http://www.image-net.org/challenges/LSVRC/2012/nnoupb/ILSVRC2012_img_test.tar  # 13GB

b6p318za@p10a109:/nvme/ilsvrc2012$ ls -ltr
total 165357316
-rw-r----- 1 b6p318za IBM1   6744924160 Jun 14  2012 ILSVRC2012_img_val.tar
-rw-r----- 1 b6p318za IBM1 147897477120 Jun 14  2012 ILSVRC2012_img_train.tar
-rw-r----- 1 b6p318za IBM1    762460160 Jul  4  2012 ILSVRC2012_img_train_t3.tar
-rw-r----- 1 b6p318za IBM1  13685811200 Jul  9  2012 ILSVRC2012_img_test.tar

# 이렇게 download 받은 image tar file들을 각각의 directory에 풀어놓습니다.  단, train용 image tar file을 풀면 그 속에 다시 1000개의 tar file이 나오니, 그것들을 또 한번 더 풀어주어야 합니다.

b6p318za@p10a109:/nvme/ilsvrc2012$ mkdir val
b6p318za@p10a109:/nvme/ilsvrc2012$ mkdir test
b6p318za@p10a109:/nvme/ilsvrc2012$ mkdir train
b6p318za@p10a109:/nvme/ilsvrc2012$ mkdir train_t3

b6p318za@p10a109:/nvme/ilsvrc2012$ cd val
b6p318za@p10a109:/nvme/ilsvrc2012/val$ tar -xvf ../ILSVRC2012_img_val.tar

b6p318za@p10a109:/nvme/ilsvrc2012$ cd train
b6p318za@p10a109:/nvme/ilsvrc2012/train$ tar -xvf ../ILSVRC2012_img_train.tar
b6p318za@p10a109:/nvme/ilsvrc2012/train$ for i in `ls`
> do
> dir=`echo $i | cut -d. -f1`
> mkdir $dir
> cd $dir
> tar -xf ../$i
> cd ..
> done

b6p318za@p10a109:/nvme/ilsvrc2012$ cd test
b6p318za@p10a109:/nvme/ilsvrc2012/test$ tar -xvf ../ILSVRC2012_img_test.tar

b6p318za@p10a109:/nvme/ilsvrc2012$ cd train_t3
b6p318za@p10a109:/nvme/ilsvrc2012/train_t3$ tar -xvf ../ILSVRC2012_img_train_t3.tar
b6p318za@p10a109:/nvme/ilsvrc2012/train_t3$ for i in *.tar
> do
> tar -xvf $i
> done


# 기본 시스템 성능 튜닝입니다.   GPU의 autoboost도 on 시켜 놓습니다.

b6p318za@p10a109:~$ sudo apt-get install linux-tools-common cpufrequtils
b6p318za@p10a109:~$ sudo cpupower frequency-set --governor performance

b6p318za@p10a109:~$ sudo nvidia-smi -pm ENABLED

b6p318za@p10a109:~$ sudo nvidia-smi -ac 715,1480


# 이미 설치된 PowerAI 중 NV-caffe를 사용합니다.  이를 위해서는 아래와 같은 명령을 수행하여 PATH 환경 변수 등을 NV-caffe를 기본으로 하도록 합니다.   PowerAI 설치에 대해서는 지난 posting ( http://hwengineer.blogspot.kr/2017/05/minsky-cuda-powerai-tuning.html )을 참조하십시요.

b6p318za@p10a109:/opt/DL$ source /opt/DL/caffe-nv/bin/caffe-activate

b6p318za@p10a109:/opt/DL$ which caffe
/opt/DL/caffe-ibm/bin/caffe


# 아래와 같이 caffe-test를 수행했을 때 혹시 error가 난다면 다음과 같이 LD_LIBRARY_PATH를 제대로 설정해주시면 됩니다.

b6p318za@p10a109:/opt/DL$ caffe-test
caffe-test: symbol lookup error: /opt/DL/caffe-ibm/test/../lib/libcaffe.so.1.0.0-rc3: undefined symbol: _ZNK6google8protobuf7Message11GetTypeNameB5cxx11Ev

b6p318za@p10a109:~$ export LD_LIBRARY_PATH=/usr/lib/powerpc64le-linux-gnu:/usr/local/lib:$LD_LIBRARY_PATH

b6p318za@p10a109:~$ sudo vi /etc/ld.so.conf.d/DL.conf
/opt/DL/openblas/lib

b6p318za@p10a109:~$ sudo ldconfig

b6p318za@p10a109:/opt/DL$ caffe-test
...
[----------] Global test environment tear-down
[==========] 2081 tests from 277 test cases ran. (664293 ms total)
[  PASSED  ] 2081 tests.


# Alexnet 수행 준비를 위해, 먼저 get_ilsvrc_aux.sh를 수행합니다.  그리고 download된 train.txt의 내용을 실제 file 위치에 맞도록 수정합니다.

b6p318za@p10a109:/opt/DL/caffe-ibm/data/ilsvrc12$ ./get_ilsvrc_aux.sh

b6p318za@p10a109:/opt/DL/caffe-ibm$ vi data/ilsvrc12/train.txt
n01440764/n01440764_10026.JPEG 0
n01440764/n01440764_10027.JPEG 0
...
---->
/nvme/ilsvrc2012/train/n01440764/n01440764_10026.JPEG 0
/nvme/ilsvrc2012/train/n01440764/n01440764_10027.JPEG 0
...

# 전체 image 준비를 위해 create_imagenet.sh를 수행합니다.  단, 수행 전에 이 script에서 RESIZE=true로 바꾸고, 기타 TRAIN_DATA_ROOT 등의 각종 path 등을 수정합니다.   특히, 기본적으로 $EXAMPLE 밑에 생성되도록 되어 있는 ilsvrc12_train_lmdb과 ilsvrc12_val_lmdb 이 고성능 filesystem(NVMe 혹은 ESS)에 생성되도록 path를 바꿔 줍니다.

b6p318za@p10a109:/opt/DL/caffe-ibm$ vi ./examples/imagenet/create_imagenet.sh
...
RESIZE=true
#RESIZE=false

b6p318za@p10a109:/opt/DL/caffe-ibm$ time ./examples/imagenet/create_imagenet.sh
Creating train lmdb...
...
I1116 08:56:31.806813  8234 convert_imageset.cpp:147] Processed 49000 files.
I1116 08:56:37.932953  8234 convert_imageset.cpp:147] Processed 50000 files.
Done.

(약 2시간 넘게 걸립니다.)

# 그 결과로 LMDB file들이 제대로 생성되었는지 확인합니다.

b6p318za@p10a109:/opt/DL/caffe-ibm$ ls -l /nvme/ilsvrc2012/ilsvrc12_train_lmdb
total 287040648
-rw-r----- 1 b6p318za IBM1 293929582592 Nov 16 08:51 data.mdb
-rw-r----- 1 b6p318za IBM1         8192 Nov 16 08:59 lock.mdb
b6p318za@p10a109:/opt/DL/caffe-ibm$ ls -l /nvme/ilsvrc2012/ilsvrc12_val_lmdb
total 11203084
-rw-r----- 1 b6p318za IBM1 11471945728 Nov 16 08:56 data.mdb
-rw-r----- 1 b6p318za IBM1        8192 Nov 16 08:56 lock.mdb


# 생성된 LMDB로부터 전체 imagenet data의 평균값을 구하기 위해 make_imagenet_mean.sh를 수행합니다.  (약 6시간 가까이 걸립니다.)

b6p318za@p10a109:/opt/DL/caffe-ibm$ nohup time ./examples/imagenet/make_imagenet_mean.sh &



# models/bvlc_alexnet/solver.prototxt을 적절한 위치로 copy 한 뒤 수정하여 stepsize를 20000으로 바꾸고, 각종 path도 적절한 위치로 바꿉니다.

b6p318za@p10a109:/tmp$ vi /nvme/bsyu/solver.prototxt
net: "/nvme/bsyu/train_val.prototxt"
test_iter: 1000
test_interval: 1000
base_lr: 0.01
lr_policy: "step"
gamma: 0.1
stepsize: 20000
display: 20
max_iter: 25000
momentum: 0.9
weight_decay: 0.0005
snapshot: 25000
snapshot_prefix: "/nvme/bsyu/models/caffe_alexnet_train"
solver_mode: GPU

# models/bvlc_alexnet/train_val.prototxt를 적절한 위치로 copy 한 뒤 수정하여 train data의 batch_size를 256으로, val data의 batch_size는 64로 바꾸고, 각종 path도 적절히 변경합니다.

b6p318za@p10a109:/tmp$ vi "/nvme/bsyu/train_val.prototxt"
name: "AlexNet"
layer {
  name: "data"
  type: "Data"
  top: "data"
  top: "label"
  include {
    phase: TRAIN
  }
  transform_param {
    mirror: true
    crop_size: 227
    mean_file: "/nvme/ilsvrc2012/imagenet_mean.binaryproto"
  }
  data_param {
    source: "/nvme/ilsvrc2012/ilsvrc12_train_lmdb"
    batch_size: 256
    backend: LMDB
  }
}
layer {
  name: "data"
  type: "Data"
  top: "data"
  top: "label"
  include {
    phase: TEST
  }
  transform_param {
    mirror: false
    crop_size: 227
    mean_file: "/nvme/ilsvrc2012/imagenet_mean.binaryproto"
  }
  data_param {
    source: "/nvme/ilsvrc2012/ilsvrc12_val_lmdb"
    batch_size: 64
    backend: LMDB
  }
}
...


# 다음과 같은 train_alexnet.sh를 만들어 수행합니다.

b6p318za@p10a109:/opt/DL/caffe-nv$ vi ./examples/imagenet/train_alexnet.sh
#!/usr/bin/env sh
set -e
#./bin/caffe train -gpu 0,1 --solver=models/bvlc_alexnet/solver.prototxt
./bin/caffe train -gpu all --solver=models/bvlc_alexnet/solver.prototxt


b6p318za@p10a109:/opt/DL/caffe-nv$ nohup time ./examples/imagenet/train_caffenet.sh &
[1] 9820


# 결과로 생기는 nohup.out에서 grep accuracy를 수행하여, accuracy가 0.5 이상으로 올라갈 때까지 걸린 시간을 계산합니다.

b6p318za@p10a109:/opt/DL/caffe-ibm$ grep accuracy ~/nohup.out
I1118 06:55:19.538242 48597 solver.cpp:442]     Test net output #0: accuracy = 0.000999999
I1118 07:41:13.278080 48597 solver.cpp:442]     Test net output #0: accuracy = 0.18552
I1118 07:50:19.552739 48597 solver.cpp:442]     Test net output #0: accuracy = 0.26228
...
I1118 08:27:07.605918 48597 solver.cpp:442]     Test net output #0: accuracy = 0.405379
I1118 08:36:16.722012 48597 solver.cpp:442]     Test net output #0: accuracy = 0.42512
...
I1118 11:36:23.555583 48597 solver.cpp:442]     Test net output #0: accuracy = 0.50274
I1118 20:38:10.207644 48597 solver.cpp:442]     Test net output #0: accuracy = 0.52026

(약 1시간 남짓 걸릴 것입니다.)

2017년 7월 7일 금요일

GPU를 이용하는 Caffe training을 위한 LSF 환경 setup

LSF를 GPU 환경에서 사용하는 가장 큰 이유는 값비싼 GPU 자원을 여러 deep learning 연구원이 공동으로 사용하는 것을 편리하게 해주기 때문입니다.   가령 내가 GPU 2장을 이용한 training 작업을 걸어야 하는데, 전체 4장의 GPU 중 3장을 누군가 다른 연구원들이 쓰고 있다면 그 작업들이 끝날 때까지 기다려야 합니다.  그 작업들이 언제 끝날 줄 알고 기다리겠습니까 ?  그냥 작업을 돌려 놓고 퇴근하거나 다른 연구에 집중하면 좋겠는데, 무턱대고 그렇게 job을 돌리면 error가 나거나, 다른 연구원이 애써 수행 중인 training job까지 망쳐놓기 딱 좋기 때문에 그럴 수도 없습니다.  

이때 필요한 것이 IBM Spectrum LSF입니다.  GPU를 위한 LSF 설정 방법을 caffe를 예로 삼아 여기에 정리했습니다.

여기서는 NVIDIA K80 GPU 2장 (GK210 GPU * 4장)이 설치된 IBM POWER8 GPU 서버인 S822LC 서버, 흔히 code명 Firestone으로 불리는 서버 1대를 사용했습니다.  OS는 물론 ppc64le 기반의 Ubuntu 16.04 LTS 입니다.

먼저, 다음과 같이 Spectrum LSF HPC Suite를 설치합니다.  정확하게는 HPC Suite 전체를 설치하는 것이 아니라, 여기서는 그 속에 들었는 LSF만을 설치하는 것입니다.  참고로 HPC Suite 속에는 LSF 뿐만 아니라 LS(License Server), PAC(Platform Application Center), PPM(Platform Process Manager), SMPI(Spectrum MPI) 등이 함께 들어 있습니다.  그러나 여기서는 다 필요없고 LSF만 있으면 됩니다.

이 HPC Suite에 들어있는 LSF를 사용하기 위해서는 lsf_std_entitlement.dat 라는 이름의 standard edition용 entitlement file이 필요하고, 이는 license를 정식으로 구매하실 때 별도로 제공됩니다.   정식 버전의 LSF가 아닌, 무료로 사용할 수 있는 Communitty Edition도 있고, 그 설치/사용방법은 이 standard edition과 동일합니다.  단, 일부 기능에 제약이 있습니다.

root@ubuntu02:/home/test# tar -zxvf lsfshpc10.1.1-ppc64le.tar.gz

test@ubuntu02:~/lsfshpc10.1.1-ppc64le$ ls
ls  lsf  pac  ppm  smpi

root@ubuntu02:/home/test# cd lsfshpc10.1.1-ppc64le/lsf/

root@ubuntu02:/home/test/lsfshpc10.1.1-ppc64le/lsf# ls
lsf10.1_lnx310-lib217-ppc64le.tar.Z  lsf10.1_lsfinstall_linux_ppc64le.tar.Z

위와 같이 LSF directory 속에는 두개의 Z 압축 파일이 있는데, 이중 install_ 어쩌고 하는 file만 압축해제하시면 됩니다.  lib 어쩌고 하는 이름의 file은 압축해제하시면 안됩니다.

root@ubuntu02:/home/test/lsfshpc10.1.1-ppc64le/lsf# zcat lsf10.1_lsfinstall_linux_ppc64le.tar.Z |  tar xvf -

root@ubuntu02:/home/test/lsfshpc10.1.1-ppc64le/lsf# cd lsf10.1_lsfinstall

root@ubuntu02:/home/test/lsfshpc10.1.1-ppc64le/lsf/lsf10.1_lsfinstall# ls
conf_tmpl       instlib     lsf_unix_install.pdf  pversions   rpm
hostsetup       lap         patchinstall          README      scripts
install.config  lsfinstall  patchlib              rhostsetup  slave.config

이제 저 install.config를 수정하면 됩니다.  모두 직관적으로 아실 수 있는 이름들의 parameter인데, LSF_MASTER_LIST에는 원래 빈칸(space)로 구분된 여러대의 서버 이름을 적으시는 것입니다.  리스트의 맨 앞에 있는 서버가 active master이고, 그 뒤에 있는 것들이 secondary master들이 됩니다.  여기서는 master이자 slave인 서버가 딱 1대 (ubuntu02) 있으므로, 1대의 이름만 적었습니다.
LSF_ADD_SERVERS에는 실제로 job을 수행할 slave 서버들을 적으셔야 하는데, 역시 빈칸(space)로 구분되는 서버 이름들을 적으시면 됩니다.  여기서는 ubuntu02 1대만 적습니다.
LSF_TARDIR에는 위에서 압축해제하지 말라고 말씀드린, lsf10.1_lnx310-lib217-ppc64le.tar.Z 파일이 들어있는 directory 이름을 적으시면 됩니다.

root@ubuntu02:/home/test/lsfshpc10.1.1-ppc64le/lsf/lsf10.1_lsfinstall# vi install.config
LSF_TOP="/usr/share/lsf"
LSF_ADMINS="test"
LSF_CLUSTER_NAME="firestone"
LSF_MASTER_LIST="ubuntu02"
LSF_TARDIR="/home/test/lsfshpc10.1.1-ppc64le/lsf"
# CONFIGURATION_TEMPLATE="DEFAULT|PARALLEL|HIGH_THROUGHPUT"
LSF_ADD_SERVERS="ubuntu02"

수정이 끝나면 아래와 같이 그 config 파일로 lsfinstall 명령을 수행합니다.

root@ubuntu02:/home/test/lsfshpc10.1.1-ppc64le/lsf/lsf10.1_lsfinstall# ./lsfinstall -f install.config

그리고 위에서 언급한, 미리 받아둔 standard edition용 entitlement file을 다음과 같이 제 위치에 복사합니다.

root@ubuntu02:/home/test/lsfshpc10.1.1-ppc64le/lsf/lsf10.1_lsfinstall# cp /home/test/lsf_std_entitlement.dat /usr/share/lsf/conf/lsf.entitlement


이것이 끝나면 원래 slave 서버에서 수행해야 하는 hostsetup 명령을 수행합니다.  (다시 말씀드리지만 여기서는 ubuntu02 서버가 master이자 slave입니다.)   --boot="y" 옵션을 쓰시면 부팅할 때마다 LSF daemon이 자동으로 구동됩니다.

root@ubuntu02:/home/test/lsfshpc10.1.1-ppc64le/lsf/lsf10.1_lsfinstall# ./hostsetup --top="/usr/share/lsf" --boot="y"

그리고나서 .bashrc 등에 아래와 같이 /usr/share/lsf/conf/profile.lsf가 항상 수행되도록 등록해줍니다.  root 사용자에서 뿐만 아니라, 위에서 LSF admin으로 등록한 test 사용자에서도 같은 entry를 .bashrc에 넣어 줍니다.

root@ubuntu02:/home/test/lsfshpc10.1.1-ppc64le/lsf/lsf10.1_lsfinstall# vi /root/.bashrc
. /usr/share/lsf/conf/profile.lsf

root@ubuntu02:/home/test/lsfshpc10.1.1-ppc64le/lsf/lsf10.1_lsfinstall# . /root/.bashrc

또한 LSF를 sudo 권한으로 수행할 수 있도록 test 사용자를 아래 file에 등록해줍니다.   단, 이 /etc/lsf.sudoers의 permission은 반드시 600, owner는 root:root 여야 합니다.

root@ubuntu02:/home/test/lsfshpc10.1.1-ppc64le/lsf/lsf10.1_lsfinstall# sudo vi /etc/lsf.sudoers
LSB_PRE_POST_EXEC_USER=test
LSF_STARTUP_PATH=/usr/share/lsf/10.1/linux3.10-glibc2.17-ppc64le/etc
LSF_STARTUP_USERS="test"

root@ubuntu02:/home/test/lsfshpc10.1.1-ppc64le/lsf/lsf10.1_lsfinstall#  ls -l /etc/lsf.sudoers
-rw------- 1 root root 126 Jun 29 17:38 /etc/lsf.sudoers


이제 LSF daemon들을 구동합니다.  원래 여러개가 있는데, 하나하나 따로 할 필요없이 lsfstartup으로 시작하고 lsfshutdown으로 끝내면 됩니다.  Master에서 전체 cluster들의 daemon을 다 한꺼번에 살리고 내릴 수 있습니다.   물론 이를 위해서는 passwd 문답 없이도 ssh가 되도록 ssh id를 미리 copy해놓아야 합니다.  여기서는 1대의 서버가 master/slave 노릇을 다 합니다만, 스스로에 대해서도 passwd 문답 없이 ssh가 되도록 설정을 미리 해두어야 합니다.  (여기서는 그 과정 생략했습니다.  그에 대해서는  https://hwengineer.blogspot.kr/2017/06/power8-lsf-tensorflow-docker-image.html 을 참조하십시요.)

root@ubuntu02:/home/test/lsfshpc10.1.1-ppc64le/lsf/lsf10.1_lsfinstall# which lsfstartup
/usr/share/lsf/10.1/linux3.10-glibc2.17-ppc64le/bin/lsfstartup

root@ubuntu02:/home/test/lsfshpc10.1.1-ppc64le/lsf/lsf10.1_lsfinstall# lsfstartup
Starting up all LIMs ...
Do you really want to start up LIM on all hosts ? [y/n]y
Start up LIM on <ubuntu02> ...... done

Waiting for Master LIM to start up ...  Master LIM is ok
Starting up all RESes ...
Do you really want to start up RES on all hosts ? [y/n]y
Start up RES on <ubuntu02> ...... done

Starting all slave daemons on LSBATCH hosts ...
Do you really want to start up slave batch daemon on all hosts ? [y/n] y
Start up slave batch daemon on <ubuntu02> ...... done

Done starting up LSF daemons on the local LSF cluster ...


일단 LSF cluster는 구성이 되었습니다.   그러나 여기서 그대로 GPU를 이용하는 caffe job을 submit하면 error가 나는 것을 보실 수 있을 겁니다.   그 이유와 해결 방법에 대해서 찬찬히 살펴보겠습니다.

먼저, caffe를 이용하여 CIFAR-10 모델을 training하기 위한 준비를 하겠습니다.

편의를 위해, 아래와 같이 test 사용자가 passwd 문답 없이도 sudo를 수행할 수 있도록 설정을 하겠습니다.

test@ubuntu02:/opt/DL/caffe-nv$ sudo vi /etc/sudoers
...
test  ALL=(ALL) NOPASSWD: ALL


이제 PowerAI toolkit에 포함된 NVIDIA Caffe (caffe-nv)를 이용하여 CIFAR-10 data와 script를 준비하겠습니다.  쉽습니다.

test@ubuntu02:/opt/DL/caffe-nv$ sudo ./data/cifar10/get_cifar10.sh
Downloading...
--2017-07-04 10:37:53--  http://www.cs.toronto.edu/~kriz/cifar-10-binary.tar.gz
Resolving www.cs.toronto.edu (www.cs.toronto.edu)... 128.100.3.30
Connecting to www.cs.toronto.edu (www.cs.toronto.edu)|128.100.3.30|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 170052171 (162M) [application/x-gzip]
Saving to: ‘cifar-10-binary.tar.gz’

cifar-10-binary.tar.gz   100%[================================>] 162.17M  4.10MB/s    in 25s

2017-07-04 10:38:19 (6.59 MB/s) - ‘cifar-10-binary.tar.gz’ saved [170052171/170052171]

Unzipping...
Done.

일부 script의 PATH는 잘못 되어 있으므로 아래와 같이 수정해줍니다.

test@ubuntu02:/opt/DL/caffe-nv$ sudo vi ./examples/cifar10/create_cifar10.sh
...
if [ -z "$CAFFE_BIN" ]; then
#  EXAMPLES=./build/$EXAMPLE
  EXAMPLES=./bin
#  TOOLS=./build/tools
  TOOLS=./bin
else
...

이제 아래와 같이 CIFAR-10 LMDB를 생성합니다.

test@ubuntu02:/opt/DL/caffe-nv$ sudo LD_LIBRARY_PATH=/opt/DL/openblas/lib:/opt/DL/nccl/lib:/opt/DL/caffe-nv/lib ./examples/cifar10/create_cifar10.sh
Creating lmdb...
I0704 10:58:13.721052 84760 db_lmdb.cpp:35] Opened lmdb examples/cifar10/cifar10_train_lmdb
I0704 10:58:13.721252 84760 convert_cifar_data.cpp:52] Writing Training data
I0704 10:58:13.721264 84760 convert_cifar_data.cpp:55] Training Batch 1
I0704 10:58:13.764257 84760 convert_cifar_data.cpp:55] Training Batch 2
I0704 10:58:13.801908 84760 convert_cifar_data.cpp:55] Training Batch 3
I0704 10:58:13.830626 84760 convert_cifar_data.cpp:55] Training Batch 4
I0704 10:58:13.877624 84760 convert_cifar_data.cpp:55] Training Batch 5
I0704 10:58:18.264618 84760 convert_cifar_data.cpp:73] Writing Testing data
I0704 10:58:18.264998 84760 db_lmdb.cpp:35] Opened lmdb examples/cifar10/cifar10_test_lmdb
Computing image mean...
Done.

이제 CIFAR-10 training을 시작할 준비가 끝났습니다.  기본으로 제공되는 train_quick.sh을 그냥 수행해보면 아래와 같이 1장의 GPU를 이용해 training이 잘 수행됩니다.  (여기서도 아래처럼 build 대신 bin directory로 일부 script 내용을 고쳐야 합니다.)

test@ubuntu02:/opt/DL/caffe-nv$ sudo vi ./examples/cifar10/train_quick.sh
...
if [ -z "$CAFFE_BIN" ]; then
#  TOOLS=./build/tools
  TOOLS=./bin
else
  TOOLS=$CAFFE_BIN
fi

$TOOLS/caffe train \
  --solver=examples/cifar10/cifar10_quick_solver.prototxt


test@ubuntu02:/opt/DL/caffe-nv$ sudo LD_LIBRARY_PATH=/opt/DL/openblas/lib:/opt/DL/nccl/lib:/opt/DL/caffe-nv/lib time ./examples/cifar10/train_quick.sh
...
I0704 11:48:33.841511 87435 sgd_solver.cpp:106] Iteration 4800, lr = 0.0001
I0704 11:48:35.339391 87435 solver.cpp:242] Iteration 4900 (66.76 iter/s, 1.4979s/100 iter), loss = 0.38117
I0704 11:48:35.339428 87435 solver.cpp:261]     Train net output #0: loss = 0.38117 (* 1 = 0.38117 loss)
I0704 11:48:35.339442 87435 sgd_solver.cpp:106] Iteration 4900, lr = 0.0001
I0704 11:48:36.822921 87435 solver.cpp:489] Snapshotting to HDF5 file examples/cifar10/cifar10_quick_iter_5000.caffemodel.h5
I0704 11:48:36.824291 87435 sgd_solver.cpp:283] Snapshotting solver state to HDF5 file examples/cifar10/cifar10_quick_iter_5000.solverstate.h5
I0704 11:48:36.829028 87435 solver.cpp:342] Iteration 5000, loss = 0.456113
I0704 11:48:36.829043 87435 solver.cpp:362] Iteration 5000, Testing net (#0)
I0704 11:48:37.224135 87435 solver.cpp:429]     Test net output #0: accuracy = 0.7594
I0704 11:48:37.224155 87435 solver.cpp:429]     Test net output #1: loss = 0.734521 (* 1 = 0.734521 loss)
I0704 11:48:37.224179 87435 solver.cpp:347] Optimization Done.
I0704 11:48:37.224186 87435 caffe.cpp:234] Optimization Done.
138.60user 30.86system 1:23.55elapsed 202%CPU (0avgtext+0avgdata 678784maxresident)k
16inputs+6016outputs (0major+24918minor)pagefaults 0swaps

위 training은 아래의 'nvidia-smi -l 5' 명령으로 모니터링한 결과처럼, GPU 1장을 이용합니다.  Default로 caffe는 무조건 첫번째 GPU에 job을 던집니다.  (여기서는 GPU 2를 첫번째 GPU로 인식하네요.)

Tue Jul  4 11:48:09 2017
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 361.119                Driver Version: 361.119                   |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Tesla K80           Off  | 0002:03:00.0     Off |                    0 |
| N/A   39C    P8    26W / 149W |      2MiB / 11441MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
|   1  Tesla K80           Off  | 0002:04:00.0     Off |                    0 |
| N/A   36C    P8    30W / 149W |      2MiB / 11441MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
|   2  Tesla K80           Off  | 0004:03:00.0     Off |                    0 |
| N/A   57C    P0   129W / 149W |    216MiB / 11441MiB |     95%      Default |
+-------------------------------+----------------------+----------------------+
|   3  Tesla K80           Off  | 0004:04:00.0     Off |                    0 |
| N/A   38C    P8    29W / 149W |      2MiB / 11441MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID  Type  Process name                               Usage      |
|=============================================================================|
|    2     87308    C   ./bin/caffe                                    214MiB |
+-----------------------------------------------------------------------------+


이번에는 CPU를 2장씩 사용하여 training하도록 해보겠습니다.  caffe 명령에서 -gpu 옵션을 쓰도록 train_quick.sh 스크립트를 수정해줍니다.

test@ubuntu02:/opt/DL/caffe-nv$ sudo vi ./examples/cifar10/train_quick.sh
if [ -z "$CAFFE_BIN" ]; then
#  TOOLS=./build/tools
  TOOLS=./bin
else
  TOOLS=$CAFFE_BIN
fi

$TOOLS/caffe train -gpu 0,1 \
  --solver=examples/cifar10/cifar10_quick_solver.prototxt

# reduce learning rate by factor of 10 after 8 epochs
$TOOLS/caffe train -gpu 0,1 \
  --solver=examples/cifar10/cifar10_quick_solver_lr1.prototxt \
  --snapshot=examples/cifar10/cifar10_quick_iter_4000.solverstate.h5


이제 수행해보면 2장씩 쓰는 것을 보실 수 있습니다.   GPU 2, 3을 쓰는군요.

test@ubuntu02:/opt/DL/caffe-nv$ sudo LD_LIBRARY_PATH=/opt/DL/openblas/lib:/opt/DL/nccl/lib:/opt/DL/caffe-nv/lib time ./examples/cifar10/train_quick.sh
...
I0704 11:51:57.520256 87780 solver.cpp:242] Iteration 4800 (94.5059 iter/s, 1.05814s/100 iter), loss = 0.429975
I0704 11:51:57.520298 87780 solver.cpp:261]     Train net output #0: loss = 0.429975 (* 1 = 0.429975 loss)
I0704 11:51:57.520318 87780 sgd_solver.cpp:106] Iteration 4800, lr = 0.0001
I0704 11:51:58.578877 87780 solver.cpp:242] Iteration 4900 (94.4687 iter/s, 1.05855s/100 iter), loss = 0.631555
I0704 11:51:58.578930 87780 solver.cpp:261]     Train net output #0: loss = 0.631555 (* 1 = 0.631555 loss)
I0704 11:51:58.578975 87780 sgd_solver.cpp:106] Iteration 4900, lr = 0.0001
I0704 11:51:59.628901 87780 solver.cpp:489] Snapshotting to HDF5 file examples/cifar10/cifar10_quick_iter_5000.caffemodel.h5
I0704 11:51:59.630488 87780 sgd_solver.cpp:283] Snapshotting solver state to HDF5 file examples/cifar10/cifar10_quick_iter_5000.solverstate.h5
I0704 11:51:59.633839 87780 solver.cpp:342] Iteration 5000, loss = 0.444928
I0704 11:51:59.633874 87780 solver.cpp:362] Iteration 5000, Testing net (#0)
I0704 11:52:00.025651 87780 solver.cpp:429]     Test net output #0: accuracy = 0.7373
I0704 11:52:00.025693 87780 solver.cpp:429]     Test net output #1: loss = 0.784022 (* 1 = 0.784022 loss)
I0704 11:52:00.025703 87780 solver.cpp:347] Optimization Done.
I0704 11:52:00.041434 87780 caffe.cpp:234] Optimization Done.
162.54user 28.23system 1:02.22elapsed 306%CPU (0avgtext+0avgdata 997696maxresident)k
0inputs+6016outputs (0major+36442minor)pagefaults 0swaps


Tue Jul  4 11:51:21 2017
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 361.119                Driver Version: 361.119                   |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Tesla K80           Off  | 0002:03:00.0     Off |                    0 |
| N/A   39C    P8    26W / 149W |      2MiB / 11441MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
|   1  Tesla K80           Off  | 0002:04:00.0     Off |                    0 |
| N/A   36C    P8    30W / 149W |      2MiB / 11441MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
|   2  Tesla K80           Off  | 0004:03:00.0     Off |                    0 |
| N/A   50C    P0   113W / 149W |    191MiB / 11441MiB |     92%      Default |
+-------------------------------+----------------------+----------------------+
|   3  Tesla K80           Off  | 0004:04:00.0     Off |                    0 |
| N/A   49C    P0   128W / 149W |    152MiB / 11441MiB |     92%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID  Type  Process name                               Usage      |
|=============================================================================|
|    2     87633    C   ./bin/caffe                                    189MiB |
|    3     87633    C   ./bin/caffe                                    150MiB |
+-----------------------------------------------------------------------------+


이번에는 이 script를 2번 연속으로 수행해보겠습니다.   여기서는 스크립트 안에 -gpu 0,1이라고 지정되어 있으므로, 두 job이 모두 같은 2개의 GPU를 이용하려고 들 것입니다.  이럴 경우 어떻게 될까요 ?   위에서 보시다시피 GPU당 메모리는 180MB 정도만 사용하므로 2개 job이 동시에 돌아도 문제는 없을 것처럼 보입니다.

Wed Jul  5 10:42:55 2017
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 361.119                Driver Version: 361.119                   |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Tesla K80           Off  | 0002:03:00.0     Off |                    0 |
| N/A   39C    P8    27W / 149W |      2MiB / 11441MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
|   1  Tesla K80           Off  | 0002:04:00.0     Off |                    0 |
| N/A   36C    P8    30W / 149W |      2MiB / 11441MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
|   2  Tesla K80           Off  | 0004:03:00.0     Off |                    0 |
| N/A   44C    P0    75W / 149W |    383MiB / 11441MiB |     99%      Default |
+-------------------------------+----------------------+----------------------+
|   3  Tesla K80           Off  | 0004:04:00.0     Off |                    0 |
| N/A   44C    P0    85W / 149W |    304MiB / 11441MiB |     99%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID  Type  Process name                               Usage      |
|=============================================================================|
|    2      7906    C   ./bin/caffe                                    189MiB |
|    2      8023    C   ./bin/caffe                                    189MiB |
|    3      7906    C   ./bin/caffe                                    150MiB |
|    3      8023    C   ./bin/caffe                                    150MiB |
+-----------------------------------------------------------------------------+

결론적으로는 이렇게 수행하면 일단 수행 시작은 됩니다만, 두 job이 모두 두배씩 더 오래 걸려 수행되는 것이 아니라 위와 같은 상태에서 아예 hang이 걸려 버립니다.  즉, 2개 job이 서로에게 lock을 걸어 버리는 것입니다.

이런 현상을 피하려면 지금 어느 GPU가 놀고 있는지 확인한 뒤 caffe를 수행하는 스크립트를 수정하여 놀고 있는 GPU 번호를 적어야 합니다.  여기서는 -gpu 0,1이 아니라 -gpu 2,3으로 적어야 하는 것이지요.  이렇게 하면 아래와 같이 잘 수행됩니다.

$TOOLS/caffe train -gpu 0,1 --solver=examples/cifar10/cifar10_quick_solver.prototxt

$TOOLS/caffe train -gpu 2,3 --solver=examples/cifar10/cifar10_quick_solver.prototxt

Wed Jul  5 11:09:03 2017
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 361.119                Driver Version: 361.119                   |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Tesla K80           Off  | 0002:03:00.0     Off |                    0 |
| N/A   46C    P0   111W / 149W |    191MiB / 11441MiB |     90%      Default |
+-------------------------------+----------------------+----------------------+
|   1  Tesla K80           Off  | 0002:04:00.0     Off |                    0 |
| N/A   44C    P0   124W / 149W |    152MiB / 11441MiB |     87%      Default |
+-------------------------------+----------------------+----------------------+
|   2  Tesla K80           Off  | 0004:03:00.0     Off |                    0 |
| N/A   48C    P0   110W / 149W |    191MiB / 11441MiB |     92%      Default |
+-------------------------------+----------------------+----------------------+
|   3  Tesla K80           Off  | 0004:04:00.0     Off |                    0 |
| N/A   47C    P0   127W / 149W |    152MiB / 11441MiB |     92%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID  Type  Process name                               Usage      |
|=============================================================================|
|    0      5343    C   ./bin/caffe                                    189MiB |
|    1      5343    C   ./bin/caffe                                    150MiB |
|    2      5221    C   ./bin/caffe                                    189MiB |
|    3      5221    C   ./bin/caffe                                    150MiB |
+-----------------------------------------------------------------------------+

그러나 이와 같이 일일이 GPU 상황을 모니터링하고 그에 따라 수행 스크립트를 고친다는 것은 당연히 불편한 일입니다.  LSF를 이용하는 이유가 그런 모니터링 없이도, 그저 수행 스크립트 대충 짜서 submit하면 알아서 스케쥴링을 해주기를 바라기 때문인데, 일일이 그 수행 스크립트를 수정하는 것은 곤란합니다.

특히 caffe는 특성상 -gpu 옵션을 안 쓰는 것도 문제입니다.  -gpu 옵션을 안 쓸 경우, 무조건 첫번째 GPU로 job이 assign 되거든요.  따라서 caffe에서 -gpu 옵션을 쓰지 않는다면 수작업으로 job을 직접 수행하든 LSF로 수행하든 다 error가 날 수 밖에 없습니다.

이 문제의 해결을 위해서는 아래와 같은 과정을 거쳐야 합니다.  먼저, GPU의 compute mode를 default(shared) mode에서 exclusive mode로 변경해주어야 합니다.

test@ubuntu02:~$ nvidia-smi -q | grep -i compute
    Compute Mode                    : Default
    Compute Mode                    : Default
    Compute Mode                    : Default
    Compute Mode                    : Default

Document를 보면 compute mode 1은 EXCLUSIVE_THREAD라고 되어있는데, CUDA 8.0에서는 그 mode는 depreciated 되었다면서 그냥 EXCLUSIVE_PROCESS (3)으로 설정하네요.

test@ubuntu02:~$ sudo nvidia-smi -c 1
Warning: Exclusive_Thread was deprecated! Setting Exclusive_Process instead.
Set compute mode to EXCLUSIVE_PROCESS for GPU 0002:03:00.0.
Warning: Exclusive_Thread was deprecated! Setting Exclusive_Process instead.
Set compute mode to EXCLUSIVE_PROCESS for GPU 0002:04:00.0.
Warning: Exclusive_Thread was deprecated! Setting Exclusive_Process instead.
Set compute mode to EXCLUSIVE_PROCESS for GPU 0004:03:00.0.
Warning: Exclusive_Thread was deprecated! Setting Exclusive_Process instead.
Set compute mode to EXCLUSIVE_PROCESS for GPU 0004:04:00.0.
All done.

참고로 compute mode 2는 PROHIBITED, 즉 연산은 아예 못 하게 막는 모드입니다.

test@ubuntu02:~$ sudo nvidia-smi -c 2
Set compute mode to PROHIBITED for GPU 0002:03:00.0.
Set compute mode to PROHIBITED for GPU 0002:04:00.0.
Set compute mode to PROHIBITED for GPU 0004:03:00.0.
Set compute mode to PROHIBITED for GPU 0004:04:00.0.
All done.

실제적으로는 그냥 3번 mode를 택하셔야 합니다.  어차피 1번 mode를 택해도 둘다 EXCLUSIVE_PROCESS로 setting 됩니다.  이 모드는 reboot하면 없어지므로, 영구히 setup하기 위해서는 /etc/rc.local 등에 등록해야 합니다.

test@ubuntu02:~$ sudo nvidia-smi -c 3
Set compute mode to EXCLUSIVE_PROCESS for GPU 0002:03:00.0.
Set compute mode to EXCLUSIVE_PROCESS for GPU 0002:04:00.0.
Set compute mode to EXCLUSIVE_PROCESS for GPU 0004:03:00.0.
Set compute mode to EXCLUSIVE_PROCESS for GPU 0004:04:00.0.
All done.

이제 gpu 0에서 training이 돌고 있는데 두번째 training에서 동일한 gpu 0을 쓰려고 하면 나중에 수행된 job은 아래와 같이 error가 발생하면서 fail나는 것을 보실 수 있습니다.  먼저 수행되던 것은 영향을 받지 않고 정상적으로 수행됩니다.

test@ubuntu02:/opt/DL/caffe-nv$ sudo LD_LIBRARY_PATH=/opt/DL/openblas/lib:/opt/DL/nccl/lib:/opt/DL/caffe-nv/lib time ./examples/cifar10/train_quick.sh
F0705 11:32:12.339743  7616 gpu_memory.cpp:168] Check failed: error == cudaSuccess (46 vs. 0)  all CUDA-capable devices are busy or unavailable
*** Check failure stack trace: ***
    @     0x3fff9f28ce0c  google::LogMessage::Fail()
    @     0x3fff9f28f284  google::LogMessage::SendToLog()
    @     0x3fff9f28c768  google::LogMessage::Flush()
    @     0x3fff9f2911c4  google::LogMessageFatal::~LogMessageFatal()
    @     0x3fff9f5d9c50  caffe::GPUMemory::Manager::update_dev_info()
    @     0x3fff9f5daf74  caffe::GPUMemory::Manager::init()
    @         0x1000b128  (unknown)
    @         0x10007b54  (unknown)
    @     0x3fff9e97309c  (unknown)
    @     0x3fff9e973298  __libc_start_main
    @              (nil)  (unknown)
Aborted (core dumped)
F0705 11:32:12.533152  7620 gpu_memory.cpp:168] Check failed: error == cudaSuccess (46 vs. 0)  all CUDA-capable devices are busy or unavailable
*** Check failure stack trace: ***
    @     0x3fff9fb7ce0c  google::LogMessage::Fail()
    @     0x3fff9fb7f284  google::LogMessage::SendToLog()
    @     0x3fff9fb7c768  google::LogMessage::Flush()
    @     0x3fff9fb811c4  google::LogMessageFatal::~LogMessageFatal()
    @     0x3fff9fec9c50  caffe::GPUMemory::Manager::update_dev_info()
    @     0x3fff9fecaf74  caffe::GPUMemory::Manager::init()
    @         0x1000b128  (unknown)
    @         0x10007b54  (unknown)
    @     0x3fff9f26309c  (unknown)
    @     0x3fff9f263298  __libc_start_main
    @              (nil)  (unknown)
Aborted (core dumped)
Command exited with non-zero status 134
0.07user 0.08system 0:00.37elapsed 42%CPU (0avgtext+0avgdata 64512maxresident)k
0inputs+1024outputs (0major+3069minor)pagefaults 0swaps


이번에는 LSF로 caffe job을 submit 해보겠습니다.   별다른 옵션 없이, 그냥 bsub 명령을 앞에 붙이기만 하면 됩니다.

test@ubuntu02:/opt/DL/caffe-nv$ bsub sudo LD_LIBRARY_PATH=/opt/DL/openblas/lib:/opt/DL/nccl/lib:/opt/DL/caffe-nv/lib time ./examples/cifar10/train_quick.sh
Job <220> is submitted to default queue <normal>.

test@ubuntu02:/opt/DL/caffe-nv$ bsub sudo LD_LIBRARY_PATH=/opt/DL/openblas/lib:/opt/DL/nccl/lib:/opt/DL/caffe-nv/lib time ./examples/cifar10/train_quick.sh
Job <221> is submitted to default queue <normal>.

Submit은 잘 되었으나, 실제로 job이 잘 돌아가는지 봐야지요.  이는 bhist 명령으로 볼 수 있습니다.   당연한 일이지만, 일단 첫번째로 submit한 job은 잘 완료되었습니다.

test@ubuntu02:/opt/DL/caffe-nv$ bhist -l 220

Job <220>, User <test>, Project <default>, Command <sudo LD_LIBRARY_PATH=/opt/D
                     L/openblas/lib:/opt/DL/nccl/lib:/opt/DL/caffe-nv/lib time
                     ./examples/cifar10/train_quick.sh>
Wed Jul  5 11:34:11: Submitted from host <ubuntu02>, to Queue <normal>, CWD </o
                     pt/DL/caffe-nv>;
Wed Jul  5 11:34:12: Dispatched 1 Task(s) on Host(s) <ubuntu02>, Allocated 1 Sl
                     ot(s) on Host(s) <ubuntu02>, Effective RES_REQ <select[typ
                     e == local] order[r15s:pg] >;
Wed Jul  5 11:34:12: Starting (Pid 7824);
Wed Jul  5 11:34:12: Running with execution home </home/test>, Execution CWD </
                     opt/DL/caffe-nv>, Execution Pid <7824>;
Wed Jul  5 11:35:43: Done successfully. The CPU time used is 183.6 seconds;
Wed Jul  5 11:35:45: Post job process done successfully;

MEMORY USAGE:
MAX MEM: 271 Mbytes;  AVG MEM: 269 Mbytes

Summary of time in seconds spent in various states by  Wed Jul  5 11:35:45
  PEND     PSUSP    RUN      USUSP    SSUSP    UNKWN    TOTAL
  1        0        91       0        0        0        92

문제는 두번째 job인데, 역시 안 되었습니다.  Exit code 134, 그러니까 수작업으로 돌렸을 때와 동일한 error가 납니다.

test@ubuntu02:/opt/DL/caffe-nv$ bhist -l 221

Job <221>, User <test>, Project <default>, Command <sudo LD_LIBRARY_PATH=/opt/D
                     L/openblas/lib:/opt/DL/nccl/lib:/opt/DL/caffe-nv/lib time
                     ./examples/cifar10/train_quick.sh>
Wed Jul  5 11:35:15: Submitted from host <ubuntu02>, to Queue <normal>, CWD </o
                     pt/DL/caffe-nv>;
Wed Jul  5 11:35:15: Dispatched 1 Task(s) on Host(s) <ubuntu02>, Allocated 1 Sl
                     ot(s) on Host(s) <ubuntu02>, Effective RES_REQ <select[typ
                     e == local] order[r15s:pg] >;
Wed Jul  5 11:35:15: Starting (Pid 7963);
Wed Jul  5 11:35:15: Running with execution home </home/test>, Execution CWD </
                     opt/DL/caffe-nv>, Execution Pid <7963>;
Wed Jul  5 11:35:18: Exited with exit code 134. The CPU time used is 0.7 second
                     s;
Wed Jul  5 11:35:18: Completed <exit>;

MEMORY USAGE:
MAX MEM: 37 Mbytes;  AVG MEM: 37 Mbytes

Summary of time in seconds spent in various states by  Wed Jul  5 11:35:18
  PEND     PSUSP    RUN      USUSP    SSUSP    UNKWN    TOTAL
  0        0        3        0        0        0        3


이 error의 원인은 무엇일까요 ?  생각해보면 간단합니다.  LSF에는 현재 GPU 자원에 대한 감시 체계도 갖춰져 있지 않고, 또 제가 job을 submit 할 때 GPU job에 대한 요구조건도 주지 않았습니다.  따라서, LSF는 그냥 기본 요구조건인 slot (CPU 자원) 상황만 보고 job을 배치한 것이고, 따라서 같은 GPU에 대해 2개 caffe job이 수행되는 결과를 낳은 것입니다.

이 문제를 해결하기 위한 첫번째 단계는 LSF에게 GPU 자원을 인식하고 모니터링하게 등록하는 것입니다.

맨 먼저, LSF 10.1에서는 어떤 GPU 자원 항목이 있는지 보시겠습니다.  이를 위해 elim.gpu 명령을 수행하겠습니다.  이 명령은 /usr/share/lsf/10.1/linux3.10-glibc2.17-ppc64le/etc 밑에 존재하고, 따로 종료되지 않으므로 그냥 control-C로 끊어주셔야 합니다.

test@ubuntu02:/opt/DL/caffe-nv$ elim.gpu
4 ngpus 4 ngpus_shared 0 ngpus_excl_t 0 ngpus_excl_p 4
^C

맨 앞에 나오는 숫자 4는 4개 parameter가 display된다는 뜻이고, GPU 개수(ngpus)가 4, shared mode의 GPU(ngpus_shared)가 0, exclusive thread mode의 GPU(ngpus_excl_t)가 0, 끝으로 exclusive process mode의 GPU(ngpus_excl_p)가 4개 있다는 뜻입니다.  이는 바로 위에서 제가 GPU의 compute mode를 3, 즉 EXCLUSIVE_PROCESS 로 설정했기 때문에 이렇게 나오는 것입니다.

이제 이 항목을 lsf에 등록하겠습니다.  LSF conf directory에 가서 lsf.shared 파일을 수정하면 되는데, 기존 stanza를 보면 Begin Resource와 End Resource 사이에 mips니 sparc이니 하는 항목이 보이고, aix라는 이름도 보입니다.

test@ubuntu02:/usr/share/lsf/10.1/linux3.10-glibc2.17-ppc64le/etc$ cd $LSF_ENVDIR

test@ubuntu02:/usr/share/lsf/conf$ vi lsf.shared

Begin Resource
   mips       Boolean ()       ()          (MIPS architecture)
   sparc      Boolean ()       ()          (SUN SPARC)
   hpux       Boolean ()       ()          (HP-UX UNIX)
   aix        Boolean ()       ()          (AIX UNIX)
   irix       Boolean ()       ()          (IRIX UNIX)
... 중략 ...
   openmpi     Boolean ()       ()         (OPENMPI)
   bluegene   Boolean ()       ()          (BLUEGENE)
   define_ncpus_procs   Boolean () ()      (ncpus := procs)
   define_ncpus_cores   Boolean () ()      (ncpus := cores)
   define_ncpus_threads Boolean () ()      (ncpus := threads)
   vnode      Boolean ()       ()          (Simulation node used by integrations for example Cray Linux)
   craylinux  Boolean ()       ()          (Cray Linux Environment: CRAY XT/XE login nodes and compute nodes)
   gpu        Boolean ()       ()          (gpu availability)
End Resource

이 항목들은 그대로 내버려 두시고, 그 밑에 아래와 같은 새로운 Begin Resource ~ End Resource stanza를 삽입해줍니다.

Begin Resource

RESOURCENAME  TYPE     INTERVAL  INCREASING  CONSUMABLE  DESCRIPTION # Keywords
ngpus         Numeric  60        N           N           (Number of GPUs)
ngpus_shared     Numeric  60        N           N           (Number of GPUs in Shared Mode)
ngpus_excl_t  Numeric  60        N           Y           (Number of GPUs in Exclusive thread Mode)
ngpuprohibited Numeric  60        N           N           (Number of GPUs in Prohibited Mode)
ngpus_excl_p  Numeric  60        N           Y           (Number of GPUs in Exclusive process Mode)

End Resource

이어서 lsf.cluster."cluster_name" 파일도 수정해줍니다.  여기서 제 cluster의 이름은 firestone입니다.   역시 기존 항목들은 내버려두시고, 아래의 Begin ResourceMap ~ End ResourceMap 부분을 추가해줍니다.

test@ubuntu02:/usr/share/lsf/conf$ vi lsf.cluster.firestone
...
Begin ResourceMap
RESOURCENAME            LOCATION
ngpus                   ([default])
ngpus_shared               ([default])
ngpus_excl_t            ([default])
ngpuprohibited           ([default])
ngpus_excl_p            ([default])
End ResourceMap


이제 reconfig를 합니다.

test@ubuntu02:/usr/share/lsf/conf$ lsadmin reconfig

Checking configuration files ...
No errors found.

Restart only the master candidate hosts? [y/n] n
Do you really want to restart LIMs on all hosts? [y/n] y
Restart LIM on <ubuntu02> ...... done

test@ubuntu02:/usr/share/lsf/conf$ badmin reconfig

Checking configuration files ...

No errors found.

Reconfiguration initiated


이제 bhosts -l 명령을 내려 봅니다.

test@ubuntu02:~$ bhosts -l
HOST  ubuntu02
STATUS           CPUF  JL/U    MAX  NJOBS    RUN  SSUSP  USUSP    RSV DISPATCH_WINDOW
ok             250.00     -     16      0      0      0      0      0      -

 CURRENT LOAD USED FOR SCHEDULING:
                r15s   r1m  r15m    ut    pg    io   ls    it   tmp   swp   mem  slots  ngpus
 Total           0.0   0.0   0.0    0%   0.0    29    1     0  782G 37.6G  125G     16    4.0
 Reserved        0.0   0.0   0.0    0%   0.0     0    0     0    0M    0M    0M      -    0.0

               ngpus_shared ngpus_excl_t ngpuprohibited ngpus_excl_p
 Total                  0.0          0.0            0.0          4.0
 Reserved               0.0          0.0             -           0.0


 LOAD THRESHOLD USED FOR SCHEDULING:
           r15s   r1m  r15m   ut      pg    io   ls    it    tmp    swp    mem
 loadSched   -     -     -     -       -     -    -     -     -      -      -
 loadStop    -     -     -     -       -     -    -     -     -      -      -

            ngpus ngpus_shared ngpus_excl_t ngpuprohibited ngpus_excl_p
 loadSched     -            -            -              -            -
 loadStop      -            -            -              -            -


 CONFIGURED AFFINITY CPU LIST: all


방금 제가 등록한 ngpus_shared ngpus_excl_t ngpuprohibited ngpus_excl_p  항목들이 모니터링 되는 것을 보실 수 있습니다.

이제 bsub 명령만 붙여서 caffe job을 submit 해보겠습니다.

test@ubuntu02:/opt/DL/caffe-nv$ bsub sudo LD_LIBRARY_PATH=/opt/DL/openblas/lib:/opt/DL/nccl/lib:/opt/DL/caffe-nv/lib time ./examples/cifar10/train_quick.sh
Job <1033> is submitted to default queue <normal>.

아래 보시다시피 이 job 자체는 잘 돌아갑니다.  상태가 RUN인 것을 확인하십시요.

test@ubuntu02:/opt/DL/caffe-nv$ bjobs
JOBID   USER    STAT  QUEUE      FROM_HOST   EXEC_HOST   JOB_NAME   SUBMIT_TIME
1033    test    RUN   normal     ubuntu02    ubuntu02    *_quick.sh Jul  7 10:27

그러나 bhosts -l 명령으로 보면, ngpus_excl_p가 여전히 total 4개로 보이고, Reserved 항목은 0으로 되어 있는 것을 보실 수 있습니다.   이때 실제로 nvidia-smi 명령으로 보면 GPU 1개가 caffe를 열심히 수행하고 있는데도 그렇습니다.

test@ubuntu02:/opt/DL/caffe-nv$ bhosts -l
HOST  ubuntu02
STATUS           CPUF  JL/U    MAX  NJOBS    RUN  SSUSP  USUSP    RSV DISPATCH_WINDOW
ok             250.00     -     16      1      1      0      0      0      -

 CURRENT LOAD USED FOR SCHEDULING:
                r15s   r1m  r15m    ut    pg    io   ls    it   tmp   swp   mem  slots  ngpus
 Total           0.0   0.0   0.0    0%   0.0    58    1     0  782G 37.6G 124.6G     15    4.0
 Reserved        0.0   0.0   0.0    0%   0.0     0    0     0    0M    0M    0M      -    0.0

               ngpus_shared ngpus_excl_t ngpuprohibited ngpus_excl_p
 Total                  0.0          0.0            0.0          4.0
 Reserved               0.0          0.0             -           0.0


 LOAD THRESHOLD USED FOR SCHEDULING:
           r15s   r1m  r15m   ut      pg    io   ls    it    tmp    swp    mem
 loadSched   -     -     -     -       -     -    -     -     -      -      -
 loadStop    -     -     -     -       -     -    -     -     -      -      -

            ngpus ngpus_shared ngpus_excl_t ngpuprohibited ngpus_excl_p
 loadSched     -            -            -              -            -
 loadStop      -            -            -              -            -


 CONFIGURED AFFINITY CPU LIST: all

test@ubuntu02:/opt/DL/caffe-nv$ bhist -l 1033

Job <1033>, User <test>, Project <default>, Command <sudo LD_LIBRARY_PATH=/opt/
                     DL/openblas/lib:/opt/DL/nccl/lib:/opt/DL/caffe-nv/lib time
                      ./examples/cifar10/train_quick.sh>
Fri Jul  7 10:27:55: Submitted from host <ubuntu02>, to Queue <normal>, CWD </o
                     pt/DL/caffe-nv>;
Fri Jul  7 10:27:55: Dispatched 1 Task(s) on Host(s) <ubuntu02>, Allocated 1 Sl
                     ot(s) on Host(s) <ubuntu02>, Effective RES_REQ <select[typ
                     e == local] order[r15s:pg] >;
Fri Jul  7 10:27:55: Starting (Pid 14241);
Fri Jul  7 10:27:55: Running with execution home </home/test>, Execution CWD </
                     opt/DL/caffe-nv>, Execution Pid <14241>;

Summary of time in seconds spent in various states by  Fri Jul  7 10:28:23
  PEND     PSUSP    RUN      USUSP    SSUSP    UNKWN    TOTAL
  0        0        28       0        0        0        28

이 상황에서 caffe job을 하나 더 넣을 경우, 아래와 같이 exit code 134와 함께 error가 납니다.  즉, caffe가 default 거동에 따라 첫번째 GPU에 또 caffe job을 배치하므로 error가 나면서 job이 죽는 것입니다.   이와 같은 상황은 LSF가 이렇게 submit된 job을 GPU 자원을 필요로 하는 job이라고 인식 못 하기 때문에 발생하는 것입니다.

test@ubuntu02:/opt/DL/caffe-nv$ bsub sudo LD_LIBRARY_PATH=/opt/DL/openblas/lib:/opt/DL/nccl/lib:/opt/DL/caffe-nv/lib time ./examples/cifar10/train_quick.sh
Job <1034> is submitted to default queue <normal>.
test@ubuntu02:/opt/DL/caffe-nv$ bjobs
JOBID   USER    STAT  QUEUE      FROM_HOST   EXEC_HOST   JOB_NAME   SUBMIT_TIME
1033    test    RUN   normal     ubuntu02    ubuntu02    *_quick.sh Jul  7 10:27
test@ubuntu02:/opt/DL/caffe-nv$ bhist -l 1034

Job <1034>, User <test>, Project <default>, Command <sudo LD_LIBRARY_PATH=/opt/
                     DL/openblas/lib:/opt/DL/nccl/lib:/opt/DL/caffe-nv/lib time
                      ./examples/cifar10/train_quick.sh>
Fri Jul  7 10:28:40: Submitted from host <ubuntu02>, to Queue <normal>, CWD </o
                     pt/DL/caffe-nv>;
Fri Jul  7 10:28:41: Dispatched 1 Task(s) on Host(s) <ubuntu02>, Allocated 1 Sl
                     ot(s) on Host(s) <ubuntu02>, Effective RES_REQ <select[typ
                     e == local] order[r15s:pg] >;
Fri Jul  7 10:28:41: Starting (Pid 14363);
Fri Jul  7 10:28:41: Running with execution home </home/test>, Execution CWD </
                     opt/DL/caffe-nv>, Execution Pid <14363>;
Fri Jul  7 10:28:42: Exited with exit code 134. The CPU time used is 0.2 second
                     s;
Fri Jul  7 10:28:42: Completed <exit>;

Summary of time in seconds spent in various states by  Fri Jul  7 10:28:42
  PEND     PSUSP    RUN      USUSP    SSUSP    UNKWN    TOTAL
  1        0        1        0        0        0        2


이를 해결하기 위해서는 job을 submit할 때, 이것이 GPU 자원을 필요로 하는 것이고, 그에 따라 배정되어야 한다는 것을 LSF에게 알려야 합니다.  그것이 바로 select와 rusage 옵션입니다.

아래의 예에서, select[ngpus>0]는 gpu가 1개 이상인 서버에 job을 assign하라는 뜻이고, rusage[ngpus_excl_p=1]는 이 job이 EXCLUSIVE_PROCESS 모드의 GPU를 1개 사용한다는 뜻입니다.

test@ubuntu02:/opt/DL/caffe-nv$ bsub -R "select[ngpus>0] rusage[ngpus_excl_p=1]" sudo LD_LIBRARY_PATH=/opt/DL/openblas/lib:/opt/DL/nccl/lib:/opt/DL/caffe-nv/lib time ./examples/cifar10/train_quick.sh
Job <1153> is submitted to default queue <normal>.

이렇게 옵션을 주면, bhost 명령으로 볼 때  ngpus_excl_p 항목의 값이 4에서 3으로 줄고, 대신 그 밑의 Reserved 항목 값이 1로 바뀐 것을 보실 수 있습니다.

test@ubuntu02:/opt/DL/caffe-nv$ bhosts -l
HOST  ubuntu02
STATUS           CPUF  JL/U    MAX  NJOBS    RUN  SSUSP  USUSP    RSV DISPATCH_WINDOW
ok             250.00     -     16      1      1      0      0      0      -

 CURRENT LOAD USED FOR SCHEDULING:
                r15s   r1m  r15m    ut    pg    io   ls    it   tmp   swp   mem  slots  ngpus
 Total           0.0   0.0   0.0    0%   0.0   123    1     0  781G 37.6G 124.1G     15    4.0
 Reserved        0.0   0.0   0.0    0%   0.0     0    0     0    0M    0M    0M      -    0.0

               ngpus_shared ngpus_excl_t ngpuprohibited ngpus_excl_p
 Total                  0.0          0.0            0.0          3.0
 Reserved               0.0          0.0             -           1.0


이 상태에서 두번째 job을 던지면 어떻게 될까요 ?

test@ubuntu02:/opt/DL/caffe-nv$ bsub -R "select[ngpus>0] rusage[ngpus_excl_p=1]" sudo LD_LIBRARY_PATH=/opt/DL/openblas/lib:/opt/DL/nccl/lib:/opt/DL/caffe-nv/lib time ./examples/cifar10/train_quick.sh
Job <1154> is submitted to default queue <normal>.

test@ubuntu02:/opt/DL/caffe-nv$ bhosts -l
HOST  ubuntu02
STATUS           CPUF  JL/U    MAX  NJOBS    RUN  SSUSP  USUSP    RSV DISPATCH_WINDOW
ok             250.00     -     16      1      1      0      0      0      -

 CURRENT LOAD USED FOR SCHEDULING:
                r15s   r1m  r15m    ut    pg    io   ls    it   tmp   swp   mem  slots  ngpus
 Total           0.0   0.0   0.0    1%   0.0    28    1     0  781G 37.6G 123.7G     15    4.0
 Reserved        0.0   0.0   0.0    0%   0.0     0    0     0    0M    0M    0M      -    0.0

               ngpus_shared ngpus_excl_t ngpuprohibited ngpus_excl_p
 Total                  0.0          0.0            0.0          2.0
 Reserved               0.0          0.0             -           2.0

보시다시피 ngpus_excl_p 개수가 2개로 줄고, Reserved가 2로 늘어난 것을 보실 수 있습니다.  즉, 이제 LSF가 caffe job을 default로 던지는 것이 아니라, 이미 점거된 GPU는 환경에서 빼고 던지는 것입니다 !

bjobs 명령으로 첫번째 job id인 1153을 살펴 보겠습니다.  저 아래에 ubuntu02:gpus=2 라고 gpu 2번이 할당된 것을 보실 수 있습니다.

test@ubuntu02:/opt/DL/caffe-nv$ bjobs -l 1153

Job <1153>, User <test>, Project <default>, Status <RUN>, Queue <normal>, Comma
                     nd <sudo LD_LIBRARY_PATH=/opt/DL/openblas/lib:/opt/DL/nccl
                     /lib:/opt/DL/caffe-nv/lib time ./examples/cifar10/train_qu
                     ick.sh>, Share group charged </test>
Fri Jul  7 17:34:02: Submitted from host <ubuntu02>, CWD </opt/DL/caffe-nv>, Re
                     quested Resources <select[ngpus>0] rusage[ngpus_excl_p=1]>
                     ;
Fri Jul  7 17:34:02: Started 1 Task(s) on Host(s) <ubuntu02>, Allocated 1 Slot(
                     s) on Host(s) <ubuntu02>, Execution Home </home/test>, Exe
                     cution CWD </opt/DL/caffe-nv>;
Fri Jul  7 17:35:01: Resource usage collected.
                     The CPU time used is 117 seconds.
                     MEM: 277 Mbytes;  SWAP: 0 Mbytes;  NTHREAD: 78
                     PGID: 12981;  PIDs: 12981 12985 12987 12988 12989 12990


 MEMORY USAGE:
 MAX MEM: 277 Mbytes;  AVG MEM: 274 Mbytes

 SCHEDULING PARAMETERS:
           r15s   r1m  r15m   ut      pg    io   ls    it    tmp    swp    mem
 loadSched   -     -     -     -       -     -    -     -     -      -      -
 loadStop    -     -     -     -       -     -    -     -     -      -      -

            ngpus ngpus_shared ngpus_excl_t ngpuprohibited ngpus_excl_p
 loadSched     -            -            -              -            -
 loadStop      -            -            -              -            -

 EXTERNAL MESSAGES:
 MSG_ID FROM       POST_TIME      MESSAGE                             ATTACHMENT
 0      test       Jul  7 17:34   ubuntu02:gpus=2;                        N

 RESOURCE REQUIREMENT DETAILS:
 Combined: select[(ngpus>0) && (type == local)] order[r15s:pg] rusage[ngpus_exc
                     l_p=1.00]
 Effective: select[(ngpus>0) && (type == local)] order[r15s:pg] rusage[ngpus_ex
                     cl_p=1.00]

bjobs 명령으로 두번째 job id인 1154를 살펴 보겠습니다.  저 아래에 ubuntu02:gpus=3 이라고 gpu 3번이 할당된 것을 보실 수 있습니다.

test@ubuntu02:/opt/DL/caffe-nv$ bjobs -l 1154

Job <1154>, User <test>, Project <default>, Status <EXIT>, Queue <normal>, Comm
                     and <sudo LD_LIBRARY_PATH=/opt/DL/openblas/lib:/opt/DL/ncc
                     l/lib:/opt/DL/caffe-nv/lib time ./examples/cifar10/train_q
                     uick.sh>, Share group charged </test>
Fri Jul  7 17:34:04: Submitted from host <ubuntu02>, CWD </opt/DL/caffe-nv>, Re
                     quested Resources <select[ngpus>0] rusage[ngpus_excl_p=1]>
                     ;
Fri Jul  7 17:34:04: Started 1 Task(s) on Host(s) <ubuntu02>, Allocated 1 Slot(
                     s) on Host(s) <ubuntu02>, Execution Home </home/test>, Exe
                     cution CWD </opt/DL/caffe-nv>;
...

 EXTERNAL MESSAGES:
 MSG_ID FROM       POST_TIME      MESSAGE                             ATTACHMENT
 0      test       Jul  7 17:34   ubuntu02:gpus=3;                        N

 RESOURCE REQUIREMENT DETAILS:
 Combined: select[(ngpus>0) && (type == local)] order[r15s:pg] rusage[ngpus_exc
                     l_p=1.00]
 Effective: select[(ngpus>0) && (type == local)] order[r15s:pg] rusage[ngpus_ex
                     cl_p=1.00]


이번에는 GPU를 2개씩 사용하도록 caffe 명령에 -gpu 옵션을 붙여 보겠습니다.   아래처럼 -gpu 0,1 이라고 지정해놓으면 gpu0과 gpu1을 지정해서 사용하게 됩니다.

test@ubuntu02:/opt/DL/caffe-nv$ cat ./examples/cifar10/train_01.sh
#!/usr/bin/env sh

# Check if CAFFE_BIN is unset
if [ -z "$CAFFE_BIN" ]; then
#  TOOLS=./build/tools
  TOOLS=./bin
else
  TOOLS=$CAFFE_BIN
fi

$TOOLS/caffe train -gpu 0,1 \
  --solver=examples/cifar10/cifar10_quick_solver.prototxt

# reduce learning rate by factor of 10 after 8 epochs
$TOOLS/caffe train -gpu 0,1 \
  --solver=examples/cifar10/cifar10_quick_solver_lr1.prototxt \
  --snapshot=examples/cifar10/cifar10_quick_iter_4000.solverstate.h5


이 script를 연달아 2번 돌려보겠습니다.  0번 1번 GPU라고 지정했으니, 같은 GPU 2개를 두개의 job이 서로 점유하려고 할까요 ?

test@ubuntu02:/opt/DL/caffe-nv$ bsub -R "select[ngpus>0] rusage[ngpus_excl_p=2]" sudo LD_LIBRARY_PATH=/opt/DL/openblas/lib:/opt/DL/nccl/lib:/opt/DL/caffe-nv/lib time ./examples/cifar10/train_01.sh
Job <1155> is submitted to default queue <normal>.

test@ubuntu02:/opt/DL/caffe-nv$ bsub -R "select[ngpus>0] rusage[ngpus_excl_p=2]" sudo LD_LIBRARY_PATH=/opt/DL/openblas/lib:/opt/DL/nccl/lib:/opt/DL/caffe-nv/lib time ./examples/cifar10/train_01.sh
Job <1156> is submitted to default queue <normal>.

아래처럼 bhosts에서 ngpus_excl_p가 0으로, Reserved가 4로 변한 것을 보실 수 있습니다.  즉, gpu0, gpu1이 이미 첫번째 job에 의해 점유된 것을 보고, LSF가 두번째 job은 gpu2, gpu3에 할당한 것입니다.

test@ubuntu02:/opt/DL/caffe-nv$ bhosts -l
HOST  ubuntu02
STATUS           CPUF  JL/U    MAX  NJOBS    RUN  SSUSP  USUSP    RSV DISPATCH_WINDOW
ok             250.00     -     16      1      1      0      0      0      -

 CURRENT LOAD USED FOR SCHEDULING:
                r15s   r1m  r15m    ut    pg    io   ls    it   tmp   swp   mem  slots  ngpus
 Total           0.0   0.0   0.0    0%   0.0    11    1     0  781G 37.6G 124.1G     15    4.0
 Reserved        0.0   0.0   0.0    0%   0.0     0    0     0    0M    0M    0M      -    0.0

               ngpus_shared ngpus_excl_t ngpuprohibited ngpus_excl_p
 Total                  0.0          0.0            0.0          0.0
 Reserved               0.0          0.0             -           4.0

bjobs 명령으로 보면 좀더 확실히 확인하실 수 있습니다.

test@ubuntu02:/opt/DL/caffe-nv$ bjobs -l 1155 | grep gpu
                     quested Resources <select[ngpus>0] rusage[ngpus_excl_p=2]>
            ngpus ngpus_shared ngpus_excl_t ngpuprohibited ngpus_excl_p
 0      test       Jul  7 17:39   ubuntu02:gpus=2,3;                      N
 Combined: select[(ngpus>0) && (type == local)] order[r15s:pg] rusage[ngpus_exc
 Effective: select[(ngpus>0) && (type == local)] order[r15s:pg] rusage[ngpus_ex

test@ubuntu02:/opt/DL/caffe-nv$ bjobs -l 1156 | grep gpu
                     quested Resources <select[ngpus>0] rusage[ngpus_excl_p=2]>
            ngpus ngpus_shared ngpus_excl_t ngpuprohibited ngpus_excl_p
 0      test       Jul  7 17:39   ubuntu02:gpus=0,1;                      N
 Combined: select[(ngpus>0) && (type == local)] order[r15s:pg] rusage[ngpus_exc
 Effective: select[(ngpus>0) && (type == local)] order[r15s:pg] rusage[ngpus_ex

이번에는 이렇게 GPU 2개를 사용하는 job을 연달아 3번 submit하면 어떻게 될까요 ?  Error가 날까요 ?

test@ubuntu02:/opt/DL/caffe-nv$ bsub -R "select[ngpus>0] rusage[ngpus_excl_p=2]" sudo LD_LIBRARY_PATH=/opt/DL/openblas/lib:/opt/DL/nccl/lib:/opt/DL/caffe-nv/lib time ./examples/cifar10/train_01.sh
Job <1269> is submitted to default queue <normal>.

test@ubuntu02:/opt/DL/caffe-nv$ bsub -R "select[ngpus>0] rusage[ngpus_excl_p=2]" sudo LD_LIBRARY_PATH=/opt/DL/openblas/lib:/opt/DL/nccl/lib:/opt/DL/caffe-nv/lib time ./examples/cifar10/train_01.sh
Job <1270> is submitted to default queue <normal>.

test@ubuntu02:/opt/DL/caffe-nv$ bsub -R "select[ngpus>0] rusage[ngpus_excl_p=2]" sudo LD_LIBRARY_PATH=/opt/DL/openblas/lib:/opt/DL/nccl/lib:/opt/DL/caffe-nv/lib time ./examples/cifar10/train_01.sh
Job <1271> is submitted to default queue <normal>.

아닙니다.  첫번째와 두번째 job들이 GPU 자원을 2개씩 다 사용하므로 세번째 job은 당장 가용한 GPU 자원이 없게 되는데, 이 경우 그냥 PENDING 상태에서 다른 job들이 다 종료되어 GPU 자원이 풀려나기를 기다리게 됩니다.

test@ubuntu02:/opt/DL/caffe-nv$ bjobs
JOBID   USER    STAT  QUEUE      FROM_HOST   EXEC_HOST   JOB_NAME   SUBMIT_TIME
1269    test    RUN   normal     ubuntu02    ubuntu02    *ain_01.sh Jul  7 18:02
1270    test    RUN   normal     ubuntu02    ubuntu02    *ain_01.sh Jul  7 18:02
1271    test    PEND  normal     ubuntu02                *ain_01.sh Jul  7 18:02

모든 job들이 다 완료된 이후, bhist 명령으로 job들의 history를 보겠습니다.  두번째 수행된 job 1270의 경우 16초만 PENDING 상태에 있다가 곧장 dispatch되어 GPU를 사용하기 시작했습니다.

test@ubuntu02:/opt/DL/caffe-nv$ bhist -l 1270

Job <1270>, User <test>, Project <default>, Command <sudo LD_LIBRARY_PATH=/opt/
                     DL/openblas/lib:/opt/DL/nccl/lib:/opt/DL/caffe-nv/lib time
                      ./examples/cifar10/train_01.sh>
Fri Jul  7 18:02:11: Submitted from host <ubuntu02>, to Queue <normal>, CWD </o
                     pt/DL/caffe-nv>, Requested Resources <select[ngpus>0] rusa
                     ge[ngpus_excl_p=2]>;
Fri Jul  7 18:02:27: Dispatched 1 Task(s) on Host(s) <ubuntu02>, Allocated 1 Sl
                     ot(s) on Host(s) <ubuntu02>, Effective RES_REQ <select[(ng
                     pus>0) && (type == local)] order[r15s:pg] rusage[ngpus_exc
                     l_p=2.00] >;
Fri Jul  7 18:02:27: Starting (Pid 5830);
Fri Jul  7 18:02:27: Running with execution home </home/test>, Execution CWD </
                     opt/DL/caffe-nv>, Execution Pid <5830>;
Fri Jul  7 18:02:28: External Message "ubuntu02:gpus=0,1;" was posted from "tes
                     t" to message box 0;
Fri Jul  7 18:03:44: Done successfully. The CPU time used is 222.3 seconds;
Fri Jul  7 18:03:45: Post job process done successfully;

MEMORY USAGE:
MAX MEM: 517 Mbytes;  AVG MEM: 467 Mbytes

Summary of time in seconds spent in various states by  Fri Jul  7 18:03:45
  PEND     PSUSP    RUN      USUSP    SSUSP    UNKWN    TOTAL
  16       0        77       0        0        0        93

그러나 세번째로 수행된 job 1271의 경우, 첫번째 job인 job 1269가 끝날 때까지 약 86초 동안 PENDING 상태에 있다가 dispatch된 것을 보실 수 있습니다.

test@ubuntu02:/opt/DL/caffe-nv$ bhist -l 1271

Job <1271>, User <test>, Project <default>, Command <sudo LD_LIBRARY_PATH=/opt/
                     DL/openblas/lib:/opt/DL/nccl/lib:/opt/DL/caffe-nv/lib time
                      ./examples/cifar10/train_01.sh>
Fri Jul  7 18:02:15: Submitted from host <ubuntu02>, to Queue <normal>, CWD </o
                     pt/DL/caffe-nv>, Requested Resources <select[ngpus>0] rusa
                     ge[ngpus_excl_p=2]>;
Fri Jul  7 18:03:41: Dispatched 1 Task(s) on Host(s) <ubuntu02>, Allocated 1 Sl
                     ot(s) on Host(s) <ubuntu02>, Effective RES_REQ <select[(ng
                     pus>0) && (type == local)] order[r15s:pg] rusage[ngpus_exc
                     l_p=2.00] >;
Fri Jul  7 18:03:42: Starting (Pid 6729);
Fri Jul  7 18:03:42: Running with execution home </home/test>, Execution CWD </
                     opt/DL/caffe-nv>, Execution Pid <6729>;
Fri Jul  7 18:04:52: Done successfully. The CPU time used is 207.9 seconds;
Fri Jul  7 18:04:53: Post job process done successfully;

MEMORY USAGE:
MAX MEM: 530 Mbytes;  AVG MEM: 463 Mbytes

Summary of time in seconds spent in various states by  Fri Jul  7 18:04:53
  PEND     PSUSP    RUN      USUSP    SSUSP    UNKWN    TOTAL
  86       0        71       0        0        0        157


이제 기본적인 GPU를 이용한 Deep Learning용 LSF 환경이 준비된 것입니다.