2017년 10월 27일 금요일

caffe DDL을 이용한 Alexnet training

지난 9월 포스팅(http://hwengineer.blogspot.kr/2017/09/ibm-powerai-40-caffe-distributed-deep.html)에서 PowerAI에 포함된 DDL(Distributed Deep Learning), 즉 MPI를 이용한 분산처리 기능에 대해 간단히 설명드린 바 있습니다.  이번에는 그것으로 ILSVRC2012의 128만장 image dataset을 caffe alexnet으로 training 해보겠습니다.

제가 잠깐 빌릴 수 있는 Minsky 서버가 딱 1대 뿐이라, 원래 여러대의 Minsky 서버를 묶어서 하나의 model을 train시킬 수 있지만 여기서는 1대의 서버에서 caffe DDL을 수행해보겠습니다.  잠깐, 1대라고요 ?  1대에서 MPI 분산처리가 의미가 있나요 ?

예, 없지는 않습니다.  Multi-GPU를 이용한 training을 할 때 일반 caffe와 caffe DDL의 차이는 multi-thread냐, multi-process냐의 차이입니다.  좀더 쉽게 말해, 일반 caffe에서는 하나의 caffe process가 P2P를 통해 여러개의 GPU를 사용합니다.  그에 비해, caffe DDL에서는 GPU당 1개씩 별도의 caffe process가 떠서, 서로간에 MPI를 이용한 통신을 하며 여러개의 GPU를 사용합니다.

이를 그림으로 표현하면 아래와 같습니다.



실제로, 일반 caffe를 사용할 경우 nvidia-smi로 관찰해보면 다음과 같이 caffe의 PID가 모두 같지만, caffe DDL에서는 각 GPU를 사용하는 caffe PID가 서로 다릅니다.

일반 caffe :

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID  Type  Process name                               Usage      |
|=============================================================================|
|    0     30681    C   caffe                                        15497MiB |
|    1     30681    C   caffe                                        14589MiB |
|    2     30681    C   caffe                                        14589MiB |
|    3     30681    C   caffe                                        14589MiB |
+-----------------------------------------------------------------------------+

caffe DDL :

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID  Type  Process name                               Usage      |
|=============================================================================|
|    0     31227    C   caffe                                        15741MiB |
|    1     31228    C   caffe                                        14837MiB |
|    2     31229    C   caffe                                        14837MiB |
|    3     31230    C   caffe                                        14837MiB |
+-----------------------------------------------------------------------------+

자, 대략 차이를 이해하셨으면, alexnet training을 한번은 일반 caffe로, 또 한번은 caffe DDL로 training해보시지요.  물론 모두 같은 Minsky 서버, 즉 4-GPU 시스템 1대를 써서 테스트한 것입니다.  각각의 성능 측정은 128만장을 2-epochs, 즉 2회 반복 training할 때 걸린 시간으로 측정하겠습니다.

일반 caffe :

test@ubuntu:/nvme$ caffe train --solver=models/bvlc_alexnet/solver.prototxt -gpu all

caffe DDL :

test@ubuntu:/nvme$ mpirun -x PATH -x LD_LIBRARY_PATH -n 4 -rf 4x1x1.rf caffe train --solver=models/bvlc_alexnet/solver.prototxt -gpu 0 -ddl "-mode n:4x1x1 -dev_sync 1"

지난번에 잠깐 설명드린 것을 반복하자면 이렇습니다.

- mpirun은 여러대의 서버 노드에 동일한 명령을 동일한 환경변수 (-x 옵션)을 써서 수행해주는 병렬환경 명령어입니다.
- 4x1x1.rf라는 이름의 파일은 rank file입니다.  이 속에 병렬 서버 환경의 toplogy가 들어있습니다.
- -n 4라는 것은 MPI client의 총 숫자이며, 쉽게 말해 training에 이용하려는 GPU의 갯수입니다.
- -gpu 0에서, 왜 4개가 아니라 gpu 0이라고 1개로 지정했는지 의아하실 수 있는데, MPI 환경에서는 각각의 GPU가 하나의 learner가 됩니다.  따라서 실제 물리적 서버 1대에 GPU가 몇 장 장착되어있든 상관없이 모두 -gpu 0, 즉 GPU는 1개로 지정한 것입니다.
- "-mode b:4x1x1"에서 b라는 것은 가능하면 enhanced NCCL을 이용하라는 뜻입니다.  4x1x1은 4장의 GPU를 가진 서버 1대가 하나의 rack에 들어있다는 뜻입니다.
- dev_sync에서 0은 GPU간 sync를 하지 말라는 것이고, 1은 통신 시작할 때 sync하라는 뜻, 2는 시작할 때와 끝낼 때 각각 sync하라는 뜻입니다.

여기서 사용된 rank file 4x1x1.rf 속의 내용은 아래와 같습니다.

rank 0=minksy           slot=0:0-3
rank 1=minksy           slot=0:4-7
rank 2=minksy           slot=1:0-3
rank 3=minksy           slot=1:4-7

128만장 x 2-epochs를 처리하기 위해서는, solver.prototxt와 train_val.prototxt 속에 표시된 batch_size와 max_iter의 곱이 128만장 x 2 = 256만장이면 됩니다.  batch_size를 조절함에 따라 training 속도가 꽤 달라지는데, 여기서는 256부터 512, 768 순으로 늘려가며 테스트해보겠습니다.



위 표에서 보시다시피, batch_size가 작을 때는 일반 caffe의 성능이 더 빨랐는데, batch_size가 점점 커지면서 caffe DDL의 성능이 점점 더 빨라져서 결국 역전하게 됩니다.  batch_size와 MPI를 이용한 DDL의 성능과의 상관 관계가 있을 것 같기는 한데, 아직 그 이유는 파악을 못 했습니다.  Lab에 문의해봤는데, batch_size와는 무관할 것이라는 답변을 받긴 했습니다.


여기서 batch_size를 1024보다 더 키우면 어떻게 될까요 ?

...
F1025 17:48:43.059572 30265 syncedmem.cpp:651] Check failed: error == cudaSuccess (2 vs. 0)  out of memoryF1025 17:48:43.071281 30285 syncedmem.cpp:651] Check failed: error == cudaSuccess (2 vs. 0)  out of memory
*** Check failure stack trace: ***
    @     0x3fffb645ce0c  google::LogMessage::Fail()
    @     0x3fffb69649cc  caffe::Solver<>::Step()
    @     0x3fffb645f284  google::LogMessage::SendToLog()
