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

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

3天内不再提示

CUDA 6中的统一内存模型

Linux阅码场 来源:Linuxer 作者:Linuxer 2020-07-02 14:08 次阅读

CUDA介绍

CUDA(Compute Unified Device Architecture,统一计算设备架构)是由NVIDIA公司于 2006 年所推出的一种并行计算技术,是该公司对于GPGPU( General-purpose computing on graphics processing units, 图形处理单元上的通用计算 )技术的正式命名。通过此技术,用户可在GPU上进行通用计算,而开发人员可以使用C语言来为CUDA架构编写程序 。相比CPU,拥有CUDA技术的GPU成本不高,但计算性能很突出。本文中提到的是2014年发布的CUDA6, CUDA6最重要的新特性就是支持统一内存模型(Unified Memory)。

注:文中经常出现“主机和设备”,本文的“主机”特指CPU、“设备”特指GPU。

CUDA 6中的统一内存模型

NVIDIA在CUDA 6中引入了统一内存模型 ( Unified Memory ),这是CUDA历史上最重要的编程模型改进之一。在当今典型的PC或群集节点中,CPU和GPU的内存在物理上是独立的,并通过PCI-Express总线相连。在CUDA6之前, 这是程序员最需要注意的地方。CPU和GPU之间共享的数据必须在两个内存中都分配,并由程序直接地在两个内存之间来回复制。这给CUDA编程带来了很大难度。

统一内存模型创建了一个托管内存池(a pool of managed memory),该托管内存池由CPU和GPU共享,跨越了CPU与GPU之间的鸿沟。CPU和GPU都可以使用单指针访问托管内存。关键是系统会自动地在主机和设备之间迁移在统一内存中分配的数据,从而使那些看起来像CPU内存中的代码在CPU上运行,而另一些看起来像GPU内存中的代码在GPU上运行。

在本文中,我将向您展示统一内存模型如何显著简化GPU加速型应用程序中的内存管理。下图显示了一个非常简单的示例。两种代码都从磁盘加载文件,对其中的字节进行排序,然后在释放内存之前使用CPU上已排序的数据。右侧的代码使用CUDA和统一内存模型在GPU上运行。和左边代码唯一的区别是,右边代码由GPU来启动一个内核(并在启动后进行同步),并使用新的API cudaMallocManaged() 在统一内存模型中为加载的文件分配空间。

CUDA 6中的统一内存模型

如果您曾经编程过CUDA C / C++,那么毫无疑问,右侧的代码会为您带来震撼。请注意,我们只分配了一次内存,并且只有一个指针指向主机和设备上的可访问数据。我们可以直接地将文件的内容读取到已分配的内存,然后就可以将内存的指针传递给在设备上运行的CUDA内核。然后,在等待内核处理完成之后,我们可以再次从CPU访问数据。CUDA运行时隐藏了所有复杂性,自动将数据迁移到访问它的地方。

统一内存模型提供了什么

统一内存模型为程序员提供了两大捷径

简化编程、简化内存模型

统一内存模型通过使设备内存管理(device memory management)成为一项可选的优化,而不是一项硬性的要求,从而降低了CUDA平台上并行编程的门槛。借助统一内存模型,程序员现在可以直接开发并行的CUDA内核,而不必担心分配和复制设备内存的细节。这将降低在CUDA平台上编程的学习成本,也使得将现有代码移植到GPU的工作变得容易。但这些好处不仅有利于初学者。我在本文后面的示例中将展示统一内存模型如何使复杂的数据结构更易于与设备代码一起使用,以及它与C++结合时的强大威力。

通过数据局部性原理提高性能

通过在CPU和GPU之间按需迁移数据,统一内存模型可以满足GPU上本地数据的性能需求,同时还提供了易于使用的全局共享数据。这个功能的复杂细节被 CUDA驱动程序和运行时隐藏了,以确保应用程序代码更易于编写。迁移的关键是从每个处理器获得全部带宽。250 GB / s的GDDR5内存对于保证开普勒( Kepler )GPU的计算吞吐量至关重要。

值得注意的是, 一个经过精心调优的CUDA程序,即使用流(streams)和 cudaMemcpyAsync来有效地将执行命令与数据传输重叠的程序,会比仅使用统一内存模型的CUDA程序更好 。可以理解的是:CUDA运行时从来没有像程序员那样提供何处需要数据或何时需要数据的信息!CUDA程序员仍然可以显式地访问设备内存分配和异步内存拷贝,以优化数据管理和CPU-GPU并发机制 。首先,统一内存模型提高了生产力,它为并行计算提供了更顺畅的入口,同时它又不影响高级用户的任何CUDA功能。

