使用ms-swift训练模型-个人可运行的示例-25年04月
Written with StackEdit .
环境:魔搭社区modelscope,notebooksGPU环境
大纲:
swifft是什么
加载、训练、输出的示例
自定义损失函数
疑惑: 4. 为什么要用template? 5. 能否dataset也用swift加载。swift的dataset逻辑与Dataset类有什么不同?
swift是什么 swift有很多种意思,例如: 它可以表示一种Apple开发的编程语言; 它可以作为形容词表示“快速的,迅速的”; ……
在这里,swift是一个第三方 Python 库,通常是用于对大模型进行高效微调和推理 。可以通过下面的命令安装:
pip install ms-swift ``` 根据[swift官方文档](https://swift.readthedocs.io/en/latest/GetStarted/Quick-start.html),可以得知: `ms-swift` 是由阿里巴巴的 ModelScope 社区开发和维护的 Python 框架,全称为 **SWIFT(Scalable lightWeight Infrastructure for Fine-Tuning)**。它专为大语言模型(LLM)和多模态大模型的微调、推理、评估、量化和部署而设计,支持超过 450 个文本模型和 150 个多模态模型,包括 Qwen、InternLM、GLM、Baichuan、Yi、LLaMA、Mistral 等主流模型。 --- 前言: 如果你是一位做深度学习的研究者,有过项目经验,你可以直接阅读官方github项目的示例代码: [10-minute self-cognition SFT](https://github.com/modelscope/ms-swift/blob/main/examples/notebook/qwen2_5-self-cognition/self-cognition-sft.ipynb)。(直接询问AI助手很容易因为"swift" 本身的混淆产生幻觉,补充官方文档的信息即可自行利用) 简单概括,下面的过程是“使用swift的LoRA微调方法微调Qwen2.5-3B-Instruct,并尝试添加自定义损失函数”。 ```python from swift.llm import ( get_model_tokenizer, get_template, ) from swift.tuners import Swift, LoRAConfig import torch model_id = 'Qwen/Qwen2.5-3B-Instruct' model, tokenizer = get_model_tokenizer( model_id_or_path=model_id, torch_dtype=torch.bfloat16 if torch.cuda.is_available() else torch.float32, model_kwargs={"device_map" : "auto" } )
LoRA配置 lora_config = LoRAConfig( r=16 , lora_alpha=32 , target_modules=[ "q_proj" , "k_proj" , "v_proj" , "o_proj" , "gate_proj" , "up_proj" , "down_proj" ], lora_dropout=0.05 , bias="none" ) model = Swift.prepare_model(model, lora_config) if torch.cuda.is_available():model = model.to('cuda' )
数据集加载部分 这部分略过,请结合具体数据集自行进行处理后载入。 个人实现的大致思路为:
from datasets import load_datasetdataset = load_dataset("json" , data_files="dataset.jsonl" , split="train" ) def preprocess_function (examples ): dialogues = [] for messages in examples["messages" ]: dialogue = "" for msg in messages: dialogues.append(dialogue.strip()) inputs = tokenizer(dialogues, truncation=True , padding="max_length" , max_length=256 ) inputs["labels" ] = inputs["input_ids" ].copy() return inputs dataset = dataset.map (preprocess_function, batched=True , remove_columns=["messages" ]) train_dataset, eval_dataset = dataset.train_test_split(test_size=0.1 ).values()
训练 from swift.llm import get_templatetemplate = get_template( model.model_meta.template, tokenizer, max_length=512 ) template.set_mode('train' ) from swift import Trainer, TrainingArgumentstorch.set_grad_enabled(True ) training_args = TrainingArguments( output_dir="./output" , num_train_epochs=1 , per_device_train_batch_size=4 , per_device_eval_batch_size=4 , optim="adamw_torch" , learning_rate=3.7e-5 , save_steps=10_000 , save_total_limit=2 , evaluation_strategy="steps" , eval_steps=500 , logging_steps=500 , warmup_steps=500 , weight_decay=0.01 , logging_dir="./logs" , fp16=False , bf16=torch.cuda.is_bf16_supported(), ) model.enable_input_require_grads() trainer = Trainer( model=model, args=training_args, train_dataset=train_dataset, eval_dataset=eval_dataset, template=template, ) trainer.train() model.save_pretrained("./output" )
推理 def inference (model, tokenizer, input_text ): input_ids = tokenizer(input_text, return_tensors="pt" ).input_ids.to(model.device) output = model.generate(input_ids, max_length=256 ) response = tokenizer.decode(output[0 ], skip_special_tokens=True ) return response input_text = "输入,毕竟它是3B模型,不用期待它有满血版ds质量的输出。" response = inference(model, tokenizer, input_text) print (response)
补充:自定义损失函数 贴出一个使用交叉熵损失(如果没有指明损失函数,默认采用的就是交叉熵损失)的基本实现。
class CustomLoss (nn.Module): def __init__ (self ): super ().__init__() self.cross_entropy_loss = nn.CrossEntropyLoss() def forward (self, outputs, labels, **kwargs ): """ 参数说明: - outputs: 模型输出(需包含logits和生成的文本) - labels: 真实标签 - kwargs: Trainer可能传入的其他参数(如num_items_in_batch),如果不添加**kwargs并且还未对特定参数进行单独处理,会报传入未知参数的错误 - attention_mark是掩码,当你处于让数据长度相同等目的,对数据进行padding时,用于记录哪些位是填充前的有效数据 """ logits = outputs['logits' ] if isinstance (outputs, dict ) else outputs mask = outputs.get('attention_mask' , None ) if mask is not None : assert mask.shape == labels.shape, f"Mask shape {mask.shape} != labels shape {labels.shape} " loss = F.cross_entropy( logits.view(-1 , logits.size(-1 )), labels.view(-1 ), reduction='none' ) valid_tokens = mask.view(-1 ).sum () ce_loss = (loss * mask.view(-1 )).sum () / (valid_tokens + 1e-8 ) else : ce_loss = self.cross_entropy_loss( logits.view(-1 , logits.size(-1 )), labels.view(-1 ) ) return ce_loss