【GPU】Nvidia CUDA 编程高级教程——利用蒙特卡罗法求解近似值(NVSHMEM)

news2024/9/28 21:25:28

博主未授权任何人或组织机构转载博主任何原创文章,感谢各位对原创的支持!
博主链接

本人就职于国际知名终端厂商,负责modem芯片研发。
在5G早期负责终端数据业务层、核心网相关的开发工作,目前牵头6G算力网络技术标准研究。


博客内容主要围绕:
       5G/6G协议讲解
       算力网络讲解(云计算,边缘计算,端计算)
       高级C语言讲解
       Rust语言讲解



利用蒙特卡罗法求解 𝜋 的近似值(NVSHMEM)

在这里插入图片描述

NVSHMEM

       NVSHMEM是一个并行编程模型,用于在多个 NVIDIA GPU 之间进行高效和可扩展的通信。NVSHMEM 依托于 OpenSHMEM 构建而成,可为横跨多个 GPU 内存的数据提供全局地址空间,并可通过细粒度的由 GPU 发起的操作、由 CPU 发起的操作和 CUDA 流操作访问该空间。NVSHMEM 为许多应用提供了令人信服的多 GPU 编程模型,对于具有高密度 GPU 和复杂互连的现代 GPU 服务器(例如NVIDIA DGX A100 服务器 的 NVIDIA NVSwitch)来说尤其有价值。

请添加图片描述

为什么使用NVSHMEM

       传统上,涉及多服务器 GPU 的通信模式可能看起来如下所示:计算发生在 GPU 上,而通信在同步 GPU 后发生在 CPU 上(确保数据发送有效)。虽然这种方法很容易编程,但会在应用的关键路径上引入初始化通信或启动核函数的延迟。我们会丧失计算与通信重叠的能力。如果我们通过流水操作工作来重叠通信和计算,延迟确实可以部分地隐藏,但代价是让应用变得更加复杂。

请添加图片描述

       相反,在使用 GPU 而不是 CPU 启动的通信模型中,我们直接利用 GPU 同时进行计算和通信。我们可以用这种方式编写细粒度的通信模式,并且可以通过 GPU 架构的本质来隐藏通信延迟(在 GPU 架构中,计算中的Warp可以继续进行,而其他的Warps则会停下来等待数据)。

请添加图片描述

启动NVSHMEM应用

       与 MPI 一样,NVSHMEM 也是具有 SPMD 编程风格的示例之一。NVSHMEM 提供了一个启动脚本1,其名为nvshmrun,可用于处理启动 𝑀 个进程。nvshmrun的参数是-np,也就是要启动的进程数,然后是应用程序的可执行文件,然后是该可执行文件的任何参数。每个独立进程又名为处理单元 (PE),有一个唯一的(零索引的)数字标识符与之相关联2

请添加图片描述

初始化及终止 NVSHMEM

作为主机端的核心需求,我们必须初始化并终止 NVSHMEM,将这两者作为程序中的第一项和最后一项。

nvshmem_init();
...
nvshmem_finalize();

获取处理单元的 ID

API 调用 nvshmem_my_pe() 返回每个 PE 的唯一数字 ID。

int my_pe = nvshmem_my_pe();
int device = my_pe;
cudaSetDevice(device);

在多节点环境中,您必须考虑到一个事实,即 CUDA 设备在每个节点中始终都是零索引的。在这种情况下,您将获得仅对该节点有意义的本地 PE 标识符 。例如,如果我们使用两个节点,每个节点有四个 GPU,那么我们将要求工作启动程序在每个节点上运行四个任务(如nvshmrun -np 8 -ppn 4 -hosts hostname1,hostname2),然后完成3

int my_pe_node = nvshmem_team_my_pe(NVSHMEMX_TEAM_NODE);
int device = my_pe_node;
cudaSetDevice(device);

编译 NVSHMEM 代码

编译看起来和以前相似,但我们现在需要为 NVSHMEM 指向相关的文件包含命令include和库目录(-I $NVSHMEM_HOME/include -L $NVSHMEM_HOME/lib -lnvshmem)以及 CUDA 驱动 API 中的链接(-lcuda)。我们还需要把#include <nvshmem.h>4#include <nvshmemx.h>5 添加到代码中。最后,我们需要添加-rdc=true以启用 浮动设备代码,这是 NVSHMEM 的一项需求。