统一内存模型 vs 统一虚拟寻址?

自CUDA4起,CUDA就支持统一虚拟寻址(UVA),并且尽管统一内存模型依赖于UVA,但它们并不是一回事。UVA为 系统中的所有内存提供了单个虚拟内存地址空间,无论指针位于系统中的何处,无论在设备内存(在相同或不同的GPU上)、主机内存、或片上共享存储器。UVA也允许 cudaMemcpy在不指定输入和输出参数确切位置的情况下使用。UVA启用“零复制(Zero-Copy)” 内存,“零复制”内存是固定的主机内存,可由设备上的代码通过PCI-Express总线直接访问,而无需使用 memcpy。零复制为统一内存模型提供了一些便利,但是却没有提高性能,因为它总是通过带宽低而且延迟高的PCI-Express进行访问。

UVA不会像统一内存模型一样自动将数据从一个物理位置迁移到另一个物理位置。由于统一内存模型能够在主机和设备内存之间的各级页面自动地迁移数据,因此它需要进行大量的工程设计,因为它需要在CUDA运行时(runtime)、设备驱动程序、甚至OS内核中添加新功能。以下示例旨在让您领会到这一点。示例:消除深层副本

统一内存模型的主要优势在于,在访问GPU内核中的结构化数据时,无需进行深度复制(deep copies),从而简化了异构计算内存模型。如下图所示,将包含指针的数据结构从CPU传递到GPU要求进行“深度复制”。

下面以struct dataElem为例。

struct dataElem {int prop1;int prop2;char *name;}

要在设备上使用此结构体,我们必须复制结构体本身及其数据成员,然后复制该结构体指向的所有数据,然后更新该结构体。副本中的所有指针。这导致下面的复杂代码,这些代码只是将数据元素传递给内核函数。

