0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

并行计算平台和NVIDIA编程模型CUDA的更简单介绍

星星科技指导员 来源:NVIDIA 作者:Mark Harris 2022-04-11 09:46 次阅读

这篇文章是对 CUDA 的一个超级简单的介绍,这是一个流行的并行计算平台和 NVIDIA 的编程模型。我在 2013 年给 CUDA 写了一篇前一篇 “简单介绍” ,这几年来非常流行。但是 CUDA 编程变得越来越简单, GPUs 也变得更快了,所以是时候更新(甚至更容易)介绍了。

CUDA C ++只是使用 CUDA 创建大规模并行应用程序的一种方式。它让您使用强大的 C ++编程语言来开发由数千个并行线程加速的高性能算法 GPUs 。许多开发人员已经用这种方式加速了他们对计算和带宽需求巨大的应用程序,包括支持人工智能正在进行的革命的库和框架 深度学习

所以,您已经听说了 CUDA ,您有兴趣学习如何在自己的应用程序中使用它。如果你是 C 或 C ++程序员,这个博客应该给你一个好的开始。接下来,您需要一台具有 CUDA – 功能的 GPU 计算机( Windows 、 Mac 或 Linux ,以及任何 NVIDIA GPU 都可以),或者需要一个具有 GPUs 的云实例( AWS 、 Azure 、 IBM 软层和其他云服务提供商都有)。您还需要安装免费的 CUDA 工具箱 。

我们开始吧!

从简单开始

我们将从一个简单的 C ++程序开始,它添加两个数组的元素,每个元素有一百万个元素。

#include 
#include  // function to add the elements of two arrays
void add(int n, float *x, float *y)
{ for (int i = 0; i < n; i++) y[i] = x[i] + y[i];
} int main(void)
{ int N = 1<<20; // 1M elements float *x = new float[N]; float *y = new float[N]; // initialize x and y arrays on the host for (int i = 0; i < N; i++) { x[i] = 1.0f; y[i] = 2.0f; } // Run kernel on 1M elements on the CPU add(N, x, y); // Check for errors (all values should be 3.0f) float maxError = 0.0f; for (int i = 0; i < N; i++) maxError = fmax(maxError, fabs(y[i]-3.0f)); std::cout << "Max error: " << maxError << std::endl; // Free memory delete [] x; delete [] y; return 0;
}

首先,编译并运行这个 C ++程序。将代码放在一个文件中,并将其保存为add.cpp,然后用 C ++编译器编译它。我在 Mac 电脑上,所以我用的是clang++,但你可以在 Linux 上使用g++,或者在 Windows 上使用 MSVC 。

> clang++ add.cpp -o add

然后运行它:

> ./add Max error: 0.000000

(在 Windows 上,您可能需要命名可执行文件添加. exe 并使用.dd运行它。)

正如预期的那样,它打印出求和中没有错误,然后退出。现在我想让这个计算在 GPU 的多个核心上运行(并行)。其实迈出第一步很容易。

首先,我只需要将我们的add函数转换成 GPU 可以运行的函数,在 CUDA 中称为内核。要做到这一点,我所要做的就是把说明符__global__添加到函数中,它告诉 CUDA C ++编译器,这是一个在 GPU 上运行的函数,可以从 CPU 代码调用。

// CUDA Kernel function to add the elements of two arrays on the GPU
__global__
void add(int n, float *x, float *y)
{ for (int i = 0; i < n; i++) y[i] = x[i] + y[i];
}

这些__global__函数被称为果仁,在 GPU 上运行的代码通常称为设备代码,而在 CPU 上运行的代码是主机代码

CUDA 中的内存分配

为了在 GPU 上计算,我需要分配 GPU 可访问的内存, CUDA 中的统一存储器通过提供一个系统中所有 GPUs 和 CPU 都可以访问的内存空间,这使得这一点变得简单。要在统一内存中分配数据,请调用cudaMallocManaged(),它返回一个指针,您可以从主机( CPU )代码或设备( GPU )代码访问该指针。要释放数据,只需将指针传递到cudaFree()

我只需要将上面代码中对new的调用替换为对cudaMallocManaged()的调用,并将对delete []的调用替换为对cudaFree.的调用

 // Allocate Unified Memory -- accessible from CPU or GPU float *x, *y; cudaMallocManaged(&x, N*sizeof(float)); cudaMallocManaged(&y, N*sizeof(float)); ... // Free memory cudaFree(x); cudaFree(y);

最后,我需要发射内核,它在add()上调用它。 CUDA 内核启动是使用三角括号语法指定的。我只需要在参数列表之前将它添加到对 CUDA 的调用中。

add<<<1, 1>>>(N, x, y);

容易的!我很快将详细介绍尖括号内的内容;现在您只需要知道这行代码启动了一个 GPU 线程来运行add()