...

일반 caffe든 caffe DDL이든 batch_size가 1100만 되어도 이렇게 out-of-memory (OOM) error를 내며 죽어버립니다.  그러니 아쉽게도 더 큰 batch_size에서는 테스트가 안되는 것이지요.

batch_size가 너무 커서 OOM error가 난다면 그걸 또 피해가는 방법이 있습니다.  역시 caffe-ibm에 포함된 LMS(large model support)입니다.   아래와 같이 -lms 옵션을 주면 caffe나 caffe DDL이나 모두 batch_size=1200 정도까지는 무난히 돌릴 수 있습니다.  -lms 800000이라는 것은 800000KB 이상의 memory chunk는 GPU 말고 CPU에 남겨두라는 뜻입니다.   (http://hwengineer.blogspot.kr/2017/09/inference-gpu-sizing-ibm-caffe-large.html 참조)


일반 caffe with LMS :

test@ubuntu:/nvme$ caffe train -lms 800000 --solver=models/bvlc_alexnet/solver.prototxt -gpu all

caffe DDL with LMS :

test@ubuntu:/nvme$ mpirun -x PATH -x LD_LIBRARY_PATH -n 4 -rf 4x1x1.rf caffe train -lms 800000 --solver=models/bvlc_alexnet/solver.prototxt -gpu 0 -ddl "-mode n:4x1x1 -dev_sync 1"


그 결과는 아래와 같습니다.   확실히 batch_size가 커질 수록 일반 caffe보다 caffe DDL의 성능이 더 잘 나옵니다.




궁금해하실 분들을 위해서, caffe DDL을 수행할 경우 나오는 메시지의 앞부분과 뒷부분 일부를 아래에 붙여놓습니다.

--------------------------------------------------------------------------
[[31653,1],2]: A high-performance Open MPI point-to-point messaging module
was unable to find any relevant network interfaces:

Module: OpenFabrics (openib)
  Host: minsky

Another transport will be used instead, although this may result in
lower performance.
--------------------------------------------------------------------------
ubuntu: n0(0) n1(0) n2(0) n3(0) 
I1025 18:59:45.681555 31227 caffe.cpp:151] [MPI:0   ] spreading GPUs per MPI rank
I1025 18:59:45.681725 31227 caffe.cpp:153] [MPI:0   ]    use gpu[0]
I1025 18:59:45.681541 31228 caffe.cpp:151] [MPI:1   ] spreading GPUs per MPI rank
I1025 18:59:45.681725 31228 caffe.cpp:153] [MPI:1   ]    use gpu[1]
I1025 18:59:45.681541 31229 caffe.cpp:151] [MPI:2   ] spreading GPUs per MPI rank
I1025 18:59:45.681726 31229 caffe.cpp:153] [MPI:2   ]    use gpu[2]
I1025 18:59:45.681735 31229 caffe.cpp:283] Using GPUs 2
I1025 18:59:45.681541 31230 caffe.cpp:151] [MPI:3   ] spreading GPUs per MPI rank
I1025 18:59:45.681726 31230 caffe.cpp:153] [MPI:3   ]    use gpu[3]
I1025 18:59:45.681735 31230 caffe.cpp:283] Using GPUs 3
I1025 18:59:45.681735 31227 caffe.cpp:283] Using GPUs 0
I1025 18:59:45.681733 31228 caffe.cpp:283] Using GPUs 1
I1025 18:59:45.683846 31228 caffe.cpp:288] GPU 1: Tesla P100-SXM2-16GB
I1025 18:59:45.683897 31227 caffe.cpp:288] GPU 0: Tesla P100-SXM2-16GB
I1025 18:59:45.683955 31230 caffe.cpp:288] GPU 3: Tesla P100-SXM2-16GB
I1025 18:59:45.684010 31229 caffe.cpp:288] GPU 2: Tesla P100-SXM2-16GB
I1025 18:59:46.056959 31227 caffe.cpp:302] [MPI:0   ]  name = minsky root = 1
I1025 18:59:46.067212 31228 caffe.cpp:302] [MPI:1   ]  name = minsky root = 1
I1025 18:59:46.070734 31230 caffe.cpp:302] [MPI:3   ]  name = minsky root = 1
I1025 18:59:46.071211 31229 caffe.cpp:302] [MPI:2   ]  name = minsky root = 1
I1025 18:59:46.073958 31227 solver.cpp:44] Initializing solver from parameters: 
test_iter: 1000
test_interval: 1000
base_lr: 0.01
display: 500
max_iter: 2500
lr_policy: "step"
gamma: 0.1

