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

news2025/7/18 5:45:49

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

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


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



利用蒙特卡罗法求解 𝜋 的近似值(CUDA-Aware MPI)

在这里插入图片描述

CUDA-Aware MPI

       MPI 帮助我们清理了在显式管理多个设备时使用的样板程序,但也牺牲了多个 GPU 之间直接对话的好处。MPI 是一种分布式内存并行编程模型,其中每个处理器都有自己的(虚拟)内存和地址空间,即使所有成员都在同一服务器上并因此共享相同的物理内存也不例外。(通常情况下,与之不同的是共享内存并行编程模型 ,其中每个处理线程都可以访问相同的内存空间,如 OpenMP。类似的还有传统的单 GPU CUDA 编程,其中所有线程都可以访问全局内存。) 因此我们将每个 GPU 的结果复制到 CPU,然后在 CPU 上求和。

       但只要我们停留在单个服务器上,CUDA 通用地址空间的规则仍然有效,因此所有通过 CUDA 分配内存的结果都是虚拟地址,可以在进程之间有意义地共享(即使通常的 CPU 动态分配的内存不能实现这一点也无妨)。因此,MPI 可以在底层直接实现点对点内存拷贝。对于远程服务器之间的通信,这样是不行的,但还有其它技术允许 GPU 通过网络接口直接与远端 GPU 实现通信,尤其是 GPUDirect RDMA。由于认识到利用这些技术进行有效通信的价值,许多 MPI 的实现(包括我们使用的 OpenMPI)提供了CUDA-Aware MPI,程序员可以利用它向MPI通信函数提供设备内存的地址,然后 MPI 可以自由使用任何通信方案实现从一个 GPU 传输数据到另一个 GPU,包括使用 GPUDirect P2PGPUDirect RDMA,只要合适就行。(请注意,尽管经常看到将 GPUDirect 和CUDA-Aware MPI 这两个术语错误的合并在一起,但前者指的是一系列技术,后者指的是可以在底层使用这些技术的 API。)

请添加图片描述

因此,CUDA-Aware MPI 提供了简化编程的好处,同时保留了避免不必要的将数据拷贝到 CPU 内存的性能优势。请谨记,撰写最终归约的方法之一是……

MPI_Reduce(d_hits, total_hits, 1, MPI_INT, MPI_SUM, root, MPI_COMM_WORLD);

……在此方法中,MPI 会自动发现发送缓冲区 d_hits 驻留在设备上,而接收缓冲区total_hits驻留在主机上,并会在幕后进行正确操作来启动数据拷贝。


一个例子

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

#include <mpi.h>

#define N 1024*1024

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

    // 初始化随机数状态(网格中的每个线程不得重复)
    int seed = device;
    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) {
    // 初始化 MPI
    MPI_Init(&argc, &argv);

    // 获取我们的rank和rank总数
    // MPI_COMM_WORLD 意味着我们想要包含所有进程
    // (可以在 MPI 中创建仅
    // 包含某些rank的“通信器”)。

    int rank, num_ranks;
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &num_ranks);

    // 确保我们的rank个数不超过 GPU 数量

    int device_count;
    cudaGetDeviceCount(&device_count);

    if (num_ranks > device_count) {
        std::cout << "Error: more MPI ranks than GPUs" << std::endl;
        return -1;
    }

    // 每个rank(任意)选择与其rank对应的 GPU
    int dev = rank;
    cudaSetDevice(dev);

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

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

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

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

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

    // 将所有rank的结果累加到第0号 rank 的结果中
    int* d_total_hits;
    cudaMalloc((void**) &d_total_hits, sizeof(int));

    int root = 0;
    MPI_Reduce(d_hits, d_total_hits, 1, MPI_INT, MPI_SUM, root, MPI_COMM_WORLD);

    if (rank == root) {
        // 将结果复制回主机
        int* total_hits = (int*) malloc(sizeof(int));
        cudaMemcpy(total_hits, d_total_hits, sizeof(int), cudaMemcpyDeviceToHost);

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

        // 打印结果
        std::cout << "Estimated value of pi = " << pi_est << std::endl;
        std::cout << "Error = " << std::abs((M_PI - pi_est) / pi_est) << std::endl;
    }

    // 清理
    free(hits);
    cudaFree(d_hits);

    // 最终确定 MPI
    MPI_Finalize();

    return 0;
}

编译命令(只做参考)

