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.pyximport numpy as np
cimport numpy as np
cimport cythoncdef extern from "<dlfcn.h>" nogil:void *dlopen(const char *, int)char *dlerror()void *dlsym(void *, const char *)int dlclose(void *)enum:RTLD_LAZYctypedef 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 nogilcdef 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 Gatherfloat timeTakenint 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 BatchGatherfloat timeTakenint N = idx.shape[0] // Bint M = x.shape[0] // Bfloat[:] 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 reswhile not True:dlclose(handle)

这里我们还是接受一维的数组,多引入一个Batch维度的参数B,其他的都是一样的。

Python调用文件

最后是用来调用的最上层Python端的代码test_gather.py

import numpy as np
np.random.seed(0)
from wrapper import batch_cuda_gatherB = 2
M = 1024 * 1024 * 128
N = 1024 * 1024x = 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中被调用。然后将Cython文件编译成动态链接库,使其可以在Python中被调用。最后运行Python代码即可:

$ nvcc -shared ./cuda_index.cu -Xcompiler -fPIC -o ./libcuindex.so
$ cythonize -i -f wrapper.pyx
$ python3 test_gather.py

运行结果如下:

# 总结概要
以学习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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/893000.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ntdll DLL取消挂钩-磁盘导入

一、介绍 本文介绍如何通过将已挂钩的 NTDLL 的文本段覆盖为从磁盘的 NTDLL 映像中获取的未挂钩版本来实现 NTDLL 反挂钩。执行 NTDLL 反挂钩的步骤如下: 通过读取或映射(下面展示了这两种方法)从磁盘检索一个干净版本的 NTDLL 的句柄。 获取属于当前进程的挂钩 NTDLL 的句柄…

华大基因测序芯片(Flowcell)

一、公司简介: 深圳华大智造科技股份有限公司(简称华大智造)秉承“创新智造引领生命科技”的理念,致力于成为生命科技核心工具缔造者,专注于生命科学与生物技术领域,以仪器设备、试剂耗材等相关产品的研发、生产和销售为主要业务,为精准医疗、精准农业和精准健康等行业提…

欧拉OpenEuler使用nfs和rsync复制文件夹到新服务器.v2.250303

案例: 服务器A是新服务器 服务器B为老服务器 需要将服务器B的/data/storage ,拷贝到服务器A的 /home/sync-data下一、服务器A 新服务器配置nfs 1. 安装nfs systemctl stop firewalld df -h mkdir -p /home/sync-datayum install nfs-utils systemctl status nfs-se…

代码审查规范

前言 CodeReview是一种文化、一件有趣的事情,不要把做成一种约束和批判的事懂,本规范的目时是为了建立起适合运配团队的Code Review机制,让所有人都参与进来、融入进来,保持正面的积极的态度,让CR变成我们开发过程的一部分,并在实行的过程中逐步演进,最后形成良好的CR文…

拓普微7寸智能显示模块:激光焊接设备的理想之选

在现代化工业制造领域,激光焊接设备以其高精度、高效率的特点,成为了众多制造业企业不可或缺的加工工具。然而,在复杂的工业环境中,激光焊接设备的显示与控制界面面临着诸多挑战,如抗干扰能力不足、阳光下难以观察以及开发界面复杂等问题。为了解决这些难题,众多知名激光…

Stream4Graph:动态图上的增量计算

通过结合图处理和流处理技术,蚂蚁图计算开源的GeaFlow引擎实现了动态图上的增量计算,大幅提升大规模实时图计算效率,适用于复杂图数据分析场景。作者:张奇众所周知,当我们需要对数据做关联性分析的时候,一般会采用表连接(SQL join)的方式完成。但是SQL join时的笛卡尔积…

Day01 新手入土markdown练习

Markdownd 学习 二级标题 三级标题 四级标题 五级标题 六级标题 ####### 七级标题 井号键+空格+标题内容 字体设置 hello,word 两边双星号为粗体 ctrl+b hello,word 两边单个星号 ctrl+i hello,word 三个星号又粗又斜 hello,word 双波浪号删除线hello,word ctrl…

workerman GatewayWorker laralvel 即时通讯

1.官网地址https://www.workerman.net/doc/gateway-worker 2.把下载的GatewayWorker 放入laravel 框架中 3.windows 中启动GetWayWork 双击启动 只需在events.php中做调整就好(每次修改都需要重新启动才会生效) 使用 websocket 协议 Events.phpclass Events {/*** 当客户端…

特性分支开发

背景 现在git创建,合并,发布,发布后处理,每个业务条线不一致,并且没有传承记录,为保证git统一使用,并保持一致,故有如下规则。 Git常用分支包括 master:项目主分支,有且仅有一个,除项目负责人外其他开发人员不得向 master 分支合并内容。hotfix:紧急线上 bug 修复分…

第八届西湖论剑 DS-easyrawencode

easyrawencode 解压后是一个raw,vol启动虚拟机性能太差了,我直接本机win跑vol.\volatility_2.6.exe -f "C:\Users\Eth\Desktop\match\西湖\easyrawencode.raw" imageinfo.\volatility_2.6.exe -f "C:\Users\Eth\Desktop\match\西湖\easyrawencode.raw" -…

victoriametrics 基础

victoriametrics 介绍 VictoriaMetrics 是一个高性能、高扩展性的开源时间序列数据库和监控解决方案,专为处理大规模指标数据设计。 victoriametrics 特点 兼容 Prometheus API,支持 PromQL 高压缩率(比 Prometheus 高 5-10 倍) 支持水平扩展(集群模式) 低资源消耗,单节…