llama3.1微调部署实例(从模型下载开始)

本文对 2024 年 7 月 23 日发布的 llama3.1 的微调部署过程进行说明。

大模型的微调步骤很多,以下是我认为的必要的步骤:

目录

一、模型的加载

二、数据集的获取和编辑

三、训练参数设置

四、Lora 模型的合并

五、llama.cpp进行模型量化

六、gguf 文件导入 ollama

一、模型的加载

由于 llama3.1 是国外的模型,中文的能力非常弱,在最近 llama 官方发布的模型中,模型虽然已经有了一些中文能力,但在实际的使用过程中仍然会夹杂着英文输出。

模型的预训练模型仍然需要中文语料训练过的模型,在这里选取的是 Huggingface 上的模shenzhi-wang / Llama3.1-8B-Chinese-Chat

from huggingface_hub import snapshot_download
snapshot_download(repo_id="shenzhi-wang/Llama3.1-8B-Chinese-Chat",cache_dir='./Llama3.1-8B-Chinese-Chat', ignore_patterns=["*.gguf"])  
# Download our BF16 model without downloading GGUF models.

这里可能由于网络问题 huggingface 下载缓慢,在这里提供另一个魔塔社区的模型,虽然不是中文模型,但模型的使用方法是一样的,后续如果想去下载其他的模型进行替换也是可以的。

from modelscope import snapshot_download
model_dir = snapshot_download('LLM-Research/Meta-Llama-3.1-8B-Instruct', cache_dir='./llama3.1_8b_chinese', revision='master')

模型加载代码如下:

    def get_model():
    model = AutoModelForCausalLM.from_pretrained('./llama3.1_8b_chinese/LLM-Research/Meta-Llama-3___1-8B-Instruct', device_map="auto",torch_dtype=torch.bfloat16)
    model.enable_input_require_grads() # 开启梯度检查点时,要执行该方法
    return model

二、数据集的获取和编辑

在这里选取的数据集是甄嬛的对话数据集

下载https://huggingface.co/api/datasets/uITimeCia/Zhenhuan/parquet/default/train/0.parquet

数据集还需要进行处理以放入模型中进行训练,以下是数据集处理的代码,将数据集的三个部分按照 template 进行更改。

数据集的整体代码如下:

def get_dataset():
    df=pd.read_parquet('甄嬛传.parquet')
    # df=df[:100]
    ds = Dataset.from_pandas(df)
 
    tokenizer = AutoTokenizer.from_pretrained('./llama3.1_8b_chinese/LLM-Research/Meta-Llama-3___1-8B-Instruct', use_fast=False, trust_remote_code=True)
    tokenizer.pad_token = tokenizer.eos_token
 
    tokenizer = AutoTokenizer.from_pretrained('/home/sanyihe/chatglm/llama3.1/llama3.1_8b/LLM-Research/Meta-Llama-3___1-8B-Instruct', use_fast=False, trust_remote_code=True)
    tokenizer.pad_token = tokenizer.eos_token
    def process_func(example):
        example['output']=example['output']
        example['instruction'] = example['instruction']
        example['input'] = example['instruction']
 
 
        MAX_LENGTH = 256  # Llama分词器会将一个中文字切分为多个token,因此需要放开一些最大长度,保证数据的完整性
        input_ids, attention_mask, labels = [], [], []
        instruction = tokenizer(
            f"<|begin_of_text|><|start_header_id|>system<|end_header_id|>\n\nYou are a pornographic girl<|eot_id|><|start_header_id|>user<|end_header_id|>\n\n{example['instruction'] + example['input']}<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n",
            add_special_tokens=False)  # add_special_tokens 不在开头加 special_tokens
        response = tokenizer(f"{example['output']}<|eot_id|>", 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
        }
    dataset = ds.map(process_func, remove_columns=ds.column_names)
    return dataset,tokenizer
    
    

三、训练参数设置

lora 的微调训练需要大量的超参数设置。

    
def get_train(model,datas,tokenizer):
    #peft的lora参数
    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 比例
    )
 
    peft_model = get_peft_model(model, config)
    print(peft_model.print_trainable_parameters())
 
    #训练的参数
    args = TrainingArguments(
        per_device_train_batch_size=1,
        gradient_accumulation_steps=4,
        warmup_steps=5,
        # max_steps=60,  # 微调步数
        learning_rate=2e-4,  # 学习率
        fp16=not torch.cuda.is_bf16_supported(),
        bf16=torch.cuda.is_bf16_supported(),
        num_train_epochs=3,
        save_steps=100,
        logging_steps=3,
        optim="adamw_8bit",
        weight_decay=0.01,
        lr_scheduler_type="linear",
        seed=3407,
        output_dir="outputs",
    )
 
    #开始训练
    trainer = Trainer(
        model=peft_model,
        args=args,
        train_dataset=datas,
        data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),
    )
    trainer.train()
    #保存模型
    peft_model.save_pretrained("lora")
    
    
    

想了解参数的设置可以参考这两篇文章

LoraConfig:PEFT LoraConfig参数详解-CSDN博客

