Cluster是硬件资源到逻辑资源的映射,支持不同分组策略将硬件资源划分为多组Virtual Devices。本文介绍Cluster支持的分组策略、调用格式、参数说明、调用示例及Worker角色概念,以指导您进行资源划分和映射。

背景信息

分布式框架的工作是将用户模型的各部分通过不同并行化方法放至分布式计算硬件上,并进行计算。该过程在Whale中总结为模型划分、资源划分及映射。Whale提供的分布式计算资源划分工具Cluster,适用于资源划分和映射部分。

该部分涉及的重要概念包括:
  • cluster

    cluster是对整个硬件资源到逻辑资源的划分映射管理类,支持不同分组策略将硬件资源划分为多组Virtual Devices。

  • layout

    layout决定了如何从硬件资源到逻辑资源划分映射,其中layoutcluster的一个参数。

格式

cluster(worker_hosts=None,
        ps_hosts=None,
        job_name="worker",
        rank=0,
        layout="all")

参数

  • worker_hosts:Worker Hosts信息,STRING类型。默认值为None,表示从TF_CONFIG环境变量中获取Hosts、Rank等信息。
  • ps_hosts:Parameter Server Hosts信息,STRING类型。
  • job_name:当前Worker Job Name,STRING类型,默认值为"worker"
  • rank:当前Worker Rank,INTEGER类型。
  • layout:Cluster的分组策略,DICT类型,默认值为all。Whale的Cluster提供的资源分组策略有以下几种:
    • all

      将所有GPU放置在一个分组中。

      以下图的资源为例,介绍如何实现该分组策略及实现效果。划分策略all您可以使用如下代码实现该分组策略。
      import whale as wh
      cluster = wh.cluster(layout="all")
      返回的cluster.slices内容如下。
      [
          [ 
              [Worker0:GPU0], 
              [Worker0:GPU1], 
              [Worker0:GPU2], 
              [Worker0:GPU3], 
              [Worker1:GPU0], 
              [Worker1:GPU1], 
              [Worker1:GPU2], 
              [Worker1:GPU3] 
          ] 
      ]
    • average

      Cluster内的所有资源平均划分,每份N张卡。

      将Worker内的GPU按行排布,如下图所示。按行排布您可以使用如下代码实现该分组策略。
      import whale as wh
      cluster = wh.cluster(layout = {'average' : 4 } )
      返回的cluster.slices内容如下。
      [
          [ 
              [Worker0:GPU0], 
              [Worker0:GPU1],
              [Worker0:GPU2], 
              [Worker0:GPU3]
          ],
          [
              [Worker1:GPU0],
              [Worker1:GPU1],
              [Worker1:GPU2],
              [Worker1:GPU3]
          ],
      ]
      Cluster的划分结果如下图所示。averge划分
    • block

      将Cluster按照GPU Block分组。

      下图中的资源划分为两个组,分别包含6张卡和2张卡。每个分组又细分为两个子分组,通过Cluster的slices属性可以获取各分组信息。分组策略block您可以使用如下代码实现该分组策略。
      import whale as wh
      cluster = wh.cluster(layout={"block" : "3,1"})
      返回的cluster.slices内容如下。
      [
          [ 
              [
                  Worker0:GPU0, 
                  Worker0:GPU1, 
                  Worker0:GPU2
              ],
              [
                  Worker1:GPU0, 
                  Worker1:GPU1, 
                  Worker1:GPU2
              ]
          ],
          [
              [Worker0:GPU3], 
              [Worker1:GPU3] 
          ]
      ]
    • row

      将Worker内的GPU按行排布。

      如下图所示,row的划分则是水平机器内划分。划分策略row您可以使用如下代码实现该分组策略。
      import whale as wh
      cluster = wh.cluster(layout = {"row" : 4 })
      返回的cluster.slices内容如下。
      [
          [ 
              [Worker0:GPU0], 
              [Worker0:GPU1], 
              [Worker0:GPU2], 
              [Worker0:GPU3]
          ],
          [
              [Worker1:GPU0], 
              [Worker1:GPU1],
              [Worker1:GPU2], 
              [Worker1:GPU3] 
          ]
      ]
    • column

      将Worker内的GPU按行排布,Column的划分按照垂直跨机划分。

      Column的划分如下图所示。划分策略column您可以使用如下代码实现该分组策略。
      import whale as wh
      cluster = wh.cluster(layout = {"column" : 2 })
      返回的cluster.slices内容如下。
      [
          [ 
              [Worker0:GPU0], 
              [Worker1:GPU0]
          ],
          [
              [Worker0:GPU1], 
              [Worker1:GPU1]
          ],
          [
              [Worker0:GPU2], 
              [Worker1:GPU2]
          ],
          [
              [Worker0:GPU3], 
              [Worker1:GPU3] 
          ]
      ]
      当存在更多Worker时,Cluster按column划分,如果使用layout={"column" : 2 } 策略,则Cluster划分结果如下图所示。多worker返回的cluster.slices内容如下。
      [
          [ 
              [Worker0:GPU0], 
              [Worker1:GPU0]
          ],
          [
              [Worker2:GPU0],
              [Worker3:GPU0]
          ],
          [
              [Worker0:GPU1], 
              [Worker1:GPU1]
          ],
          [
              [Worker2:GPU1],
              [Worker3:GPU1]
          ],
          [
              [Worker0:GPU2], 
              [Worker1:GPU2]
          ],
          [
              [Worker2:GPU2],
              [Worker3:GPU2]
          ],
          [
              [Worker0:GPU3], 
              [Worker1:GPU3]
          ],
          [
              [Worker2:GPU3], 
              [Worker3:GPU3],
          ]
      ]
    • specific

      通过配置具体的Device信息进行cluster划分。

      将Worker内的GPU按行排布,如下图所示。Specific按行排布您可以使用如下代码实现该分组策略。
      import whale as wh
      cluster = wh.cluster(
          layout = {'specific' : [ [['worker:0/gpu:0'], ['worker:0/gpu:1']], [['worker:1/gpu:0'], ['worker:1/gpu:1']] ] } )
      返回的cluster.slices内容如下。
      [
          [ 
              [Worker0:GPU0], 
              [Worker0:GPU1]
          ],
          [
              [Worker1:GPU0],
              [Worker1:GPU1]
          ],
      ]
      Cluster划分结果如下图所示。Specific划分结果

