레이블이 GPU인 게시물을 표시합니다. 모든 게시물 표시
레이블이 GPU인 게시물을 표시합니다. 모든 게시물 표시

2023년 4월 10일 월요일

NPU와 GPU의 차이점에 대한 문답

 

Q1. NPU와 GPU의 차이점을 알려주세요.


A1. NPU와 GPU는 둘 다 특정 유형의 계산을 위해 설계된 특수 프로세서 유형입니다. NPU는 Neural Processing Unit의 약자이고 GPU는 Graphics Processing Unit의 약자입니다. 두 가지 주요 차이점은 다음과 같습니다.


기능: GPU는 주로 그래픽 처리 및 렌더링용으로 설계된 반면 NPU는 딥 러닝 워크로드를 가속화하기 위해 특별히 설계되었습니다.


아키텍처: GPU에는 일반적으로 다양한 유형의 계산에 사용할 수 있는 더 많은 범용 처리 장치가 있으며 NPU는 딥 러닝 알고리즘의 핵심 작업인 행렬 곱셈에 최적화되어 있습니다.


메모리: GPU는 일반적으로 NPU보다 메모리 용량과 대역폭이 더 큽니다. 이는 많은 양의 데이터를 빠르게 처리해야 하는 특정 유형의 애플리케이션에 중요할 수 있습니다.


전력 효율성: NPU는 딥 러닝 워크로드가 계산 집약적일 수 있고 많은 에너지가 필요할 수 있으므로 GPU보다 전력 효율적으로 설계되었습니다.


소프트웨어 호환성: GPU는 훨씬 더 오래 사용되어 왔으며 더 성숙한 소프트웨어 생태계를 가지고 있는 반면, NPU는 여전히 상대적으로 새롭고 소프트웨어 지원이 제한적일 수 있습니다.


전반적으로 NPU와 GPU의 주요 차이점은 NPU는 딥 러닝 워크로드를 가속화하도록 특별히 설계된 반면 GPU는 그래픽 처리 및 기타 다양한 작업에 적합한 범용 프로세서라는 것입니다.





Q2. GPU에서 범용 처리 장치의 사용 사례에 대한 몇 가지 예를 알려주십시오. CUDA 코어가 범용 처리 장치로도 설명될 수 있다는 것을 몰랐습니다.



A2. 확신하는! GPU는 주로 그래픽 처리 및 렌더링에 사용되는 것으로 알려져 있지만 고성능 컴퓨팅이 필요한 다양한 기타 응용 프로그램에도 사용됩니다. 다음은 몇 가지 예입니다.


기계 학습: GPU는 일반적으로 기계 학습 모델의 학습 및 추론을 가속화하는 데 사용됩니다. 대량의 데이터를 처리하고 기존 CPU보다 훨씬 빠르게 신경망에 필요한 행렬 곱셈 및 기타 작업을 수행할 수 있습니다.


과학적 시뮬레이션: GPU는 물리학, 화학, 생물학과 같은 과학 분야에서 시뮬레이션을 가속화하는 데 사용됩니다. 복잡한 물리적 시스템을 시뮬레이션하고 CPU보다 훨씬 효율적으로 대규모 계산을 수행하는 데 사용할 수 있습니다.


암호화폐 채굴: GPU는 채굴 알고리즘에 필요한 복잡한 계산을 수행하여 암호화폐를 채굴하는 데 사용됩니다. Ethereum, Bitcoin 및 Litecoin을 포함한 다양한 암호 화폐를 채굴하는 데 사용할 수 있습니다.


비디오 인코딩: GPU는 매우 계산 집약적인 비디오 인코딩 및 디코딩을 가속화하는 데 사용됩니다. 많은 양의 비디오 데이터를 처리하고 CPU보다 훨씬 빠르게 처리할 수 있습니다.


게임: 물론 GPU는 게임에도 사용됩니다! 고품질 그래픽을 렌더링하고 최신 비디오 게임에서 원활한 게임 플레이를 가능하게 하는 일을 담당합니다.


두 번째 질문과 관련하여 CUDA 코어는 실제로 그래픽 처리를 넘어 광범위한 작업에 사용할 수 있는 범용 처리 장치입니다. 병렬 처리 작업을 가속화하도록 특별히 설계되었으며 다양한 계산을 수행하도록 프로그래밍할 수 있습니다. 실제로 CUDA 프로그래밍 언어 및 툴킷은 기계 학습, 과학 시뮬레이션 및 비디오 처리와 같은 작업에 널리 사용됩니다.



Q3. NPU의 제한된 소프트웨어 생태계에 대해 자세히 알려주십시오. 그렇다면 tensorflow 또는 pytorch와 같은 잘 알려진 프레임워크가 NPU를 지원하지 않는 경우 NPU는 기계 학습에 어떻게 사용됩니까?



A3. NPU는 여전히 상대적으로 새로운 기술이므로 NPU의 소프트웨어 생태계는 GPU만큼 성숙하지 않습니다. TensorFlow, PyTorch 및 MXNet과 같은 인기 있는 기계 학습 프레임워크는 기본적으로 NPU를 지원하지 않지만 NPU를 기계 학습에 사용할 수 있는 몇 가지 방법이 있습니다.


공급업체별 라이브러리: Huawei 및 Cambricon과 같은 일부 NPU 공급업체는 해당 NPU에 최적화된 자체 기계 학습 라이브러리를 개발했습니다. 이러한 라이브러리는 공급업체의 NPU에서 실행할 수 있는 기계 학습 모델을 작성하는 데 사용할 수 있습니다.


컴파일러 기반 최적화: 컴파일러 기반 최적화 도구를 사용하여 머신 러닝 모델을 자동으로 최적화하여 NPU에서 실행할 수 있습니다. 이러한 도구는 모델을 분석하고 NPU의 특정 아키텍처에 최적화된 코드를 생성할 수 있습니다.


프레임워크 확장: TensorFlow 및 PyTorch와 같은 일부 기계 학습 프레임워크에는 NPU에서 실행할 수 있는 확장이 있습니다. 이러한 확장은 이러한 프레임워크의 친숙한 API를 사용하여 기계 학습 모델을 작성하는 동시에 NPU 가속의 이점을 계속 활용할 수 있는 방법을 제공합니다.


NPU 지원 프레임워크로 모델 포팅: 또 다른 옵션은 기계 학습 모델을 NPU를 지원하는 프레임워크로 포팅하는 것입니다. 예를 들어 TensorFlow로 작성된 모델은 Huawei에서 개발한 MindSpore와 같은 NPU 지원 프레임워크로 내보낼 수 있습니다.


NPU를 위한 제한된 소프트웨어 에코시스템으로 인해 머신 러닝에 NPU를 사용하는 것이 어려울 수 있지만 NPU를 머신 러닝 워크플로에 통합할 수 있는 몇 가지 방법이 여전히 있습니다. NPU의 사용이 보다 널리 보급됨에 따라 더 많은 소프트웨어 지원이 제공될 가능성이 높습니다.




Q4. 일반적으로 NPU는 서버에 어떻게 연결됩니까? PCIe 버스 또는 특수 커넥터 기술을 통해 연결됩니까?


A4. 일반적으로 NPU는 고속 직렬 컴퓨터 확장 버스 표준인 PCIe(Peripheral Component Interconnect Express) 인터페이스를 사용하여 서버에 연결됩니다. NPU는 일반적으로 서버 마더보드의 사용 가능한 PCIe 슬롯에 연결되는 확장 카드로 설치됩니다. PCIe 인터페이스는 NPU와 서버의 CPU 및 메모리 간에 고속 데이터 전송을 제공합니다.


일부 공급업체는 특수 커넥터 또는 상호 연결과 같은 NPU에 대한 다른 인터페이스 옵션도 제공할 수 있지만 PCIe는 NPU를 서버에 연결하기 위한 가장 일반적이고 널리 지원되는 인터페이스입니다. PCIe 인터페이스는 NPU에서 처리해야 하는 대량의 데이터에 적합한 고대역폭, 저지연 연결을 제공합니다.


서버에 연결되면 NPU를 사용하여 서버의 CPU에서 특정 유형의 계산을 오프로드하여 CPU가 다른 작업을 수행할 수 있도록 할 수 있습니다. 이는 많은 계산이 필요한 기계 학습 또는 데이터 분석과 같은 특정 유형의 워크로드를 가속화하는 데 도움이 될 수 있습니다.


Q5. 10개의 NPU 공급업체와 그들의 NPU 제품을 인기순으로 나열할 수 있습니까? 목록에서 GPU 공급업체를 제외하세요.


A5. 물론입니다. GPU 공급업체를 제외하는 목록은 다음과 같습니다.


Google: Google의 TPU(Tensor Processing Unit)는 딥 러닝 워크로드에 사용하도록 설계되었습니다. 최신 제품인 TPU v4는 교육 및 추론 워크로드를 위해 설계되었습니다.


Intel: Intel의 Nervana 신경망 프로세서(NNP)는 딥 러닝 워크로드에 사용하도록 설계되었습니다. 최신 제품인 NNP-T1000은 교육용으로 설계되었으며 NNP-I1000은 추론용으로 설계되었습니다.


Qualcomm: Qualcomm의 AI 엔진에는 딥 러닝을 포함한 기계 학습 워크로드를 실행하기 위한 하드웨어 가속기가 포함되어 있습니다. 최신 제품인 AI Engine 880은 모바일 장치용으로 설계되었습니다.


Huawei: Huawei의 NPU Ascend 시리즈에는 기계 학습 워크로드를 실행하기 위한 하드웨어가 포함되어 있습니다. 최신 제품인 Ascend 910은 데이터 센터 및 클라우드 컴퓨팅에서 사용하도록 설계되었습니다.


Amazon: Amazon의 Inferentia는 클라우드 컴퓨팅 환경에서 사용하도록 설계된 NPU입니다. 최신 제품인 Inferentia 2는 기계 학습 및 추론 워크로드를 실행하도록 설계되었습니다.


Xilinx: Xilinx의 Alveo 가속기 카드 시리즈에는 기계 학습 및 데이터 분석을 위한 하드웨어가 포함되어 있습니다. 최신 제품인 Alveo U50은 데이터 센터 및 클라우드 컴퓨팅 환경에서 워크로드를 가속화하도록 설계되었습니다.


Habana Labs: Habana Labs는 Gaudi 및 Goya를 비롯한 여러 NPU 제품을 생산하는 이스라엘 회사입니다. 이러한 NPU는 클라우드 컴퓨팅, 데이터 센터 및 에지 컴퓨팅에서 사용하도록 설계되었습니다.


Cambricon: Cambricon은 Cambricon 1M, Cambricon 1H 및 Cambricon 1H2를 비롯한 여러 NPU 제품을 생산하는 중국 회사입니다. 이러한 NPU는 클라우드 컴퓨팅, 데이터 센터 및 에지 컴퓨팅에서 사용하도록 설계되었습니다.


Graphcore: Graphcore의 IPU(Intelligence Processing Unit)는 딥 러닝 애플리케이션에 사용하도록 설계되었습니다. 최신 제품인 IPU-M2000은 교육 및 추론 워크로드 모두를 위해 설계되었습니다.


