本文首發於個人博客 不會裝cuda配環境的小學生怎麼躺擼caffe
收錄於簡書專題[深度學習·計算機視覺與機器學習](http://www.jianshu.com/c/1249336e61cb)
DL如今已經快成爲全民玄學了,感覺離民科入侵不遠了。唯一的門檻可能是環境不好配,特別是caffe這種依賴數10種其它軟件打框架。不過有了docker和k8s之後,小學生也能站擼DL了。
從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
- 安裝jupyter
apt-get update
#安裝python dev包
apt-get install python-dev
#安裝jupyter
pip install jupyter
- 設置密碼
分三小步
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
- 下面這句表示超時時間,設置大一點比較好,不然可能提前結束
set timeout 30000
- 下面這句是每個pod裏面的GPU數量,根據實際情況自己設置
set gpuNum 1
- 創建rc來創建pod ,sleep10保證在執行下一句之前pod能處於running 狀態,根據需要時間可以調長
exec kubectl create -f /mnt/lustre/xxx/xxxx/yaml/xxx_cuda_controller_test.yaml
sleep 10
- 下面是獲取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,所以這裏這樣寫。
- 訓練網絡的例子,根據自己需要進行修改
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