CUDA算子:reduce优化

如何对GPU中的reduce算法进行优化。官方地址

https://developer.download.nvidia.cn/assets/cuda/files/reduction.pdf

reduce算法的本质:x = x_{0}\bigotimes x_{1}\bigotimes x_{2}...\bigotimes x_{n-1}\bigotimes x_{n}

并行算法设计

GPU中,reduce采用了树形的计算方式

从上到下,将数据不断累加,直到得出最后的结果,25。GPU没有针对global数据的同步操作,只能针对block的数据进行同步。所以将reduce分为两个阶段,示意图如下:

 假设给定一个长度为N的数组,需要计算该数组的所有元素之和。首先需要将数组分为m个小份。在第一阶段,开启m个block计算出m个小份的reduce值。在第二阶段,使用一个block将m个小份再次进行reduce,得到最终结果。由于第二段本质上可以调用第一阶段的kernel,所以本文只探索第一阶段的优化技巧。

kernel接口

__global__void reduce(T *input, T* output)

input:输入的数组,一个长度为N的数组;output:输出的数组,第一阶段的结果,长度为M的数组。

在开始CUDA编程前,设置三个参数:

1.BlockNum开启的block数量,即M,代表需要将数组切成几份

2.Thread_per_block:每个block中开启的线程束,一般:128,256,512,1024

3.Num_per_block:每个block需要进行reduce操作的长度

其中:BlockNum*Num_per_block = N


reduce baseline算法:if (tid%(2*s)==0)

三个步骤:

1.将数据load至shared memory中

2.在shared memory中对数据进行reduce操作

3.将最后的结果写回global memory中。

在第一个步骤中,让Num_per_block和Thread_per_block一致,每个block设置256个线程,一个block负责256个数据的reduce工作。

假设需要处理32M(2^{5}*2^{20})的数据,则有128K(2^{7}*2^{10})个block。

tid:线程号;i:原始数组中的索引号。第tid号线程将第i号的数据从global中取出,放到shared memory的第tid元素中。比如在第0号block中,0号线程将0号元素取出,放到shared memory的第0号位置。

从硬件角度分析,为了执行代码,GPU需要分配存储资源计算资源。存储资源包括在global memory中分配的一块32M*sizeof(float)的空间以及在shared memory中分配的256*sizeof(float)的空间。shared memory存在bank冲突的问题。计

在AutoTVM上实现批量矩阵乘法算子需要进行以下步骤: 1. 定义算子:在TVM中,可以使用`tvm.te.compute`和`tvm.te.reduce`函数定义算子。对于批量矩阵乘法算子,可以使用`tvm.te.compute`定义输入和输出张量,并使用`tvm.te.reduce`定义矩阵乘法。 2. 定义调度策略:使用AutoTVM的调度器来自动寻找最佳的调度策略。可以使用`autotvm.tuner`模块中的`auto_scheduler.Task`类来定义任务,并使用`autotvm.tuner`模块中的`tuner`类来搜索最佳调度策略。 3. 运行调度:使用`autotvm.measure`模块中的`measure_batch`函数对多个调度进行评估,并选取最优的调度策略。 以下是一个简单的示例代码,用于在AutoTVM上实现批量矩阵乘法算子: ```python import tvm from tvm import te, autotvm # 定义批量矩阵乘法算子 def batch_matmul(N, M, K): A = te.placeholder((N, M, K), name='A') B = te.placeholder((N, K, M), name='B') k = te.reduce_axis((0, K), name='k') C = te.compute((N, M, M), lambda i, j, k: te.sum(A[i, j, k] * B[i, k, j], axis=k), name='C') return [A, B, C] # 定义调度策略 def schedule_batch_matmul(outs): s = te.create_schedule([x.op for x in outs]) A, B, C = outs N, M, K = C.shape # 将矩阵乘法中的reduce_axis进行并行化 k = C.op.reduce_axis[0] ko, ki = s[C].split(k, factor=32) s[C].parallel(ko) # 优化循环顺序,减小存储访问量 s[C].reorder(ki, s[C].op.axis[0], s[C].op.axis[1]) # 将计算放置在GPU上 s[C].bind(s[C].op.axis[0], te.thread_axis("blockIdx.x")) s[C].bind(s[C].op.axis[1], te.thread_axis("threadIdx.x")) return s # 定义任务 N, M, K = 32, 64, 128 task = autotvm.task.create('batch_matmul', args=(N, M, K), target='cuda') # 运行调度器 measure_input = autotvm.measure.MeasureInput(task.target, task=task, args=task.args, setup=task.config_space) tuner = autotvm.tuner.XGBTuner(task) tuner.tune(n_trial=1000, measure_option=autotvm.measure_option( builder=autotvm.LocalBuilder(), runner=autotvm.LocalRunner(repeat=3, min_repeat_ms=100), timeout=4.0, )) # 获取最佳调度策略 dispatch_context = tuner.load_best() # 实例化计算图 with tvm.target.Target('cuda'): s, arg_bufs = task.instantiate(dispatch_context) func = tvm.build(s, arg_bufs) # 运行计算图 ctx = tvm.gpu() A = tvm.nd.array(np.random.rand(N, M, K).astype('float32'), ctx) B = tvm.nd.array(np.random.rand(N, K, M).astype('float32'), ctx) C = tvm.nd.array(np.zeros((N, M, M), dtype='float32'), ctx) func(A, B, C) # 验证结果 np.testing.assert_allclose(np.matmul(A.asnumpy(), B.asnumpy()), C.asnumpy()) ``` 这个示例代码中,我们使用`batch_matmul`函数定义了批量矩阵乘法算子,并使用`schedule_batch_matmul`函数定义了针对GPU的调度策略。我们使用AutoTVM的调度器来搜索最佳的调度策略,并使用TVM的`build`函数实例化计算图。最后,我们使用TVM的`ndarray`类来运行计算图并验证结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值