Tenstorrent: Tenstorrent의 Grayskull 아키텍처는 딥 러닝 및 머신 러닝 워크로드에 사용하도록 설계되었습니다. 최신 제품인 Tensix 칩은 훈련과 추론을 위해 설계되었습니다.


다시 말하지만, 인기도는 주관적일 수 있으며 특정 산업이나 사용 사례에 따라 달라질 수 있다는 점에 유의할 가치가 있습니다. 그럼에도 불구하고 이러한 NPU 공급업체와 해당 제품은 모두 업계의 핵심 업체로 인식되고 있습니다.




Q6. Habana Labs Gaudi에서 ML/DL에 어떤 종류의 소프트웨어 환경을 사용할 수 있습니까?


A6. Habana Labs Gaudi NPU는 ML/DL 워크로드를 위한 다양한 소프트웨어 환경에서 작동하도록 설계되었습니다. 여기 몇 가지 예가 있어요.


TensorFlow: Habana Labs는 Gaudi에서 실행하도록 최적화된 TensorFlow-Habana라는 맞춤형 TensorFlow 배포판을 제공합니다. 이 배포판에는 컨볼루션 및 순환 신경망과 같은 널리 사용되는 ML/DL 작업에 대한 지원이 포함되며 Gaudi의 아키텍처를 최대한 활용하도록 설계되었습니다.


PyTorch: Gaudi는 인기 있는 오픈 소스 ML/DL 프레임워크인 PyTorch도 지원합니다. Habana Labs는 Gaudi에 최적화된 HabanaPytorch라는 맞춤형 PyTorch 확장 기능을 제공하며 회선 및 배치 정규화와 같은 널리 사용되는 작업에 대한 지원을 포함합니다.


ONNX: Gaudi는 ML/DL 모델을 나타내는 개방형 표준인 ONNX(Open Neural Network Exchange) 형식도 지원합니다. ONNX 모델은 Gaudi로 가져와 고성능으로 실행할 수 있습니다.


Habana SynapseAI: Habana Labs는 SynapseAI라는 자체 소프트웨어 개발 키트도 제공합니다. 여기에는 ML/DL 개발자를 위한 고급 API 세트가 포함되어 있습니다. 이러한 API를 통해 개발자는 Gaudi에서 모델을 쉽게 생성, 교육 및 배포하고 NPU의 전체 기능을 활용할 수 있습니다.


전반적으로 Habana Labs는 개발자가 Gaudi NPU의 성능과 기능을 쉽게 활용할 수 있도록 설계된 다양한 소프트웨어 도구와 API를 제공합니다.



Q7. Habana Labs Gaudi는 어떤 OS에서 지원되나요?


A7. Habana Labs Gaudi NPU는 다음을 포함한 여러 운영 체제에서 지원됩니다.


Ubuntu: Habana Labs는 Ubuntu 18.04 및 20.04 LTS에서 Gaudi용 드라이버, 라이브러리 및 소프트웨어 패키지를 제공합니다. 이러한 패키지는 시스템의 패키지 소스에 추가할 수 있는 Habana Labs APT 저장소를 통해 사용할 수 있습니다.


RHEL(Red Hat Enterprise Linux): Habana Labs는 RHEL 8.4에서 Gaudi용 드라이버 및 소프트웨어 패키지를 제공합니다. 이러한 패키지는 시스템의 패키지 소스에 추가할 수 있는 Habana Labs YUM 저장소를 통해 사용할 수 있습니다.


CentOS: Gaudi는 RHEL과 동일한 패키지 및 리포지토리를 사용하여 CentOS 8.4에서도 지원됩니다.


SUSE Linux Enterprise Server(SLES): Habana Labs는 SLES 15 SP3에서 Gaudi용 드라이버 및 소프트웨어 패키지를 제공합니다. 이러한 패키지는 Habana Labs SUSE 리포지토리를 통해 사용할 수 있습니다.


Gaudi를 사용하려면 시스템이 지원되는 CPU, 메모리 및 마더보드를 포함하여 특정 하드웨어 요구 사항을 충족해야 합니다. Habana Labs는 웹 사이트에서 Gaudi와 호환되는 인증 시스템 목록을 제공합니다.



2020년 4월 8일 수요일

LSF 관련 Q&A : job의 suspend - resume, 방화벽 환경에서 뚫어놓아야 할 port들


Q1.  GPU를 사용하는 job의 경우에도 job을 suspend - resume하는 것이 가능한지요 ?

: LSF에서는 훨씬 더 긴급하고 중요한 job B가 생겼는데 이미 수행 중인 기존 job A가 자원을 다 쓰고 있어서 당장 가용한 자원이 없을 경우, 이미 RUNNING 상태로 수행 중인 기존 job A를 잠시 suspend 시키고, 그렇게 풀려난 자원을 이용하여 긴급하고 중요한 job B를 수행시킨 뒤, 그것이 다 끝나면 suspend 되었던 기존 job A를 다시 resume 할 수 있다고 들었습니다.  이것은 CPU 상에서 job이 수행될 때의 이야기일텐데, GPU 상에서 수행되는 job에 대해서도 suspend-resume이 잘 수행되는지요 ?

A1.  가능합니다만 tensorflow와 같은 deep learning training job에 대해서는 현실적인 효용성이 떨어집니다.   

: 이미 알고 계시는 것처럼 bkill 명령은 이미 submit 되어 수행 중인 LSF job을 글자 그대로 kill 하는 것이지만, bstop 명령은 중단시키는 것이 아니라 suspend 시키는 명령입니다.   (좀더 정확하게 말하자면 사용 옵션에 따라 bkill도 suspend를 시킬 수는 있습니다.)   그렇게 suspend 된 job은 나중에 bresume 명령 (또는 bkill -s CONT 명령)으로 resume할 수 있습니다.
다만, 이때 suspend된 job은 CPU 자원만 release할 뿐이고, memory 상에 올라가 있는 기존 process의 영역을 지우지는 않습니다.  따라서, bstop - bresume 명령을 통해 잠시 suspend 시킨 뒤에 나중에 다시 resume하기 위해서는 서버에 충분한 free memory 용량이 있어야 합니다.  대개의 경우 HPC cluster node들에는 큰 용량의 RAM이 장착되어 있으므로 CPU job에서는 bstop - bresume 명령을 통한 일시 중단 및 재개가 잘 됩니다.

그러나 GPU 상에서는 이야기가 좀더 복잡합니다.  GPU 상에서 돌아가는 deep learning application, 가령 tensorflow를 사용하는 python의 경우 효율적인 training을 위해 GPU 상에 존재하는 모든 GPU memory를 다 써버리는 경우가 대부분입니다.  이렇게 tensorflow를 이용한 python process에 대해서도 bstop 명령을 날리면 해당 procecss가 kill 되지는 않고 suspend되는데, 그때 tensorflow가 사용하던 메모리는 그대로 모두 사용된 채로 남아 있게 됩니다.  즉, nvidia-smi 등의 명령으로 GPU 사용량을 확인해보면 GPU compute %는 0%지만 해당 GPU를 여전히 tensorflow python이 쥐고 있고 특히 GPU memory 사용량도 거의 100% 다 사용 중인 것으로 나오는 것을 보실 수 있습니다.

그런 경우에도 suspend - resume이 가능하기는 합니다만, 애초에 그렇게 수행 중이던 job을 suspend 시키는 이유가 더 급한 다른 job을 수행하기 위함인데 정작 그 상태에서는 다른 job을 수행시킬 수가 없습니다.  새로 수행되는 job이 사용할 available GPU memory가 없는 것으로 나올테니까요.  물론 caffe나 h2o 등과 같이, GPU memory를 전체 다 사용해버리지 않고 필요한 만큼만 점거하는 application의 경우, CPU에서와 같이 bstop - bresume 명령을 통해 잠시 suspend 시켜놓고 더 중요한 다른 job을 수행하는 것이 가능합니다.


Q2.  LSF가 정상적인 동작을 하기 위해서 사용하는 network port들은 어떤 것들이 있는지요 ?

: 방화벽이 구현되어 있는 네트워크 환경에서 LSF를 구성해서 사용하려고 해보니 잘 되지 않습니다.  아마 LSF가 필요로 하는 network port가 방화벽에 막혀 있어서 그런 것 같은데, ssh에 필요한 22번 외에 어떤 port들을 뚫어놓아야 하는지요 ?

A2.  LSF 운용에 필요한 기본 network port들은 아래와 같이 확인하실 수 있습니다. 

$ cd $EGO_TOP/conf

$ grep PORT lsf.conf
LSF_LIM_PORT=7869
LSF_RES_PORT=6878
LSB_MBD_PORT=6881
LSB_SBD_PORT=6882
LSB_QUERY_PORT=6891

단, LSF_LIM_PORT=7869 은 tcp/udp 둘 다 뚫어놓아야 하고, 나머지는 모두 tcp 입니다.  이에 대해서는 아래 URL을 참조하시기 바랍니다.

https://www.ibm.com/support/pages/configure-firewall-ports-lsf-master-platform-rtm-monitoring
https://www.ibm.com/support/pages/purpose-different-lsf-ports

2018년 12월 19일 수요일

Tensorflow보다 46배 빠른 deep learning framework ?? - Snap ML 소개

IBM에서는 GPU 서버 등 HW만 만드는 것이 아니라 tensorflow나 caffe와 같은 deep learning용 framework SW도 개발합니다.  Snap ML이라고 하는 것이 바로 그것입니다.   이름이 왜 Snap ML이냐하면 손가락을 딱 튕기는(snap) 순식간에 training이 이루어질 정도로 빠르다고 해서 그렇습니다.



(finger snap 하면 타노스인데 말입니다.)


이 Snap ML의 성능과 효용성에 대해서는 아래 IBM 공식 blog에 잘 나와 있습니다.

https://www.ibm.com/blogs/research/2018/03/machine-learning-benchmark/

위 블로그 내용을 요약하면 구글이 Google Cloud Platform (worker machine 60대 + parameter machine 34대)에서 tensorflow를 60분 동안 걸려 training한 결과 얻은 log loss를 IBM Snap ML은 IBM POWER9과 NVIDIA GPU를 장착한 AC922 4대에서 Snap ML로 불과 91.5초의 training 만으로 얻었다는 것입니다.  여기에 사용된 dataset은 Criteo Labs에서 제공하는 실제 internet 광고 click 건수 data로서, 총 40억 row에 달하는 광대한 data입니다. 

이런 고성능이 가능했던 것은 물론 CPU와 GPU 간을 PCI 버스가 아닌 NVLink로 연결하는 AC922 서버의 우수한 아키텍처 영향도 있습니다만, 이 Snap ML이라는 framework SW 자체의 우수성도 있습니다. 

이 posting에서는 PowerAI 5.4 버전에 포함된 Snap ML을 설치하고 거기에 포함된 example 몇 개를 돌려보겠습니다. 

먼저, Anaconda2 5.2를 설치하여 conda/python 환경을 구성합니다.

[root@localhost home]# wget https://repo.continuum.io/archive/Anaconda2-5.2.0-Linux-ppc64le.sh

[root@localhost home]# chmod a+x Anaconda2-5.2.0-Linux-ppc64le.sh

