首页 励志语录 文学杂读 农贸分析

从0开始微调LLama2系列 (3)

2023-12-28

从0开始微调LLama2系列 (3) - Lora微调

在上一期完成对模型简单体验之后,在这一期我们正式准备进行模型微调。模型微调的原理是利用一些特定领域的数据来提高模型在某些方面/领域的表现,增强模型的专业性、准确率等等。

本文首先介绍如何进行Lora微调,主要基于Chinese-LLaMA-Alpaca-2 项目。

注 :Chinese-LLaMA-Alpaca-2 项目 使用的是HuggingFace的Trainer接口,对多机多卡的支持并不友好

基本原理

对于LLM来说,模型的能力很大程度上来自于预训练过程,无论是RLHF还是fine-tuning,很大程度上是在做alignment [1]。

fine-tuning的效果和微调数据集、任务等因素相关。一般来说,高质量的数据会带来好的微调效果。基础模型比较擅长的任务,微调也能带来不错的提升。微调一般来说有两种方式:

全模型微调:不固定模型参数,基于SFT数据训练较少的epoch。全模型微调时,要特别注意可能出现灾难性遗忘(模型在多任务上效果下降)。训练数据的多样性(混合多个任务的SFT数据、包含原始预训练数据)非常重要。全模型微调本质上还是重复Pre-Train的过程,因此可以注入新的知识到模型中。

固定部分参数形式:增加一些旁路模块,微调时只更新这些旁路模块的参数,不更新整个模型,极大的减少所需要的显存。典型的adapter、Lora等微调方式,通过固定基础模型参数只训练额外参数等形式,有效避免了灾难性遗忘, 但效果可能不如全模型微调(未证实)。

数据集准备

我们所面对的场景主要是销售,基于销售和客户的对话构建出微调数据集。我们期望的是模型能够根据给出的对话内容,结构化的输出对对话内容的总结和提炼出来的标签,我们也期望模型能输出结构化的结果。

举个例子,假设对话内容是:

(客户):你好,我是玛丽,我听说你们这里有一家很好吃的餐厅,我想预订一张桌子。 (员工):你好,玛丽,欢迎来到我们餐厅!当然可以帮你预订桌子。请问您是几位? (客户):我们是四个人,今晚7点半。 (员工):好的,四位玛丽,今晚7点半。请问您有任何特殊要求吗?比如靠窗户还是离吸烟区远一点? (客户):靠窗户的位置最好,而且请给我们一个安静的角落,因为我们要庆祝一个特殊的场合。 (员工):明白了,靠窗户的安静角落,我已经记录下来了。请问您的联系电话是多少,以便我们确认预订? (客户):我的电话号码是555-123-4567。 (员工):好的,谢谢您,玛丽。我们会提前为您准备好桌子,期待您的光临! (客户):谢谢!期待今晚的晚餐。再见!

对对话内容的总结是,总结和内容标签可以完全利用GPT-4/3.5生成,避免人工手动标注数据:

总结: 玛丽预订了四人桌,要求靠窗户和安静角落,庆祝特殊场合,留下电话号码。

该内容的标签:餐厅预订,特殊场合,窗户位置,电话预订

微调时需要将这些数据处理成Stanford Alpaca格式,

[ {"instruction" : ..., "input" : ..., "output" : ...}, ...]

利用GPT-4写好转换脚本,最终微调的数据样例如下:

{ "instruction": "Based on the dialogue content, provide a summary and relevant tags. {\"dialogue\": \"(客户):你好,我是玛丽,我听说你们这里有一家很好吃的餐厅,我想预订一张桌子。(员工):你好,玛丽,欢迎来到我们餐厅!当然可以帮你预订桌子。请问您是几位?(客户):我们是四个人,今晚7点半。(员工):好的,四位玛丽,今晚7点半。请问您有任何特殊要求吗?比如靠窗户还是离吸烟区远一点?(客户):靠窗户的位置最好,而且请给我们一个安静的角落,因为我们要庆祝一个特殊的场合。(员工):明白了,靠窗户的安静角落,我已经记录下来了。请问您的联系电话是多少,以便我们确认预订?(客户):我的电话号码是555-123-4567。(员工):好的,谢谢您,玛丽。我们会提前为您准备好桌子,期待您的光临!(客户):谢谢!期待今晚的晚餐。再见!\"}", "input": "", "output": "{\"summary\": \"玛丽预订了四人桌,要求靠窗户和安静角落,庆祝特殊场合,留下电话号码。\", \"tags\": ["餐厅预订","特殊场合","窗户位置","电话预订"]}" },

