根据《证券期货投资者适当性管理办法》及配套指引,本资料仅面向华创证券客户中的金融机构专业投资者,请勿对本资料进行任何形式的转发。若您不是华创证券客户中的金融机构专业投资者,请勿订阅、接收或使用本资料中的信息。
本资料难以设置访问权限,若给您造成不便,敬请谅解。感谢您的理解与配合。
并
行计算
根据菲利(Flynn)的分类体系,系统可分为四种类型:单指令单数据(SISD)、单指令多数据(SIMD)、多指令单数据(MISD)和多指令多数据(MIMD)。随着金融数据量的持续增长以及对分析结果时效性的迫切需求,加速计算任务成为当务之急。充分利用计算机的计算资源,将计算任务从“单指令单数据”向“多指令多数据”转变,已成为一项重要课题。本篇报告主要介绍了CPU和GPU加速计算的原理;提供了利用python和C语言(CUDA)对常见计算进行加速的案例;比较了影响计算效率的影响因素,例如进程数量、数据量大小等
。
CPU并行
现代CPU通常集成多个核心,每个核心都可独立执行指令。多核计算利用这些核心同时执行不同任务,从而提高计算性能。Python和C语言都支持多进程和多线程编程,使用户能够充分利用多核并行计算。相对于串行计算,合理利用并行计算可将特定任务的计算时间缩短80%以上。
本报告
提供了
CPU
并行计算案例以及重要参数对计算效率的影响
。测试结果表明,多进程计算随着进程数量的增加,存在“快速提高计算效率”、“效率不再提升”和“效率逐渐下降”三个阶段。
GPU并行
从并行计算的视角来看,英伟达于2001年发布的GeForce 3代表着GPU并行计算的重要突破。随后推出的一系列计算型显卡推动了GPU并行计算的发展。利用GPU的并行计算能力结合CUDA技术,可以实现复杂任务的加速,例如利用GPU加速常见的机器学习框架TensorFlow、PyTorch等。实验结果显示,相对于串行计算,利用GPU计算特定任务可使计算时间缩短90%以上。
测试结果表明对
大量数据的合并、聚合统计和用户自定义函数
GPU
的加速效果明显
,
处理数据量较小的任务直接使用
CPU
更好。
CPU(Central Processing Unit,中央处理器)逻辑上由三个部分组成,分别是控制单元(下图中CU和中断系统)、运算单元(Arithmetic and Logic Unit,ALU)和存储单元(缓存和寄存器),三部分通过内部总线连接起来。
CPU
每取出来并执行一条指令所需的全部时间称为指令周期,也就是
CPU
完成一条指令的时间。在大多数情况下,
CPU
就是按照“取指令
-
执行指令
-
再取指令
-
再执行指令
…
”这个流程进行的。其对应于
CPU
主频频率,在同系列的
CPU
中,
主频越高运行速度越快
。
CPU的存储单元是CPU中暂时保存数据的地方,保存着待处理或者已经处理好的数据,利用寄存器(cache)和缓存,
增加寄存器容量可以减少
CPU访问内存的次数
,从而提高CPU的运行速度。缓存包括CPU的一级缓存和二级缓存。
以上为单个CPU内核的主要参数,但是对于单个CPU核心而言,同一时刻只能运行一个进程。为了提高计算机的运行效率,CPU逐渐向着单块CPU上集成多个CPU核心和单个核心可以执行多个进程发展。多核CPU指的是将多个CPU核心放在一块CPU上。多线程技术指的是一个CPU核心能够运行多个线程的技术。
CPU的并行主要有两种方式,多进程和多线程。计算机程序并不能单独执行,只有将程序加载到内存,系统分配资源后才能执行。进程指的是系统中正在运行的程序,是系统分配资源的基本单位,在内存中有完备的数据空间和代码空间。
而线程,是进程的一个实体,是CPU调度的基本单位。一个CPU核心在同一个时刻只能运行一个线程。一个进程至少拥有一个线程,也可以拥有多个线程。由于同一个核只能运行一个线程,因此当线程数量超过核数的时候反而会影响计算速度。
多进程适用于计算复杂型任务,对于资源管理和保护要求高,不限制开销和效率的任务。因为多进程的每个进程互相独立,都会在内存中开辟计算空间,因此子进程崩溃并不会影响主进程的稳定,但是对内存的占用比较高。多进程的编程和调试相对于多线程也更加简单。对于有全局锁的编程语言,只能使用多进程。
多线程适用于IO复杂型任务,需要频繁创建和销毁的任务场景,例如Web服务器连接、爬虫等。多线程占用内存少、切换简单、速度很快。但多线程中,子进程报错会影响全部进程。线程之间同步复杂,例如不同线程执行任务快慢不同、某些变量不能同时被两个线程修改。
上一节中,我们介绍了影响CPU性能的核心因素是频率和核数。本节我们列举了两块常见的CPU产品,以产品性能参数具体解释其对计算效率的影响。
以英特尔系列产品为例:
对于以酷睿i9为例,其性能核可以同时运行两个线程,而效能核只能运行一个线程,因此13代i9总共可以运行32个线程,12代i9可以运行24个线程。13代酷睿i9在睿频(主频)和存续大小上较12代酷睿i9也有一定提高。高频率意味着处理器能够更快地执行单个任务,而多核心设计则使其能够同时处理多个任务或多线程任务,从而提高整体计算效率。
在选购CPU时,商家通常都会在显著位置标注上述核心指标。从品牌来看,英特尔系列产品和AMD系列产品是目前主流产品,在同等价位下,两者性能差异较小。需要注意的是会有部分机器学习的库依赖CPU型号。
此外还有另一种针对服务器类型的CPU,例如英特尔至强(Xeon)系列。这类CPU侧重于性能稳定、支持多路互联等等。在相同的性能下,服务器CPU价格通常高于普通CPU。
Pandas是python中数据分析常用的一个库,pandarallel对于pandas常见的API(Application Programming Interface)都有比较好的支持。目前pandarallel支持的pandas API有:
测试样本所用的
CPU为12代英特尔酷睿i7。结果表明,多进程计算随着进程数量的增加,存在“快速提高计算效率”、“效率不再提升”和“效率逐渐下降”三个阶段。其原因在于,一开始,计算机的内存和I/O资源是充分的,多进程会迅速提升计算效率,随着进程数量的增加,计算机除线程外的资源会互相竞争导致计算效率下降:
Multiprocessing 库是一个用来产生进程的包,Multiprocessing 包同时提供了本地和远程并发操作,通过使用子进程而非线程有效地绕过了全局解释器锁。Multiprocessing提供了多种方法来进行进程间的通信,如队列、管道、共享内存等。这些通信方式使得不同进程之间能够安全地交换数据和信息。Multiprocessing模块允许程序员充分利用计算机上的多个处理器。
运行测试代码可以从任务管理器中观察到计算机打开了多个python程序,如下图所示,其中python程序的数量取决于计算机的线程数量。
基于线程并行的库 ——threading。
在
CPython
中,由于存在全局解释器锁,同一时刻只有一个线程可以执行
Python
代码,但针对
I/O
密集型任务,多线程仍然是一个合适的选项。
I/O
密集型任务指的是需求频繁进行数据进出操作的任务。因为在
I/O
密集型任务中,大部分时间都花费在等待
I/O
操作完成上,而不是在执行计算密集型操作上。在这种情况下,多线程能够利用等待
I/O
操作的时间来执行其他任务,从而提高了系统的整体效率。
随着python作为一种“胶水语言”的发展,目前将C语言作为生产力工具的使用人员越来越少。但是Cpython作为广泛使用的一种python解释器,背后离不开C语言的优良性质。
在C语言中,实现并行有两种方法, 单进程多线程,或多进程实现,每个进程都有一个或多个线程。 多进程适用于线程管理互斥的任务,例如同时提供用户界面和执行后台计算。多线程适合异步输入和输出 (I/O) 、I/O 完成端口、异步过程调用 (APC)以及等待多个事件的能力。
但由于
C
语言受众比较小,我们不做详细介绍。
GPU的发展离不开图形显示技术的发展。20世纪80年代,用户开始购买2D显示加速卡的个人计算机。这些显卡提供了基于硬件的位图运算功能,能够在图形操作系统的显示和可用性起到辅助作用。20世纪90年代,消费者应用程序对显卡的需求快速增长,其中第一人称射击游戏,例如Doom等,都要求PC游戏创建更加真实的3D场景,可以说,第一人称射击游戏在初期为3D图形技术在消费者应用中的普及起到了极大的推动作用。
从并行计算角度,2001年英伟达发布的GeForce3代表最重要的突破,GeForce3是计算工业第一块实现Direct 8.0标准的芯片。该标准要求硬件中包含可编程的像素点着色功能。开发人员正是从GeForce3系列第一次开始能够对GPU中的计算实现某种精度的控制。
在GeForce3发布的5年后,英伟达发布了GeForce 8800 GTX。GeForce 8800 GTX是第一块基于CUDA(Compute Unified Device Architecture)架构的GPU。CUDA架构使得程序可以对芯片上每个数学逻辑单元进行排列,GPU上的执行单元不仅能任意读写内存,同时可以访问共享内存。CUDA架构的这些性质使得GPU不仅能够执行传统的图形显示,还能高效实现通用计算。换句话说,CUDA提供了在GPU上编程的功能。
CUDA架构最初被用于物理上的流体力学计算和分子动力学模拟,如今被广泛应用于深度学习等领域。
GPU于CPU最大的不同在于其更多的晶体管被用于数据处理,而不是数据缓存和流量控制,如下图所示:
一块
4核CPU可以同时执行4个运算,而一块GPU可以同时执行成千上万个运算。
2010年英伟达发布的费米(Fermi)架构是第一个完整的GPU架构,后续英伟达陆续发布了开普勒(Kepler)和麦克斯韦(Maxwell)架构。从2016年开始英伟达开始向着深度学习发展,推出了Pascal、Volta、Turing和Ampere等架构。
为了理解GPU是如何工作的。我们以2010年英伟达发布的费米(Fermi)架构为例说明GPU能够加速的核心逻辑及其与CPU的差异。Fermi计算核心由16个SM(Stream
Multiprocesser)组成,每个SM包含2个线程束(Warp),16组加载存储单元(LD/ST)和4个特殊函数单元(SFU)组成。每个线程束包含16个Cuda Core组成,每一个Cuda Core由1个浮点数单元FPU和1个逻辑运算单元ALU组成。
SM 又可以拆分成一组流处理器(Stream
Processors),每个流处理器包含一个真正可以用于计算的核心(Core),每个核心都可以执行一个线程。核心是GPU计算的最小单元。所有的核心可以在同一个时刻在不同数据上执行同一个程序,这就是GPU可以加速计算的关键。每个SM包含一定的量的寄存器。这部分寄存器可以存放一些GPU上的变量(local variable)。
费米架构共计包含
512个CUDA核心,每个核心包含一个运算单元。在不考虑其他条件情况下,一块费米架构的GPU可以同时进行512个线程计算,而目前较先进的CPU含有32个进程,只能同时完成32线程的计算。
由于GPU在计算的时候,CUDA核心只能通过GPU的内存取数据,费米架构提供了最多6GB的GDDR5 DRAM内存。GPU内存中的数据通常会被缓存在高速缓存中,以加快访问速度。缓存可以减少对主存或全局内存的访问次数,从而提高程序的性能和效率。在CUDA编程中,需要将数据从主机内存复制到GPU内存,以及将计算结果从GPU内存复制回主机内存,更大的内存有助于减少数据传输所用的时间。
GPU带宽用于在主机(CPU)和设备(GPU)之间传输数据。在进行GPU计算时,通常需要将数据从主机内存复制到GPU内存,以及将计算结果从GPU内存复制回主机内存。GPU带宽决定了数据传输的速度,高带宽意味着可以更快地完成数据传输操作。
在上一章中,我们提到影响GPU性能的核心因素包括了,CUDA核心、缓存、带宽和内存大小。Nvidia公司为了比较不同系列的GPU在计算方面的能力,给出了一个通用指标,计算能力(Compute Capability)。计算能力由设备的版本号表示,有时也称为其“SM版本”。该版本号标识了 GPU 硬件支持的特性,并且在运行时由应用程序使用,以确定当前 GPU 上可用的硬件特性和指令。
计算能力包括主要修订号 X 和次要修订号 Y,并以 X.Y 表示。现在的部分机器学习模式会对GPU的计算力提出要求,例如Tensorflow 2.0要求计算能力不能低于3.5。
具有相同主要修订号的设备具有相同的核心架构。基于 NVIDIA Hopper GPU 架构的设备的主要修订号为 9,基于 NVIDIA Ampere GPU 架构的设备的主要修订号为 8,基于 Volta 架构的设备的主要修订号为 7,基于 Pascal 架构的设备的主要修订号为 6,基于 Maxwell 架构的设备的主要修订号为 5,基于 Kepler 架构的设备的主要修订号为 3。以下为部分Nvidia GPU产品的计算能力。
在不同的计算力版本下,
GPU
所能完成的任务有差异,主要差异在于
GPU
的
SM
设计差异,下表列出了
SM
的差异。目前,很多利用
GPU
的计算任务都会对算力提出要求。
GPU的SM计算能力相同的情况下,不同GPU主要是由其架构、CUDA核心数量、内存大小和内存带宽影响。例如在相同算力的情况下,面向计算的显卡NVIDIA L4其功耗方面有优势,面向消费者的
GeForce RTX 4090
在核心数量方面有优势。
面向不同任务的时候,所需要考虑的因素不同,例如进行大规模计算的时候,显卡功耗越低,其电力需求成本越小,而面向消费者的显卡为了追求性能,并不会考虑功耗。因此,如果是个人消费者,主要目的是打游戏,把深度学习作为研究兴趣,建议配置GeForce高端卡,如果是深度学习或并行计算研究员,建议配置对应的专业卡。
Python利用GPU并行计算的一个重要包是PyCUDA。CUDA是通过对C语言的扩展实现的。CUDA的一个核心思想是“层次”编程,其将程序分解为两部分,一部分通过CPU(host)执行,另一部分通过GPU(device)执行。
通常在使用pyCUDA的时候分为如下几个步骤:
首先在
GPU
上分配内存;
复制
host
上的内容进入
device
;
编译并运行在
GPU
上的函数(
kernel function
);
将结果复制进入
host
;
释放
GPU
上的内存。
上图我们简单描述了,pyCUDA是如何执行程序的。在具体执行的时候,由于GPU是对成批次数据进行计算的。需要将程序指定到不同的须(threads)上。CUDA通过两个层级定义须,分别是grid和block,通过这样一个结构可以指定不同的须去控制GPU执行程序。
实际在使用时,面对不同的程序任务,设置不同的grid和block大小,甚至是block的维度都会对程序产生影响。另外数据调度的时间也不能忽略,从CPU调度数据进入GPU、从GPU拷贝结果到CPU的时间都会对计算时间造成影响。
由于本报告旨在提供给投资者一个可实践可复现的加速手段。因此如何利用pyCUDA计算并不作为本报告的主要内容。如果投资者希望提供定制化加速服务,欢迎联系华创金工。
这里,我们主要介绍一些对不同模块进行加速的包。
RAPIDS是一个开源软件库,旨在加速数据科学和机器学习工作流程。它由NVIDIA推出,利用GPU的并行计算能力来加速数据处理和机器学习任务。RAPIDS提供了一系列用于数据预处理、特征工程、机器学习和深度学习的库和工具,旨在使数据科学家和机器学习工程师能够更快地分析和处理大规模数据集。RAPIDS库的核心组件包括:
cuDF
:基于GPU的数据框架,类似于Pandas,用于高效地处理和分析结构化数据。cuDF提供了Pandas API的GPU实现,可以在GPU上直接执行数据操作,从而实现更快的数据处理速度。
cuML
:基于GPU的机器学习库,提供了各种常见的机器学习算法的GPU实现,包括线性回归、逻辑回归、决策树、随机森林、聚类、降维等。cuML可以利用GPU的并行计算能力来加速模型训练和推理过程,从而缩短训练时间并提高模型性能。
cuGraph
:基于GPU的图分析库,用于在大规模图数据上执行图算法。cuGraph提供了各种图算法的GPU实现,包括最短路径、连通性、图聚类、图分区等,可以加速图数据的分析和处理过程。
cuSpatial
:基于GPU的空间数据分析库,用于处理和分析地理空间数据。cuSpatial提供了各种空间数据算法的GPU实现,包括距离计算、空间索引、空间连接等,可以加速地理空间数据的处理和分析过程。
由于笔者的服务群体主要使用
DataFrame
,我们着重介绍
cuDF
。
cuDF
是一个使用
GPU
对
DataFrame
进行加速的库,可以被用于数据聚合、连接等操作。用户使用
cuDF
对原有程序进行加速时,不需要改动任何原有程序,只需要指明利用
GPU
加速运行
Pandas
即可。
我们以cuDF为例,介绍如何对常规pandas进行替换和加速,系统为window11 家庭中文版,有条件的开发者建议直接选择linux系统。
用户首先需要确保:CUDA版本在11.2以上、NVIDIA 驱动在450.80.02以上、显卡的计算力在7.0以上、系统为Ubuntu 20.04 或 22.04、CentOS 7、 Rocky Linux 8、 WSL2 on Windows 11。
如果用户需要从零开始安装,首先需要在官网下载适合自己的CUDA
TOOLKIT(图14所示)。 在window系统下直接安装即可,在linux系统下,建议做好备份,以命令行模式安装,因为CUDA自带驱动,可能会与linux自带驱动冲突。
Window和linux下的查看方式是打开命令行,输入nvidia-smi 获得如下结果(图15所示)
上图中,DriverVerision为546.12 ,CUDA version 为12.3。接下来,用户可以打开Cuda toolkit的安装包,并运行deviceQuery获得计算的计算力(图16所示)
其中, CUDA Capability Major 显示算力为7.5。用户也可以在NVIDIA官网(CUDA GPUs - Compute Capability |
NVIDIA Developer
)直接查看对应显卡的计算力,如下图所示(图17所示)
常见的RAPIDS 支持的消费显卡需要在Geforce RTX 2060或T500以上。
由于
cuDF
只能在
Linux
系统下运行,因此我们需要在
Window
系统下安装
Linux
虚拟机。有两种方法,一、在
Window
系统下,通过
wsl
安装
Linux
系统,默认为安装
Ubuntu 22.04
。二、在
Window
应用商店搜索
Ubuntu
进行安装。
打开wsl后,有三种方式安装cuDF库:
通过Conda安装:这种方式是通过Conda建立了RAPIDS的环境。用户可以在这个环境下进行开发。缺点是用户使用时需要先指定所使用的库。
通过pip安装:直接通过pip安装的方式简单直接,用户使用时可以直接导入。这种方式的缺点是不同库之间可能会存在依赖,导致部分库无法使用。
源码安装:这种方式针对不希望引入过多的依赖包的用户。安装和使用都较为不便。
安装完成cuDF后,用户可以用三种方式调用cuDF,如下表所示
Numba 是一个适用于 Python 代码的开源式即时编译器。借助该编译器,开发者可以使用标准 Python 函数在 CPU 和 GPU 上加速数值函数。为了提高执行速度,Numba 会在执行前立即将 Python 字节代码转换为机器代码。Numba无需新语法,也无需替换 Python 解释器或安装 C/C++ 编译器。只需将 @jit Numba 修饰器应用于 Python 函数即可,Numba会自动利用GPU对函数进行优化,如果Numba发现报错会返回利用CPU进行运算。
Numba比python快的原因在于Numba使用LLVM编译器替换了纯Python 的cpython编译器。
Numba的运行流程如下所示(图21所示):简要的来说,通过修饰函数,程序首先将python函数变成二进制代码,再将二进制代码,进入LLVM虚拟机编码,最后转成机器语言。
值得一提的是,一方面,GPU计算需要将数据复制进入device,再将计算好的结果复制进host。如果数据量太少,直接使用CPU计算甚至会快于GPU计算;另一个方面,GPU计算对thread数量设置和核函数写法有很高的优化要求。如无特殊需要,建议大家直接使用numba的修饰器。
Numba的缺点主要包括,Numba支持python原生函数和numpy函数,对python的类和其他库支持有限。
CUDA 允许开发人员使用C编程语言来编写并行程序,这些程序可以在NVIDIA的GPU上执行。如果没有必要,笔者建议直接使用NVIDIA提供的一系列高性能的CUDA库,涵盖了各种领域的计算需求,包括线性代数、图像处理、信号处理等等。开发者自己编写的核函数、数据传输和程序架构很难在性能上超过NVIDIA提供的计算库。
常用的CUDA计算库包括有cuDNN(CUDA Deep Neural Network)、cuBLAS(CUDA Basic Linear Algebra Subprograms)等。
计算的结果需要从GPU复制进入CPU,由于数据在host和device的转移需要时间,因此数据量太少反而利用CUDA会慢。
为了更好的让读者理解CUDA的工作流程,我们将简述其工作原理:
CPU(host)首先将任务分发给GPU(device),GPU会对CPU分发的任务进行处理,GPU的同一个线程块(grid)的众多须(Thread)会处理同样一个核(kernel)函数(如图22)。
“须”在线程块中有线程编号,根据线程的编号可以进行复杂寻址,一个应用程序可以指定一个块作为一个二维或三维数组的任意大小,并且通过二维或三维索引代替来指定每条线程。比如对于一个大小为 (Dx,Dy)二维线程块,线程的索引是(x, y),这个线程编号是(x + y Dx),对于一个三维的大小为 (Dx,Dy,Dz)的块,这个线程的索引是(x,y,z), 线程的编号是(x + y Dx + z DxDy)。
GPU的存储可以同时被CPU和GPU访问,其内存大小有限(如图23)。由于现在的深度学习模型越来越大,因此GPU的内存显得格外重要,小内存GPU无法承载庞大深度学习参数。另外,在深度学习中,通常会使用批处理训练数据来加速训练过程。较大的批处理通常会导致更多的内存使用,因为需要同时存储更多的参数和计算结果。
数据表的操作中,聚合、分组计算和分组应用用户自定义函数都非常常见。我们以一款常见的台式机配置分别对不同的计算任务进行了
CPU
计算和
GPU
计算的比较。测试所用的硬件配置为
:
结果表明,用户应当根据自己的计算数据量大小选择合适的加速方式,数据量在100000以下时,建议使用CPU,数据量超过1000000时,建议使用GPU。例如,月度数据计算建议使用CPU计算,日度数据建议根据数据量选择CPU或GPU,股票tick数据计算建议使用GPU计算。
数据聚合是数据处理过程中常见的任务,它的作用是可以根据一个或多个键将不同的DatFrame连接起来。Merge是python pandas 一个常用的函数,类似于mysql中的join函数和excel中的vlookup函数。
当数据量在1000以下时,CPU比GPU统计效率高,当数据量超过10000后GPU统计效率高。随着数据量的增加,GPU的表现比CPU表现更好:
在 Pandas 中,groupby 函数根据一个或多个列的数值对 DataFrame 进行分组。一旦分组完成,你可以在每个组上执行聚合操作,比如计算平均值、求和、计数等。类似于sql中
,
GROUP
BY用于将行分组为汇总行,并且结合聚合函数(如 COUNT、SUM、AVG 等)可以对每个组进行汇总计算。Excel中类似的功能为数据透视表
。
结果如下图所示,当数据量在10000以下时,CPU比GPU统计效率高,当数据量超过100000后GPU统计效率高。随着数据量的增加,GPU的表现比CPU表现更好。
Pandas 中 apply 是一个在数据处理中经常使用的函数,它允许你在 DataFrame 或 Series 中的每一行或每一列上应用自定义的函数。在 Pandas 中,apply 可以用于对数据进行逐行或逐列的操作,通常与 lambda 函数或自定义函数一起使用。在 Excel 中,类似的功能是 VBA(Visual Basic for
Applications)来编写自定义函数,例如用户可以编写一个自定义函数,然后在Excel 中使用这个函数来对数据进行处理
:
结果如上图所示,不论是台式机或者笔记本,当数据量在100000以下时,CPU比GPU运算快,当数据量超过1000000后GPU运算快。随着数据量的增加,GPU的表现比CPU表现更好。
本报告分章节介绍了CPU和GPU的工作原理及其影响其计算效率的核心性能指标,用户应当选择适合自己任务的硬件。
CPU 的设计旨在处理通用计算任务,通常有更大的缓存和更高的时钟频率,适合于串行和低并行度任务。CPU核心较少,但每个核心的性能较高,CPU并行适合逻辑复杂性算计任务,如一般的计算任务、服务器应用、数据库管理等。