[root@localhost home]# ./Anaconda2-5.2.0-Linux-ppc64le.sh

[root@localhost home]# . /root/.bashrc

[root@localhost home]# which python
/opt/anaconda2/bin/python


그 다음에 PowerAI의 장점을 살려 아주 간단하게 Snap ML을 설치합니다. 

[root@localhost home]# yum install snap-ml-local.ppc64le
==============================================================================================
 Package              Arch         Version                 Repository                    Size
==============================================================================================
Installing:
 snap-ml-local        ppc64le      1.1.0-241.1887dec       mldl-repo-local               29 M
Installing for dependencies:
 powerai-license      ppc64le      5.4.0-124.150cee0       mldl-repo-local              4.3 M
 python-devel         ppc64le      2.7.5-76.el7            rhel-7-for-power-9-rpms      398 k


이렇게 설치된 snap-ml-local 속에는 아래와 같은 fileset들이 들어 있습니다.   보시다시피 저 위의 IBM 공식 블로그에 소개했던 Criteo Teraclick training의 미니 버전이라고 할 수 있는 criteo Kaggle example도 들어있습니다.

[root@localhost home]# rpm -ql snap-ml-local-1.1.0-241.1887dec.ppc64le
/opt/DL
/opt/DL/snap-ml-local
/opt/DL/snap-ml-local/bin
/opt/DL/snap-ml-local/bin/check_dependencies
/opt/DL/snap-ml-local/bin/install_dependencies
/opt/DL/snap-ml-local/bin/snap-ml-local-activate
/opt/DL/snap-ml-local/conda-pkgs
/opt/DL/snap-ml-local/conda-pkgs/channeldata.json
/opt/DL/snap-ml-local/conda-pkgs/icons
...
/opt/DL/snap-ml-local/examples/creditcard
/opt/DL/snap-ml-local/examples/creditcard/README.md
/opt/DL/snap-ml-local/examples/creditcard/example-creditcard.py
/opt/DL/snap-ml-local/examples/creditcard/preprocess-creditcard.py
/opt/DL/snap-ml-local/examples/criteo
/opt/DL/snap-ml-local/examples/criteo/README.md
/opt/DL/snap-ml-local/examples/criteo/example-criteo-kaggle-privacy.py
/opt/DL/snap-ml-local/examples/criteo/example-criteo-kaggle.py
/opt/DL/snap-ml-local/examples/criteo/preprocess-criteo-kaggle-privacy.py
/opt/DL/snap-ml-local/examples/criteo/preprocess-criteo-kaggle.py
...

Snap ML의 사용을 위해서는 먼저 install_dependencies를 수행하여 관련 conda package들을 설치합니다.

[root@localhost home]# /opt/DL/snap-ml-local/bin/install_dependencies
...
    package                    |            build
    ---------------------------|-----------------
    powerai-snap-ml-local-prereqs-1.1.0_241.1887dec|           py27_0           4 KB  file:///opt/DL/snap-ml-local/conda-pkgs
    conda-4.5.11               |           py27_0         1.0 MB
    openssl-1.0.2p             |       h14c3975_0         3.3 MB
    openblas-devel-0.2.20      |                7         101 KB
    blas-1.1                   |         openblas           4 KB  file:///opt/DL/snap-ml-local/conda-pkgs
    certifi-2018.11.29         |           py27_0         146 KB
    pai4sk-0.20rc1_241.1887dec |           py27_0        14.0 MB  file:///opt/DL/snap-ml-local/conda-pkgs
    ------------------------------------------------------------
                                           Total:        18.5 MB
...

그리고 PowerAI license에 동의하는 script를 수행하고 이어서 snap-ml-local-activate를 수행하여 PATH 등의 환경 변수를 설정합니다.

[root@localhost home]# /opt/DL/license/bin/accept-powerai-license.sh

[root@localhost home]# source /opt/DL/snap-ml-local/bin/snap-ml-local-activate

이제 Amazone cloud에서 Criteo의 test dataset 미니 버전을 download 합니다.  4.5GB 정도 됩니다.

[root@localhost home]# mkdir data && cd data

[root@localhost data]# wget https://s3-us-west-2.amazonaws.com/criteo-public-svm-data/criteo.kaggle2014.svm.tar.gz
Length: 4849417655 (4.5G) [application/x-tar]
Saving to: ‘criteo.kaggle2014.svm.tar.gz’
100%[======================================>] 4,849,417,655 22.5MB/s   in 4m 29s
2018-12-14 12:28:34 (17.2 MB/s) - ‘criteo.kaggle2014.svm.tar.gz’ saved [4849417655/4849417655]

이 tar 압축을 풀고 data preprocessing을 위한 python code를 수행합니다.  약 1시간 정도 걸립니다.

[root@localhost data]# tar xzf criteo.kaggle2014.svm.tar.gz 

[root@localhost data]# time python /opt/DL/snap-ml-local/examples/criteo/preprocess-criteo-kaggle.py --data_path=/home
real    67m57.038s
user    67m20.277s
sys     0m27.750s


이제 다음과 같이 criteo kaggle2014 dataset의 포맷이 완료되었습니다. 

[root@localhost data]# ls -ltr crit*
-r--r--r-- 1 1007 1007  3523847664 Sep 27  2017 criteo.kaggle2014.test.svm
-r--r--r-- 1 1007 1007 26737621696 Sep 27  2017 criteo.kaggle2014.train.svm
-rw-r--r-- 1 root root  4849417655 Sep 27  2017 criteo.kaggle2014.svm.tar.gz
-rw-r--r-- 1 root root 16227489113 Dec 14 13:46 criteo.kaggle2014.X_train.npz
-rw-r--r-- 1 root root  5409163153 Dec 14 13:47 criteo.kaggle2014.X_test.npz
-rw-r--r-- 1 root root   275043776 Dec 14 13:47 criteo.kaggle2014.y_train.npy
-rw-r--r-- 1 root root    91681320 Dec 14 13:47 criteo.kaggle2014.y_test.npy


이걸 GPU 1번 1개를 이용하여 training하기 위해 다음과 같이 example-criteo-kaggle.py를 수행해 줍니다.

[root@localhost data]# time python /opt/DL/snap-ml-local/examples/criteo/example-criteo-kaggle.py --data_path=/home --use_gpu --device_ids 1
Data load time (s): 28.92
[Info] Tolerance 0.001 attained after 49 epochs.
/opt/anaconda2/lib/python2.7/site-packages/pai4sk/sml_solvers/logistic.py:406: UserWarning: PowerAI: Default solver is set to 'snapml'. Specify a solver to silence this warning.
  UserWarning)
[pai4sk] Training time (s):  25.89
[pai4sk] Logarithmic loss:   0.4558
[Info] Tolerance 0.001 attained after 49 epochs.
/opt/anaconda2/lib/python2.7/site-packages/pai4sk/linear_model/logistic.py:432: FutureWarning: Default solver will be changed to 'lbfgs' in 0.22. Specify a solver to silence this warning.
  FutureWarning)
[pai4sk.lmodel] Training time (s):  32.28
[pai4sk.lmodel] Logarithmic loss:   0.4558
[sklearn] Training time (s):  624.55
[sklearn] Logarithmic loss:   0.4558

real    12m14.974s
user    11m57.544s
sys     0m34.245s


위의 example 결과는 크게 3가지입니다. 

[pai4sk] Training time (s):  25.89 :  Snap ML을 이용한 training 결과
[pai4sk.lmodel] Training time (s):  32.28 :  Snap ML Linear Regression을 이용한 training 결과
[sklearn] Training time (s):  624.55 :  일반 sklearn을 이용한 training 결과

즉, Snap ML을 이용하면 일반 sklearn을 이용하는 것보다 거의 25배 더 빨리 training을 마칠 수 있으며, 그럼에도 불구하고 log loss는 비슷한 수준이라는 것을 아실 수 있습니다.

더 자세한 내용은 아래 URL들을 참조하시기 바랍니다.

https://ibmsoe.github.io/snap-ml-doc/index.html
https://ibmsoe.github.io/snap-ml-doc/tutorials.html
https://ibmsoe.github.io/snap-ml-doc/notebookcreditlocal.html#notebook-credit-local
https://ibmsoe.github.io/snap-ml-doc/pai4skapidocumentation.html#pai4sk-api-documentation

2018년 10월 5일 금요일

IBM이 자랑하는 POWER9 + V100 GPU의 조합이 H2O DriverlessAI에 어떤 도움이 될까 ?

H2O Driverless AI는 model training 중 GPU에서 돌리는 것이 더 효과적이라고 판단될 때마다 필요에 따라 드문드문 GPU를 사용합니다.   이는 우측 하단의 "GPU usage" tab을 클릭하면 아래와 같이 보실 수 있습니다.



물론 nvidia-smi 에서도 모니터링하실 수 있습니다.   GPU 1개당 1개의 process가 수행되는 것이 아니라, GPU 메모리가 허용하는 한 필요에 따라 GPU 1개당 여러 개의 process들이 수행되기도 합니다.

Fri Oct  5 03:36:45 2018
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 396.26                 Driver Version: 396.26                    |
|-------------------------------+----------------------+----------------------+
| 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 P100-SXM2...  On   | 00000002:01:00.0 Off |                    0 |
| N/A   34C    P0    64W / 300W |    455MiB / 16280MiB |     40%      Default |
+-------------------------------+----------------------+----------------------+
|   1  Tesla P100-SXM2...  On   | 00000003:01:00.0 Off |                    0 |
| N/A   36C    P0    77W / 300W |    455MiB / 16280MiB |     38%      Default |
+-------------------------------+----------------------+----------------------+
|   2  Tesla P100-SXM2...  On   | 0000000A:01:00.0 Off |                    0 |
| N/A   32C    P0    71W / 300W |    455MiB / 16280MiB |     40%      Default |
+-------------------------------+----------------------+----------------------+
|   3  Tesla P100-SXM2...  On   | 0000000B:01:00.0 Off |                    0 |
| N/A   36C    P0    64W / 300W |    455MiB / 16280MiB |     38%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|    0     94004      C   ...el-running(prot=False)-XGBoostModel-fit   445MiB |
|    1     94011      C   ...el-running(prot=False)-XGBoostModel-fit   445MiB |
|    2     94044      C   ...el-running(prot=False)-XGBoostModel-fit   445MiB |
|    3     94126      C   ...el-running(prot=False)-XGBoostModel-fit   445MiB |
+-----------------------------------------------------------------------------+


과연 여기서 NVLink가 어느 정도의 효과를 발휘할까요 ?  그것을 확인하는 가장 쉽고 생생한 방법은 nvprof 명령을 이용하여 DriverlessAI process들이 GPU를 어떻게 사용하는지 profile을 떠보는 것입니다.

먼저, DAI의 홈 디렉토리에 있는 start-dai.sh를 다음과 같이 수정합니다.  원래의 line은 #으로 막아놓았고, 아래 붉은색의 명령어들이 새로 삽입된 nvprof 명령어입니다.

[root@p57a22 dai-1.3.1-linux-ppc64le]# cat start-dai.sh
#!/bin/bash

