本主题解释了如何从CU或PTX(并行线程执行)文件创建可执行内核,并从MATLAB在GPU上运行该内核®。核在MATLAB中用a表示CUDAKernel
对象,可以操作MATLAB数组或gpuArray变量。
以下步骤描述了CUDAKernel的一般工作流程:
使用编译后的PTX代码创建一个CUDAKernel对象,其中包含GPU可执行代码。
设置CUDAKernel对象的属性来控制它在GPU上的执行。
呼叫函数宏指令
在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);
以下部分提供了这些命令和工作流程的详细信息。
如果您要在GPU上执行CU文件,则必须首先编译它以创建PTX文件。这样做的一种方法是学校网站
编译器在nvidia®CUDA®工具包。例如,如果调用CU文件myfun.cu
,您可以使用shell命令创建编译的ptx文件:
nvcc -ptx myfun.cu
这将生成名为myfun.ptx
。
与A..cu
文件和一个.ptx
文件可以创建一个CUDAKernel
对象,然后你可以用它来计算内核:
k = parallel.gpu.cudakernel('myfun.ptx',“myfun.cu”);
请注意
你不能保存
要么负载
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了支持的C/ c++标准数据类型。
浮动类型 | 整数类型 | 布尔和字符类型 |
---|---|---|
|
|
|
此外,当包含。时,还支持以下整数类型金宝apptmwtypes.h
程序中的标题文件。
整数类型 |
---|
|
头文件作为
。在你的程序中包含这行文件: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上分配内存)。
当您创建一个没有结束分号的内核对象时,或者当您在命令行中输入对象变量时,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编程指南。
使用函数宏指令
函数在GPU上评估CUDAKernel。下面的例子展示了如何使用MATLAB工作空间变量和gpuArray变量执行内核。
假设您已经用本地语言编写了一些内核,并希望在MATLAB中使用它们在GPU上执行。有一个对两个向量进行卷积的核;用两个随机输入向量加载并运行它:
k = parallel.gpu.cudakernel(“conv.ptx”,'conv.cu');结果=函数宏指令(k,兰德(100 1),兰特(100 1));
即使输入是MATLAB工作区数据的常量或变量,输出也是如此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)
输入值x1
和x2
相当于引脚
和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的驱动程序。
执行此操作的CU代码如下所示。
__global__ void add1(双* pi,double c){* pi + = c;}
指令__global__
指示这是内核的入口点。代码使用指针将结果发送进来PI.
,它既是输入又是输出。将此代码放入名为test.cu
在当前目录中。
在shell命令行上编译CU代码,生成一个名为test.ptx
。
nvcc -ptx test.cu
在MATLAB中创建内核。目前这个PTX文件只有一个条目,所以你不需要指定它。如果要添加更多的内核,则需要指定add1
的条目。
k = parallel.gpu.cudakernel('test.ptx','test.cu');
使用两个数字输入运行内核。默认情况下,内核运行在一个线程上。
结果= Feval(k,2,3)
结果= 5
这个示例扩展了前面的示例,将两个向量相加。为简单起见,假设vector中的线程数量与元素数量完全相同,并且只有一个线程块。
CU代码与上一个示例略有不同。两个输入都是指针,其中一个是常量,因为您没有更改它。每个线程将简单地在其线程索引处添加元素。线程索引必须确定该线程应该添加哪个元素(获取这些线程和块特定的值是CUDA编程中非常常见的模式)。
__global__ void add2(double * v1, const double * v2) {int idx = threaddx .x;[idx] v1 + v2 = [idx];}
将此代码保存到文件中test.cu
。
像使用之前一样编译学校网站
。
nvcc -ptx test.cu
如果这段代码与第一个示例的代码放在同一个CU文件中,那么这次需要指定入口点名称,以区分它。
k = parallel.gpu.cudakernel('test.ptx','test.cu',“add2”);
在运行内核之前,请正确设置要添加的向量的线程数。
N = 128;k.ThreadBlockSize = N;三机一体= 1 (N, 1,“gpuArray”);in2 = 1 (N, 1“gpuArray”);结果= Feval(k,In1,In2);
关于如何使用CUDA的示例,并提供了CU和PTX文件供您试验,请参见说明GPU计算的三种方法:曼德尔布罗特集。