练习1:使用带有 MC π 代码的 NVSHMEM

#include <iostream>
#include <curand_kernel.h>

#include <nvshmem.h>
#include <nvshmemx.h>

inline void CUDA_CHECK (cudaError_t err) {
    if (err != cudaSuccess) {
        fprintf(stderr, "CUDA error: %s\n", cudaGetErrorString(err));
        exit(-1);
    }
}

#define N 1024*1024

__global__ void calculate_pi(int* hits) {
    int idx = threadIdx.x + blockIdx.x * blockDim.x;

    // 初始化随机数状态(网格中的每个线程不得重复)
    int seed = 0;
    int offset = 0;
    curandState_t curand_state;
    curand_init(seed, idx, offset, &curand_state);

    // 在 (0.0, 1.0] 内生成随机坐标
    float x = curand_uniform(&curand_state);
    float y = curand_uniform(&curand_state);

    // 如果这一点在圈内,增加点击计数器
    if (x * x + y * y <= 1.0f) {
        atomicAdd(hits, 1);
    }
}


int main(int argc, char** argv) {
    // 初始化 NVSHMEM
    nvshmem_init();

    // 获取 NVSHMEM 处理元素 ID
    int my_pe = nvshmem_my_pe();

    // 每个 PE(任意)选择与其 ID 对应的 GPU
    int device = my_pe;
    CUDA_CHECK(cudaSetDevice(device));

    // 分配主机和设备值
    int* hits;
    hits = (int*) malloc(sizeof(int));

    int* d_hits;
    CUDA_CHECK(cudaMalloc((void**) &d_hits, sizeof(int)));

    // 初始化点击次数并复制到设备
    *hits = 0;
    CUDA_CHECK(cudaMemcpy(d_hits, hits, sizeof(int), cudaMemcpyHostToDevice));

    // 启动核函数进行计算
    int threads_per_block = 256;
    int blocks = (N + threads_per_block - 1) / threads_per_block;

    calculate_pi<<<blocks, threads_per_block>>>(d_hits);
    CUDA_CHECK(cudaDeviceSynchronize());

    // 将最终结果复制回主机
    CUDA_CHECK(cudaMemcpy(hits, d_hits, sizeof(int), cudaMemcpyDeviceToHost));

    // 计算 pi 的最终值
    float pi_est = (float) *hits / (float) (N) * 4.0f;

    // 打印结果
    std::cout << "Estimated value of pi on PE " << my_pe << " = " << pi_est << std::endl;
    std::cout << "Relative error on PE " << my_pe << " = " << std::abs((M_PI - pi_est) / pi_est) << std::endl;

    free(hits);
    CUDA_CHECK(cudaFree(d_hits));

    // 最终确定 nvshmem
    nvshmem_finalize();

    return 0;
}

编译和运行命令如下:

nvcc -x cu -arch=sm_70 -rdc=true -I $NVSHMEM_HOME/include -L $NVSHMEM_HOME/lib -lnvshmem -lcuda -o nvshmem_pi_step1 exercises/nvshmem_pi_step1.cpp
nvshmrun -np $NUM_DEVICES ./nvshmem_pi_step1

您运行了首个 NVSHMEM 程序,但很遗憾,我们所做的工作没什么意思,因为每个 PE 都执行同样的工作。(您可通过比较所有 PE 的输出来检查这句话是否正确。) 理想情况下,我们希望将工作分散到不同的 PE 和 GPU 上。

练习2:跨 PE 分配工作

在本练习中,每个 GPU 将 𝑁 个样本点除以 PE 的数量 𝑀 。我们可以使用 API nvshmem_n_pes()来获得:

int n_pes = nvshmem_n_pes();

然后将 𝑁 除以 n_pes就行了。为了让PE的工作更有意思,我们执行一个额外的步骤,即为每个 PE 选择各自唯一的随机数的种子,这样可以让每个 GPU 做不同的工作:

int seed = nvshmem_my_pe();
#include <iostream>
#include <curand_kernel.h>

#include <nvshmem.h>
#include <nvshmemx.h>

inline void CUDA_CHECK (cudaError_t err) {
    if (err != cudaSuccess) {
        fprintf(stderr, "CUDA error: %s\n", cudaGetErrorString(err));
        exit(-1);
    }
}

#define N 1024*1024

