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

打印 上一主题 下一主题

主题 922|帖子 922|积分 2766

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

1.1 LLaMA-Factory安装

安装可以参考官方 readme (https://github.com/hiyouga/LLaMA-Factory/blob/main/README_zh.md)
懒得跳转网页的也可以直接复制下面的内容:
  1. git clone https://github.com/hiyouga/LLaMA-Factory.git
  2. cd LLaMA-Factory
  3. 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格式即可,即:
  1. [
  2.   {
  3.     "instruction": "人类指令(必填)",
  4.     "input": "人类输入(选填)",
  5.     "output": "模型回答(必填)",
  6.     "system": "系统提示词(选填)",
  7.     "history": [
  8.       ["第一轮指令(选填)", "第一轮回答(选填)"],
  9.       ["第二轮指令(选填)", "第二轮回答(选填)"]
  10.     ]
  11.   }
  12. ]
复制代码
数据为一个列表,列表中每个元素为一个字典。假如是单轮对话则无需 history。
当数据集构建完成后,可以放在data/路径下,也可以放在其他位置。
3. 配置dataset_info.json文件

将数据集构建完成后,需要在data/目录下找到dataset_info.json文件,并在这个文件中配置数据集的信息(个人以为这一步类似于django中的注册url)。
假设构建的数据集名字为input_data.json,那么在dataset_info.json需要加如下的字典:
  1. '数据集名称': {
  2.     "file_name": "input_data.json",
  3.     "columns": {
  4.       "prompt": "instruction",
  5.       "query": "input",
  6.       "response": "output"
  7.     }
  8.   },
复制代码
这里数据集名称可以自行界说,只需要在后续写yaml文件时把这个名称写进去就行。这里的file_name要与数据集的名称相同,假如文件放在了data/路径下,只需要数据集名称就行,而假如文件没有放在该路径下,则需要在这里指定路径。
4. 微调模型

微调模型可以选用启动网页服务来举行微调,实行以下下令启动网页服务:
  1. 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),具体而言:
  1. ### model
  2. model_name_or_path: Qwen2-1.5B-Instruct
  3. ### method
  4. stage: sft
  5. do_train: true
  6. finetuning_type: lora
  7. lora_target: all
  8. ### dataset
  9. dataset: input_data
  10. template: qwen
  11. cutoff_len: 1024
  12. max_samples: 1000
  13. overwrite_cache: true
  14. preprocessing_num_workers: 16
  15. ### output
  16. output_dir: saves/llama3-8b/lora/sft
  17. logging_steps: 10
  18. save_steps: 500
  19. plot_loss: true
  20. overwrite_output_dir: true
  21. ### train
  22. per_device_train_batch_size: 1
  23. gradient_accumulation_steps: 8
  24. learning_rate: 1.0e-4
  25. num_train_epochs: 3.0
  26. lr_scheduler_type: cosine
  27. warmup_ratio: 0.1
  28. bf16: true
  29. ddp_timeout: 180000000
  30. ### eval
  31. val_size: 0.1
  32. per_device_eval_batch_size: 1
  33. eval_strategy: steps
  34. 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:假如不需要验证这一坨其实可以不需要的。
微调的时间有大概显卡被人占用了,需要指定在哪几张卡上举行微调,所以用如下的下令举行微调:
  1. 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,具体而言:
  1. ### Note: DO NOT use quantized model or quantization_bit when merging lora adapters
  2. ### model
  3. model_name_or_path: Qwen/Qwen2-VL-7B-Instruct
  4. adapter_name_or_path: saves/qwen2_vl-7b/lora/sft
  5. template: qwen2_vl
  6. finetuning_type: lora
  7. ### export
  8. export_dir: models/qwen2_vl_lora_sft
  9. export_size: 2
  10. export_device: cpu
  11. export_legacy_format: false
复制代码


  • model_name_or_path:如上面一样是原始模型的路径。
  • adapter_name_or_path:LORA的参数位置,即第四步中output_dir的路径。
  • export_dir:合并后的文件路径。
  • export_size:单个文件的最大巨细(GB)。
运行下令:
  1. llamafactory-cli export yaml路径
复制代码
6. 部署

我这里选用的vLLM作为部署框架,有两种部署方式。假如是自己做实行的话可以离线部署,假如是上线的话发起用在线部署。
6.1 离线部署

