GPU/DCU减少cudaMemcpy/hipMemcpy时间方案

2023-12-26 06:58

本文主要是介绍GPU/DCU减少cudaMemcpy/hipMemcpy时间方案,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

CUDA是GPU加速器上的编程语言,HIP是DCU加速器上的编程语言,二者生态相似,CUDA可通过hipify工具转换为HIP HIP docs: https://rocmdocs.amd.com/en/latest/index.html

cudaMemcpy/hipMemcpy时间 = a + b + c

  如上图所示,cudaMemcpy/hipMemcpy的时间应该由三部分组成,分别是CPU的访存时间、CPU到加速卡的时间、还有加速卡的访存时间。

曙光超算节点天河2A超算节点
a带宽DRMA:30~40GB/sDRMA:30~40GB/s
b带宽PCIe 3.0 x16: 16GB/sPCIe 3.0 x16: 16GB/s
c带宽HBM2: 1TB/sGDDR5: 480GB/s
加速卡计算峰值DCU: 5.7Tflop/sGPU:2.91 Tflop/s

  上表对比了曙光先进计算服务平台节点和天河2A超算节点的带宽与加速卡峰值,可以看到两台超算的a、b、c三者中b是主要的瓶颈,也就是CPU到加速卡的时间,主要受限于PCIe总线带宽。PCIe 3.0 x16的带宽和计算峰值几个Tflop/s相差太大,所以应该尽量减少主机端和设备之间的数据传输,下文测试了两台超算上的传输时间并探讨减少传输时间的几个方案。

一、传输延迟测试

  在两种设备上用了同一个测试用例,测试规模从50万的整型数到2亿个整型数,测试对象主要是从CPU往加速设备端拷贝时间的过程,理论上从主机到设备端和从设备端到主机的时间应该是一致的,循环10次拷贝的过程,每一次都输出,统计时取10次拷贝时间的平均值。然后对同一个节点里的不同加速设备都进行了测试,曙光先进计算服务平台上每个节点里有4个DCU,天河中国国家网格12区上有2个GPU的权限。

小规模拷贝时间测试
大规模拷贝时间测试
  测试结果可以看出较小规模的(也就是1000万整型数、9.5MB以下)的GPU拷贝速度更快,较大规模的DCU拷贝速度更快。

二、减少传输时间方案

1.数据重用

  首先应该尽可能地重用数据,能传一次就不传两次。
  以Lanczos迭代法为例,该算法的目的是形成矩阵A的三对角矩阵T:
       T = [ α 1 β 1 0 β 1 α 2 ⋱ ⋱ ⋱ ⋱ ⋱ α n − 1 β n − 1 0 β n − 1 α n ] T=\left[\begin{array}{lllll}\alpha_{1} & \beta_{1} & & & 0 \\ \beta_{1} & \alpha_{2} & \ddots & & \\ & \ddots & \ddots & \ddots & \\ & & \ddots & \alpha_{n-1} & \beta_{n-1} \\ 0 & & & \beta_{n-1} & \alpha_{n}\end{array}\right] T=α1β10β1α2αn1βn10βn1αn
T = Q T A Q T=Q^{T}AQ T=QTAQ Q = [ q 1 , q 2 , . . . , q n ] Q=[q_{1},q_{2},...,q_{n}] Q=[q1,q2,...,qn],Q是正交矩阵, q i q_{i} qi是Q的列向量,其迭代格式为:
     - α 1 = q 1 T A q 1 \alpha_{1}=q_{1}^{T} A q_{1} α1=q1TAq1
     - r i = A q i − α i q i − β i − 1 q i − 1 r_{i}=A q_{i}-\alpha_{i} q_{i}-\beta_{i-1} q_{i-1} ri=Aqiαiqiβi1qi1
     - β i = ∣ r i ∣ 2 \beta_{i}=\left|r_{i}\right|_{2} βi=ri2
     - q i + 1 = r i / β 1 q_{i+1}=r_{i} / \beta_{1} qi+1=ri/β1
     - α i + 1 = q i + 1 T A q i + 1 , i = 1 , 2 , ⋯ , k \alpha_{i+1}=q_{i+1}^{T} A q_{i+1}, i=1,2, \cdots, \mathrm{k} αi+1=qi+1TAqi+1,i=1,2,,k
  每一次迭代的过程都会求得矩阵Q的一个列向量 q i q_{i} qi,但本次迭代是依赖于上一次迭代求得的列向量 q i − 1 q_{i-1} qi1的,所以算法整体有很强的数据相关性,难以对整个迭代过程并行化,但是可以对Lanczos过程中的计算热点稀疏矩阵向量乘 A q i A q_{i} Aqi进行并行化。
  并行思路是将A矩阵按行划分,使用GPU/DCU等加速卡,把N行分到N个线程块中去计算。可以看到迭代过程虽然是数据相关的,但只要用到上一次迭代的 q i − 1 q_{i-1} qi1向量,而规模最大的A矩阵在每一次迭代中都是一样的,所以可以只在迭代的第一次把A传到设备端,使用__device__标识的全局变量把它存起来,后面的迭代直接从全局变量中取,可以省不少时间。

