主要内容

CUDA或者GPU上的PTX代码

概述

本主题解释了如何从CU或PTX(并行线程执行)文件创建可执行内核,并从MATLAB在GPU上运行该内核®。核在MATLAB中用a表示CUDAKernel对象,可以操作MATLAB数组或gpuArray变量。

以下步骤描述了CUDAKernel的一般工作流程:

  1. 使用编译后的PTX代码创建一个CUDAKernel对象,其中包含GPU可执行代码。

  2. 设置CUDAKernel对象的属性来控制它在GPU上的执行。

  3. 呼叫函数宏指令在CUDAKernel上使用所需的输入,在GPU上运行内核。

MATLAB代码遵循这些步骤可能看起来像这样:

% 1。创建CUDAKernel对象。k = parallel.gpu.cudakernel('myfun.ptx',“myfun.cu”,“entryPt1”);%2.设置对象属性。K.GridSize = [8 1];K.Threadblocksize = [16 1];% 3。使用定义的输入调用feval。g1 = gpuArray (in);输入gpuArray %。g2 = gpuArray (in2);输入gpuArray %。结果=函数宏指令(k, g1, g2);

以下部分提供了这些命令和工作流程的详细信息。

创建一个Cudakernel对象

从CU文件编译PTX文件

如果您要在GPU上执行CU文件,则必须首先编译它以创建PTX文件。这样做的一种方法是学校网站编译器在nvidia®CUDA®工具包。例如,如果调用CU文件myfun.cu,您可以使用shell命令创建编译的ptx文件:

nvcc -ptx myfun.cu

这将生成名为myfun.ptx

用CU文件输入构造CUDAKernel对象

与A..cu文件和一个.ptx文件可以创建一个CUDAKernel对象,然后你可以用它来计算内核:

k = parallel.gpu.cudakernel('myfun.ptx',“myfun.cu”);

请注意

你不能保存要么负载CUDAKernel对象。

用C原型输入构造CUDAKernel对象

如果你没有对应于你的PTX文件的CU文件,你可以为你的C内核指定C原型而不是CU文件。例如:

k = parallel.gpu.cudakernel('myfun.ptx','float *,const float *,float');

C prototype输入的另一种用途是,当源代码对受支持的数据类型使用无法识别的重命名时。金宝app(请参阅下面所支持金宝app的类型。)假设您的内核包含以下代码。

typedef float argtype;__global__ void add3(argtype * v1,const argtype * v2){int idx = threadidx.x;[idx] v1 + v2 = [idx];}

ArgType本身未被识别为支持的数据类型,因此在MATLAB中创建Cud金宝appakernel对象时,CU文件不能直接用作输入。但是,支持的输入类型金宝appadd3内核可以指定为C原型输入到CUDAKernel构造函数。例如:

k = parallel.gpu.cudakernel('test.ptx','float *,const float *',“add3”);

金宝app支持的数据类型

下表列出金宝app了支持的C/ c++标准数据类型。

浮动类型 整数类型 布尔和字符类型

,double2

浮动,float2.

,无符号短,short2,ushort2

int,无符号整型,INT2.,uint2

,毫无符号,马龙,ulong2

长长,毫无符号长,longlong2,ulonglong2.

ptrdiff_t,size_t.

保龄球

字符,无符号字符,CHAR2.,uchar2

此外,当包含。时,还支持以下整数类型金宝apptmwtypes.h程序中的标题文件。

整数类型

int8_T,int16_t.,INT32_T.,int64_T

uint8_T,uint16_t.,UINT32_T.,uint64_T

头文件作为matlabroot./交换/ include/tmwtypes.h.。在你的程序中包含这行文件:

# include“tmwtypes.h”

论证限制

所有的输入都可以是标量或指针,并且可以被标记常量

内核的C声明总是这样的:

__global__ void内核(输入…)
  • 内核必须不返回任何内容,并且只对其输入参数(标量或指针)进行操作。

  • 内核无法分配任何形式的内存,因此所有输出必须在内核执行之前预先分配。因此,在运行内核之前,必须知道所有输出的大小。

  • 原则上,传入内核的所有指针都不是常量可以包含输出数据,因为内核的许多线程可以修改该数据。

在将C中的内核定义翻译成MATLAB时:

  • C的所有标量输入(,浮动,int等)必须是MATLAB中的标量,或标量(即单个元素)GPUARRAY变量。

  • 所有常量指针输入在c(const双*等等)可以是标量或矩阵在MATLAB中。它们被转换为正确的类型,复制到设备上,并将指向第一个元素的指针传递给内核。没有将原始大小的信息传递给内核。这就好像内核直接收到了mxgetdata.在一个mxarray.

  • C中的所有非常量指针输入都以非常量指针的方式传送到内核。然而,由于一个非常量指针可以被内核更改,这将被视为来自内核的输出。

  • 来自MATLAB工作空间标量和数组的输入被转换成请求的类型,然后传递给内核。然而,gpuArray输入并不是自动转换的,因此它们的类型和复杂性必须完全符合预期。

