[CUDA编程] cuda graph优化心得

CUDA Graph

1. cuda graph的使用场景

  • cuda graph在一个kernel要多次执行,且每次只更改kernel 参数或者不更改参数时使用效果更加;但是如果将graph替换已有的kernel组合,且没有重复执行,感觉效率不是很高反而低于原始的kernel调用;【此外, graph启动还需要耗时】

2. 使用方式

2.1 stream capture 方式

  • 基本范式, 通过start capture 和end Capture 以及 构建graph exec方式实现graph执行,效率不高;用于graph多次执行的情况。ref: cuda_sample: jacobi
  • 不需要GraphCreate 一个graph对象。cudaStreamEndCapture 会直接创建一个graph。
checkCudaErrors(cudaStreamBeginCapture(stream, cudaStreamCaptureModeGlobal));checkCudaErrors(cudaMemsetAsync(d_sum, 0, sizeof(double), stream));if ((k & 1) == 0) {JacobiMethod<<<nblocks, nthreads, 0, stream>>>(A, b, conv_threshold, x,x_new, d_sum);} else {JacobiMethod<<<nblocks, nthreads, 0, stream>>>(A, b, conv_threshold,x_new, x, d_sum);}checkCudaErrors(cudaMemcpyAsync(&sum, d_sum, sizeof(double),cudaMemcpyDeviceToHost, stream));checkCudaErrors(cudaStreamEndCapture(stream, &graph));if (graphExec == NULL) {checkCudaErrors(cudaGraphInstantiate(&graphExec, graph, NULL, NULL, 0));} else {cudaGraphExecUpdateResult updateResult_out;checkCudaErrors(cudaGraphExecUpdate(graphExec, graph, NULL, &updateResult_out));if (updateResult_out != cudaGraphExecUpdateSuccess) {if (graphExec != NULL) {checkCudaErrors(cudaGraphExecDestroy(graphExec));}printf("k = %d graph update failed with error - %d\n", k,updateResult_out);checkCudaErrors(cudaGraphInstantiate(&graphExec, graph, NULL, NULL, 0));}}checkCudaErrors(cudaGraphLaunch(graphExec, stream));checkCudaErrors(cudaStreamSynchronize(stream));// 封装 capture过程
class MyCudaGraph {public:CudaGraph(): graph_(nullptr),graph_instance_(nullptr),stream_(nullptr),is_captured_(false) {RPV_CUDA_CHECK(cudaGraphCreate(&graph_, 0));}~CudaGraph() {if (graph_ != nullptr) {RPV_CUDA_CHECK(cudaGraphDestroy(graph_));}if (graph_instance_ != nullptr) {RPV_CUDA_CHECK(cudaGraphExecDestroy(graph_instance_));}}void set_stream(const cudaStream_t& stream) { stream_ = stream; }const cudaGraph_t& graph() const { return graph_; }const cudaGraphExec_t& graph_instance() const { return graph_instance_; }void CaptureStart() const {RPV_CUDA_CHECK(cudaStreamBeginCapture(stream_, cudaStreamCaptureModeGlobal));}void CaptureEnd() const {// stream 捕捉模式不需要cudaGraphCreate 来初始化 graph_.RPV_CUDA_CHECK(cudaStreamEndCapture(stream_, &graph_));}bool IsCaptured() const { return is_captured_; }void Launch() const {if (graph_instance_ == nullptr) {RPV_CUDA_CHECK(cudaGraphInstantiate(&graph_instance_, graph_, nullptr, nullptr, 0));}RPV_CUDA_CHECK(cudaGraphLaunch(graph_instance_, stream_));}void UpdateLaunch() const {cudaGraphExecUpdateResult update_result;// 当第一次构建完graph_instance_(cudaGraphExec_t)后, 后续捕捉都只需要更新graphexec 即可。RPV_CUDA_CHECK(cudaGraphExecUpdate(graph_instance_, graph_, nullptr, &update_result));if (update_result != cudaGraphExecUpdateSuccess) {if (graph_instance_ != nullptr) { // 注意,如果更新失败,则需要将graph_instance_ 删除,并用cudaGraphInstantiate重新生成一个新的graph exec对象。RPV_CUDA_CHECK(cudaGraphExecDestroy(graph_instance_));}LOG(WARNING) << "cuda graph update failed.";RPV_CUDA_CHECK(cudaGraphInstantiate(&graph_instance_, graph_, nullptr, nullptr, 0));}RPV_CUDA_CHECK(cudaGraphLaunch(graph_instance_, stream_)); // 执行graph是通过cudaGraphLaunch 执行cudaGraphExec_t对象来实现}void AddKernelNode(cudaGraphNode_t& node, cudaKernelNodeParams& param) const {node_ = node;cudaGraphAddKernelNode(&node_, graph_, nullptr, 0, &param); // 往graph中添加node_,注意需要提前cudaGraphCreate graph才行。}void ExecKernelNodeSetParams(cudaKernelNodeParams& param) const {cudaGraphExecKernelNodeSetParams(graph_instance_, node_, &param);RPV_CUDA_CHECK(cudaGraphLaunch(graph_instance_, stream_));}private:mutable cudaGraphNode_t node_;mutable cudaGraph_t graph_;mutable cudaGraphExec_t graph_instance_;mutable cudaStream_t stream_;mutable bool is_captured_;DISALLOW_COPY_AND_ASSIGN(CudaGraph);
};

2.2 Node Param方式

