C for Metal-用于Intel显卡的计算的贵金属

您的计算机上有多少个英特尔处理器内核? 如果您使用基于Intel的系统,那么在大多数情况下,您需要在答案中添加一个。 多年来,几乎所有英特尔处理器-从Atom到Xeon E3的组成,当然都没有缺少Core,现在包括集成的图形内核Intel Graphics,它本质上是一个功能全面的处理器,因此,不仅能够在屏幕上显示图片并加快视频处理,而且还可以执行“常规”通用计算。 如何有效地使用它? 看下切。



首先,我们将简要解释为什么您应该依赖英特尔GPU。 当然,系统中的CPU性能几乎总是大大超过GPU,因为它也是中央处理器。

但是有趣的是,在过去的十年中,英特尔集成GPU的性能在百分比上的增长远远超过了CPU的增长,随着新的分立式英特尔图形卡的出现,这种趋势肯定会继续。 此外,GPU凭借其体系结构(许多矢量执行设备),更适合于执行某种类型的任务-图像处理,也就是说,实际上是对数据阵列执行任何相同类型的操作。 GPU通过完全内部并行化来做到这一点,在其上花费的能量少于CPU,在某些情况下甚至在绝对速度上超过了它。 最后,GPU和CPU可以并行工作,各自执行自己的任务,从而为整个系统提供最佳性能和/或最低功耗。

-好的,英特尔。 我们决定使用Intel GPU进行通用计算,该怎么做?
-在图形(Direct3D和OpenGL着色器)中不需要任何特殊知识的最简单方法是OpenCL。

OpenCL内核是独立于平台的,将在系统中可用的所有计算设备(CPU,GPU,FPGA等)上自动执行。 但是,这种多功能性的代价远非每种类型的设备(尤其是集成的Intel GPU)所能达到的最高性能。 这里我们举个例子:在任何转置16x16字节矩阵的Intel GPU上执行代码时,Intel GPU直接编程的性能优势将比OpenCL版本高8倍!

此外,OpenCL根本不支持实现通用算法所需的某些功能(例如,“宽过滤器”在一次转换中使用来自大量像素的数据)。

因此,如果您需要在GPU上达到最高速度和/或需要比独立处理阵列的每个元素及其最接近的邻居更复杂的操作,则Intel C for Metal(ICM)将为您提供帮助-一种用于开发在Intel Graphics上运行的应用程序的工具。

ICM-欢迎来到伪造!


从性能和功能的角度来看,可以将ICM视为“英特尔图形卡的汇编程序”,就电路和可用性而言,可以将其视为“英特尔图形卡OpenCL的样板”。

多年来,英特尔内部一直在使用ICM来开发英特尔GPU上的媒体处理产品。 但是在2018年,ICM甚至向公众发布了!

几个月前,Intel C for Metal有了它的现名,在此之前它被称为Intel C for Media(相同的缩写ICM或CM甚至Cm),甚至更早的名称是Media Development Framework(MDF)。 因此,如果在组件名称,文档或开放源代码注释中的某个位置,旧名称相符-不必担心,这是历史价值。

因此,与OpenCL中一样,ICM应用程序代码包含两部分:“管理”部分(在处理器上执行)和内核(在GPU上执行)。 毫不奇怪,第一部分称为主机,第二部分称为内核。

内核具有处理给定像素块(或仅数据)的功能,这些功能以Intel C for Metal语言编写,并使用ICM编译器编译为Intel GPU指令集(ISA)。

主机是一种“内核团队管理器”,它负责管理CPU和GPU之间的数据传输过程,并通过ICM Runtime运行时库和Intel GPU媒体驱动程序执行其他“管理工作”。
详细的ICM工作流程如下所示:


  • ICM主机代码由任何x86 C / C ++编译器以及整个应用程序编译;
  • ICM内核代码由ICM编译器编译为带有一些通用指令集(通用ISA)的二进制文件;
  • 在运行时,这套通用的JIT指令可转换为特定的Intel GPU。
  • ICM主机调用ICM运行时库以与GPU和操作系统进行通信。

还有两个更重要和有用的观点:

  • ICM中用于表示/存储数据的表面可以与DirectX 11和9(Linux上的DXVA)共享。
  • GPU可以从与CPU共享的视频内存和系统内存中读取和写入数据。 ICM包括双向双向数据传输的特殊功能。 同时,系统内存是完全共享的,不需要真正的复制-为此,ICM中提供了所谓的零复制。

ICM-在火山喷口中!


从名称“ C for Iron”本身开始,就可以看出该语言设备与Intel的内部图形设备相对应。 即,考虑到将在图形卡的几十个执行单元(执行单元)上执行代码的事实,每个执行单元都是能够同时执行多个线程的全矢量处理器。