if [ "x${RUN_DAI_CREATE_PIDFILE}" == "x1" ]; then
# python -m h2oai &
 /usr/local/cuda-9.2/bin/nvprof --log-file /tmp/nvprof1.%p.txt --export-profile /tmp/nvprof1.%p.nvvp --print-gpu-trace --profile-child-processes python -m h2oai &
    echo $! > ${RUN_DAI_PIDFILE_DIR}/dai.pid
else
# exec python -m h2oai
    exec /usr/local/cuda-9.2/bin/nvprof --log-file /tmp/nvprof1.%p.txt --export-profile /tmp/nvprof1.%p.nvvp --print-gpu-trace --profile-child-processes python -m h2oai
fi


위와 같이 해놓고 kill-dai.sh를 수행하여 기존 DAI를 stop시킨 뒤, run-dai.sh를 수행하여 DAI를 새로 구동한 뒤에 큼직한 dataset으로 training을 해봅니다.   이때, 다음과 같이 child process마다 txt와 nvvp로 텍스트와 바이너리 로그 파일이 생성되는 것을 보실 수 있습니다.

[root@p57a22 dai-1.3.1-linux-ppc64le]# ls -l /tmp/nvprof1*
...
-rw-r--r-- 1 root root 170096640 Oct  5 03:16 /tmp/nvprof1.83368.nvvp
-rw-r--r-- 1 root root 233180816 Oct  5 03:22 /tmp/nvprof1.83368.txt
-rw-r--r-- 1 root root 180073472 Oct  5 03:16 /tmp/nvprof1.83405.nvvp
-rw-r--r-- 1 root root 246428557 Oct  5 03:20 /tmp/nvprof1.83405.txt
...

이 중 하나를 CUDA에 딸려 있는 NVIDIA Visual Profiler (NVVP)로 분석해보면 다음과 같습니다.




"CUDA memcpy HtoD" (Host to Device, 즉 서버 CPU에서 GPU로의 memcpy도 9 GB/s에 가깝고 DtoD (GPU간의 memcpy)는 134 GB/s에 가깝게 사용되는 것을 보실 수 있습니다.

특히 8개의 GPU를 장착한 일반 x86 기반 GPU 서버들은 NVLink를 1개씩만 이용하여 연결되므로 GPU간의 통신은 이론상 50 GB/s, CPU와 GPU간의 통신은 이론상 32 GB/s가 최대인 것에 비해, IBM AC922은 3개의 NVLink를 aggregation시켜 CPU와 GPU이나 GPU끼리나 모두 150 GB/s로 연결된 것이 H2O DriverlessAI의 운용에 크게 유리하다는 것을 쉽게 보실 수 있습니다.


2017년 11월 10일 금요일

tensorflow 1.3, caffe2, pytorch의 nvidia-docker를 이용한 테스트

tensorflow 1.3, caffe2, pytorch의 nvidia-docker를 이용한 테스트 방법입니다.

1) tensorflow v1.3

다음과 같이 tensorflow 1.3 docker image를 구동합니다.

root@minsky:~# nvidia-docker run -ti --rm  -v /data:/data bsyu/tf1.3-ppc64le:v0.1 bash

먼저 각종 PATH 환경 변수를 확인합니다.

root@67c0e6901bb2:/# env | grep PATH
LIBRARY_PATH=/usr/local/cuda/lib64/stubs:
LD_LIBRARY_PATH=/usr/local/nvidia/lib:/usr/local/nvidia/lib64
PATH=/opt/anaconda3/bin:/usr/local/nvidia/bin:/usr/local/cuda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PYTHONPATH=/opt/anaconda3/lib/python3.6/site-packages

cifar10 관련된 example code가 들어있는 directory로 이동합니다.

root@67c0e6901bb2:/# cd /data/imsi/tensorflow/models/tutorials/image/cifar10

수행할 cifar10_multi_gpu_train.py code를 일부 수정합니다.  (원래는 --train_dir 등의 명령어 파라미터로 조정이 가능해야 하는데, 실제로는 직접 source를 수정해야 제대로 수행되는 것 같습니다.)

root@67c0e6901bb2:/data/imsi/tensorflow/models/tutorials/image/cifar10# time python cifar10_multi_gpu_train.py --batch_size 512 --num_gpus 2
usage: cifar10_multi_gpu_train.py [-h] [--batch_size BATCH_SIZE]
                                  [--data_dir DATA_DIR] [--use_fp16 USE_FP16]
cifar10_multi_gpu_train.py: error: unrecognized arguments: --num_gpus 2

위와 같은 error를 막기 위해, 아래와 같이 직접 code를 수정합니다.

root@67c0e6901bb2:/data/imsi/tensorflow/models/tutorials/image/cifar10# vi cifar10_multi_gpu_train.py
...
#parser.add_argument('--train_dir', type=str, default='/tmp/cifar10_train',
parser.add_argument('--train_dir', type=str, default='/data/imsi/test/tf1.3',
                    help='Directory where to write event logs and checkpoint.')

#parser.add_argument('--max_steps', type=int, default=1000000,
parser.add_argument('--max_steps', type=int, default=10000,
                    help='Number of batches to run.')

#parser.add_argument('--num_gpus', type=int, default=1,
parser.add_argument('--num_gpus', type=int, default=4,
                    help='How many GPUs to use.')


이제 다음과 같이 run 하시면 됩니다.  여기서는 batch_size를 512로 했는데, 더 크게 잡아도 될 것 같습니다.

root@67c0e6901bb2:/data/imsi/tensorflow/models/tutorials/image/cifar10# time python cifar10_multi_gpu_train.py --batch_size 512
>> Downloading cifar-10-binary.tar.gz 6.1%
...
2017-11-10 01:20:23.628755: step 9440, loss = 0.63 (15074.6 examples/sec; 0.034 sec/batch)
2017-11-10 01:20:25.052011: step 9450, loss = 0.64 (14615.4 examples/sec; 0.035 sec/batch)
2017-11-10 01:20:26.489564: step 9460, loss = 0.55 (14872.0 examples/sec; 0.034 sec/batch)
2017-11-10 01:20:27.860303: step 9470, loss = 0.61 (14515.9 examples/sec; 0.035 sec/batch)
2017-11-10 01:20:29.289386: step 9480, loss = 0.54 (13690.6 examples/sec; 0.037 sec/batch)
2017-11-10 01:20:30.799570: step 9490, loss = 0.69 (15940.8 examples/sec; 0.032 sec/batch)
2017-11-10 01:20:32.239056: step 9500, loss = 0.54 (12581.4 examples/sec; 0.041 sec/batch)
2017-11-10 01:20:34.219832: step 9510, loss = 0.60 (14077.9 examples/sec; 0.036 sec/batch)
...

다음으로는 전체 CPU, 즉 2개 chip 총 16-core의 절반인 1개 chip 8-core와, 전체 GPU 4개 중 2개의 GPU만 할당한 docker를 수행합니다.   여기서 --cpuset-cpus을 써서 CPU 자원을 control할 때, 저렇게 CPU 번호를 2개씩 그룹으로 줍니다.  이는 IBM POWER8가 SMT(hyperthread)가 core당 8개씩 낼 수 있는 특성 때문에 core 1개당 8개의 logical CPU 번호를 할당하기 때문입니다.  현재는 deep learning의 성능 최적화를 위해 SMT를 8이 아닌 2로 맞추어 놓았습니다.

root@minsky:~# NV_GPU=0,1 nvidia-docker run -ti --rm --cpuset-cpus="0,1,8,9,16,17,24,25,32,33,40,41,48,49" -v /data:/data bsyu/tf1.3-ppc64le:v0.1 bash

root@3b2c2614811d:~# nvidia-smi

Fri Nov 10 02:24:14 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 P100-SXM2...  On   | 0002:01:00.0     Off |                    0 |
| N/A   38C    P0    30W / 300W |      0MiB / 16280MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
|   1  Tesla P100-SXM2...  On   | 0003:01:00.0     Off |                    0 |
| N/A   40C    P0    33W / 300W |      0MiB / 16280MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID  Type  Process name                               Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+


root@3b2c2614811d:/# cd /data/imsi/tensorflow/models/tutorials/image/cifar10

이제 GPU가 4개가 아니라 2개이므로, cifar10_multi_gpu_train.py도 아래와 같이 수정합니다.

root@3b2c2614811d:/data/imsi/tensorflow/models/tutorials/image/cifar10# vi cifar10_multi_gpu_train.py
...
#parser.add_argument('--num_gpus', type=int, default=1,
parser.add_argument('--num_gpus', type=int, default=2,
                    help='How many GPUs to use.')

수행하면 잘 돌아갑니다.

root@3b2c2614811d:/data/imsi/tensorflow/models/tutorials/image/cifar10# time python cifar10_multi_gpu_train.py --batch_size 512
>> Downloading cifar-10-binary.tar.gz 1.7%
...
2017-11-10 02:35:50.040462: step 120, loss = 4.07 (15941.4 examples/sec; 0.032 sec/batch)
2017-11-10 02:35:50.587970: step 130, loss = 4.14 (19490.7 examples/sec; 0.026 sec/batch)
2017-11-10 02:35:51.119347: step 140, loss = 3.91 (18319.8 examples/sec; 0.028 sec/batch)
2017-11-10 02:35:51.655916: step 150, loss = 3.87 (20087.1 examples/sec; 0.025 sec/batch)
2017-11-10 02:35:52.181703: step 160, loss = 3.90 (19215.5 examples/sec; 0.027 sec/batch)
2017-11-10 02:35:52.721608: step 170, loss = 3.82 (17780.1 examples/sec; 0.029 sec/batch)
2017-11-10 02:35:53.245088: step 180, loss = 3.92 (18888.4 examples/sec; 0.027 sec/batch)
2017-11-10 02:35:53.777146: step 190, loss = 3.80 (19103.7 examples/sec; 0.027 sec/batch)
2017-11-10 02:35:54.308063: step 200, loss = 3.76 (18554.2 examples/sec; 0.028 sec/batch)
...


2) caffe2

여기서는 처음부터 GPU 2개와 CPU core 8개만 가지고 docker를 띄워 보겠습니다.

root@minsky:~# NV_GPU=0,1 nvidia-docker run -ti --rm --cpuset-cpus="0,1,8,9,16,17,24,25,32,33,40,41,48,49" -v /data:/data bsyu/caffe2-ppc64le:v0.3 bash

보시는 바와 같이 GPU가 2개만 올라옵니다.

root@dc853a5495a0:/# nvidia-smi

Fri Nov 10 07:22: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 P100-SXM2...  On   | 0002:01:00.0     Off |                    0 |
| N/A   32C    P0    29W / 300W |      0MiB / 16280MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+
|   1  Tesla P100-SXM2...  On   | 0003:01:00.0     Off |                    0 |
| N/A   35C    P0    32W / 300W |      0MiB / 16280MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID  Type  Process name                               Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

환경변수를 확인합니다.  여기서는 caffe2가 /opt/caffe2에 설치되어 있으므로, LD_LIBRARY_PATH나 PYTHONPATH를 거기에 맞춥니다.