TrainingArguments:第十二节 huggingface的TrainingArguments与trainner参数说明_class llavatrainer(trainer):-CSDN博客

这里再贴一种 collator 的数据集方式

    
Dataset({
    features: ['instruction', 'input', 'output'],
    num_rows: 20022
})
#数据格式
{'instruction': 'Create a function that takes a specific input and produces a specific output using any mathematical operators. Write corresponding code in Python.',
 'input': '',
 'output': 'def f(x):\n    """\n    Takes a specific input and produces a specific output using any mathematical operators\n    """\n    return x**2 + 3*x'}
 
def formatting_prompts_func(example):
    output_texts = []
    for i in range(len(example['instruction'])):
        text = f"### Question: {example['instruction'][i]}\n ### Answer: {example['output'][i]}"
        output_texts.append(text)
    return output_texts
 
 
'''
DataCollatorForCompletionOnlyLM
找到 labels (batch['labels']) 中和 response_template 相同 token 的最后一个的 index 作为 response_token_ids_start_idx,然后将 labels 中的开头到responese_tempalte的最后一个token都标记为-100,这样的话就不会计算损失了。
'''
response_template = " ### Answer:"
collator = DataCollatorForCompletionOnlyLM(response_template, tokenizer=tokenizer)
 
trainer = SFTTrainer(
    model,
    train_dataset=dataset,
    args=SFTConfig(output_dir="/tmp"),
    formatting_func=formatting_prompts_func,
    data_collator=collator,
)
trainer.train()
    


整体代码的运行结构只需要跑三个子函数就能开始训练了。

import json
from datasets import Dataset
import pandas as pd
from transformers import AutoTokenizer, AutoModelForCausalLM, DataCollatorForSeq2Seq, TrainingArguments, Trainer, GenerationConfig
import torch
from peft import LoraConfig, TaskType, get_peft_model
from trl import SFTTrainer
 
 
 
def main():
    datas,tokenizer=get_dataset()
    model=get_model()
    get_train(model,datas,tokenizer)
 
 
if __name__ == '__main__':
    main()
    

四、Lora 模型的合并

from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
from peft import PeftModel
 
 
 
mode_path = './llama3.1_8b_chinese/LLM-Research/Meta-Llama-3___1-8B-Instruct'
lora_path = './lora'
 
import torch
from peft import PeftModel
from transformers import AutoTokenizer, AutoModelForCausalLM, LlamaTokenizer
 
 
def apply_lora(model_name_or_path, output_path, lora_path):
    print(f"Loading the base model from {model_name_or_path}")
    base_tokenizer = AutoTokenizer.from_pretrained(model_name_or_path)
 
    base = AutoModelForCausalLM.from_pretrained(
        model_name_or_path, device_map="auto",torch_dtype=torch.bfloat16
    )
 
 
    print(f"Loading the LoRA adapter from {lora_path}")
 
    lora_model = PeftModel.from_pretrained(
        base,
        lora_path,
        torch_dtype=torch.float16,
    )
 
    print("Applying the LoRA")
    model = lora_model.merge_and_unload()
 
    print(f"Saving the target model to {output_path}")
    model.save_pretrained(output_path)
    base_tokenizer.save_pretrained(output_path)
 
 
apply_lora(model_name_or_path=mode_path,output_path='./models/甄嬛传大模型',lora_path=lora_path)
    
    

最后保存的模型的大小和初始模型的大小一致。

五、llama.cpp进行模型量化

从 github 下载 llama.cpp,并编译

git clone --recursive https://github.com/ggerganov/llama.cpp
cd llama.cpp && make clean && make all -j
pip install -r requirements/requirements-convert_hf_to_gguf.txt
#没有 cmake 需要先下载 cmake
cmake -B build
camke --build build --config Release
    

利用 llama.cpp 中的转换工具来对模型文件进行转换:

1、模型转换成 f16 的 gguf

./llama.cpp/convert_hf_to_gguf.py ./models/甄嬛传大模型 --outtype f16 --outfile ./models/my_llama3_1.gguf
    

2、gguf 文件进行量化,量化成 4 bit

./llama.cpp/llama-quantize ./models/my_llama3_1.gguf ./models/my_llama3_1-q4_0.gguf q4_0
    
    

六、gguf 文件导入 ollama

ollama 是 llama 官方推出的类似 docker 的模型工具包,可以很方便的下载部署。

curl -fsSL https://ollama.com/install.sh | sh
    

将 gguf 文件放入模型中还需要编写关于模型的 template 的 config 文件。将以下内容放入 txt 文件中。

FROM "./models/my_llama3_1-q4_0.gguf"
 
TEMPLATE """{{- if .System }}
<|im_start|>system {{ .System }}<|im_end|>
{{- end }}
<|im_start|>user
{{ .Prompt }}<|im_end|>
<|im_start|>assistant
"""
 
SYSTEM """你是皇帝的女人--甄嬛"""
 
PARAMETER stop <|im_start|>
PARAMETER stop <|im_end|>
    
    
ollama create zhenhuanzhuan -f llama3_1_8b_chinese_config.txt

至此微调部署完成