LLaMA-Factory学习笔记(1)——采用LORA对大模型进行SFT并采用vLLM部署的全流程

devtools/2024/11/14 2:23:01/

该博客是我根据自己学习过程中的思考与总结来写作的,由于初次学习,可能会有错误或者不足的地方,望批评与指正。

1. 安装

1.1 LLaMA-Factory安装

安装可以参考官方 readme (https://github.com/hiyouga/LLaMA-Factory/blob/main/README_zh.md)

懒得跳转网页的也可以直接复制下面的内容:

git clone https://github.com/hiyouga/LLaMA-Factory.git
cd LLaMA-Factory
pip install -e .

1.2 下载LLM

huggingface或者镜像源(https://hf-mirror.com/)下载想要微调的模型。

PS:可以不用下载到本地,不过因为我们服务器不允许挂梯子,而且担心网络不稳定,所以我都是下载到本地的。

2. 准备训练数据

这应该是用LLaMA-Factory微调整个大模型中为数不多的需要写代码的部分。数据集的说明请参考:https://github.com/hiyouga/LLaMA-Factory/blob/main/data/README_zh.md。

由于我们是采用SFT的方式来构建数据集,所以简单点可以直接看https://github.com/hiyouga/LLaMA-Factory/blob/main/data/alpaca_zh_demo.json 这个json文件的格式,将数据集构建为上述json格式即可,即:

[{"instruction": "人类指令(必填)","input": "人类输入(选填)","output": "模型回答(必填)","system": "系统提示词(选填)","history": [["第一轮指令(选填)", "第一轮回答(选填)"],["第二轮指令(选填)", "第二轮回答(选填)"]]}
]

数据为一个列表,列表中每个元素为一个字典。如果是单轮对话则无需 history

当数据集构建完成后,可以放在data/路径下,也可以放在其他位置。

3. 配置dataset_info.json文件

将数据集构建完成后,需要在data/目录下找到dataset_info.json文件,并在这个文件中配置数据集的信息(个人觉得这一步类似于django中的注册url)。

假设构建的数据集名字为input_data.json,那么在dataset_info.json需要加如下的字典:

'数据集名称': {"file_name": "input_data.json","columns": {"prompt": "instruction","query": "input","response": "output"}},

这里数据集名称可以自行定义,只需要在后续写yaml文件时把这个名称写进去就行。这里的file_name要与数据集的名称相同,如果文件放在了data/路径下,只需要数据集名称就行,而如果文件没有放在该路径下,则需要在这里指定路径。

4. 微调模型

微调模型可以选用启动网页服务来进行微调,执行以下命令启动网页服务:

llamafactory-cli webui

当然我更喜欢用配置文件的方式来微调模型。新建一个 yaml 文件,假设名为train_qwen_lora_sft.yaml,可以在该文件中定义具体的训练设置。当然很多朋友不知道有哪些参数需要设置,可以查看github中的demo进行配置(https://github.com/hiyouga/LLaMA-Factory/blob/main/examples/train_lora/llama3_lora_sft.yaml),具体而言:

### model
model_name_or_path: Qwen2-1.5B-Instruct### method
stage: sft
do_train: true
finetuning_type: lora
lora_target: all### dataset
dataset: input_data
template: qwen
cutoff_len: 1024
max_samples: 1000
overwrite_cache: true
preprocessing_num_workers: 16### output
output_dir: saves/llama3-8b/lora/sft
logging_steps: 10
save_steps: 500
plot_loss: true
overwrite_output_dir: true### train
per_device_train_batch_size: 1
gradient_accumulation_steps: 8
learning_rate: 1.0e-4
num_train_epochs: 3.0
lr_scheduler_type: cosine
warmup_ratio: 0.1
bf16: true
ddp_timeout: 180000000### eval
val_size: 0.1
per_device_eval_batch_size: 1
eval_strategy: steps
eval_steps: 500

这里要注意的有几个参数:

  • model_name_or_path: 这个是需要微调的模型,如果下载到了本地,则指定本地路径;如果没有下载到本地,就在huggingface上找路径。
  • dataset:这个是数据集的名称,即在第三步中配置的数据集名称。这里支持多个数据集进行微调,如果要设置多个数据集,那么用逗号隔开,即数据集名称1,数据集名称2
  • template: 用的什么模型就用什么模板,比如用的通义千问就设置qwen。template的名字可以参考https://zhuanlan.zhihu.com/p/689333581。
  • output_dir:这里是输出的文件路径,需要注意的是,由于采用的是LORA进行微调,所以这里输出的文件是LORA的参数,在实际部署的时候最好将LORA参数和原模型参数进行合并。具体合并的方式下一步会讲。
  • num_samples:如果想用数据集中所有数据进行训练,这个值可以设置的很大,比如999999,否则这个值如果小于数据集大小的话只会选择部分数据进行训练。
  • learning_rate:不只是learning_rate,所有的数字都可能会面临一个问题,即如果采用科学计数法(如1e-6),会报错,详见:https://blog.csdn.net/qq_35357274/article/details/143615453,解决方式就是用1.0e-6或者0.000001
  • eval:如果不需要验证这一坨其实可以不需要的。

微调的时候有可能显卡被人占用了,需要指定在哪几张卡上进行微调,所以用如下的命令进行微调:

CUDA_VISIBLE_DEVICES=0 llamafactory-cli train yaml路径

微调完成后,在output_dir路径下会有个文件夹,即LORA的参数文件,接着需要合并文件。

5. 合并文件

合并文件的yaml文件(注意不是训练的yaml文件,需要新建一个,我最开始以为两个是一个文件)可以参考https://github.com/hiyouga/LLaMA-Factory/blob/main/examples/merge_lora/qwen2vl_lora_sft.yaml,具体而言:

### Note: DO NOT use quantized model or quantization_bit when merging lora adapters### model
model_name_or_path: Qwen/Qwen2-VL-7B-Instruct
adapter_name_or_path: saves/qwen2_vl-7b/lora/sft
template: qwen2_vl
finetuning_type: lora### export
export_dir: models/qwen2_vl_lora_sft
export_size: 2
export_device: cpu
export_legacy_format: false
  • model_name_or_path:如上面一样是原始模型的路径。
  • adapter_name_or_pathLORA的参数位置,即第四步中output_dir的路径。
  • export_dir:合并后的文件路径。
  • export_size:单个文件的最大大小(GB)。

运行命令:

llamafactory-cli export yaml路径

6. 部署

我这里选用的vLLM作为部署框架,有两种部署方式。如果是自己做实验的话可以离线部署,如果是上线的话建议用在线部署。

6.1 离线部署

离线部署即自己写个类或者方法,调用微调后的模型,代码如下:

import os
import torch
os.environ["CUDA_VISIBLE_DEVICES"] = "2"
from vllm import LLM, SamplingParamsclass Detect:def __init__(self, model_path, temperature=0.8, top_p=0.95):""":param model_path: str:param temperature: float取值范围: [0, +∞]- 当 temperature 设为 0 时,模型总是选择具有最高概率的单词,这会导致生成的结果非常确定性,缺乏随机变化。- 当 temperature 接近 1 时,模型的选择更加随机,这可以增加生成文本的多样性。- 高于 1 的值可能会导致生成的文本更加随机,有时可能失去连贯性- 注: 如果生成的内容太多就调低这个值:param top_p: float取值范围: (0, 1]- top_p 参数表示从候选词汇中选取累积概率总和达到或超过 p 的词汇集合,然后从中随机选取下一个词汇。这种方式可以减少 temperature 为高值时可能出现的无意义输出。- 当 top_p 设置为接近 1 的值时,几乎所有的候选词汇都会被考虑,这类似于 temperature 接近 1 的情况。- 当 top_p 设置为较低值时,只有最有可能出现的几个词汇会被选中,这样可以减少输出的不确定性。- 注: 如果生成的内容太多就调低这个值"""if not isinstance(temperature, (int, float)) or not isinstance(top_p, (int, float)):raise ValueError('变量 "temperature" 和 "top_p" 必须为整型或浮点数')if temperature < 0:raise ValueError('变量 "temperature" 的取值范围为[0, +∞]')if top_p <= 0 or top_p > 1:raise ValueError('变量 "top_p" 的取值范围为(0, 1]')self.template = 'hi'self.llm = LLM(model=model_path, dtype=torch.bfloat16)self.tokenizer = self.llm.get_tokenizer()self.sampling_params = SamplingParams(temperature=temperature, top_p=top_p)def detect(self, input_texts):prompts = []for input_text in input_texts:prompt = [{'role': 'system', 'content': self.template},{'role': 'user', 'content': input_text + '\n输出:'}]prompts.append(prompt)prompts = self.tokenizer.apply_chat_template(prompts, add_generation_prompt=True, tokenize=False)outputs = self.llm.generate(prompts, self.sampling_params)return [output.outputs[0].text for output in outputs]

这里model_path即微调后的模型路径。还需注意的是,如果服务器足够垃圾,有可能self.llm = LLM(model=model_path, dtype=torch.bfloat16)会报错,因为老的服务器有可能不支持bfloat16,改为float16就行。

离线的代码如果部署到线上,在多线程调用的情况下,很有可能会触发cuda显存不够的错误,所以部署项目的时候通常采用在线部署的方式。

6.2 在线部署

与离线部署不同,采用在线部署首先需要启动服务。

CUDA_VISIBLE_DEVICES=2 python3 -m vllm.entrypoints.openai.api_server --model model_path --served-model-name qwen --trust-remote-code --host 0.0.0.0 --port 8080 --dtype bfloat16

这里model_path是微调后的模型路径;host是主机,默认为localhost,如果项目是在容器内的话,这里需要自行设置IP地址;port是端口号,默认为8000。

当启动服务后,会有这么一个提示信息:
提示信息
这个IP地址即服务的地址,就可以采用访问http的形式来调用模型。

有的人会使用curl http的形式来调用接口,我选用的是采用python脚本实现,具体代码如下:

from openai import OpenAIdef detect(input_texts, template, port='http://localhost:8000/v1', temperature=0.8, top_p=0.95):""":param input_texts: list输入文本:param template: str提示模板:param port: str端口号:param temperature: float取值范围: [0, +∞]- 当 temperature 设为 0 时,模型总是选择具有最高概率的单词,这会导致生成的结果非常确定性,缺乏随机变化。- 当 temperature 接近 1 时,模型的选择更加随机,这可以增加生成文本的多样性。- 高于 1 的值可能会导致生成的文本更加随机,有时可能失去连贯性- 注: 如果生成的内容太多就调低这个值:param top_p: float取值范围: (0, 1]- top_p 参数表示从候选词汇中选取累积概率总和达到或超过 p 的词汇集合,然后从中随机选取下一个词汇。这种方式可以减少 temperature 为高值时可能出现的无意义输出。- 当 top_p 设置为接近 1 的值时,几乎所有的候选词汇都会被考虑,这类似于 temperature 接近 1 的情况。- 当 top_p 设置为较低值时,只有最有可能出现的几个词汇会被选中,这样可以减少输出的不确定性。- 注: 如果生成的内容太多就调低这个值:return:"""if not isinstance(temperature, (int, float)) or not isinstance(top_p, (int, float)):raise ValueError('变量 "temperature" 和 "top_p" 必须为整型或浮点数')if temperature < 0:raise ValueError('变量 "temperature" 的取值范围为[0, +∞]')if top_p <= 0 or top_p > 1:raise ValueError('变量 "top_p" 的取值范围为(0, 1]')openai_api_key = "EMPTY"openai_api_base = portclient = OpenAI(api_key=openai_api_key,base_url=openai_api_base,)outputs = []for input_text in input_texts:chat_response = client.chat.completions.create(model="qwen",messages=[{"role": "system", "content": template},{"role": "user", "content": input_text},],temperature=temperature,top_p=top_p,)outputs.append(chat_response.choices[0].message.content)return outputs

这个代码里面port即上面控制台信息中返回的IP地址,传入即可。

7. 参考

[1] https://github.com/hiyouga/LLaMA-Factory/tree/main
[2] llama-factory SFT系列教程 (二),大模型在自定义数据集 lora 训练与部署
[3] VLLM 把模型部署成 openai API server 形式
[4] https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct
[5] https://github.com/echonoshy/cgft-llm/blob/master/llama-factory/README.md


http://www.ppmy.cn/devtools/133808.html

相关文章

19.(开发工具篇mysql库)mysql锁表问题解决

1&#xff1a;查看锁表情况 show OPEN TABLES where In_use > 0; 2&#xff1a;查看所有进程命令 show processlist 3&#xff1a;杀对应进程&#xff08;通过host&#xff0c;db找对应的ID&#xff09; kill 57303

开源大模型推理引擎现状及常见推理优化方法总结

原文&#xff1a;https://zhuanlan.zhihu.com/p/755874470 前言 前一段时间sglang-v0.3.0和vllm-v0.6.0前后脚发布之后&#xff0c;就一直想总结梳理一下现在主流的大模型推理引擎。因为我觉得这也算是一个有意义的节点吧&#xff0c;从此开源大模型推理引擎总算是由"非…

React官网生成Recat项目的区别

1. Next.js 特点&#xff1a; 页面级路由&#xff1a;使用文件系统路由&#xff0c;基于 /pages 文件夹的结构自动创建 URL 路径。渲染模式&#xff1a;支持三种渲染模式&#xff1a;静态生成 (SSG)、服务器端渲染 (SSR) 和客户端渲染 (CSR)&#xff0c;并允许根据页面的具体需…

Chromium 中chrome.system.storage扩展接口定义c++

一、chrome.system.storage 您可以使用 chrome.system.storage API 查询存储设备信息&#xff0c;并在连接和分离可移动存储设备时收到通知。 权限 system.storage 类型 EjectDeviceResultCode 枚举 "success" 移除命令成功执行 - 应用可以提示用户移除设备。…

Qt第三课 ----------布局

作者前言 &#x1f382; ✨✨✨✨✨✨&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f367;&#x1f382; ​&#x1f382; 作者介绍&#xff1a; &#x1f382;&#x1f382; &#x1f382; &#x1f389;&#x1f389;&#x1f389…

API接口精准获取商品详情信息案例

在当今数字化时代&#xff0c;电子商务平台的蓬勃发展&#xff0c;使得商品信息的获取变得尤为重要。API&#xff08;Application Programming Interface&#xff0c;应用程序编程接口&#xff09;作为连接前端用户界面与后端服务的桥梁&#xff0c;扮演着至关重要的角色。本文…

Python——飞机大战

以下是一个简单的用Python编写的飞机大战游戏的源代码&#xff1a; import pygame import random# 初始化游戏 pygame.init()# 设置游戏窗口的尺寸 screen_width 480 screen_height 640 screen pygame.display.set_mode((screen_width, screen_height))# 设置游戏标题 pyga…

516.最长回文子序列

刷算法题&#xff1a; 第一遍&#xff1a;1.看5分钟&#xff0c;没思路看题解 2.通过题解改进自己的解法&#xff0c;并且要写每行的注释以及自己的思路。 3.思考自己做到了题解的哪一步&#xff0c;下次怎么才能做对(总结方法) 4.整理到自己的自媒体平台。 5.再刷重复的类…