离线部署即自己写个类大概方法,调用微调后的模型,代码如下:
  1. import os
  2. import torch
  3. os.environ["CUDA_VISIBLE_DEVICES"] = "2"
  4. from vllm import LLM, SamplingParams
  5. class Detect:
  6.     def __init__(self, model_path, temperature=0.8, top_p=0.95):
  7.         """
  8.         :param model_path: str
  9.         :param temperature: float
  10.                 取值范围: [0, +∞]
  11.                 - 当 temperature 设为 0 时,模型总是选择具有最高概率的单词,这会导致生成的结果非常确定性,缺乏随机变化。
  12.                 - 当 temperature 接近 1 时,模型的选择更加随机,这可以增加生成文本的多样性。
  13.                 - 高于 1 的值可能会导致生成的文本更加随机,有时可能失去连贯性
  14.                 - 注: 如果生成的内容太多就调低这个值
  15.         :param top_p: float
  16.                 取值范围: (0, 1]
  17.                 - top_p 参数表示从候选词汇中选取累积概率总和达到或超过 p 的词汇集合,然后从中随机选取下一个词汇。
  18.                   这种方式可以减少 temperature 为高值时可能出现的无意义输出。
  19.                 - 当 top_p 设置为接近 1 的值时,几乎所有的候选词汇都会被考虑,这类似于 temperature 接近 1 的情况。
  20.                 - 当 top_p 设置为较低值时,只有最有可能出现的几个词汇会被选中,这样可以减少输出的不确定性。
  21.                 - 注: 如果生成的内容太多就调低这个值
  22.         """
  23.         if not isinstance(temperature, (int, float)) or not isinstance(top_p, (int, float)):
  24.             raise ValueError('变量 "temperature" 和 "top_p" 必须为整型或浮点数')
  25.         if temperature < 0:
  26.             raise ValueError('变量 "temperature" 的取值范围为[0, +∞]')
  27.         if top_p <= 0 or top_p > 1:
  28.             raise ValueError('变量 "top_p" 的取值范围为(0, 1]')
  29.                
  30.                 self.template = 'hi'
  31.         self.llm = LLM(model=model_path, dtype=torch.bfloat16)
  32.         self.tokenizer = self.llm.get_tokenizer()
  33.         self.sampling_params = SamplingParams(temperature=temperature, top_p=top_p)
  34.     def detect(self, input_texts):
  35.         prompts = []
  36.         for input_text in input_texts:
  37.             prompt = [
  38.                 {'role': 'system', 'content': self.template},
  39.                 {'role': 'user', 'content': input_text + '\n输出:'}
  40.             ]
  41.             prompts.append(prompt)
  42.         prompts = self.tokenizer.apply_chat_template(prompts, add_generation_prompt=True, tokenize=False)
  43.         outputs = self.llm.generate(prompts, self.sampling_params)
  44.         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 在线部署

与离线部署差别,采用在线部署起首需要启动服务。
  1. 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脚本实现,具体代码如下:
  1. from openai import OpenAI
  2. def detect(input_texts, template, port='http://localhost:8000/v1', temperature=0.8, top_p=0.95):
  3.     """
  4.     :param input_texts: list
  5.             输入文本
  6.     :param template: str
  7.             提示模板
  8.     :param port: str
  9.             端口号
  10.     :param temperature: float
  11.             取值范围: [0, +∞]
  12.             - 当 temperature 设为 0 时,模型总是选择具有最高概率的单词,这会导致生成的结果非常确定性,缺乏随机变化。
  13.             - 当 temperature 接近 1 时,模型的选择更加随机,这可以增加生成文本的多样性。
  14.             - 高于 1 的值可能会导致生成的文本更加随机,有时可能失去连贯性
  15.             - 注: 如果生成的内容太多就调低这个值
  16.     :param top_p: float
  17.             取值范围: (0, 1]
  18.             - top_p 参数表示从候选词汇中选取累积概率总和达到或超过 p 的词汇集合,然后从中随机选取下一个词汇。
  19.               这种方式可以减少 temperature 为高值时可能出现的无意义输出。
  20.             - 当 top_p 设置为接近 1 的值时,几乎所有的候选词汇都会被考虑,这类似于 temperature 接近 1 的情况。
  21.             - 当 top_p 设置为较低值时,只有最有可能出现的几个词汇会被选中,这样可以减少输出的不确定性。
  22.             - 注: 如果生成的内容太多就调低这个值
  23.     :return:
  24.     """
  25.     if not isinstance(temperature, (int, float)) or not isinstance(top_p, (int, float)):
  26.         raise ValueError('变量 "temperature" 和 "top_p" 必须为整型或浮点数')
  27.     if temperature < 0:
  28.         raise ValueError('变量 "temperature" 的取值范围为[0, +∞]')
  29.     if top_p <= 0 or top_p > 1:
  30.         raise ValueError('变量 "top_p" 的取值范围为(0, 1]')
  31.     openai_api_key = "EMPTY"
  32.     openai_api_base = port
  33.     client = OpenAI(
  34.         api_key=openai_api_key,
  35.         base_url=openai_api_base,
  36.     )
  37.     outputs = []
  38.     for input_text in input_texts:
  39.         chat_response = client.chat.completions.create(
  40.             model="qwen",
  41.             messages=[
  42.                 {"role": "system", "content": template},
  43.                 {"role": "user", "content": input_text},
  44.             ],
  45.             temperature=temperature,
  46.             top_p=top_p,
  47.         )
  48.         outputs.append(chat_response.choices[0].message.content)
  49.     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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

种地

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表