当前位置:   article > 正文

ollama 使用自己的微调模型_ollama 微调

ollama 微调

目录

前言

一、微调大模型

1.LORA微调

1.1 选择基础大模型

1.2 下载基础大模型

2.选择数据集

3.lora微调

3.1安装依赖

3.2编写训练脚本

3.2.1 指定模型路径

3.2.2加载tokenizer

3.2.3加载数据集

3.2.4处理数据集

3.2.5加载模型

3.2.6设置Lora训练参数

3.2.7设置训练参数

3.2.8开始训练

3.2.9完整的训练脚本

4.将checkpoint转换为lora

5.合并模型

二、量化模型

1.转换模型文件

2.量化模型

三、ollama 创建模型

四、总结


前言

上一期,介绍了如何使用ollama自定义模型,但是创建的模型都是未经微调,对话效果不够专业。这一期将介绍,如何用ollama创建微调过的模型,让对话效果更符合特定化需要。

对话数据原文为:

{
  "instruction": "樟脑丸是我吃过最难吃的硬糖有奇怪的味道怎么还有人买",
  "input": "",
  "output": "樟脑丸并不是硬糖,而是一种常见的驱虫药,不能食用。虽然它的味道可能不太好,但是由于其有效的驱虫效果,所以仍然有很多人会购买。"
}

1.基础模型,未经过微调,使用ollama+openwebui的对话效果:

2.基础模型+lora微调,再用ollama创建模型,对话效果如下:

3.通过对比发现,微调后的模型达到了预期效果。

一、微调大模型

1.LORA微调

微调大模型的方法,这里不展开说,我使用的lora微调方法。微调大模型需要比较高的显存,推荐在云服务器上进行训练,系统环境为linux。训练方法参考https://github.com/datawhalechina/self-llm

要想体验和stable diffussion一样的ui训练界面,可以参考https://github.com/hiyouga/LLaMA-Factory

1.1 选择基础大模型

基础大模型我选择Chinese-Mistral-7B-Instruct-v0.1,模型文件可以在https://huggingface.co/,或者huggingface镜像网站HF-Mirror - Huggingface 镜像站,或者魔搭社区进行下载,我用魔搭社区的python脚本进行下载,执行前需要先运行pip install modelscope。

1.2 下载基础大模型

新建一个download.py脚本

  1. from modelscope import snapshot_download
  2. #模型存放路径
  3. model_path = '/root/autodl-tmp'
  4. #模型名字
  5. name = 'itpossible/Chinese-Mistral-7B-Instruct-v0.1'
  6. model_dir = snapshot_download(name, cache_dir=model_path, revision='master')

2.选择数据集

微调大模型要想获得比较好的效果,拥有高质量的数据集是关键。可以选择用网上开源的,或者是自己制作。以中文数据集弱智吧为例,约1500条对话数据,数据集可以从https://huggingface.co/,或者huggingface镜像网站HF-Mirror - Huggingface 镜像站进行下载。我是手动下载后,上传到服务器。

3.lora微调

3.1安装依赖

我是miniconda创建的python环境,python版本=3.10。

依赖文件requirements.txt内容如下:

  1. transformers
  2. streamlit==1.24.0
  3. sentencepiece==0.1.99
  4. accelerate==0.29.3
  5. datasets
  6. peft==0.10.0
pip install - r requirements.txt

3.2编写训练脚本

3.2.1 指定模型路径
  1. from datasets import Dataset
  2. import pandas as pd
  3. from transformers import (
  4. AutoTokenizer,
  5. AutoModelForCausalLM,
  6. DataCollatorForSeq2Seq,
  7. TrainingArguments,
  8. Trainer, )
  9. import torch,os
  10. from peft import LoraConfig, TaskType, get_peft_model
  11. import warnings
  12. warnings.filterwarnings("ignore", category=UserWarning) # 忽略告警
  13. device = 'cuda' if torch.cuda.is_available() else 'cpu'
  14. # 模型文件路径
  15. model_path = r'/root/autodl-tmp/itpossible/Chinese-Mistral-7B-Instruct-v0.1'
  16. # 训练过程数据保存路径
  17. name = 'ruozhiba'
  18. output_dir = f'./output/Mistral-7B-{name}'
  19. #是否从上次断点处接着训练,如果需要从上次断点处继续训练,值应为True
  20. train_with_checkpoint = False
3.2.2加载tokenizer
  1. # 加载tokenizer
  2. tokenizer = AutoTokenizer.from_pretrained(model_path, use_fast=False, trust_remote_code=True)
  3. tokenizer.pad_token = tokenizer.eos_token