  • ref: cuda sample: jacobi
  • 注意node的方式需要 构建每个node的依赖node。并且通过更新kernel param的方式来更新graph exec, 效率可能更高。但是
cudaGraph_t graph;cudaGraphExec_t graphExec = NULL;double sum = 0.0;double *d_sum = NULL;checkCudaErrors(cudaMalloc(&d_sum, sizeof(double)));std::vector<cudaGraphNode_t> nodeDependencies;cudaGraphNode_t memcpyNode, jacobiKernelNode, memsetNode;cudaMemcpy3DParms memcpyParams = {0};cudaMemsetParams memsetParams = {0};memsetParams.dst = (void *)d_sum;memsetParams.value = 0;memsetParams.pitch = 0;// elementSize can be max 4 bytes, so we take sizeof(float) and width=2memsetParams.elementSize = sizeof(float);memsetParams.width = 2;memsetParams.height = 1;checkCudaErrors(cudaGraphCreate(&graph, 0));checkCudaErrors(cudaGraphAddMemsetNode(&memsetNode, graph, NULL, 0, &memsetParams));nodeDependencies.push_back(memsetNode);cudaKernelNodeParams NodeParams0, NodeParams1;NodeParams0.func = (void *)JacobiMethod;NodeParams0.gridDim = nblocks;NodeParams0.blockDim = nthreads;NodeParams0.sharedMemBytes = 0;void *kernelArgs0[6] = {(void *)&A, (void *)&b,     (void *)&conv_threshold,(void *)&x, (void *)&x_new, (void *)&d_sum};NodeParams0.kernelParams = kernelArgs0;NodeParams0.extra = NULL;checkCudaErrors(cudaGraphAddKernelNode(&jacobiKernelNode, graph, nodeDependencies.data(),nodeDependencies.size(), &NodeParams0));nodeDependencies.clear();nodeDependencies.push_back(jacobiKernelNode);memcpyParams.srcArray = NULL;memcpyParams.srcPos = make_cudaPos(0, 0, 0);memcpyParams.srcPtr = make_cudaPitchedPtr(d_sum, sizeof(double), 1, 1);memcpyParams.dstArray = NULL;memcpyParams.dstPos = make_cudaPos(0, 0, 0);memcpyParams.dstPtr = make_cudaPitchedPtr(&sum, sizeof(double), 1, 1);memcpyParams.extent = make_cudaExtent(sizeof(double), 1, 1);memcpyParams.kind = cudaMemcpyDeviceToHost;checkCudaErrors(cudaGraphAddMemcpyNode(&memcpyNode, graph, nodeDependencies.data(),nodeDependencies.size(), &memcpyParams));checkCudaErrors(cudaGraphInstantiate(&graphExec, graph, NULL, NULL, 0));NodeParams1.func = (void *)JacobiMethod;NodeParams1.gridDim = nblocks;NodeParams1.blockDim = nthreads;NodeParams1.sharedMemBytes = 0;void *kernelArgs1[6] = {(void *)&A,     (void *)&b, (void *)&conv_threshold,(void *)&x_new, (void *)&x, (void *)&d_sum};NodeParams1.kernelParams = kernelArgs1;NodeParams1.extra = NULL;int k = 0;for (k = 0; k < max_iter; k++) {checkCudaErrors(cudaGraphExecKernelNodeSetParams(graphExec, jacobiKernelNode,((k & 1) == 0) ? &NodeParams0 : &NodeParams1));checkCudaErrors(cudaGraphLaunch(graphExec, stream));checkCudaErrors(cudaStreamSynchronize(stream));if (sum <= conv_threshold) {checkCudaErrors(cudaMemsetAsync(d_sum, 0, sizeof(double), stream));nblocks.x = (N_ROWS / nthreads.x) + 1;size_t sharedMemSize = ((nthreads.x / 32) + 1) * sizeof(double);if ((k & 1) == 0) {finalError<<<nblocks, nthreads, sharedMemSize, stream>>>(x_new, d_sum);} else {finalError<<<nblocks, nthreads, sharedMemSize, stream>>>(x, d_sum);}checkCudaErrors(cudaMemcpyAsync(&sum, d_sum, sizeof(double),cudaMemcpyDeviceToHost, stream));checkCudaErrors(cudaStreamSynchronize(stream));printf("GPU iterations : %d\n", k + 1);printf("GPU error : %.3e\n", sum);break;}}
  • 对比发现 graph 反而耗时更长
    在这里插入图片描述

2.3 通过传递kernel为指针,然后更改指针的值来是graph执行更高效

