Horovod 사용기
- Pytorch기반으로 OCR 학습기를 개발하던 중, multi-gpu학습이 안되는 현상이 발생했다.
- Pytorch의 경우 data parallel을 이용하여 손쉽게 multi-gpu로 학습이 가능하다.
- Data parallel로 모델을 감싸서 실행을 하면 아래와 같이 동작한다.
- batch크기가 8이고 gpu가 4개인 경우, 그리고 GT의 shape가 예를 들어{32}인 1-D tensor일 경우 input과 gt를 정확히 4등분을 해서({8}, {8}, {8}, {8}) 각 gpu로 복사한다.
- 그런데 OCR의 경우 정답인 word의 길이가 가변적이므로, 어떤 input은 정답의 길이가 4이고 어떤 input은 7이다. 그래서 이를 정확히 n등분 할 경우 input과 정답이 맞지 않는 상황이 발생한다.
- 이를 해결하기 위해선 분산 환경에서 학습하는 기술이 필요했고, 검색하다보니 horovod가 꽤 오랫동안 개발이 되었고 안정적으로 보여서 한번 사용해 보았다.
- 일단 개념적으로 돌아가는 구조는 아래와 같다.
- Master node와 slave node가 존재하고, master node에서 학습에 필요한 데이터(optimizer, gradients, scheduler 등)를 주고받으면서 sync를 맞춘다.
- 여기서 각 node라는 표현은 horovod에서는 rank라는 표현으로 쓰인다.
- Pytorch에서 사용하는 방법을 간략하게 말로 설명하면 아래와 같다. (티스토리 코드 삽입이 좀 그렇네..)
- torch.utils.data.distributed.DistributedSampler로 sampler를 정의하는데, 매개변수로 dataset과 총 rank가 몇 개이고, 현재 rank가 몇 번째 rank인지를 넣어주면 된다.
- 그리고 torch.utils.data.DataLoader부분에서 동일하게 사용하면 되는데, num_workers는 0으로 해주는 것이 좋다.
- learning rate의 경우 scaling을 해주는게 일반적이라고 한다. 즉, batch size를 32로하고 4대의 머신에 분산으로 학습을 시키는 경우를 가정하면, 각 머신에는 8개의 sample을 학습시키게 된다. 이 때, learning rate를 1개에 머신에서 batch size를 32로 하는 경우와 유사하게 만들어주기 위해서 learning rate를 분산 머신의 갯수만큼 곱해줌으로서 적절히 scaling을 해야 하는게 일반적이다. 왜냐하면, 분산 학습에서는 average gradient를 이용하여 network를 update시키기 때문이다.
- 그러나 경험적으로는 꼭 scaling을 시켜줄 필요는 없으며, 나는 안시키는 것이 성능이 더 잘 나왔다.
- hvd.broadcast_parameters와 broadcast_optimizer_state 함수를 이용하여 현재 모든 머신에 parameter와 optimizer 정보를 다 뿌려준다.
- 그리고 hvd.DistributedOptimizer를 이용해서 optimizer를 정의한다. 해당 optimizer는 allreduce나 allgather를 이용해서 각 머신의 gradients들의 평균을 구하게 된다. 이 average gradient를 이용해서 weight를 업데이트하고 업데이트된 weight를 다시 모든 머신에 뿌리고, 이런 식으로 동작이 진행된다.
- 코드 레벨에서 고칠 부분은 거의 위에 말로 설명한 부분이 전부다.
- 이제 command line에서 다음과 같이 실행시킬 수 있다.
- horovodrun -np 4 -H localhost:4 python train.py
- horovodrun -np 16 -H server0:4,server1:4,server2:4,server3:4 python train.py
- -np는 총 rank의 갯수를 의미하고 -H는 host를 의미한다. hostname:num_of_rank형태로 선언해주면 된다.
- 한방에 성공했었는데, 일단 한번에 성공하는걸 보니 간단하면서 효과적인 툴이라고 판단했다.
- 아주 가끔씩 segmentation fault를 내면서 죽는데..원인 파악이 엄청 어렵다. (소스 코드를 한땀한땀 읽어가며 분석..)
https://github.com/horovod/horovod
horovod/horovod
Distributed training framework for TensorFlow, Keras, PyTorch, and Apache MXNet. - horovod/horovod
github.com
추가적으로 nvidia-docker 설치 방법 및 host name 설정 방법에 대해서는 아래를 참조
Docker install
sudo apt-get remove docker docker-engine docker.io containerd runc
sudo apt-get update
sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
gnupg-agent \
software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io
Nvidia-docker
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
sudo systemctl restart docker
# Test
docker run --gpus all nvidia/cuda:10.0-base nvidia-smi
Hostname
sudo vi /etc/hosts
#example
192.165.0.1 hostname
위 파일에 host name 추가. (ip address tab hostname)
vi ~/.ssh/config
# example
Host hostname
User aaa
위 파일에 ssh 접속 정보를 아래 example과 같이 기입
그리고 password 없이 접속하기 위해 ssh key를 발급받아서 복사
HostA@ ssh-keygen -t rsa
HostA@ ssh accounts@HostB mkdir -p .ssh
HostA@ cat .ssh/id_rsa.pub | ssh accounts@HostB 'cat >> .ssh/authorized_keys'
ssh hostname 으로 접속되는지 확인
-----------------------------------------------------------------------------
Troubleshooting
docker에서 network option으로 host를 줘서, host와 동일한 network 옵션을 가져왔지만 interface name error가 발생했다.
root@local# docker run --gpus all -it --network=host -v /mnt/share/ssh:/root/.ssh horovod:latest
root@server# docker run --gpus all -it --network=host -v /mnt/share/ssh/:/root/.ssh horovod:latest bash -c "/usr/sbin/sshd -p 12345; sleep infinity"
local과 server에서는 위와 같은 command로 docker container 올림
root# horovodrun -np 2 -H server:1,localhost:1 -p 12345 python pytorch_mnist.py
horovod.run.common.util.network.NoValidAddressesFound:
Horovod was unable to connect to horovod task service #1 on any of the following
addresses: {'lo': [('127.0.0.1', 46468)], 'enp6s0': [('192.168.0.14', 46468)],
'wlp5s0': [('192.168.0.23', 46468)], 'docker0': [('172.17.0.1', 46468)]}.
조금 이상한 것은 multinode로 돌리지 않고, 각 single mode로 돌렸을 때는 모두 정상동작했다. (local->local, local->server)
조금 찾아보니 아래의 이슈 발견
github.com/horovod/horovod/issues/975
Horovod runs when I ran on single node, but on multiple node it hangs · Issue #975 · horovod/horovod
Environment: Framework: (TensorFlow, Keras, PyTorch, MXNet):Pytorch Framework version: 1.0.1.post2 Horovod version: 0.16.1 MPI version: 4.0.0 CUDA version: 10.0 NCCL version: 2.4.2 Python version: ...
github.com
읽어보니 interface name이 달라서 그렇단다.
github.com/horovod/horovod/issues/1724
github.com/horovod/horovod/pull/1808
업데이트 되었단다. 오 좋아좋아
reply에 성공했다는 글도 있다. 굿굿
root# horovodrun -np 2 -H server15:1,localhost:1 -p 12345 --network-interface="enp6s0,enp5s0" --mpi --mpi-args="--oversubscribe" python pytorch_mnist.py
At least one pair of MPI processes are unable to reach each other for
MPI communications. This means that no Open MPI device has indicated
that it can be used to communicate between these processes. This is
an error; Open MPI requires that all MPI processes be able to reach
each other. This error can sometimes be the result of forgetting to
specify the "self" BTL.
Process 1 ([[13990,1],1]) is on host: videotf-Alienware-Aurora-R7
Process 2 ([[13990,1],0]) is on host: hotaekhan-desktop
BTLs attempted: self
Your MPI job is now going to abort; sorry.
--------------------------------------------------------------------------
[1,1]<stderr>:Error in MPI_Isend(139992514207500, 1, 0x7f52ac305f00, 0, -27, 139993052921280) (-12)
[videotf-Alienware-Aurora-R7:00262] *** An error occurred in MPI_Comm_dup
[videotf-Alienware-Aurora-R7:00262] *** reported by process [916848641,1]
[videotf-Alienware-Aurora-R7:00262] *** on communicator MPI_COMM_WORLD
[videotf-Alienware-Aurora-R7:00262] *** MPI_ERR_INTERN: internal error
[videotf-Alienware-Aurora-R7:00262] *** MPI_ERRORS_ARE_FATAL (processes in this communicator will now abort,
[videotf-Alienware-Aurora-R7:00262] *** and potentially your MPI job)
[hotaekhan-desktop:01511] 1 more process has sent help message help-mpi-btl-tcp.txt / invalid if_inexclude
[hotaekhan-desktop:01511] Set MCA parameter "orte_base_help_aggregate" to 0 to see all help / error messages
안된다..에러 찾기도 힘들다.
다시 구글링하고 issue를 뒤져봤다.
github.com/horovod/horovod/issues/403
중간에 mpirun으로 돌려서 됐다는 글이 있다.
root# mpirun -np 2 -H localhost:1,server15:1 --allow-run-as-root -bind-to none -map-by slot -x NCCL_DEBUG=INFO -x LD_LIBRARY_PATH -x PATH -x NCCL_SOCKET_IFNAME=^docker0 -mca pml ob1 -mca btl ^openib -mca btl_tcp_if_exclude lo,docker0 -mca plm_rsh_args "-p 12345" python pytorch_mnist.py
됐다. local과 sever에서 모두 process가 뜨는 것을 확인.
근데 mpirun의 option이 무슨 의미인지는 잘 모르겠다.
일단 쓰고..document 또 살펴봐야징..