65.9K
CodeProject 正在变化。 阅读更多。
Home

HyCuda,用于 CUDA 的混合框架代码生成器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (4投票s)

2013年12月18日

CPOL

6分钟阅读

viewsIcon

15876

用于 CUDA 的混合框架代码生成器

引言

本文将介绍我最新的与 CUDA 相关的项目 HyCuda。这个名字是 PyCUDA 的一个文字游戏,但与 Python 无关。相反,它生成 C++ 代码,让您可以轻松构建混合算法。

在我进行 CUDA 实现时,我意识到我不是唯一一个想要尝试不同设备设置的人。我想看看当我在不同设备上运行一个或多个子程序时,我的程序的性能会受到怎样的影响。这当然意味着需要实现两次相同的算法(用于 CPU 和 GPU),但代码需要进行一些修改,以确保所有数据都在正确的位置。这时我开始开发一个模板框架来处理这些任务。然而,该框架特别适合我的需求以及我正在使用的特定算法。然后我开始思考如何更通用地解决这个问题,并提出了 HyCuda。

HyCuda 的工作方式是读取程序员提供的描述算法的规范文件。它包含有关子程序、它们使用的数据以及数据类型的信息。然后,它生成一个 C++ 模板框架,您可以在其中实例化一个类模板,其模板参数包含您想使用的设备信息。这允许您实例化多个执行相同算法但位于不同机器上的对象。模板(元编程)机制在编译时(显然)会处理好哪些数据需要何时移动。

本文不会深入介绍细节,因为我已经在 SourceForge 上提供了一份手动(PDF)供您下载,网址是 sourceforge。相同的说明也可以在 http://hycuda.sourceforge.net 上以 HTML 格式提供,但格式不是很好。我只是使用了 latex2html 生成它,没有费力去使其看起来美观。

背景

如果您不熟悉 C++ 和/或 CUDA,本文可能不适合您。如果您想了解 GPU 编程(特别是使用 CUDA),我建议您先阅读相关资料。

使用代码

规范文件

如引言中所述,HyCuda 基于描述算法的规范文件生成 C++ 代码。该文件包含 4 个部分,由两个连续的百分号(%%)分隔。

  1. 指令(类名、命名空间等)
  2. 数据(类型、大小、输入/输出到算法)
  3. 例程(它们的名称是什么,它们依赖于哪些数据以及如何依赖?)
  4. 顺序(例程应该按什么顺序执行?)
下面是一个例子,摘自 手册 的示例部分。
/* filename: spec.hycuda */

// Directives-Section
%namespace: Example
%class-name: ExampleAlgorithm
%parameters: Params {
    float m1;
    float m2;
}

%% // Memory-Section
vec1 (i) : float, vectorSize
vec2 (i) : float, vectorSize
vec3     : float, vectorSize
sum  (o) : float, 1

%% // Routine-Section
multiplyM1  : vec1 (rw), vec2 (rw)
multiplyM2  : vec3 (rw)
addVec1Vec2 : vec1 (r), vec2 (r), vec3 (w)
sumVec3     : vec3 (r), sum (w)

%% // Routine-order
%order : $1, $3, $2, $4

这个相当愚蠢的例子描述了一个执行一些向量运算的算法。它将生成一个名为 ExampleAlgorithm 的类,声明在 Example 命名空间中,其行为由两个名为 m1m2 的 float 参数定义。沿途将使用 3 个不同的向量,分别为 vec1vec2vec3,它们都包含总共 vectorSize 个 float。前两个向量将用作输入,由 (i) 指定,而第三个向量将是中间产品,因此没有输入或输出说明符与之关联。输出将称为 sum,并且只包含一个元素(再次是一个 float)。

将有 4 个子程序构成整个算法。(当然,这是执行这些运算的一种非常愚蠢的方式。算法被分解成这样纯粹是为了教学目的。)

  1. 将两个输入向量乘以参数 m1
  2. 将得到的向量相加,并将结果称为 vec3
  3. vec3 乘以另一个参数 m2
  4. vec3 中的每个元素相加,并将结果存储在 sum 中。

