01 Introduction

本节简要介绍了reikna的功能。有关更多详细信息,请参见基础和高级教程。

http://reikna.publicfields.net/en/latest/tutorial-basic.html#tutorial-basic

http://reikna.publicfields.net/en/latest/tutorial-advanced.html#tutorial-advanced

CLUDA

CLUDA是PyCUDA / PyOpenCL之上的抽象层。其主要目的是将reikna的其余部分与API的不同区分开来,但是它也可以单独用于一些简单的任务。

参考以下示例,该示例与PyCUDA文档的索引页中的示例非常相似:

import numpy
import reikna.cluda as cluda

N = 256

api = cluda.ocl_api()
thr = api.Thread.create()

program = thr.compile("""
KERNEL void multiply_them(
    GLOBAL_MEM float *dest,
    GLOBAL_MEM float *a,
    GLOBAL_MEM float *b)
{
  const SIZE_T i = get_local_id(0);
  dest[i] = a[i] * b[i];
}
""")

multiply_them = program.multiply_them

a = numpy.random.randn(N).astype(numpy.float32)
b = numpy.random.randn(N).astype(numpy.float32)
a_dev = thr.to_device(a)
b_dev = thr.to_device(b)
dest_dev = thr.empty_like(a_dev)

multiply_them(dest_dev, a_dev, b_dev, local_size=N, global_size=N)
print((dest_dev.get() - a * b == 0).all())

如果您熟悉PyCUDA或PyOpenCL,您将很容易理解我们在这里所做的所有步骤。 cluda.ocl_api()调用是唯一提及OpenCL的地方,如果将其替换为cluda.cuda_api(),则足以使代码使用CUDA。通过在Python端使用通用API模块以及在内核端使用特殊的宏(KERNEL, GLOBAL_MEM,等)来实现抽象。

compile() 方法的参数也可以是模板,这对于元编程非常有用,还可以用来补偿CUDA和OpenCL中缺少复数运算的情况。让我们通过使初始示例乘以复杂数组来说明这两种情况。 reikna中首选的模板引擎是 Mako,鼓励您阅读它,因为它非常有用。就本示例而言,我们需要知道的是$ {python_expression()}是一种合成表达式构造,用于呈现表达式结果。

import numpy
from numpy.linalg import norm

from reikna import cluda
from reikna.cluda import functions, dtypes

N = 256
dtype = numpy.complex64

api = cluda.ocl_api()
thr = api.Thread.create()

program = thr.compile("""
KERNEL void multiply_them(
    GLOBAL_MEM ${ctype} *dest,
    GLOBAL_MEM ${ctype} *a,
    GLOBAL_MEM ${ctype} *b)
{
  const SIZE_T i = get_local_id(0);
  dest[i] = ${mul}(a[i], b[i]);
}
""", render_kwds=dict(
    ctype=dtypes.ctype(dtype),
    mul=functions.mul(dtype, dtype)))

multiply_them = program.multiply_them

r1 = numpy.random.randn(N).astype(numpy.float32)
r2 = numpy.random.randn(N).astype(numpy.float32)
a = r1 + 1j * r2
b = r1 - 1j * r2
a_dev = thr.to_device(a)
b_dev = thr.to_device(b)
dest_dev = thr.empty_like(a_dev)

multiply_them(dest_dev, a_dev, b_dev, local_size=N, global_size=N)
print(norm(dest_dev.get() - a * b) / norm(a * b) <= 1e-6)

请注意,CLUDA Thread是通过静态方法而不是使用构造函数创建的。构造函数是为更可能的场景保留的,在这种情况下,我们希望在较大的程序中包括一些reikna功能,并且希望它使用现有的上下文和流/队列(请参阅Thread构造函数)。在这种情况下,将使用提供的对象执行线程的所有其他操作。

在这里,我们向模板传递了两个值:ctype(具有C类型名称的字符串)和mul(包含单个乘法函数的 Module对象)。该对象由函数mul()创建,该函数接受要乘以的数据类型并返回经过相应参数化的模块。在模板内部,变量mul本质上是模块中所有全局C对象(函数,结构,宏等)的前缀。如果模块中只有一个公共对象(建议使用),通常的做法是给它一个仅包含前缀的名称,以便可以从父代码轻松地调用它。

有关模块的更多信息,请参见教程(Tutorial: modules and snippets):模块和代码片段。有关CLUDA中可用内容的完整列表,请参见CLUDA reference.参考。

