[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.rhkb.cn/news/349988.html

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

相关文章

基于springboot实现火锅店管理系统项目【项目源码+论文说明】

基于springboot实现火锅店管理系统演示 摘要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装火锅店管理系统软件来…

MySQL与PostgreSQL关键对比四(关联查询性能)

引言&#xff1a;MySQL单表的数据规模一般建议在百万级别&#xff0c;而PostgreSQL的单表级别一般可以到亿级&#xff0c;如果是MPP版本就会更多。从基础数据建议上&#xff0c;不难看出&#xff0c;MySQL在Join的情况下也就是主要查询的情况下性能和PostgreSQL相差还是很大的。…

javaWeb项目-ssm+vue在线购物系统功能介绍

本项目源码&#xff1a;java-ssmvue在线购物系统的设计与实现源码说明文档资料资源-CSDN文库 项目关键技术 开发工具&#xff1a;IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架&#xff1a;ssm、Springboot 前端&#xff1a;Vue、ElementUI 关键技术&#xff1a;sprin…

如何阅读?从阅读中学阅读—《海绵阅读法》

大家好&#xff0c;我是老三&#xff0c;最近读了《海绵阅读法&#xff1a;如何吸收一本书的精华》&#xff0c;第一次阅读教如何阅读的书&#xff0c;整理一番读书笔记&#xff0c;分享给大家。 读书动机 我前一阵子写了篇文章&#xff0c;2024Q1&#xff0c;盘点我看过的54本…

手机IP地址距离多远会变:解析移动设备的网络定位奥秘

在移动互联网时代&#xff0c;手机IP地址扮演着至关重要的角色&#xff0c;它不仅是我们访问网络的基础&#xff0c;还常常与网络定位、地理位置服务等相关联。那么&#xff0c;手机IP地址在距离多远时会发生变化呢&#xff1f;手机IP地址距离多远会变&#xff1f;下面跟着虎观…

Maven认识与学习

1. Maven介绍 1.2 初识Maven 1.2.1 什么是Maven Maven是Apache旗下的一个开源项目&#xff0c;是一款用于管理和构建java项目的工具。 官网&#xff1a;Maven – Welcome to Apache Maven Apache 软件基金会&#xff0c;成立于1999年7月&#xff0c;是目前世界上最大的最受…

【学习笔记】C++每日一记[20240612]

给定两个有序的数组&#xff0c;计算两者的交集 给定两个有序整型数组&#xff0c;数组中 的元素是递增的&#xff0c;且各数组中没有重复元素。 第一时间解法&#xff1a;通过一个循环扫描array_1中的每一个元素&#xff0c;然后利用该元素去比较array_2中的每一个元素&…

解决 Visual C++ 17.5 __cplusplus 始终为 199711L 的问题

目录 软件环境问题描述查阅资料解决问题参考文献 软件环境 Visual Studio 2022, Visual C, Version 17.5.4 问题描述 在应用 https://github.com/ToniLipponen/cpp-sqlite 的过程中&#xff0c;发现源代码文件 sqlite.hpp 中&#xff0c;有一处宏&#xff0c;和本项目的 C L…

ChatGPT中文镜像网站分享

ChatGPT 是什么&#xff1f; ChatGPT 是 OpenAI 开发的一款基于生成预训练变换器&#xff08;GPT&#xff09;架构的大型语言模型。主要通过机器学习生成文本&#xff0c;能够执行包括问答、文章撰写、翻译等多种文本生成任务。截至 2023 年初&#xff0c;ChatGPT 的月活跃用户…

vscode中模糊搜索和替换

文章目录 调出搜索&#xff08;快捷键&#xff09;使用正则&#xff08;快捷键&#xff09;替换&#xff08;快捷键&#xff09;案例假设给定文本如下目标1&#xff1a;查找所有函数名目标2&#xff1a;替换所有函数名为hello目标3&#xff1a;给url增加查询字符串参数 调出搜索…

网络超时

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 在访问一个网页时&#xff0c;如果该网页长时间未响应&#xff0c;系统就会判断该网页超时&#xff0c;所以无法打开网页。下面通过代码来模拟一个网…

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

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

【Redis】String的常用命令及图解String使用场景

本文将详细介绍 Redis String 类型的常见命令及其使用场景&#xff0c;包括缓存、计数器、共享会话、手机验证码、分布式锁等场景&#xff0c;并且配图和伪代码进一步方便理解和使用。 命令执行效果时间复杂度set key value [key value…]设置key的值是valueO(k),k是键个数get…

长难句打卡6.14

When public opinion is particularly polarized, as it was following the end of the Franco regime, monarchs can rise above “mere” politics and “embody” a spirit of national unity. 当公众舆论严重分化时&#xff0c;正如佛朗哥执政未期那样&#xff0c;君主们就…

Vulnhub-DC-9

靶机IP:192.168.20.144 kaliIP:192.168.20.128 网络有问题的可以看下搭建Vulnhub靶机网络问题(获取不到IP) 信息收集 nmap扫描一下端口及版本号 dirsearch扫目录 最后去前端界面观察发现也没什么隐藏路径。 观察功能&#xff0c;search引起注意&#xff0c;SQL注入测试 当输…

AI论文速读 | 2024[SIGIR]基于大语言模型的下一个兴趣点推荐

论文标题&#xff1a;Large Language Models for Next Point-of-Interest Recommendation 作者&#xff1a;Peibo Li ; Maarten de Rijke ; Hao Xue &#xff08;薛昊&#xff09;; Shuang Ao ; Yang Song ; Flora D. Salim 机构&#xff1a;新南威尔士大学(UNSW)&#xff0c…

Uni-App中的u-datetime-picker时间选择器Demo

目录 前言Demo 前言 对于网页端的推荐阅读&#xff1a;【ElementUI】详细分析DatePicker 日期选择器 事情起因是两个时间选择器同步了&#xff0c;本身是从后端慢慢步入全栈&#xff0c;对此将这个知识点从实战进行提炼 通过Demo进行总结 Demo 用于选择日期和时间的组件&a…

【TB作品】MSP430G2553,DS1302,LCD1602,时间读取和显示,万年历,Proteus仿真

效果 部分代码 #include <MSP430.h> #include "ds1302.h" #include "LCD.h"//关掉ccs优化&#xff0c;并且Convert_BCD_To_Dec函数中只能是10.0f才行&#xff0c;不然有bugvoid main(void) {char cnt 0;char disp[16];WDTCTL WDTPW WDTHOLD; /* …

基于springboot实现农产品直卖平台系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现农产品直卖平台系统的设计演示 摘要 计算机网络发展到现在已经好几十年了&#xff0c;在理论上面已经有了很丰富的基础&#xff0c;并且在现实生活中也到处都在使用&#xff0c;可以说&#xff0c;经过几十年的发展&#xff0c;互联网技术已经把地域信息的隔…

错题记录(小测)

单选 错题1 错题2 错题3 代码题 反转链表 链表的回文结构