作者:Tim Dettmers
深度学习计算十分密集,所以我们需要一个快速多核CPU,还是说采购快速CPU可能是整个项目中的一种浪费?搭建一个深度学习系统时,最糟糕的事情之一就是把钱浪费在并非必需的硬件上。本文将一步步带我们了解一个高性能经济系统所需的硬件。
研究并行化深度学习过程中,我搭建了一个GPU集群。为此,需要仔细挑选硬件。尽管经过了仔细的研究和逻辑推理,但是挑选硬件时还是会犯相当多的错误。当我在实践中应用集群时,错误就会显现出来。下面就是我想分享的所得的东西,希望你们可以不要再掉入同样的陷阱。
1.GPU
在这篇博文中,我们假设你会利用GPU来进行深度学习。如果你正在构建或升级你的深度学习系统,忽视GPU是不理智的。GPU正是深度学习应用的核心要素——计算性能提升上收获巨大,而且不可忽视。
我在以前的博客中详细谈到过GPU的选择,GPU的选择也许是深度学习系统中最关键的选择。当前市场上的GPU:通常来说,如果预算十分有限,我推荐到eBay上购买GTX 680,或者GTX Titan X(如果预算还算充裕),或者GTX 980(非常有性价比,但对于大型卷积神经网络有点局限),另外如果需要一个低成本的存储则可以选择GTX Titan。我之前支持过GTX 580,但是由于cnDNN库的新升级大幅增加了卷积的速度,那么所有不支持cuDNN的GPU都得淘汰了——GTX 580就是这样的一个GPU。如果你完全不使用卷积神经网络,那么GTX 580仍然是一个可靠的选择。
2.CPU
为了能够明智地选择CPU我们首先需要理解CPU,以及它是如何与深度学习相关联的,CPU能为深度学习做什么呢?当你在GPU上跑深度网络时,CPU进行的计算很少,但是CPU仍然需要处理以下事情:
l 在代码中写入并读取变量
l 执行指令,如函数调用
l 启动在GPU上函数调用
l 创建小批量数据
l 启动到GPU的数据传输
3.CPU核的数量
当我用三个不同的库训练深度学习网络时,我经常观察到一个CPU线程显示100%(并且有时候另一个线程会在0-100%之间浮动),你会立刻知道大部分深度学习库——以及实际上大部分通用软件应用——仅仅只利用一个线程。这意味着多核CPU非常无用。但如果你运行多GPU,并使用像MPI一样的并行化框架,那么你就同时运行了多个程序,你也会需要多个线程。每个GPU跑一个线程还不错,但是每个GPU跑两个线程对于大部分深度学习库来说能够带来更好的表现;这些库在单核上运行,但是某些时候调用异步函数则会用到第二个CPU线程。记住许多CPU能够在每一个核上运行多个线程(尤其对于Intel的CPU 来说,其实也就是CPU核的时间分片),因此每个GPU对应一个CPU核通常就够了。
4.CPU与PCI-Express
这里是个陷阱!一些新型号Haswell CPU并不支持全部40个PCIe通道,而旧的CPU则可以——避免这些CPU,如果你希望建立一个多GPU的系统。并且确保你的处理器确定能够支持PCIe3.0,如果你的主板采用PCIe 3.0。
5.CPU缓存大小
正如我们之后会看到的,CPU缓存大小与CPU-GPU下游管道运算没有关联,但是我会用一个简短的部分来说明,这样我们就可以确定计算管道上每个可能的瓶颈都被考虑在内,从而全面理解整个过程。
人们购买CPU时经常会忽视缓存这个问题,但是通常来说,它在整个性能问题中是非常重要的一部分。CPU缓存是容量非常小的直接位于CPU芯片上的存储,物理位置非常接近CPU,能够用来进行高速计算和操作。CPU通常有缓存分级,从小型高速缓存(L1,L2)到低速大型缓存(L3,L4)。作为一个程序员,你可以将它想成一个哈希表, 每条数据都是一个键值对(key-value-pair),可以高速的基于特定键进行查找:如果找到,就可以在缓存的值中进行快速读取和写入操作;如果没有找到(被称为缓存未命中),CPU需要等待RAM,之后再从内存进行读值——一个非常缓慢的过程。重复的缓存未命中会导致性能的大幅下降。有效的 CPU缓存方案与架构对于CPU性能来说非常关键。
CPU是如何决定它的缓存方案是一个复杂的话题,但是通常来说,重复使用的变量、指令和内存RAM地址会保存在缓存中,而不经常出现的则不会。
在深度学习中,对于每一个小批量数据单元相同的内存范围会重复进行读取直至送至GPU(同时这块内存范围会被新数据覆盖),但是内存数据是否能够被存储在缓存中则取决于小批量单元大小。对于128的小批量单元的大小,我们会有相对应的0.4MB的MNIST数据与1.5MB的CIFAR数据,能够放入大部分 CPU缓存。对于ImageNet来说,我们每个小批量单元有超过85MB的数据,这即使对于最大的缓存(L3缓存仅仅有几个MB)来说也实在太大了。
由于通常数据集对于缓存太过巨大,每一批新的小批量处理单元数据都需要从内存RAM中进行读取——因此对内存RAM的访问是持续在进行。
内存RAM记忆地址存储于缓存中(CPU能够在缓存中迅速查找,指出数据在RAM中的确切地址),但是这只在整个数据集都存储于RAM的时候才能如此,否则记忆地址会改变,缓存也不会加速(你可能会想利用固定保留地址避免这种情况,但是就像你之后会看到的,其实这也没有意义)。
深度学习代码的其他部分——例如变量与函数调用——则会从缓存中受益,但是这些通常都数量很少,并且很容易就存储于几乎所有CPU的小型快速L1缓存中。从这些推论中可以总结一下,CPU缓存大小并不是很重要,下一部分的深层分析则会继续阐述这个结论。
6.寻找合适的CPU时钟频率(频率)
当人们想到快速的CPU时,他们一般最先想到其时钟频率(clock rate)。很多人认为4GHz比3.5GHz快,是这样吗?在比较具有相同架构的处理器时,这一般是对的,例如「第三代酷睿处理器」(Ivy Bridge)。但在比较不同架构的处理器时,这个想法却不那么正确。而且这也不总是对于性能的最好测量指标。
在深度学习中,只有很少一部分的计算会用CPU来运行:增值几个变量、评估几个布尔表达式、在GPU或在编程里面调用几个函数——所有这些会取决于CPU核的频率。这种推理似乎是合理的,但当我运行深度学习编程的时候,CPU会有100%的使用率,那这是怎么回事儿?为了找出原因,我做了一些CPU核频率降频的实验。
CPU降频后在MNIST及ImageNet的表现:使用不同的CPU核频率,将MNIST数据集运行200遍或遍历1/4的ImageNet数据集运行作为测量时间,我们测量CPU的性能,其中我们将每个CPU的最高频率定位参照线。对于比较:在性能上,GTX Titan比GTX 680提升了15%;GTX 980比GTX Titan提升了20%;GPU超频比任何GPU提升了5%。
问题是:为什么当 CPU核频率是无关紧要的的时候,它会用上100%的使用率?答案可能是CPU的高速缓存未命中:一般情况下,CPU会不断忙于访问RAM,与此同时,它需要一直等待频率较慢的RAM跟上步伐,因此这可能导致了即忙碌着等待中的矛盾状态。如果真是这样,将CPU核降频将不会导致其性能的大幅降低——正如你在上面看到的结果。
CPU也执行其他一些操作,如将数据抄到微型批次里,并将数据准备好以便复制到GPU,但这些操作其实只取决于内存频率,而不是CPU核频率。所以我们现在来看看内存。
7.寻找合适的RAM频率
CPU-RAM以及其他设备与RAM的互动都是非常复杂的。我在这儿会给出该互动过程的一个简化版。为了得到一个更为全面的理解,让我们潜入其中并剖析从CPU-RAM 到 GPU-RAM的这一过程。
CPU的内存频率与 RAM是相互交织的。CPU的内存频率决定了RAM的最大频率,并且这两个东西结合起来构成了CPU整体内存的带宽。但通常RAM本身决定了整体可使用的带宽,因为它可以比CPU的内存频率更慢。你可以如此决定带宽,其中64是64位的CPU结构。而我的处理器及RAM模块的带宽是51.2GB/s。
那么这与深度学习程序有什么关系?我刚刚说带宽有可能是重要的,但在下一个步骤中,它还不是那么重要。RAM的带宽决定了小批次数据对应内存被覆盖并被分配来启动一次到GPU数据传输上有多快,但在下一个步骤,从 CPU-RAM到GPU-RAM,才真正是瓶颈了,此步骤利用了直接存储器存取(DMA)。正如上面所引述的,我的RAM模块的内存带宽是 51.2GB/s,但DMA带宽却只有12GB/s!
DMA带宽与一般的带宽相关,但细节是不太需要知道的,你可以看看这个维基百科条目,你能在此查阅RAM模块的DMA带宽(峰值传输限制)。但先让我们看看DMA是如何工作的。(http://en.wikipedia.org/wiki/DDR3_SDRAM#JEDEC_standard_modules)
8.直接存储存取(DMA)
CPU 和其对应的RAM只能透过DMA与GPU通信。第一步,一个特定的DMA传输缓冲器在CPU RAM 及 GPU RAM里被保留;第二步,CPU将所请求的数据写入CPU那边的DMA缓冲器里;第三步,受保留的缓冲器在没有CPU的帮助下被转移到GPU RAM里。你的PCIe的带宽是8GB/s (PCIe 2.0) 或是 15.75GB/s (PCIe 3.0),所以你需要买一个像上面所说的,具有良好峰值传输限制的RAM吗?未必。软件在这里起着很大的作用。如果用聪明的方法来做一些传输的话,你将可以能利用更便宜更慢的内存来完成任务。
9.异步的微批次分配
一旦你的GPU在当前的微批次中完成计算,它希望马上计算下一微批次。现在你当然可以初始化一个DMA传输,然后等待传输完成以使你的GPU可以继续运算。但有一个更有效的方法:提前准备好下一批微批次以便让你的GPU完全不再需要等待。这可以轻易并异步地完成来确保发挥出GPU的性能。
异步微批次分配的CUDA代码:当GPU开始处理当前批次时,头两个调用被执行;当GPU处理完当前批次以后,最后两个调用被执行。数据传输早在数据流在第二步被同步的时候完成了,所以GPU可以无延迟的开始处理下一个批次。
在ImageNet 2012竞赛中,对于Alex Krishevsky论文中的的卷积网络,一个大小为128的迷你批次只用了0.35秒就进行了一次完整的反向传播。我们能在这么短的时间里分配下一个批次吗?
如果我们采用大小为 128的批次及维度为244x244x3的数据,大概总量为0.085 GB。采用超慢内存,我们也有6.4GB/s的速度,或换句话说,每秒75微批次!所以有了异步微批次分配,即使是最慢的RAM对于应付深度学习也是绰绰有余了。如果采用了异步微批次分配,买较快的RAM模块将没有任何优势。
这个过程还间接意味着CPU缓存是不相关的。对于DMA传输来说,CPU可以多快改写(在高速缓存中)及准备(将缓存写入RAM)一个微批次是无关紧要的,因为整个传输早在GPU要求下一个微批次前就完成了——所以大缓存真的不是那么重要。因此底线是RAM的频率是不相关的。买那些便宜的就行了——故事完结。但你需要购买多少呢?
10.内存大小
你应该拥有至少和你的GPU内存大小相同的内存。你能用更小的内存工作。但是,你或许需要一步步转移数据。不过,就我个人经验而言,内存越大,工作起来越舒服。
心理学家告诉我们,专注力这种资源会随着时间的推移而逐渐耗尽。内存就是为数不多的,让你保存注意力资源,以解决更困难编程问题的硬件之一。与其在内存瓶颈上兜转,浪费时间,不如把注意力放在更加紧迫的问题上,如果你有更多的内存。有了这一前提条件,你可以避免那些瓶颈,节约时间,在更紧迫问题上投入更多的生产力。特别是在Kaggle竞赛中,我发现,额外的内存对特征工程非常有用。所以,如果你有钱而且需要做很多预处理工作,那么额外内存将会是不错的选择。
11.硬盘驱动器/SSD
在一些深度学习案例中,硬驱会成为明显的瓶颈。如果你的数据组很大,通常会在硬驱上放一些数据,内存中也放一些,GPU内存中也放两mini-batch。为了持续供给GPU,我们需要以GPU能够跑完这些数据的速度提供新的mini-batch。
为此,我们需要采用和异步mini-batch分配一样的思路。用多重mini-batch异步读取文件 ——这真的很重要!如果不异步处理,结果表现会被削弱很多(5-10%),而且让你认真打造的硬件优势荡然无存 ——在GTX680上,好的深度学习软件跑起来会快得多,胜过GTX980上的糟糕的深度学习软件。
出于这种考虑,如果我们将数据保存为32比特浮点数据,我们会遇到Alex参加ImageNet 所使用的卷积网络遇到的数据传输速率问题,大约每0.3秒0.085GB,计算如下:
或每秒290MB。
不过,如果我们将它保存为jpeg数据,我们就能将它压缩5-15倍,将所需读取带宽降低到大约30MB每秒。
类似,一个人能够使用mp3或者其他压缩技巧来处理声音文件,但是,对于其他处理原始32比特浮点数据的数据组来说,不可能很好地压缩数据:我们只能压缩 32比特的浮点数据的10-15%。因此,如果你有大的32比特数据组,那么,你肯定需要SSD,因为100-150MB/S的硬驱会很慢,不足以跟上你的GPU——因此,如果你的工作需要处理这样的数据,买个SSD吧,否则的话,普通硬驱就够了。
许多人买一个SSD是为了舒服:程序开始和响应都快多了,大文件预处理也快很多,但是,对于深度学习来说,仅当你的输入维数很高,不能充分压缩数据时,这才是必须的。
如果你买了SSD,你应该选择能够存下和你通常要处理的数据集大小相当的存储容量,也额外留出数十GB的空间。另外用普通硬驱保存你尚未使用的数据集的主意也不错。
12.电源供应设备(PSU)
一般说来,你需要一个给你未来所有GPU充足供应的PSU。随着时间的推移,GPU通常会更加高效节能;因此,尽管其他组件会需要更换,PSU会用很久,一个好的PSU是一个好的投资。
CPU加上GPU所需瓦特,再加上其他组件额外所需以及作为电力峰值缓冲的100-300瓦特,你就能计算出所需瓦特。一个需要注意的重要部分就是,你的PSU的PCIe接头是否支持带有一条连接线的8pin+6pin接头。我买过一个PSU,有6x PCIe端口,但是,仅适用于8pin或者6pin的接头,所以,我没法用那个PSU跑4个GPU。另一个重要的事情就是买一个能效等级高的PSU,——特别是,如果你跑很多GPU而且还要跑比较久。
在全功率(1000-1500瓦特)上跑一个4 GPU系统,训练一个卷积网络两个礼拜,耗能300-500kWh,在德国——电力成本更高,每kWh多出20美分——会花60到100欧元(66到 111美元)。如果这是百分百高效节能的价格,那么,用80%的电源供应来训练这样一个网络,成本将额外增加18到26欧元。对于1个GPU来说,花费就少很多,但是,仍要抓住要点 ——将更多的钱花在高效电源供应上,很有意义。
13.散热
散热十分重要,这更有可能成为一个大瓶颈,比糟糕的硬件选择更能降低系统表现。你可能会满足于给CPU配一个标准的散热片,但是,要给你的GPU配些什么东西,需要特别的考量。
当现代的GPU运行一个算法时,它会一直加速至最大极限,同时加速的还有能量消耗,但当到达它的温度壁垒,通常是80摄氏度,它就会降低运行速度来避免打破临界温度。在保护GPU避免过热、情况安全的同时,也让GPU达到最佳状态。
但是,典型的电扇速度设置是被预编程的,完全不是为运行深度学习项目而设计的,所以,在开始深度学习程序后的几秒内就会达到其温度壁垒。结果,GPU的运行效果下降(几个百分比),如果有好几个GPU,由于GPU之间会相互加热,那么,整体效果的下降幅度就会非常明显(10-25%)。
由于NVIDIA GPU是首屈一指的电竞GPU,它们非常适应Windows系统的运作。Windows系统中,你可以通过几个简单的按键改变风扇的设置,但在Linux系统中不行,不过,多数深度学习库都是在Linux环境下运行的,所以,这会导致问题出现。
最容易且性价比最高的变通方案就是,用一个有更合理风扇设定的BIOS程序更新GPU以提升速度,这样就能保持GPU凉爽的同时,噪声也不至于不可接受(如果你用服务器解决这个问题,那么,为了避免难以忍受的噪声,你很可能会在风扇运行速度上打折)。也可能超速运行GPU的存储器,这会有一些噪声(30-50兆赫),这也是相对可行的方法。更新BIOS的软件是一个在Windows系统下设计的程序,但可以用wine操作系统从Linux/Unix OS中调用这个程序。
另一个选择是为你的 Xorg 服务器(Ubuntu操作系统)设置配置(set a configuration),在这个Xorg 服务器中,设置「coolbits」选项。在只有一台GPU的时候这个方法非常有效,但如果对多台GPU,也就是说有「无头(headless)」 GPU,比如这些「无头」GPU没有连显示器,那么,就必须模拟一台显示器,但模拟显示器难度非常之高。我曾试了很久,无比沮丧的花了数个小时,用实时 (live)启动光盘恢复我的图形设置,始终没法让这个程序在「无头」GPU上正常运转。
还有一个方法花费较高也较为复杂,就是用水冷却。应用于单GPU时,即使是最大功率的情况,也几乎可以将温度减半,所以永远不会达到温度壁垒。就算是面对空气降温无法应对的多GPU情况,也可以很好的降温。用水降温的另一个好处是,它运行时产生的噪音较少,如果你处在一个与他们共同工作的环境下,这将带来极大的方便。水冷却法花费大约为每GPU100美元左右,前期大概要额外投入50美元。水冷却法还需要另花一些精力组装电脑,对于这方面可以找到很多详尽实用的指南手册,且花不了几个小时。维护期间就会简单方便许多了。
从我的经验来看,这些是比较对症下药的方法。我为我的深度学习组团买过大型的塔型机箱,因为他们在GPU的区域有另加的风扇,但我发现,这并没有太大的作用,大约只降低了 2-5摄氏度,不值得为此花这么多钱还得忍受笨重的机箱。最重要的还是直接作用于GPU的降温措施:更新BIOS,水冷法或忍受打了一定折扣的运行效果,在某些场合下,这些都是非常有效的应对方式,你可以根据自己需要找到合适的应对方式。
14.主板和电脑机箱
你的主板要有足够的PCIe接口,来支持你希望同时运行的GPU(通常局限于4台GPU,即使主板上有很多PCIe槽)。记住,多数GPU都需要占2个PCIe槽的宽度,例如,你会需要7个槽口来链接4台GPU。PCIe2.0接口也是可以接受的,但PCIe3.0接口更加经济,即使只对单GPU性价比也很高,如果对多GPU,通常都会使用PCIe3.0接口,这会对多GPU运作带来很大的好处,因为PCIe的连接口多数情况下是个瓶颈所在。
主板的选择策略十分直接:就选那款可以支持你想要的硬件配置的就行。
当你选择机箱时,要确保它能满足你GPU的长度要求,这样它才能在主板上放稳。多数机箱都能满足要求,但如果你选的是一个小机箱,就需要多加留心。要核查它的面积和规格,你也可以尝试用google的图片搜索引擎,看看能不能找到GPU放在上面的图片。
15.显示器
我刚开始也觉得写显示器是件很滑稽的事,但它们确实会造成很大的影响,非常重要,不能忽视。我在我的3’27英寸显示器上付出了可能是我从未有过的大代价,买了几个显示器后,效率提高了很多。如果我只能用一台显示器,那我的效率肯定会大打折扣。别在这上面和自己过不去,如果你不能有效的运转一个快速的深度学习系统,那它又有什么好处?
我进行深度学习系统工作时,显示器的典型布置一般是:Papers、Google搜索、gmail、stackoverflow网在左,中间是编程界面,右边是输出窗口、R、文件夹、系统显示器、GPU显示器、待办事项以及一些其他小的应用。