返回值

返回指定layout分组策略后的whale.Cluster对象,具有以下属性:
  • rank
    调用方式如下。
    cluster.rank
    返回当前的Worker Rank。
  • worker_num
    调用方式如下。
    cluster.worker_num
    返回Worker数量。
  • gpu_num_per_worker
    调用方式如下。
    cluster.gpu_num_per_worker
    返回每个Worker的GPU数量。
  • slices
    调用方式如下。
    cluster.slices
    返回Cluster通过layout划分后的资源分组,返回类型为嵌套的LIST。
  • total_gpu_num
    调用方式如下。
    cluster.total_gpu_num
    返回Cluster中所有GPU数量。

示例

将模型映射到Cluster划分好的Virtual Devices slices组,有两种实现方法,示例分别如下:
  • 使用with自动映射
    在模型定义最前面,通过with语法,Whale框架可以自动将模型映射到Virtual Devices,示例代码如下。
    import whale as wh
    with wh.cluster(layout = {"row" : 4 }):
        # 假设将Cluster划分为如下2个Slices。
        # [
        #    [ [Worker0:GPU0], [Worker0:GPU1], [Worker0:GPU2], [Worker0:GPU3] ],
        #    [ [Worker1:GPU0], [Worker1:GPU1],[Worker1:GPU2], [Worker1:GPU3] ]
        # ]
        with wh.stage():
            model_part_1()
        with wh.stage():
            model_part_2()
  • 使用Slices手动映射
    Whale的Cluster通过layout划分之后,可以通过Slices方法获取划分后的分组。Whale Scopes支持直接设置Devices信息,因此可以使用Slices手动映射。示例代码如下。
    import whale as wh
    cluster = wh.cluster(layout = {"row" : 4 })
    # 假设将cluster划分成如下2个slices。
    # [
    #    [ [Worker0:GPU0], [Worker0:GPU1], [Worker0:GPU2], [Worker0:GPU3] ],
    #    [ [Worker1:GPU0], [Worker1:GPU1],[Worker1:GPU2], [Worker1:GPU3] ]
    # ]
    with wh.stage(cluster.slices[0]):
        model_part_1()
    with wh.stage(cluster.slices[1]):
        model_part_2()
    手动设置的方法可以更灵活地为每个Scope设置Device信息,例如多个Scopes复用相同Slices。
说明 不用混用with自动用法和手动用法。

Whale中Worker角色概念

根据whale.cluster定义,申请到的所有Worker会通过layout策略进行分组,生成Virtual Devices Slices,该Slices是一个嵌套LIST。Whale框架将slices[0]中所有Device的对应Worker称之为Master Worker,负责原始图构造,并完成并行化图的改写。其余Worker不进行构图,直接调用server.join等待Master Worker分配任务,这些Worker称之为Slave Workers。

例如申请了3台服务器,每台服务器2张GPU卡。那么通过layout=wh.cluster(layout={"block" : "3,3" })生成对应的cluster.slices如下。
[
    [
        ["/job:worker/replica:0/worker:0/device:GPU:0"],
        ["/job:worker/replica:0/worker:0/device:GPU:1"],
        ["/job:worker/replica:0/worker:1/device:GPU:0"]
    ],
    [
        ["/job:worker/replica:0/worker:1/device:GPU:1"],
        ["/job:worker/replica:0/worker:2/device:GPU:0"],
        ["/job:worker/replica:0/worker:2/device:GPU:1"]
    ]
]
由上可知cluster.slices[0]取值如下。
[
  ["/job:worker/replica:0/worker:0/device:GPU:0"],
  ["/job:worker/replica:0/worker:0/device:GPU:1"],
  ["/job:worker/replica:0/worker:1/device:GPU:0"]
]
所以task_index为0和1的Worker即为Master Workers,task_index为2的Worker即为Slave Workers。