将数据集划分为train.json(训练数据)、valid.json(验证数据),保存在路径:/root/playground/finetuning-data/ih_test

Lora 微调

Chinese-LLaMA-Alpaca-2 提供了Lora微调的教程和脚本,在路径Chinese-LLaMA-Alpaca-2/scripts/training 下

f1.png

核心脚本是run_clm_pt_with_perft.py(全模型微调)、run_clm_sft_with_peft.py(lora 微调)。对应的run_pt.sh, run_sft.sh给出了示例参数和如何运行。ds_zero2_no_offload.json是deepspeed的配 置。

deepspeed是微软开发的大模型训练优化框架,该框架应用了一系列技术。简单来说,由于LLM训练时需要大量显存,但这对大多数开发者并不友好,Deepspeed可以将显存占用load到内存中来减少显存占用,另外也支持模型并行、数据并行等技术。

打开run_sft脚本,我们能看到如下内容:

# 运行脚本前请仔细阅读wiki(https://github.com/ymcui/Chinese-LLaMA-Alpaca-2/wiki/sft_scripts_zh) # Read the wiki(https://github.com/ymcui/Chinese-LLaMA-Alpaca-2/wiki/sft_scripts_zh) carefully before running the script lr=1e-4 lora_rank=64 lora_alpha=128 lora_trainable="q_proj,v_proj,k_proj,o_proj,gate_proj,down_proj,up_proj" modules_to_save="embed_tokens,lm_head" lora_dropout=0.05 pretrained_model=path/to/hf/llama-2/or/chinese-llama-2/dir/or/model_id chinese_tokenizer_path=path/to/chinese-llama-2/tokenizer/dir dataset_dir=path/to/sft/data/dir per_device_train_batch_size=1 per_device_eval_batch_size=1 gradient_accumulation_steps=8 max_seq_length=512 output_dir=output_dir validation_file=validation_file_name deepspeed_config_file=ds_zero2_no_offload.json torchrun --nnodes 1 --nproc_per_node 1 run_clm_sft_with_peft.py \ --deepspeed ${deepspeed_config_file} \ --model_name_or_path ${pretrained_model} \ --tokenizer_name_or_path ${chinese_tokenizer_path} \ --dataset_dir ${dataset_dir} \ --per_device_train_batch_size ${per_device_train_batch_size} \ --per_device_eval_batch_size ${per_device_eval_batch_size} \ --do_train \ --do_eval \ --seed $RANDOM \ --fp16 \ --num_train_epochs 1 \ --lr_scheduler_type cosine \ --learning_rate ${lr} \ --warmup_ratio 0.03 \ --weight_decay 0 \ --logging_strategy steps \ --logging_steps 10 \ --save_strategy steps \ --save_total_limit 3 \ --evaluation_strategy steps \ --eval_steps 100 \ --save_steps 200 \ --gradient_accumulation_steps ${gradient_accumulation_steps} \ --preprocessing_num_workers 8 \ --max_seq_length ${max_seq_length} \ --output_dir ${output_dir} \ --overwrite_output_dir \ --ddp_timeout 30000 \ --logging_first_step True \ --lora_rank ${lora_rank} \ --lora_alpha ${lora_alpha} \ --trainable ${lora_trainable} \ --lora_dropout ${lora_dropout} \ --modules_to_save ${modules_to_save} \ --torch_dtype float16 \ --validation_file ${validation_file} \ --load_in_kbits 16 \ --gradient_checkpointing \ --ddp_find_unused_parameters False

一般来说我们需要关注的是如下参数:

# 预训练模型地址 pretrained_model=/root/playground/model/chinese-alpaca-2-13b # 分词器地址 一般和模型地址相同 chinese_tokenizer_path=/root/playground/model/chinese-alpaca-2-13b # 训练数据地址 dataset_dir=/root/playground/finetuning-data/ih_test ... # 微调后模型输出地址 output_dir=/root/playground/model/finetuning-model # 验证集地址 validation_file=/root/playground/finetuning-data/ih_test/valid.json

执行脚本后可以看到输出:

