深度学习完全攻略!(连载二:GPU加速技术指南)

此刻我正在喝茶!

Cm compiler的repo如下:

https://github.com/intel/cm-compiler.git

这一节写一写CM中client端如何编程。client是直接与应用程序交互,同时连接GPU的kernel。如下内容按照调用的先后顺序来写。话不多罗嗦,上干货。

前言

cm_result_check()这个函数主要用来检查client函数的返回值。

第一步:

创建CM的device

CmDevice *device = nullptr;

unsigned int version = 0;

cm_result_check(::CreateCmDevice(device, version));

这一步中,CreateCmDevice()会创建一个GPU的device,后续所有的操作都是在这个device上操作。实际上是物理GPU的一个抽象,也可以理解为物理上的GPUdevice,。

第二步:

1,加载kernel部分的加速文件,这个文件是由kernel的执行函数由编译器编译而来。以.isa后缀结尾。例如,linear_walker_genx.isa这个文件是由linear_walker_genx.cpp文件经过编译而来。(这一部分在后面介绍)

std::string isa_code = cm::util::isa::loadFile("linear_walker_genx.isa");

2,加载program。

CmProgram *program = nullptr;

cm_result_check(device->LoadProgram(const_cast<char*>(isa_code.data()),isa_code.size(),program));

这一步是创建包含上述isa中内核的program.

第三步:

上面有了device,有了可以放kernel的program,那么就可以创建kernel了,比如说我们创建了一个“linear”的kernel。

CmKernel *kernel = nullptr;

cm_result_check(device->CreateKernel(program, "linear", kernel));

第四步:

有了kernel,那么函数执行时,输入输出,就需要有保存和接收的地方。这就是surface。

我们先创建一个输入的surface:(相当于分配内存空间了)

CmSurface2D *input_surface = nullptr;

cm_result_check(device->CreateSurface2D(width*3/4,height,CM_SURFACE_FORMAT_A8R8G8B8,input_surface));

注意,这个地方为什么有个*3/4呢,因为,对于CM_SURFACE_FORMAT_A8R8G8B8格式,每个像素占用32位,而对于RGB图像而言,占用24位,所以创建的surface,应该是format的宽度乘以3除以4,例如,32*3/4=24。

将图像写到分配的空间中:

cm_result_check(input_surface->WriteSurface(input_image.getData(), nullptr));

我们再创建一个输出的surface:

cm_result_check(device->CreateSurface2D(width*3/4,height,CM_SURFACE_FORMAT_A8R8G8B8,output_surface));

第五步:

这一步我们就是想有多少个相同的kernel来执行kernel函数。怎么做呢?

假设,我们每次处理的块大小为:6x8,那么,

int thread_width = width/8;

int thread_height = height/6;

Thread_width 和thread_height实际上是一个线程池,这个线程池的宽度和高度跟处理的块大小是有关系的,width和height是图像的高度和宽度。线程池里面有很多线程,系统会把这些线程分配给每个GPU的执行单元去并行执行。好了,让我们创建线程池:

CmThreadSpace *thread_space = nullptr;

cm_result_check(device->CreateThreadSpace(thread_width,thread_height,thread_space));

第六步:

这一步,我们需要把输入和输出的surface关联起来,什么意思呢?Kernel有了,线程池有了,输入输出有了,那么,把这些都整合起来才能去执行,不是吗?

1,拿到输入的一个引用,用surfaceindex来表示。

SurfaceIndex *input_surface_idx = nullptr;

cm_result_check(input_surface->GetIndex(input_surface_idx));

2,把输入关联到kernel里面

cm_result_check(kernel->SetKernelArg(0,sizeof(SurfaceIndex),input_surface_idx));

3,拿到输出的引用:

SurfaceIndex *output_surface_idx = nullptr;

cm_result_check(output_surface->GetIndex(output_surface_idx));

4,把输出关联到kernel里面

cm_result_check(kernel->SetKernelArg(1,sizeof(SurfaceIndex),output_surface_idx));

第七步:

准备,让GPU跑起来。

1,创建一个队列,这个队列里面放的都是task.

CmQueue *cmd_queue = nullptr;

cm_result_check(device->CreateQueue(cmd_queue));

2,创建一个task,这个task里面放的是要执行的kernel。

CmTask *task = nullptr;

cm_result_check(device->CreateTask(task));

3,把kernel放到task里面。

cm_result_check(task->AddKernel(kernel));

4,把task放到queue里面,一个task 执行完了,就执行下一个task。

CmEvent *sync_event = nullptr;

cm_result_check(cmd_queue->Enqueue(task,sync_event,thread_space));

第八步:

拿结果,卸载CM的device。

1,我们把task终结了。

cm_result_check(device->DestroyTask(task));

2,把线程池也搞没

cm_result_check(device->DestroyThreadSpace(thread_space));

3,把surface变成图像。

cm_result_check(output_surface->ReadSurface(output_image.getData(),sync_event));

4,还想了解下GPU跑了多长时间。

UINT64 execution_time = 0;

cm_result_check(sync_event->GetExecutionTime(execution_time));

5,把event也摧毁了。(还记得第七步的最后一步吗,event是用来监控task执行状态的。)

cm_result_check(cmd_queue->DestroyEvent(sync_event));

6,摧毁GPU虚假的device。

cm_result_check(::DestroyCmDevice(device));

7,保存处理完的图像。

output_image.save("linear_out.bmp");

好了,跟app打交道的client端设计就介绍到这里了,相信细心的同学肯定会发现问题,比如说,第二步和第三步中的ISA又是个什么东西,怎么来的。这一部分,且听下回分解。

本文已同步至公众号,欢迎关注!

发布了77 篇原创文章 · 获赞 150 · 访问量 23万+

猜你喜欢

转载自blog.csdn.net/Aoulun/article/details/89528161