  • 官方其他实例,通过更新值
  • ref: mandrake: wtsne_gpu
    这个开源工程通过封装 device value为一个container,从而通过替换这个显存问题的值来重复执行graph. 效率更高。
// Start capturecapture_stream.capture_start();// Y updatewtsneUpdateYKernel<real_t><<<block_count, block_size, 0, capture_stream.stream()>>>(device_ptrs.rng, get_node_table(), get_edge_table(), device_ptrs.Y,device_ptrs.I, device_ptrs.J, device_ptrs.Eq, device_ptrs.qsum,device_ptrs.qcount, device_ptrs.nn, device_ptrs.ne, eta0, nRepuSamp,device_ptrs.nsq, bInit, iter_d.data(), maxIter,device_ptrs.n_workers, n_clashes_d.data());// s (Eq) updatecub::DeviceReduce::Sum(qsum_tmp_storage_.data(), qsum_tmp_storage_bytes_,qsum_.data(), qsum_total_device_.data(),qsum_.size(), capture_stream.stream());cub::DeviceReduce::Sum(qcount_tmp_storage_.data(), qcount_tmp_storage_bytes_, qcount_.data(),qcount_total_device_.data(), qcount_.size(), capture_stream.stream());update_eq<real_t><<<1, 1, 0, capture_stream.stream()>>>(device_ptrs.Eq, device_ptrs.nsq, qsum_total_device_.data(),qcount_total_device_.data(), iter_d.data());capture_stream.capture_end(graph.graph());// End capture// Main SCE loop - run captured graph maxIter times// NB: Here I have written the code so the kernel launch parameters (and all// CUDA API calls) are able to use the same parameters each loop, mainly by// using pointers to device memory, and two iter counters.// The alternative would be to use cudaGraphExecKernelNodeSetParams to// change the kernel launch parameters. See// 0c369b209ef69d91016bedd41ea8d0775879f153const auto start = std::chrono::steady_clock::now();for (iter_h = 0; iter_h < maxIter; ++iter_h) {graph.launch(graph_stream.stream());if (iter_h % MAX(1, maxIter / 1000) == 0) {// Update progress meterEq_device_.get_value_async(&Eq_host_, graph_stream.stream()); // 只是更改kernel参数指针中的值n_clashes_d.get_value_async(&n_clashes_h, graph_stream.stream());real_t eta = eta0 * (1 - static_cast<real_t>(iter_h) / (maxIter - 1));// Check for interrupts while copyingcheck_interrupts();// Make sure copies have finishedgraph_stream.sync();update_progress(iter_h, maxIter, eta, Eq_host_, write_per_worker,n_clashes_h);}if (results->is_sample_frame(iter_h)) {Eq_device_.get_value_async(&Eq_host_, copy_stream.stream());update_frames(results, graph_stream, copy_stream, curr_iter, curr_Eq,iter_h, Eq_host_);}}

2.4