2.Pinned Memory

  第二个方案是使用锁页内存。所谓锁页内存就是CPU内存中不会被换入换出的一块内存。
  主机端内存采用分页式管理,为了提高主机端访存效率,操作系统会经常换入换出DRAM中的页到Cache或外存中,对于主机端程序没有影响,只要逻辑地址正确操作系统会帮助管理物理页。但从主机端传输到设备端过程中为了保证传输的页不被“换走”,需要进行页面锁定,把要传输到GPU的页复制到固定内存,再从固定内存传入到设备内存。这就需要两次传送过程,增加了传输延迟。解决办法是在分配内存的时候,使用cudaMallocHost/hipMallocHost直接从锁页内存里面分配,这样就少了一次传输过程,如下图:
pinned memory
  锁页内存是可以自己制定分配多少的,但它的缺点是是分配空间过多可能会降低主机系统的性能,因为它减少了用于存储虚拟内存数据的可分页内存的数量,所以需要在机器上测试合适的锁页内存。

3.高维矩阵传输

  第三个方案是使用高维的矩阵传输命令:hipMallocPitch配合上hipMemcpy2D / hipMemcpy3D。通常来讲,在DCU中分配内存使用的是hipMalloc函数,但是对于二维或者三维矩阵而言,使用hipMalloc来分配内存并不能得到最好的性能,原因是对于2D或者3D内存,对齐是一个很重要的性质,而hipMallocPitch或者hipMalloc3D这两个函数能够保证分配的内存是合理对齐的,满足合并访问要求,因此可以确保对行访问时具有最优的效率。
  比如说下面这个矩阵a1-a9:
               [ a 1 a 2 a 3 a 4 a 5 a 6 a 7 a 8 a 9 ] \left[\begin{array}{lll}a_{1} & a_{2} & a_{3} \\ a_{4} & a_{5} & a_{6} \\ a_{7} & a_{8} & a_{9}\end{array}\right] a1a4a7a2a5a8a3a6a9
  如果用普通的分配方式hipMalloc来存储矩阵,在内存里面他会挨着线性存储,但是用对齐分配方式hipMallocPitch来分配的话,存储的时候他会按照对齐的字节数来存,假如内存是以4个字节对齐的,并且每个线程束里面有4个线程,写入的时候会使每行的首地址对齐,都是4的倍数,即使有没填满的内存也没关系;读取的时候直接三个线程数可以并行读取a1-a9,如下图:
内存对齐
  这里只是举一个例子以4个字节对齐,事实上hip访问global memory的过程中,从128字节对齐的地址(addr=0, ,128,256)开始的连续访问是最有效率的。

4.传输、计算时间重叠

  第四个方案是将计算和传输时间进行重叠,在hip中可以利用stream流来做到这一点,Stream是一种逻辑队列,如果不指定某指令的stream那它会默认放到0号stream上。假设有3个kernel以及一些内存拷贝函数,launch到默认0号stream上:
在这里插入图片描述

hipMemcpy(d_a1,h_a1,Nbytes,hipMemcpyHostToDevice);
hipMemcpy(d_a2,h_a2,Nbytes,hipMemcpyHostToDevice);
hipMemcpy(d_a3,h_a3,Nbytes,hipMemcpyHostToDevice);
hipLaunchKernelGGL(myKernel1,blocks,threads,0,0,N,d_a1);
hipLaunchKernelGGL(myKernel2,blocks,threads,0,0,N,d_a2);
hipLaunchKernelGGL(myKernel3,blocks,threads,0,0,N,d_a3);
hipMemcpy(h_a1, d_a1, Nbytes,hipMemcpyDeviceToHost);
hipMemcpy(h_a2, d_a2, Nbytes,hipMemcpyDeviceToHostTo);
hipMemcpy(h_a3, d_a3, Nbytes,hipMemcpyDeviceToHost);

  也可以通过异步拷贝函数和指定不同stream上的指令流来让计算和拷贝重叠:
在这里插入图片描述

hipMemcpyAsync(d_a1,h_a1,Nbytes,hipMemcpyHostToDevice,stream1);
hipMemcpyAsync(d_a2,h_a2,Nbytes,hipMemcpyHostToDevice,stream2);
hipMemcpyAsync(d_a3,h_a3,Nbytes,hipMemcpyHostToDevice,stream3);
hipLaunchKernelGGL(myKernel1,blocks,threads,0,0,N,d_a1);
hipLaunchKernelGGL(myKernel2,blocks,threads,0,0,N,d_a2);
hipLaunchKernelGGL(myKernel3,blocks,threads,0,0,N,d_a3);
hipMemcpyAsync(h_a1, d_a1, Nbytes,hipMemcpyDeviceToHost,stream1);
hipMemcpyAsync(h_a2, d_a2, Nbytes,hipMemcpyDeviceToHost,stream2);
hipMemcpyAsync(h_a3, d_a3, Nbytes,hipMemcpyDeviceToHost,stream3);