root@dc853a5495a0:/# env | grep PATH
LIBRARY_PATH=/usr/local/cuda/lib64/stubs:
LD_LIBRARY_PATH=/opt/caffe2/lib:/opt/DL/nccl/lib:/usr/local/nvidia/lib:/usr/local/nvidia/lib64
PATH=/opt/caffe2/bin:/usr/local/nvidia/bin:/usr/local/cuda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PYTHONPATH=/opt/caffe2

caffe2는 아래의 resnet50_trainer.py를 이용해 테스트합니다.  그 전에, 먼저 https://github.com/caffe2/caffe2/issues/517 에 나온 lmdb 생성 문제를 해결하기 위해 이 URL에서 제시하는 대로 아래와 같이 code 일부를 수정합니다.

root@dc853a5495a0:/# cd /data/imsi/caffe2/caffe2/python/examples
root@dc853a5495a0:/data/imsi/caffe2/caffe2/python/examples# vi lmdb_create_example.py
...
            flatten_img = img_data.reshape(np.prod(img_data.shape))
#            img_tensor.float_data.extend(flatten_img)
            img_tensor.float_data.extend(flatten_img.flat)

이어서 다음과 같이 lmdb를 생성합니다.   이미 1번 수행했으므로 다시 할 경우 매우 빨리 수행될 것입니다.

root@dc853a5495a0:/data/imsi/caffe2/caffe2/python/examples# python lmdb_create_example.py --output_file /data/imsi/test/caffe2/lmdb
>>> Write database...
Inserted 0 rows
Inserted 16 rows
Inserted 32 rows
Inserted 48 rows
Inserted 64 rows
Inserted 80 rows
Inserted 96 rows
Inserted 112 rows
Checksum/write: 1744827
>>> Read database...
Checksum/read: 1744827

그 다음에 training을 다음과 같이 수행합니다.  여기서는 GPU가 2개만 보이는 환경이므로, --gpus에 0,1,2,3 대신 0,1만 써야 합니다.

root@dc853a5495a0:/data/imsi/caffe2/caffe2/python/examples# time python resnet50_trainer.py --train_data /data/imsi/test/caffe2/lmdb --gpus 0,1 --batch_size 128 --num_epochs 1

수행하면 다음과 같이 'not a valid file'이라는 경고 메시지가 나옵니다만, github 등을 googling해보면 무시하셔도 되는 메시지입니다.

Ignoring @/caffe2/caffe2/contrib/nccl:nccl_ops as it is not a valid file.
Ignoring @/caffe2/caffe2/contrib/gloo:gloo_ops as it is not a valid file.
Ignoring @/caffe2/caffe2/contrib/gloo:gloo_ops_gpu as it is not a valid file.
Ignoring @/caffe2/caffe2/distributed:file_store_handler_ops as it is not a valid file.
Ignoring @/caffe2/caffe2/distributed:redis_store_handler_ops as it is not a valid file.
INFO:resnet50_trainer:Running on GPUs: [0, 1]
INFO:resnet50_trainer:Using epoch size: 1499904
INFO:data_parallel_model:Parallelizing model for devices: [0, 1]
INFO:data_parallel_model:Create input and model training operators
INFO:data_parallel_model:Model for GPU : 0
INFO:data_parallel_model:Model for GPU : 1
INFO:data_parallel_model:Adding gradient operators
INFO:data_parallel_model:Add gradient all-reduces for SyncSGD
INFO:data_parallel_model:Post-iteration operators for updating params
INFO:data_parallel_model:Calling optimizer builder function
INFO:data_parallel_model:Add initial parameter sync
WARNING:data_parallel_model:------- DEPRECATED API, please use data_parallel_model.OptimizeGradientMemory() -----
WARNING:memonger:NOTE: Executing memonger to optimize gradient memory
INFO:memonger:Memonger memory optimization took 0.252535104752 secs
WARNING:memonger:NOTE: Executing memonger to optimize gradient memory
INFO:memonger:Memonger memory optimization took 0.253523111343 secs
INFO:resnet50_trainer:Starting epoch 0/1
INFO:resnet50_trainer:Finished iteration 1/11718 of epoch 0 (27.70 images/sec)
INFO:resnet50_trainer:Training loss: 7.39205980301, accuracy: 0.0
INFO:resnet50_trainer:Finished iteration 2/11718 of epoch 0 (378.51 images/sec)
INFO:resnet50_trainer:Training loss: 0.0, accuracy: 1.0
INFO:resnet50_trainer:Finished iteration 3/11718 of epoch 0 (387.87 images/sec)
INFO:resnet50_trainer:Training loss: 0.0, accuracy: 1.0
INFO:resnet50_trainer:Finished iteration 4/11718 of epoch 0 (383.28 images/sec)
INFO:resnet50_trainer:Training loss: 0.0, accuracy: 1.0
INFO:resnet50_trainer:Finished iteration 5/11718 of epoch 0 (381.71 images/sec)
...

다만 위와 같이 처음부터 accuracy가 1.0으로 나오는 문제가 있습니다.  이 resnet50_trainer.py 문제에 대해서는 caffe2의 github에 아래와 같이 discussion들이 있었습니다만, 아직 뾰족한 해결책은 없는 상태입니다.  하지만 상대적 시스템 성능 측정에는 별 문제가 없습니다.

https://github.com/caffe2/caffe2/issues/810


3) pytorch

이번에는 pytorch 이미지로 테스트하겠습니다.

root@8ccd72116fee:~# env | grep PATH
LIBRARY_PATH=/usr/local/cuda/lib64/stubs:
LD_LIBRARY_PATH=/usr/local/nvidia/lib:/usr/local/nvidia/lib64
PATH=/opt/anaconda3/bin:/usr/local/nvidia/bin:/usr/local/cuda/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

먼저 docker image를 아래와 같이 구동합니다.  단, 여기서는 --ipc=host 옵션을 씁니다.  이유는 https://discuss.pytorch.org/t/imagenet-example-is-crashing/1363/2 에서 언급된 hang 현상을 피하기 위한 것입니다. 

root@minsky:~# nvidia-docker run -ti --rm --ipc=host -v /data:/data bsyu/pytorch-ppc64le:v0.1 bash

가장 간단한 example인 mnist를 아래와 같이 수행합니다.  10 epochs를 수행하는데 대략 1분 30초 정도가 걸립니다.

root@8ccd72116fee:/data/imsi/examples/mnist# time python main.py --batch-size 512 --epochs 10
...
rain Epoch: 9 [25600/60000 (42%)]      Loss: 0.434816
Train Epoch: 9 [30720/60000 (51%)]      Loss: 0.417652
Train Epoch: 9 [35840/60000 (59%)]      Loss: 0.503125
Train Epoch: 9 [40960/60000 (68%)]      Loss: 0.477776
Train Epoch: 9 [46080/60000 (76%)]      Loss: 0.346416
Train Epoch: 9 [51200/60000 (85%)]      Loss: 0.361492
Train Epoch: 9 [56320/60000 (93%)]      Loss: 0.383941

Test set: Average loss: 0.1722, Accuracy: 9470/10000 (95%)

Train Epoch: 10 [0/60000 (0%)]  Loss: 0.369119
Train Epoch: 10 [5120/60000 (8%)]       Loss: 0.377726
Train Epoch: 10 [10240/60000 (17%)]     Loss: 0.402854
Train Epoch: 10 [15360/60000 (25%)]     Loss: 0.349409
Train Epoch: 10 [20480/60000 (34%)]     Loss: 0.295271
...

다만 이건 single-GPU만 사용하는 example입니다.  Multi-GPU를 사용하기 위해서는 아래의 imagenet example을 수행해야 하는데, 그러자면 ilsvrc2012 dataset을 download 받아 풀어놓아야 합니다.  그 data는 아래와 같이 /data/imagenet_dir/train과 /data/imagenet_dir/val에 각각 JPEG 형태로 풀어놓았습니다.

root@minsky:/data/imagenet_dir/train# while read SYNSET; do
> mkdir -p ${SYNSET}
> tar xf ../../ILSVRC2012_img_train.tar "${SYNSET}.tar"
> tar xf "${SYNSET}.tar" -C "${SYNSET}"
> rm -f "${SYNSET}.tar"
> done < /opt/DL/caffe-nv/data/ilsvrc12/synsets.txt

root@minsky:/data/imagenet_dir/train# ls -1 | wc -l
1000
root@minsky:/data/imagenet_dir/train# du -sm .
142657  .
root@minsky:/data/imagenet_dir/train# find . | wc -l
1282168

root@minsky:/data/imagenet_dir/val# ls -1 | wc -l
50000

이 상태에서 그대로 main.py를 수행하면 다음과 같은 error를 겪게 됩니다.  이유는 이 main.py는 val 디렉토리 밑에도 label별 디렉토리에 JPEG 파일들이 들어가 있기를 기대하는 구조이기 때문입니다.

RuntimeError: Found 0 images in subfolders of: /data/imagenet_dir/val
Supported image extensions are: .jpg,.JPG,.jpeg,.JPEG,.png,.PNG,.ppm,.PPM,.bmp,.BMP

따라서 아래와 같이 inception 디렉토리의 preprocess_imagenet_validation_data.py를 이용하여 label별 디렉토리로 JPEG 파일들을 분산 재배치해야 합니다.

root@minsky:/data/models/research/inception/inception/data# python preprocess_imagenet_validation_data.py /data/imagenet_dir/val imagenet_2012_validation_synset_labels.txt

이제 다시 보면 label별로 재분배된 것을 보실 수 있습니다.

root@minsky:/data/imagenet_dir/val# ls | head -n 3
n01440764
n01443537
n01484850

root@minsky:/data/imagenet_dir/val# ls | wc -l
1000
root@minsky:/data/imagenet_dir/val# find . | wc -l
51001

이제 다음과 같이 main.py를 수행하면 됩니다. 

root@8ccd72116fee:~# cd /data/imsi/examples/imagenet

root@8ccd72116fee:/data/imsi/examples/imagenet# time python main.py -a resnet18 --epochs 1 /data/imagenet_dir
=> creating model 'resnet18'
Epoch: [0][0/5005]      Time 11.237 (11.237)    Data 2.330 (2.330)      Loss 7.0071 (7.0071)    Prec@1 0.391 (0.391)   Prec@5 0.391 (0.391)
Epoch: [0][10/5005]     Time 0.139 (1.239)      Data 0.069 (0.340)      Loss 7.1214 (7.0515)    Prec@1 0.000 (0.284)   Prec@5 0.000 (1.065)
Epoch: [0][20/5005]     Time 0.119 (0.854)      Data 0.056 (0.342)      Loss 7.1925 (7.0798)    Prec@1 0.000 (0.260)   Prec@5 0.781 (0.930)
...




* 위에서 사용된 docker image들은 다음과 같이 backup을 받아두었습니다.

root@minsky:/data/docker_save# docker save --output caffe2-ppc64le.v0.3.tar bsyu/caffe2-ppc64le:v0.3
root@minsky:/data/docker_save# docker save --output pytorch-ppc64le.v0.1.tar bsyu/pytorch-ppc64le:v0.1
root@minsky:/data/docker_save# docker save --output tf1.3-ppc64le.v0.1.tar bsyu/tf1.3-ppc64le:v0.1
root@minsky:/data/docker_save# docker save --output cudnn6-conda2-ppc64le.v0.1.tar bsyu/cudnn6-conda2-ppc64le:v0.1
root@minsky:/data/docker_save# docker save --output cudnn6-conda3-ppc64le.v0.1.tar bsyu/cudnn6-conda3-ppc64le:v0.1

