File size: 10,358 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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# 插件化

> [!WARNING]
> 该文档待更新到ms-swift4.0

插件化是SWIFT3.0中新增的重要能力。我们希望通过插件化的方式,让开发者对开发流程的定制更加自然。

## callback回调

example在[这里](https://github.com/modelscope/ms-swift/blob/main/swift/callbacks).

`callback`机制是transformers Trainer中的一种训练定制化机制。开发者可以在callback中控制训练流程。通常来说,callback的定制化类似下面的样子:
```python

class CustomCallback(TrainerCallback):



    def on_train_begin(self, args: TrainingArguments, state: TrainerState, control: TrainerControl, **kwargs):

        # Doing something when the training begins.

        pass



    def on_save(self, args: TrainingArguments, state: TrainerState, control: TrainerControl, **kwargs):

        # Doing something when save checkpoint

        pass

```
callback会在trainer构造前注册进trainer中,example中给出了一个简单版本的EarlyStop方案。注册你自己的callback的方式比较简单:
```python

extra_callbacks = [CustomCallback()]

```
开发者可以在plugin/callback.py中增加新的callback,并定制自己的训练流程。callback的具体参数可以查看[这里](https://huggingface.co/docs/transformers/main_classes/callback)。


## 定制化loss

example在[这里](https://github.com/modelscope/ms-swift/blob/main/swift/loss/mapping.py).

SWIFT支持在plugin中定制loss。如果不使用这个能力,默认会使用交叉熵Loss(CE Loss)。开发者可以在这个文件中编写代码,注册后在训练时设置`--loss_type custom_loss`使用你定制的loss方法。
例如在plugin/loss.py中添加下面的代码:
```python

def custom_loss_func(outputs, labels, loss_scale=None, num_items_in_batch=None) -> torch.Tensor:

    # Write your own loss calculating here

    return loss



loss_map['custom_loss'] = custom_loss_func

```
需要注意的是,loss和trainer训练的任务是强相关的,目前的loss定制针对pt和sft任务,如果是人类对齐任务(例如DPO、PPO等)或分类任务(seq_cls)任务在插件中是无法定制的。



## 定制化loss_scale

example在[这里](https://github.com/modelscope/ms-swift/blob/main/swift/loss_scale/mapping.py).

loss_scale机制在SWIFT中是非常重要的机制之一。在pt和sft任务中,可训练token的loss是均匀的,即每个token平等的进行bp。但在某些情况下,某些token的权重比较大,需要被额外关注,

在这种情况下就需要更高的权重。loss_scale可以让开发者自由地定义自己的token权重。
```python

class LastRoundLossScale(LossScale):



    def get_loss_scale(self, context: str, context_type: ContextType, is_last_round: bool, **kwargs):

        if context_type == ContextType.RESPONSE:

            return [context], [float(is_last_round)]

        return super().get_loss_scale(context, context_type, is_last_round)

```
在上面的代码中,返回了一个Tuple,第一个返回是context(或拆解后的context),第二个参数是context对应的loss_scale,float值代表了权重。例如下面的权重设置:

```text

["学习", "好", "数学", "是", "重要", "的"]

[1.0, 0.5, 2.0, 0.5, 2.0, 0.1]

```

我们更看重数学和重要两个词,因此我们把它们的权重提升到2.0。

回到上面的代码,我们判断了传入的context是否是response,如果是response且如果是多轮对话的最后一轮才返回[1],在其他情况下使用基类的实现(在本场景下loss_scale时[0])。使用这种方案,
我们做到了只有最后一轮的response参与训练,其他response不参与训练。使用这种方式,可以让所有token(prompt、response)参与训练,或针对agent某些特殊字符重点训练等。
在pt和sft中,loss_scale是整体支持(是否参与训练,以及权重大小)的,而人类对齐中只能支持某些token是否参与训练,无法支持权重大小。



## 定制化metric



example在[这里](https://github.com/modelscope/ms-swift/blob/main/swift/metrics).



metric可以定制训练时使用的评测参数:

```python

metric_mapping = {
    'acc': (compute_acc_metrics, preprocess_logits_for_acc),

    'nlg': (compute_nlg_metrics, None),

    'custom': (custom_metric, custom_preprocess),

}



def get_metric(metric: str):

    return metric_mapping[metric]
```

在上面的定义中,我们添加了新的custom metric,它的value有两个值,第一个值是计算metric的过程,返回一个包含metric key-value对的dict,第二个值是针对logits做前处理,返回实际的predictions。



## 定制化optimizer



example在[这里](https://github.com/modelscope/ms-swift/blob/main/swift/optimizers/mapping.py).

- 对模型不同部分采用不同的学习率,例如:ViT和LLM分别使用不同的学习率,参考[这里](https://github.com/modelscope/ms-swift/blob/main/examples/train/multimodal/lora_llm_full_vit/custom_plugin.py)。



用户可以在这里增加自己的optimizer和lr_scheduler实现:

```python

def create_custom_optimizers(args, model, dataset):

    # 创建自己的optimizer

    return CustomOptimizer(optimizer_grouped_parameters, **optimizer_kwargs), CustomScheduler(...)



optimizers_map = {

    'custom': create_custom_optimizers,

    ...

}

```

当开发者需要使用其他optimizer,例如某些新论文中定义的optimizer时,可以在这里定义其创建过程,并在参数中使用:
```shell

--optimizer custom

```
就可以实际调用了。

## 定制化agent template

example在[这里](https://github.com/modelscope/ms-swift/blob/main/swift/agent_template/mapping.py).

## 定制化tuner

example在[这里](https://github.com/modelscope/ms-swift/blob/main/swift/tuner_plugin).
- 多模态模型对ViT部分使用全参数训练,LLM部分使用LoRA训练,参考[这里](https://github.com/modelscope/ms-swift/tree/main/examples/train/multimodal/lora_llm_full_vit)。
- Phi4-multimodal,直接对其已有LoRA进行训练而不额外附加LoRA,参考[这里](https://github.com/modelscope/ms-swift/blob/main/examples/train/plugins/tuner_phi4_mm.sh)。

tuner定制也是swift中有特色的能力之一,开发者可以无视复杂的tuner初始化流程和代码整合成本,将新的tuner注册在这里:
```python

class IA3(Tuner):



    @staticmethod

    def prepare_model(args: 'SftArguments', model: torch.nn.Module) -> torch.nn.Module:

        model_arch: ModelKeys = model.model_meta.model_arch

        ia3_config = IA3Config(

            target_modules=find_all_linears(model), feedforward_modules='.*' + model_arch.mlp.split('{}.')[1] + '.*')

        return get_peft_model(model, ia3_config)



    @staticmethod

    def save_pretrained(

        model: torch.nn.Module,

        save_directory: str,

        state_dict: Optional[dict] = None,

        safe_serialization: bool = True,

        **kwargs,

    ) -> None:

        model: PeftModel

        model.save_pretrained(save_directory, state_dict=state_dict, safe_serialization=safe_serialization, **kwargs)



    @staticmethod

    def from_pretrained(model: torch.nn.Module, model_id: str, **kwargs) -> torch.nn.Module:

        return PeftModel.from_pretrained(model, model_id, **kwargs)

```

上面的例子中,我们将peft的IA3应用于模型训练中,在这个类中包含了三个方法:
- prepare_model: 如何将原始模型使用tuner进行封装,并设置好可训练参数

- save_pretrained: 如何在训练中保存模型
- from_pretrained: 如何在后续训练和推理中将之前存下来的checkpoint重新拉起



上面的三个方法会在swift训练流程中被调用,这样就做到了开发者可以不阅读复杂的训练代码而使用自己的tuner。



## PRM



example在[这里](https://github.com/modelscope/ms-swift/blob/main/swift/rewards/prm.py)。



PRM是过程奖励模型,PRM会在`swift sample`命令中使用。PRM需要支持的接口比较简单:

```python

class PRM:



    def __init__(self):

        # init here

        pass



    def __call__(self, infer_requests: List[InferRequest], **kwargs) -> List[Union[float, List[float]]]:

        raise NotImplementedError

```



其中的InferRequest来自于`swift.infer_engine`,返回的`List[Union[float, List[float]]]`,列表中可能是reward也可能是若干reward。开发者可以在infer_requests中拿到queries和responses,并按照自己的方式进行切分,例如:

```text

Let's think step by step.



Step1: xxx



Step2: xxx



So, the answer is ...

```

开发者可以在这里对过程进行切分,并按batch传入PRM中进行推理并返回rewards。更通用来说,开发者可以在这里调用一个远端URL,例如一个闭源PRM大模型并返回rewards。



## ORM



example在[这里](https://github.com/modelscope/ms-swift/blob/main/swift/rewards/orm.py)。



ORM是结果奖励模型。ORM一般使用正则表达式来进行,ORM决定了response是否是正确的。例如:



```python

class MathORM(ORM):



    @staticmethod

    def extract_boxed_result(text):

        pattern = r'\\boxed{([^}]*)}'

        match = re.search(pattern, text)

        if match:

            return match.group(1).strip()

        else:

            return None



    def __call__(self, infer_requests: List[InferRequest], ground_truths: List[str],

                **kwargs) -> List[float]:

        rewards = []

        predictions = [request.messages[-1]['content'] for request in infer_requests]

        for prediction, ground_truth in zip(predictions, ground_truths):

            res1 = MathORM.extract_boxed_result(prediction) or ''

            res2 = MathORM.extract_boxed_result(ground_truth) or ''

            rewards.append(float(res1.strip() == res2.strip()))



        return rewards





orms = {

    'math': MathORM,

}

```



在上面的代码中,我们定义了一个对数学response进行解析的过程,如果结果相同则返回score为1.0,否则为0.0。和PRM不同,这个类的infer中有一个额外参数`ground_truths`,

该参数是对应的infer_requests的实际label(数据集中定义的标准response)。