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

2024-01-20 15:10

本文主要是介绍【GPU】Nvidia CUDA 编程高级教程——利用蒙特卡罗法求解 的近似值,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

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


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



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

在这里插入图片描述

算法简介

估算 𝜋 有一个著名的技巧,那就是在单位面积内随机选择大量点,并计算落在单位圆内的点数。因为正方形的面积是 1,圆的面积是 𝜋/4 ,所以落在圆上的点的点数乘以 4,就是一个 𝜋 的良好近似值。

高度可并行

从并行编程的角度来看,该算法的良好特征之一是每个随机点都可以独立计算。我们只需要知道一个点的坐标,即可评估其是否落在圆内,对于点坐标 (𝑥,𝑦) 而言,如果 𝑥 2 + 𝑦 2 < = 1 𝑥^{2}+𝑦^{2}<=1 x2+y2<=1,那么点落在圆内,只要我们能处理好与计数器相关的任何竞态条件,表示圆内点数的计数器就可以递增。

单一 GPU 实现

我们来看看在单 GPU 上的 CUDA 中的实现情况。我们已提供实现情况的示例,如下所示:

#include <iostream>
#include <curand_kernel.h>#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) {// 分配主机和设备值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 + threads_per_block - 1) / threads_per_block;calculate_pi<<<blocks, threads_per_block>>>(d_hits);cudaDeviceSynchronize();// 将最终结果复制回主机cudaMemcpy(hits, d_hits, sizeof(int), cudaMemcpyDeviceToHost);// 计算 pi 的最终值float pi_est = (float) *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);return 0;
}

请注意,此代码仅用于指导目的,并不代表具有特别高的性能。具体原因如下:

  • 我们将使用设备侧 API(属于cuRAND),直接在核函数中生成随机数。即使您不熟悉 cuRAND 也无妨,只需知道每个 CUDA 线程都有各自唯一的随机数即可。
  • 我们让每个线程只计算一个值,所以计算强度很低。
  • 在更改hits(“命中”)计数器时,我们将遇到许多原子操作的冲突。

即便如此,我们仍可用 100 万个样本点快速估算 𝜋 。与正确值相比,我们的计算误差应该仅约为 0.05%。

运行结果如下:

Estimated value of pi = 3.14319
Error = 0.000507708
CPU times: user 51.4 ms, sys: 16.5 ms, total: 67.9 ms
Wall time: 3.17 s

扩展到多个 GPU

有一个简单的方法可以将我们的示例扩展到多个 GPU,那就是使用管理多个 GPU 的单一主机进程。如果我们利用 M 个 GPU 对 N 个采样点进行计算,则可以将N/M采样点分配给每个 GPU,原则上可以M 倍地加快计算。

为了实施这一方法,我们要:

  • 使用cudaGetDeviceCount确定可用 GPU 的数量。
  • 以GPU数量为循环次数,在每次循环中使用cudaSetDevice指定执行代码的是哪个GPU。
  • 在指定的 GPU 上执行分配给它的那部分工作。
    int device_count;
    cudaGetDeviceCount(&device_count);for (int i = 0; i < device_count; ++i) {cudaSetDevice(i);# Do single GPU worth of work.
    }
    

代码实现

请注意,在此示例中,我们会给每个 GPU 一个不同的随机数生成器种子,以便每个 GPU 进行不同的工作。因此,我们的答案会有所不同。

#include <iostream>
#include <curand_kernel.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) {// 确定 GPU 数量int device_count;cudaGetDeviceCount(&device_count);std::cout << "Using " << device_count << " GPUs" << std::endl;// 分配主机和设备值(每个 GPU 一个)int** hits = (int**) malloc(device_count * sizeof(int*));for (int i = 0; i < device_count; ++i) {hits[i] = (int*) malloc(sizeof(int));}int** d_hits = (int**) malloc(device_count * sizeof(int*));for (int i = 0; i < device_count; ++i) {cudaSetDevice(i);cudaMalloc((void**) &d_hits[i], sizeof(int));}// 初始化点击次数并复制到设备for (int i = 0; i < device_count; ++i) {*hits[i] = 0;cudaSetDevice(i);cudaMemcpy(d_hits[i], hits[i], sizeof(int), cudaMemcpyHostToDevice);}// 启动核函数进行计算int threads_per_block = 256;int blocks = (N / device_count + threads_per_block - 1) / threads_per_block;// 先启动所有核函数,以支持异步执行// 然后在所有设备上同步。for (int i = 0; i < device_count; ++i) {cudaSetDevice(i);calculate_pi<<<blocks, threads_per_block>>>(d_hits[i], i);}for (int i = 0; i < device_count; ++i) {cudaSetDevice(i);cudaDeviceSynchronize();}// 将最终结果复制回主机for (int i = 0; i < device_count; ++i) {cudaSetDevice(i);cudaMemcpy(hits[i], d_hits[i], sizeof(int), cudaMemcpyDeviceToHost);}// 计算所有设备的点击总数int hits_total = 0;for (int i = 0; i < device_count; ++i) {hits_total += *hits[i];}// 计算 pi 的最终值float pi_est = (float) hits_total / (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;// 清理for (int i = 0; i < device_count; ++i) {free(hits[i]);cudaFree(d_hits[i]);}free(hits);free(d_hits);return 0;
}

运行结果

我这边使用了4个GPU,根据测试环境不同,我们显示的结果也是不同的。

Using 4 GPUs
Estimated value of pi = 3.14072
Error = 0.000277734
CPU times: user 27.2 ms, sys: 16.1 ms, total: 43.3 ms
Wall time: 2.46 s


在这里插入图片描述

这篇关于【GPU】Nvidia CUDA 编程高级教程——利用蒙特卡罗法求解 的近似值的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/626420

相关文章

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

AI Toolkit + H100 GPU,一小时内微调最新热门文生图模型 FLUX

上个月,FLUX 席卷了互联网,这并非没有原因。他们声称优于 DALLE 3、Ideogram 和 Stable Diffusion 3 等模型,而这一点已被证明是有依据的。随着越来越多的流行图像生成工具(如 Stable Diffusion Web UI Forge 和 ComyUI)开始支持这些模型,FLUX 在 Stable Diffusion 领域的扩展将会持续下去。 自 FLU

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

如何用GPU算力卡P100玩黑神话悟空?

精力有限,只记录关键信息,希望未来能够有助于其他人。 文章目录 综述背景评估游戏性能需求显卡需求CPU和内存系统需求主机需求显式需求 实操硬件安装安装操作系统Win11安装驱动修改注册表选择程序使用什么GPU 安装黑神话悟空其他 综述 用P100 + PCIe Gen3.0 + Dell720服务器(32C64G),运行黑神话悟空画质中等流畅运行。 背景 假设有一张P100-

【编程底层思考】垃圾收集机制,GC算法,垃圾收集器类型概述

Java的垃圾收集(Garbage Collection,GC)机制是Java语言的一大特色,它负责自动管理内存的回收,释放不再使用的对象所占用的内存。以下是对Java垃圾收集机制的详细介绍: 一、垃圾收集机制概述: 对象存活判断:垃圾收集器定期检查堆内存中的对象,判断哪些对象是“垃圾”,即不再被任何引用链直接或间接引用的对象。内存回收:将判断为垃圾的对象占用的内存进行回收,以便重新使用。

Go Playground 在线编程环境

For all examples in this and the next chapter, we will use Go Playground. Go Playground represents a web service that can run programs written in Go. It can be opened in a web browser using the follow