nvcc -ccbin=mpicxx -x cu -arch=sm_70 -o monte_carlo_mgpu_cuda_mpi_cuda_aware exercises/monte_carlo_mgpu_cuda_mpi_cuda_aware.cpp
mpirun -np $NUM_DEVICES ./monte_carlo_mgpu_cuda_mpi_cuda_aware

运行结果(只做参考)

Estimated value of pi = 3.14072
Error = 0.000277734

总结

您现在已经掌握了管理多个 GPU 以分配并行工作负载的多种方法,其中包括在设备上显式地循环,以及使用 MPI 隐式地使用多台设备的做法。您还了解了处理 GPU 之间数据传输的多种方法,包括直接点对点内存访问、点对点 CUDA 内存拷贝和通过 CPU 内存的数据拷贝。

希望您已借此更好地了解了每种方法的优缺点。值得一提的是,CUDA-Aware MPI 的方法十分出众,可实现超高性能

但即使是CUDA-Aware MPI,我们也必须返回到 CPU 来启动传输。对于许多应用而言,尤其是那些由于 GPU 在传统计算中的高效率而遭遇扩展性限制的应用,返回 CPU 的延迟可能会造成严重的性能损失。如果我们能够拥有类似 MPI 的 SPMD 的特性,从而实现高度可理解的编程,同时保留直接从核函数启动传输的潜在性能优势,岂不是两全其美? 接下来,我们将介绍 NVSHMEM,并演示其正好提供了上述好处。

请添加图片描述



在这里插入图片描述

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

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

相关文章

11.24直播预告 | AIGC,看热闹不如看门道

如果把AI比作既富含能源&#xff0c;又无限神秘的海域&#xff0c;那么AIGC这条近半年内流经学术界和创投圈的支流&#xff0c;也无疑既带来了无数机遇&#xff0c;又蕴藏未知风险。 几天前&#xff0c;将门-TechBeat社区的专题&#xff08;《AIGC的八大前沿创新》&#xff09;…

Allegro基本规则设置指导书

Allegro基本规则设置指导书 下面介绍基本规则设置指导书之Analysis Modes 点击set-up-constrains-Modes 调出Analysis Modes,这个是所有DRC的总开关 下面介绍常用的一些开关设置 Design Options (Soldermask) 从上往下 阻焊到阻焊的间距 阻焊到pad和走线间距 阻焊到shape…

leecode #加一#二进制求和

题目描述&#xff1a; 给定一个由 整数 组成的 非空 数组所表示的非负整数&#xff0c;在该数的基础上加一。 最高位数字存放在数组的首位&#xff0c; 数组中每个元素只存储单个数字。 你可以假设除了整数 0 之外&#xff0c;这个整数不会以零开头。 分析&#xff1a;对数…

简单版的采用前后端分离模式实现SpingBoot新增查询功能

目录 后端代码编写 前端代码编写 首页展示&#xff1a; 新增&#xff1a; 开发工具&#xff1a;IDEA、HbuilderX 技术点&#xff1a;后端&#xff1a;SpringBoot&#xff0c;前端&#xff1a;ElementUIvue,采用前后端分离模式实现。 后端代码编写 目录&#xff1a; 代码…

多线程高并发笔记

一、基础知识 1. 线程打断的三种方法 interrupt() 打断某个线程&#xff08;其实只是设置一个标志位&#xff09;isInterrupted() 查询某线程是否被打断过&#xff08;查询是否设置了标志位&#xff09;static interrupted() 查询当前线程是否被打断过&#xff0c;并重置打断…

计算机毕业设计ssm+vue基本微信小程序的今日菜谱系统

项目介绍 谈到外出就餐,我们除了怕排队,也怕这家餐厅的服务员不够用,没人为我们点餐,那么一餐饭排队一小时,点餐恐怕也要花个半小时,这样不仅给消费者的用餐体验大打折扣同时也给商家的口碑造成了严重负面的影响,所以开发今日菜谱微信小程序系统是必须也是必然的。 本系统采用微…

vue2 sass 安装及使用

最近在看前端。其一因为手里有个项目uniapp的里面使用了sass&#xff0c;但是有sass报错&#xff0c;其次想自己写个vue2后台&#xff0c;感觉sass写起来科学点。但是……版本这个东西……太费劲了…… vue2-cli集成webpack&#xff0c;使用vue create 项目名后再安装sass相当…

C++程序设计期末考试复习试题及解析 3(自用~)