还有一件事:我需要 CPU 等到内核完成后再访问结果(因为 CUDA 内核启动不会阻塞调用的 CPU 线程)。为此,我只需在对 CPU 进行最后的错误检查之前调用cudaDeviceSynchronize()

以下是完整的代码:

#include 
#include 
// Kernel function to add the elements of two arrays
__global__
void add(int n, float *x, float *y)
{ for (int i = 0; i < n; i++) y[i] = x[i] + y[i];
} int main(void)
{ int N = 1<<20; float *x, *y; // Allocate Unified Memory – accessible from CPU or GPU cudaMallocManaged(&x, N*sizeof(float)); cudaMallocManaged(&y, N*sizeof(float)); // initialize x and y arrays on the host for (int i = 0; i < N; i++) { x[i] = 1.0f; y[i] = 2.0f; } // Run kernel on 1M elements on the GPU add<<<1, 1>>>(N, x, y); // Wait for GPU to finish before accessing on host cudaDeviceSynchronize(); // Check for errors (all values should be 3.0f) float maxError = 0.0f; for (int i = 0; i < N; i++) maxError = fmax(maxError, fabs(y[i]-3.0f)); std::cout << "Max error: " << maxError << std::endl; // Free memory cudaFree(x); cudaFree(y); return 0;
}

CUDA 文件具有文件扩展名;.cu。所以把代码保存在一个名为

> nvcc add.cu -o add_cuda
> ./add_cuda
Max error: 0.000000

这只是第一步,因为正如所写的,这个内核只适用于一个线程,因为运行它的每个线程都将在整个数组上执行 add 。此外,还有一个竞争条件,因为多个并行线程读写相同的位置。

注意:在 Windows 上,您需要确保在 Microsoft Visual Studio 中项目的配置属性中将“平台”设置为 x64 。

介绍一下!

我认为找出运行内核需要多长时间的最简单的方法是用nvprof运行它,这是一个带有 CUDA 工具箱的命令行 GPU 分析器。只需在命令行中键入nvprof ./add_cuda

$ nvprof ./add_cuda
==3355== NVPROF is profiling process 3355, command: ./add_cuda
Max error: 0
==3355== Profiling application: ./add_cuda
==3355== Profiling result:
Time(%) Time Calls Avg Min Max Name
100.00% 463.25ms 1 463.25ms 463.25ms 463.25ms add(int, float*, float*)
...

上面是来自nvprof的截断输出,显示了对add的单个调用。在 NVIDIA Tesla K80 加速器上需要大约半秒钟的时间,而在我 3 岁的 Macbook Pro 上使用 NVIDIA GeForce GT 740M 大约需要半秒钟的时间。

让我们用并行来加快速度。

把线捡起来

既然你已经用一个线程运行了一个内核,那么如何使它并行?键是在 CUDA 的<<<1, 1>>>语法中。这称为执行配置,它告诉 CUDA 运行时要使用多少并行线程来启动 GPU 。这里有两个参数,但是让我们从更改第二个参数开始:线程块中的线程数。 CUDA GPUs 运行内核时使用的线程块大小是 32 的倍数,因此 256 个线程是一个合理的选择。

add<<<1, 256>>>(N, x, y);

如果我只在这个修改下运行代码,它将为每个线程执行一次计算,而不是将计算分散到并行线程上。为了正确地执行它,我需要修改内核。 CUDA C ++提供了关键字,这些内核可以让内核获得运行线程的索引。具体来说,threadIdx.x包含其块中当前线程的索引,blockDim.x包含块中的线程数。我只需修改循环以使用并行线程跨过数组。

__global__
void add(int n, float *x, float *y)
{ int index = threadIdx.x; int stride = blockDim.x; for (int i = index; i < n; i += stride) y[i] = x[i] + y[i];
}

add函数没有太大变化。事实上,将index设置为 0 ,stride设置为 1 会使其在语义上与第一个版本相同。

将文件另存为add_block.cu,然后再次在nvprof中编译并运行。在后面的文章中,我将只显示输出中的相关行。

Time(%) Time Calls Avg Min Max Name
100.00% 2.7107ms 1 2.7107ms 2.7107ms 2.7107ms add(int, float*, float*)

这是一个很大的加速( 463 毫秒下降到 2 . 7 毫秒),但并不奇怪,因为我从 1 线程到 256 线程。 K80 比我的小 MacBookProGPU 快( 3 . 2 毫秒)。让我们继续取得更高的表现。

走出街区

CUDA GPUs 有许多并行处理器组合成流式多处理器或 SMs 。每个 SM 可以运行多个并发线程块。例如,基于 Tesla 的 Tesla P100帕斯卡 GPU 体系结构有 56 个短消息,每个短消息能够支持多达 2048 个活动线程。为了充分利用所有这些线程,我应该用多个线程块启动内核。