root@minsky:/data/docker_save# ls -l
total 28023280
-rw------- 1 root root 4713168896 Nov 10 16:48 caffe2-ppc64le.v0.3.tar
-rw------- 1 root root 4218520064 Nov 10 17:10 cudnn6-conda2-ppc64le.v0.1.tar
-rw------- 1 root root 5272141312 Nov 10 17:11 cudnn6-conda3-ppc64le.v0.1.tar
-rw------- 1 root root 6921727488 Nov 10 16:51 pytorch-ppc64le.v0.1.tar
-rw------- 1 root root 7570257920 Nov 10 16:55 tf1.3-ppc64le.v0.1.tar

비상시엔 이 이미지들을 docker load 명령으로 load 하시면 됩니다.

(예)  docker load --input caffe2-ppc64le.v0.3.tar

2017년 9월 13일 수요일

Inference 시스템을 위한 GPU 용량 sizing, 그리고 IBM caffe의 Large Model Support (LMS) 옵션

오늘은 inference, 그 중에서도 inference를 위한 GPU 시스템의 sizing을 어떻게 해야 하는지에 대해서 보겠습니다.  여기서는 특정적으로, caffe를 이용하여 image data를 inference할 때 어떻게 하는지를 보겠습니다.  그리고 덧붙여, IBM Minsky 서버에서만 가능한 옵션, -lms (Large Model Support)가 어떤 혜택을 주는지도 보시겠습니다.

이에 대해서는 아래 site에 기본적인 방법이 소개됩니다.  IBM China의 Deep Learning 개발팀의 박사님들에게 물어보니, 이 방법이 맞다고 합니다.

https://stackoverflow.com/questions/36867591/how-to-estimate-inference-time-from-average-forward-pass-time-in-caffe

여기서 핵심적인 부분은 바로 아래 부분입니다.

For instance, if I run the default command that comes with Caffe:

build/tools/caffe time --model=models/bvlc_alexnet/deploy.prototxt --gpu=0
I get the following output

...
I0426 13:07:32.701490 30417 layer_factory.hpp:77] Creating layer data
I0426 13:07:32.701513 30417 net.cpp:91] Creating Layer data
I0426 13:07:32.701529 30417 net.cpp:399] data -> data
I0426 13:07:32.709048 30417 net.cpp:141] Setting up data
I0426 13:07:32.709079 30417 net.cpp:148] Top shape: 10 3 227 227 (1545870)
I0426 13:07:32.709084 30417 net.cpp:156] Memory required for data: 6183480
...
I0426 13:07:34.390281 30417 caffe.cpp:377] Average Forward pass: 16.7818 ms.
I0426 13:07:34.390290 30417 caffe.cpp:379] Average Backward pass: 12.923 ms.
I0426 13:07:34.390296 30417 caffe.cpp:381] Average Forward-Backward: 29.7969 ms.
The following line:

I0426 13:07:32.709079 30417 net.cpp:148] Top shape: 10 3 227 227 (1545870)
is super important. It says that your input layer is 10x3x227x227-dimensional. In this case, the batch size is 10 images, each of size 3x227x227 (the 3 refers to each of the rgb channels in an image).

So effectively, it took 1.67818 ms/image to do a forward pass or inference time per image.


즉, caffe 명령어의 sub-comand 중 time 명령, 즉 caffe를 이용한 성능 benchmark 결과에서 평균 forward pass에 걸린 시간이 해당 model과 해당 이미지에 대해서 걸릴 inference time이라는 것입니다.  당연한 이야기지만 해당 model에 지정하는 data layer의 Top shape 10 3 227 227, 즉 batch size 10 x channel (RGB) 3 x height 227 x width 227이 클 수록 더 많은 시간이 걸립니다.

HPC cloud 서비스 업체인 Nimbix (nimbix.net/powerai)에서 제공하는 Minsky 서버의 P100 1장짜리 가상머신을 사용할 기회가 있어, 거기에서 이 test를 해봤습니다.  참고로 Nimbix는 docker 기반의 NVLink P100 GPU 가상 머신을 제공하는데, 이에 대해서도 나중에 다룰 기회가 있을 것입니다.

먼저, 1200x1200 크기의 이미지 1장에 대해서 GoogleNet으로 inference하는데 NVLink P100으로는 시간이 얼마나 걸리는지 보시겠습니다.  이를 위해서 먼저 GoogleNet에 포함된 deploy.prototxt를 아래와 같이 편집합니다.  원본 line은 아래에 #으로 comment-out 처리했습니다.

nimbix@JARVICENAE-0A0A1844:/data$ vi bvlc_googlenet/deploy.prototxt
name: "GoogleNet"
layer {
  name: "data"
  type: "Input"
  top: "data"
  input_param { shape: { dim: 1 dim: 3 dim: 1200 dim: 1200 } }
#  input_param { shape: { dim: 10 dim: 3 dim: 224 dim: 224 } }
}

이제 이렇게 수정된 model로 caffe time을 수행합니다.

nimbix@JARVICENAE-0A0A1844:/data$ caffe time -gpu 0 -model=/data/bvlc_googlenet/deploy.prototxt --iterations=1

그 과정을 다 보실 필요는 없고, 사실 맨 끝의 benchmark 결과에서 Average Forward pass 시간만 보시면 됩니다.

I0908 05:39:36.830621   567 caffe.cpp:513]       prob   forward: 0.020864 ms.
I0908 05:39:36.830627   567 caffe.cpp:516]       prob   backward: 0.00368 ms.
I0908 05:39:36.830641   567 caffe.cpp:521] Average Forward pass: 45.3671 ms.
I0908 05:39:36.830649   567 caffe.cpp:523] Average Backward pass: 102.551 ms.
I0908 05:39:36.830657   567 caffe.cpp:525] Average Forward-Backward: 150.178 ms.
I0908 05:39:36.830673   567 caffe.cpp:527] Total Time: 150.178 ms.
I0908 05:39:36.830689   567 caffe.cpp:528] *** Benchmark ends ***

여기서 만약 우리가 batch size(맨 앞의 dim)를 10으로 했다면 저 Average Forward pass 시간을 10으로 나눠야 합니다.  그러나 우리는 dim을 1로 주었으므로 그럴 필요없이 저것을 그대로 쓰면 됩니다.  즉, RGB 3 채널의 1200x1200 이미지 1장을 P100 GPU를 이용하여 GoogleNet으로 inference하는데 0.045초가 걸린다고 보시면 됩니다.

위의 테스트에서 display되는 benchmark 과정을 보면 Deep Learning의 얼개를 대충 보실 수 있습니다.  아래처럼 먼저 Top shape를 1 x 3 x 1200 x 1200으로 시작했다가, 다음 단계에서는 1 x 64 x 600 x 600으로, 그 다음에는 다시 300 x 300으로 계속 절반으로 줄여나가다가 결국 31 x 31에서 마무리 됩니다.   마지막 단계에서의 channel 수는 무려 1024로 늘어나게 되는데, 그 의미를 (저 같은 무식한 HW 엔지니어는) 잘 모르겠군요.   사실 HW 엔지니어에게 중요한 것은 거기에 필요로 하는 메모리 사이즈입니다.  각 단계별 top shape마다 필요로 하는 메모리 사이즈가 'Memory required for data'라는 항목으로 display되는데, 처음 단계에서는 17MB 정도로 시작했다가 맨 마지막 단계에서는 거의 1.6GB 가까이 갑니다.

...
I0908 05:39:25.035709   567 net.cpp:135] Top shape: 1 3 1200 1200 (4320000)
I0908 05:39:25.035733   567 net.cpp:143] Memory required for data: 17280000
I0908 05:39:25.035754   567 layer_factory.hpp:77] Creating layer conv1/7x7_s2
I0908 05:39:25.035786   567 net.cpp:90] Creating Layer conv1/7x7_s2
I0908 05:39:25.035799   567 net.cpp:635] conv1/7x7_s2 <- data
I0908 05:39:25.035816   567 net.cpp:609] conv1/7x7_s2 -> conv1/7x7_s2
I0908 05:39:29.695616   567 net.cpp:128] Setting up conv1/7x7_s2
I0908 05:39:29.695672   567 net.cpp:135] Top shape: 1 64 600 600 (23040000)
I0908 05:39:29.695695   567 net.cpp:143] Memory required for data: 109440000
...
I0908 05:39:29.862272   567 net.cpp:128] Setting up pool5/drop_7x7_s1
I0908 05:39:29.862279   567 net.cpp:135] Top shape: 1 1024 31 31 (984064)
I0908 05:39:29.862287   567 net.cpp:143] Memory required for data: 1587930496
I0908 05:39:29.862294   567 layer_factory.hpp:77] Creating layer loss3/classifier
I0908 05:39:29.862305   567 net.cpp:90] Creating Layer loss3/classifier
I0908 05:39:29.862311   567 net.cpp:635] loss3/classifier <- pool5/7x7_s1
I0908 05:39:29.862320   567 net.cpp:609] loss3/classifier -> loss3/classifier
I0908 05:39:36.385628   567 net.cpp:128] Setting up loss3/classifier
I0908 05:39:36.385684   567 net.cpp:135] Top shape: 1 1000 (1000)
I0908 05:39:36.385696   567 net.cpp:143] Memory required for data: 1587934496
I0908 05:39:36.385712   567 layer_factory.hpp:77] Creating layer prob
I0908 05:39:36.385728   567 net.cpp:90] Creating Layer prob
I0908 05:39:36.385737   567 net.cpp:635] prob <- loss3/classifier
I0908 05:39:36.385749   567 net.cpp:609] prob -> prob
I0908 05:39:36.386745   567 net.cpp:128] Setting up prob
I0908 05:39:36.386756   567 net.cpp:135] Top shape: 1 1000 (1000)
I0908 05:39:36.386765   567 net.cpp:143] Memory required for data: 1587938496
I0908 05:39:36.386771   567 net.cpp:206] prob does not need backward computation.
...


잠깐만요, 1.6GB라고요 ?  P100의 GPU 메모리 크기가 16GB 밖에 안되는데, 저런 image를 10장을 한꺼번에 inference하면 어떻게 된다는 것일까요 ?  설마 error가 날까요 ?  한번 해보겠습니다.  위와 동일한 모델을 사용하되, 단지 맨 앞의 dim, 즉 batch size를 1에서 10으로 바꾸겠습니다.

nimbix@JARVICENAE-0A0A1844:/data$ vi bvlc_googlenet/deploy.prototxt
name: "GoogleNet"
layer {
  name: "data"
  type: "Input"
  top: "data"
  input_param { shape: { dim: 10 dim: 3 dim: 1200 dim: 1200 } }
#  input_param { shape: { dim: 1 dim: 3 dim: 1200 dim: 1200 } }
#  input_param { shape: { dim: 10 dim: 3 dim: 224 dim: 224 } }
}

이제 동일하게 caffe time을 수행합니다.

