diff --git a/README.md b/README.md index 4131148..3fabd72 100644 --- a/README.md +++ b/README.md @@ -75,11 +75,11 @@ ### 已支持模型 - [ERNIE-4.5](https://huggingface.co/baidu/ERNIE-4.5-0.3B-PT) - - [ ] ERNIE-4.5-0.3B-PT Lora 微调及 SwanLab 可视化记录 + - [x] [ERNIE-4.5-0.3B-PT Lora 微调及 SwanLab 可视化记录](./models/ERNIE-4.5/01-ERNIE-4.5-0.3B-PT%20Lora%20微调及%20SwanLab%20可视化记录.md) @不要葱姜蒜 + - [x] [ERNIE-4.5-0.3B-PT Lora Docker 镜像](https://www.codewithgpu.com/i/datawhalechina/self-llm/ERNIE-4.5-lora) @不要葱姜蒜 - [Hunyuan-A13B-Instruct](https://github.com/Tencent-Hunyuan/Hunyuan-A13B) - [ ] [Hunyuan-A13B-Instruct 模型架构解析 Blog]() - - [ ] [Hunyuan-A13B-Instruct vllm 部署调用]() - [ ] [Hunyuan-A13B-Instruct SGLang 部署调用]() - [ ] [Hunyuan-A13B-Instruct EvalScope 并发测试]() - [ ] [Hunyuan-A13B-Instruct Lora SwanLab 可视化微调]() diff --git a/models/ERNIE-4.5/01-ERNIE-4.5-0.3B-PT Lora 微调及 SwanLab 可视化记录.ipynb b/models/ERNIE-4.5/01-ERNIE-4.5-0.3B-PT Lora 微调及 SwanLab 可视化记录.ipynb new file mode 100644 index 0000000..a58e105 --- /dev/null +++ b/models/ERNIE-4.5/01-ERNIE-4.5-0.3B-PT Lora 微调及 SwanLab 可视化记录.ipynb @@ -0,0 +1,1598 @@ +{ + "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", + "\n", + "from transformers import AutoTokenizer, AutoModelForCausalLM, DataCollatorForSeq2Seq, TrainingArguments, Trainer" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "e098d9eb", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# 将JSON文件转换为CSV文件\n", + "df = pd.read_json('./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": [], + "source": [ + "tokenizer = AutoTokenizer.from_pretrained('/root/autodl-tmp/PaddlePaddle/ERNIE-4.5-0.3B-PT', trust_remote_code=True)\n", + "tokenizer.pad_token = tokenizer.eos_token" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "714f9eab", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<|begin_of_sentence|>===system_message_test===\n", + "User: ===user_message_test===\n", + "Assistant: ===assistant_message_test===<|end_of_sentence|>Assistant: \n" + ] + } + ], + "source": [ + "messages = [\n", + " {\"role\": \"system\", \"content\": \"===system_message_test===\"},\n", + " {\"role\": \"user\", \"content\": \"===user_message_test===\"},\n", + " {\"role\": \"assistant\", \"content\": \"===assistant_message_test===\"},\n", + "]\n", + "\n", + "text = tokenizer.apply_chat_template(\n", + " messages,\n", + " tokenize=False,\n", + " add_generation_prompt=True,\n", + ")\n", + "print(text)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "2503a5fa-9621-4495-9035-8e7ef6525691", + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def process_func(example):\n", + " MAX_LENGTH = 1024 # 设置最大序列长度为1024个token\n", + " input_ids, attention_mask, labels = [], [], [] # 初始化返回值\n", + " # 适配chat_template\n", + " instruction = tokenizer(\n", + " f\"<|begin_of_sentence|>现在你要扮演皇帝身边的女人--甄嬛\\n\" \n", + " f\"User: {example['instruction']}\\n\" \n", + " f\"Assistant: \", \n", + " add_special_tokens=False \n", + " )\n", + " response = tokenizer(f\"{example['output']}<|end_of_sentence|>\", add_special_tokens=False)\n", + " # 将instructio部分和response部分的input_ids拼接,并在末尾添加eos token作为标记结束的token\n", + " input_ids = instruction[\"input_ids\"] + response[\"input_ids\"] + [tokenizer.pad_token_id]\n", + " # 注意力掩码,表示模型需要关注的位置\n", + " attention_mask = attention_mask = [1]*len(input_ids)\n", + " # 对于instruction,使用-100表示这些位置不计算loss(即模型不需要预测这部分)\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": 7, + "id": "84f870d6-73a9-4b0f-8abf-687b32224ad8", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "ffc177b8e7d942c0bd9c7f98890b532a", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Map: 0%| | 0/3729 [00:00 现在你要扮演皇帝身边的女人--甄嬛\n", + "User: 小姐,别的秀女都在求中选,唯有咱们小姐想被撂牌子,菩萨一定记得真真儿的——\n", + "Assistant: 嘘——都说许愿说破是不灵的。<|end_of_sentence|>\n" + ] + } + ], + "source": [ + "print(tokenizer.decode(tokenized_id[0]['input_ids']))" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "97f16f66-324a-454f-8cc3-ef23b100ecff", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " 你们俩话太多了,我该和温太医要一剂药,好好治治你们。<|end_of_sentence|>\n" + ] + } + ], + "source": [ + "print(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": 10, + "id": "170764e5-d899-4ef4-8c53-36f6dec0d198", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "Ernie4_5_ForCausalLM(\n", + " (model): Ernie4_5_Model(\n", + " (embed_tokens): Embedding(103424, 1024)\n", + " (layers): ModuleList(\n", + " (0-17): 18 x Ernie4_5_DecoderLayer(\n", + " (self_attn): Ernie4_5_Attention(\n", + " (q_proj): Linear(in_features=1024, out_features=2048, bias=False)\n", + " (k_proj): Linear(in_features=1024, out_features=256, bias=False)\n", + " (v_proj): Linear(in_features=1024, out_features=256, bias=False)\n", + " (o_proj): Linear(in_features=2048, out_features=1024, bias=False)\n", + " (rotary_emb): Ernie4_5_RopeEmbedding()\n", + " )\n", + " (mlp): Ernie4_5_MLP(\n", + " (gate_proj): Linear(in_features=1024, out_features=3072, bias=False)\n", + " (up_proj): Linear(in_features=1024, out_features=3072, bias=False)\n", + " (down_proj): Linear(in_features=3072, out_features=1024, bias=False)\n", + " (act_fn): SiLU()\n", + " )\n", + " (input_layernorm): Ernie4_5_RMSNorm()\n", + " (post_attention_layernorm): Ernie4_5_RMSNorm()\n", + " (residual_add1): Ernie4_5_FusedDropoutImpl(\n", + " (dropout): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (residual_add2): Ernie4_5_FusedDropoutImpl(\n", + " (dropout): Dropout(p=0.0, inplace=False)\n", + " )\n", + " )\n", + " )\n", + " (norm): Ernie4_5_RMSNorm()\n", + " )\n", + " (lm_head): Ernie4_5_LMHead()\n", + ")" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import torch\n", + "\n", + "model = AutoModelForCausalLM.from_pretrained('/root/autodl-tmp/PaddlePaddle/ERNIE-4.5-0.3B-PT', device_map=\"auto\",torch_dtype=torch.bfloat16, trust_remote_code=True)\n", + "model" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "f808b05c-f2cb-48cf-a80d-0c42be6051c7", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "torch.bfloat16" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model.dtype" + ] + }, + { + "cell_type": "markdown", + "id": "13d71257-3c1c-4303-8ff8-af161ebc2cf1", + "metadata": {}, + "source": [ + "# lora " + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "2d304ae2-ab60-4080-a80d-19cac2e3ade3", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "LoraConfig(task_type=, peft_type=, auto_mapping=None, base_model_name_or_path=None, revision=None, inference_mode=False, r=8, target_modules={'k_proj', 'gate_proj', 'v_proj', 'up_proj', 'down_proj', 'q_proj', 'o_proj'}, exclude_modules=None, 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', trainable_token_indices=None, loftq_config={}, eva_config=None, corda_config=None, use_dora=False, layer_replication=None, runtime_config=LoraRuntimeConfig(ephemeral_gpu_offload=False), lora_bias=False)" + ] + }, + "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(task_type=, peft_type=, auto_mapping=None, base_model_name_or_path='/root/autodl-tmp/PaddlePaddle/ERNIE-4.5-0.3B-PT', revision=None, inference_mode=False, r=8, target_modules={'k_proj', 'gate_proj', 'v_proj', 'up_proj', 'down_proj', 'q_proj', 'o_proj'}, exclude_modules=None, 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', trainable_token_indices=None, loftq_config={}, eva_config=None, corda_config=None, use_dora=False, layer_replication=None, runtime_config=LoraRuntimeConfig(ephemeral_gpu_offload=False), lora_bias=False)" + ] + }, + "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: 3,022,848 || all params: 363,770,880 || trainable%: 0.8310\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/ERNIE-4.5-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", + " report_to=\"none\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "9faedcb1", + "metadata": {}, + "outputs": [], + "source": [ + "import swanlab\n", + "from swanlab.integration.transformers import SwanLabCallback\n", + "\n", + "# 实例化SwanLabCallback\n", + "swanlab_callback = SwanLabCallback(\n", + " project=\"self-llm\", \n", + " experiment_name=\"ERNIE-4.5-0.5B-lora\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "f142cb9c-ad99-48e6-ba86-6df198f9ed96", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "No label_names provided for model class `PeftModelForCausalLM`. Since `PeftModel` hides base models input arguments, if label_names is not given, label_names can't be set automatically within `Trainer`. Note that empty label_names list will be used instead.\n" + ] + } + ], + "source": [ + "trainer = Trainer(\n", + " model=model,\n", + " args=args,\n", + " train_dataset=tokenized_id,\n", + " data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),\n", + " callbacks=[swanlab_callback]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "aec9bc36-b297-45af-99e1-d4c4d82be081", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1m\u001b[34mswanlab\u001b[0m\u001b[0m: Using SwanLab to track your experiments. Please refer to \u001b[33mhttps://docs.swanlab.cn\u001b[0m for more information.\n", + "\u001b[1m\u001b[34mswanlab\u001b[0m\u001b[0m: (1) Create a SwanLab account.\n", + "\u001b[1m\u001b[34mswanlab\u001b[0m\u001b[0m: (2) Use an existing SwanLab account.\n", + "\u001b[1m\u001b[34mswanlab\u001b[0m\u001b[0m: (3) Don't visualize my results.\n" + ] + }, + { + "name": "stdin", + "output_type": "stream", + "text": [ + "\u001b[1m\u001b[34mswanlab\u001b[0m\u001b[0m: Enter your choice: 2\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[1m\u001b[34mswanlab\u001b[0m\u001b[0m: You chose 'Use an existing swanlab account'\n", + "\u001b[1m\u001b[34mswanlab\u001b[0m\u001b[0m: Logging into \u001b[33mhttps://swanlab.cn\u001b[0m\n", + "\u001b[1m\u001b[34mswanlab\u001b[0m\u001b[0m: You can find your API key at: \u001b[33mhttps://swanlab.cn/space/~/settings\u001b[0m\n", + "\u001b[1m\u001b[34mswanlab\u001b[0m\u001b[0m: Paste an API key from your profile and hit enter, or press 'CTRL + C' to quit: \n" + ] + }, + { + "name": "stdin", + "output_type": "stream", + "text": [ + " ········\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + " \u001b[1m\u001b[34mswanlab\u001b[0m\u001b[0m: Tracking run with swanlab version 0.6.4\n", + "\u001b[1m\u001b[34mswanlab\u001b[0m\u001b[0m: Run data will be saved locally in \u001b[35m\u001b[1m/root/autodl-tmp/swanlog/run-20250703_172130-a3b1799d\u001b[0m\u001b[0m\n", + "\u001b[1m\u001b[34mswanlab\u001b[0m\u001b[0m: 👋 Hi \u001b[1m\u001b[39mkmno4\u001b[0m\u001b[0m, welcome to swanlab!\n", + "\u001b[1m\u001b[34mswanlab\u001b[0m\u001b[0m: Syncing run \u001b[33mERNIE-4.5-0.5B-lora\u001b[0m to the cloud\n", + "\u001b[1m\u001b[34mswanlab\u001b[0m\u001b[0m: 🏠 View project at \u001b[34m\u001b[4mhttps://swanlab.cn/@kmno4/self-llm\u001b[0m\u001b[0m\n", + "\u001b[1m\u001b[34mswanlab\u001b[0m\u001b[0m: 🚀 View run at \u001b[34m\u001b[4mhttps://swanlab.cn/@kmno4/self-llm/runs/odghce2d6e3lp9v5digvr\u001b[0m\u001b[0m\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " Show Iframe\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "\n", + "
\n", + " \n", + " \n", + " [702/702 07:36, Epoch 3/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", + " \n", + " \n", + " \n", + " \n", + "
StepTraining Loss
104.332400
203.720400
303.700500
403.520600
503.588400
603.540400
703.581000
803.636600
903.589500
1003.558700
1103.534600
1203.526800
1303.507400
1403.450900
1503.513800
1603.524800
1703.508900
1803.367100
1903.425300
2003.420200
2103.425300
2203.428900
2303.530200
2403.518500
2503.281400
2603.084500
2703.100300
2803.214400
2903.269700
3003.177700
3103.205800
3203.159700
3303.202300
3403.175500
3503.252000
3603.173300
3703.315900
3803.236900
3903.150800
4003.074300
4103.243200
4203.151200
4303.187100
4403.185300
4503.182800
4603.168700
4702.995900
4802.867200
4902.927100
5003.020300
5102.950900
5202.966600
5302.950200
5402.995100
5503.101900
5603.065100
5703.005300
5802.920200
5902.877900
6002.951600
6102.978900
6202.914900
6303.024100
6402.879400
6503.036000
6603.008600
6703.085700
6802.819400
6902.971700
7002.926700

" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "TrainOutput(global_step=702, training_loss=3.2409834644393705, metrics={'train_runtime': 479.6521, 'train_samples_per_second': 23.323, 'train_steps_per_second': 1.464, 'total_flos': 1620699778086912.0, 'train_loss': 3.2409834644393705, 'epoch': 3.0})" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " Show Iframe\n", + " \n", + " \n", + " \n", + "\n", + "\n", + "

" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "`use_cache=True` is incompatible with gradient checkpointing. Setting `use_cache=False`.\n" + ] + }, + { + "data": { + "text/html": [ + "\n", + "
\n", + " \n", + " \n", + " [699/699 13:20, 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
104.287200
203.227500
303.159800
403.039000
503.043600
603.000000
703.050700
803.076000
903.106600
1002.985500
1103.032900
1202.982700
1302.952500
1402.932900
1503.025800
1603.014300
1703.003700
1802.920500
1902.907000
2002.945700
2102.869400
2202.878500
2302.915600
2403.004300
2502.736400
2602.528400
2702.613000
2802.686000
2902.714200
3002.584100
3102.612300
3202.631600
3302.588400
3402.614400
3502.624800
3602.601100
3702.715300
3802.606000
3902.586500
4002.540000
4102.649600
4202.581000
4302.634300
4402.610700
4502.602800
4602.568900
4702.478000
4802.239600
4902.209800
5002.263900
5102.229700
5202.221800
5302.218100
5402.266800
5502.360600
5602.340000
5702.293600
5802.149000
5902.235100
6002.181800
6102.241800
6202.217700
6302.318700
6402.202300
6502.255900
6602.242800
6702.325000
6802.128100
6902.172100

" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "TrainOutput(global_step=699, training_loss=2.6425710331557988, metrics={'train_runtime': 879.9696, 'train_samples_per_second': 12.713, 'train_steps_per_second': 0.794, 'total_flos': 5.190619083415757e+16, 'train_loss': 2.6425710331557988, 'epoch': 2.990353697749196})" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "trainer.train()" + ] + }, + { + "cell_type": "markdown", + "id": "8abb2327-458e-4e96-ac98-2141b5b97c8e", + "metadata": {}, + "source": [ + "# 合并加载模型" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "bd2a415a-a9ad-49ea-877f-243558a83bfc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "PeftModelForCausalLM(\n", + " (base_model): LoraModel(\n", + " (model): Ernie4_5_ForCausalLM(\n", + " (model): Ernie4_5_Model(\n", + " (embed_tokens): Embedding(103424, 1024)\n", + " (layers): ModuleList(\n", + " (0-17): 18 x Ernie4_5_DecoderLayer(\n", + " (self_attn): Ernie4_5_Attention(\n", + " (q_proj): lora.Linear(\n", + " (base_layer): Linear(in_features=1024, out_features=2048, bias=False)\n", + " (lora_dropout): ModuleDict(\n", + " (default): Dropout(p=0.1, inplace=False)\n", + " )\n", + " (lora_A): ModuleDict(\n", + " (default): Linear(in_features=1024, out_features=8, bias=False)\n", + " )\n", + " (lora_B): ModuleDict(\n", + " (default): Linear(in_features=8, out_features=2048, bias=False)\n", + " )\n", + " (lora_embedding_A): ParameterDict()\n", + " (lora_embedding_B): ParameterDict()\n", + " (lora_magnitude_vector): ModuleDict()\n", + " )\n", + " (k_proj): lora.Linear(\n", + " (base_layer): Linear(in_features=1024, out_features=256, bias=False)\n", + " (lora_dropout): ModuleDict(\n", + " (default): Dropout(p=0.1, inplace=False)\n", + " )\n", + " (lora_A): ModuleDict(\n", + " (default): Linear(in_features=1024, out_features=8, bias=False)\n", + " )\n", + " (lora_B): ModuleDict(\n", + " (default): Linear(in_features=8, out_features=256, bias=False)\n", + " )\n", + " (lora_embedding_A): ParameterDict()\n", + " (lora_embedding_B): ParameterDict()\n", + " (lora_magnitude_vector): ModuleDict()\n", + " )\n", + " (v_proj): lora.Linear(\n", + " (base_layer): Linear(in_features=1024, out_features=256, bias=False)\n", + " (lora_dropout): ModuleDict(\n", + " (default): Dropout(p=0.1, inplace=False)\n", + " )\n", + " (lora_A): ModuleDict(\n", + " (default): Linear(in_features=1024, out_features=8, bias=False)\n", + " )\n", + " (lora_B): ModuleDict(\n", + " (default): Linear(in_features=8, out_features=256, bias=False)\n", + " )\n", + " (lora_embedding_A): ParameterDict()\n", + " (lora_embedding_B): ParameterDict()\n", + " (lora_magnitude_vector): ModuleDict()\n", + " )\n", + " (o_proj): lora.Linear(\n", + " (base_layer): Linear(in_features=2048, out_features=1024, bias=False)\n", + " (lora_dropout): ModuleDict(\n", + " (default): Dropout(p=0.1, inplace=False)\n", + " )\n", + " (lora_A): ModuleDict(\n", + " (default): Linear(in_features=2048, out_features=8, bias=False)\n", + " )\n", + " (lora_B): ModuleDict(\n", + " (default): Linear(in_features=8, out_features=1024, bias=False)\n", + " )\n", + " (lora_embedding_A): ParameterDict()\n", + " (lora_embedding_B): ParameterDict()\n", + " (lora_magnitude_vector): ModuleDict()\n", + " )\n", + " (rotary_emb): Ernie4_5_RopeEmbedding()\n", + " )\n", + " (mlp): Ernie4_5_MLP(\n", + " (gate_proj): lora.Linear(\n", + " (base_layer): Linear(in_features=1024, out_features=3072, bias=False)\n", + " (lora_dropout): ModuleDict(\n", + " (default): Dropout(p=0.1, inplace=False)\n", + " )\n", + " (lora_A): ModuleDict(\n", + " (default): Linear(in_features=1024, out_features=8, bias=False)\n", + " )\n", + " (lora_B): ModuleDict(\n", + " (default): Linear(in_features=8, out_features=3072, bias=False)\n", + " )\n", + " (lora_embedding_A): ParameterDict()\n", + " (lora_embedding_B): ParameterDict()\n", + " (lora_magnitude_vector): ModuleDict()\n", + " )\n", + " (up_proj): lora.Linear(\n", + " (base_layer): Linear(in_features=1024, out_features=3072, bias=False)\n", + " (lora_dropout): ModuleDict(\n", + " (default): Dropout(p=0.1, inplace=False)\n", + " )\n", + " (lora_A): ModuleDict(\n", + " (default): Linear(in_features=1024, out_features=8, bias=False)\n", + " )\n", + " (lora_B): ModuleDict(\n", + " (default): Linear(in_features=8, out_features=3072, bias=False)\n", + " )\n", + " (lora_embedding_A): ParameterDict()\n", + " (lora_embedding_B): ParameterDict()\n", + " (lora_magnitude_vector): ModuleDict()\n", + " )\n", + " (down_proj): lora.Linear(\n", + " (base_layer): Linear(in_features=3072, out_features=1024, bias=False)\n", + " (lora_dropout): ModuleDict(\n", + " (default): Dropout(p=0.1, inplace=False)\n", + " )\n", + " (lora_A): ModuleDict(\n", + " (default): Linear(in_features=3072, out_features=8, bias=False)\n", + " )\n", + " (lora_B): ModuleDict(\n", + " (default): Linear(in_features=8, out_features=1024, bias=False)\n", + " )\n", + " (lora_embedding_A): ParameterDict()\n", + " (lora_embedding_B): ParameterDict()\n", + " (lora_magnitude_vector): ModuleDict()\n", + " )\n", + " (act_fn): SiLU()\n", + " )\n", + " (input_layernorm): Ernie4_5_RMSNorm()\n", + " (post_attention_layernorm): Ernie4_5_RMSNorm()\n", + " (residual_add1): Ernie4_5_FusedDropoutImpl(\n", + " (dropout): Dropout(p=0.0, inplace=False)\n", + " )\n", + " (residual_add2): Ernie4_5_FusedDropoutImpl(\n", + " (dropout): Dropout(p=0.0, inplace=False)\n", + " )\n", + " )\n", + " )\n", + " (norm): Ernie4_5_RMSNorm()\n", + " )\n", + " (lm_head): Ernie4_5_LMHead()\n", + " )\n", + " )\n", + ")" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from transformers import AutoModelForCausalLM, AutoTokenizer\n", + "import torch\n", + "from peft import PeftModel\n", + "\n", + "mode_path = '/root/autodl-tmp/PaddlePaddle/ERNIE-4.5-0.3B-PT'\n", + "lora_path = './output/ERNIE-4.5-lora/checkpoint-702' # 这里改称你的 lora 输出对应 checkpoint 地址\n", + "\n", + "# 加载tokenizer\n", + "tokenizer = AutoTokenizer.from_pretrained(mode_path, trust_remote_code=True)\n", + "\n", + "# 加载模型\n", + "model = AutoModelForCausalLM.from_pretrained(mode_path, device_map=\"auto\",torch_dtype=torch.bfloat16, trust_remote_code=True)\n", + "\n", + "# 加载lora权重\n", + "model = PeftModel.from_pretrained(model, model_id=lora_path)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "8a802aa0-52e0-4cfb-91f2-126d763fd2e9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "generate_text: 我是甄嬛,家父是大理寺少卿甄远道。\n" + ] + } + ], + "source": [ + "prompt = \"你是谁?\"\n", + "\n", + "messages = [\n", + " {\"role\": \"system\", \"content\": \"假设你是皇帝身边的女人--甄嬛。\"},\n", + " {\"role\": \"user\", \"content\": prompt}\n", + "]\n", + "\n", + "text = tokenizer.apply_chat_template(\n", + " messages,\n", + " tokenize=False,\n", + " add_generation_prompt=True\n", + ")\n", + "model_inputs = tokenizer([text], add_special_tokens=False, return_tensors=\"pt\").to(model.device)\n", + "\n", + "# conduct text completion\n", + "generated_ids = model.generate(\n", + " model_inputs.input_ids,\n", + " max_new_tokens=1024\n", + ")\n", + "output_ids = generated_ids[0][len(model_inputs.input_ids[0]):].tolist()\n", + "\n", + "# decode the generated ids\n", + "generate_text = tokenizer.decode(output_ids, skip_special_tokens=True).strip(\"\\n\")\n", + "print(\"generate_text:\", generate_text)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c7ba8ac0", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/models/ERNIE-4.5/01-ERNIE-4.5-0.3B-PT Lora 微调及 SwanLab 可视化记录.md b/models/ERNIE-4.5/01-ERNIE-4.5-0.3B-PT Lora 微调及 SwanLab 可视化记录.md new file mode 100644 index 0000000..ab00b7d --- /dev/null +++ b/models/ERNIE-4.5/01-ERNIE-4.5-0.3B-PT Lora 微调及 SwanLab 可视化记录.md @@ -0,0 +1,407 @@ +# 01-ERNIE-4.5-0.3B-PT Lora 微调及 SwanLab 可视化记录 + +本节我们简要介绍如何基于 transformers、peft 等框架,使用由笔者合作开源的 [Chat-甄嬛](https://github.com/KMnO4-zx/huanhuan-chat) 项目中的**嬛嬛数据集**作为微调数据集,对 ERNIE-4.5-0.3B-PT 模型进行 LoRA 微调, 以构建一个能够模拟甄嬛对话风格的个性化 LLM , 数据集路径为[`../../dataset/huanhuan.json`](../../dataset/huanhuan.json)。同时使用 [SwanLab](https://github.com/swanhubx/swanlab) 监控训练过程与评估模型效果。 + +![](./images/06-01.png) + +> **LoRA** 是一种高效微调方法,深入了解其原理可参见博客:[知乎|深入浅出 LoRA](https://zhuanlan.zhihu.com/p/650197598)。 + +> 本教程会在同目录下给大家提供一个 [**notebook 文件** ERNIE-4.5-0.3B-PT Lora 微调及 SwanLab 可视化记录.ipynb](./01-ERNIE-4.5-0.3B-PT%20Lora%20微调及%20SwanLab%20可视化记录.ipynb) ,来帮助大家更好的学习。 + +- 代码:文本的完整微调代码部分,或本目录下的 [01-ERNIE-4.5-0.3B-PT Lora 微调及 SwanLab 可视化记录.ipynb](./01-ERNIE-4.5-0.3B-PT%20Lora%20微调及%20SwanLab%20可视化记录.ipynb) +- 可视化训练过程:[datawhale-kmno4/self-llm](https://swanlab.cn/@datawhale-kmno4/self-llm/overview) +- 模型:[ERNIE-4.5-0.3B-PT](https://www.modelscope.cn/models/PaddlePaddle/ERNIE-4.5-0.3B-PT) +- 数据集:[huanhuan](../../dataset/huanhuan.json) +- 显存需求:约 24GB + +
+ +## 目录 + +- [01-ERNIE-4.5-0.3B-PT Lora 微调及 SwanLab 可视化记录](#01-ernie-45-03b-pt-lora-微调及-swanlab-可视化记录) + - [目录](#目录) + - [1. 环境配置](#1-环境配置) + - [2. 模型下载](#2-模型下载) + - [3. 指令集构建](#3-指令集构建) + - [4. 数据格式化](#4-数据格式化) + - [5. 加载 tokenizer 和半精度模型 (model)](#5-加载-tokenizer-和半精度模型-model) + - [6. 定义 LoraConfig](#6-定义-loraconfig) + - [7. 自定义 TrainingArguments 参数](#7-自定义-trainingarguments-参数) + - [8. SwanLab 可视化](#8-swanlab-可视化) + - [SwanLab 简介](#swanlab-简介) + - [实例化 SwanLabCallback](#实例化-swanlabcallback) + - [9. 使用 Trainer 训练](#9-使用-trainer-训练) + - [10. 训练结果演示](#10-训练结果演示) + - [11. 加载 LoRA 权重推理](#11-加载-lora-权重推理) + +
+ +## 1. 环境配置 + +实验所依赖的基础开发环境如下: + +``` +---------------- +ubuntu 22.04 +Python 3.12.3 +cuda 12.4 +pytorch 2.5.1 +---------------- +``` + +> 本文默认学习者已安装好以上 Pytorch(cuda) 环境,如未安装请自行安装。 + +首先 `pip` 换源加速下载并安装依赖包: + +```shell +# 升级pip +python -m pip install --upgrade pip +# 更换 pypi 源加速库的安装 +pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple + +pip install modelscope==1.25.0 # 用于模型下载和管理 +pip install transformers==4.51.3 # Hugging Face 的模型库,用于加载和训练模型 +pip install accelerate==1.6.0 # 用于分布式训练和混合精度训练 +pip install datasets==3.5.1 # 用于加载和处理数据集 +pip install peft==0.15.2 # 用于 LoRA 微调 +pip install swanlab==0.5.7 # 用于监控训练过程与评估模型效果 +``` + +> 考虑到部分同学配置环境可能会遇到一些问题,我们在 AutoDL 平台准备了 ERNIE-4.5-0.3B-PT Lora 的环境镜像,点击下方链接并直接创建 Autodl 示例即可。 +> ***[ERNIE-4.5-0.3B-PT Lora](https://www.codewithgpu.com/i/datawhalechina/self-llm/ERNIE-4.5-lora)*** + +## 2. 模型下载 + +`modelscope` 是一个模型管理和下载工具,支持从魔搭 (Modelscope) 等平台快速下载模型。 + +这里使用 `modelscope` 中的 `snapshot_download` 函数下载模型,第一个参数 `model_name_or_path` 为模型名称或者本地路径,第二个参数 `cache_dir` 为模型的下载路径,第三个参数 `revision` 为模型的版本号。 + +在 `/root/autodl-tmp` 路径下新建 `model_download.py` 文件并在其中粘贴以下代码,并保存文件。 + +```python +from modelscope import snapshot_download + +model_dir = snapshot_download('PaddlePaddle/ERNIE-4.5-0.3B-PT', cache_dir='/root/autodl-tmp', revision='master') +``` + +> 注意:记得修改 cache_dir 为你的模型下载路径哦~ + +在终端运行 `python /root/autodl-tmp/model_download.py` 执行下载,模型大小为 57GB 左右,下载时间较久。 + +## 3. 指令集构建 + +LLM 的微调一般指指令微调过程。所谓指令微调,是说我们使用的微调数据形如: + +```json +{ + "instruction": "回答以下用户问题,仅输出答案。", + "input": "1+1等于几?", + "output": "2" +} +``` + +其中,`instruction` 是用户指令,告知模型其需要完成的任务;`input` 是用户输入,是完成用户指令所必须的输入内容;`output` 是模型应该给出的输出。 + +即我们的核心训练目标是让模型具有理解并遵循用户指令的能力。因此,在指令集构建时,我们应针对我们的目标任务,针对性构建任务指令集。 + +例如,在本节我们使用由笔者合作开源的 [**Chat-甄嬛**](https://github.com/KMnO4-zx/huanhuan-chat) 项目作为示例,我们的目标是构建一个能够模拟甄嬛对话风格的个性化 LLM,因此我们构造的指令形如: + +```json +{ + "instruction": "你是谁?", + "input": "", + "output": "家父是大理寺少卿甄远道。" +} +``` + +我们所构造的全部指令数据集会被保存在根目录下。 + +## 4. 数据格式化 + +`LoRA` 训练的数据是需要经过格式化、编码之后再输入给模型进行训练的,如果是熟悉 `Pytorch` 模型训练流程的同学会知道,我们一般需要将输入文本编码为 `input_ids`,将输出文本编码为 `labels`,编码之后的结果都是多维的向量。 + +为了得到 ERNIE-4.5-0.3B-PT 的 Prompt Template,使用 tokenizer 构建 messages 并打印, 查看 chat_template 的输出格式 + +```python +messages = [ + {"role": "system", "content": "===system_message_test==="}, + {"role": "user", "content": "===user_message_test==="}, + {"role": "assistant", "content": "===assistant_message_test==="}, +] + +text = tokenizer.apply_chat_template( + messages, + tokenize=False, + add_generation_prompt=True, +) +print(text) +``` + +得到输出结果如下 + +```text +<|begin_of_sentence|>===system_message_test=== +User: ===user_message_test=== +Assistant: ===assistant_message_test===<|end_of_sentence|>Assistant: +``` + +然后我们就可以定义预处理函数 `process_func`,这个函数用于对每一个样本,编码其输入、输出文本并返回一个编码后的字典,方便模型使用: + +```python +def process_func(example): + MAX_LENGTH = 1024 # 设置最大序列长度为1024个token + input_ids, attention_mask, labels = [], [], [] # 初始化返回值 + # 适配chat_template + instruction = tokenizer( + f"<|begin_of_sentence|>现在你要扮演皇帝身边的女人--甄嬛\n" + f"User: {example['instruction']}\n" + f"Assistant: ", + add_special_tokens=False + ) + response = tokenizer(f"{example['output']}<|end_of_sentence|>", add_special_tokens=False) + # 将instructio部分和response部分的input_ids拼接,并在末尾添加eos token作为标记结束的token + input_ids = instruction["input_ids"] + response["input_ids"] + [tokenizer.pad_token_id] + # 注意力掩码,表示模型需要关注的位置 + attention_mask = attention_mask = [1]*len(input_ids) + # 对于instruction,使用-100表示这些位置不计算loss(即模型不需要预测这部分) + 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 + } +``` + +> 注意:因为Ernie4_5_Tokenizer重写了def _pad() 函数返回的 attention_mask 是 3d 而非 1d ,可以直接attention_mask = [1]*len(input_ids) + +## 5. 加载 tokenizer 和半精度模型 (model) + +`tokenizer` 是将文本转换为模型 (`model`) 能理解的数字的工具,`model` 是根据这些数字生成文本的核心部分。 + +以半精度形式加载 `model`, 如果你的显卡比较新的话,可以用 `torch.bfolat` 形式加载。对于自定义模型,必须指定 `trust_remote_code=True` ,以确保加载自定义代码时不会报错。 + +```python +model_path = '/root/autodl-tmp/PaddlePaddle/ERNIE-4.5-0.3B-PT' + +tokenizer = AutoTokenizer.from_pretrained(model_path, use_fast=False, trust_remote_code=True) + +model = AutoModelForCausalLM.from_pretrained(model_path, device_map="auto", + torch_dtype=torch.bfloat16, + trust_remote_code=True) +``` + +> 注意:此处要记得修改为自己的模型路径哦~ + +如果想要查看模型结构,可以打印模型: + +```python +print(model) +``` + +输出结果如下 + +```text +Ernie4_5_ForCausalLM( + (model): Ernie4_5_Model( + (embed_tokens): Embedding(103424, 1024) + (layers): ModuleList( + (0-17): 18 x Ernie4_5_DecoderLayer( + (self_attn): Ernie4_5_Attention( + (q_proj): Linear(in_features=1024, out_features=2048, bias=False) + (k_proj): Linear(in_features=1024, out_features=256, bias=False) + (v_proj): Linear(in_features=1024, out_features=256, bias=False) + (o_proj): Linear(in_features=2048, out_features=1024, bias=False) + (rotary_emb): Ernie4_5_RopeEmbedding() + ) + (mlp): Ernie4_5_MLP( + (gate_proj): Linear(in_features=1024, out_features=3072, bias=False) + (up_proj): Linear(in_features=1024, out_features=3072, bias=False) + (down_proj): Linear(in_features=3072, out_features=1024, bias=False) + (act_fn): SiLU() + ) + (input_layernorm): Ernie4_5_RMSNorm() + (post_attention_layernorm): Ernie4_5_RMSNorm() + (residual_add1): Ernie4_5_FusedDropoutImpl( + (dropout): Dropout(p=0.0, inplace=False) + ) + (residual_add2): Ernie4_5_FusedDropoutImpl( + (dropout): Dropout(p=0.0, inplace=False) + ) + ) + ) + (norm): Ernie4_5_RMSNorm() + ) + (lm_head): Ernie4_5_LMHead() +) +``` + +上面打印了 `Ernie4_5_ForCausalLM` 的模型结构, 可以看到里面的 `self_attn` 和 `mlp` 是两个主要的模块, 因此可以考虑将这两个模块作为 **LoRA** 微调 的 `target_modules` , 包括 `q_proj`, `k_proj`, `v_proj`, `o_proj` 以及 `gate_proj`、`up_proj` 和 `down_proj` 。 + +通常我们只对 `self_attn` 模块中的 `q_proj`, `k_proj`, `v_proj`, `o_proj`进行微调, 本教程里我们也将对这四个模块进行微调演示, 感兴趣的同学可以自行尝试添加对 `mlp` 中的三个 `proj` 模块进行微调。 + +## 6. 定义 LoraConfig + +`LoraConfig`类用于设置 LoRA 微调参数,虽然可以设置很多参数,但主要的参数没多少,简单讲一讲,感兴趣的同学可以直接看源码。 + +- `task_type`:模型类型 +- `target_modules`:需要训练的模型层的名字,主要就是 `attention`部分的层,不同的模型对应的层的名字不同,可以传入数组,也可以字符串,也可以正则表达式。 +- `r`:`LoRA`的秩,具体可以看 `LoRA`原理。 +- `lora_alpha`:`LoRA alaph` ,具体作用参见 `LoRA` 原理。 +- `lora_dropout`: `LoRA` 层的 `Dropout` 比例,用于防止过拟合,具体作用参见 `LoRA` 原理。 + +`LoRA`的缩放是啥嘞?当然不是 `r`(秩),这个缩放就是 `lora_alpha/r`, 在这个 `LoraConfig`中缩放就是 4 倍。 + +```python +from peft import LoraConfig, TaskType, get_peft_model + +config = LoraConfig( + task_type=TaskType.CAUSAL_LM, + target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"], # 可以自行添加更多微调的target_modules + inference_mode=False, # 训练模式 + r=8, # LoRA 秩 + lora_alpha=32, # LoRA alaph,具体作用参见 LoRA 原理 + lora_dropout=0.1 # Dropout 比例 +) +``` + +## 7. 自定义 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/ERNIE-4.5-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, + report_to="none", +) +``` + +## 8. SwanLab 可视化 + +### SwanLab 简介 + +[SwanLab](https://github.com/swanhubx/swanlab) 是一个开源的模型训练记录工具,面向 AI 研究者,提供了训练可视化、自动日志记录、超参数记录、实验对比、多人协同等功能。在 `SwanLab` 上,研究者能基于直观的可视化图表发现训练问题,对比多个实验找到研究灵感,并通过在线链接的分享与基于组织的多人协同训练,打破团队沟通的壁垒。 + +**为什么要记录训练** + +相较于软件开发,模型训练更像一个实验科学。一个品质优秀的模型背后,往往是成千上万次实验。研究者需要不断尝试、记录、对比,积累经验,才能找到最佳的模型结构、超参数与数据配比。在这之中,如何高效进行记录与对比,对于研究效率的提升至关重要。 + +### 实例化 SwanLabCallback + +建议先在 [SwanLab 官网](https://swanlab.cn/) 注册账号,然后在训练初始化阶段选择 + +`(2) Use an existing SwanLab account` 并使用 private API Key 登录 + +SwanLab 与 Transformers 已经做好了集成,用法是在 Trainer 的 callbacks 参数中添加 SwanLabCallback 实例,就可以自动记录超参数和训练指标,简化代码如下: + +```python +import swanlab +from swanlab.integration.transformers import SwanLabCallback + +# 实例化SwanLabCallback +swanlab_callback = SwanLabCallback( + project="self-llm", + experiment_name="ERNIE-4.5-0.5B-lora" +) +``` + +## 9. 使用 Trainer 训练 + +我们使用 `Trainer` 类来管理训练过程。`TrainingArguments` 用于设置训练参数,`Trainer` 则负责实际的训练逻辑。 + +```python +trainer = Trainer( + model=model, # 要训练的模型 + args=args, # 训练参数 + train_dataset=tokenized_id, # 训练数据集 + data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True), + callbacks=[swanlab_callback] + # 数据整理器 +) +trainer.train() # 开始训练 +``` +## 10. 训练结果演示 + +访问可视化训练过程:[ERNIE-4.5-0.5B-lora](https://swanlab.cn/@datawhale-kmno4/self-llm/runs/iu8cvv929sqfq7kg0wrn7/chart) + +在 SwanLab 上查看最终的训练结果: + +可以看到在 3 个 epoch 之后,微调后的 ERNIE-4.5-0.5B 的 loss 降低到了不错的水平。 + +![](./images/01-1.png) + +至此,你已经完成了 ERNIE-4.5-0.5B Lora 微调的训练!如果需要加强微调效果,可以尝试增加训练的数据量。 + +
+ +## 11. 加载 LoRA 权重推理 + +训练好了之后可以使用如下方式加载 `LoRA`权重进行推理: + +```python +from transformers import AutoModelForCausalLM, AutoTokenizer +import torch +from peft import PeftModel + +mode_path = '/root/autodl-tmp/PaddlePaddle/ERNIE-4.5-0.3B-PT' +lora_path = './output/ERNIE-4.5-lora/checkpoint-702' # 这里改称你的 lora 输出对应 checkpoint 地址 + +# 加载tokenizer +tokenizer = AutoTokenizer.from_pretrained(mode_path, trust_remote_code=True) + +# 加载模型 +model = AutoModelForCausalLM.from_pretrained(mode_path, device_map="auto",torch_dtype=torch.bfloat16, trust_remote_code=True) + +# 加载lora权重 +model = PeftModel.from_pretrained(model, model_id=lora_path) + +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], add_special_tokens=False, return_tensors="pt").to(model.device) + +# conduct text completion +generated_ids = model.generate( + model_inputs.input_ids, + max_new_tokens=1024 +) +output_ids = generated_ids[0][len(model_inputs.input_ids[0]):].tolist() + +# decode the generated ids +generate_text = tokenizer.decode(output_ids, skip_special_tokens=True).strip("\n") +print("generate_text:", generate_text) +``` + +```text +generate_text: 我是甄嬛,家父是大理寺少卿甄远道。 +``` + +> 注意修改为自己的模型路径哦~ + +> 如果显示 `Some parameters are on the meta device because they were offloaded to the cpu.` 的报错,需要将实例关机,重启后单独运行本条代码。 diff --git a/models/ERNIE-4.5/images/01-1.png b/models/ERNIE-4.5/images/01-1.png new file mode 100644 index 0000000..4168cc5 Binary files /dev/null and b/models/ERNIE-4.5/images/01-1.png differ