此刻我正在喝茶!
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又是个什么东西,怎么来的。这一部分,且听下回分解。
本文已同步至公众号,欢迎关注!