nimbix@JARVICENAE-0A0A1844:/data$ caffe time -gpu 0 -model=/data/bvlc_googlenet/deploy.prototxt --iterations=1

I0908 05:43:44.249899   646 net.cpp:135] Top shape: 10 3 1200 1200 (43200000)
I0908 05:43:44.249914   646 net.cpp:143] Memory required for data: 172800000
I0908 05:43:44.249928   646 layer_factory.hpp:77] Creating layer conv1/7x7_s2
I0908 05:43:44.249949   646 net.cpp:90] Creating Layer conv1/7x7_s2
I0908 05:43:44.249956   646 net.cpp:635] conv1/7x7_s2 <- data
I0908 05:43:44.249967   646 net.cpp:609] conv1/7x7_s2 -> conv1/7x7_s2
I0908 05:43:44.614331   646 net.cpp:128] Setting up conv1/7x7_s2
I0908 05:43:44.614367   646 net.cpp:135] Top shape: 10 64 600 600 (230400000)
I0908 05:43:44.614382   646 net.cpp:143] Memory required for data: 1094400000
...
I0908 05:43:44.763245   646 net.cpp:135] Top shape: 10 1024 31 31 (9840640)
I0908 05:43:44.763254   646 net.cpp:143] Memory required for data: 15839942400
I0908 05:43:44.763260   646 layer_factory.hpp:77] Creating layer pool5/drop_7x7_s1
I0908 05:43:44.763272   646 net.cpp:90] Creating Layer pool5/drop_7x7_s1
I0908 05:43:44.763278   646 net.cpp:635] pool5/drop_7x7_s1 <- pool5/7x7_s1
I0908 05:43:44.763285   646 net.cpp:596] pool5/drop_7x7_s1 -> pool5/7x7_s1 (in-place)
I0908 05:43:44.763319   646 net.cpp:128] Setting up pool5/drop_7x7_s1
I0908 05:43:44.763325   646 net.cpp:135] Top shape: 10 1024 31 31 (9840640)
I0908 05:43:44.763334   646 net.cpp:143] Memory required for data: 15879304960
I0908 05:43:44.763340   646 layer_factory.hpp:77] Creating layer loss3/classifier
I0908 05:43:44.763352   646 net.cpp:90] Creating Layer loss3/classifier
I0908 05:43:44.763358   646 net.cpp:635] loss3/classifier <- pool5/7x7_s1
I0908 05:43:44.763367   646 net.cpp:609] loss3/classifier -> loss3/classifier
I0908 05:43:51.338423   646 net.cpp:128] Setting up loss3/classifier
I0908 05:43:51.345638   646 net.cpp:135] Top shape: 10 1000 (10000)
I0908 05:43:51.345651   646 net.cpp:143] Memory required for data: 15879344960
I0908 05:43:51.345667   646 layer_factory.hpp:77] Creating layer prob
I0908 05:43:51.345683   646 net.cpp:90] Creating Layer prob
I0908 05:43:51.345693   646 net.cpp:635] prob <- loss3/classifier
I0908 05:43:51.345705   646 net.cpp:609] prob -> prob
I0908 05:43:51.346666   646 net.cpp:128] Setting up prob
I0908 05:43:51.346678   646 net.cpp:135] Top shape: 10 1000 (10000)
I0908 05:43:51.346685   646 net.cpp:143] Memory required for data: 15879384960
...
I0908 05:43:51.724148   646 caffe.cpp:465] Initial loss: 0
I0908 05:43:51.724202   646 caffe.cpp:466] Performing Backward
I0908 05:43:51.724215   646 caffe.cpp:474] *** Benchmark begins ***
I0908 05:43:51.724222   646 caffe.cpp:475] Testing for 1 iterations.
F0908 05:43:51.915272   646 syncedmem.cpp:651] Check failed: error == cudaSuccess (2 vs. 0)  out of memory
*** Check failure stack trace: ***
    @     0x100000f5ce0c  google::LogMessage::Fail()
    @     0x100000f5f284  google::LogMessage::SendToLog()
    @     0x100000f5c768  google::LogMessage::Flush()
    @     0x100000f611c4  google::LogMessageFatal::~LogMessageFatal()
    @     0x10000026e3a0  caffe::SyncedMemory::mutable_gpu_data()
    @     0x1000002736c4  caffe::Blob<>::mutable_gpu_diff()
    @     0x1000004e774c  caffe::InnerProductLayer<>::Backward_gpu()
    @         0x10018ca8  (unknown)
    @         0x10012974  (unknown)
    @     0x100001c2309c  (unknown)
    @     0x100001c23298  __libc_start_main
    @              (nil)  (unknown)


아 !  정말 error가 납니다.   정말 data에만 무려 15.8GB의 메모리가 필요하다고 나오더니, 실제 벤치마크에 들어가자마자 out of memory 에러가 나면서 중단됩니다.  정말 GPU의 발목을 잡는 것은 GPU 메모리 크기의 한계라는 것을 절실히 깨닫는 순간입니다.

하지만 IBM과 NVIDIA는 여기서 포기하지 않습니다.  원래 NVIDIA의 CUDA에서는 Unified Memory라고 해서, GPU가 CPU 메모리를 마치 GPU 메모리처럼 쓸 수 있는 기능을 내놓았지요.  그러나 실제로는 그렇게 GPU가 CPU memory에 접근하는 통로가 느려터진 PCIe이다보니, Unified Memory를 쓰면 편리하기는 해도 성능은 거의 1/10 수준으로 떨어져 버리는 것이 상식이었습니다.  이는 NVLink P100을 장착한 DGX-1 서버에서도 마찬가지였습니다.  DGX-1도 GPU끼리만 NVLink로 연결될 뿐, 정작 CPU와 GPU를 연결하는 것은 PCIe거든요.  그래서 결국 아무도 caffe에서 unified memory를 쓸 생각을 하지 않았습니다.

그러나 IBM Minsky는 다릅니다.  POWER8 processor에는 NVLink port가 박혀있으므로, CPU와 GPU가 NVLink로 직접 연결되며, 그것도 NVLink 2개를 뭉쳐서 무려 80GB/sec로 연결됩니다.  PCIe의 2.5배나 되는 대역폭입니다.  이를 활용하여 caffe에서 CPU-GPU 간에 data를 직접 주고받을 수 있습니다 !  실제로 IBM은 최근 발표한 PowerAI 4.0에 포함된 IBM caffe (caffe-ibm)에 이를 적용했습니다.  그 결과, IBM caffe에서는 일반 bvlc caffe나 NV caffe에는 없는 새로운 옵션, -lms (LMS, Large Model Support)를 사용할 수 있습니다.

이에 대해서는 아래 문서를 참조하시면 됩니다.

https://public.dhe.ibm.com/software/server/POWER/Linux/mldl/ubuntu/README.html

역시 귀찮으신 분들을 위해 간략히 요약해드리면 이렇습니다.

-lms 8000000 : 이는 8000000 (kbyte 단위, 즉 8GB) 이상의 메모리 덩어리는 그냥 CPU 메모리 상에 두라는 뜻입니다.

즉, -lms 뒤에 큰 수를 적을 수록 가급적 GPU 메모리를 많이 쓰고 CPU 메모리는 정말 필요한 경우에만 쓰라는 이야기입니다.  당연히 최대치는 16000000 정도가 될 것이고, 이보다 더 큰 수를 적는 것은 사실상 LMS 옵션을 disable하는 효과를 냅니다.  반면에 -lms를 매우 작게, 가령 100으로 주는 것은 사실상 GPU 메모리를 쓰지 말고 다 CPU 메모리를 쓰라는 이야기가 됩니다.

또 -lms_frac <0~1.0> 이라는 옵션을 줄 수도 있습니다.  가령 -lms_frac 0.4로 주면, GPU 메모리 사용률이 40%가 되기 전에는 LMS 기능을 쓰지 말라는 것이 됩니다.  작은 크기의 model을 수행할 때는 굳이 느린 CPU 메모리를 쓸 필요가 없으므로, -lms_frac 0.9 정도로 주는 것이 좋습니다.

이제 위에서 out of memory를 낸 model에 대해 실제로 -lms 옵션을 적용해 보시지요.  먼저 -lms 8192, 즉 8MB 이상의 메모리 덩어리는 모두 CPU 메모리에 두라고 지시했습니다.

nimbix@JARVICENAE-0A0A1844:/data$ caffe time -gpu 0 -lms 8192 -model=/data/bvlc_googlenet/deploy.prototxt --iterations=1

I0908 05:47:44.949090   676 net.cpp:135] Top shape: 10 3 1200 1200 (43200000)
I0908 05:47:44.949105   676 net.cpp:143] Memory required for data: 172800000
I0908 05:47:44.949124   676 layer_factory.hpp:77] Creating layer conv1/7x7_s2
I0908 05:47:44.949146   676 net.cpp:90] Creating Layer conv1/7x7_s2
I0908 05:47:44.949153   676 net.cpp:635] conv1/7x7_s2 <- data
I0908 05:47:44.949167   676 net.cpp:609] conv1/7x7_s2 -> conv1/7x7_s2
I0908 05:47:45.580006   676 net.cpp:128] Setting up conv1/7x7_s2
I0908 05:47:45.580046   676 net.cpp:135] Top shape: 10 64 600 600 (230400000)
I0908 05:47:45.580060   676 net.cpp:143] Memory required for data: 1094400000
...
I0908 05:47:57.704324   676 caffe.cpp:465] Initial loss: 0
I0908 05:47:57.704356   676 caffe.cpp:466] Performing Backward
I0908 05:47:57.704371   676 caffe.cpp:474] *** Benchmark begins ***
I0908 05:47:57.704377   676 caffe.cpp:475] Testing for 1 iterations.
I0908 05:47:57.711424   676 syncedmem.cpp:355] [LMS] memory[0x110024232400]  device_=0 size_ = 921600000 allocation=7349057792 fragmented size = 655558000 gpu_ptr_=1155371368464
I0908 05:47:57.769644   676 syncedmem.cpp:355] [LMS] memory[0x110024258aa0]  device_=0 size_ = 230400000 allocation=7579458048 fragmented size = 425158224 gpu_ptr_=1122381070352
I0908 05:47:57.778683   676 syncedmem.cpp:355] [LMS] memory[0x110024286d30]  device_=0 size_ = 230400000 allocation=7809858304 fragmented size = 425158464 gpu_ptr_=1122842444032
I0908 05:47:57.790587   676 syncedmem.cpp:355] [LMS] memory[0x1100242c0be0]  device_=0 size_ = 691200000 allocation=8731458560 fragmented size = 655558704 gpu_ptr_=1156294115344
I0908 05:47:57.838747   676 syncedmem.cpp:355] [LMS] memory[0x1100242df300]  device_=0 size_ = 691200000 allocation=9653058816 fragmented size = 885958944 gpu_ptr_=1157447262464
...
I0908 05:47:58.203995   676 caffe.cpp:513] pool5/7x7_s1 forward: 4.48429 ms.
I0908 05:47:58.204002   676 caffe.cpp:516] pool5/7x7_s1 backward: 0.002144 ms.
I0908 05:47:58.204010   676 caffe.cpp:513] pool5/drop_7x7_s1    forward: 0.367552 ms.
I0908 05:47:58.204015   676 caffe.cpp:516] pool5/drop_7x7_s1    backward: 0.002112 ms.
I0908 05:47:58.204022   676 caffe.cpp:513] loss3/classifier     forward: 18.1078 ms.
I0908 05:47:58.204033   676 caffe.cpp:516] loss3/classifier     backward: 0.002112 ms.
I0908 05:47:58.204041   676 caffe.cpp:513]       prob   forward: 0.022848 ms.
I0908 05:47:58.204047   676 caffe.cpp:516]       prob   backward: 0.011328 ms.
I0908 05:47:58.204061   676 caffe.cpp:521] Average Forward pass: 495.206 ms.
I0908 05:47:58.204067   676 caffe.cpp:523] Average Backward pass: 2.21437 ms.
I0908 05:47:58.204074   676 caffe.cpp:525] Average Forward-Backward: 499.65 ms.
I0908 05:47:58.204092   676 caffe.cpp:527] Total Time: 499.65 ms.
I0908 05:47:58.204107   676 caffe.cpp:528] *** Benchmark ends ***


