汽车长翅膀:GPU 是如何加速深度学习模型的训练和推理过程的?

编者按:深度学习的飞速发展离不开硬件技术的突破,而 GPU 的崛起无疑是其中最大的推力之一。但你是否曾好奇过,为何一行简单的“.to(‘cuda’)”代码就能让模型的训练速度突飞猛进?本文正是为解答这个疑问而作。

作者以独特的视角,将复杂的 GPU 并行计算原理转化为通俗易懂的概念。从 CPU 与 GPU 的设计哲学对比,到 CUDA 编程的核心要素,再到具体的代码实现,文章循序渐进地引领读者把握 GPU 并行计算的精髓。特别是文中巧妙的比喻 —— 将 CPU 比作法拉利,GPU 比作公交车,这一比喻生动形象地诠释了两种处理器的特性。

这篇文章不仅回答了"为什么",更指明了"如何做",在当前人工智能技术飞速发展的背景下,理解底层技术原理的重要性不言而喻。这篇文章虽为入门级别的技术内容介绍,但也提到了更高级的优化技术和工具库,指明了进一步的学习方向,具有一定的学习和参考价值。

作者 | Lucas de Lima Nogueira

编译 | 岳扬

Image by the author with the assistance of AI (https://copilot.microsoft.com/images/create)

现如今,当我们提及深度学习时,人们自然而然地会联想到通过 GPU 来增强其性能。

GPU(图形处理器,Graphical Processing Units)起初是为了加速图像(images)及 2D、3D 图形(graphics)的渲染而生。但凭借其强大的并行运算能力,GPU 的应用范围迅速拓展,已扩展至深度学习(deep learning)等应用领域。

GPU 在深度学习模型中的应用始于 2000 年代中后期,2012 年 AlexNet 的横空出世更是将这种趋势推向高潮。 AlexNet,这款由 Alex Krizhevsky、Ilya Sutskever 和 Geoffrey Hinton 共同设计、研发的卷积神经网络,在 2012 年的 ImageNet Large Scale Visual Recognition Challenge (ILSVRC) 上一鸣惊人。这一胜利具有里程碑式的意义,它不仅证实了深度神经网络在图像分类领域(image classification)的卓越性能,同时也彰显了使用 GPU 训练大型模型的有效性。

在这一技术突破之后,GPU 在深度学习模型中的应用愈发广泛,PyTorch 和 TensorFlow 等框架应运而生。

如今,我们只需在 PyTorch 中轻敲 .to(“cuda”),即可将数据传递给 GPU,从而加速模型的训练。但在实践中,深度学习算法究竟是如何巧妙地利用 GPU 算力的呢?让我们一探究竟吧!

深度学习的核心架构,如神经网络、CNNs、RNNs 和 transformer,其本质都围绕着矩阵加法(matrix addition)、矩阵乘法(matrix multiplication)以及对矩阵应用函数(applying a function a matrix)等基本数学操作展开。因此,优化这些核心运算,便是提升深度学习模型性能的关键所在。

那么,让我们从最基础的场景说起。想象一下,你需要对两个向量执行相加操作 C = A + B。

可以用 C 语言简单实现这一功能:

不难发现,传统上,计算机需逐一访问向量中的各个元素(elements),在每次迭代中按顺序对每对元素进行加法运算。但有一点需要注意,各对元素间的加法操作互不影响,即任意一对元素的加法不依赖于其它任何一对。那么,若我们能同时执行这些数学运算,实现所有元素对(pairs of elements)的并行相加,效果会如何呢?

直接做法是借助 CPU 的多线程功能,并行执行所有数学运算。但在深度学习领域,我们需要处理的向量规模巨大,往往包含数百万个元素。通常情况下,普通 CPU 只能同时处理十几条线程。此时,GPU 的优势便凸显出来!目前的主流 GPU 能够同时运行数百万个线程,极大地提高了处理大规模向量中数学运算的效率。

01 GPU vs. CPU comparison

虽然从单次运算(single operation)的处理速度来看,CPU 或许略胜 GPU 一筹,但 GPU 的优势在于其卓越的并行处理能力。究其根源,这一情况源于两者设计初衷的差异。CPU 的设计侧重于高效执行单一序列的操作(即线程(thread)),但一次仅能同时处理几十个;相比之下,GPU 的设计目标是实现数百万个线程的并行运算,虽有所牺牲单个线程的运算速度,却在整体并行性能上实现了质的飞跃。

打个比方,你可以将 CPU 视作一辆炫酷的法拉利(Ferrari)跑车,而 GPU 则如同一辆宽敞的公交车。倘若你的任务仅仅是运送一位乘客,毫无疑问,法拉利(CPU)是最佳选择。然而,如若当前的运输需求是运送多位乘客,即使法拉利(CPU)单程速度占优,公交车(GPU)却能一次容纳全部乘客,其集体运输效率远超法拉利多次单独接送的效率。由此可见,CPU 更适于处理连续性的单一任务,而 GPU 则在并行处理大量任务时展现出色的效能。

Image by the author with the assistance of AI (https://copilot.microsoft.com/images/create)

为了实现更出色的并行计算能力,GPU 在设计上倾向于将更多晶体管资源(transistors)投入到数据处理中,而非数据缓存(data caching)和流控机制(flow contro),这与 CPU 的设计思路大相径庭。CPU 为了优化单一线程的执行效率和复杂指令集的处理,特意划拨了大量的晶体管来加强这些方面的性能。

下图生动地描绘了 CPU 与 GPU 在芯片资源分配上的显著差异。

Image by the author with inspiration from CUDA C++ Programming Guide

(https://docs.nvidia.com/cuda/pdf/CUDA_C_Programming_Guide.pdf)

CPU 配备了高性能内核(powerful cores)与更为精妙的缓存内存架构(cache memory architecture)(消耗了相当多的晶体管资源),这种设计方案能够极大地优化顺序任务的执行速度。而图形处理器(GPU)则着重于内核(cores)数量,以实现更高的并行处理能力。

现在已经介绍完这些基础知识,那么在实际应用中,我们应如何有效利用并行计算的优势呢?

02 Introduction to CUDA

当我们着手构建深度学习模型时,很可能会倾向于采用诸如 PyTorch 或 TensorFlow 这类广受欢迎的 Python 开发库。尽管如此,一个不争的事实是,这些库的核心代码都是 C/C++ 代码。另外,正如我们先前所提及的,利用 GPU 加快数据的处理速度往往是一种主流优化方案。此时,CUDA 的重要作用便凸显出来!CUDA 是统一计算设备架构(Compute Unified Device Architecture)的缩写,是英伟达(NVIDIA)为使 GPU 能够在通用计算领域大放光彩而精心打造的平台。与 DirectX 被游戏引擎用于图形运算(graphical computation)不同,CUDA 使开发人员能够将英伟达(NVIDIA)的 GPU 计算能力集成到通用软件中,而不仅仅局限于图形渲染。

为了实现这一目标,CUDA 推出了一款基于 C/C++ 的简易接口(CUDA C/C++),帮助开发者调用 GPU 虚拟指令集(virtual intruction se)及执行特定操作(specific operations)(如在 CPU 与 GPU 间传输数据)。

在继续深入技术细节之前,我们有必要澄清几个 CUDA 编程的基础概念和专业术语:

  • host:特指 CPU 及其配套内存;
  • device:对应 GPU 及其专属内存;
  • kernel:指代在设备(GPU)上运行的函数代码;

因此,在一份使用 CUDA 撰写的基本代码(basic code)中,程序主体在 host (CPU) 上执行,随后将数据传递给 device (GPU) ,并调用 kernels (functions) 在 device (GPU) 上并行运行。这些 kernels 由多条线程同时执行。运算完成后,结果再从 device (GPU) 回传至 host (CPU) 。

话说回来,让我们再次聚焦于两组向量相加这个具体问题:

借助 CUDA C/C++,编程人员能够创建一种被称为 kernels 的 C/C++ 函数;一旦这些 kernels 被调用, N 个不同的 CUDA 线程会并行执行 N 次。

若想定义这类 kernel,可运用 __global__ 关键字作为声明限定符(declaration specifier),而若欲设定执行该 kernel 的具体 CUDA 线程数目,则需采用 <<<...>>> 来完成:

每个 CUDA 线程在执行 kernel 时,都会被赋予一个独一无二的线程 ID,即 threadIdx,它可以通过 kernel 中的预设变量获取。上述示例代码将两个长度(size)均为 N 的向量 A 和 B 相加,并将结果保存到向量 C 中。值得我们注意的是,相较于循环逐次处理成对加法的传统串行方式,CUDA 的优势在于其能够并行利用 N 个线程,一次性完成全部加法运算。

不过,在运行上述这段代码前,我们还需对其进行一次修改。切记,kernel 函数的运行环境是 device (GPU) ,这意味着所有相关数据均须驻留于 device 的内存之中。 要达到这一要求,可以借助 CUDA 提供的以下内置函数:

直接将变量 A、B 和 C 传入 kernel 的做法并不适用于本情况,我们应当使用指针。在 CUDA 编程环境下,host 数组(比如示例中的 A、B 和 C)无法直接用于 kernel 启动(<<<…>>>)。鉴于 CUDA kernels 的工作空间为 device 的内存(device memory),故需向 kernel 提供 device 指针(device pointers)(d_A、d_B 和 d_C),以确保其能在 device 的内存上运行。

除此之外,我们还需通过调用 cudaMalloc 函数在 device 上划分内存空间,并运用 cudaMemcpy 实现 host 和 device 之间的数据传输。

至此,我们可在代码中实现向量 A 和 B 的初始化,并在程序结尾处清理 CUDA 内存(cuda memory)。

另外,调用 kernel 后,务必插入 cudaDeviceSynchronize(); 这一行代码。该函数的作用在于协调 host 线程与 device 间的同步,确保 host 线程在继续执行前,device 已完成所有先前提交的 CUDA 操作。

此外,CUDA 的错误检测机制同样不可或缺,这种检测机制能协助我们及时发现并修正 GPU 上潜在的程序缺陷(bugs)。倘若忽略此环节,device 线程(CPU)将持续运行,而 CUDA 相关的故障排查则将变得异常棘手,很难识别与 CUDA 相关的错误。

下面是这两种技术的具体实现方式:

要编译和运行 CUDA 代码,首先需要确保系统中已装有 CUDA 工具包(CUDA toolkit)。紧接着,使用 nvcc —— NVIDIA CUDA 编译器完成相关代码编译工作。

然而,当前的代码尚存优化空间。在前述示例中,我们处理的向量规模仅为 N = 1000,这一数值偏小,难以充分展示 GPU 强大的并行处理能力。特别是在深度学习场景下,我们时常要应对含有数以百万计参数的巨型向量。然而,倘若尝试将 N 的数值设为 500000,并采用 <<<1, 500000>>> 的方式运行 kernel ,上述代码便会抛出错误。因此,为了完善代码,使之能顺利执行此类大规模运算,我们亟需掌握 CUDA 编程中的核心理念 —— 线程层级结构(Thread hierarchy)。

03 Thread hierarchy(线程层级结构)

调用 kernel 函数时,采用的是 <<<number_of_blocks, threads_per_block>>> 这种格式(notation)。因此,在上述示例中,我们是以单个线程块的形式,启动了 N 个 CUDA 线程。然而,每个线程块所能容纳的线程数量都有限制,这是因为所有处于同一线程块内的线程,都被要求共存于同一流式多处理器核心(streaming multiprocessor core),并共同使用该核心的内存资源。

欲查询这一限制数量的具体数值,可通过以下代码实现:

就作者当前使用的 GPU 而言,其单一线程块最多能承载 1024 个线程。因此,为了有效处理示例中提及的巨型向量(massive vector),我们必须部署更多线程块,以实现更大规模的线程并发执行。 同时,这些线程块被精心布局成网格状结构(grids),如下图所展示:

https://handwiki.org/wiki/index.php?curid=1157670 (CC BY-SA 3.0)

现在,我们可以通过以下途径获取线程 ID:

于是,该代码脚本更新为:

04 性能对比分析

下表展示了在处理不同大小向量的加法运算时,CPU 与 GPU 的计算性能对比情况。

Image by the author

显而易见,GPU 的处理效能优势,唯有在处理大规模向量时方能得以凸显。此外,切勿忽视一件事,此处的时间对比仅仅考量了 kernel/function 的执行耗时,而未将 host 和 device 间数据传输所需的时间纳入考虑范围。尽管在大多数情况下,数据传输的时间开销微不足道,但就我们目前仅执行简易加法运算(simple addition operation)的情形而言,这部分时间消耗却显得相对可观。因此,我们应当铭记,GPU 的计算性能,仅在面对那些既高度依赖计算能力又适合大规模并行处理的任务时,才能得以淋漓尽致地展现。

05 多维线程处理(Multidimensional threads)

现在,我们已经知道如何提升简单数组操作(simple array operation)的性能了。然而,在处理深度学习模型时,必须要处理矩阵和张量运算(matrix and tensor operations)。在前文的示例中,我们仅使用了内含 N 个线程的一维线程块(one-dimensional blocks)。然而,执行多维线程块(multidimensional thread blocks)(最高支持三维)同样也是完全可行的。因此,为了方便起见,当我们需要处理矩阵运算时,可运行一个由 N x M 个线程组成的线程块。还可以通过 row = threadIdx.x 来确定矩阵的行索引,而 col = threadIdx.y 则可用来获取列索引。此外,为了简化操作,还可以使用 dim3 变量类型定义 number_of_blocks 和 threads_per_block。

下文的示例代码展示了如何实现两个矩阵的相加运算。

此外,我们还可以将此示例进一步拓展,实现对多个线程块的处理:

此外,我们也可以用同样的思路将这个示例扩展到三维运算(3-dimensional operations)操作的处理。

上文已经介绍了处理多维数据(multidimensional data)的方法,接下来,还有一个既重要又容易理解的概念值得我们学习:如何在 kernel 中调用 functions。 一般可以通过使用 __device__ 声明限定符(declaration specifier)来实现。这种限定符定义了可由 device (GPU)直接调用的函数(functions)。因此,这些函数仅能在 __global__ 或其他 __device__ 函数中被调用。下面这个示例展示了如何对一个向量进行 sigmoid 运算(这是深度学习模型中极其常见的一种运算方式)。

至此,我们已经掌握了 CUDA 编程的核心概念,现在可以着手构建 CUDA kernels 了。对于深度学习模型而言,其实质就是一系列涉及矩阵(matrix)与张量(tensor)的运算操作,包括但不限于求和(sum)、乘法(multiplication)、卷积(convolution)以及归一化(normalization )等。举个例子,一个基础的矩阵乘法算法,可以通过以下方式实现并行化:

我们可以注意到,在 GPU 版本的矩阵乘法算法中,循环次数明显减少,从而显著提升了运算处理速度。下面这张图表直观地展现了 N x N 矩阵乘法在 CPU 与 GPU 上的性能对比情况:

Image by the author

我们会发现,随着矩阵大小(matrix size)的增大,GPU 在处理矩阵乘法运算时的性能提升幅度更大。

接下来,让我们聚焦于一个基础的神经网络模型,其核心运算通常表现为 y = σ(Wx + b),如下图所示:

Image by the author

上述运算主要涉及矩阵乘法(matrix multiplication)、矩阵加法(matrix addition)以及对数组施加函数变换(applying a function to an array)。如若你已掌握这些并行化处理技术,意味着你现在完全具备了从零构建、并在 GPU 上构建神经网络的能力!

06 Conclusion

本文我们探讨了通过 GPU processing (译者注:使用 GPU进行数据处理和计算。)提升深度学习模型效能的入门概念。不过,有一点还需要指出,本文所介绍的内容仅仅是皮毛,背后还隐藏着很多很多更深层次的东西。PyTorch 和 Tensorflow 等框架实现了诸多高级性能优化技术,涵盖了 optimized memory access、batched operations 等复杂概念(其底层利用了基于 CUDA 的 cuBLAS 和 cuDNN 等库)。 但愿这篇文章能够让各位读者对使用 .to(“cuda”) 方法,在 GPU 上构建、运行深度学习模型时的底层原理,有个初步的了解。

Thanks so much for reading! 😊

Lucas de Lima Nogueira

https://www.linkedin.com/in/lucas-de-lima-nogueira/

END

原文链接:

https://towardsdatascience.com/why-deep-learning-models-run-faster-on-gpus-a-brief-introduction-to-cuda-programming-035272906d66

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何使用代理IP进行电子邮件保护?

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 前言 随着企业信息化的深入发展&#xff0c;电子邮件在私人生活和商业运营中起到越来越重要的作用&#xff0c;随之而来电子邮件…

【编程工具使用技巧】VS如何显示行号

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《编程工具与技巧探索》 期待您的关注 目录 引言 一、VS编译器行号显示的基本步骤 1.打开VS与项目 2.进入选项设置 3.找到并…

LeetCode 637, 67, 399

文章目录 637. 二叉树的层平均值题目链接标签思路代码 67. 二进制求和题目链接标签思路代码 399. 除法求值题目链接标签思路导入value 属性find() 方法union() 方法query() 方法 代码 637. 二叉树的层平均值 题目链接 637. 二叉树的层平均值 标签 树 深度优先搜索 广度优先…

SQL语句(以MySQL为例)——单表、多表查询

笛卡尔积&#xff08;或交叉连接&#xff09;: 笛卡尔乘积是一个数学运算。假设我有两个集合 X 和 Y&#xff0c;那么 X 和 Y 的笛卡尔积就是 X 和 Y 的所有可能组合&#xff0c;也就是第一个对象来自于 X&#xff0c;第二个对象来自于 Y 的所有可能。组合的个数即为两个集合中…

开源监控 - 夜莺项目 v7 正式发版了

前言 上周五去参加了第二届 CCF夜莺开发者创新论坛&#xff0c;在会上&#xff0c;夜莺 v7 LTS 版本正式发布&#xff0c;另有多名嘉宾分享了自己公司的可观测性实践经验&#xff0c;挺有收获。 夜莺 v7 新功能 夜莺 v7 版本更多的着眼在提升用户体验&#xff0c;开箱即用方面…

在WPF中使用WebView2详解

Microsoft Edge WebView2 Microsoft Edge WebView2 控件允许在本机应用中嵌入 web 技术(HTML、CSS 以及 JavaScript)。 WebView2 控件使用 Microsoft Edge 作为绘制引擎&#xff0c;以在本机应用中显示 web 内容。 使用 WebView2 可以在本机应用的不同部分嵌入 Web 代码&…

apache2和httpd web服务器

apache2和httpd web服务器 apache2和httpd web服务器是啥apache是软件基金会apache2是一个web服务httpd和apache2是同一个东西&#xff0c;但是不同linux发行版中叫法不一样。就是同一个东西&#xff0c;但是看上去有一些不一样。 apache2和httpd web服务器是啥 apache是软件基…

AI学习记录 -使用react开发一个网页,对接chatgpt接口,附带一些英语的学习prompt

实现了如下功能&#xff08;使用react实现&#xff0c;原创&#xff09; 实现功能&#xff1a; 1、对接gpt35模型问答&#xff0c;并实现了流式传输&#xff08;在java端&#xff09; 2、在实际使用中&#xff0c;我们的问答历史会经常分享给他人&#xff0c;所以下图的 copy …

Hive多维分析函数——With cube、Grouping sets、With rollup

有些指标涉及【多维度】的聚合&#xff0c;大的汇总维度&#xff0c;小的明细维度&#xff0c;需要精细化的下钻。 grouping sets&#xff1a; 多维度组合&#xff0c;组合维度自定义&#xff1b;with cube&#xff1a; 多维度组合&#xff0c;程序自由组合&#xff0c;组合为…

日拱一卒 | JVM

文章目录 什么是JVM&#xff1f;JVM的组成JVM的大致工作流程JVM的内存模型 什么是JVM&#xff1f; 我们知道Java面试&#xff0c;只要你的简历上写了了解JVM&#xff0c;那么你就必然会被问到以下问题&#xff1a; 什么是JVM&#xff1f;简单说一下JVM的内存模型&#xff1f;…

大疆创新2025校招内推

大疆2025校招-内推 一、我们是谁&#xff1f; 大疆研发软件团队&#xff0c;致力于把大疆的硬件设备和大疆用户紧密连接在一起&#xff0c;我们的使命是“让机器有温度&#xff0c;让数据会说话”。 在消费和手持团队&#xff0c;我们的温度来自于激发用户灵感并助力用户创作…

破局产品同质化:解锁3D交互式营销新纪元!

近年来&#xff0c;随着数字体验经济的蓬勃发展&#xff0c;3D交互式营销作为一种创新手段迅速崛起&#xff0c;它巧妙地解决了传统产品展示中普遍存在的缺乏差异性和互动性的问题&#xff0c;使您的产品在激烈的市场竞争中独树一帜&#xff0c;脱颖而出。 若您正面临产品营销…

基于.NET开源、强大易用的短链生成及监控系统

前言 今天大姚给大家分享一个基于.NET开源&#xff08;MIT License&#xff09;、免费、强大易用的短链生成及监控系统&#xff1a;SuperShortLink。 项目介绍 SuperShortLink是一个基于.NET开源&#xff08;MIT License&#xff09;、免费、强大易用的短链生成及监控系统&a…

java算法day25

java算法day25 广度优先搜索岛屿数量深搜岛屿数量广搜994 腐烂的橘子 广度优先搜索 核心&#xff1a;从起点出发&#xff0c;以起始点为中心一圈一圈进行搜索&#xff0c;一旦遇到终点&#xff0c;记录之前走过的节点就是一条最短路。搜索的方式是上下左右 一张图说明白模拟…

【目标检测】Yolo5基本使用

前言 默认安装好所有配置&#xff0c;只是基于Yolo5项目文件开始介绍的。基于配置好的PyCharm进行讲解配置。写下的只是些基本内容&#xff0c;方便以后回忆用。避免配置好Yolo5的环境&#xff0c;拉取好Yolo5项目后&#xff0c;不知道该如何下手。如果有时间&#xff0c;我还是…

SeaCMS海洋影视管理系统远程代码执行漏洞复现

SeaCMS海洋影视管理系统远程代码执行漏洞复现 Ⅰ、环境搭建Ⅱ、漏洞复现Ⅲ、漏洞分析 免责声明&#xff1a;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&…

2024年7月29日 十二生肖 今日运势

小运播报&#xff1a;2024年7月29日&#xff0c;星期一&#xff0c;农历六月廿四 &#xff08;甲辰年辛未月甲午日&#xff09;&#xff0c;法定工作日。 红榜生肖&#xff1a;羊、虎、狗 需要注意&#xff1a;兔、牛、鼠 喜神方位&#xff1a;东北方 财神方位&#xff1a;…

扰动观测器DOB设计及其MATLAB/Simulink实现

扰动观测器(Disturbance Observer, DOB)是一种在控制系统中用于估计和补偿未知扰动的重要工具,以增强系统的鲁棒性和稳定性。其设计过程涉及系统建模、观测器结构设计以及控制律的调整。 扰动观测器设计原理 系统建模: 首先,需要建立被控对象的数学模型,明确系统的状态变…

HiveSQL题——炸裂+开窗

一、每个学科的成绩第一名是谁&#xff1f; 0 问题描述 基于学生成绩表输出每个科目的第一名是谁呢&#xff1f; 1 数据准备 with t1 as(selectzs as name,[{"Chinese":80},{"Math":70},{"English"…

mac下通过brew安装mysql的环境调试

mac安装mysql 打开终端&#xff0c;运行命令&#xff08;必须已经装过homebrew哦&#xff09;&#xff1a; 安装brewbin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"已安装brew直接运行&#xff1a;brew install mysql8.0报…