3.2.3加载数据集
  1. df = pd.read_json(f'./dataset/{name}.json')
  2. ds = Dataset.from_pandas(df)
  3. print(ds)

3.2.4处理数据集

需要将数据集的内容按大模型的对话格式进行处理,不同的模型,对话格式不一样,比如qwen1.5、llama3的对话格式都不一样。以下面这一条对话数据为例。

处理前的内容:

  1. {
  2. "instruction": "只剩一个心脏了还能活吗?",
  3. "input": "",
  4. "output": "能,人本来就只有一个心脏。"
  5. }

处理后,喂给大模型的内容:

  1. <s>[INST] <<SYS>>
  2. <</SYS>>
  3. 只剩一个心脏了还能活吗? [/INST] 能,人本来就只有一个心脏。 </s>
  1. # 对数据集进行处理,需要将数据集的内容按大模型的对话格式进行处理
  2. def process_func_mistral(example):
  3. MAX_LENGTH = 384 # Llama分词器会将一个中文字切分为多个token,因此需要放开一些最大长度,保证数据的完整性
  4. instruction = tokenizer(
  5. f"<s>[INST] <<SYS>>\n\n<</SYS>>\n\n{example['instruction']+example['input']}[/INST]",add_special_tokens=False) # add_special_tokens 不在开头加 special_tokens
  6. response = tokenizer(f"{example['output']}", add_special_tokens=False)
  7. input_ids = instruction["input_ids"] + response["input_ids"] + [tokenizer.pad_token_id]
  8. attention_mask = instruction["attention_mask"] + response["attention_mask"] + [1] # 因为pad_token_id咱们也是要关注的所以 补充为1
  9. labels = [-100] * len(instruction["input_ids"]) + response["input_ids"] + [tokenizer.pad_token_id]
  10. if len(input_ids) > MAX_LENGTH: # 做一个截断
  11. input_ids = input_ids[:MAX_LENGTH]
  12. attention_mask = attention_mask[:MAX_LENGTH]
  13. labels = labels[:MAX_LENGTH]
  14. return {
  15. "input_ids": input_ids,
  16. "attention_mask": attention_mask,
  17. "labels": labels
  18. }
  19. inputs_id = ds.map(process_func_mistral, remove_columns=ds.column_names)
3.2.5加载模型
  1. #加载模型
  2. model = AutoModelForCausalLM.from_pretrained(model_path, device_map=device, torch_dtype=torch.bfloat16, use_cache=False)
  3. model.enable_input_require_grads() # 开启梯度检查点时,要执行该方法
  4. print(model)

模型信息如下,其中q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj这几层权重是我们要微调学习的参数。

3.2.6设置Lora训练参数
  1. config = LoraConfig(
  2. task_type=TaskType.CAUSAL_LM,
  3. target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
  4. inference_mode=False, # 训练模式
  5. r=8, # Lora 秩
  6. lora_alpha=32, # Lora alaph,具体作用参见 Lora 原理
  7. lora_dropout=0.1 # Dropout 比例
  8. )
3.2.7设置训练参数
  1. model = get_peft_model(model, config)
  2. model.print_trainable_parameters()
  3. args = TrainingArguments(
  4. output_dir=output_dir,
  5. per_device_train_batch_size=2,
  6. gradient_accumulation_steps=2,
  7. logging_steps=20,
  8. num_train_epochs=2,
  9. save_steps=25,
  10. save_total_limit=2,
  11. learning_rate=1e-4,
  12. save_on_each_node=True,
  13. gradient_checkpointing=True
  14. )

输出的信息显示:trainable params: 20,971,520 || all params: 7,523,799,040 || trainable%: 0.27873578080043987

可以看到模型总共参数约75亿个,需要训练的参数约2千万个,只微调了约0.3%的参数。

3.2.8开始训练
  1. trainer = Trainer(
  2. model=model,
  3. args=args,
  4. train_dataset=inputs_id,
  5. data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),
  6. )
  7. # 如果训练中断了,还可以从上次中断保存的位置继续开始训练
  8. if train_with_checkpoint:
  9. checkpoint = [file for file in os.listdir(output_dir) if 'checkpoint' in file][-1]
  10. last_checkpoint = f'{output_dir}/{checkpoint}'
  11. print(last_checkpoint)
  12. trainer.train(resume_from_checkpoint=last_checkpoint)
  13. else:
  14. trainer.train()