例程部分列出了每个子程序的名称,并告诉 HyCuda 它将使用哪些数据。它还指定数据的使用方式:读取 (r)、写入 (w) 或两者兼有 (rw)/(wr)。此信息用于确定是否需要将数据移动到当前设备。

设备策略

我将跳过实现函数/内核的部分。这在手册中有详细描述。相反,我将转移到您已经设置好所有内容并希望运行算法的部分。为此,您需要指定哪个子程序将在哪个设备上运行。这是通过将模板参数传递给生成的类来实现的,该参数称为 DevicePolicies。为了方便起见,HyCuda 生成了一个已设置默认策略的头文件。该文件看起来像下面的代码片段:

/* filename: examplealgorithm.h */

#include "examplealgorithm_algorithm.h"
#include "examplealgorithm_hybrid.h"
#include "examplealgorithm_devicepolicies.h"

namespace Example {

// Specify which device to use for each of the routines (CPU/GPU)

typedef DevicePolicies <
	MultiplyM1Device  < CPU >,
	MultiplyM2Device  < CPU >,
	AddVec1Vec2Device < CPU >,
	SumVec3Device     < CPU >

> CustomPolicy;

typedef Hybrid_< CustomPolicy > Hybrid;
typedef ExampleAlgorithm_< Hybrid > ExampleAlgorithm;

} //Example

这个小片段实际上有很多内容,但主要的是 DevicePolicies 类模板的 typedef。它接受一些模板参数,这些参数本身也是类模板(例如 MultiplyM1Device)。这些参数都以 spec 文件中的例程命名,并且它们自己的参数告诉算法使用哪个设备来执行每个特定的例程。正确实现后,策略列表中 CPU/GPU 参数的每种排列都会导致设备以不同的方式使用,但产生相同的结果。

以下是使用该算法处理一些输入的 main 函数:

/* examplealgorithm_main.cc */

#include "examplealgorithm.h"
using namespace Example
using namespace std;

size_t readVectorsFromFile(char const *filename, 
                           float **v1, float **v2);

int main(int argc, char **argv)
{
    // Initialize parameters 
    Params params;
    params.m1 = 2;
    params.m2 = 3;
    
    // Initialize vectors
    float *v1, *v2;
    size_t vectorSize = readVectorsFromFile(argv[1], &v1, &v2);
    
    // Initialize input
    Input in;
    in.vec1 = {v1, vectorSize};
    in.vec2 = {v2, vectorSize};
    
    // Initialize output
    float sum;
    Output out;
    out.sum = {&sum, 1};
    
    // Process
    ExampleAlgorithm alg(params);
    alg.process(in, out);
    
    // Output
    cout << "Sum: " << sum << '\n';
}

输入向量不归用户所有,用户应确保它们已正确分配和初始化。输出数据(在此例中为 sum)也是如此。然后使用参数初始化算法,并调用其成员 process,将输入和输出作为其参数。当它返回时,sum 将包含适当的值。

最后的 remarks

我意识到本文中的信息对于您实际使用该程序来说是绝对不够的。但是,我希望它能让您对 HyCuda 是什么以及它是如何工作的有一个初步的了解。有关更多信息,我再次推荐您访问项目页面:http://www.sourceforge.net/projects/hycuda

此外,我非常希望得到一些反馈。到目前为止,我还没有编写一个合适的构建脚本来让您轻松使用。我想只有当确实有人打算使用它时,我才需要这样做。因此,如果您认为这个程序有任何潜力(或者完全没有),请告诉我,我会付出额外的努力。

构建 HyCuda

目前,我将发布一些关于如何开始使用 HyCuda 的说明。它附带一个 makefile,您可以在任何类 Unix 环境中使用它。我诚挚地为 Windows 用户带来的不便表示歉意。但是,生成器需要骨架文件,这些文件的路径包含在 parser/skeletons.h 头文件中。因此,在调用 make 之前,您应该找到一个合适的(绝对)目录来包含这些文件(例如 /etc/skel/hycuda)。编辑 skeletons.h 指向此目录,然后使用 make 构建程序。现在,只需将 skeletons 目录的内容复制到您创建的目录中,并在您的路径中添加一个指向 hycuda 的符号链接,您就可以开始使用了!

历史 

  • 2013 年 12 月 18 日:初稿。
© . All rights reserved.