  • 当连续执行graph多次,且存在kernel 参数更新的话,可以看到下一个graph启动与上一个graph执行存在并行,从而实现了graph的启动隐藏,并且graph执行要比kernel执行更加快,因此对于某个kernel重复执行多次且更改不大的情况下或者多流处理时,可以考虑用graph.
  • 比如一些固定输入的kernel 需要多次执行,且可以用stream并行,那么可以考虑用graph来高效执行。
    在这里插入图片描述

3. 不同版本的api

#if CUDA_VERSION < 12000cudaGraphExecUpdateResult update_result{};cudaGraphNode_t error_node = nullptr;OF_CUDA_CHECK(cudaGraphExecUpdate(graph_exec_, graph, &error_node, &update_result));if (update_result == cudaGraphExecUpdateSuccess) { return; }
#elsecudaGraphExecUpdateResultInfo update_result{};  // 新版本使用这个结构体接受OF_CUDA_CHECK(cudaGraphExecUpdate(graph_exec_, graph, &update_result));if (update_result.result == cudaGraphExecUpdateSuccess) { return; }
#endif  // CUDA_VERSION < 12000

4. 官方文档cuda graph对engine的操作

  • nvidia-doc: https://docs.nvidia.com/deeplearning/tensorrt/developer-guide/index.html#command-line-programs
// Call enqueueV3() once after an input shape change to update internal state.
context->enqueueV3(stream);// Capture a CUDA graph instance
cudaGraph_t graph;
cudaGraphExec_t instance;
cudaStreamBeginCapture(stream, cudaStreamCaptureModeGlobal);
context->enqueueV3(stream);
cudaStreamEndCapture(stream, &graph);
cudaGraphInstantiate(&instance, graph, 0);

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

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

相关文章

Linux-笔记 设备树插件

目录 前言&#xff1a; 设备树插件的书写规范&#xff1a; 设备树插件的编译&#xff1a; 内核配置: 应用背景&#xff1a; 举例&#xff1a; 前言&#xff1a; 设备树插件&#xff08;Device Tree Blob Overlay&#xff0c;简称 DTBO&#xff09;是Linux内核和嵌入式系统…

【Ardiuno】使用ESP32单片机网络功能调用API接口(图文)

接着上文连通wifi后&#xff0c;我们通过使用HTTPClient库进行网络相关操作&#xff0c;这里我们通过http协议进行接口调用。 为了简化操作&#xff0c;小飞鱼这里使用了本地服务器上的文件作为接口&#xff0c;正常操作时会调用接口后&#xff0c;将服务器返回的数据进行解析…

门控循环单元GRU与长短期记忆网络LSTM

门控循环单元与长短期记忆网络 门控隐状态 问题提出&#xff1a;对于一个序列来说不是每个观察值都是同等重要想只记住相关的观察需要&#xff1a; 能关注的机制&#xff08;更新门&#xff09;能遗忘的机制&#xff08;重置门&#xff09; 第一个词元的影响至关重要。 我们…

【第10章】Vue之Element Plus常用组件

文章目录 前言一、表格1. 带斑马纹表格2. 展示 二、分页1.国际化(中文)2.分页代码3. 展示 三、表单1. 表单代码2. 展示 四、卡片1. 卡片代码2. 展示 总结 前言 通过上一章的快速入门&#xff0c;我们已经学习了按钮使用&#xff0c;接下来学习Element Plus的常用组件&#xff…

统计信号处理基础 习题解答10-12

题目&#xff1a; 如果&#xff0c;其中&#xff1a; 对某个&#xff0c;令。证明当时使最大。另外&#xff0c;证明。它们为什么是相同的&#xff1f;如果&#xff0c;基于的的MMSE估计量是什么&#xff1f; 解答&#xff1a; 根据多维高斯分布的定义&#xff0c;可以得到&am…

有监督学习——线性回归

1. 线性模型 有监督学习是通过已知的样本产生预测模型的学习方法&#xff0c;任何有监督学习模型都可被想象成一个函数&#xff1a; 其中&#xff0c;\(x_1,x_2,x_3…x_n\)是模型的n维的特征值&#xff0c;\(y\)是要预测的目标值/分类&#xff0c;当\(y\)是可枚举的类型时&…

11.docker镜像分层dockerfile优化

docker镜像的分层&#xff08;kvm 链接克隆&#xff0c;写时复制的特性&#xff09; 镜像分层的好处&#xff1a;复用,节省磁盘空间&#xff0c;相同的内容只需加载一份到内存。 修改dockerfile之后&#xff0c;再次构建速度快 分层&#xff1a;就是在原有的基础镜像上新增了服…

2024 年最新 Python 基于百度智能云实现短语音识别、语音合成详细教程

百度智能云语音识别 采用国际领先的流式端到端语音语言一体化建模算法&#xff0c;将语音快速准确识别为文字&#xff0c;支持手机应用语音交互、语音内容分析、机器人对话等场景。百度短语音识别可以将 60 秒以下的音频识别为文字。适用于语音对话、语音控制、语音输入等场景…

CSS实现经典打字小游戏《生死时速》

&#x1f33b; 前言 CSS 中有这样一个模块&#xff1a;Motion Path 运动模块&#xff0c;它可以使元素按照自定义的路径进行移动。本文将为你讲解这个模块属性的使用&#xff0c;并且利用它实现我小时候电脑课经常玩的一个打字游戏&#xff1a;金山打字的《生死时速》。 &…

ERP、CRM、MRP、PLM、APS、MES、WMS、SRM系统介绍

一、ERP系统 ERP系统&#xff0c;即企业资源计划&#xff08;Enterprise Resource Planning&#xff09;系统&#xff0c;是一种集成管理软件系统&#xff0c;旨在帮助企业实现资源的有效管理和优化。以下是对ERP系统的详细介绍&#xff1a; 1、定义与功能 ERP是企业资源计划…

小白跟做江科大32单片机之定时器

原理部分 1. 计数器每遇到一个上升沿就会计数值1,。 72MHZ72000000 72000000/65536/655360.0167638063430786132812559.652323555555554 (s) 2. 3. 计数时钟每来一个上升沿&#xff0c;计数值1&#xff0c;自动运行。如果计数值与存储在自动重装载寄存器中的值相等&#…

2024年心理学研究、现代化教育与社会发展国际学术会议(PRMESD 2024)

2024年心理学研究、现代化教育与社会发展国际学术会议(PRMESD 2024) 2024 International Conference on Psychological Research, Modern Education and Social Development 会议地点&#xff1a;南京&#xff0c;中国 网址&#xff1a;www.prmesd.com 邮箱: prmesdsub-con…

杨氏矩阵和杨辉三角的空间复杂度较小的解题思路

文章目录 题目1 杨氏矩阵题目2 杨辉三角 题目1 杨氏矩阵 有一个数字矩阵&#xff0c;矩阵的每行从左到右是递增的&#xff0c;矩阵从上到下是递增的&#xff0c;请编写程序在这样的矩阵中查找某个数字是否存在。 要求&#xff1a;时间复杂度小于O(N); 思路: 我们可以通过题目…

谷歌重塑Transformer:无限记忆力,无限长输入,登上Nature

Infini-attention机制为Transformer在具有挑战性的长语境任务中释放出了新的能力&#xff0c;对于调整现有模型以适应长输入也非常实用。 谷歌的最新研究成果Infini-attention机制&#xff08;无限长注意力&#xff09;将内存压缩引入了传统注意力机制&#xff0c;并在单个Tra…

Github 2024-06-15Rust开源项目日报Top10

根据Github Trendings的统计,今日(2024-06-15统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Rust项目10TypeScript项目1JavaScript项目1Deno: 现代JavaScript和TypeScript运行时 创建周期:2118 天开发语言:Rust, JavaScript协议类型:M…

重装了mysql,然后安装为服务时,net start 启动一直报错,MySQL服务无法启动的解决

之前写过一篇关于安装mysql的文章&#xff0c;按上面的处理&#xff0c;基本上是可以的。 今天换了下目录&#xff0c;重新安装&#xff0c;一直报错。 然后我们来看一下问题&#xff1a; mysqld -console 这里的目录是有问题的&#xff0c;设置的是&#xff1a;datadird:\to…

个人关于Leecode 49题见解(保姆级)

题目&#xff1a; 49. 字母异位词分组 中等 相关标签 相关企业 给你一个字符串数组&#xff0c;请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。 字母异位词 是由重新排列源单词的所有字母得到的一个新单词。 示例 1: 输入: strs ["eat", "…

AI助力密码安全:利用机器学习提升密码安全性

信息安全已经成为了当今数字世界的一个核心问题&#xff0c;随着互联网技术使用场景的不断增加&#xff0c;创建和管理安全的密码已经成为了保证在线账户安全的关键要求。本文将研究和探讨如何利用人工智能&#xff08;AI&#xff09;和机器学习技术来提升密码的安全性。 学习目…

GraphQL(9):Spring Boot集成Graphql简单实例

1 安装插件 我这边使用的是IDEA&#xff0c;需要先按照Graphql插件&#xff0c;步骤如下&#xff1a; &#xff08;1&#xff09;打开插件管理 在IDEA中&#xff0c;打开主菜单&#xff0c;选择 "File" -> "Settings" (或者使用快捷键 Ctrl Alt S …

11.3 Go 标准库的使用技巧

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…