...중략...

I1025 19:24:53.536928 31227 solver.cpp:414]     Test net output #0: accuracy = 0.20032
I1025 19:24:53.536965 31227 solver.cpp:414]     Test net output #1: loss = 4.09802 (* 1 = 4.09802 loss)
I1025 19:24:54.180562 31227 solver.cpp:223] Iteration 2000 (1.27922 iter/s, 390.864s/500 iters), loss = 4.18248
I1025 19:24:54.180598 31227 solver.cpp:242]     Train net output #0: loss = 4.18248 (* 1 = 4.18248 loss)
I1025 19:24:54.180613 31227 sgd_solver.cpp:121] Iteration 2000, lr = 0.01
I1025 19:26:57.349701 31256 data_layer.cpp:86] Restarting data prefetching from start.
I1025 19:30:28.547333 31256 data_layer.cpp:86] Restarting data prefetching from start.
I1025 19:30:29.081480 31227 solver.cpp:466] Snapshotting to binary proto file models/bvlc_alexnet/caffe_alexnet_train_iter_2500.caffemodel
I1025 19:30:29.283386 31228 solver.cpp:315] Iteration 2500, loss = 3.91634
I1025 19:30:29.283444 31228 solver.cpp:320] Optimization Done.
I1025 19:30:29.283535 31230 solver.cpp:315] Iteration 2500, loss = 3.9612
I1025 19:30:29.283582 31230 solver.cpp:320] Optimization Done.
I1025 19:30:29.285512 31228 caffe.cpp:357] Optimization Done.
I1025 19:30:29.285521 31228 caffe.cpp:359] [MPI:1   ] MPI_Finalize
I1025 19:30:29.285697 31230 caffe.cpp:357] Optimization Done.
I1025 19:30:29.285706 31230 caffe.cpp:359] [MPI:3   ] MPI_Finalize
I1025 19:30:29.286912 31229 solver.cpp:315] Iteration 2500, loss = 3.90313
I1025 19:30:29.286952 31229 solver.cpp:320] Optimization Done.
I1025 19:30:29.290489 31229 caffe.cpp:357] Optimization Done.
I1025 19:30:29.290498 31229 caffe.cpp:359] [MPI:2   ] MPI_Finalize
I1025 19:30:29.973234 31227 sgd_solver.cpp:356] Snapshotting solver state to binary proto file models/bvlc_alexnet/caffe_alexnet_train_iter_2500.solverstate
I1025 19:30:30.727695 31227 solver.cpp:315] Iteration 2500, loss = 3.89638
I1025 19:30:30.727744 31227 solver.cpp:320] Optimization Done.
I1025 19:30:30.729465 31227 caffe.cpp:357] Optimization Done.
I1025 19:30:30.729475 31227 caffe.cpp:359] [MPI:0   ] MPI_Finalize



댓글 없음:

댓글 쓰기