现在您可能已经猜到执行配置的第一个参数指定了线程块的数量。这些平行线程块一起构成了所谓的网格。因为我有N元素要处理,每个块有 256 个线程,所以我只需要计算块的数量就可以得到至少 N 个线程。我只需将N除以块大小(注意在N不是blockSize的倍数的情况下向上取整)。

int blockSize = 256;
int numBlocks = (N + blockSize - 1) / blockSize;
add<<<numBlocks, blockSize>>>(N, x, y);

我还需要更新内核代码来考虑线程块的整个网格。threadIdx.x提供了包含网格中块数的gridDim.x和包含网格中当前线程块索引的blockIdx.x。图 1 说明了使用 CUDA 、gridDim.xthreadIdx.x在 CUDA 中索引数组(一维)的方法。其思想是,每个线程通过计算到其块开头的偏移量(块索引乘以块大小:blockIdx.x * blockDim.x),并将线程的索引添加到块内(threadIdx.x)。代码blockIdx.x * blockDim.x + threadIdx.x是惯用的 CUDA 。

__global__
void add(int n, float *x, float *y)
{ int index = blockIdx.x * blockDim.x + threadIdx.x; int stride = blockDim.x * gridDim.x; for (int i = index; i < n; i += stride) y[i] = x[i] + y[i];
}

更新的内核还将stride设置为网格中的线程总数(blockDim.x * gridDim.x)。 CUDA 内核中的这种类型的循环通常称为栅格步幅循环

