LORA+llama.cpp模型微调全流程
准备阶段
1.下载基础大模型
新建一个download.py脚本
from modelscope import snapshot_download
#模型存放路径
model_path = '/root/autodl-tmp'
#模型名字
name = 'itpossible/Chinese-Mistral-7B-Instruct-v0.1'
model_dir = snapshot_download(name, cache_dir=model_path, revision='master')
2.下载数据集
下载地址
3.搭建lora环境
创建requirements.txt文件
transformers==4.40.0
streamlit==1.24.0
sentencepiece==0.1.99
accelerate==0.29.3
datasets
peft==0.10.0
创建虚拟环境
//创建虚拟环境
python -m venv loar4venv
// 激活虚拟环境
.\loar4venv\Scripts\activate
// 安装依赖
pip install -r requirements.txt
4.搭建CUDA - 图形处理器开发环境
下载cuda版本为 11.8
CUDA Toolkit Archive | NVIDIA Developer
测试是否安装成功
nvcc --version
Cuda compilation tools, release 11.8, V11.8.89 #安装成功后回复
安装 PyTorch
版本分别为
torch == 2.0.1+cu118
torchaudio == 2.0.2+cu118
torchvision == 0.15.2+cu118
pip install torch==2.0.1 torchvision==0.15.2 torchaudio==2.0.2 --index-url https://download.pytorch.org/whl/cu118
5.安装camke
官网地址:https://cmake.org/,点击Download。
6.下载llama.cpp源码
https://github.com/ggerganov/llama.cpp
下载解压缩后,形成第二个项目的文件,同样需要搭建环境, 安装requirements.txt的依赖
开始微调
1.运行训练脚本
创建Lora.py,复制以下代码
from datasets import Dataset
import pandas as pd
from transformers import (
AutoTokenizer,
AutoModelForCausalLM,
DataCollatorForSeq2Seq,
TrainingArguments,
Trainer, )
import torch,os
from peft import LoraConfig, TaskType, get_peft_model
import warnings
warnings.filterwarnings("ignore", category=UserWarning) # 忽略告警
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print("device="+device)
# 模型文件路径
model_path = r'/root/autodl-tmp/itpossible/Chinese-Mistral-7B-Instruct-v0.1'
# 训练过程数据保存路径
name = 'ruozhiba'
output_dir = f'./output/Mistral-7B-song'
#是否从上次断点处接着训练
train_with_checkpoint = True
# 加载tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_path, use_fast=False, trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token
#加载数据集
df = pd.read_json(f'./dataset/ruozhiba_qa.json')
ds = Dataset.from_pandas(df)
print(ds)
# 对数据集进行处理,需要将数据集的内容按大模型的对话格式进行处理
def process_func_mistral(example):
MAX_LENGTH = 384 # Llama分词器会将一个中文字切分为多个token,因此需要放开一些最大长度,保证数据的完整性
instruction = tokenizer(
f"<s>[INST] <<SYS>>\n\n<</SYS>>\n\n{example['instruction']+example['input']}[/INST]",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] # 因为pad_token_id咱们也是要关注的所以 补充为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
}
inputs_id = ds.map(process_func_mistral, remove_columns=ds.column_names)
#加载模型
model = AutoModelForCausalLM.from_pretrained(model_path, device_map=device, torch_dtype=torch.bfloat16, use_cache=False)
print(model)
model.enable_input_require_grads() # 开启梯度检查点时,要执行该方法
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 比例
)
model = get_peft_model(model, config)
model.print_trainable_parameters()
args = TrainingArguments(
output_dir=output_dir,
per_device_train_batch_size=2,
gradient_accumulation_steps=2,
logging_steps=20,
num_train_epochs=2,
save_steps=25,
save_total_limit=2,
learning_rate=1e-4,
save_on_each_node=True,
gradient_checkpointing=True
)
trainer = Trainer(
model=model,
args=args,
train_dataset=inputs_id,
data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True)
)
# 如果训练中断了,还可以从上次中断保存的位置继续开始训练
if train_with_checkpoint:
checkpoints = [os.path.join(output_dir, file) for file in os.listdir(output_dir) if 'checkpoint' in file]
if checkpoints:
last_checkpoint = max(checkpoints, key=os.path.getctime) # 找到最新的检查点
print(f"Resuming training from checkpoint: {last_checkpoint}")
trainer.train(resume_from_checkpoint=last_checkpoint)
else:
print("No checkpoint found in the output directory. Starting training from scratch.")
trainer.train()
else:
trainer.train()
运行
python Lora.py
2.将检查点checkpoint转换为lora
新建一个checkpoint_to_lora.py,将训练的checkpoint保存为lora
#将checkpoint转换为lora
from transformers import AutoModelForSequenceClassification,AutoTokenizer
import os
# 需要保存的lora路径
lora_path= "/root/lora/Mistral-7B-lora-song"
# 模型路径
model_path = '/root/autodl-tmp/itpossible/Chinese-Mistral-7B-Instruct-v0.1'
# 检查点路径
checkpoint_dir = './output/Mistral-7B-song'
checkpoint = [file for file in os.listdir(checkpoint_dir) if 'checkpoint-' in file][-1] #选择更新日期最新的检查点
print("checkpoint="+checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(f'./output/Mistral-7B-song/{checkpoint}')
# 保存模型
model.save_pretrained(lora_path)
tokenizer = AutoTokenizer.from_pretrained(model_path, use_fast=False, trust_remote_code=True)
tokenizer.pad_token = tokenizer.eos_token
# 保存tokenizer
tokenizer.save_pretrained(lora_path)
3.合并模型
新建一个merge.py文件,将基础模型和lora模型合并为一个新的模型文件
#合并模型
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
from peft import PeftModel
from peft import LoraConfig, TaskType, get_peft_model
model_path = '/root/autodl-tmp/itpossible/Chinese-Mistral-7B-Instruct-v0.1'
lora_path = "/root/lora/Mistral-7B-lora-song"
device = 'cuda' if torch.cuda.is_available() else 'cpu'
print("device="+device)
# 合并后的模型路径
output_path = r'/root/autodl-tmp/itpossible/merge'
# 等于训练时的config参数
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 比例
)
base = AutoModelForCausalLM.from_pretrained(model_path, torch_dtype=torch.bfloat16, low_cpu_mem_usage=True)
base_tokenizer = AutoTokenizer.from_pretrained(model_path)
lora_model = PeftModel.from_pretrained(
base,
lora_path,
torch_dtype=torch.float16,
config=config
)
model = lora_model.merge_and_unload()
model.save_pretrained(output_path)
base_tokenizer.save_pretrained(output_path)
4.量化模型
转换模型文件需要在llamaCpp的环境下运行
需要将多个safetensors合并为一个bin
激活环境后运行convert.py
执行转换脚本
python convert.py /root/autodl-tmp/itpossible/merge --outtype f16 --outfile /root/autodl-tmp/itpossible/convert.bin
可以得到一个convert.bin文件,这之后merge文件就没用了
开始编译
# 进入到llm/llama.cpp目录
cd llm/llama.cpp
#创建build文件夹
mkdir build
#进入build
cd build
# 构建
cmake ..
cmake --build . --config Release
编译成功后在llamaCpp\build\bin\Release生成llama-quantize.exe
量化模型
量化convert.bin
bin/Release/quantize.exe D:\root\autodl-tmp\itpossible\convert.bin D:\root\autodl-tmp\itpossible\quantized.bin q4_0
官方文档提供了多种量化格式,常用的就是q4_0。
分块量化(Block-wise Quantization)
- q4_k_m:4位量化,中等优化级别。
- q4_k_s:4位量化,轻量化级别。
- q5_k_m:5位量化,中等优化级别。
- q5_k_s:5位量化,轻量化级别。
- q6_k:6位量化。
- q8_0:8位量化。
K-Quantization(混合精度量化)
- q2_k:2位量化,混合精度优化。
- q2_k_s:2位量化,轻量化版本。
- q3_k_m:3位量化,中等优化级别。
- q3_k_s:3位量化,轻量化版本。
- q3_k_l:3位量化,高优化级别。
其他量化格式
- q4_0:4位量化,朴素方法。
- q4_1:4位量化,改进型朴素方法。
- q5_0:5位量化,朴素方法。
- q5_1:5位量化,改进型朴素方法。
- f16:半精度浮点(16位)。
- bf16:脑浮点(16位)。
- f32:全精度浮点(32位)。
特殊量化格式
- iq2_xxs:2位量化,极小模型。
- iq2_xs:2位量化,较小模型。
- iq2_s:2位量化,标准模型。
- iq2_m:2位量化,中等模型。
- iq3_xxs:3位量化,极小模型。
- iq3_xs:3位量化,较小模型。
- iq3_s:3位量化,标准模型。
- iq3_m:3位量化,中等模型。
- iq4_nl:4位非线性量化。
- iq4_xs:4位量化,较小模型。
5.ollama使用模型
需要将quantized.bin文件制作为ollama可以使用的模型
创建Modelfile 文件
创建一个test.Modelfile 文件,添加的内容如下
FROM D:\huggingface\itpossible\quantized.bin
TEMPLATE "[INST] {{ .Prompt }} [/INST]"
创建模型
指定生成的模型路径
设置模型文件保存位置,打开系统环境变量配置,添加一个环境变量OLLAMA_MODELS=D:\huggingface\ollama(自己指定任意一个文件夹路径),然后点确定。
运行
//ollama create 模型名字 -f Modelfile文件路径
ollama create chinese_song_q4 -f C:\Users\Administrator\Desktop\ollama\test.Modelfile
生成的模型