不會裝cuda配環境的小學生怎麼躺擼caffe

本文首發於個人博客 不會裝cuda配環境的小學生怎麼躺擼caffe
收錄於簡書專題[深度學習·計算機視覺與機器學習](http://www.jianshu.com/c/1249336e61cb)

DL如今已經快成爲全民玄學了,感覺離民科入侵不遠了。唯一的門檻可能是環境不好配,特別是caffe這種依賴數10種其它軟件打框架。不過有了docker和k8s之後,小學生也能站擼DL了。

enter description here

從nvidia-docker到docker,需要有這樣的操作

大致流程如下,入門版通過docker pull一個GPU版本的caffe 的image,然後安裝nvidia-docker 和 nvidia-docker-plugin 來映射宿主機的nvidia-driver並通過共享volume的方式來支持容器裏面能“看到”宿主機的GPU。進階版通過curl -s命令列出宿主機的配置顯卡配置,並通過docker run的方式來啓動。總結完成。紙上得來終覺淺,絕知此事要躬行,光說不練空把式,唯有實踐出真知。
tensorflow gpu in docker

IntelMPI:適用於單機多卡,Ethernet或者InfiniBand網絡。IntelMPI比Mvapich速度更快,對GPU更友好,沒有Mvapich中常遇到的CudaMemCpyAsync錯誤

使用nvidia-docker

sudo nohup nvidia-docker-plugin >/tmp/nvidia-docker.log &  
然後nvidia-docker run

使用docker來代替 nvidia-docker

curl -s http://localhost:3476/docker/cli

下面應該是輸出:

--device=/dev/nvidiactl --device=/dev/nvidia-uvm --device=/dev/nvidia7 --devic/dev/nvidia6 --device=/dev/nvidia5 --device=/dev/nvidia4 --device=/dev/nvidia3 --device=/dev/nvidia2 --device=/dev/nvidia1 --device=/dev/nvidia0 --volume-driver=nvidia-docker --volume=nvidia_driver_375.39:/usr/local/nvidia:ro

這樣其實

sudo docker run -ti `curl -s http://localhost:3476/v1.0/docker/cli` -v /mnt/share:/mnt/share -v /mnt/lustre:/mnt/lustre  -v /lib64:/lib64 鏡像名 bash

所以如果你想用docker的方式來運行GPU版本 那麼你就需要指明你的所有的device信息,如果卸載rc文件裏,那麼只能這樣

sudo docker run -ti --device=/dev/nvidiactl --device=/dev/nvidia-uvm --device=/dev/nvidia7 --device=/dev/nvidia6 --device=/dev/nvidia5 \
--device=/dev/nvidia4 --device=/dev/nvidia3 --device=/dev/nvidia2 --device=/dev/nvidia1 --device=/dev/nvidia0 \
--volume-driver=nvidia-docker --volume=nvidia_driver_375.39:/usr/local/nvidia:ro \
 -v /mnt/share:/mnt/share -v /mnt/lustre:/mnt/lustre  -v /lib64:/lib64  鏡像名  bash

當前你也可以有這樣風騷的走位

sudo docker run -ti  $(ls /dev/nvidia* | xargs -I{} echo '--device={}')   -v /mnt/share:/mnt/share -v /mnt/lustre:/mnt/lustre  -v /lib64:/lib64  鏡像名  bash

在鏡像裏安裝ipython notebook,需要這樣做

把大象裝進冰箱分四步,映射端口,開通open-ssh服務器,安裝jupyter,配置密碼
在鏡像中執行
0. 映射端口
在dock run的時候加-p參數
1. 開通ssh

sudo apt-get install openssh-server
  1. 安裝jupyter
apt-get update
#安裝python dev包
apt-get install python-dev
#安裝jupyter
pip install jupyter
  1. 設置密碼
    分三小步
    a. 生成jupyter配置文件,這個會生成配置文件.jupyter/jupyter_notebook_config.py
jupyter notebook --generate-config

b. 從密碼到ssa密文
在命令行輸入ipython,進入ipython命令行

#使用ipython生成密碼
In [1]: from notebook.auth import passwd
In [2]: passwd()
Enter password: 
Verify password: 
Out[2]: 'sha1:38a5ecdf288b:c82dace8d3c7a212ec0bd49bbb99c9af3bae076e'
````
c. 改配置




<div class="se-preview-section-delimiter"></div>

去配置文件.jupyter/jupyter_notebook_config.py中修改以下參數

c.NotebookApp.ip=’*’ #綁定所有地址
c.NotebookApp.password = u’剛纔生成的密文也就是sha1:38a5ecdf288b:c82dace8d3c7a212ec0bd49bbb99c9af3bae076e’
c.NotebookApp.open_browser = False #啓動後是否在瀏覽器中自動打開,注意F大寫
c.NotebookApp.port =8888 #指定一個訪問端口,默認8888,注意和映射的docker端口對應


然後執行ipython notebook --allow-root就可以在宿主機上用docker裏面的環境了,爽歪歪。

[把jupyter-notebook裝進docker裏](https://segmentfault.com/a/1190000007448177)





<div class="se-preview-section-delimiter"></div>

# 使用k8s與腳本一鍵式訓練網絡




<div class="se-preview-section-delimiter"></div>

## 簡要介紹

用k8s啓動docker能夠有效的管理宿主機資源,保證任務能夠在資源許可的情況下順利地執行,同時能夠保護宿主機的安全。但是從使用k8s到訓練網絡這中間隔着幾十步的操作、配置和交互,需要遵循相應的順序和格式,比較繁瑣。這裏通過一種expect腳本的方式簡化這種操作,讓用戶可以簡潔而又正確的使用k8s.





<div class="se-preview-section-delimiter"></div>

## 操作步驟

1. 根據需要的資源修改yaml文件
2. 修改執行腳本里的資源和網絡文件位置
3. 執行expect腳本




<div class="se-preview-section-delimiter"></div>

## 修改yaml文件

這裏舉個例子並有一些注意事項。

下面的yaml文件使用RC的方式創建pod

 ```yaml
 apiVersion: v1
kind: ReplicationController
metadata:
  name: xxx-master2
  labels:
    name: xxx-master2
spec:
  replicas: 2
  selector:
    name: xxx-master2
  template:
    metadata:
      labels:
        name: xxx-master2
    spec:
      nodeSelector:
        ip: five
      containers:
      - name: xxx-master
        image: 10.10.31.26:5000/xxx_cuda8:2.1
        #command: ["/bin/sleep", "infinity"]
        #securityContext:
        #  privileged: true
        ports:
        - containerPort: 6380
        imagePullPolicy: IfNotPresent
        resources:
          requests:
            alpha.kubernetes.io/nvidia-gpu: 1
            #cpu: 2
            #memory: 30Gi
          limits:
            alpha.kubernetes.io/nvidia-gpu: 1
            #cpu: 2
            #memory: 30Gi
        volumeMounts:
        - mountPath: /usr/local/nvidia/
          name: nvidia-driver
          readOnly: true
        - mountPath: /mnt/lustre/xxx/xxx/
          name: sensenet
        - mountPath: /mnt/lustre/share/
          name: share
      volumes:
      - hostPath:
          path: /var/lib/nvidia-docker/volumes/nvidia_driver/375.39
        name: nvidia-driver
      - hostPath:
          path: /mnt/lustre/xxx/xxx
        name: xxx
      - hostPath:
          path: /mnt/lustre/share/
        name: share
 ```
需要注意的是:





<div class="se-preview-section-delimiter"></div>

```yaml
  nodeSelector:
      ip: five

表示選擇標籤爲ip=five的結點,這句話也可以不要。注意後面的resource的gpu數不要超過物理機GPU總數。

修改expect腳本

注意這個expect 腳本運行前需保證各個pod都處於running 狀態。

xxx-pod-cfg.exp腳本內容

#!/usr/bin/expect -f

# 設置超時時間

set timeout 30000
# 設置GPU個數
set gpuNum 1
# 創建rc創建pod
exec kubectl create -f /mnt/lustre/xxx/xxx/yaml/xxxx_cuda_controller_test.yaml
sleep 10

# 首先通過k8s獲得每個pod的ip與hostname對 放在一個臨時文件中
exec kubectl get po -o=custom-columns=IP:status.podIP,NAME:.metadata.name >hehe

# 接下來把每個ip對 放在數組裏面去
set fd [open "hehe" r]
gets $fd line
set numIp 0
while { [gets $fd line] >= 0 } {
        set ips($numIp) [ lindex $line 0 ]
        set hns($numIp) [ lindex $line 1 ]
        incr numIp
        #puts $numIp
}
#puts $ips(1)

# 接下來登錄每個pod上面去修改hosts文件
for {set i 0} {$i<$numIp} {incr i} {
        set sshIp $ips($i)
        set sshUrl "root@"
        append sshUrl $sshIp
        # 連接上這個pod
        spawn ssh $sshUrl
        # 修改這個pod的文件
        expect "password:"
        send "12345678\r"
        # 下面把ip數組複製進文件裏面
        for { set j 0} {$j<$numIp} {incr j} {
                set ip $ips($j)
                set hn $hns($j)
                append ip " " $hn
                expect "#*"
                send "echo $ip >> /etc/hosts\r"
        }
        # 如果有必要的話,可以在這裏設置mpirun的位置
        expect "#*"
        send "exit\r"
        expect eof
}

# 下面生成第一個pod的key,並且copy到其它的pod裏面.
set sshIp $ips(0)
set sshUrl "root@"
append sshUrl $sshIp
spawn ssh $sshUrl
expect "password:"
send "12345678\r"

expect "#*"
send "ssh-keygen \r"
expect "id_rsa):*"
send "\r"
expect "passphrase):*"
send "\r"
expect "again:*"
send "\r"

# 接下來保證第一個pod能ssh連上其它所有的pod
for {set i 1} {$i<$numIp} {incr i} {
        set cmd "ssh-copy-id -i ~/.ssh/id_rsa.pub "
        set ip $ips($i)
        append cmd $ip
        expect "#*"
        send "$cmd \r"
        expect "yes/no)?*"
        send "yes\r"
        expect "password:*"
        send "12345678\r"
}


# 下面製作hostfile文件,把ip數組寫進文件裏
expect "#*"
send "cd /root\r"
for {set i 0} {$i<$numIp} {incr i} {
        #set content $ips($i)
        expect "#*"
        send "echo $ips($i) >>hostfile\r"
}

# 下面開始訓練resnet200
expect "#*"
send "/mnt/lustre/share/intel64/bin/mpirun -n $numIp -ppn $gpuNum -f hostfile -env I_MPI_FABRICS shm:tcp /mnt/lustre/xxx/xxx/example/build/tools/caffe train --solver=/mnt/lustre/xxx/xxx/example/resnet200/resnet200_solver.prototxt\r"


expect eof
exit
  1. 下面這句表示超時時間,設置大一點比較好,不然可能提前結束
  set timeout 30000 
  1. 下面這句是每個pod裏面的GPU數量,根據實際情況自己設置
  set gpuNum 1
  1. 創建rc來創建pod ,sleep10保證在執行下一句之前pod能處於running 狀態,根據需要時間可以調長
exec kubectl create -f /mnt/lustre/xxx/xxxx/yaml/xxx_cuda_controller_test.yaml 
sleep 10
  1. 下面是獲取ip與hostname對
  exec kubectl get po -l name==sensenet-master2  -o=custom-columns=IP:status.podIP,NAME:.metadata.name >hehe

-l後面跟的是你要獲取的pod的過濾器,也就是label的值,這裏我是用上面rc創建的兩個pod,自動給pod打標籤爲name=xxx-master2,所以這裏這樣寫。

  1. 訓練網絡的例子,根據自己需要進行修改
  send "/mnt/lustre/share/intel64/bin/mpirun -n $numIp -ppn $gpuNum -f hostfile -env I_MPI_FABRICS shm:tcp /mnt/lustre/xxx/xxx/example/build/tools/caffe train --solver=/mnt/lustre/xxx/xxxx/example/resnet200/resnet200_solver.prototxt\r"

執行expect腳本

   expect sensenet-pod-cfg.exp

自己製作的某個支持cuda的dockerfile

FROM 10.10.31.26:5000/nvidia/cuda:8.0-cudnn5-runtime-centos7
# 作者
MAINTAINER xxx "xxx.com"
# 先安裝一批需要的軟件
COPY local_base.repo  /etc/yum.repos.d/local_base.repo
COPY requirements.txt /root/requirements.txt
COPY sshd_config /etc/ssh/sshd_config

RUN yum clean all -y && yum clean metadata -y \
        && yum clean dbcache -y && yum makecache -y \
        && yum update -y \
        && yum install -y  \
        boost boost-devel \
        glog glog-devel \
        protobuf protobuf-devel protobuf-python \
        hdf5-devel hdf5 \
        openssh-server \
        lmdb-devel lmdb \
        leveldb leveldb-devel \
        opencv opencv-devel opencv-python \
        openblas-devel openblas \
        && echo 'root:12345678' | chpasswd \
        && yum clean all \
        && ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key \
        && ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key \
        && ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key \
        && ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key \
        && mkdir /var/run/sshd

參考資料

把jupyter-notebook裝進docker裏
tensorflow gpu in docker

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章