作者简介Carlos,他是跨领域的技术顾问,在在GNU/Linux系统方面拥有超过25年的经验。
边缘计算AI概念验证
几年来,我一直与那些希望在产品(大多是嵌入式设备)中使用人工智能(AI)的客户合作,但是最终都需要依赖云端。随着近年来嵌入式平台的发展,这种依赖逐渐消失,产生了边缘AI。
基于我在这一领域的专业背景和个人兴趣,我想创建一个具备以下要求的最先进的概念验证平台:
使用GPU加速的标准和低成本的硬件
尽可能采用标准和开源的软件
在硬件和软件层面都易于扩展(工作负载分配和资源优化)
易于和现有的云基础设施和工具集成
构建、测试和部署都轻松有趣
要实现这一目标,我们将创建一个低成本边缘AI集群,并借助NVIDIA Jetson Nano嵌入式开发套件(ARM64架构)实现GPU加速功能。此外,我们将会了解如何使用K3s轻松将这些设备与云中现有的集群进行管理和集成。
什么是边缘AI?
首先,我们需要了解什么是边缘AI。在边缘AI中,人工智能算法将会在硬件设备上处理,无需外部要求。该算法使用的数据在设备或本地集群上。因此设备可以使用边缘AI处理数据并作出决策而不需要与外部连接。
与传统的云端技术相比,这种方法有一些明显的优势:
可以在无法与外部连接的情况下工作,此外对外部时间和成本不产生依赖性
由于系统没有外部依赖性,执行任务时延迟较低
数据在本地处理,因此避免了存储和云端流的问题,减少了对隐私和安全方面的依赖.
平台选择:NVIDIA Jetson开发板和K3s
NVIDIA的Jetson系列的单片系统(SoCs)由于采用ARM架构、支持GPU和CUDA,是AI项目、机器人和GPU边缘计算中应用最广泛的开发板。通过Kubernetes技术(如K3s),我们可以进行集群训练和推理,并且可以根据需要添加节点,不需要外部依赖。
对于此次PoC,我选择了2个Jetson Nano开发板,使用Tensorflow(所以GPU支持是必须的)实现实时对象跟踪和识别算法。虽然在传统的Kubernetes集群上,2个节点可能不足以满足生产环境的需求,但Nano开发板的可扩展性很简单,只需要在操作系统进行最小的改动即可。有了这个设置,复制mSD卡和使用SSD硬盘都很容易。
在架构层面,我们将使用K3s以及Docker、ctr和kubectl等标准工具。
Docker是目前最知名的运行时解决方案并且在开发环境中被普遍使用。然而,2020年12月 Kubernetes 官方宣布将在Kubernetes 1.20+版本中废弃Docker作为默认容器运行时,containerd将成为替代方案。当这一方案成为行业标准时,它将变得更加强大。值得一提的是,containerd是K3s默认的容器运行时。
对于GPU的支持,我们将使用L4T最新版本中可用的NVDIA工具和库,包括nvidia-container-runtime、deviceQuery和TensorFlow。NVIDIA使用nvidia-container-runtime在Docker中支持GPU加速已经有一段时间了。2021年2月,他们宣布用他们的runc runtime实现nvidia-container-runtime为containerd提供同样的GPU加速支持。在这个PoC中,我们将使用这两个运行时。Docker用于测试GPU独立容器(作为开发环境),containerd用于Kubernetes集群(作为生产环境),两者都支持GPU,使用nvidia-container-runtime。
基本要求
硬件
2块Nvidia Jetson Nano开发板
2个microSD卡(建议使用64G)
2块电源(5V/4W)
2台风扇
1个集群用例(可选)
2个jumper(用于切换到高功率模式)
软件
JetPack:4.5
L4T:32.5.0(包括JetPack 4.5)
K3s:v1.19.7+k3s1(arm64版本)
Kubectl:v1.20(arm64版本)
请注意:所有脚本、配置文件以及docker镜像都可以在下方链接中找到:
https://github.com/xiftai/jet...
https://hub.docker.com/orgs/x...
让我们开始吧!
流程步骤
Linux基础系统设置
要部署集群,需要按照如下要求在所有开发板上配置操作系统:
8GB Swap空间
你可以从repo中获取JetsonHacks的setSwapMemorySize脚本:
https://github.com/JetsonHack...
./setSwapMemorySize.sh -g 8
4W功率模式
连接J48 jumper(参见《Jetson Nano开发者套件用户指南》的电源指南部分)。给开发板通电,然后运行。
https://developer.nvidia.com/...
sudo nvpmodel -m 0
在/etc/hosts文件上添加每个IP和hostname,在本例中(仅2个开发板)地址如下:
192.168.0.34 jetson1
192.168.0.35 jetson2
禁用IPv6:
sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1
sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1
sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=1
nVidia Docker Support
预安装L4T镜像。我们可以检查它是否工作:
nvidia-container-runtime --version
进行了这些更改之后,操作系统已经为下一步做好准备。
部署K3s
请注意:如果没有特别说明,这些命令都将在所有Jetson开发板上执行。
下载K3s和kubectl
首先下载K3s和kubectl的ARM64二进制文件并且使用执行权限将它们复制到/usr/local/bin:
sudo wget -c "https://github.com/k3s-io/k3s/releases/download/v1.19.7%2Bk3s1/k3s-arm64" -O /usr/local/bin/k3s ; chmod 755 /usr/local/bin/k3s
sudo wget -c "https://dl.k8s.io/v1.20.0/kubernetes-client-linux-arm64.tar.gz" -O /usr/local/bin/kubectl ; chmod 755 /usr/local/bin/kubectl
设置K3s
请注意:有关config.yaml文件中使用的参数的更多信息,请参阅Rancher K3s server和agent配置参考指南:
https://docs.rancher.cn/docs/...
Master(jetson1)
/etc/rancher/k3s/config.yaml
node-ip: 192.168.0.34
token: PRE_SHARED_TOKEN_KEY
Agent(jetson2)
/etc/rancher/k3s/config.yaml
node-ip: 192.168.0.35
server: https://192.168.0.34:6443
token: PRE_SHARED_TOKEN_KEY
我们可以在使用三块或更多开发板的情况下,将用于jetson2节点的相同配置应用于集群中的其他节点。
启动K3s节点:
Server节点
Master(jetson1)
k3s server -c /etc/rancher/k3s/config.yaml
K3s创建两个重要的文件,我们可以根据自身需求进行修改:
/etc/rancher/k3s/k3s.yaml
以上就是我们使用kubectl工具与集群交互所需要的Kubernetes配置文件。我们可以在任何一台装有kubectl的计算机中使用这个文件与Kubernetes集群进行交互,方法是在调用kubectl之前将服务器URL改为http://192.168.0.34:6443(我们的jetson1地址),并将KUBECONFIG环境路径设置为这个文件。
/var/lib/rancher/k3s/agent/etc/containerd/config.toml
以上是containerd的配置文件,它是在每次K3s启动时生成的,所以根据K3s高级选项和配置指南(https://docs.rancher.cn/docs/...),我们可以将它复制到同一路径的一个名为config.toml.tmpl的模板文件上,然后使用它来代替。我们需要修改这个模板文件,在containerd中添加nvidia-container-runtime的支持,在文件末尾添加接下来的几行内容,并重新启动K3s,让更改生效:
[plugins.cri.containerd.runtimes.runc]
runtime_type = "io.containerd.runtime.v1.linux"
[plugins.linux]
runtime = "nvidia-container-runtime"
Agent node(s)
Agent(jetson 2)
k3s agent -c /etc/rancher/k3s/config.yaml
如果你使用了3个及以上的开发板,在剩余的开发板上也使用同样的命令即可。
检查K3s集群状态和日志
使用可以使用以下命令借助kubectl来检查集群状态:
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl get nodes
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl get pods --all-namespac
测试GPU支持
我们将使用deviceQuery NVIDIA测试应用程序(包含在L4T内)来检查我们是否可以在集群内访问GPU。首先,我们将使用一个合适的软件创建一个Docker镜像,将它直接作为Docker运行,然后使用containerd ctr运行,最后在Kubernetes集群本身运行。
Test 1:使用GPU支持在Docker上运行deviceQuery
将deviceQuery所在的Demo复制到将要创建Docker镜像的工作目录中:
cp -R /usr/local/cuda/samples .
然后为deviceQuery镜像创建Dockerfile,如下所示:
Dockerfile.devicequery
FROM nvcr.io/nvidia/l4t-base:r32.5.0
RUN apt-get update && apt-get install -y --no-install-recommends make g++
COPY ./samples /tmp/samples
WORKDIR /tmp/samples/1_Utilities/deviceQuery
RUN make clean && make
CMD ["./deviceQuery"]
构建镜像(可以根据你的喜好更改tag名称):
docker build -t xift/jetson_devicequery:r32.5.0 . -f Dockerfile.deviceQuery
最后,使用NVIDIA运行时支持运行Docker容器:
docker run --rm --runtime nvidia xift/jetson_devicequery:r32.5.0
如果一切顺利(Result=PASS),我们就可以将镜像推送到Docker Hub仓库,以进行下一步的工作。
你可以访问下方链接获取我们已经创建完成的镜像:
https://hub.docker.com/reposi...
Test 2:使用GPU支持在containerd上运行deviceQuery
既然K3s使用containerd作为默认的容器运行时,那么我们将使用ctr命令行在containerd上来测试和部署我们推送的deviceQuery镜像,脚本如下:
!/bin/bash
IMAGE=xift/jetson_devicequery:r32.5.0
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
ctr i pull docker.io/${IMAGE}
ctr run --rm --gpus 0 --tty docker.io/${IMAGE} deviceQuery
如果一切进行得当(Result=PASS,与Docker相同),我们可以进行最后的测试:在K3s集群上运行Pod。
最终测试:在K3s集群上运行deviceQuery
对于最终测试,我们将创建一个用于在集群上部署的 pod 文件:
pod_deviceQuery.yaml
apiVersion: v1
kind: Pod
metadata:
name: devicequery
spec:
containers:
- name: nvidia
image: xift/jetson_devicequery:r32.5.0
command: [ "./deviceQuery" ]
然后使用kubectl在集群上部署:
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl apply -f ./pod_deviceQuery.yaml
我们可以使用以下kubectl命令来检查一切是否正常:
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl describe pod devicequery
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl logs devicequery
如果一切顺利(Result=PASS),那么我们可以用nodeName参数在第二个节点(jetson2)上强制部署:
pod_deviceQuery_jetson2.yaml
apiVersion: v1
kind: Pod
metadata:
name: devicequery
spec:
nodeName: jetson2
containers:
- name: nvidia
image: xift/jetson_devicequery:r32.5.0
command: [ "./deviceQuery" ]
然后使用kubectl在集群上部署:
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl apply -f ./pod_deviceQuery_jetson2.yaml
检查一切是否在jetson2上顺利运行:
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl describe pod devicequery
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl logs devicequery
如果一切顺利,恭喜你!你获得了一个拥有GPU支持的K3s Kubernetes集群。(尽管只是一个测试镜像,但依旧很棒!)
Tensorflow GPU支持
为什么要止步于一个测试镜像呢?对于实际用例,我们需要一个完全支持Tensorflow GPU的镜像。TensorFlow是目前生产环境中应用最广泛的机器学习软件平台。在写这篇文章的时候,L4T r32.5的官方Tensorflow Docker镜像还没有提供,所以我们需要自己构建。
使用GPU支持构建和运行Tensorflow Docker镜像
Dockerfile.tf
FROM nvcr.io/nvidia/l4t-base:r32.5.0
RUN apt-get update -y
RUN apt-get install python3-pip -y
RUN pip3 install -U pip
RUN DEBIAN_FRONTEND=noninteractive apt-get install libhdf5-serial-dev hdf5-tools libhdf5-dev zlib1g-dev zip libjpeg8-dev liblapack-dev libblas-dev gfortran -y
RUN DEBIAN_FRONTEND=noninteractive apt-get install python3 python-dev python3-dev build-essential libssl-dev libffi-dev libxml2-dev libxslt1-dev zlib1g-dev -yq
RUN pip install -U Cython
RUN pip install -U testresources setuptools==49.6.0
RUN pip install numpy==1.16.1 h5py==2.10.0
RUN pip install future==0.18.2 mock==3.0.5 keras_preprocessing==1.1.1 keras_applications==1.0.8 gast==0.2.2 futures protobuf pybind11
RUN pip3 install -U grpcio absl-py py-cpuinfo psutil portpicker gast astor termcolor wrapt google-pasta
RUN pip3 install --pre --extra-index-url https://developer.download.nv... tensorflow
构建:
docker build -t xift/l4t-tensorflow:r32.5.0-tf2.3.1-py3 -f Dockerfile.tf
在Docker上运行(和之前一样):
docker run -ti --rm --runtime nvidia xift/l4t-tensorflow:r32.5.0-tf2.3.1-py3
最后,使用以下命令检查Tensorflow GPU支持:
python3 -c "from tensorflow.python.client import device_lib; print(device_lib.list_local_devices());"
在K3s集群上部署TensorFlow
我们创建一个pod yaml文件用于部署:
pod_tf.yaml
apiVersion: v1
kind: Pod
metadata:
name: tf
spec:
containers:
- name: nvidia
image: xift/l4t-tensorflow:r32.5.0-tf2.3.1-py3
command: [ "sleep" ]
args: [ "1d" ]
与之前一样运行:
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl apply -f pod_tf.yaml
检查它是否正常工作:
KUBECONFIG=/etc/rancher/k3s/k3s.yaml kubectl exec -it tf -- /bin/bash
python3 -c "from tensorflow.python.client import device_lib; print(device_lib.list_local_devices());"
如果一切都没问题,我们可以看到TensorFlow的GPU检测是正确的,和之前的例子一样。
那么现在我们已经有了一个功能完备的边缘人工智能集群,并且支持Tensorflow和GPU。现在我们可以在任何Docker文件中使用xift/l4t-tensorflow:r32.5.0-tf2.3.1-py3基础镜像来部署我们的python代码,并轻松运行它。
总结及展望
我们已经证明,利用NVIDIA L4T工具、轻量级Kubernetes发行版K3s和基本的容器技术来在低成本、高性能的NVDIA开发板上创建一个可扩展的边缘AI集群是具备可行性的。
那么在接下来的文章中,我将为一个采用了深度学习的用例在集群中提供分布式训练和推理代码。此外,在架构层面还有其他一些有趣的尝试,比如使用Wireguard创建低延迟的分布式集群,以及采取综合的方法(在云端训练,在边缘部署)等。
欢迎关注我们,及时获取K3s、边缘计算相关的教程哟!