3.2.9完整的训练脚本
  1. from datasets import Dataset
  2. import pandas as pd
  3. from transformers import (
  4. AutoTokenizer,
  5. AutoModelForCausalLM,
  6. DataCollatorForSeq2Seq,
  7. TrainingArguments,
  8. Trainer, )
  9. import torch,os
  10. from peft import LoraConfig, TaskType, get_peft_model
  11. import warnings
  12. warnings.filterwarnings("ignore", category=UserWarning) # 忽略告警
  13. device = 'cuda' if torch.cuda.is_available() else 'cpu'
  14. # 模型文件路径
  15. model_path = r'/root/autodl-tmp/itpossible/Chinese-Mistral-7B-Instruct-v0.1'
  16. # 训练过程数据保存路径
  17. name = 'ruozhiba'
  18. output_dir = f'./output/Mistral-7B-{name}'
  19. #是否从上次断点处接着训练
  20. train_with_checkpoint = True
  21. # 加载tokenizer
  22. tokenizer = AutoTokenizer.from_pretrained(model_path, use_fast=False, trust_remote_code=True)
  23. tokenizer.pad_token = tokenizer.eos_token
  24. #加载数据集
  25. df = pd.read_json(f'./dataset/{name}.json')
  26. ds = Dataset.from_pandas(df)
  27. print(ds)
  28. # 对数据集进行处理,需要将数据集的内容按大模型的对话格式进行处理
  29. def process_func_mistral(example):
  30. MAX_LENGTH = 384 # Llama分词器会将一个中文字切分为多个token,因此需要放开一些最大长度,保证数据的完整性
  31. instruction = tokenizer(
  32. f"<s>[INST] <<SYS>>\n\n<</SYS>>\n\n{example['instruction']+example['input']}[/INST]",add_special_tokens=False) # add_special_tokens 不在开头加 special_tokens
  33. response = tokenizer(f"{example['output']}", add_special_tokens=False)
  34. input_ids = instruction["input_ids"] + response["input_ids"] + [tokenizer.pad_token_id]
  35. attention_mask = instruction["attention_mask"] + response["attention_mask"] + [1] # 因为pad_token_id咱们也是要关注的所以 补充为1
  36. labels = [-100] * len(instruction["input_ids"]) + response["input_ids"] + [tokenizer.pad_token_id]
  37. if len(input_ids) > MAX_LENGTH: # 做一个截断
  38. input_ids = input_ids[:MAX_LENGTH]
  39. attention_mask = attention_mask[:MAX_LENGTH]
  40. labels = labels[:MAX_LENGTH]
  41. return {
  42. "input_ids": input_ids,
  43. "attention_mask": attention_mask,
  44. "labels": labels
  45. }
  46. inputs_id = ds.map(process_func_mistral, remove_columns=ds.column_names)
  47. #加载模型
  48. model = AutoModelForCausalLM.from_pretrained(model_path, device_map=device, torch_dtype=torch.bfloat16, use_cache=False)
  49. print(model)
  50. model.enable_input_require_grads() # 开启梯度检查点时,要执行该方法
  51. config = LoraConfig(
  52. task_type=TaskType.CAUSAL_LM,
  53. target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
  54. inference_mode=False, # 训练模式
  55. r=8, # Lora 秩
  56. lora_alpha=32, # Lora alaph,具体作用参见 Lora 原理
  57. lora_dropout=0.1 # Dropout 比例
  58. )
  59. model = get_peft_model(model, config)
  60. model.print_trainable_parameters()
  61. args = TrainingArguments(
  62. output_dir=output_dir,
  63. per_device_train_batch_size=2,
  64. gradient_accumulation_steps=2,
  65. logging_steps=20,
  66. num_train_epochs=2,
  67. save_steps=25,
  68. save_total_limit=2,
  69. learning_rate=1e-4,
  70. save_on_each_node=True,
  71. gradient_checkpointing=True
  72. )
  73. trainer = Trainer(
  74. model=model,
  75. args=args,
  76. train_dataset=inputs_id,
  77. data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),
  78. )
  79. # 如果训练中断了,还可以从上次中断保存的位置继续开始训练
  80. if train_with_checkpoint:
  81. checkpoint = [file for file in os.listdir(output_dir) if 'checkpoint' in file][-1]
  82. last_checkpoint = f'{output_dir}/{checkpoint}'
  83. print(last_checkpoint)
  84. trainer.train(resume_from_checkpoint=last_checkpoint)
  85. else:
  86. trainer.train()

4.将checkpoint转换为lora