DDL1.题目及分析1.对象数组的析构顺序2.浅拷贝的隐患delete p 还是 delete[]p ?类似的题&#xff0c;自行查阅3.常数据成员的初始化4.默认构造函数5.cin、cout所属类6.重载7.静态数据成员8.多态8.联编9.内联函数10.引用11.static12.构造Complex类13.静态成员函数14.抽象类15.标…

[一篇读懂]C语言二讲:运算符与表达式

[一篇读懂]C语言二讲&#xff1a;运算符与表达式1. 算术运算符与关系运算符1 运算符分类2 算术运算符及算术表达式3 关系运算符与关系表达式【例】关系运算符的使用。4 运算符优先级2. 逻辑运算符与赋值运算符&#xff0c;求字节运算符1 逻辑运算符与逻辑表达式【例】逻辑运算符…

k8s基于kubectl命令管理资源并分配

文章目录一、资源管理介绍二、资源管理方式1、命令式对象管理2、命令式对象配置3、声明式对象配置一、资源管理介绍 在kubernetes中&#xff0c;所有的内容都抽象为资源&#xff0c;用户需要通过master节点操作资源来管理kubernetes。 &#xff08;1&#xff09;kubernetes的本…

数据结构串和数组练习题

串和数组 一 填空题 不包含任何字符&#xff08;长度为0&#xff09;的串称为 空串 &#xff1b;由一个或多个空格&#xff08;仅由空格符&#xff09;组成的串称为 空白串。 设S“A;/document/Mary.doc”&#xff0c;则strlen(s) 20 , “/”的字符定位的位置为 3 。 子串的…

Windows内核--CreateProcess到内核NtCreateProcess(2.3)

CreateProcessA vs CreateProcessW A和W后缀代表ANSI和UNICODE版本。早期&#xff0c;Windows为了兼容之前ANSI版本&#xff0c;为了推广UNICODE版本&#xff0c;所以做出了两套API. 注意&#xff0c;并不是所有Windows API都有*A和*W两套&#xff0c;只有API参数包含"字符…

[附源码]java毕业设计社区生鲜电商平台

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

linux文件上传和下载、别名设置以及环境变量

一、文件上传和下载 1.sftp &#xff08;1&#xff09;它类似于ftp传输协议&#xff0c;属于ssh&#xff0c;但它进行加密传输&#xff0c;相对FTP来讲有更高的安全性 &#xff08;2&#xff09;用法 [rootserver ~]# ssh root10.0.0.3 The authenticity of host 10.0.0.3 …

虚基类设计 c++

一个例子 首先大家看一个定义的一个虚基类 所谓的虚基类&#xff0c;是指类内包含virtual关键字定义的成员函数 class Abstract_base { public:virtual ~Abstract_base();virtual void interface() const 0;virtual const char* mumble() const {return m_mumble;} protect:ch…

Echarts:制作词云

Echarts是一个开源的可视化图表库&#xff0c;支持丰富的图表&#xff0c;官网中还有大量示例可以选择使用、参考。 其中比较好玩、有趣的是词云&#xff0c;词云就是用关键词组成的一朵云&#xff0c;更广泛的定义是&#xff0c;由关键词组成的任意一种图案均称为词云。因此&…

opencv 空域变换

图像变换是基于像素的映射&#xff0c;区别是像素是怎么映射的。灰度变换的话是通过点对点的映射&#xff0c;也就是变换后的像素点之和当前的像素点有关&#xff08;gramma变换、对数变换等等&#xff09;&#xff0c;依次来进行对比度拉伸。而空间滤波变换后的像素点是和当前…

LeetCode - 354 俄罗斯套娃信封问题

题目来源 354. 俄罗斯套娃信封问题 - 力扣&#xff08;LeetCode&#xff09; 题目描述 给你一个二维整数数组 envelopes &#xff0c;其中 envelopes[i] [wi, hi] &#xff0c;表示第 i 个信封的宽度和高度。 当另一个信封的宽度和高度都比这个信封大的时候&#xff0c;这个…

GreenPlum6.x之测试数据

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言一、generate_series是什么&#xff1f;二、使用步骤1.建张测试表2.插入简单的测试数据3.查看数据分散情况4.通过SQL测试性能总结前言 提示&#xff1a;这里可以…

浅谈一下:Java当中的构造方法

从前面的所学的知识&#xff0c;我们可以发现&#xff1a;实列化一个对象以后&#xff0c;如果要为这个对象的属性赋值&#xff0c;那么必须直接访问对象的属性或者调用setXxx()方法&#xff0c;如果需要在实列化对象的同时就为这个对象的属性进行赋值&#xff0c;可以通过构造…