嵌入式系统 树莓派3B 实现通过GPU加速pytorch计算
本项目仅用于学术用途
总思路
- 交叉编译环境为raspberry pi 3B docker镜像
- 使用github上开源的GPU库QPULib
- 在树莓派上安装pytorch,通过交叉编译加速过程
- 通过pytorch提供的接口编译并且注册c++端的程序
- 通过nfs mount docker镜像的file system,直接运行pytorch
具体实现
1. 安装交叉编译环境
具体过程收到这篇博客启发,找到这篇博客的过程如下
- 在搜索引擎搜索预编译的树莓派3B python wheel,搜索到pytorch论坛中的这条帖子
- 在这条帖子中,名为choonkiatlee的用户提到了其编译好的wheel以及编译使用的docker镜像
- 在这名用户的github博客界面中,找到了上述文章
在我的主机(windows电脑上)安装docker for windows,并进行一系列镜像加速、托管配置
托管服务
-
登录阿里云,点击容器镜像服务
-
选择个人实例
-
在仓库管理中创建个人命名空间,我的情况下是ja1zhou
-
创建访问凭证,选择固定密码,这个密码为本地进行登录时进行验证的密码
-
在主机上启动docker engine,并在终端输入
sudo docker login --username=your_username registry.cn-beijing.aliyuncs.com #之后输入设置的密码
成功登录阿里云
-
在镜像仓库中创建新的仓库名称,我的情况下为myrasp
镜像加速
-
根据阿里云官方文档进行镜像加速配置
-
直接修改docker for windows的daemon.json文件,添加
{ "registry-mirrors": ["https://your_server_name.mirror.aliyuncs.com"] }
拉取工作镜像,配置shared volume,并设置工作环境
-
docker pull choonkiatlee/raspbian:build docker volume create --driver local -o o=bind -o type=none -o device="C:\Users\MyUsername\Downloads\rasp_docker" rasp_docker #这条命令的目的是直接将下载中的诸多文件,比如wheel和QPULib,与docker进行共享 #将torch-1.4.0a0+7f73f1d-cp37-cp37m-linux_armv7l.whl下载并且放置在创建的volume中 docker run -it --name rasp -v rasp_docker:/root/rasp_docker choonkiatlee/raspbian:build
-
进入docker容器之后,需要输入以下的命令进行配置,下述命令是对上文博客的补充
#docker自带的python版本为3.7.3 apt-get update && apt-get install -y python3-numpy #torch运行需要numpy支持 cd /root/rasp_docker pip3 install torch-1.4.0a0+7f73f1d-cp37-cp37m-linux_armv7l.whl
-
进行验证
root@3288e690face:~/rasp_docker# python3 Python 3.7.3 (default, Jan 22 2021, 20:04:44) [GCC 8.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import torch >>> torch.__version__ '1.4.0a0+7f73f1d'
-
torch已经成功安装
-
将这个版本的docker镜像打包,作为后续开发的起点
docker commit rasp registry.cn-beijing.aliyuncs.com/ja1zhou/myrasp:torch docker push registry.cn-beijing.aliyuncs.com/ja1zhou/myrasp:torch
(可选)从源码编译pytorch(暂未成功复现)
-
设置代理,允许代软件通过内网、公网防火墙
-
设置代理软件,允许来自局域网的连接
-
在docker镜像中,设置环境变量
export all_proxy="http://host_ip:host_port"
-
下载并进行准备工作
cd /root git clone https://github.com/pytorch/pytorch.git git checkout v1.4.0 git submodule sync git submodule update --init --recursive apt install -y python3-cffi python3-numpy libatlas-base-dev pip3 install cython wheel pyyaml pillow #选择不编译一切附加模块 export USE_CUDA=0 export USE_CUDNN=0 export USE_MKLDNN=0 export USE_METAL=0 export USE_NCCL=OFF export USE_NNPACK=0 export USE_QNNPACK=0 export USE_DISTRIBUTED=0 export BUILD_TEST=0 export MAX_JOBS=8 python3 setup.py install
-
报错,是与submodule protobuf相关,当时作者也遇到了这个问题,但是不知道他具体编译的时候使用的是protobuf的哪个tag,已经在github上面提交了issue
2. 下载QPULib,根据其提供的功能进行代码编写
QPULib代码结构
QPULib/
Lib/
Subdirectories/
*.cpp
*.h
*.cpp
*.h
Doc/
irrelevant
Tests/
*.cpp
Makefile
QPU加速原理
-
QPU 是由 Broadcom 开发的矢量处理器,其指令可对 32 位整数或浮点值的 16 元素向量进行操作。例如,给定两个 16 元素向量
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
和
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
QPU 的整数加法指令计算第三个向量
30 32 34 36 38 40 42 44 46 48 50 52 54 56 58 60
其中输出中的每个元素是输入中对应的两个元素的总和。
-
每个 16 元素向量由四个四分之一的矢量部分组成。
-
QPU 每个时钟周期处理一个矢量部分,而 QPU 指令需要四个连续的时钟周期来提供完整的16位的结果矢量,这就是“QPU”名称的由来。
-
Pi 总共包含 12 个 QPU,每个都以 250MHz 的频率运行。这是每秒 750M 向量指令的最大吞吐量(250M 周期除以 4 个周期每条指令乘以 12 个QPU)。或者:每秒 12B 次操作(750M 指令乘以 16 个向量元素)。在某些情况下,QPU 指令可以一次提供两个结果,因此 Pi 的 QPU 通常以 24 GFLOPS 进行工作。
-
QPU 是 Raspberry Pi 图形管道的一部分。如果想在 Pi 上制作高效的图形,那么可能需要 OpenGL ES。但是,如果只想尝试加速 Pi 项目的非图形部分,那么 QPULib 值得一看。
-
-
而为了避免计算时候的阻塞,可以通过引入流水线的方式。
- 在计算该次数据的同时,去取下一次计算所需要的数据
-
同时,可以引入多QPU并行计算的方式,及每次采取多QPU计算不同区域的数据,从而实现GPU的高效利用。
-
具体阅读Makefile,发现编译时Include目录为Lib/目录,会先将Lib/下的所有*.cpp编译成*.o
-
最后,将*.o和链接到可执行文件
编译动态链接库
-
按照这个思路,先将所有Lib/下的cpp文件编译为.o文件,并将之编译成动态链接库,省去在向pytorch注册时候的重复编译,将Makefile改写如下
# Root directory of QPULib repository ROOT = ../Lib # Compiler and default flags CXX = g++ CXX_FLAGS = -fpermissive -Wconversion -std=c++0x -I $(ROOT) # Object directory OBJ_DIR = obj # Debug mode ifeq ($(DEBUG), 1) CXX_FLAGS += -DDEBUG OBJ_DIR := $(OBJ_DIR)-debug endif # QPU or emulation mode ifeq ($(QPU), 1) CXX_FLAGS += -DQPU_MODE OBJ_DIR := $(OBJ_DIR)-qpu else CXX_FLAGS += -DEMULATION_MODE endif # Object files OBJ = \ Kernel.o \ Source/Syntax.o \ Source/Int.o \ Source/Float.o \ Source/Stmt.o \ Source/Pretty.o \ Source/Translate.o \ Source/Interpreter.o \ Source/Gen.o \ Target/Syntax.o \ Target/SmallLiteral.o \ Target/Pretty.o \ Target/RemoveLabels.o \ Target/CFG.o \ Target/Liveness.o \ Target/RegAlloc.o \ Target/ReachingDefs.o \ Target/Subst.o \ Target/LiveRangeSplit.o \ Target/Satisfy.o \ Target/LoadStore.o \ Target/Emulator.o \ Target/Encode.o \ VideoCore/Mailbox.o \ VideoCore/Invoke.o \ VideoCore/VideoCore.o # Top-level targets .PHONY: top clean LIB = $(patsubst %,$(OBJ_DIR)/%,$(OBJ)) top: $(LIB) @$(CXX) $(CXX_FLAGS) -shared -fPIC $^ -o libqpu.so clean: rm -rf obj obj-debug obj-qpu obj-debug-qpu rm -f Tri GCD Print MultiTri AutoTest OET Hello ReqRecv Rot3D ID *.o rm -f HeatMap rm -f libqpu.so # Intermediate targets $(OBJ_DIR)/%.o: $(ROOT)/%.cpp $(OBJ_DIR) @echo Compiling $< @$(CXX) -c -o $@ $< $(CXX_FLAGS) %.o: %.cpp @echo Compiling $< @$(CXX) -c -o $@ $< $(CXX_FLAGS) $(OBJ_DIR): @mkdir -p $(OBJ_DIR) @mkdir -p $(OBJ_DIR)/Source @mkdir -p $(OBJ_DIR)/Target @mkdir -p $(OBJ_DIR)/VideoCore
-
通过Makefile文件可知,会将所有Lib/下的文件打包成为libqpu.so动态链接库
-
添加动态链接库到系统库
vim /etc/ld.so.conf #在该文件新增一行,为libqpu.so的路径 /root/embedded_project/ #:wq保存退出 ldconfig#刷新动态库路径
编写调用GPU进行并行计算的C++代码
-
编写流水线式的多qpu并行运算的矩阵c++运算程序
- 该程序主要实现了等大小的矩阵点乘、点加,并将结果返回
//"dot.cpp" #include <torch/extension.h> #include <vector> #include <QPULib.h> #include <stdio.h> #include <stdlib.h> #include <sys/time.h> const int NQPUS = 4; //调用的qpu数 void dotproduct(Int n, Ptr<Float> x, Ptr<Float> y) { Int inc = numQPUs() << 4; Ptr<Float> p = x + index() + (me() << 4); Ptr<Float> q = y + index() + (me() << 4); gather(p); gather(q); Float xOld, yOld; For (Int i = 0, i < n, i = i+inc) gather(p+inc); gather(q+inc);//获取下次运算需要的数据 receive(xOld); receive(yOld);//计算之前拿到的数据 store(xOld * yOld, p); p = p+inc; q = q+inc; End receive(xOld); receive(yOld); } void dotadd(Int n