这些规则有一些含意。最值得注意的是,内核的每个输出都必须是内核的输入,因为输入允许用户定义输出的大小(这是由于无法在GPU上分配内存)。

CUDAKernel对象属性

当您创建一个没有结束分号的内核对象时,或者当您在命令行中输入对象变量时,MATLAB将显示内核对象的属性。例如:

k = parallel.gpu.cudakernel(“conv.ptx”,'conv.cu')
k = parallel.gpu。CUDAKernelhandle Package: parallel.gpu Properties: ThreadBlockSize: [1 1 1] MaxThreadsPerBlock: 512 GridSize: [1 1 1] SharedMemorySize: 0 EntryPoint: '_Z8theEntryPf' MaxNumLHSArguments: 1 NumRHSArguments: 2 ArgumentTypes: {'in single vector' 'inout single vector'}

内核对象的属性控制它的一些执行行为。使用点表示法更改可更改的属性。

有关对象属性的描述,请参见CUDAKernel对象引用页面。修改可设置属性的一个典型原因是指定线程的数量,如下所述。

指定入口点

如果您的PTX文件包含多个入口点,则可以在myfun.ptx你想要的内核对象k引用:

k = parallel.gpu.cudakernel('myfun.ptx',“myfun.cu”,'mykernel1');

单个PTX文件可以包含多个输入点到不同的内核。这些入口点中的每一个都有一个唯一的名称。这些名称通常是Mangled(如C ++ Mangling)。但是,当产生时学校网站PTX名称始终包含来自CU文件的原始函数名称。例如,如果CU文件定义内核函数,则为

__global__ void simplestKernelEver(float * x, float val)

然后,PTX代码包含一个可能被调用的条目_Z18simplestKernelEverPff

当您有多个入口点时,在调用时指定特定内核的入口名称CUDAKernel生成内核。

请注意

CUDAKernel功能在PTX文件中搜索输入名称,并匹配任何子串出现。因此,您不应该将任何条目命名为任何其他的子串。

您可能无法控制原始条目名称,在这种情况下,您必须了解为每个的唯一Mangled派生。例如,考虑以下功能模板。

模板 __global__ void add4(t * v1,const t * v2){int idx = threadidx.x;[idx] v1 + v2 = [idx];}

当模板扩展为float和double时,它会导致两个入口点,两者都包含子字符串add4

模板__global__ void add4(float *, const float *);模板__global__ void add4(double *, const double *);

PTX具有相应的条目:

_Z4add4IfEvPT_PKS0_ _Z4add4IdEvPT_PKS0_

使用入口点add4If对于float版本,和add4Id对于双重版本。

k = parallel.gpu.cudakernel('test.ptx','双人*,const double *','add4id');

指定线程数

你可以通过设置两个对象属性来为你的CUDAKernel指定计算线程的数量:

  • 网格化-一个由三个元素组成的向量,其乘积决定了块的数量。

  • ThreadBlockSize-一个包含三个元素的向量,其乘积决定了每个块的线程数。(注意产品不能超过属性的价值maxthreadsperblock.。)

这两个属性的默认值都是(1 1 1),但假设您要使用500个线程在500个元素的Vectors上运行Element-Wise操作并行。实现这一目标的简单方法是创建您的Cudakernel并相应地设置其属性:

k = parallel.gpu.CUDAKernel(“myfun.ptx”、“myfun.cu”);k.ThreadBlockSize =(500年,1,1);

通常,您根据输入的大小设置网格和线程块大小。有关线程层次结构的信息,以及多维网格和块,请参阅NVIDIA CUDA C编程指南。

运行一个CUDAKernel

使用函数宏指令函数在GPU上评估CUDAKernel。下面的例子展示了如何使用MATLAB工作空间变量和gpuArray变量执行内核。

使用工作区变量

假设您已经用本地语言编写了一些内核,并希望在MATLAB中使用它们在GPU上执行。有一个对两个向量进行卷积的核;用两个随机输入向量加载并运行它:

k = parallel.gpu.cudakernel(“conv.ptx”,'conv.cu');结果=函数宏指令(k,兰德(100 1),兰特(100 1));

即使输入是MATLAB工作区数据的常量或变量,输出也是如此gpuArray