예 !  도중에 LMS가 사용된다는 메시지가 display되면서 성공적으로 완료되었습니다 !  아무래도 느린 CPU 메모리를 사용하니까 당연히 성능은 떨어졌을 것입니다.  얼마나 떨어졌을까요 ?  여기서의 결과는 Average Forward pass: 495.206 ms 인데, batch size가 10이므로 이미지 1장당 0.0495초 걸린 것입니다.  위에서 1장씩 테스트했을 때의 결과 0.045초보다 10% 정도 느려졌습니다.   10장씩 batch로 돌리면 사실 1장씩 돌린 것보다는 빨리 나와야 하는데 오히려 10% 느려진 것은 많이 느려진 것이지요.

결국 LMS를 사용하면 심각한 성능 저하는 어쩔 수 없이 발생하는 것일까요 ?  꼭 그렇지는 않습니다.  방금 제가 수행한 것은 극단적으로 거의 모든 메모리 덩어리를 CPU 메모리에 두라고 지시한 것입니다.  GPU 메모리를 적극적으로 활용하되, GPU 메모리 크기보다 큰 것들만 어쩔 수 없이 CPU 메모리를 사용하라고 지시하면 성능이 훨씬 더 좋을 것입니다.

이번에는 그렇게 -lms 160000000 옵션으로 돌려 보겠습니다.

nimbix@JARVICENAE-0A0A1844:/data$ caffe time -gpu 0 -lms 160000000 -model=/data/bvlc_googlenet/deploy.prototxt --iterations=1

I0908 06:32:20.006875  1126 net.cpp:135] Top shape: 10 3 1200 1200 (43200000)
I0908 06:32:20.006891  1126 net.cpp:143] Memory required for data: 172800000
I0908 06:32:20.006904  1126 layer_factory.hpp:77] Creating layer conv1/7x7_s2
I0908 06:32:20.006927  1126 net.cpp:90] Creating Layer conv1/7x7_s2
I0908 06:32:20.006933  1126 net.cpp:635] conv1/7x7_s2 <- data
I0908 06:32:20.006944  1126 net.cpp:609] conv1/7x7_s2 -> conv1/7x7_s2
I0908 06:32:20.591289  1126 net.cpp:128] Setting up conv1/7x7_s2
I0908 06:32:20.591329  1126 net.cpp:135] Top shape: 10 64 600 600 (230400000)
I0908 06:32:20.591343  1126 net.cpp:143] Memory required for data: 1094400000
...
I0908 06:32:28.272960  1126 net.cpp:296] [LMS] BuildLargeModelSupport
W0908 06:32:28.273018  1126 net.cpp:348] [LMS] ######################################################
W0908 06:32:28.273172  1126 net.cpp:349] [LMS] uncovered layer type: Softmax
W0908 06:32:28.273182  1126 net.cpp:350] [LMS] ######################################################
W0908 06:32:28.273310  1126 net.cpp:348] [LMS] ######################################################
W0908 06:32:28.273320  1126 net.cpp:349] [LMS] uncovered layer type: Input
W0908 06:32:28.273329  1126 net.cpp:350] [LMS] ######################################################
I0908 06:32:28.273347  1126 net.cpp:425] [LMS] data_forward [0] data:  -> data: 0x110009bfa4f0(172800000)  ### flag=0   data:
I0908 06:32:28.273361  1126 net.cpp:425] [LMS] conv1/7x7_s2_forward [1] data: 0x110009bfa4f0(172800000)  -> data: 0x1100233f7520(921600000)  ### flag=0   data: 0x110009bfa4f0(1,1)
...
I0908 06:32:29.055697  1126 caffe.cpp:513]       prob   forward: 0.022016 ms.
I0908 06:32:29.055704  1126 caffe.cpp:516]       prob   backward: 0.006848 ms.
I0908 06:32:29.055716  1126 caffe.cpp:521] Average Forward pass: 263.516 ms.
I0908 06:32:29.055724  1126 caffe.cpp:523] Average Backward pass: 2.21066 ms.
I0908 06:32:29.055730  1126 caffe.cpp:525] Average Forward-Backward: 267.967 ms.
I0908 06:32:29.055748  1126 caffe.cpp:527] Total Time: 267.967 ms.
I0908 06:32:29.055764  1126 caffe.cpp:528] *** Benchmark ends ***


이번에는 10장에 대해 263.516 ms, 즉 1장에 대해서는 0.0263초가 걸렸습니다.  이는 1장씩 테스트했을 때의 결과 0.045초보다 무려 71% 빠른 결과입니다 !  LMS 덕분에 10장씩 batch로 돌리니까 더 빨라진 것이지요.  결국 LMS를 사용하면 오히려 더 빠른 성능을 낼 수도 있는 것입니다.

2017년 8월 2일 수요일

왜 GPU를 이용한 deep learning에 LSF job scheduler가 필요한가 ?


IBM LSF(Load Sharing Facility)는 한마디로 queue 방식의 job scheduler로서, 주로 수퍼컴 클러스터에서 사용되는 SW 제품입니다.  그에 비해 deep learning training 업무는 GPU를 몇 장 장착한 1대의 서버 또는 desktop workstation에서 수행하는 것이 보통이므로 LSF와는 잘 어울리지 않는다고 생각하기 쉽습니다.

그러나 그렇지 않습니다.  GPU를 사용하는 deep learning training 업무야말로 LSF를 이용할 때 볼 수 있는 혜택이 무척 큰 업무입니다.  이유는 무엇보다 GPU 자원이 비싼 것에 비해 정작 활용률이 낮기 때문입니다.

전통적으로 deep learning training은 연구원들이 개인별로 혹은 팀별로 구매한 서버나 워크스테이션 1~2대에서 수행해왔습니다.  최근의 AI 붐으로 인해 점점 더 많은 연구원들이 더 많은 training을 하게 되었고, 이로 인해 기업이나 연구소에서는 연구원들의 요청에 따라 더 많은 서버를 구매하게 되었습니다.

또한 전통적으로 팀별로 독립된 연구 환경을 선호하는 연구하는 연구원들의 특성상, 다른 팀 또는 다른 연구원들과 하나의 GPU 서버를 공유하여 training을 수행하는 일이 많지는 않았습니다.  더 좋은 GPU 서버 1대를 사서 공유하는 것 보다는 좀더 낮은 사양의 GPU 서버를 2대 사서 각각 1대씩 따로 운용하는 것을 선호했지요.

문제는 그러다보니 GPU 서버들의 구매 비용은 말할 것도 없고, 전기사용량이 많고 소음과 발열량도 많은 GPU 서버들의 관리에 문제가 생기기 시작했다는 점입니다.  더 큰 문제는 그렇게 많은 비용을 들여 사들인 GPU 서버들의 활용률이 생각보다 매우 낮다는 것입니다.

아무리 열심히 연구활동을 하는 연구원들이라고 해도, 24시간 계속 끊이지 않고 뭔가 모델을 training시키지는 않습니다.  새로운 연구 논문도 읽어야 하고, data labeling 작업도 감독해야 할테니까요.  그러다보니 A 연구실에서는 GPU 자원이 부족하여 GPU 서버를 사야하는데 바로 옆 방의 B 연구실에서는 GPU 서버가 그냥 놀면서 전기만 먹어대는 경우도 많습니다.  더 최악인 것은, 바로 다음 달이 되면 A 연구실의 GPU가 놀 때 B 연구실에서는 GPU가 부족하다고 GPU 서버를 1대 더 사달라고 하는 경우지이요.

이런 문제를 해결해주는 것이 IBM LSF입니다.  LSF는 GPU 자원을 모니터링하며 관리하다가, 연구원들이 queue에 submit하는 training job들을 최적의 GPU에 알아서 배치해 줍니다.  그럼으로써 전체적인 GPU 자원이 낭비되는 일 없이 활용률을 높여주고, 또 연구원들도 훨씬 편리하게 작업을 수행할 수 있게 해줍니다.

아래의 두가지 시나리오를 보시면 쉽게 이해가 되실 것입니다.

#1.  당장 급하게 2개의 GPU를 이용하여 돌려야 하는 training이 있습니다.  그런데 이미 김박사님이 4개의 GPU를, 이박사님이 3개의 GPU를 이용하여 뭔가를 돌리고 계십니다.  두분께 전화를 걸어 여쭤보니 김박사님은 몇시에 끝날지 잘 모르겠다고 하시고, 이박사님은 아마 새벽 2시쯤 끝날 것이라고 말씀하십니다.   과연 저는 새벽 2시까지 기다렸다가 이박사님의 job이 끝나는 것을 확인한 뒤에 제 training 작업을 걸어놓고 퇴근해야 하나요 ?




#2.  연구원 A는 GPU 4장이 장착된 서버가 현재 놀고 있는 것을 확인했습니다.  그래서 GPU 2장, 즉 gpu0과 gpu1을 이용하여 caffe training을 하나 돌렸습니다.  그런데, 하필 거의 동시에 연구원 B도 이 서버가 놀고 있다고 생각하고는 역시 gpu0과 gpu1을 이용하여 caffe training을 걸었습니다.   과연 어떻게 될까요 ?  매번 누가 이 서버에서 몇번 GPU를 이용해서 돌리려는지 확인한 뒤 job script를 수정해야 할까요 ?  




결론적으로, 약간의 비용을 들여서 LSF를 구축하시는 것이 무작정 GPU 서버를 더 사는 것보다 훨씬 더 효율이 높으며, 이는 비단 GPU 서버 HW 구매 비용 뿐만 아니라 상면과 전력소비량, 공조기 비용 등의 절감에 큰 도움이 됩니다.   또한, 현업 연구원들 입장에서도 훨씬 더 편리하고 마음 편하게 연구 활동에만 전념할 수 있다는 장점이 있습니다.  

LSF를 이용한 deep learning의 가장 분명한 reference는 바로 IBM 자신의 Poughkeepsie 벤치마크 센터의 수퍼컴입니다.   거기서의 LSF 사용이 얼마나 간단한지는 여기서 확인하시기 바랍니다.

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 환경이 준비된 것입니다.