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

    文章

    5496

    浏览量

    109076
  • gpu
    gpu
    +关注

    关注

    28

    文章

    5099

    浏览量

    134447
  • 计算机
    +关注

    关注

    19

    文章

    7764

    浏览量

    92674
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    为啥 AI 计算速度这么惊人?—— 聊聊 GPU、内存与并行计算

    提到AI,大家常说它“算得快”,其实是指AI能在眨眼间处理海量数据。可它为啥有这本事?答案就藏在“GPU+高速内存+并行计算”这trio(组合)里。咱们可以把AI要处理的数据,想象成一大堆“小任务
    的头像 发表于 12-05 14:35 218次阅读
    为啥 AI <b class='flag-5'>计算</b>速度这么惊人?—— 聊聊 GPU、内存与<b class='flag-5'>并行计算</b>

    一文看懂AI大模型并行训练方式(DP、PP、TP、EP)

    大家都知道,AI计算(尤其是模型训练和推理),主要以并行计算为主。AI计算中涉及到的很多具体算法(例如矩阵相乘、卷积、循环层、梯度运算等),都需要基于成千上万的GPU,以
    的头像 发表于 11-28 08:33 826次阅读
    一文看懂AI大<b class='flag-5'>模型</b>的<b class='flag-5'>并行</b>训练方式(DP、PP、TP、EP)

    神经网络的并行计算与加速技术

    问题。因此,并行计算与加速技术在神经网络研究和应用中变得至关重要,它们能够显著提升神经网络的性能和效率,满足实际应用中对快速响应和大规模数据处理的需求。神经网络并行
    的头像 发表于 09-17 13:31 884次阅读
    神经网络的<b class='flag-5'>并行计算</b>与加速技术

    从自然仿真到智能调度——GPU并行计算的多场景突破

    的体系结构,成为科学仿真与智能调度的核心计算平台。在自然现象模拟中,风沙流、流体力学等问题往往涉及海量粒子间的相互作用,计算负担极为沉重,而GPU的并行邻居搜索与空间
    的头像 发表于 09-03 10:32 577次阅读
    从自然仿真到智能调度——GPU<b class='flag-5'>并行计算</b>的多场景突破

    Kintex UltraScale 纯 FPGA 开发平台,释放高速并行计算潜能,高性价比的 FPGA 解决方案

    璞致电子PZ-KU060-KFB开发板采用Xilinx Kintex UltraScale KU060芯片,提供高密度并行计算能力,配备4GB DDR4内存、20对GTH高速收发器和多种扩展接口
    的头像 发表于 08-18 13:28 505次阅读
    Kintex UltraScale 纯 FPGA 开发<b class='flag-5'>平台</b>,释放高速<b class='flag-5'>并行计算</b>潜能,高性价比的 FPGA 解决方案

    边缘AI广泛应用推动并行计算崛起及创新GPU渗透率快速提升

    是时候重新教育整个生态了。边缘AI的未来不属于那些高度优化但功能狭窄的芯片,而是属于可编程的、可适配的并行计算平台,它们能与智能软件共同成长并扩展。
    的头像 发表于 06-11 14:57 476次阅读

    借助NVIDIA技术加速半导体芯片制造

    NVIDIA Blackwell GPU、NVIDIA Grace CPU、高速 NVIDIA NVLink 网络架构和交换机,以及诸如 NVIDIA cuDSS 和
    的头像 发表于 05-27 13:59 873次阅读

    读懂极易并行计算:定义、挑战与解决方案

    GPU经常与人工智能同时提及,其中一个重要原因在于AI与3D图形处理本质上属于同一类问题——它们都适用极易并行计算。什么是极易并行计算?极易并行计算指的是符合以下特征的计算任务:任务独
    的头像 发表于 04-17 09:11 648次阅读
    读懂极易<b class='flag-5'>并行计算</b>:定义、挑战与解决方案

    使用NVIDIA CUDA-X库加速科学和工程发展

    NVIDIA GTC 全球 AI 大会上宣布,开发者现在可以通过 CUDA-X 与新一代超级芯片架构的协同,实现 CPU 和 GPU 资源间深度自动化整合与调度,相较于传统加速计算架构,该技术可使
    的头像 发表于 03-25 15:11 1202次阅读

    借助PerfXCloud和dify开发代码转换器

    随着深度学习与高性能计算的迅速发展,GPU计算的广泛应用已成为推动技术革新的一股重要力量。对于GPU编程语言的选择,CUDA和HIP是目前最为流行的两种选择。
    的头像 发表于 02-25 09:36 1366次阅读
    借助PerfXCloud和dify开发代码转换器

    GPU加速计算平台的优势

    传统的CPU虽然在日常计算任务中表现出色,但在面对大规模并行计算需求时,其性能往往捉襟见肘。而GPU加速计算平台凭借其独特的优势,吸引了行业内人士的广泛关注和应用。下面,AI部落小编为
    的头像 发表于 02-23 16:16 756次阅读

    解析DeepSeek MoE并行计算优化策略

    本期Kiwi Talks将从集群Scale Up互联的需求出发,解析DeepSeek在张量并行及MoE专家并行方面采用的优化策略。DeepSeek大模型的工程优化以及国产AI 产业链的开源与快速部署预示着国产AI网络自主自控将大
    的头像 发表于 02-07 09:20 2697次阅读
    解析DeepSeek MoE<b class='flag-5'>并行计算</b>优化策略

    xgboost的并行计算原理

    在大数据时代,机器学习算法需要处理的数据量日益增长。为了提高数据处理的效率,许多算法都开始支持并行计算。XGBoost作为一种高效的梯度提升树算法,其并行计算能力是其受欢迎的原因
    的头像 发表于 01-19 11:17 1570次阅读

    NVIDIA Cosmos世界基础模型平台发布

    NVIDIA 宣布推出NVIDIA Cosmos,该平台由先进的生成式世界基础模型、高级 tokenizer、护栏和加速视频处理管线组成,将推动自动驾驶汽车(AV)和机器人等物理 AI
    的头像 发表于 01-08 10:39 1028次阅读

    《CST Studio Suite 2024 GPU加速计算指南》

    的各个方面,包括硬件支持、操作系统支持、许可证、GPU计算的启用、NVIDIA和AMD GPU的详细信息以及相关的使用指南和故障排除等内容。 1. 硬件支持 - NVIDIA GPU:详细列出了支持
    发表于 12-16 14:25