Computations

现在是时候使用该功能的主要部分了。 reikna以基于计算的内核和基于转换(Computation-based cores and Transformation-based plug-ins)的插件的形式提供GPGPU算法。计算包含算法本身。例子有矩阵乘法,约简,排序等。转换是对计算输入或输出的并行操作,用于缩放,类型转换和其他辅助目的。转换被编译到主计算内核中,因此在性能方面非常便宜。

作为示例,我们将考虑矩阵乘法。

import numpy
from numpy.linalg import norm
import reikna.cluda as cluda
from reikna.linalg import MatrixMul

api = cluda.ocl_api()
thr = api.Thread.create()

shape1 = (100, 200)
shape2 = (200, 100)

a = numpy.random.randn(*shape1).astype(numpy.float32)
b = numpy.random.randn(*shape2).astype(numpy.float32)
a_dev = thr.to_device(a)
b_dev = thr.to_device(b)
res_dev = thr.array((shape1[0], shape2[1]), dtype=numpy.float32)

dot = MatrixMul(a_dev, b_dev, out_arr=res_dev)
dotc = dot.compile(thr)
dotc(res_dev, a_dev, b_dev)

res_reference = numpy.dot(a, b)

print(norm(res_dev.get() - res_reference) / norm(res_reference) < 1e-6)

除了创建MatrixMul对象外,上面的大多数代码应该已经很熟悉。计算构造函数采用两个类似数组的对象,代表将参与计算的数组。之后,必须编译计算对象。 compile()方法需要一个Thread对象,该对象用作有关目标API和设备的数据源,并提供执行队列。

Transformations

现在,假设您想乘以复杂的矩阵,但是数据的实部和虚部都保存在单独的数组中。您可以创建其他内核,这些内核会将数据连接到复杂值的数组中,但这将需要额外的存储空间和对GPU的额外调用。 Transformation API允许您将这些转换连接到核心计算(矩阵乘法),从而将代码有效地添加到主计算内核中并更改其签名

让我们更改前面的示例并将转换连接到该示例。

import numpy
from numpy.linalg import norm
import reikna.cluda as cluda
from reikna.core import Type
from reikna.linalg import MatrixMul
from reikna.transformations import combine_complex

api = cluda.ocl_api()
thr = api.Thread.create()

shape1 = (100, 200)
shape2 = (200, 100)

a_re = numpy.random.randn(*shape1).astype(numpy.float32)
a_im = numpy.random.randn(*shape1).astype(numpy.float32)
b_re = numpy.random.randn(*shape2).astype(numpy.float32)
b_im = numpy.random.randn(*shape2).astype(numpy.float32)

arrays = [thr.to_device(x) for x in [a_re, a_im, b_re, b_im]]
a_re_dev, a_im_dev, b_re_dev, b_im_dev = arrays

a_type = Type(numpy.complex64, shape=shape1)
b_type = Type(numpy.complex64, shape=shape2)
res_dev = thr.array((shape1[0], shape2[1]), dtype=numpy.complex64)

dot = MatrixMul(a_type, b_type, out_arr=res_dev)
combine_a = combine_complex(a_type)
combine_b = combine_complex(b_type)

dot.parameter.matrix_a.connect(
    combine_a, combine_a.output, a_re=combine_a.real, a_im=combine_a.imag)
dot.parameter.matrix_b.connect(
    combine_b, combine_b.output, b_re=combine_b.real, b_im=combine_b.imag)

dotc = dot.compile(thr)

dotc(res_dev, a_re_dev, a_im_dev, b_re_dev, b_im_dev)

res_reference = numpy.dot(a_re + 1j * a_im, b_re + 1j * b_im)

print(norm(res_dev.get() - res_reference) / norm(res_reference) < 1e-6)

为了简单起见,我们使用了来自reikna.transformations的预先创建的转换combine_complex();开发自定义转换也是可能的,并在编写转换(Writing a transformation)中进行了描述。从文档中我们知道,它将两个输入转换为一个输出。因此,我们需要将其附加到点输入(由其名称标识)之一上,并为两个新输入提供名称。

要附加的名称从特定计算的文档中获得;对于MatrixMul,它们是a和b。

在当前示例中,我们已将转换附加到两个输入。请注意,该计算现在具有新的签名,并且已编译的点对象现在可用于拆分复数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值