yuccaaa commited on
Commit
0c857c5
·
verified ·
1 Parent(s): 8b85546

Upload ms-swift/docs/source/Customization/插件化.md with huggingface_hub

Browse files
ms-swift/docs/source/Customization/插件化.md ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 插件化
2
+
3
+ 插件化是SWIFT3.0中新增的重要能力。我们希望通过插件化的方式,让开发者对开发流程的定制更加自然。
4
+
5
+ ## callback回调
6
+
7
+ example在[这里](https://github.com/modelscope/swift/blob/main/swift/plugin/callback.py).
8
+
9
+ `callback`机制是transformers Trainer中的一种训练定制化机制。开发者可以在callback中控制训练流程。通常来说,callback的定制化类似下面的样子:
10
+ ```python
11
+ class CustomCallback(TrainerCallback):
12
+
13
+ def on_train_begin(self, args: TrainingArguments, state: TrainerState, control: TrainerControl, **kwargs):
14
+ # Doing something when the training begins.
15
+ pass
16
+
17
+ def on_save(self, args: TrainingArguments, state: TrainerState, control: TrainerControl, **kwargs):
18
+ # Doing something when save checkpoint
19
+ pass
20
+ ```
21
+ callback会在trainer构造前注册进trainer中,example中给出了一个简单版本的EarlyStop方案。注册你自己的callback的方式比较简单:
22
+ ```python
23
+ extra_callbacks = [CustomCallback()]
24
+ ```
25
+ 开发者可以在plugin/callback.py中增加新的callback,并定制自己的训练流程。callback的具体参数可以查看[这里](https://huggingface.co/docs/transformers/main_classes/callback)。
26
+
27
+
28
+ ## 定制化loss
29
+
30
+ example在[这里](https://github.com/modelscope/swift/blob/main/swift/plugin/loss.py).
31
+
32
+ SWIFT支持在plugin中定制loss。如果不使用这个能力,默认会使用交叉熵Loss(CE Loss)。开发者可以在这个文件中编写代码,注册后trainer会自动使用你定制的loss方法。
33
+ 例如在plugin/loss.py中添加下面的代码:
34
+ ```python
35
+ @register_loss_func("custom_loss")
36
+ def loss_scale_func(outputs, labels, loss_scale=None, num_items_in_batch=None) -> torch.Tensor:
37
+ # Write your own loss calculating here
38
+ return loss
39
+ ```
40
+ 需要注意的是,loss和trainer训练的任务是强相关的,目前的loss定制针对pt和sft任务,如果是人类对齐任务(例如DPO、PPO等)或分类任务(seq_cls)任务在插件中是无法定制的。
41
+
42
+ ## 定制化loss_scale
43
+
44
+ example在[这里](https://github.com/modelscope/swift/blob/main/swift/plugin/loss_scale.py).
45
+
46
+ loss_scale机制在SWIFT中是非常重要的机制之一。在pt和sft任务中,可训练token的loss是均匀的,即每个token平等的进行bp。但在某些情况下,某些token的权重比较大,需要被额外关注,
47
+ 在这种情况下就需要更高的权重。loss_scale可以让开发者自由地定义自己的token权重。
48
+ ```python
49
+ class LastRoundLossScale(LossScale):
50
+
51
+ def get_loss_scale(self, context: str, context_type: ContextType, is_last_round: bool, **kwargs):
52
+ if context_type == ContextType.RESPONSE:
53
+ return [context], [float(is_last_round)]
54
+ return super().get_loss_scale(context, context_type, is_last_round)
55
+ ```
56
+ 在上面的代码中,返回了一个Tuple,第一个返回是context(或拆解后的context),第二个参数是context对应的loss_scale,float值代表了权重。例如下面的权重设置:
57
+ ```text
58
+ ["学习", "好", "数学", "是", "重要", "的"]
59
+ [1.0, 0.5, 2.0, 0.5, 2.0, 0.1]
60
+ ```
61
+ 我们更看重数学和重要两个词,因此我们把它们的权重提升到2.0。
62
+ 回到上面的代码,我们判断了传入的context是否是response,如果是response且如果是多轮对话的最后一轮才返回[1],在其他情况下使用基类的实现(在本场景下loss_scale时[0])。使用这种方案,
63
+ 我们做到了只有最后一轮的response参与训练,其他response不参与训练。使用这种方式,可以让所有token(prompt、response)参与训练,或针对agent某些特殊字符重点训练等。
64
+ 在pt和sft中,loss_scale是整体支持(是否参与训练,以及权重大小)的,而人类对齐中只能支持某些token是否参与训练,无法支持权重大小。
65
+
66
+ ## 定制化metric
67
+
68
+ example在[这里](https://github.com/modelscope/swift/blob/main/swift/plugin/metric.py).
69
+
70
+ metric可以定制训练时使用的评测参数:
71
+ ```python
72
+ METRIC_MAPPING = {
73
+ 'acc': (compute_acc_metrics, preprocess_logits_for_acc),
74
+ 'nlg': (compute_nlg_metrics, None),
75
+ 'custom': (custom_metric, custom_preprocess),
76
+ }
77
+
78
+
79
+ def get_metric(metric: str):
80
+ return METRIC_MAPPING[metric]
81
+ ```
82
+ 在上面的定义中,我们添加了新的custom metric,它的value有两个值,第一个值是计算metric的过程,返回一个包含metric key-value对的dict,第二个值是针对logits做前处理,返回实际的predictions。
83
+
84
+ ## 定制化optimizer
85
+
86
+ example在[这里](https://github.com/modelscope/swift/blob/main/swift/plugin/optimizer.py).
87
+
88
+ 用户可以在这里增加自己的optimizer和lr_scheduler实现:
89
+ ```python
90
+ def create_custom_optimizers(args, model, dataset):
91
+ # 创建自己的optimizer
92
+ return CustomOptimizer(optimizer_grouped_parameters, **optimizer_kwargs), CustomScheduler(...)
93
+
94
+ optimizers_map = {
95
+ 'custom': create_custom_optimizers,
96
+ ...
97
+ }
98
+ ```
99
+
100
+ 当开发者需要使用其他optimizer,例如某些新论文中定义的optimizer时,可以在这里定义其创建过程,并在参数中使用:
101
+ ```shell
102
+ --optimizer custom
103
+ ```
104
+ 就可以实际调用了。
105
+
106
+ ## 定制化tools
107
+
108
+ example在[这里](https://github.com/modelscope/swift/blob/main/swift/plugin/tools.py).
109
+
110
+ 可以在这里定义Agent训练的tools格式。tools格式是指训练和推理时如何将工具枚举在system字段中,例如glm4就有其独特的tools格式:
111
+ ```python
112
+ def format_glm4(tool_names, tool_descs):
113
+ GLM4_PROMPT = """你是一个名为 ChatGLM 的人工智能助手。你是基于智谱AI训练的语言模型 GLM-4 模型开发的,你的任务是针对用户的问题和要求提供适当的答复和支持。
114
+
115
+ # 可用工具
116
+
117
+ {tool_list}"""
118
+ tool_descs = [json.dumps(t) if not isinstance(t, str) else t for t in tool_descs]
119
+ tool_list = ''
120
+ for name, tool in zip(tool_names, tool_descs):
121
+ tool_list += f'## {name}\n\n{tool}\n\n'
122
+ return GLM4_PROMPT.format(tool_list=tool_list)
123
+ ```
124
+ system中的完整格式类似于这样:
125
+ ```text
126
+ 你是一个名为 ChatGLM 的人工智能助手。你是基于智谱AI训练的语言模型 GLM-4 模型开发的,你的任务是针对用户的问题和要求提供适当的答复和支持。
127
+
128
+ # 可用工具
129
+
130
+ ## 查看天气
131
+
132
+ ...
133
+
134
+ ## 搜索网络
135
+
136
+ ...
137
+ ```
138
+
139
+ ## 定制化tuner
140
+
141
+ example在[这里](https://github.com/modelscope/swift/blob/main/swift/plugin/tuner.py).
142
+
143
+ tuner定制也是swift中有特色的能力之一,开发者可以无视复杂的tuner初始化流程和代码整合成本,将新的tuner注册在这里:
144
+ ```python
145
+ class IA3(Tuner):
146
+
147
+ @staticmethod
148
+ def prepare_model(args: 'TrainArguments', model: torch.nn.Module) -> torch.nn.Module:
149
+ model_arch: ModelKeys = MODEL_ARCH_MAPPING[model.model_meta.model_arch]
150
+ ia3_config = IA3Config(
151
+ target_modules=find_all_linears(model), feedforward_modules='.*' + model_arch.mlp.split('{}.')[1] + '.*')
152
+ return get_peft_model(model, ia3_config)
153
+
154
+ @staticmethod
155
+ def save_pretrained(
156
+ model: torch.nn.Module,
157
+ save_directory: str,
158
+ state_dict: Optional[dict] = None,
159
+ safe_serialization: bool = True,
160
+ **kwargs,
161
+ ) -> None:
162
+ model: PeftModel
163
+ model.save_pretrained(save_directory, state_dict=state_dict, safe_serialization=safe_serialization, **kwargs)
164
+
165
+ @staticmethod
166
+ def from_pretrained(model: torch.nn.Module, model_id: str, **kwargs) -> torch.nn.Module:
167
+ return PeftModel.from_pretrained(model, model_id, **kwargs)
168
+ ```
169
+
170
+ 上面的例子中,我们将peft的IA3应用于模型训练中,在这个类中包含了三个方法:
171
+ - prepare_model: 如何将原始模型使用tuner进行封装,并设置好可训练参数
172
+ - save_pretrained: 如何在训练中保存模型
173
+ - from_pretrained: 如何在后续训练和推理中将之前存下来的checkpoint重新拉起
174
+
175
+ 上面的三个方法会在swift训练流程中被调用,这样就做到了开发者可以不阅读复杂的训练代码而使用自己的tuner。
176
+
177
+ ## PRM
178
+
179
+ example在[这里](https://github.com/modelscope/swift/blob/main/swift/plugin/prm.py)。
180
+
181
+ PRM是过程奖励模型,PRM会在`swift sample`命令中使用。PRM需要支持的接口比较简单:
182
+ ```python
183
+ class PRM:
184
+
185
+ def __init__(self):
186
+ # init here
187
+ pass
188
+
189
+ def __call__(self, infer_requests: List[InferRequest], **kwargs) -> List[Union[float, List[float]]]:
190
+ raise NotImplementedError
191
+ ```
192
+
193
+ 其中的InferRequest来自于`swift.llm`,返回的`List[Union[float, List[float]]]`,列表中可能是reward也可能是若干reward。开发者可以在infer_requests中拿到queries和responses,并按照自己的方式进行切分,例如:
194
+ ```text
195
+ Let's think step by step.
196
+
197
+ Step1: xxx
198
+
199
+ Step2: xxx
200
+
201
+ So, the answer is ...
202
+ ```
203
+ 开发者可以在这里对过程进行切分,并按batch传入PRM中进行推理并返回rewards。更通用来说,开发者可以在这里调用一个远端URL,例如一个闭源PRM大模型并返回rewards。
204
+
205
+ ## ORM
206
+
207
+ example在[这里](https://github.com/modelscope/swift/blob/main/swift/plugin/orm.py)。
208
+
209
+ ORM是结果奖励模型。ORM一般使用正则表达式来进行,ORM决定了response是否是正确的。例如:
210
+
211
+ ```python
212
+ class MathORM(ORM):
213
+
214
+ @staticmethod
215
+ def extract_boxed_result(text):
216
+ pattern = r'\\boxed{([^}]*)}'
217
+ match = re.search(pattern, text)
218
+ if match:
219
+ return match.group(1).strip()
220
+ else:
221
+ return None
222
+
223
+ def __call__(self, infer_requests: List[InferRequest], ground_truths: List[str],
224
+ **kwargs) -> List[float]:
225
+ rewards = []
226
+ predictions = [request.messages[-1]['content'] for request in infer_requests]
227
+ for prediction, ground_truth in zip(predictions, ground_truths):
228
+ res1 = MathORM.extract_boxed_result(prediction) or ''
229
+ res2 = MathORM.extract_boxed_result(ground_truth) or ''
230
+ rewards.append(float(res1.strip() == res2.strip()))
231
+
232
+ return rewards
233
+
234
+
235
+ orms = {
236
+ 'math': MathORM,
237
+ }
238
+ ```
239
+
240
+ 在上面的代码中,我们定义了一个对数学response进行解析的过程,如果结果相同则返回score为1.0,否则为0.0。和PRM不同,这个类的infer中有一个额外参数`ground_truths`,
241
+ 该参数是对应的infer_requests的实际label(数据集中定义的标准response)。