__global__ void calculate_pi(int* hits, int seed) {
    int idx = threadIdx.x + blockIdx.x * blockDim.x;

    // 初始化随机数状态(网格中的每个线程不得重复)
    int offset = 0;
    curandState_t curand_state;
    curand_init(seed, idx, offset, &curand_state);

    // 在 (0.0, 1.0] 内生成随机坐标
    float x = curand_uniform(&curand_state);
    float y = curand_uniform(&curand_state);

    // 如果这一点在圈内,增加点击计数器
    if (x * x + y * y <= 1.0f) {
        atomicAdd(hits, 1);
    }
}


int main(int argc, char** argv) {
    // 初始化 NVSHMEM
    nvshmem_init();

    // 获取 NVSHMEM 处理元素 ID 和 PE 数量
    int my_pe = nvshmem_my_pe();
    int n_pes = nvshmem_n_pes();

    // 每个 PE(任意)选择与其 ID 对应的 GPU
    int device = my_pe;
    CUDA_CHECK(cudaSetDevice(device));

    // 分配主机和设备值
    int* hits;
    hits = (int*) malloc(sizeof(int));

    int* d_hits;
    CUDA_CHECK(cudaMalloc((void**) &d_hits, sizeof(int)));

    // 初始化点击次数并复制到设备
    *hits = 0;
    CUDA_CHECK(cudaMemcpy(d_hits, hits, sizeof(int), cudaMemcpyHostToDevice));

    // 启动核函数进行计算
    int threads_per_block = 256;
    int blocks = (N / n_pes + threads_per_block - 1) / threads_per_block;

    int seed = my_pe;
    calculate_pi<<<blocks, threads_per_block>>>(d_hits, seed);
    CUDA_CHECK(cudaDeviceSynchronize());

    // 将最终结果复制回主机
    CUDA_CHECK(cudaMemcpy(hits, d_hits, sizeof(int), cudaMemcpyDeviceToHost));

    // 计算 pi 的最终值
    float pi_est = (float) *hits / (float) (N / n_pes) * 4.0f;

    // 打印结果
    std::cout << "Estimated value of pi on PE " << my_pe << " = " << pi_est << std::endl;
    std::cout << "Relative error on PE " << my_pe << " = " << std::abs((M_PI - pi_est) / pi_est) << std::endl;

    free(hits);
    CUDA_CHECK(cudaFree(d_hits));

    // 最终确定 nvshmem
    nvshmem_finalize();

    return 0;
}

编译和运行命令如下:

nvcc -x cu -arch=sm_70 -rdc=true -I $NVSHMEM_HOME/include -L $NVSHMEM_HOME/lib -lnvshmem -lcuda -o nvshmem_pi_step2 exercises/nvshmem_pi_step2.cpp
nvshmrun -np $NUM_DEVICES ./nvshmem_pi_step2


在这里插入图片描述


  1. nvshmrun本质上是一个到 Hydra 流程管理器的符号链接。虽然我们演示了 NVSHMEM 在独立运行的计算机中的使用情况,但 NVSHMEM 可与 MPI 作业启动环境兼容,比如它也可以与 Slurm 一起使用。如果使用 MPI 或 OpenSHMEM 启动作业,则相关代码修改如下所示。对于 MPI,我们首先初始化 MPI,然后在 MPI 上引导 NVSHMEM 初始化。关闭时,我们要先终止 NVSHMEM,然后终止 MPI。

    int main() {
        MPI_Init(&argc, &argv);
    
        nvshmemx_init_attr_t attr;
        MPI_Comm comm = MPI_COMM_WORLD;
        attr.mpi_comm = &comm;
    
        nvshmemx_init_attr(NVSHMEMX_INIT_WITH_MPI_COMM, &attr);
    
        // ...
    
        nvshmem_finalize();
    
        MPI_Finalize();
    
        return 0;
    }
    

    在 OpenSHMEM 作业中,我们会改用以下做法

    int main() {
        shmem_init();
    
        nvshmemx_init_attr_t attr;
        nvshmemx_init_attr(NVSHMEMX_INIT_WITH_SHMEM, &attr);
    
        // ...
    
        nvshmem_finalize();
    
        shmem_finalize();
    
        return 0;
    }
    
    ↩︎
  2. NVSHMEM 作为 OpenSHMEM 的实现,有许多术语都与 OpenSHMEM 相通(如 PE),并具有非常相似的 API。熟悉 MPI 的读者会发现 PE 类似于 MPI rank。 ↩︎

  3. API nvshmem_team_my_pe() 是 NVSHMEM 2.0 中的新功能。请查看这篇博客,了解更多信息。 ↩︎

  4. nvshmem.h提供符合 OpenSHMEM 标准的 API,类似于 nvshmem_*。 ↩︎

  5. nvshmemx.h提供 NVIDIA 专用的扩展程序,类似于nvshmemx_*。 ↩︎

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

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