使用gpuArray变量

也许用起来更有效率gpuArray当运行内核时,对象作为输入:

k = parallel.gpu.cudakernel(“conv.ptx”,'conv.cu');i1 = gpuArray (rand(100年1“单一”));i2 = gpuArray (rand(100年1“单一”));result1 =函数宏指令编写此表达式(k, i1、i2);

因为输出是一个gpuArray,您现在可以使用这个输入或输出数据执行其他操作,而无需在MATLAB工作区和GPU之间进行进一步的传输。当你所有的GPU计算完成,收集你的最终结果数据到MATLAB工作空间:

结果2 = Feval(k,i1,i2);R1 =聚集(结果1);r2 =聚集(结果2);

确定输入和输出的对应关系

当调用[OUT1,OUT2] = FEVAL(内核,IN1,IN2,IN3),输入三机一体,in2,和in3对应于CU文件中C函数的每个输入参数。输出OUT1.OUT2.在C内核执行之后,将第一个和第二个非const指针输入参数的值存储到C函数中。

例如,如果一个CU文件中的C内核有以下签名:

void soundsimple(float * pinout,float c)

相应的内核对象(k)在MATLAB中具有以下性质:

maxnumlhsarguments:1 numrhsarguments:2 arearaltypes:{''inout single矢量''单scalar'}

因此,要从这段代码中使用内核对象函数宏指令,你需要提供函数宏指令两个输入参数(除了内核对象),您可以使用一个输出参数:

y =函数宏指令(k, x1, x2)

输入值x1x2相当于引脚c在C函数原型中。输出参数y对应于的值引脚在C内核执行后的C函数原型中。

以下是一个稍微复杂的例子,显示Const和非Const指针的组合:

更复杂(const float * pIn, float * pInOut1, float * pInOut2)

相应的内核对象在MATLAB中具有如下性质:

maxnumlharguments: 2 numrharguments: 3 ArgumentTypes: {'in single vector' 'inout single vector' 'inout single vector'}

您可以使用函数宏指令在此代码的内核(k)使用语法:

(y1, y2) =函数宏指令(k, x1, x2, x3)

三个输入参数x1,x2,和x3,对应于传递给C函数的三个参数。输出参数日元y2的值PINOUT1.PINOUT2.在C内核执行之后。

完整的内核工作流程

添加两个数字

这个例子在GPU中添加了两个双精度。您应该安装了NVIDIA CUDA工具包,并为您的设备安装了支持CUDA的驱动程序。

  1. 执行此操作的CU代码如下所示。

    __global__ void add1(双* pi,double c){* pi + = c;}

    指令__global__指示这是内核的入口点。代码使用指针将结果发送进来PI.,它既是输入又是输出。将此代码放入名为test.cu在当前目录中。

  2. 在shell命令行上编译CU代码,生成一个名为test.ptx

    nvcc -ptx test.cu
  3. 在MATLAB中创建内核。目前这个PTX文件只有一个条目,所以你不需要指定它。如果要添加更多的内核,则需要指定add1的条目。

    k = parallel.gpu.cudakernel('test.ptx','test.cu');
  4. 使用两个数字输入运行内核。默认情况下,内核运行在一个线程上。

    结果= Feval(k,2,3)
    结果= 5

添加两个向量

这个示例扩展了前面的示例,将两个向量相加。为简单起见,假设vector中的线程数量与元素数量完全相同,并且只有一个线程块。

  1. CU代码与上一个示例略有不同。两个输入都是指针,其中一个是常量,因为您没有更改它。每个线程将简单地在其线程索引处添加元素。线程索引必须确定该线程应该添加哪个元素(获取这些线程和块特定的值是CUDA编程中非常常见的模式)。

    __global__ void add2(double * v1, const double * v2) {int idx = threaddx .x;[idx] v1 + v2 = [idx];}

    将此代码保存到文件中test.cu

  2. 像使用之前一样编译学校网站

    nvcc -ptx test.cu
  3. 如果这段代码与第一个示例的代码放在同一个CU文件中,那么这次需要指定入口点名称,以区分它。

    k = parallel.gpu.cudakernel('test.ptx','test.cu',“add2”);
  4. 在运行内核之前,请正确设置要添加的向量的线程数。

    N = 128;k.ThreadBlockSize = N;三机一体= 1 (N, 1,“gpuArray”);in2 = 1 (N, 1“gpuArray”);结果= Feval(k,In1,In2);

具有CU和PTX文件的示例

关于如何使用CUDA的示例,并提供了CU和PTX文件供您试验,请参见说明GPU计算的三种方法:曼德尔布罗特集