将文件另存为&[EZX63 ;&[编译并在&[EZX37 ;&]中运行它]

Time(%) Time Calls Avg Min Max Name
100.00% 94.015us 1 94.015us 94.015us 94.015us add(int, float*, float*)

这是另一个 28 倍的加速,从运行多个街区的所有短信 K80 !我们在 K80 上只使用了 2 个 GPUs 中的一个,但是每个 GPU 都有 13 条短信。注意,我笔记本电脑中的 GeForce 有 2 条(较弱的)短信,运行内核需要 680us 。

总结

下面是三个版本的add()内核在 Tesla K80 和 GeForce GT 750M 上的性能分析。

如您所见,我们可以在 GPUs 上实现非常高的带宽。这篇文章中的计算是非常有带宽限制的,但是 GPUs 也擅长于密集矩阵线性代数深度学习、图像和信号处理、物理模拟等大量计算限制的计算。

关于作者

Mark Harris 是 NVIDIA 杰出的工程师,致力于 RAPIDS 。 Mark 拥有超过 20 年的 GPUs 软件开发经验,从图形和游戏到基于物理的模拟,到并行算法和高性能计算。当他还是北卡罗来纳大学的博士生时,他意识到了一种新生的趋势,并为此创造了一个名字: GPGPU (图形处理单元上的通用计算)。

审核编辑:郭婷

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • NVIDIA
    +关注

    关注

    14

    文章

    4592

    浏览量

    101716
  • gpu
    gpu
    +关注

    关注

    27

    文章

    4417

    浏览量

    126702
  • 计算机
    +关注

    关注

    19

    文章

    6649

    浏览量

    84526
收藏 人收藏

    评论

    相关推荐

    基于NVIDIA开源CUDA-Q量子计算平台发布

    NVIDIA 于太平洋时间 3 月 18 日推出一项云服务,旨在帮助研究人员和开发人员在化学、生物学、材料科学等关键科学领域的量子计算研究中取得突破。
    的头像 发表于 03-21 09:54 155次阅读

    NVIDIA 推出云量子计算机模拟微服务

    量子云基于 NVIDIA 开源 CUDA-Q 量子计算平台 —— 部署量子处理器(QPU)的公司有四分之三都在
    发表于 03-19 11:27 127次阅读
    <b class='flag-5'>NVIDIA</b> 推出云量子<b class='flag-5'>计算</b>机模拟微服务

    NVIDIA 为全球领先的 AI 计算平台 Hopper 再添新动力

    NVIDIA HGX™ H200,为 Hopper 这一全球领先的 AI 计算平台再添新动力。NVIDIA HGX H200 平台基于
    发表于 11-14 14:30 112次阅读
    <b class='flag-5'>NVIDIA</b> 为全球领先的 AI <b class='flag-5'>计算</b><b class='flag-5'>平台</b> Hopper 再添新动力

    基于PyTorch的模型并行分布式训练Megatron解析

    NVIDIA Megatron 是一个基于 PyTorch 的分布式训练框架,用来训练超大Transformer语言模型,其通过综合应用了数据并行,Tensor并行和Pipeline
    的头像 发表于 10-23 11:01 1003次阅读
    基于PyTorch的<b class='flag-5'>模型</b><b class='flag-5'>并行</b>分布式训练Megatron解析

    CUDA核心是什么?CUDA核心的工作原理

    CUDA核心(Compute Unified Device Architecture Core)是NVIDIA图形处理器(GPU)上的计算单元,用于执行并行计算任务。每个
    发表于 09-27 09:38 4869次阅读
    <b class='flag-5'>CUDA</b>核心是什么?<b class='flag-5'>CUDA</b>核心的工作原理

    Arm Forge 22.1.3版用户指南

    。 Arm Forge支持许多并行体系结构和模型,包括MPI、CUDA和OpenMP。 Arm Forge是一款跨平台工具,支持最新的编译器和C++标准,以及英特尔、64位Arm、AM
    发表于 08-10 06:29

    开发者活动 | 2023 NVIDIA &amp; 创乐博 CUDA 训练营成功举办,助力学生和开发者们成为高性能计算领域的未来之星

    教学相关资源。 自 2006 年 CUDA 平台推出以来,已经累积下载 4,000 万次,全球超过 300 万开发者在使用;而 CUDA 编程技术也成为了业界高性能
    的头像 发表于 08-03 19:25 306次阅读
    开发者活动 | 2023 <b class='flag-5'>NVIDIA</b> &amp; 创乐博 <b class='flag-5'>CUDA</b> 训练营成功举办,助力学生和开发者们成为高性能<b class='flag-5'>计算</b>领域的未来之星

    开发者活动 | 2023 NVIDIA &amp; 创乐博 CUDA 线上训练营火热报名中

    NVIDIA 作为一家全栈式 AI 计算平台的赋能者,致力于推动全球性的人工智能和科学计算的发展,通过推广高性能技术应用为社会发展做出积极贡献。尤其在国内高等教育领域,十多年来
    的头像 发表于 07-13 21:15 403次阅读
    开发者活动 | 2023 <b class='flag-5'>NVIDIA</b> &amp; 创乐博 <b class='flag-5'>CUDA</b> 线上训练营火热报名中

    Raspberry Pi 4B+ IoT板上的并行计算变得简单

    电子发烧友网站提供《Raspberry Pi 4B+ IoT板上的并行计算变得简单.zip》资料免费下载
    发表于 06-16 15:23 0次下载
    Raspberry Pi 4B+ IoT板上的<b class='flag-5'>并行计算</b>变得<b class='flag-5'>简单</b>

    CUDA与Jetson Nano:并行Pollard Rho测试

    电子发烧友网站提供《CUDA与Jetson Nano:并行Pollard Rho测试.zip》资料免费下载
    发表于 06-15 09:30 0次下载
    <b class='flag-5'>CUDA</b>与Jetson Nano:<b class='flag-5'>并行</b>Pollard Rho测试

    周三研讨会预告 | 从 CUDA 到 CV-CUDA:如何为自己定制高效的 CV 任务算子

    CUDA (Compute Unified Device Architecture)编程模型 ,利用 GPU 强大的并行计算能力,为计算
    的头像 发表于 06-13 20:55 282次阅读
    周三研讨会预告 | 从 <b class='flag-5'>CUDA</b> 到 CV-<b class='flag-5'>CUDA</b>:如何为自己定制高效的 CV 任务算子

    基于Transformer做大模型预训练基本的并行范式

    在之前的内容中,我们已经介绍过流水线并行、数据并行(DP,DDP和ZeRO)。 今天我们将要介绍最重要,也是目前基于Transformer做大模型
    的头像 发表于 05-31 14:38 1705次阅读
    基于Transformer做大<b class='flag-5'>模型</b>预训练基本的<b class='flag-5'>并行</b>范式

    CUDA编程接口介绍

    编程模型介绍了核心语言扩展。它们允许程序员将内核定义为c++函数,并在每次调用函数时使用一些新的语法来指定网格和块维度。
    发表于 05-19 14:53 485次阅读

    介绍CUDA编程模型CUDA线程体系

    CUDA 编程模型主要有三个关键抽象:层级的线程组,共享内存和栅同步(barrier synchronization)。
    的头像 发表于 05-19 11:32 1140次阅读
    <b class='flag-5'>介绍</b><b class='flag-5'>CUDA</b><b class='flag-5'>编程</b><b class='flag-5'>模型</b>及<b class='flag-5'>CUDA</b>线程体系

    GPU平台生态,英伟达CUDA和AMD ROCm对比分析

    CUDA 除了是并行计算架构外,还是 CPU 和 GPU 协调工作的通用语言。在CUDA 编程模型中,主要有 Host(主机)和 Devic
    的头像 发表于 05-18 09:57 1693次阅读
    GPU<b class='flag-5'>平台</b>生态,英伟达<b class='flag-5'>CUDA</b>和AMD ROCm对比分析