CUDA与Cython之BatchGather
技术背景
在前面一篇文章中,我们介绍过Cython+CUDA框架下实现一个简单的Gather算子的方法。这里演示Gather算子的升级版本实现——BatchGather算子。不过这里只是加了一个Batch维度,并没有添加其他的维度,例如Dimension维度,在这里暂不考虑。
CUDA头文件
这里我们保留了原本的Gather部分,只添加一个BatchGather的运算,以下为cuda_index.cuh
的内容:
#include <stdio.h>
extern "C" float Gather(float *source, int *index, float *res, int N, int M);
extern "C" float BatchGather(float *source, int *index, float *res, int N, int M, int B);
BatchGather只是在Gather的基础上加了一个B的维度。除了CUDA算子本身的头文件之外,这里我们还使用到了异常捕获头文件error.cuh
:
#pragma once
#include <stdio.h>
#define CHECK(call) do{const cudaError_t error_code = call; if (error_code != cudaSuccess){printf("CUDA Error:\n"); printf(" File: %s\n", __FILE__); printf(" Line: %d\n", __LINE__); printf(" Error code: %d\n", error_code); printf(" Error text: %s\n", cudaGetErrorString(error_code)); exit(1);}} while (0)
其中的宏可用于检测CUDA函数所抛出的异常。另外还有一个用于统计CUDA函数运行时长的头文件:
#pragma once
#include <stdio.h>
#include <cuda_runtime.h>
// 宏定义,用于测量CUDA函数的执行时间
#define TIME_CUDA_FUNCTION(func) \
do { \
cudaEvent_t start, stop; \
float elapsedTime; \
cudaEventCreate(&start); \
cudaEventCreate(&stop); \
cudaEventRecord(start, NULL); \
\
func; \
\
cudaEventRecord(stop, NULL); \
cudaEventSynchronize(stop); \
cudaEventElapsedTime(&elapsedTime, start, stop); \
printf("Time taken by function %s is: %f ms\n", #func, elapsedTime); \
\
cudaEventDestroy(start); \
cudaEventDestroy(stop); \
} while (0)
// 宏定义,用于测量CUDA函数的执行时间并返回该时间
#define GET_CUDA_TIME(func) \
({ \
cudaEvent_t start, stop; \
float elapsedTime = 0.0f; \
cudaEventCreate(&start); \
cudaEventCreate(&stop); \
cudaEventRecord(start, NULL); \
\
func; \
\
cudaEventRecord(stop, NULL); \
cudaEventSynchronize(stop); \
cudaEventElapsedTime(&elapsedTime, start, stop); \
\
cudaEventDestroy(start); \
cudaEventDestroy(stop); \
\
elapsedTime; \
})
可选择直接打印时长,也可以选择返回时长的float值。
CUDA文件
接下来就是正式的CUDA函数内容cuda_index.cu
:
// nvcc -shared ./cuda_index.cu -Xcompiler -fPIC -o ./libcuindex.so
#include <stdio.h>
#include "cuda_index.cuh"
#include "error.cuh"
#include "record.cuh"
__global__ void GatherKernel(float *source, int *index, float *res, int N){
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < N){
res[idx] = source[index[idx]];
}
}
extern "C" float Gather(float *source, int *index, float *res, int N, int M){
float *souce_device, *res_device;
int *index_device;
CHECK(cudaMalloc((void **)&souce_device, M * sizeof(float)));
CHECK(cudaMalloc((void **)&res_device, N * sizeof(float)));
CHECK(cudaMalloc((void **)&index_device, N * sizeof(int)));
CHECK(cudaMemcpy(souce_device, source, M * sizeof(float), cudaMemcpyHostToDevice));
CHECK(cudaMemcpy(res_device, res, N * sizeof(float), cudaMemcpyHostToDevice));
CHECK(cudaMemcpy(index_device, index, N * sizeof(int), cudaMemcpyHostToDevice));
int block_size = 1024;
int grid_size = (N + block_size - 1) / block_size;
float timeTaken = GET_CUDA_TIME((GatherKernel<<<grid_size, block_size>>>(souce_device, index_device, res_device, N)));
CHECK(cudaGetLastError());
CHECK(cudaDeviceSynchronize());
CHECK(cudaMemcpy(res, res_device, N * sizeof(float), cudaMemcpyDeviceToHost));
CHECK(cudaFree(souce_device));
CHECK(cudaFree(index_device));
CHECK(cudaDeviceSynchronize());
CHECK(cudaFree(res_device));
CHECK(cudaDeviceReset());
return timeTaken;
}
__global__ void BatchGatherKernel(float *source, int *index, float *res, int N, int M, int B){
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < N*B){
int batch_idx = idx / N;
int source_idx = batch_idx * M + index[idx];
res[idx] = source[source_idx];
}
}
extern "C" float BatchGather(float *source, int *index, float *res, int N, int M, int B){
float *souce_device, *res_device;
int *index_device;
CHECK(cudaMalloc((void **)&souce_device, B * M * sizeof(float)));
CHECK(cudaMalloc((void **)&res_device, B * N * sizeof(float)));
CHECK(cudaMalloc((void **)&index_device, B * N * sizeof(int)));
CHECK(cudaMemcpy(souce_device, source, B * M * sizeof(float), cudaMemcpyHostToDevice));
CHECK(cudaMemcpy(res_device, res, B * N * sizeof(float), cudaMemcpyHostToDevice));
CHECK(cudaMemcpy(index_device, index, B * N * sizeof(int), cudaMemcpyHostToDevice));
int block_size = 1024;
int grid_size = (B * N + block_size - 1) / block_size;
float timeTaken = GET_CUDA_TIME((BatchGatherKernel<<<grid_size, block_size>>>(souce_device, index_device, res_device, N, M, B)));
CHECK(cudaGetLastError());
CHECK(cudaDeviceSynchronize());
CHECK(cudaMemcpy(res, res_device, B * N * sizeof(float), cudaMemcpyDeviceToHost));
CHECK(cudaFree(souce_device));
CHECK(cudaFree(index_device));
CHECK(cudaDeviceSynchronize());
CHECK(cudaFree(res_device));
CHECK(cudaDeviceReset());
return timeTaken;
}
这里传入到CUDA之前,我们需要在Cython或者Python中把相关的数据压缩为一维,所以传入CUDA函数的是一个一维的指针。相比于单一的Gather操作,BatchGather中的几个输入含义有所变化,例如N
表示的是单Batch的Index长度,M
表示的是单Batch的源数组长度。
Cython文件
对于一个新的Batch函数来说,我们需要构建一个新的Cython调用函数wrapper.pyx
:
# cythonize -i -f wrapper.pyx
import numpy as np
cimport numpy as np
cimport cython
cdef extern from "<dlfcn.h>" nogil:
void *dlopen(const char *, int)
char *dlerror()
void *dlsym(void *, const char *)
int dlclose(void *)
enum:
RTLD_LAZY
ctypedef float (*GatherFunc)(float *source, int *index, float *res, int N, int M) noexcept nogil
ctypedef float (*BatchGatherFunc)(float *source, int *index, float *res, int N, int M, int B) noexcept nogil
cdef void* handle = dlopen('/path/to/libcuindex.so', RTLD_LAZY)
@cython.boundscheck(False)
@cython.wraparound(False)
cpdef float[:] cuda_gather(float[:] x, int[:] idx):
cdef:
GatherFunc Gather
float timeTaken
int N = idx.shape[0]
int M = x.shape[0]
float[:] res = np.zeros((N, ), dtype=np.float32)
Gather = <GatherFunc>dlsym(handle, "Gather")
timeTaken = Gather(&x[0], &idx[0], &res[0], N, M)
print (timeTaken)
return res
@cython.boundscheck(False)
@cython.wraparound(False)
cpdef float[:] batch_cuda_gather(float[:] x, int[:] idx, int B):
cdef:
BatchGatherFunc BatchGather
float timeTaken
int N = idx.shape[0] // B
int M = x.shape[0] // B
float[:] res = np.zeros((B*N, ), dtype=np.float32)
BatchGather = <BatchGatherFunc>dlsym(handle, "BatchGather")
timeTaken = BatchGather(&x[0], &idx[0], &res[0], N, M, B)
print (timeTaken)
return res
while not True:
dlclose(handle)
这里我们还是接受一维的数组,多引入一个Batch维度的参数B
,其他的都是一样的。
Python调用文件
最后是用来调用的最上层Python端的代码test_gather.py
:
import numpy as np
np.random.seed(0)
from wrapper import batch_cuda_gather
B = 2
M = 1024 * 1024 * 128
N = 1024 * 1024
x = np.random.random((M*B,)).astype(np.float32)
idx = np.random.randint(0, M, (N*B,)).astype(np.int32)
np_res = np.zeros((B, N), dtype=np.float32)
for i in range(B):
np_res[i] = x.reshape((B,-1))[i][idx.reshape((B, -1))[i]]
np_res = np_res.reshape(-1)
res = np.asarray(batch_cuda_gather(x, idx, B))
print (res.shape)
print ((res==np_res).sum())
为了方便处理,在构建数据的时候,我们直接在生成数据阶段就生成一维的数据,然后直接调用Cython函数进行CUDA相关运算。
运行方法
总结概要
以学习CUDA为目的,接上一篇关于Cython与CUDA架构下的Gather算子实现,这里我们加一个Batch的维度,做一个BatchGather的简单实现。
版权声明
本文首发链接为:https://www.cnblogs.com/dechinphy/p/cython-cuda-batchgather.html
作者ID:DechinPhy
更多原著文章:https://www.cnblogs.com/dechinphy/
请博主喝咖啡:https://www.cnblogs.com/dechinphy/gallery/image/379634.html
CUDA与Cython之BatchGather的更多相关文章
- Caffe + Ubuntu 14.04 64bit + 无CUDA(linux下安装caffe(无cuda)以及python接口)
安装Caffe指导书 环境: Linux 64位 显卡为Intel + AMD,非英伟达显卡 无GPU 一. 安装准备工作 1. 以管理员身份登录 在左上角点击图标,搜索terminal(即终端),以 ...
- ubuntu14.04安装cuda
1 装系统时候注意,另外14.04要好于12.04,自带了无线驱动 ubuntu14.04安装完不要update 2 安装cuda和cudnn http://blog.csdn.net/l297969 ...
- Caffe + Ubuntu 15.04 + CUDA 7.0 安装以及配置
作为小码农的我,昨天就在装这个东东了,主要参考第一篇博文,但是过程发现很多问题,经过反反复复,千锤百炼,终于柳暗花明,我把这个caffe给搞定了,是故,我发布出来,后之来者,欲将有感于斯文~ 本分分为 ...
- 【转载】Caffe + Ubuntu 14.04 + CUDA 6.5 新手安装配置指南
洋洋洒洒一大篇,就没截图了,这几天一直在折腾这个东西,实在没办法,不想用Linux但是,为了Caffe,只能如此了,安装这些东西,遇到很多问题,每个问题都要折磨很久,大概第一次就是这样的.想想,之后应 ...
- 记一次CUDA编程任务
这个月6号开始,着手解决一个具有实际意义的计算任务.任务数据有9879896条,每条包含30个整数,任务是计算每两条数据之间的斯皮尔相关系数及其P值.原始数据只有500+MB,因此我并不认为这是个多么 ...
- 【深度学习】PyTorch CUDA环境配置及安装
Pytorch版本介绍 torch:1.6 CUDA:10.2 cuDNN:8.1.0 安装 NVIDIA 显卡驱动程序 一般 电脑出厂/装完系统 会自动安装显卡驱动 如果有 可直接进行下一步 下载链 ...
- win10家庭中文版CUDA+CUDNN+显卡GPU使用tensorflow-gpu训练模型安装过程(精华帖汇总+重新修改多次复现)
查看安装包 pip list 本帖提供操作过程,具体操作网上有好多了,不赘述.红色字体为后来复现出现的问题以及批注 题外话: (1)python 的环境尽量保持干净,尽量单一,否则容易把自己搞晕,不知 ...
- 在NVIDIA(CUDA,CUBLAS)和Intel MKL上快速实现BERT推理
在NVIDIA(CUDA,CUBLAS)和Intel MKL上快速实现BERT推理 直接在NVIDIA(CUDA,CUBLAS)或Intel MKL上进行高度定制和优化的BERT推理,而无需tenso ...
- CUDA[2] Hello,World
Section 0:Hello,World 这次我们亲自尝试一下如何用粗(CU)大(DA)写程序 CUDA最新版本是7.5,然而即使是最新版本也不兼容VS2015 ...推荐使用VS2012 进入VS ...
- CUDA[1] Introductory
Section 0 :Induction of CUDA CUDA是啥?CUDA®: A General-Purpose Parallel Computing Platform and Program ...
随机推荐
- T语言开发笔记1
为什么会有开发语言的想法 在2012年,我准备开发一个给前端切图使用的屏幕取色器. 需求很简单,前端经常需要获取设计稿特定位置的颜色代码.虽然当时 PhotoShop 提供了内部取色器,但操作麻烦,而 ...
- 【前端】【JavaScript】简单的加减乘除计算器
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 设置VirtualBox共享文件夹的方法
1.创建共享文件夹进入linux终端,通过如下指令创建共享文件夹.在此处的/mnt/share是Linux下的共享文件夹. sudo mkdir /mnt/share2.在VirtualBox的设置中 ...
- 龙哥量化:什么是ZXNH直线拟合指标?ZXNH信号漂移,未来函数检测不到, 函数列表没有,大坑哦哦哦
如果您需要代写技术指标公式, 请联系我. 龙哥QQ:591438821 龙哥微信:Long622889 这个函数太坑, 我也不明白原理是什么, 是未来函数,信号会漂移, zxnh的值只有0和1,下面一 ...
- Qt音视频开发16-通用悬浮按钮工具栏的设计
一.前言 通用悬浮按钮工具栏这个功能经过了好几个版本的迭代,一开始设计的时候是写在视频控件widget窗体中,当时功能简单就放一排按钮在顶部悬浮widget中就好,随着用户需求的变化,用户需要自定义悬 ...
- Map中经常被忽略但又非常好用的方法
1. 简介 map是我们日常开发中常会的集合类之一, 但是我们除了常用的get和put之外,其他的方法好像很少会用到,接下来我们就介绍一下几个经常被忽略但又很好用的方法. 2. Quick Start ...
- 解决STM32 CubeMX中配置RTC每次上电就会重置的问题
自从有了Cube MX,配置STM32的外设就变得格外简单.不过这次差点踩坑,下载完程序后RTC自动就恢复到了我设置的初始值,重启之后现象依旧. 下面就以上问题简单分析一下代码. /* RTC ini ...
- Docker 迁移数据目录
Centos7 环境,采用yum安装的,默认数据目录在/var/lib/docker中 1. 关闭docker服务 systemctl stop docker 2. 备份和迁移 # 迁移 cp -r ...
- Solution -「WC 2014」「洛谷 P3920」紫荆花之恋
\(\mathscr{Description}\) Link. 维护一棵树,初始时树空.接下来 \(n\) 次操作,每次操作加入一片叶子 \(u\),\(u\) 到其邻接点的边权为 \(w\) ...
- Hugo|30分钟搭建完整的个人博客
本文将讲述如何使用 Hugo,从0到1完成一个"静态博客"的搭建.展示 hugo 可以通过简单配置,自定义装饰博客界面的能力,并集成网站数据统计能力. 下一篇文章将教会你将站点免费 ...