[WARNING|logging.py:305] 2023-10-01 14:48:37,523 >> `use_cache=True` is incompatible with gradient checkpointing. Setting `use_cache=False`... {'loss': 1.5904, 'learning_rate': 5e-05, 'epoch': 0.02} {'loss': 1.2601, 'learning_rate': 9.448285454973738e-05, 'epoch': 0.18} {'loss': 1.0654, 'learning_rate': 7.413961013653726e-05, 'epoch': 0.36} {'loss': 1.0089, 'learning_rate': 4.5560205233853266e-05, 'epoch': 0.54} {'loss': 1.0417, 'learning_rate': 1.849560782091445e-05, 'epoch': 0.72} {'loss': 1.0778, 'learning_rate': 2.179950786173879e-06, 'epoch': 0.9} 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 55/55 [03:39<00:00, 3.78s/it][INFO|trainer.py:1960] 2023-10-01 14:52:16,557 >> Training completed. Do not forget to share your model on huggingface.co/models =) {'train_runtime': 331.5023, 'train_samples_per_second': 1.345, 'train_steps_per_second': 0.166, 'train_loss': 1.0821823293512518, 'epoch': 0.99} 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 55/55 [03:39<00:00, 3.98s/it] [INFO|tokenization_utils_base.py:2235] 2023-10-01 14:52:19,482 >> tokenizer config file saved in /root/playground/model/finetuning-model/sft_lora_model/tokenizer_config.json [INFO|tokenization_utils_base.py:2242] 2023-10-01 14:52:19,486 >> Special tokens file saved in /root/playground/model/finetuning-model/sft_lora_model/special_tokens_map.json ***** train metrics ***** epoch = 0.99 train_loss = 1.0822 train_runtime = 0:05:31.50 train_samples = 446 train_samples_per_second = 1.345 train_steps_per_second = 0.166 10/01/2023 14:52:19 - INFO - __main__ - *** Evaluate *** [INFO|trainer.py:3115] 2023-10-01 14:52:19,506 >> ***** Running Evaluation ***** [INFO|trainer.py:3117] 2023-10-01 14:52:19,507 >> Num examples = 49 [INFO|trainer.py:3120] 2023-10-01 14:52:19,507 >> Batch size = 1 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 49/49 [00:03<00:00, 14.47it/s] ***** eval metrics ***** epoch = 0.99 eval_loss = 0.7393 eval_runtime = 0:00:03.45 eval_samples = 49 eval_samples_per_second = 14.193 eval_steps_per_second = 14.193 perplexity = 2.0944

执行完成后载输出地址下可以看到如下内容:

(base) ➜ ~ ll /root/playground/model/finetuning-model total 28K -rw-r--r-- 1 root root 394 Oct 1 14:34 all_results.json drwxr-xr-x 4 root root 4.0K Aug 28 18:02 checkpoint-200 drwxr-xr-x 4 root root 4.0K Aug 28 18:14 checkpoint-400 -rw-r--r-- 1 root root 223 Oct 1 14:34 eval_results.json drwxr-xr-x 2 root root 4.0K Aug 19 11:37 sft_lora_model -rw-r--r-- 1 root root 1.4K Oct 1 14:34 trainer_state.json -rw-r--r-- 1 root root 192 Oct 1 14:34 train_results.json

在sft_lora_model下保存了我们的lora模型:

(base) ➜ ~ ll /root/playground/model/finetuning-model/sft_lora_model total 1.6G -rw-r--r-- 1 root root 522 Oct 1 14:34 adapter_config.json -rw-r--r-- 1 root root 1.6G Oct 1 14:34 adapter_model.bin -rw-r--r-- 1 root root 435 Oct 1 14:34 special_tokens_map.json -rw-r--r-- 1 root root 844 Oct 1 14:34 tokenizer_config.json -rw-r--r-- 1 root root 825K Oct 1 14:34 tokenizer.model

我们可以使用合并脚本来讲原始模型和lora模型进行合并,参考合并教程:

(base) ➜ Chinese-LLaMA-Alpaca-2 git:(master) python scripts/merge_llama2_with_chinese_lora_low_mem.py \ --base_model /mnt/bn/ih-llama2/root/playground/model/chinese-alpaca-2-13b \ --lora_model /root/playground/model/finetuning-model/sft_lora_model \ --output_type huggingface \ --output_dir /root/playground/model/finetuning-model/full_model [2023-10-01 14:51:17,413] [INFO] [real_accelerator.py:158:get_accelerator] Setting ds_accelerator to cuda (auto detect) ================================================================================ Base model: /mnt/bn/ih-llama2/root/playground/model/chinese-alpaca-2-13b LoRA model: /root/playground/model/finetuning-model/sft_lora_model Loading /root/playground/model/finetuning-model/sft_lora_model Loading ckpt pytorch_model-00001-of-00003.bin Merging... Saving ckpt pytorch_model-00001-of-00003.bin to /root/playground/model/finetuning-model/full_model in HF format... Loading ckpt pytorch_model-00002-of-00003.bin Merging... Saving ckpt pytorch_model-00002-of-00003.bin to /root/playground/model/finetuning-model/full_model in HF format... Loading ckpt pytorch_model-00003-of-00003.bin Merging... Saving ckpt pytorch_model-00003-of-00003.bin to /root/playground/model/finetuning-model/full_model in HF format... Saving tokenizer Saving config.json Saving generation_config.json Saving pytorch_model.bin.index.json Done. Check output dir: /root/playground/model/finetuning-model/full_model

