File size: 5,800 Bytes
7134ce7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# ray的支持

SWIFT已经支持使用ray来进行多卡或多节点训练。已有功能中对ray的支持情况如下:

| 功能       | 支持ray | 例子                                                                             | 可分配角色           |
|----------|-------|--------------------------------------------------------------------------------|-----------------|
| pt/sft   | ✅     | https://github.com/modelscope/ms-swift/tree/main/examples/train/multi-node/ray | default         |
| dpo      | ❎     |                                                                                |                 |
| grpo     | ❎     |                                                                                |                 |
| ppo      | ❎     |                                                                                |                 |
| megatron | ❎     |                                                                                |                 |
| sampling | ✅     | https://github.com/modelscope/ms-swift/tree/main/examples/sampler/distill      | sampler/prm/orm |
| distill  | ✅     | https://github.com/modelscope/ms-swift/tree/main/examples/sampler/sample       | sampler/prm/orm |

## 技术细节

在叙述参数设置之前,我们有必要先行讲一下技术细节。由于SWIFT的内部当前使用了大量transformers和trl的已有实现,像veRL或ROLL一样拆解为不同的ray角色是不现实的,而且拆解后会以ray为中心,对非ray的场景的支持会不良。
因此SWIFT采取了装饰器为主的技术方案,以函数级别定义了不同角色,这些角色可以在参数中被定义如何使用。看下面的例子:

```python

from swift.ray import RayHelper



@RayHelper.worker(group=['model1', 'model2'])

class MyTrainer:



    def __init__(self, args):

        self._prepare_model1()

        self._prepare_model2()

        self._prepare_datasets()



    @RayHelper.function(group='model1')

    def _prepare_model1(self):

        ...



    @RayHelper.function(group='model2')

    def _prepare_model2(self):

        ...



    @RayHelper.function(group='model1')

    def rollout(self, inputs):

        return self.model1.generate(inputs)



    @RayHelper.function(group='model2')

    def forward_model2(self, inputs):

        loss = self.model2.forward(inputs)

        loss.backward()



    def _prepare_datasets(self):

        self.dataset = ...



    def train(self):

        for batch in DataLoader(self.dataset):

            generated = self.rollout(batch)

            self.forward_model2(generated)

            ...





if __name__ == '__main__':

    ...

    MyTrainer(args).train()

```

RayHelper会将被装饰的方法分配到不同的硬件集群中,本地调用会被平滑转换到ray集群中进行远程调用。也可以以类为中心进行划分:

```python



@RayHelper.worker(group=['model1'])

class Model1:

    ...



    @RayHelper.function(group='model1')

    def rollout(self):

        ...



@RayHelper.worker(group=['model2'])

class Model2:

    ...



    @RayHelper.function(group='model2')

    def forward_and_optimize(self):

        ...





class Trainer:

    ...

```

SWIFT对ray的支持本质上是使用@worker和@function两个注解的组合使用,worker指定ray集群的角色,function指定如何分配数据。

function注解有额外的几个参数:
```python

    @staticmethod

    def function(group: str,

                 dispatch: Union[Literal['slice', 'all'], Callable] = 'all',

                 execute: Literal['first', 'all'] = 'all',

                 collect: Union[Literal['none', 'flatten'], Callable] = 'none'):

```

- dispatch: 如何分配调用入参
  - slice:对入参切分,也就是worker负载均衡执行
  - all:各个worker入参完全相同
  - 自定义切分方式,格式为:
    ```python

        def my_custom_slice(n, i, data):

            # n是worker数量,i是当前worker索引,data是原始入参

            # 返回第i个的入参

    ```

- execute: 如何执行

  - first: rank0执行,此时slice和Callable方式切分无效

  - all: 全部执行


- collect: 如何收集返回数据
  - none:原样返回,格式为各个worker返回值的列表
  - flatten: 将worker返回的结果进行拉平,支持tuple的拉平
  - Callable: 自定义collect方式,格式为:
    ```python

        def my_custom_collect(result):

            # result是各个worker返回的列表

            # 输入你想要的格式

    ```


## 参数设置

讲完技术细节后,可以将参数配置了。开发者可以根据不同的流程中的角色列表,设置不同的硬件搭配方式,例如采样功能中,共有三个角色,sampler、prm、orm,可以这样配置:

```yaml

device_groups:

  nproc_per_node: 4

  sample_group:

    device: GPU

    ranks: list(range(0, 2))

    workers:

      - sampler

  rm_group:

    device: GPU

    ranks: list(range(2, 4))

    workers:

      - prm

      - orm

```

- nproc_per_node: ray集群中需要的每个node的最小卡数。
xxx_group: 每个ray组的名称,可以随意指定

  - device: 设备类型,当前支持GPU/CPU等。

  - ranks: 当前组分配到哪些ranks上。如果是CPU,ranks只能为整数,代表共需要多少进程,如果是GPU,可以为`[0,1,2,3]`, `4`, `list(range(0, 4))`等格式。

  - workers: 哪些角色分配到当前组中。



所有可用的角色可以见本文最上面的表。



如果使用命令行,device_groups也可以以`--device_groups xxx`方式传入,xxx为jsonstring。为了配置的简便,我们强烈推荐使用yaml方式搭配ray使用。