void launch(dataElem *elem) { dataElem *d_elem;char *d_name;

int namelen = strlen(elem-》name) + 1;

// Allocate storage for struct and name cudaMalloc(&d_elem, sizeof(dataElem)); cudaMalloc(&d_name, namelen);

// Copy up each piece separately, including new “name” pointer value cudaMemcpy(d_elem, elem, sizeof(dataElem), cudaMemcpyHostToDevice); cudaMemcpy(d_name, elem-》name, namelen, cudaMemcpyHostToDevice); cudaMemcpy(&(d_elem-》name), &d_name, sizeof(char*), cudaMemcpyHostToDevice);

// Finally we can launch our kernel, but CPU & GPU use different copies of “elem” Kernel《《《 。.. 》》》(d_elem);}

可以想象,在CPU和GPU代码之间分享复杂的数据结构所需的额外主机端代码对生产率有严重影响。统一内存模型中分配我们的“ dataElem”结构可消除所有多余的设置代码,这些代码与主机代码被相同的指针操作,留给我们的就只有内核启动了。这是一个很大的进步!

void launch(dataElem *elem) { kernel《《《 。.. 》》》(elem);}

但统一内存模型不仅大幅降低了代码复杂性。还可以做一些以前无法想象的事情。让我们看另一个例子。

Example: CPU/GPU Shared Linked Lists

链表是一种非常常见的数据结构,但是由于它们本质上是由指针组成的嵌套数据结构,因此在内存空间之间传递它们非常复杂。如果没有统一内存模型,则无法在CPU和GPU之间分享链表。唯一的选择是在零拷贝内存(被pin住的主机内存)中分配链表,这意味着GPU的访问受限于PCI-express性能。通过在统一内存模型中分配链表数据,设备代码可以正常使用GPU上的指针,从而发挥设备内存的全部性能。程序可以维护单链表,并且无论在主机或设备中都可以添加和删除链表元素。

将具有复杂数据结构的代码移植到GPU上曾经是一项艰巨的任务,但是统一内存模型使此操作变得非常容易。我希望统一内存模型能够为CUDA程序员带来巨大的生产力提升。

Unified Memory with C++

统一内存模型确实在C++数据结构中大放异彩。C++通过带有拷贝构造函数(copy constructors)的类来简化深度复制问题。拷贝构造函数是一个知道如何创建类所对应对象的函数,拷贝构造函数为对象的成员分配空间并从其他对象复制值过来。C++还允许 new和 delete这俩个内存管理运算符被重载。这意味着我们可以创建一个基类,我们将其称为 Managed,它在重载的 new运算符内部使用 cudaMallocManaged(),如以下代码所示。

class Managed {public:void *operator new(size_t len) {void *ptr; cudaMallocManaged(&ptr, len); cudaDeviceSynchronize();return ptr; }

void operator delete(void *ptr) { cudaDeviceSynchronize(); cudaFree(ptr); }};

然后,我们可以让 String类继承 Managed类,并实现一个拷贝构造函数,该拷贝构造函数为需要拷贝的字符串分配统一内存。

// Deriving from “Managed” allows pass-by-referenceclass String : public Managed { int length; char *data;

public:// Unified memory copy constructor allows pass-by-value String (const String &s) { length = s.length; cudaMallocManaged(&data, length); memcpy(data, s.data, length); }

// 。..};

同样,我们使我们的 dataElem类也继承 Managed。

// Note “managed” on this class, too.// C++ now handles our deep copiesclass dataElem : public Managed {public:int prop1;int prop2; String name;};

通过这些更改,C++的类将在统一内存中分配空间,并自动处理深度复制。我们可以像分配任何C++的对象那样在统一内存中分配一个 dataElem。

dataElem *data = new dataElem;

请注意,您需要确保树中的每个类都继承自 Managed,否则您的内存映射中会有一个漏洞。实际上,任何你想在CPU和GPU之间分享的内容都应该继承 Managed。如果你倾向于对所有程序都简单地使用统一内存模型,你可以在全局重载 new和 delete, 但这只在这种情况下有作用——你的程序中没有仅被CPU访问的数据(即程序中的所有数据都被GPU访问),因为只有CPU数据时没有必要迁移数据。

现在,我们可以选择将对象传递给内核函数了。如在C++中一样,我们可以按值传递或按引用传递,如以下示例代码所示。

// Pass-by-reference version__global__ void kernel_by_ref(dataElem &data) { 。.. }

// Pass-by-value version__global__ void kernel_by_val(dataElem data) { 。.. }

int main(void) { dataElem *data = new dataElem; 。..// pass data to kernel by reference kernel_by_ref《《《1,1》》》(*data);

// pass data to kernel by value -- this will create a copy kernel_by_val《《《1,1》》》(*data);}

多亏了统一内存模型,深度复制、按值传递和按引用传递都可以正常工作。统一内存模型为在GPU上运行C++代码提供了巨大帮助。

这篇文章的例子可以在Github上找到。

统一内存模型的光明前景

CUDA 6中关于统一内存模型的最令人兴奋的事情之一就是它仅仅是个开始。我们针对统一内存模型有一个包括性能提升与特性的长远规划。我们的第一个发行版旨在使CUDA编程更容易,尤其是对于初学者而言。从CUDA 6开始, cudaMemcpy()不再是必需的。通过使用 cudaMallocManaged(),您可以拥有一个指向数据的指针,并且可以在CPU和GPU之间共享复杂的C / C++数据结构。这使编写CUDA程序变得容易得多,因为您可以直接编写内核,而不是编写大量数据管理代码并且要维护在主机和设备之间所有重复的数据。您仍然可以自由使用 cudaMemcpy()(特别是 cudaMemcpyAsync())来提高性能,但现在这不是一项要求,而是一项优化。

CUDA的未来版本可能会通过添加数据预取和迁移提示来提高使用统一内存模型的应用程序的性能。我们还将增加对更多操作系统的支持。我们的下一代GPU架构将带来许多硬件改进,以进一步提高性能和灵活性。
责任编辑:pj

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

    关注

    8

    文章

    6876

    浏览量

    88805
  • 内存
    +关注

    关注

    8

    文章

    2998

    浏览量

    73873
  • 编程
    +关注

    关注

    88

    文章

    3587

    浏览量

    93585
收藏 人收藏

    评论

    相关推荐

    抛弃8GB内存,端侧AI大模型加速内存升级

    电子发烧友网报道(文/黄晶晶)端侧AI大模型的到来在存储市场产生了最直接的反应。年初在我们对旗舰智能手机的存储容量统计,16GB内存+512GB存储成为几乎所有旗舰机型都提供的选择。毕竟AI大
    的头像 发表于 11-03 00:02 3959次阅读
    抛弃8GB<b class='flag-5'>内存</b>,端侧AI大<b class='flag-5'>模型</b>加速<b class='flag-5'>内存</b>升级

    【「大模型时代的基础架构」阅读体验】+ 第、二章学习感受

    如下图所示。无论是CPU还是GPU,所有运算过程的中间结果都需要被保存到内存,而TPU根本没有将中间结果保存到内存,而是在执行完毕后直接将中间结果传递给下
    发表于 10-10 10:36

    统一多云管理平台怎么用?

    的IT基础设施管理功能,帮助企业在日益复杂的云计算环境实现高效管理和成本优化,Rak小编统一多云管理平台怎么用?
    的头像 发表于 08-14 11:28 191次阅读

    打破英伟达CUDA壁垒?AMD显卡现在也能无缝适配CUDA

    电子发烧友网报道(文/梁浩斌)直以来,围绕CUDA打造的软件生态,是英伟达在GPU领域最大的护城河,尤其是随着目前AI领域的发展加速,市场火爆,英伟达GPU+CUDA的开发生态则更加稳固,AMD
    的头像 发表于 07-19 00:16 4519次阅读

    软件生态上超越CUDA,究竟有多难?

    神坛的,还是围绕CUDA打造的系列软件生态。   英伟达——CUDA的绝对统治   相信对GPU有过定了解的都知道,英伟达的最大护城河就是CUD
    的头像 发表于 06-20 00:09 3483次阅读

    Keil使用AC6编译提示CUDA版本过高怎么解决?

    \' ArmClang: warning: Unknown CUDA version 10.2. Assuming the latest supported version 10.1
    发表于 04-11 07:56

    摩尔线程MUSA/MUSIFY与英伟达CUDA无依赖,开发者无忧

    首先,摩尔线程MUSA/MUSIFY并不受到英伟达CUDA这项条款的限制,使用者可以放心地使用其相关内容。MUSA即摩尔线程自行研发,享有高度自主知识产权的全功能GPU先进计算统一系统架构;
    的头像 发表于 03-06 09:22 1210次阅读

    物理内存模型的演变

    内存管理概述,主要是以Linux v2.6.11为例进行分析的,但是计算技术在不断发展,新的存储架构、新的指令集架构、新的SoC架构等都对物理内存模型的抽象提出了更高要求。为此,必须
    的头像 发表于 02-25 10:35 433次阅读

    深入浅出理解PagedAttention CUDA实现

    vLLM ,LLM 推理的 prefill 阶段 attention 计算使用第三方库 xformers 的优化实现,decoding 阶段 attention 计算则使用项目编译 CUDA 代码实现。
    的头像 发表于 01-09 11:43 1755次阅读
    深入浅出理解PagedAttention <b class='flag-5'>CUDA</b>实现

    什么是CUDA?谁能打破CUDA的护城河?

    在最近的场“AI Everywhere”发布会上,Intel的CEO Pat Gelsinger炮轰Nvidia的CUDA生态护城河并不深,而且已经成为行业的众矢之的。
    的头像 发表于 12-28 10:26 1.2w次阅读
    什么是<b class='flag-5'>CUDA</b>?谁能打破<b class='flag-5'>CUDA</b>的护城河?

    【飞腾派4G版免费试用】第四章:部署模型到飞腾派的尝试

    部署模型到飞腾派 本章作为个这几天,我尝试将训练的佩奇检测模型部署到飞腾派的阶段总结,在部署的过程,我的计划是分三步走: 在第三章的基础上,直接迁移
    发表于 12-20 21:10

    个用于6D姿态估计和跟踪的统一基础模型

    今天笔者将为大家分享NVIDIA的最新开源方案FoundationPose,是个用于 6D 姿态估计和跟踪的统一基础模型。只要给出CAD模型
    的头像 发表于 12-19 09:58 815次阅读
    <b class='flag-5'>一</b>个用于<b class='flag-5'>6</b>D姿态估计和跟踪的<b class='flag-5'>统一</b>基础<b class='flag-5'>模型</b>

    英特尔:让我们起消灭CUDA

    基尔辛格认为:"由于推理的发生,旦你训练了模型......就不会依赖CUDA。"关键在于,你能否很好地运行该模型?他表示,英特尔将利用今日首次在舞台上展示的 Gaudi3 迎接挑战,
    的头像 发表于 12-15 17:12 965次阅读

    jvm内存模型内存结构

    JVM(Java虚拟机)是Java程序的运行平台,它负责将Java程序转换成机器码并在计算机上执行。在JVM内存模型内存结构是两个重要的概念,本文将详细介绍它们。
    的头像 发表于 12-05 11:08 889次阅读

    java内存溢出排查方法

    Java内存溢出(Memory overflow)是指Java虚拟机(JVM)的堆内存无法满足对象分配的需求,导致程序抛出OutOfMemoryError异常。内存溢出是Java开发
    的头像 发表于 11-23 14:46 3101次阅读