[CUDA编程] cuda graph优化心得

news2024/11/14 11:16:13

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=2
  memsetParams.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 capture
    capture_stream.capture_start();
    // Y update
    wtsneUpdateYKernel<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) update
    cub::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
    // 0c369b209ef69d91016bedd41ea8d0775879f153
    const 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 meter
        Eq_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 copying
        check_interrupts();

        // Make sure copies have finished
        graph_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_);
      }
    }

3. 不同版本的api

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

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

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

相关文章

使用fvm切换flutter版本

切换flutter版本 下载fvm 1、dart pub global activate fvm dart下载fvm 2、warning中获取下载本地的地址 3、添加用户变量path&#xff1a; 下载地址 终端查看fvm版本 fvm --version 4、指定fvm文件缓存地址 fvm config --cache-path C:\src\fvm&#xff08;自定义地址&…

北京多商入驻app开发项目的主要优势及功能

多商入驻app开发项目的定义 随着电子支付技术的不断成熟&#xff0c;全国各地的消费者通过网络在线上购物的频率越来越高&#xff0c;为此&#xff0c;多商入驻app开发项目应用而生。各商家也纷纷开始申请入驻商城平台&#xff0c;开设自己的店铺。 图片来源&#xff1a;unspl…

【Ardiuno】实验使用ESP32单片机连接Wifi(图文)

ESP32单片机最为精华和有特色的地方当然是wifi连接&#xff0c;这里我们就写程序实验一下适使用ESP32主板连接wifi&#xff0c;为了简化实验我们这里只做了连接部分&#xff0c;其他实验在后续再继续。 由于本实验只要在串口监视器中查看结果状态即可&#xff0c;因此电路板上…

Ubuntu server 24 (Linux) lvm 动态扩容磁盘空间

1 查看磁盘信息 sudo fdisk -l 2 查看lvm分区信息 sudo lvdisplay 3 扩展逻辑卷 sudo lvextend -l 100%FREE /dev/ubuntu-vg/ubuntu-lv 4 刷新逻辑卷 sudo resize2fs /dev/ubuntu-vg/ubuntu-lv 5 查看磁盘信息 df -h

[Shell编程学习路线]——编制第一个shell脚本入门篇

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f6e0;️Shell编程专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年6月12日10点23分 &#x1f004;️文章质量&#xff1a;93分 目录 ——前言—— &#x1f4a5;常用的几种shell Bash Sh …

【吊打面试官系列-Mysql面试题】什么是通用 SQL 函数?

大家好&#xff0c;我是锋哥。今天分享关于 【什么是通用 SQL 函数&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; 什么是通用 SQL 函数&#xff1f; 1、CONCAT(A, B) – 连接两个字符串值以创建单个字符串输出。通常用于将两个或多个字段合并为一个字段。 10…

40V/1A 步进电机驱动芯片SS6810R兼容BD68610

SS6810R是一款功能丰富的PWM电流驱动的双极低功耗电机驱动集成芯片&#xff0c;其工作电压范围&#xff1a;10V&#xff5e;40V&#xff1b;有两路H桥驱动&#xff0c;输出40V/1A&#xff1b;具有较大的输出能力和多种保护功能。它适用于各种电机驱动应用&#xff0c;能够提供稳…

Apollo9.0 PNC源码学习之Control模块(三)

本文将对Apollo的纵向控制器进行讲解&#xff0c;看完本文&#xff0c;你将会对百度Apollo的纵向控制有更深的理解 前面文章&#xff1a; Apollo9.0 PNC源码学习之Control模块&#xff08;一&#xff09; Apollo9.0 PNC源码学习之Control模块&#xff08;二&#xff09; 1 纵向…

c语言回顾-函数递归

1.递归的介绍 1.1什么是递归 递归是指在一个函数的定义中调用自身的过程。简单来说&#xff0c;递归是一种通过重复调用自身来解决问题的方法。 递归包括两个关键要素&#xff1a;基本情况和递归情况。基本情况是指当问题达到某个特定条件时&#xff0c;不再需要递归调用&am…

6.7.32 用于计算机辅助检测和诊断研究的精选乳房 X 线摄影数据集