新建一个checkpoint_to_lora.py,将训练的checkpoint保存为lora

  1. from transformers import AutoModelForSequenceClassification,AutoTokenizer
  2. import os
  3. # 需要保存的lora路径
  4. lora_path= "/root/lora/Mistral-7B-lora-ruozhiba"
  5. # 模型路径
  6. model_path = '/root/autodl-tmp/itpossible/Chinese-Mistral-7B-Instruct-v0.1'
  7. # 检查点路径
  8. checkpoint_dir = '/root/output/Mistral-7B-ruozhiba'
  9. checkpoint = [file for file in os.listdir(checkpoint_dir) if 'checkpoint-' in file][-1] #选择更新日期最新的检查点
  10. model = AutoModelForSequenceClassification.from_pretrained(f'/root/output/Mistral-7B-ruozhiba/{checkpoint}')
  11. # 保存模型
  12. model.save_pretrained(lora_path)
  13. tokenizer = AutoTokenizer.from_pretrained(model_path, use_fast=False, trust_remote_code=True)
  14. tokenizer.pad_token = tokenizer.eos_token
  15. # 保存tokenizer
  16. tokenizer.save_pretrained(lora_path)

5.合并模型

新建一个merge.py文件,将基础模型和lora模型合并为一个新的模型文件

  1. from transformers import AutoModelForCausalLM, AutoTokenizer
  2. import torch
  3. from peft import PeftModel
  4. from peft import LoraConfig, TaskType, get_peft_model
  5. model_path = '/root/autodl-tmp/itpossible/Chinese-Mistral-7B-Instruct-v0.1'
  6. lora_path = "/root/lora/Mistral-7B-lora-ruozhiba"
  7. device = 'cuda' if torch.cuda.is_available() else 'cpu'
  8. # 合并后的模型路径
  9. output_path = r'/root/autodl-tmp/itpossible/merge'
  10. # 等于训练时的config参数
  11. config = LoraConfig(
  12. task_type=TaskType.CAUSAL_LM,
  13. target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
  14. inference_mode=False, # 训练模式
  15. r=8, # Lora 秩
  16. lora_alpha=32, # Lora alaph,具体作用参见 Lora 原理
  17. lora_dropout=0.1 # Dropout 比例
  18. )
  19. base = AutoModelForCausalLM.from_pretrained(model_path, torch_dtype=torch.bfloat16, low_cpu_mem_usage=True)
  20. base_tokenizer = AutoTokenizer.from_pretrained(model_path)
  21. lora_model = PeftModel.from_pretrained(
  22. base,
  23. lora_path,
  24. torch_dtype=torch.float16,
  25. config=config
  26. )
  27. model = lora_model.merge_and_unload()
  28. model.save_pretrained(output_path)
  29. base_tokenizer.save_pretrained(output_path)

二、量化模型

1.转换模型文件

基础模型和lora合并后的模型,仍然为多个safetensors,需要将多个safetensors合并为一个bin。合并方法需要使用llama.cpp中convert.py转换脚本,github地址https://github.com/ggerganov/llama.cpp。转换方法详见ollama 使用自定义大模型-CSDN博客

python convert.py /root/autodl-tmp/itpossible/merge --outtype f16 --outfile /root/autodl-tmp/itpossible/convert.bin

执行转换后,可以得到一个convert.bin文件,约等于14G。为了节约存储空间,之前的合并模型文件夹可以删除了。

rm -rf /root/autodl-tmp/itpossible/merge

2.量化模型

对llama.cpp项目编译后,有个quantize可执行文件

/root/ollama/llm/llama.cpp/quantize /root/autodl-tmp/itpossible/convert.bin q5_k_m

得到文件ggml-model-Q5_K_M.gguf,量化参数有多个标准可以选择,选择不同的量化,模型的推理效果不一样。

三、ollama 创建模型

使用ollama,根据ggml-model-Q5_K_M.gguf创建模型,方法详见ollama 使用自定义大模型_ollama 上面好用的大模型-CSDN博客

四、总结

1.我还分别使用了llama3-8b,qwen1.5-1.8b进行lora微调,但是在使用llama.cpp进行模型转换环节,出现了NotImplementedError: BPE pre-tokenizer was not recognized - update get_vocab_base_pre(),从官网查询了很多issues,暂未找到解决的办法,所以目前只有Chinese-Mistral-7B-Instruct-v0.1成功了。

2.ollama的modelfile中还提供了添加ADAPTER的方法,也就是将lora单独作为ADAPTER,试了一下,模型推理效果不正确,暂未找到原因。目前,试验成功的方法只有这一个。将基础模型+lora模型合并后,再用ollama创建模型,推理效果达到了预期。

3.我只训练了不到半小时,要想微调后的对话效果更好,需要更多的数据集,和更长时间的训练。

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号