相关文章

MyBatis + SQL Server Using Table-Valued Parameters

一、实现原理 参考文档 Using table-valued parametersSystem requirements for the JDBC driverMicrosoft JDBC Driver for SQL Server1、微软官方封装了 JDBC 驱动 jar 包&#xff0c;提供 SQLServerDataTable 类&#xff1b; 2、Mybatis 官方提供自定义类型处理接口 TypeHa…

Python学习笔记-Pygame

目录 一、Pygame概述 1.安装Pyganme 2.Pygame常用模块介绍 2.1 display模块常用方法 2.2 pygame.event模块常用方法 2.3 Surface对象的常用方法 记述关于Pyganme开发的基本知识。 一、Pygame概述 Pygame是跨平台的python模块&#xff0c;转为电子游戏设计&#xff08;包…

Vue打包后的不同版本解析

vue源码打包版本 这里选取我们开发中常见的几个版本进行说明。 1、vue(.runtime).global(.prod).js 在html页面中通过 <script src“...”> 标签直接使用。通过CDN引入和npm下载的Vue就是这个版本。会暴露一个全局的Vue来使用。&#xff08;.runtime&#xff09;和&…

2022年12月python的字符串常用操作

字符串在整整个开发的过程中&#xff0c;使用频率相对来说是较高的。 在此总结几个字符串的常用操作&#xff0c; 字符串的操作&#xff0c;转换后即生成为新字符串 【长度统计 切片&#xff1a; 【 根据索引进行切片str[开始索引:结束索引:步长] 根据指定标识符进行切片str.sp…

【码极客精讲】桶排序

桶排序 (Bucket sort)或所谓的箱排序&#xff0c;是一个排序算法&#xff0c;工作的原理是将数组分到有限数量的桶子里。每个桶子再个别排序&#xff08;有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序&#xff09;。桶排序是鸽巢排序的一种归纳结果。当要被排…

【硬币识别】形态学硬币计数【含Matlab源码 683期】

⛄一、硬币图像识别简介 本设计为硬币图像识别统计装置&#xff0c;通过数码相机获取平铺无重叠堆积的硬币的图像&#xff0c;并通过Matlab工具处理后统计硬币的数目。 1 图像格式转换 取的图像格式为RGB彩色图像&#xff0c;需要先将其转换为8位256级的灰度图像。本程序采用…

SAP Gateway 里的 REST 概念

SAP Gateway 有助于轻松配置和使用 SAP Business Suite 系统的业务逻辑和内容&#xff0c;用于移动和 Web 应用程序。它降低了访问 SAP 数据所需的复杂性和技能组合&#xff0c;从而消除了部署障碍。使用简单的界面有助于缩短开发时间。 SAP Gateway 使以人为本的应用程序能够…

【笔记:模拟CMOS集成电路】MOS特性仿真分析

【笔记&#xff1a;模拟CMOS集成电路】MOS特性仿真分析前言一、电路图二、电路仿真&#xff08;1&#xff09;Ids与Vds的关系仿真仿真结果仿真结果分析&#xff08;2&#xff09;Ids与Vgs的关系仿真仿真结果仿真结果分析前言 本文为本人学习模拟集成电路相关知识的的学习笔记&a…

USB接口WIFI(MT7601芯片)的驱动源码移植过程详解(驱动源码编译、wpa_supplicant工具交叉编译、文件系统移植)

1、MT7601的移植步骤 (1)确认你的WT7601网卡硬件是正常的&#xff1b; (2)修改驱动源码&#xff0c;依赖内核源码树编译并加载&#xff1b; (3)交叉编译wpa_supplicant工具&#xff0c;移植到根文件系统里&#xff1b; (4)添加驱动和wpa_supplicant工具依赖的配置文件&#xff…

2022 年时间序列分析最顶流的 Python 库