最后展示一下显存占用大约为34GB

f2.png

微调效果

先部署下模型:

(torch2) ➜ Chinese-LLaMA-Alpaca-2 git:(master) python scripts/openai_server_demo/openai_api_server.py --base_model /root/playground/model/finetuning-model/full_model [2023-10-01 15:01:31,216] [INFO] [real_accelerator.py:133:get_accelerator] Setting ds_accelerator to cuda (auto detect) USE_MEM_EFF_ATTENTION: True STORE_KV_BEFORE_ROPE: False Apply NTK scaling with ALPHA=1.0 Loading checkpoint shards: 100%|████████████████████████████████████████████████████████████████████████████████████████████████| 3/3 [00:18<00:00, 6.03s/it] /root/miniconda3/envs/torch2/lib/python3.10/site-packages/transformers/generation/configuration_utils.py:362: UserWarning: `do_sample` is set to `False`. However, `temperature` is set to `0.9` -- this flag is only used in sample-based generation modes. You should set `do_sample=True` or unset `temperature`. This was detected when initializing the generation config instance, which means the corresponding file may hold incorrect parameterization and should be fixed. warnings.warn( /root/miniconda3/envs/torch2/lib/python3.10/site-packages/transformers/generation/configuration_utils.py:367: UserWarning: `do_sample` is set to `False`. However, `top_p` is set to `0.6` -- this flag is only used in sample-based generation modes. You should set `do_sample=True` or unset `top_p`. This was detected when initializing the generation config instance, which means the corresponding file may hold incorrect parameterization and should be fixed. warnings.warn( Vocab of the base model: 55296 Vocab of the tokenizer: 55296 2023-10-01 15:01:54,819 - INFO - Started server process [3360] 2023-10-01 15:01:54,820 - INFO - Waiting for application startup. 2023-10-01 15:01:54,820 - INFO - Application startup complete. 2023-10-01 15:01:54,820 - INFO - Uvicorn running on 0.0.0.0:19327 (Press CTRL+C to quit)

测试一下:

(torch2) training git:(master) curl http://localhost:19327/v1/completions \ -H "Content-Type: application/json" \ -d '{ "prompt": "Based on the dialogue content, provide a summary and relevant tags. {\"dialogue\": \"(客户):你好,我是玛丽,我听说你们这里有一家很好吃的餐厅,我想预订一张桌子。(员工):你好,玛丽,欢迎来到我们餐厅!当然可以帮你预订桌子。请问您是几位?(客户):我们是四个人,今晚7点半。(员工):好的,四位玛丽,今晚7点半。请问您有任何特殊要求吗?比如靠窗户还是离吸烟区远一点?(客户):靠窗户的位置最好,而且请给我们一个安静的角落,因为我们要庆祝一个特殊的场合。(员工):明白了,靠窗户的安静角落,我已经记录下来了。请问您的联系电话是多少,以便我们确认预订?(客户):我的电话号码是555-123-4567。(员工):好的,谢谢您,玛丽。我们会提前为您准备好桌子,期待您的光临!(客户):谢谢!期待今晚的晚餐。再见!\"}" }' {"id":"cmpl-GtXwFJZdqfvWbtvGk7MzYx","object":"text_completion","created":1696143890,"model":"chinese-llama-alpaca-2","choices":[{"index":0,"text":"{\"summary\": \"客户询问关于座位、烟雾和电话联系等信息\", \"tags\": [\"座位安排\", \"烟雾问题\", \"电话联系\"]}"}]}#

看起来模型能够很好的输出结构化的数据。

下期预告

由于脚本使用的是Trainer接口,而该接口对模型并行&deepspeed-zero3支持并不是很好。因此如果想进行全模型微调/尝试更大的模型,在没有很好显卡的情况下显存经常吃紧,因此下一期带来如何利用原生Deepspeed进行微调LLM。

热门文章

随机推荐

推荐文章