5.批量传输、选择大粒度并行

  第五个方案是积少成多,把小的数据尽可能合并、打包成为大一点的数据批量传输,因为每次传输过程中不只有传输过程耗时,还有一些其他的开销,比如传输指令本身的延迟。例如下面这个表,实际传输时间和理论传输时间还是有一定差距。

数据规模/万个int数据规模/MBDCU实际传输时间/ms理论传输时间/ms
50019.12.21.1
100038.14.02.3
200076.37.84.7
4000152.615.29.3
6000228.915.514.0

  还可以提高并行粒度,在数据相关性不高的情况下尽量在循环的外层进行优化,内循环中使用传输函数的开销太大

总结

  减少传输时间的方案:

	 - 数据重用 - 使用pinned memory - 高维数组使用pitch、2Dcpy方法 - 将数据传输与计算的时间重叠 - 批量传输数据,集小为大- 避免对小粒度程序并行(e.g 内循环)

这篇关于GPU/DCU减少cudaMemcpy/hipMemcpy时间方案的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

Python的Darts库实现时间序列预测

《Python的Darts库实现时间序列预测》Darts一个集统计、机器学习与深度学习模型于一体的Python时间序列预测库,本文主要介绍了Python的Darts库实现时间序列预测,感兴趣的可以了解... 目录目录一、什么是 Darts?二、安装与基本配置安装 Darts导入基础模块三、时间序列数据结构与

MyBatis Plus实现时间字段自动填充的完整方案

《MyBatisPlus实现时间字段自动填充的完整方案》在日常开发中,我们经常需要记录数据的创建时间和更新时间,传统的做法是在每次插入或更新操作时手动设置这些时间字段,这种方式不仅繁琐,还容易遗漏,... 目录前言解决目标技术栈实现步骤1. 实体类注解配置2. 创建元数据处理器3. 服务层代码优化填充机制详

防止Linux rm命令误操作的多场景防护方案与实践

《防止Linuxrm命令误操作的多场景防护方案与实践》在Linux系统中,rm命令是删除文件和目录的高效工具,但一旦误操作,如执行rm-rf/或rm-rf/*,极易导致系统数据灾难,本文针对不同场景... 目录引言理解 rm 命令及误操作风险rm 命令基础常见误操作案例防护方案使用 rm编程 别名及安全删除

Python实现批量CSV转Excel的高性能处理方案

《Python实现批量CSV转Excel的高性能处理方案》在日常办公中,我们经常需要将CSV格式的数据转换为Excel文件,本文将介绍一个基于Python的高性能解决方案,感兴趣的小伙伴可以跟随小编一... 目录一、场景需求二、技术方案三、核心代码四、批量处理方案五、性能优化六、使用示例完整代码七、小结一、

C++统计函数执行时间的最佳实践

《C++统计函数执行时间的最佳实践》在软件开发过程中,性能分析是优化程序的重要环节,了解函数的执行时间分布对于识别性能瓶颈至关重要,本文将分享一个C++函数执行时间统计工具,希望对大家有所帮助... 目录前言工具特性核心设计1. 数据结构设计2. 单例模式管理器3. RAII自动计时使用方法基本用法高级用法

C#使用Spire.Doc for .NET实现HTML转Word的高效方案

《C#使用Spire.Docfor.NET实现HTML转Word的高效方案》在Web开发中,HTML内容的生成与处理是高频需求,然而,当用户需要将HTML页面或动态生成的HTML字符串转换为Wor... 目录引言一、html转Word的典型场景与挑战二、用 Spire.Doc 实现 HTML 转 Word1

使用Python实现Word文档的自动化对比方案

《使用Python实现Word文档的自动化对比方案》我们经常需要比较两个Word文档的版本差异,无论是合同修订、论文修改还是代码文档更新,人工比对不仅效率低下,还容易遗漏关键改动,下面通过一个实际案例... 目录引言一、使用python-docx库解析文档结构二、使用difflib进行差异比对三、高级对比方

C# LiteDB处理时间序列数据的高性能解决方案

《C#LiteDB处理时间序列数据的高性能解决方案》LiteDB作为.NET生态下的轻量级嵌入式NoSQL数据库,一直是时间序列处理的优选方案,本文将为大家大家简单介绍一下LiteDB处理时间序列数... 目录为什么选择LiteDB处理时间序列数据第一章:LiteDB时间序列数据模型设计1.1 核心设计原则

MySQL按时间维度对亿级数据表进行平滑分表

《MySQL按时间维度对亿级数据表进行平滑分表》本文将以一个真实的4亿数据表分表案例为基础,详细介绍如何在不影响线上业务的情况下,完成按时间维度分表的完整过程,感兴趣的小伙伴可以了解一下... 目录引言一、为什么我们需要分表1.1 单表数据量过大的问题1.2 分表方案选型二、分表前的准备工作2.1 数据评估

Python多线程应用中的卡死问题优化方案指南

《Python多线程应用中的卡死问题优化方案指南》在利用Python语言开发某查询软件时,遇到了点击搜索按钮后软件卡死的问题,本文将简单分析一下出现的原因以及对应的优化方案,希望对大家有所帮助... 目录问题描述优化方案1. 网络请求优化2. 多线程架构优化3. 全局异常处理4. 配置管理优化优化效果1.