diff --git a/.gitignore b/.gitignore index 8c2a1c3..66b0628 100644 --- a/.gitignore +++ b/.gitignore @@ -67,7 +67,7 @@ instance/ # Scrapy stuff: .scrapy - +.history # Sphinx documentation docs/_build/ diff --git a/README.md b/README.md index c0d9e82..46d8173 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ - [ ] [Qwen2-vl-2B FastApi 部署调用]() - [ ] [Qwen2-vl-2B WebDemo 部署]() - [ ] [Qwen2-vl-2B vLLM 部署]() - - [ ] [Qwen2-vl-2B Lora 微调]() + - [ ] [Qwen2-vl-2B Lora 微调]() @李柯辰 - [Qwen2.5](https://github.com/QwenLM/Qwen2.5) - [x] [Qwen2.5-7B-Instruct FastApi 部署调用](./models/Qwen2.5/01-Qwen2.5-7B-Instruct%20FastApi%20部署调用.md) @娄天奥 diff --git a/models/Qwen2-vl-2B/04-Qwen2-vl-2B Lora .ipynb b/models/Qwen2-vl-2B/04-Qwen2-vl-2B Lora .ipynb new file mode 100644 index 0000000..40953fe --- /dev/null +++ b/models/Qwen2-vl-2B/04-Qwen2-vl-2B Lora .ipynb @@ -0,0 +1,954 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "de53995b-32ed-4722-8cac-ba104c8efacb", + "metadata": {}, + "source": [ + "# 导入环境" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "52fac949-4150-4091-b0c3-2968ab5e385c", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from datasets import Dataset\n", + "import pandas as pd\n", + "from transformers import AutoTokenizer, AutoModelForCausalLM, DataCollatorForSeq2Seq, TrainingArguments, Trainer, GenerationConfig" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "e098d9eb", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# 将JSON文件转换为CSV文件\n", + "df = pd.read_json('dataset/huanhuan.json')\n", + "ds = Dataset.from_pandas(df)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "8ac92d42-efae-49b1-a00e-ccaa75b98938", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "{'instruction': ['小姐,别的秀女都在求中选,唯有咱们小姐想被撂牌子,菩萨一定记得真真儿的——',\n", + " '这个温太医啊,也是古怪,谁不知太医不得皇命不能为皇族以外的人请脉诊病,他倒好,十天半月便往咱们府里跑。',\n", + " '嬛妹妹,刚刚我去府上请脉,听甄伯母说你来这里进香了。'],\n", + " 'input': ['', '', ''],\n", + " 'output': ['嘘——都说许愿说破是不灵的。', '你们俩话太多了,我该和温太医要一剂药,好好治治你们。', '出来走走,也是散心。']}" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "ds[:3]" + ] + }, + { + "cell_type": "markdown", + "id": "51d05e5d-d14e-4f03-92be-9a9677d41918", + "metadata": {}, + "source": [ + "# 处理数据集" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "74ee5a67-2e55-4974-b90e-cbf492de500a", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Special tokens have been added in the vocabulary, make sure the associated word embeddings are fine-tuned or trained.\n" + ] + }, + { + "data": { + "text/plain": [ + "Qwen2Tokenizer(name_or_path='/root/autodl-tmp/qwen/Qwen2.5-7B-Instruct', vocab_size=151643, model_max_length=131072, is_fast=False, padding_side='right', truncation_side='right', special_tokens={'eos_token': '<|im_end|>', 'pad_token': '<|endoftext|>', 'additional_special_tokens': ['<|im_start|>', '<|im_end|>', '<|object_ref_start|>', '<|object_ref_end|>', '<|box_start|>', '<|box_end|>', '<|quad_start|>', '<|quad_end|>', '<|vision_start|>', '<|vision_end|>', '<|vision_pad|>', '<|image_pad|>', '<|video_pad|>']}, clean_up_tokenization_spaces=False), added_tokens_decoder={\n", + "\t151643: AddedToken(\"<|endoftext|>\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),\n", + "\t151644: AddedToken(\"<|im_start|>\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),\n", + "\t151645: AddedToken(\"<|im_end|>\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),\n", + "\t151646: AddedToken(\"<|object_ref_start|>\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),\n", + "\t151647: AddedToken(\"<|object_ref_end|>\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),\n", + "\t151648: AddedToken(\"<|box_start|>\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),\n", + "\t151649: AddedToken(\"<|box_end|>\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),\n", + "\t151650: AddedToken(\"<|quad_start|>\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),\n", + "\t151651: AddedToken(\"<|quad_end|>\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),\n", + "\t151652: AddedToken(\"<|vision_start|>\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),\n", + "\t151653: AddedToken(\"<|vision_end|>\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),\n", + "\t151654: AddedToken(\"<|vision_pad|>\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),\n", + "\t151655: AddedToken(\"<|image_pad|>\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),\n", + "\t151656: AddedToken(\"<|video_pad|>\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),\n", + "\t151657: AddedToken(\"\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=False),\n", + "\t151658: AddedToken(\"\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=False),\n", + "\t151659: AddedToken(\"<|fim_prefix|>\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=False),\n", + "\t151660: AddedToken(\"<|fim_middle|>\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=False),\n", + "\t151661: AddedToken(\"<|fim_suffix|>\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=False),\n", + "\t151662: AddedToken(\"<|fim_pad|>\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=False),\n", + "\t151663: AddedToken(\"<|repo_name|>\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=False),\n", + "\t151664: AddedToken(\"<|file_sep|>\", rstrip=False, lstrip=False, single_word=False, normalized=False, special=False),\n", + "}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tokenizer = AutoTokenizer.from_pretrained('/root/autodl-tmp/qwen/Qwen2.5-7B-Instruct', use_fast=False, trust_remote_code=True)\n", + "tokenizer" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "2503a5fa-9621-4495-9035-8e7ef6525691", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def process_func(example):\n", + " MAX_LENGTH = 384 # Llama分词器会将一个中文字切分为多个token,因此需要放开一些最大长度,保证数据的完整性\n", + " input_ids, attention_mask, labels = [], [], []\n", + " instruction = tokenizer(f\"<|im_start|>system\\n现在你要扮演皇帝身边的女人--甄嬛<|im_end|>\\n<|im_start|>user\\n{example['instruction'] + example['input']}<|im_end|>\\n<|im_start|>assistant\\n\", add_special_tokens=False) # add_special_tokens 不在开头加 special_tokens\n", + " response = tokenizer(f\"{example['output']}\", add_special_tokens=False)\n", + " input_ids = instruction[\"input_ids\"] + response[\"input_ids\"] + [tokenizer.pad_token_id]\n", + " attention_mask = instruction[\"attention_mask\"] + response[\"attention_mask\"] + [1] # 因为eos token咱们也是要关注的所以 补充为1\n", + " labels = [-100] * len(instruction[\"input_ids\"]) + response[\"input_ids\"] + [tokenizer.pad_token_id] \n", + " if len(input_ids) > MAX_LENGTH: # 做一个截断\n", + " input_ids = input_ids[:MAX_LENGTH]\n", + " attention_mask = attention_mask[:MAX_LENGTH]\n", + " labels = labels[:MAX_LENGTH]\n", + " return {\n", + " \"input_ids\": input_ids,\n", + " \"attention_mask\": attention_mask,\n", + " \"labels\": labels\n", + " }" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "84f870d6-73a9-4b0f-8abf-687b32224ad8", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Map: 0%| | 0/3729 [00:00system\\n现在你要扮演皇帝身边的女人--甄嬛<|im_end|>\\n<|im_start|>user\\n小姐,别的秀女都在求中选,唯有咱们小姐想被撂牌子,菩萨一定记得真真儿的——<|im_end|>\\n<|im_start|>assistant\\n嘘——都说许愿说破是不灵的。<|endoftext|>'" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tokenizer.decode(tokenized_id[0]['input_ids'])" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "97f16f66-324a-454f-8cc3-ef23b100ecff", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "'你们俩话太多了,我该和温太医要一剂药,好好治治你们。<|endoftext|>'" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "tokenizer.decode(list(filter(lambda x: x != -100, tokenized_id[1][\"labels\"])))" + ] + }, + { + "cell_type": "markdown", + "id": "424823a8-ed0d-4309-83c8-3f6b1cdf274c", + "metadata": {}, + "source": [ + "# 创建模型" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "170764e5-d899-4ef4-8c53-36f6dec0d198", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "289ae6a90ec24c3c992405abbcf347c0", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Loading checkpoint shards: 0%| | 0/4 [00:00, auto_mapping=None, base_model_name_or_path=None, revision=None, task_type=, inference_mode=False, r=8, target_modules={'o_proj', 'gate_proj', 'q_proj', 'k_proj', 'up_proj', 'down_proj', 'v_proj'}, lora_alpha=32, lora_dropout=0.1, fan_in_fan_out=False, bias='none', use_rslora=False, modules_to_save=None, init_lora_weights=True, layers_to_transform=None, layers_pattern=None, rank_pattern={}, alpha_pattern={}, megatron_config=None, megatron_core='megatron.core', loftq_config={}, use_dora=False, layer_replication=None)" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from peft import LoraConfig, TaskType, get_peft_model\n", + "\n", + "config = LoraConfig(\n", + " task_type=TaskType.CAUSAL_LM, \n", + " target_modules=[\"q_proj\", \"k_proj\", \"v_proj\", \"o_proj\", \"gate_proj\", \"up_proj\", \"down_proj\"],\n", + " inference_mode=False, # 训练模式\n", + " r=8, # Lora 秩\n", + " lora_alpha=32, # Lora alaph,具体作用参见 Lora 原理\n", + " lora_dropout=0.1# Dropout 比例\n", + ")\n", + "config" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "2c2489c5-eaab-4e1f-b06a-c3f914b4bf8e", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "LoraConfig(peft_type=, auto_mapping=None, base_model_name_or_path='/root/autodl-tmp/qwen/Qwen2.5-7B-Instruct', revision=None, task_type=, inference_mode=False, r=8, target_modules={'o_proj', 'gate_proj', 'q_proj', 'k_proj', 'up_proj', 'down_proj', 'v_proj'}, lora_alpha=32, lora_dropout=0.1, fan_in_fan_out=False, bias='none', use_rslora=False, modules_to_save=None, init_lora_weights=True, layers_to_transform=None, layers_pattern=None, rank_pattern={}, alpha_pattern={}, megatron_config=None, megatron_core='megatron.core', loftq_config={}, use_dora=False, layer_replication=None)" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model = get_peft_model(model, config)\n", + "config" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "ebf5482b-fab9-4eb3-ad88-c116def4be12", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "trainable params: 20,185,088 || all params: 7,635,801,600 || trainable%: 0.26434798934534914\n" + ] + } + ], + "source": [ + "model.print_trainable_parameters()" + ] + }, + { + "cell_type": "markdown", + "id": "ca055683-837f-4865-9c57-9164ba60c00f", + "metadata": {}, + "source": [ + "# 配置训练参数" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "7e76bbff-15fd-4995-a61d-8364dc5e9ea0", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "args = TrainingArguments(\n", + " output_dir=\"./output/Qwen2.5_instruct_lora\",\n", + " per_device_train_batch_size=4,\n", + " gradient_accumulation_steps=4,\n", + " logging_steps=10,\n", + " num_train_epochs=3,\n", + " save_steps=100, \n", + " learning_rate=1e-4,\n", + " save_on_each_node=True,\n", + " gradient_checkpointing=True\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "f142cb9c-ad99-48e6-ba86-6df198f9ed96", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "trainer = Trainer(\n", + " model=model,\n", + " args=args,\n", + " train_dataset=tokenized_id,\n", + " data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "aec9bc36-b297-45af-99e1-d4c4d82be081", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "`use_cache=True` is incompatible with gradient checkpointing. Setting `use_cache=False`...\n", + "/root/miniconda3/lib/python3.10/site-packages/torch/utils/checkpoint.py:429: UserWarning: torch.utils.checkpoint: please pass in use_reentrant=True or use_reentrant=False explicitly. The default value of use_reentrant will be updated to be False in the future. To maintain current behavior, pass use_reentrant=True. It is recommended that you use use_reentrant=False. Refer to docs for more details on the differences between the two variants.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "
\n", + " \n", + " \n", + " [699/699 10:02, Epoch 2/3]\n", + "
\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
StepTraining Loss
103.759900
203.096600
303.063300
402.917600
502.900500
602.953600
702.912600
803.013900
903.045900
1002.933000
1102.927600
1202.942600
1302.921200
1402.876200
1502.984700
1602.968600
1702.955000
1802.809300
1902.859800
2002.913100
2102.815100
2202.853700
2302.863900
2402.807300
2502.647200
2602.501700
2702.542600
2802.585100
2902.608100
3002.556000
3102.517500
3202.585400
3302.566300
3402.512500
3502.631100
3602.518600
3702.568600
3802.528400
3902.564100
4002.423300
4102.631800
4202.402200
4302.598100
4402.533700
4502.479900
4602.486800
4702.390800
4802.299600
4902.225800
5002.185200
5102.116700
5202.199900
5302.308900
5402.233000
5502.086700
5602.243200
5702.195300
5802.232300
5902.163100
6002.155500
6102.266100
6202.220200
6302.120700
6401.994400
6502.347600
6602.120000
6702.190900
6802.190100
6902.143500

" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/root/miniconda3/lib/python3.10/site-packages/peft/utils/save_and_load.py:154: UserWarning: Could not find a config file in /root/autodl-tmp/qwen/Qwen2.5-7B-Instruct - will assume that the vocabulary was not modified.\n", + " warnings.warn(\n", + "/root/miniconda3/lib/python3.10/site-packages/torch/utils/checkpoint.py:429: UserWarning: torch.utils.checkpoint: please pass in use_reentrant=True or use_reentrant=False explicitly. The default value of use_reentrant will be updated to be False in the future. To maintain current behavior, pass use_reentrant=True. It is recommended that you use use_reentrant=False. Refer to docs for more details on the differences between the two variants.\n", + " warnings.warn(\n", + "/root/miniconda3/lib/python3.10/site-packages/peft/utils/save_and_load.py:154: UserWarning: Could not find a config file in /root/autodl-tmp/qwen/Qwen2.5-7B-Instruct - will assume that the vocabulary was not modified.\n", + " warnings.warn(\n", + "/root/miniconda3/lib/python3.10/site-packages/torch/utils/checkpoint.py:429: UserWarning: torch.utils.checkpoint: please pass in use_reentrant=True or use_reentrant=False explicitly. The default value of use_reentrant will be updated to be False in the future. To maintain current behavior, pass use_reentrant=True. It is recommended that you use use_reentrant=False. Refer to docs for more details on the differences between the two variants.\n", + " warnings.warn(\n", + "/root/miniconda3/lib/python3.10/site-packages/peft/utils/save_and_load.py:154: UserWarning: Could not find a config file in /root/autodl-tmp/qwen/Qwen2.5-7B-Instruct - will assume that the vocabulary was not modified.\n", + " warnings.warn(\n", + "/root/miniconda3/lib/python3.10/site-packages/torch/utils/checkpoint.py:429: UserWarning: torch.utils.checkpoint: please pass in use_reentrant=True or use_reentrant=False explicitly. The default value of use_reentrant will be updated to be False in the future. To maintain current behavior, pass use_reentrant=True. It is recommended that you use use_reentrant=False. Refer to docs for more details on the differences between the two variants.\n", + " warnings.warn(\n", + "/root/miniconda3/lib/python3.10/site-packages/peft/utils/save_and_load.py:154: UserWarning: Could not find a config file in /root/autodl-tmp/qwen/Qwen2.5-7B-Instruct - will assume that the vocabulary was not modified.\n", + " warnings.warn(\n", + "/root/miniconda3/lib/python3.10/site-packages/torch/utils/checkpoint.py:429: UserWarning: torch.utils.checkpoint: please pass in use_reentrant=True or use_reentrant=False explicitly. The default value of use_reentrant will be updated to be False in the future. To maintain current behavior, pass use_reentrant=True. It is recommended that you use use_reentrant=False. Refer to docs for more details on the differences between the two variants.\n", + " warnings.warn(\n", + "/root/miniconda3/lib/python3.10/site-packages/peft/utils/save_and_load.py:154: UserWarning: Could not find a config file in /root/autodl-tmp/qwen/Qwen2.5-7B-Instruct - will assume that the vocabulary was not modified.\n", + " warnings.warn(\n", + "/root/miniconda3/lib/python3.10/site-packages/torch/utils/checkpoint.py:429: UserWarning: torch.utils.checkpoint: please pass in use_reentrant=True or use_reentrant=False explicitly. The default value of use_reentrant will be updated to be False in the future. To maintain current behavior, pass use_reentrant=True. It is recommended that you use use_reentrant=False. Refer to docs for more details on the differences between the two variants.\n", + " warnings.warn(\n", + "/root/miniconda3/lib/python3.10/site-packages/peft/utils/save_and_load.py:154: UserWarning: Could not find a config file in /root/autodl-tmp/qwen/Qwen2.5-7B-Instruct - will assume that the vocabulary was not modified.\n", + " warnings.warn(\n", + "/root/miniconda3/lib/python3.10/site-packages/torch/utils/checkpoint.py:429: UserWarning: torch.utils.checkpoint: please pass in use_reentrant=True or use_reentrant=False explicitly. The default value of use_reentrant will be updated to be False in the future. To maintain current behavior, pass use_reentrant=True. It is recommended that you use use_reentrant=False. Refer to docs for more details on the differences between the two variants.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "text/plain": [ + "TrainOutput(global_step=699, training_loss=2.5707558644176043, metrics={'train_runtime': 603.8093, 'train_samples_per_second': 18.527, 'train_steps_per_second': 1.158, 'total_flos': 4.578125275274342e+16, 'train_loss': 2.5707558644176043, 'epoch': 3.0})" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trainer.train()" + ] + }, + { + "cell_type": "markdown", + "id": "8abb2327-458e-4e96-ac98-2141b5b97c8e", + "metadata": {}, + "source": [ + "# 合并加载模型" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "bd2a415a-a9ad-49ea-877f-243558a83bfc", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Special tokens have been added in the vocabulary, make sure the associated word embeddings are fine-tuned or trained.\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "e6dc6e1f8180464aa17ad958188e3aff", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Loading checkpoint shards: 0%| | 0/4 [00:00 考虑到部分同学配置环境可能会遇到一些问题,我们在 AutoDL 平台准备了 Qwen2.5 的环境镜像,该镜像适用于该仓库除 Qwen-GPTQ 和 vllm 外的所有部署环境。点击下方链接并直接创建 Autodl 示例即可。 +> ***https://www.codewithgpu.com/i/datawhalechina/self-llm/Qwen2.5-self-llm*** --> + +> 注意:flash-attn 安装会比较慢,大概需要十几分钟。 + +在本节教程里,我们将微调数据集放置在根目录 [/dataset](../dataset/huanhuan.json)。 + +## 模型下载 +使用 `modelscope` 中的 `snapshot_download` 函数下载模型,第一个参数为模型名称,参数 `cache_dir` 为模型的下载路径。 + +新建 `model_download.py` 文件输入以下代码,并运行 `python model_download.py` 执行下载。 + +此处使用 `modelscope` 提供的 `snapshot_download` 函数进行下载,该方法对国内的用户十分友好。 + +在 /root/autodl-tmp 路径下新建 model_download.py 文件并在其中输入以下内容,粘贴代码后请及时保存文件,如下图所示。并运行 `python /root/autodl-tmp/model_download.py` 执行下载,模型大小为 15GB,下载模型大概需要 5 分钟。 + +```python +import torch +from modelscope import snapshot_download, AutoModel, AutoTokenizer +import os +model_dir = snapshot_download('qwen/Qwen2.5-7B-Instruct', cache_dir='/root/autodl-tmp', revision='master') +``` + +## 指令集构建 + +LLM 的微调一般指指令微调过程。所谓指令微调,是说我们使用的微调数据形如: + +```json +{ + "instruction": "回答以下用户问题,仅输出答案。", + "input": "1+1等于几?", + "output": "2" +} +``` + +其中,`instruction` 是用户指令,告知模型其需要完成的任务;`input` 是用户输入,是完成用户指令所必须的输入内容;`output` 是模型应该给出的输出。 + +即我们的核心训练目标是让模型具有理解并遵循用户指令的能力。因此,在指令集构建时,我们应针对我们的目标任务,针对性构建任务指令集。例如,在本节我们使用由笔者合作开源的 [Chat-甄嬛](https://github.com/KMnO4-zx/huanhuan-chat) 项目作为示例,我们的目标是构建一个能够模拟甄嬛对话风格的个性化 LLM,因此我们构造的指令形如: + +```json +{ + "instruction": "你是谁?", + "input": "", + "output": "家父是大理寺少卿甄远道。" +} +``` + +我们所构造的全部指令数据集在根目录下。 + +## 数据格式化 + +`Lora` 训练的数据是需要经过格式化、编码之后再输入给模型进行训练的,如果是熟悉 `Pytorch` 模型训练流程的同学会知道,我们一般需要将输入文本编码为 input_ids,将输出文本编码为 `labels`,编码之后的结果都是多维的向量。我们首先定义一个预处理函数,这个函数用于对每一个样本,编码其输入、输出文本并返回一个编码后的字典: + +```python +def process_func(example): + MAX_LENGTH = 384 # Llama分词器会将一个中文字切分为多个token,因此需要放开一些最大长度,保证数据的完整性 + input_ids, attention_mask, labels = [], [], [] + instruction = tokenizer(f"<|im_start|>system\n现在你要扮演皇帝身边的女人--甄嬛<|im_end|>\n<|im_start|>user\n{example['instruction'] + example['input']}<|im_end|>\n<|im_start|>assistant\n", add_special_tokens=False) # add_special_tokens 不在开头加 special_tokens + response = tokenizer(f"{example['output']}", add_special_tokens=False) + input_ids = instruction["input_ids"] + response["input_ids"] + [tokenizer.pad_token_id] + attention_mask = instruction["attention_mask"] + response["attention_mask"] + [1] # 因为eos token咱们也是要关注的所以 补充为1 + labels = [-100] * len(instruction["input_ids"]) + response["input_ids"] + [tokenizer.pad_token_id] + if len(input_ids) > MAX_LENGTH: # 做一个截断 + input_ids = input_ids[:MAX_LENGTH] + attention_mask = attention_mask[:MAX_LENGTH] + labels = labels[:MAX_LENGTH] + return { + "input_ids": input_ids, + "attention_mask": attention_mask, + "labels": labels + } +``` + +`Qwen2` 采用的 `Prompt Template`格式如下: + +```text +<|im_start|>system +You are a helpful assistant.<|im_end|> +<|im_start|>user +你是谁?<|im_end|> +<|im_start|>assistant +我是一个有用的助手。<|im_end|> +``` + +## 加载 tokenizer 和半精度模型 + +模型以半精度形式加载,如果你的显卡比较新的话,可以用 `torch.bfolat`形式加载。对于自定义的模型一定要指定 `trust_remote_code`参数为 `True`。 + +```python +tokenizer = AutoTokenizer.from_pretrained('/root/autodl-tmp/qwen/Qwen2.5-7B-Instruct/', use_fast=False, trust_remote_code=True) + +model = AutoModelForCausalLM.from_pretrained('/root/autodl-tmp/qwen/Qwen2.5-7B-Instruct/', device_map="auto",torch_dtype=torch.bfloat16) +``` + +## 定义 LoraConfig + +`LoraConfig`这个类中可以设置很多参数,但主要的参数没多少,简单讲一讲,感兴趣的同学可以直接看源码。 + +- `task_type`:模型类型 +- `target_modules`:需要训练的模型层的名字,主要就是 `attention`部分的层,不同的模型对应的层的名字不同,可以传入数组,也可以字符串,也可以正则表达式。 +- `r`:`lora`的秩,具体可以看 `Lora`原理 +- `lora_alpha`:`Lora alaph`,具体作用参见 `Lora` 原理 + +`Lora`的缩放是啥嘞?当然不是 `r`(秩),这个缩放就是 `lora_alpha/r`, 在这个 `LoraConfig`中缩放就是 4 倍。 + +```python +config = LoraConfig( + task_type=TaskType.CAUSAL_LM, + target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"], + inference_mode=False, # 训练模式 + r=8, # Lora 秩 + lora_alpha=32, # Lora alaph,具体作用参见 Lora 原理 + lora_dropout=0.1# Dropout 比例 +) +``` + +## 自定义 TrainingArguments 参数 + +`TrainingArguments`这个类的源码也介绍了每个参数的具体作用,当然大家可以来自行探索,这里就简单说几个常用的。 + +- `output_dir`:模型的输出路径 +- `per_device_train_batch_size`:顾名思义 `batch_size` +- `gradient_accumulation_steps`: 梯度累加,如果你的显存比较小,那可以把 `batch_size` 设置小一点,梯度累加增大一些。 +- `logging_steps`:多少步,输出一次 `log` +- `num_train_epochs`:顾名思义 `epoch` +- `gradient_checkpointing`:梯度检查,这个一旦开启,模型就必须执行 `model.enable_input_require_grads()`,这个原理大家可以自行探索,这里就不细说了。 + +```python +args = TrainingArguments( + output_dir="./output/Qwen2.5_instruct_lora", + per_device_train_batch_size=4, + gradient_accumulation_steps=4, + logging_steps=10, + num_train_epochs=3, + save_steps=100, + learning_rate=1e-4, + save_on_each_node=True, + gradient_checkpointing=True +) +``` + +## 使用 Trainer 训练 + +```python +trainer = Trainer( + model=model, + args=args, + train_dataset=tokenized_id, + data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True), +) +trainer.train() +``` + +## 加载 lora 权重推理 + +训练好了之后可以使用如下方式加载 `lora`权重进行推理: + +```python +from transformers import AutoModelForCausalLM, AutoTokenizer +import torch +from peft import PeftModel + +model_path = '/root/autodl-tmp/qwen/Qwen2.5-7B-Instruct/' +lora_path = 'lora_path' + +# 加载tokenizer +tokenizer = AutoTokenizer.from_pretrained(model_path) + +# 加载模型 +model = AutoModelForCausalLM.from_pretrained(model_path, device_map="auto",torch_dtype=torch.bfloat16) + +# 加载lora权重 +model = PeftModel.from_pretrained(model, model_id=lora_path, config=config) + +prompt = "你是谁?" +messages = [ + {"role": "system", "content": "现在你要扮演皇帝身边的女人--甄嬛"}, + {"role": "user", "content": prompt} +] + +text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) + +model_inputs = tokenizer([text], return_tensors="pt").to('cuda') + +generated_ids = model.generate( + model_inputs.input_ids, + max_new_tokens=512 +) +generated_ids = [ + output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids) +] + +response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0] + +print(response) +```