时间序列分析是一种强大的工具&#xff0c;可用于从数据中提取有价值的信息并对未来事件进行预测。它可用于识别趋势、季节性模式和变量之间的其他关系。时间序列分析还可用于预测未来事件&#xff0c;例如销售、需求或价格变动。 如果你在 Python 中处理时间序列数据&#xf…

数据库实验三:完整性语言实验

实验三 完整性语言实验 实验 3.1 实体完整性实验 1.实验目的 ​ 掌握实体完整性的定义和维护方法。 2.实验内容和要求 ​ 定义实体完整性&#xff0c;删除实体完整性。能够写出两种方式定义实体完整性的SQL语句&#xff1b;创建表时定义实体完整性、创建表后定义实体完整性…

C++ Reference: Standard C++ Library reference: Containers: map: map: key_comp

C官网参考链接&#xff1a;https://cplusplus.com/reference/map/map/key_comp/ 公有成员函数 <map> std::map::key_comp key_compare key_comp() const;返回键比较对象 返回容器用于比较键的比较对象的副本。map对象的比较对象在构造&#xff08;construction&#xff…

kubernetes 挂载传播

kubernetes 挂载传播 kubernetes 的 mountPropagation 翻译成中文就是挂载传播。挂载传播提供了共享卷挂载的能力, 它允许在同一个 Pod, 甚至同一个节点内, 在多个容器之间共享卷的挂载。 说白了就是在容器或 host 内的挂载目录中 再 mount 了一个别的挂载。 kubernetes 中 卷…

第18章 条件概率

第18章 条件概率 18.1蒙特霍尔困惑 对于上一章的三个门的问题&#xff0c;有一个漏洞。假设参赛者选择门A且门B后有一只山羊&#xff0c;刚好产生3个结果&#xff1a; 以上结果出现的概率分别是1/18,1/18,1/9。 在这些结果中&#xff0c;只有最后一个结果(C,A,B)&#xff0c…

Redis常见面试题(三)

目录 1、Redis String值最大存储多少? 2、Redis事务有什么用? 3、Redis事务相关的命令有哪几个? 4、Redis事务是原子性的吗? 5、Redis持久化有什么用? 6、Redis有哪几种持久化方式? 7、Redis持久化方式如何选择? 8、如何保证Redis中的数据都是热点数据? 9、Red…

vue前后端分离项目打包成app,部署成移动端

将vue项目打包成app,在手机上运行。 1. vue打包 npm run build 先将vue的前端项目打包成dist文件夹 2. 安装hbuilderX Hbuilder官网地址 3. hbuilderX 1&#xff09;新建项目 我是vue的&#xff0c;所以直接选择的h5app&#xff0c;然后起个名字&#xff0c;选择路径。 2…

微服务实用篇6-分布式搜索elasticsearch篇2

今天我们继续学习分布式搜索引擎elasticsearch&#xff0c;今天主要学习四个模块&#xff0c;分别为DSL查询文档&#xff0c;搜索结果处理&#xff0c;RestClient查询文档&#xff0c;还有最好演示一个旅游案例。下面开始今天的学习吧。 目录 一、DSL查询文档 1.1、DSL查询分…

Hadoop学习----软件安装

Hadoop源码下载重新编译 软件下载&#xff1a;https://hadoop.apache.org/releases.html 建议是下载源码包。 源码包和官方编译安装包有什么不一样呢&#xff1f; 正常情况下&#xff0c;非生产环境直接使用官方编译安装包即可&#xff0c;但是官方提供的安装包不支持本地库。…

ANTLR4入门(二):图示说明eclipse安装Antlr4IDE插件的过程

如果你能正常通过Eclipse Market找到antlr4的插件并正常安装&#xff0c;可以忽略本文。 如果不能&#xff0c;那多半是因为网络问题导致安装Antlr4IDE插件时无法下载文件造成的。我就遇到了这个问题&#xff0c;无法下载的原因很复杂&#xff0c;我不想去深究了&#xff0c;我…

WPF/XAML关于x:key和x:name的区别,全面解读超详细

x:key和x:name的区别 x:Keyx:Name用于xaml Resources&#xff0c;ResourceDictionary用在ResourceDictionary以外任何地方使用key访问xaml指定对象使用name访问xaml对象标识资源创建和引用&#xff0c;存在于 ResourceDictionary 中的元素唯一标识对象元素&#xff0c;以便于从…