本文首先将会对Vitis统一软件平台和Vitsi AI进行简单介绍,然后介绍如何在KV260上部署DPU镜像,最后在KV260 DPU镜像上运行Vitis AI自带的图像分类示例。通过本文,你将会对Vitis软件平台、Vitsi AI架构有初步认识,并知道如何在KV260上快速体验Vitsi AI图像分类示例程序。
开始本文的实操环节之前,这里我先介绍一些背景知识,分别是Vitis统一软件平台和Vitis AI。
来自Xilinx官网的简介,Vitis 统一软件平台包括:
来自官网的Vitis统一软件平台架构图:
从官网的介绍页面我们也可以看到,Vitis 统一软件平台包括如下组件:
今天我们将会重点介绍Vitis AI。
本节内容主要参考了自官方github.io文档。
AMD Vitis™ AI 是一个集成开发环境,可用于加速 AMD 平台上的 AI 推理。该工具链提供优化的IP、工具、库、模型以及资源,例如示例设计和教程,可在整个开发过程中为用户提供帮助。它在设计时充分考虑了高效率和易用性,在 AMD 自适应 SoC 和 Alveo 数据中心加速卡上释放了 AI 加速的全部潜力。
Vitis™ AI 解决方案由三个主要组件组成:
Vitis AI 解决方案的打包和交付方式如下:
在KV260开发板上正式体验Vitis AI之前,需要将上一节中提到的DPU镜像下载下来并烧录到SD上。
支持KV260的最新DPU镜像下载链接:https://china.xilinx.com/member/forms/download/design-license-xef.html?filename=xilinx-kv260-dpu-v2022.2-v3.0.0.img.gz
下载完成后,解压压缩包,通过Rufus将解压的wic文件写入SD卡。使用Rufus选择文件时,需要注意将右侧的默认文件类型修改为全部文件,否则默认不支持wic文件:
写入过程显示进度:
完成DPU镜像写入SD卡后,将SD卡读卡器从PC移除后,将SD卡插入到开发板,插好串口线,打开串口终端,波特率设置为115200,就可以准备上电开机了。
启动之后会自动登录root账号(默认密码为root):
插上网线的话,启动后还可以看到输出了dashborad访问链接:
根据ifconfig查看的IP地址,浏览器访问dashboard链接,可以看到实时状态监控:
DPU镜像默认带有SSH服务,并且是开机启动的,因此可以使用MobaXterm的SSH客户端通过网络登录KV260,如下图所示:
MobaXterm的SSH客户端时带有X11-forwarding功能的,支持将远程程序界面通过SSH协议显示在本地。
登录系统后,可以看到,/home/root目录下已经有了两个目录。
使用tree命令,可以看到Vitis-AI目录结构:
接下来我们将会尝试运行vai_runtime下的resnet50示例程序,我们先看看这个目录下的文件结构:
里面有文件的作用分别为:
DPU镜像默认已经安装了gcc,直接运行build.sh就可以编译src/main.cc,并生成resnet50可执行文件。可以尝试将resnet50可执行文件删除掉,再运行build.sh脚本,观察是否重新生成了resnet50。
通过以下命令,下载并解压resnet50测试图片:
cd ~
wget https://china.xilinx.com/bin/public/openDownload?filename=vitis_ai_runtime_r3.0.0_image_video.tar.gz -O vitis_ai_runtime_r3.0.0_image_video.tar.gz
mkdir vitis_ai_runtime_r3.0.0_image_video
tar -C vitis_ai_runtime_r3.0.0_image_video -xzvf vitis_ai_runtime_r3.0.0_image_video.tar.gz
接下来,通过如下命令,运行resnet50示例程序:
cd ~/Vitis-AI/examples/vai_runtime/resnet50
./resnet50 /usr/share/vitis_ai_library/models/resnet50/resnet50.xmodel
运行结果如下:
报错说../images目录找不到。
创建../images目录,并将刚刚下载的resnet50测试图片拷贝到该目录中:
mkdir -v ../images
cp -vr ~/vitis_ai_runtime_r3.0.0_image_video/images/* ../images/
运行输出如下:
再次运行resnet50示例程序:
成功识别了。
命令行第二个参数 /usr/share/vitis_ai_library/models/resnet50/resnet50.xmodel 是resnet50的DPU模型文件,该文件在DPU镜像中已经有了,因此不需要手动下载。
words.txt 文件中是resnet50识别结果的分类标签,可以看到前面几行中包含金鱼(goldfish)分类:
因此,可以找一个金鱼图片进行测试。
随便找了一张:
通过MobaXterm左侧边栏的上传功能传到开发板上:
默认上传位置为HOME目录(~)。
接下来,将金鱼图片拷贝到../images目录,并将原来的测试图片删除掉,重新运行resnet50示例程序,可以看到成功识别了金鱼:
接下来我们看看resnet50目录下的src/main.cc文件内容。
首先是main函数:
/**
* [url=home.php?mod=space&uid=2666770]@Brief[/url] Entry for runing ResNet50 neural network
*
* [url=home.php?mod=space&uid=1902110]@NOTE[/url] Runner APIs prefixed with "dpu" are used to easily program &
* deploy ResNet50 on DPU platform.
*
*/
int main(int argc, char* argv[]) {
// Check args
if (argc != 2) {
cout << "Usage of resnet50 demo: ./resnet50 [model_file]" << endl;
return -1;
}
auto graph = xir::Graph::deserialize(argv[1]);
auto subgraph = get_dpu_subgraph(graph.get());
CHECK_EQ(subgraph.size(), 1u)
<< "resnet50 should have one and only one dpu subgraph.";
LOG(INFO) << "create running for subgraph: " << subgraph[0]->get_name();
/*create runner*/
auto runner = vart::Runner::create_runner(subgraph[0], "run");
// ai::XdpuRunner* runner = new ai::XdpuRunner("./");
/*get in/out tensor*/
auto inputTensors = runner->get_input_tensors();
auto outputTensors = runner->get_output_tensors();
/*get in/out tensor shape*/
int inputCnt = inputTensors.size();
int outputCnt = outputTensors.size();
TensorShape inshapes[inputCnt];
TensorShape outshapes[outputCnt];
shapes.inTensorList = inshapes;
shapes.outTensorList = outshapes;
getTensorShape(runner.get(), &shapes, inputCnt, outputCnt);
/*run with batch*/
runResnet50(runner.get());
return 0;
}
其中,
接下来我们看看runReset50函数:
/**
* @brief Run DPU Task for ResNet50
*
* [url=home.php?mod=space&uid=3142012]@param[/url] taskResnet50 - pointer to ResNet50 Task
*
* [url=home.php?mod=space&uid=1141835]@Return[/url] none
*/
void runResnet50(vart::Runner* runner) {
/* Mean value for ResNet50 specified in Caffe prototxt */
vector<string> kinds, images;
/* Load all image names.*/
ListImages(baseImagePath, images);
if (images.size() == 0) {
cerr << "\nError: No images existing under " << baseImagePath << endl;
return;
}
/* Load all kinds words.*/
LoadWords(wordsPath + "words.txt", kinds);
if (kinds.size() == 0) {
cerr << "\nError: No words exist in file words.txt." << endl;
return;
}
float mean[3] = {104, 107, 123};
/* get in/out tensors and dims*/
auto outputTensors = runner->get_output_tensors();
auto inputTensors = runner->get_input_tensors();
auto out_dims = outputTensors[0]->get_shape();
auto in_dims = inputTensors[0]->get_shape();
auto input_scale = get_input_scale(inputTensors[0]);
auto output_scale = get_output_scale(outputTensors[0]);
/*get shape info*/
int outSize = shapes.outTensorList[0].size;
int inSize = shapes.inTensorList[0].size;
int inHeight = shapes.inTensorList[0].height;
int inWidth = shapes.inTensorList[0].width;
int batchSize = in_dims[0];
std::vector<std::unique_ptr<vart::TensorBuffer>> inputs, outputs;
vector<Mat> imageList;
int8_t* imageInputs = new int8_t[inSize * batchSize];
float* softmax = new float[outSize];
int8_t* FCResult = new int8_t[batchSize * outSize];
std::vector<vart::TensorBuffer*> inputsPtr, outputsPtr;
std::vector<std::shared_ptr<xir::Tensor>> batchTensors;
/*run with batch*/
for (unsigned int n = 0; n < images.size(); n += batchSize) {
unsigned int runSize =
(images.size() < (n + batchSize)) ? (images.size() - n) : batchSize;
in_dims[0] = runSize;
out_dims[0] = batchSize;
for (unsigned int i = 0; i < runSize; i++) {
Mat image = imread(baseImagePath + images[n + i]);
/*image pre-process*/
Mat image2; //= cv::Mat(inHeight, inWidth, CV_8SC3);
resize(image, image2, Size(inHeight, inWidth), 0, 0);
for (int h = 0; h < inHeight; h++) {
for (int w = 0; w < inWidth; w++) {
for (int c = 0; c < 3; c++) {
imageInputs[i * inSize + h * inWidth * 3 + w * 3 + c] =
(int8_t)((image2.at<Vec3b>(h, w)[c] - mean[c]) * input_scale);
}
}
}
imageList.push_back(image);
}
/* in/out tensor refactory for batch inout/output */
batchTensors.push_back(std::shared_ptr<xir::Tensor>(
xir::Tensor::create(inputTensors[0]->get_name(), in_dims,
xir::DataType{xir::DataType::XINT, 8u})));
inputs.push_back(std::make_unique<CpuFlatTensorBuffer>(
imageInputs, batchTensors.back().get()));
batchTensors.push_back(std::shared_ptr<xir::Tensor>(
xir::Tensor::create(outputTensors[0]->get_name(), out_dims,
xir::DataType{xir::DataType::XINT, 8u})));
outputs.push_back(std::make_unique<CpuFlatTensorBuffer>(
FCResult, batchTensors.back().get()));
/*tensor buffer input/output */
inputsPtr.clear();
outputsPtr.clear();
inputsPtr.push_back(inputs[0].get());
outputsPtr.push_back(outputs[0].get());
/*run*/
auto job_id = runner->execute_async(inputsPtr, outputsPtr);
runner->wait(job_id.first, -1);
for (unsigned int i = 0; i < runSize; i++) {
cout << "\nImage : " << images[n + i] << endl;
/* Calculate softmax on CPU and display TOP-5 classification results */
CPUCalcSoftmax(&FCResult[i * outSize], outSize, softmax, output_scale);
TopK(softmax, outSize, 5, kinds);
/* Display the impage */
bool quiet = (getenv("QUIET_RUN") != nullptr);
if (!quiet) {
cv::imshow("Classification of ResNet50", imageList[i]);
cv::waitKey(10000);
}
}
imageList.clear();
inputs.clear();
outputs.clear();
}
delete[] FCResult;
delete[] imageInputs;
delete[] softmax;
}
其中,关键代码行如下:
好了本篇内容就到这里了,感谢阅读,下次再会。
更多回帖