ICM语言本身是C ++,具有一些限制和扩展。 与C ++相比,ICM缺少...指针,内存分配和静态变量。 禁止下还具有递归功能。 但是,有一个显式的矢量模型(SIMD)编程:矢量数据类型-矢量,矩阵和曲面; 对这些数据类型进行矢量运算,如果/否则对矢量的每个元素独立执行矢量条件; 以及用于访问Intel GPU硬件固定功能的内置功能。

通过“子集”对象可以简化在实际任务中使用矢量,矩阵和曲面的工作-从相应的基本对象中,您只能选择您感兴趣的“参考”块,或者在特殊情况下,通过遮罩选择单个元素。

例如,让我们看一下实现线性滤波器的ICM代码-替换一个值
每个像素的RGB颜色(按其平均值)和图片中的8个邻居:
I(x,y)= [I(x-1,y-1)+ I(x-1,y)+ I(x-1,y + 1)+ I(x,y-1)+
+ I(x,y)+ I(x,y + 1)+ I(x + 1,y-1)+ I(x + 1,y)+ I(x + 1,y + 1)] / 9

如果矩阵中的颜色(数据)位于R8G8B8 ,则将输入图像分为6x8像素(6x24字节数据元素)的块的计算将如下所示:

_GENX_MAIN_ void linear(SurfaceIndex inBuf, SurfaceIndex outBuf, uint h_pos, uint v_pos){ //    8x32 matrix<uchar, 8, 32> in; //   6x24 matrix<uchar, 6, 24> out; matrix<float, 6, 24> m; //    read(inBuf h_pos*24, v_pos*6, in); //    -  m = in.select<6,1,24,1>(1,3); m += in.select<6,1,24,1>(0,0); m += in.select<6,1,24,1>(0,3); m += in.select<6,1,24,1>(0,6); m += in.select<6,1,24,1>(1,0); m += in.select<6,1,24,1>(1,6); m += in.select<6,1,24,1>(2,0); m += in.select<6,1,24,1>(2,3); m += in.select<6,1,24,1>(2,6); //  -   9   * 0.111f; out = m * 0.111f; //   write(outBuf, h_pos*24, v_pos*6, out); } 

  • 矩阵的大小以<数据类型,高度,宽度>的形式设置;
  • select <v_size,v_stride,h_size,h_stride>运算符(i,j)返回以元素(i,j)开头的子矩阵, v_size显示所选行的数量, v_stride-所选行之间的距离h_size-所选行的数量, h_stride-两者之间的距离。

请注意,选择8x32输入矩阵的大小是因为尽管算法上8x30块足以计算6x24块中所有像素的值,但数据块不是以字节为单位,而是以32位dword元素以ICM读取的。

实际上,以上代码是完整的ICM内核。 如前所述,它将由ICM编译器分两个阶段进行编译(预编译和后续的JIT转换)。 ICM编译器基于LLVM构建,并且,如果需要,可以在源代码中进行研究并由您自己构建

但是ICM主机做什么? 调用ICM Runtime运行时库函数,这些函数包括:

  • 使用GPU设备(CmDevice)以及包含内核中使用的用户数据的表面(CmSurface)之后创建,初始化和删除;
  • 使用内核-从预编译的.isa文件中下载它们,准备它们的参数,指示每个内核将使用的数据部分;
  • 创建和管理内核执行队列;
  • 它们控制在GPU上执行每个内核的线程的操作;
  • 管理事件(CmEvent)-GPU和CPU的同步对象;
  • 在GPU和CPU之间,或者在系统和视频内存之间传输数据;
  • 报告错误,测量内核的运行时间。

最简单的主机代码如下所示:

 //  CmDevice cm_result_check(::CreateCmDevice(p_cm_device, version)); //  hello_world_genx.isa std::string isa_code = isa::loadFile("hello_world_genx.isa"); //    isa  CmProgram CmProgram *p_program = nullptr; cm_result_check(p_cm_device->LoadProgram(const_cast<char* >(isa_code.data()),isa_code.size(), p_program)); //  hello_world . CmKernel *p_kernel = nullptr; cm_result_check(p_cm_device->CreateKernel(p_program, "hello_world", p_kernel)); //       CmKernel CmThreadSpace *p_thread_space = nullptr; cm_result_check(p_cm_device->CreateThreadSpace(thread_width, thread_height, p_thread_space)); //   . cm_result_check(p_kernel->SetKernelArg(0, sizeof(thread_width), &thread_width)); //  CmTask –      //         //     . CmTask *p_task = nullptr; cm_result_check(p_cm_device->CreateTask(p_task)); cm_result_check(p_task->AddKernel(p_kernel)); //   CmQueue *p_queue = nullptr; cm_result_check(p_cm_device->CreateQueue(p_queue)); //    GPU (    ). CmEvent *p_event = nullptr; cm_result_check(p_queue->Enqueue(p_task, p_event, p_thread_space)); //   . cm_result_check(p_event->WaitForTaskFinished()); 

如您所见,创建和使用内核和主机并不复杂。 一切都很简单!

要返回现实世界,要警告的唯一困难:当前在ICM的公开版本中,调试内核的唯一方法是printf消息。 在Hello,World示例中可以看到如何正确使用它们。

ICM-不是重金属!


现在让我们看看它在实践中是如何工作的。 ICM开发人员工具包适用于Windows和Linux ,并且两种操作系统都包含ICM编译器,文档和教程用例。 这些培训示例的详细说明是单独下载的

对于Linux,该软件包还包括用于VAAPI的用户模式介质驱动程序,并带有集成的ICM Runtime运行时库。 对于Windows,通常的Windows英特尔图​​形驱动程序将与ICM一起使用。 ICM Runtime运行时库包含在此驱动程序的dll中。 ICM软件包仅包含链接.lib文件。 如果由于某种原因您的系统缺少驱动程序,则可以从英特尔网站下载该驱动程序,并保证从版本15.60-2017开始ICM在驱动程序中的正确运行。

组件的源代码可以在这里找到:


本节的其他内容仅适用于Windows,但是使用ICM的一般原则也适用于Linux。

对于ICM软件包的“常规”工作,您将需要从2015年开始的Visual Studio和从3.2版开始的Cmake。 同时,培训示例的配置和脚本文件是为VS 2015设计的,要使用VS文件的较新版本,您将必须自己研究和编辑VS组件的路径。

因此,了解Windows的ICM:

  • 下载档案 ;
  • 打开包装;
  • 我们使用三个参数启动(最好在VS命令行上)setupenv.bat环境配置脚本-英特尔GPU生成(对应于内置GPU的处理器,默认情况下可以保留它:gen9),编译平台:x86 \ x64和DirectX版本用于与ICM共享:dx9 / dx11。

之后,您可以简单地构建所有训练示例-在examples文件夹中, build_all.bat脚本将执行此操作或为Microsoft Visual Studio生成项目-这将创建一个带有特定示例名称作为参数的create_vs.bat脚本。

如您所见,ICM应用程序将是带有主机部分的.exe文件和带有相应的预编译GPU部分的.isa文件。

ICM程序包中包含各种示例-从最简单的Hello,World,它显示了ICM操作的基本原理,到相当复杂的示例-算法的实现,该算法可找到用于图像分割和拼接的图形的“最大流-最小割”(最大流最小割问题) 。

所有ICM案例研究均已在代码中以及已提到的单独描述中得到了充分的记录。 建议仔细研究ICM-依次研究和运行示例,然后进行修改以适合您的需求。

为了大致了解所有现有ICM功能,强烈建议您研究“规范”- \ documents \ compiler \ html \ cmlangspec文件夹中的ICM描述cmlangspec.html

特别是,它描述了在硬件中实现的ICM功能的API-访问所谓的纹理采样器(Sampler)-一种用于过滤不同格式的图像以及评估视频帧之间的运动(运动估计)和某些视频分析功能的机制。

ICM-趁热罢工!


在谈到ICM应用程序的性能时,应该注意的是,案例研究包括评估其工作时间,以便通过在目标系统上运行它们并将它们与您的任务进行比较,您可以评估对它们使用ICM的适当性。

关于ICM性能的一般考虑很简单:

  • 在GPU上卸载计算时,请记住传输CPU <-> GPU数据并同步这些设备的开销。 因此,诸如Hello,World之类的示例不是ICM实现的理想选择。 但是ICM需要计算机视觉,人工智能以及对数据数组的任何不平凡的处理,尤其是这些数据在过程中或输出处的顺序发生变化的算法。
  • 此外,在设计ICM代码时,有必要考虑内部GPU设备,即,建议创建足够数量(> 1000)的GPU线程并将它们全部加载工作。 在这种情况下,最好将图像分割成小块进行处理。 但是,特定的分区方式以及选择特定的处理算法以实现最佳性能并不是一件容易的事。 但是,这适用于使用任何GPU(和CPU)的任何方式。

您是否具有OpenCL代码,但其性能不能令人满意? 还是CUDA代码,但是您想在更多平台上工作? 然后值得一看ICM。

ICM是一个不断发展的产品。 您可以参与其使用和开发-github上的相应存储库正在等待您的提交。 这两个过程所需的所有信息都在本文和github中的自述文件中。 如果缺少某些内容,它将出现在您的问题后的注释中。

Source: https://habr.com/ru/post/zh-CN466521/


All Articles