由于在乳房 X 线摄影决策支持系统领域缺乏标准的评估数据集&#xff0c;已发表的研究结果很难复制&#xff1b;大多数乳房 X 线摄影中乳腺癌的计算机辅助诊断 (CADx) 和检测 (CADe) 算法都是在私人数据集或公共数据库的未指定子集上进行评估的。这导致无法直接比较方法的性能或…

MyBatis插件机制介绍与原理

插件简介 什么是插件 插件是一种软件组件&#xff0c;可以在另一个软件程序中添加功能或特性。插件通常被设计成可以 随时添加或删除 的&#xff0c;而不影响 主程序 的功能。插件可以 扩展 软件程序的功能&#xff0c;这让用户可以根据自己的需求定制软件&#xff0c;提高工作…

flutter报错You are currently using Java 1.8

flutter报错Could not run phased build action using connection to Gradle distribution ‘https://services.gradle.org/distributions/gradle-7.6.3-all.zip’.\r\norg.gradle.api.ProjectConfigurationException: A problem occurred configuring root project ‘android’…

物联网安全的优秀实践以及七种策略

大多数物联网安全漏洞都是可以预防的&#xff0c;甚至可能是全部。看看任何引人注目的物联网攻击&#xff0c;都会发现一个已知的安全漏洞。 2019年的Ring智能摄像头漏洞?用户可以创建弱密码并跳过多因素身份验证。2021年的Verkada监视服务攻击?该公司的系统中有太多的超级管…

SAP SO定价上面2个ZPR1 其中一个不活跃

查看价格表 取定价的时候排除不活动的 即可

冯喜运:6.12今日黄金原油行情还会涨吗?黄金原油独家操作策略

【黄金消息面分析】&#xff1a;据荷兰国际集团(ING)大宗商品策略师埃瓦?曼西(Ewa Manthey)称&#xff0c;黄金价格正面临来自美元走强和中国需求疲软的新阻力&#xff0c;但一旦美联储开始降息&#xff0c;黄金价格将恢复反弹。      【黄金技术面分析】&#xff1a;黄金…

易保全网络赋强公证系统,“公证赋强+科技赋能”双重增信

网络赋强公证系统是一种创新的法律服务模式&#xff0c;旨在通过线上方式赋予债权文书强制执行效力。具体来说&#xff0c;该系统结合了互联网技术与公证业务&#xff0c;允许公证机构根据当事人的申请&#xff0c;利用互联网公证技术手段对互联网上的债权文书进行公证&#xf…

基于深度学习的图像边缘和轮廓提取

导读&#xff1a;边缘和轮廓的提取是一个非常棘手的工作&#xff0c;细节也许就会被过强的图像线条掩盖&#xff0c;纹理&#xff08;texture&#xff09;本身就是一种很弱的边缘分布模式&#xff0c;分级&#xff08;hierarchical&#xff09;表示是常用的方法&#xff0c;俗称…

PWN环境配置

虚拟机安装 镜像下载网站(http://old-releases.ubuntu.com/releases/)虚拟机建议硬盘 256 G 以上&#xff0c;内存也尽量大一些。硬盘大小只是上界&#xff0c;256 G 不是真就占了 256G&#xff0c;而后期如果硬盘空间不足会很麻烦。lsb_release -a查看版本更换 ubuntu 镜像源…

【教程】怎么给网站添加弹窗广告代码javascript

由于最近支付宝悬赏领红包活动比较多邀请别人扫码自己也有奖励于是就想到了给自己网站上添加一个这种弹窗广告用户可以自己领取红包 效果图 代码也很简单下面附上代码 首先引入jquery <script src”https://pay.codewo.cn/static/index/user/assets/vendor/libs/jquery/j…

绘出你的梦中情人,AI绘画Stable Diffusion 万金油模型推荐 ,助你快速涨粉!

嘿&#xff0c;大家好&#xff0c;我是向阳 到目前为止&#xff0c;我已经分享了近百篇AI绘画类的文章教程以及模型分享 其中有些模型已经无法下载了&#xff0c;原因懂得自懂 你是否也和我一样&#xff0c;每天看着这样的小姐姐乐不思蜀&#xff0c;简单的提示词就能实现你…