参数服务器
DL4J支持使用Spark和参数服务器进行快速分布式训练。
Last updated
DL4J支持使用Spark和参数服务器进行快速分布式训练。
Last updated
DL4J支持Apache Spark环境和Aeron中的分布式训练,用于Spark之外的高性能节点间通信。这个思想相对简单:单个工作节点在他们的数据集上计算梯度。
在将梯度应用于网络权重之前,它们被累积在中间存储机制(每台机器一个)中。聚合之后,超过某些可配置阈值的更新值作为稀疏二进制数组在网络上传播。低于阈值的值被存储并添加到将来的更新中,因此它们不会丢失,而只是在通信中延迟。
与发送整个密集更新或参数向量的天真方法相比,这种阈值化方法将网络通信需求减少了许多数量级,同时保持了高精度。
关于阈值化方法的更多细节,参见Strom, 2015 - 使用商业GPU云计算进行弹性分布式DNN训练。
下面是Nikko Strom提出的原始算法中添加的一些额外优点:
可变阈值:如果每次迭代的更新次数太少,则阈值将自动被一个可配置的步骤值减少。
密集位图编码:如果更新次数太高,则使用另一种编码方案,它为任何给定的更新消息提供在线发送的“最大字节数”保证。
周期性地,我们发送“抖动”消息,用明显更小的阈值编码,以共享不能超过当前阈值的延迟权重。
注意,使用Spark需要开销。为了确定Spark是否会帮助你,考虑使用Performance Listener并查看毫秒迭代时间。如果是<150毫秒,Spark可能不值得。
为了运行训练你只需要一个Spark 1.x/2.x集群和至少一个开放的UDP端口(入站/出站)。
如上所述,DL4J同时支持Spark 1.x和Spark 2.x集群。但是,这个特定的实现也需要Java 8 +来运行。如果你的群集运行Java 7,则你必须升级或使用我们的参数平均训练模式。
梯度共享严重依赖于UDP协议用于训练期间主节点和从节点之间的通信。如果你在诸如AWS或Azure之类的云环境中运行集群,则需要允许一个UDP端口用于入站/出站连接,并且必须在传递给SharedTrainingMaster构造函数的VoidConfiguration.unicastPort(int)bean中指定该端口。
另一个需要记住的选项:如果使用YARN(或任何其他处理Spark网络的资源管理器),则必须指定用于UDP通信的网络的网络掩码。这可以通过以下方式完成:VoidConfiguration.setNetworkMask(“10.1.1.0/24”)。
选择IP地址的最后一种选择是DL4J_VOID_IP 环境变量。在你正在运行的每个节点上设置该变量,并使用本地IP地址进行通信。
网络掩码是CIDR符号,只是告诉软件应该使用哪些网络接口进行通信的一种方式。例如,如果你的集群有3个具有以下IP地址的盒子:192.168.1.23、192.168.1.78、192.168.2.133,则它们的网络地址的公共部分是192.168.*,因此子网掩码是192.168.0/16。你还可以在维基百科:https://en.wikipedia.org/wiki/Subnetwork中找到子掩网码的详细解释。
当Spark集群运行在hadoop之上,或者任何其他不假定Spark IP地址已公布的环境时,我们将使用网络掩码。在这种情况下,应在VoidConfiguration bean中提供有效的网络掩码,并将使用它来选择Spark之外的通信接口。
下面是唯一需要的依赖项的模板:
例如:
以下是来自Github上的示例仓库的一个示例项目的片段
请注意:此配置假定在集群内的所有节点上都打开了UDP端口40123。
网络IO有自己的代价,该算法也做了一些IO。训练时间的额外开销可以计算为
更新编码时间+消息序列化时间+来自其他工作节点的更新应用程序。
原始迭代时间越长,共享带来的相对影响就越小,并且你将获得更好的假设可伸缩性。 这是一个简单的表单,可以在扩展性预期上帮助你:
迭代时间 | 编码时间 | 解码时间 | 更新时间 | 服务开销 |
550 | 50 | 5 | 50 | 20 |
节点数量 | 每个节点的工作节点 |
8 | 4 |
可伸缩率: 70.51%
通过设计,Spark允许你为任务配置执行器的数量和每个执行器的内核数量。假设你有一个18个节点的集群 ,在每个节点中有32个核心。
在这种情况下,你的--num-executors值是18,而建议的--executor-core值在2到32之间。这个选项将基本定义RDD将分成多少个分区。
另外,你可以手动设置每个节点上使用的DL4J工作节点的具体数量。这可以通过SharedTrainingMaster.Builder() workersPerNode(int)方法来完成。
如果你的节点使用GPU,那么通常最好将workersPerNode(int)设置为每盒GPU的数量,或者保持其默认值以进行自动调优。
较高的阈值将给你提供更多的稀疏更新,这将提高网络IO性能,但它可能会(也可能会)影响神经网络的学习性能。
较低的阈值将给予你更密集的更新,因此每个单独的更新消息将变得更大。这将降低网络IO性能。单独的“最佳阈值”是不可能预测的,因为它可能因不同的结构而变化,但是1e-3的默认值是一个好的开始值。
经验法则很简单:网络越快,性能就越好。1GBe网络应该被认为是绝对最小值,但是10GBe由于延迟较小而性能更好。
当然,性能取决于神经网络大小和计算量。较大的神经网络需要更大的带宽,但是每次迭代也需要更多的时间(因此可能为异步通信留下更多的时间)。
为了确保最大的兼容性(例如,对于不支持多播的云计算环境,如AWS和Azure),DL4J当前只使用UDP单播。
UDP广播传输应该更快,但是对于训练性能,差异不应该显著(除了可能非常小的工作负载)。
根据设计,每个工作节点每次迭代发送1条更新消息,并且无论UDP传输类型如何,都不会改变。由于UDP单播传输中的消息重传由主节点处理(通常利用率较低),并且由于消息传递是异步的。为了获得性能,我们只需要更新通信时间小于网络迭代时间-通常是这种情况。
在设备之间PCIe/NVLink P2P连通性盒子,可以预期最佳结果。然而,即使没有P2P,一切仍然可以正常工作。只是“稍慢”。:)