Qwen1.5-7B-Chat FastApi 部署调用
环境准备
本文基础环境如下:
----------------
ubuntu 22.04
python 3.12
cuda 12.1
pytorch 2.3.0
----------------
本文默认学习者已安装好以上 PyTorch(cuda) 环境,如未安装请自行安装。
接下来开始环境配置、模型下载和运行演示 ~
pip 换源加速下载并安装依赖包
# 升级pip
python -m pip install --upgrade pip
# 更换 pypi 源加速库的安装
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
pip install fastapi==0.111.1
pip install uvicorn==0.30.3
pip install modelscope==1.16.1
pip install transformers==4.43.2
pip install accelerate==0.32.1
考虑到部分同学配置环境可能会遇到一些问题,我们在AutoDL平台准备了Qwen1.5的环境镜像,该镜像适用于该仓库除Qwen-GPTQ和vllm外的所有部署环境。点击下方链接并直接创建Autodl示例即可。 https://www.codewithgpu.com/i/datawhalechina/self-llm/self-llm-Qwen1.5
模型下载
使用 modelscope 中的 snapshot_download 函数下载模型,第一个参数为模型名称,参数 cache_dir 为自定义的模型下载路径,参数revision为模型仓库分支版本,master代表主分支,也是一般模型上传的默认分支。
先切换到 autodl-tmp 目录,cd /root/autodl-tmp
然后新建名为 model_download.py 的 python 文件,并在其中输入以下内容并保存
# model_download.py
from modelscope import snapshot_download
model_dir = snapshot_download('qwen/Qwen1.5-7B-Chat', cache_dir='/root/autodl-tmp', revision='master')
然后在终端中输入 python model_download.py 执行下载,注意该模型权重文件比较大,因此这里需要耐心等待一段时间直到模型下载完成。
注意:记得修改
cache_dir为你的模型下载路径哦~
代码准备
在 /root/autodl-tmp 路径下新建 api.py 文件并在其中输入以下内容,粘贴代码后请及时保存文件。下面的代码有很详细的注释,大家如有不理解的地方,欢迎提出 issue。
from fastapi import FastAPI, Request
from transformers import AutoTokenizer, AutoModelForCausalLM, GenerationConfig
import uvicorn
import json
import datetime
import torch
# 设置设备参数
DEVICE = "cuda" # 使用CUDA
DEVICE_ID = "0" # CUDA设备ID,如果未设置则为空
CUDA_DEVICE = f"{DEVICE}:{DEVICE_ID}" if DEVICE_ID else DEVICE # 组合CUDA设备信息
# 清理GPU内存函数
def torch_gc():
if torch.cuda.is_available(): # 检查是否可用CUDA
with torch.cuda.device(CUDA_DEVICE): # 指定CUDA设备
torch.cuda.empty_cache() # 清空CUDA缓存
torch.cuda.ipc_collect() # 收集CUDA内存碎片
# 创建FastAPI应用
app = FastAPI()
# 处理POST请求的端点
@app.post("/")
async def create_item(request: Request):
global model, tokenizer # 声明全局变量以便在函数内部使用模型和分词器
json_post_raw = await request.json() # 获取POST请求的JSON数据
json_post = json.dumps(json_post_raw) # 将JSON数据转换为字符串
json_post_list = json.loads(json_post) # 将字符串转换为Python对象
prompt = json_post_list.get('prompt') # 获取请求中的提示
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": prompt}
]
# 调用模型进行对话生成
input_ids = tokenizer.apply_chat_template(messages,tokenize=False,add_generation_prompt=True)
model_inputs = tokenizer([input_ids], return_tensors="pt").to('cuda')
generated_ids = model.generate(model_inputs.input_ids,max_new_tokens=512)
generated_ids = [
output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
now = datetime.datetime.now() # 获取当前时间
time = now.strftime("%Y-%m-%d %H:%M:%S") # 格式化时间为字符串
# 构建响应JSON
answer = {
"response": response,
"status": 200,
"time": time
}
# 构建日志信息
log = "[" + time + "] " + '", prompt:"' + prompt + '", response:"' + repr(response) + '"'
print(log) # 打印日志
torch_gc() # 执行GPU内存清理
return answer # 返回响应
# 主函数入口
if __name__ == '__main__':
# 加载预训练的分词器和模型
model_name_or_path = '/root/autodl-tmp/qwen/Qwen1.5-7B-Chat'
tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, use_fast=False)
model = AutoModelForCausalLM.from_pretrained(model_name_or_path, device_map="auto", torch_dtype=torch.bfloat16)
# 启动FastAPI应用
# 用6006端口可以将autodl的端口映射到本地,从而在本地使用api
uvicorn.run(app, host='0.0.0.0', port=6006, workers=1) # 在指定端口和主机上启动应用
Api 部署
在终端输入以下命令启动api服务:
cd /root/autodl-tmp
python api.py
加载完毕后出现如下信息说明成功。
默认部署在 6006 端口,通过 POST 方法进行调用,可以使用 curl 调用,如下所示:
curl -X POST "http://127.0.0.1:6006" \
-H 'Content-Type: application/json' \
-d '{"prompt": "你好"}'
也可以使用 python 中的 requests 库进行调用,如下所示:
import requests
import json
def get_completion(prompt):
headers = {'Content-Type': 'application/json'}
data = {"prompt": prompt}
response = requests.post(url='http://127.0.0.1:6006', headers=headers, data=json.dumps(data))
return response.json()['response']
if __name__ == '__main__':
print(get_completion('你好'))
得到的返回值如下所示:
{"response":"你好!有什么我可以帮助你的吗?","status":200,"time":"2024-02-05 18:08:19"}
Qwen1.5-7B-Chat 接入 LangChain 搭建知识库助手
环境准备
本文基础环境如下:
----------------
ubuntu 22.04
python 3.12
cuda 12.1
pytorch 2.3.0
----------------
本文默认学习者已安装好以上 PyTorch(cuda) 环境,如未安装请自行安装。
接下来开始环境配置、模型下载和运行演示 ~
pip 换源加速下载并安装依赖包
# 升级pip
python -m pip install --upgrade pip
# 更换 pypi 源加速库的安装
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
pip install modelscope==1.16.1
pip install langchain==0.2.3
pip install transformers==4.43.2
pip install accelerate==0.32.1
考虑到部分同学配置环境可能会遇到一些问题,我们在AutoDL平台准备了Qwen1.5的环境镜像,该镜像适用于该仓库除Qwen-GPTQ和vllm外的所有部署环境。点击下方链接并直接创建Autodl示例即可。 https://www.codewithgpu.com/i/datawhalechina/self-llm/self-llm-Qwen1.5
模型下载
使用 modelscope 中的 snapshot_download 函数下载模型,第一个参数为模型名称,参数 cache_dir 为自定义的模型下载路径,参数revision为模型仓库分支版本,master代表主分支,也是一般模型上传的默认分支。
先切换到 autodl-tmp 目录,cd /root/autodl-tmp
然后新建名为 model_download.py 的 python 文件,并在其中输入以下内容并保存
# model_download.py
from modelscope import snapshot_download
model_dir = snapshot_download('qwen/Qwen1.5-7B-Chat', cache_dir='/root/autodl-tmp', revision='master')
然后在终端中输入 python model_download.py 执行下载,注意该模型权重文件比较大,因此这里需要耐心等待一段时间直到模型下载完成。
注意:记得修改
cache_dir为你的模型下载路径哦~
代码准备
为便捷构建 LLM 应用,我们需要基于本地部署的 Qwen1.5-LM,自定义一个 LLM 类,将 Qwen1.5 接入到 LangChain 框架中。完成自定义 LLM 类之后,可以以完全一致的方式调用 LangChain 的接口,而无需考虑底层模型调用的不一致。
基于本地部署的 Qwen1.5 自定义 LLM 类并不复杂,我们只需从 LangChain.llms.base.LLM 类继承一个子类,并重写构造函数与 _call 函数即可:
from langchain.llms.base import LLM
from typing import Any, List, Optional
from langchain.callbacks.manager import CallbackManagerForLLMRun
from transformers import AutoTokenizer, AutoModelForCausalLM, GenerationConfig, LlamaTokenizerFast
import torch
class Qwen1_5_LLM(LLM):
# 基于本地 Qwen1.5 自定义 LLM 类
tokenizer: AutoTokenizer = None
model: AutoModelForCausalLM = None
def __init__(self, mode_name_or_path :str):
super().__init__()
print("正在从本地加载模型...")
self.tokenizer = AutoTokenizer.from_pretrained(mode_name_or_path, use_fast=False)
self.model = AutoModelForCausalLM.from_pretrained(mode_name_or_path, torch_dtype=torch.bfloat16, device_map="auto")
self.model.generation_config = GenerationConfig.from_pretrained(mode_name_or_path)
print("完成本地模型的加载")
def _call(self, prompt : str, stop: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
**kwargs: Any):
messages = [{"role": "user", "content": prompt }]
input_ids = self.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
model_inputs = self.tokenizer([input_ids], return_tensors="pt").to('cuda')
generated_ids = self.model.generate(model_inputs.input_ids,max_new_tokens=512)
generated_ids = [
output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]
response = self.tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
return response
@property
def _llm_type(self) -> str:
return "Qwen1_5_LLM"
在上述类定义中,我们分别重写了构造函数和 _call 函数:对于构造函数,我们在对象实例化的一开始加载本地部署的 Qwen1.5 模型,从而避免每一次调用都需要重新加载模型带来的时间过长;_call 函数是 LLM 类的核心函数,LangChain 会调用该函数来调用 LLM,在该函数中,我们调用已实例化模型的 generate 方法,从而实现对模型的调用并返回调用结果。
在整体项目中,我们将上述代码封装为 LLM.py,后续将直接从该文件中引入自定义的 LLM 类。
调用
然后就可以像使用任何其他的langchain大模型功能一样使用了。
from LLM import Qwen1_5_LLM
llm = Qwen1_5_LLM(mode_name_or_path = "/root/autodl-tmp/qwen/Qwen1.5-7B-Chat")
llm("你是谁")
Qwen1.5-7B-Chat WebDemo 部署
Qwen1.5 介绍
Qwen1.5 是 Qwen2 的测试版,Qwen1.5 是基于 transformer 的 decoder-only 语言模型,已在大量数据上进行了预训练。与之前发布的 Qwen 相比,Qwen1.5 的改进包括 6 种模型大小,包括 0.5B、1.8B、4B、7B、14B 和 72B;Chat模型在人类偏好方面的性能显著提高;基础模型和聊天模型均支持多种语言;所有大小的模型均稳定支持 32K 上下文长度,无需 trust_remote_code。
环境准备
本文基础环境如下:
----------------
ubuntu 22.04
python 3.12
cuda 12.1
pytorch 2.3.0
----------------
本文默认学习者已安装好以上 PyTorch(cuda) 环境,如未安装请自行安装。
接下来开始环境配置、模型下载和运行演示 ~
pip 换源加速下载并安装依赖包
pip install --upgrade pip
# 更换 pypi 源加速库的安装
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
pip install modelscope==1.16.1
pip install langchain==0.2.3
pip install streamlit==1.37.0
pip install transformers==4.43.2
pip install accelerate==0.32.1
考虑到部分同学配置环境可能会遇到一些问题,我们在AutoDL平台准备了Qwen1.5的环境镜像,该镜像适用于该仓库除Qwen-GPTQ和vllm外的所有部署环境。点击下方链接并直接创建Autodl示例即可。 https://www.codewithgpu.com/i/datawhalechina/self-llm/self-llm-Qwen1.5
模型下载
使用 modelscope 中的 snapshot_download 函数下载模型,第一个参数为模型名称,参数 cache_dir 为自定义的模型下载路径,参数revision为模型仓库分支版本,master代表主分支,也是一般模型上传的默认分支。
先切换到 autodl-tmp 目录,cd /root/autodl-tmp
然后新建名为 model_download.py 的 python 文件,并在其中输入以下内容并保存
# model_download.py
from modelscope import snapshot_download
model_dir = snapshot_download('qwen/Qwen1.5-7B-Chat', cache_dir='/root/autodl-tmp', revision='master')
然后在终端中输入 python model_download.py 执行下载,注意该模型权重文件比较大,因此这里需要耐心等待一段时间直到模型下载完成。
注意:记得修改
cache_dir为你的模型下载路径哦~
代码准备
在/root/autodl-tmp路径下新建 chatBot.py 文件并在其中输入以下内容,粘贴代码后记得保存文件。下面的代码有很详细的注释,大家如有不理解的地方,欢迎提出issue。
# 导入所需的库
from transformers import AutoTokenizer, AutoModelForCausalLM, GenerationConfig
import torch
import streamlit as st
# 在侧边栏中创建一个标题和一个链接
with st.sidebar:
st.markdown("## Qwen1.5 LLM")
"[开源大模型食用指南 self-llm](https://github.com/datawhalechina/self-llm.git)"
# 创建一个滑块,用于选择最大长度,范围在0到1024之间,默认值为512
max_length = st.slider("max_length", 0, 1024, 512, step=1)
# 创建一个标题和一个副标题
st.title("💬 Qwen1.5 Chatbot")
st.caption("🚀 A streamlit chatbot powered by Self-LLM")
# 定义模型路径
mode_name_or_path = '/root/autodl-tmp/qwen/Qwen1.5-7B-Chat'
# 定义一个函数,用于获取模型和tokenizer
@st.cache_resource
def get_model():
# 从预训练的模型中获取tokenizer
tokenizer = AutoTokenizer.from_pretrained(mode_name_or_path, use_fast=False)
# 从预训练的模型中获取模型,并设置模型参数
model = AutoModelForCausalLM.from_pretrained(mode_name_or_path, torch_dtype=torch.bfloat16, device_map="auto")
return tokenizer, model
# 加载Qwen1.5-4B-Chat的model和tokenizer
tokenizer, model = get_model()
# 如果session_state中没有"messages",则创建一个包含默认消息的列表
if "messages" not in st.session_state:
st.session_state["messages"] = [{"role": "assistant", "content": "有什么可以帮您的?"}]
# 遍历session_state中的所有消息,并显示在聊天界面上
for msg in st.session_state.messages:
st.chat_message(msg["role"]).write(msg["content"])
# 如果用户在聊天输入框中输入了内容,则执行以下操作
if prompt := st.chat_input():
# 将用户的输入添加到session_state中的messages列表中
st.session_state.messages.append({"role": "user", "content": prompt})
# 在聊天界面上显示用户的输入
st.chat_message("user").write(prompt)
# 构建输入
input_ids = tokenizer.apply_chat_template(st.session_state.messages,tokenize=False,add_generation_prompt=True)
model_inputs = tokenizer([input_ids], return_tensors="pt").to('cuda')
generated_ids = model.generate(model_inputs.input_ids, max_new_tokens=512)
generated_ids = [
output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
# 将模型的输出添加到session_state中的messages列表中
st.session_state.messages.append({"role": "assistant", "content": response})
# 在聊天界面上显示模型的输出
st.chat_message("assistant").write(response)
# print(st.session_state)
运行 demo
在终端中运行以下命令,启动streamlit服务,并按照 autodl 的指示将端口映射到本地,然后在浏览器中打开链接 http://localhost:6006/ ,即可看到聊天界面。
streamlit run /root/autodl-tmp/chatBot.py --server.address 127.0.0.1 --server.port 6006
效果如下所示:
Qwen1.5-7B-Chat Lora 微调
本节我们简要介绍如何基于 transformers、peft 等框架,对 Qwen1.5-7B-chat 模型进行 Lora 微调。Lora 是一种高效微调方法,深入了解其原理可参见博客:知乎|深入浅出Lora。
环境配置
本文基础环境如下:
----------------
ubuntu 22.04
python 3.12
cuda 12.1
pytorch 2.3.0
----------------
本文默认学习者已安装好以上 PyTorch(cuda) 环境,如未安装请自行安装。
接下来开始环境配置、模型下载和运行演示 ~
pip 换源加速下载并安装依赖包
pip install --upgrade pip
# 更换 pypi 源加速库的安装
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
pip install modelscope==1.16.1
pip install transformers==4.43.2
pip install accelerate==0.32.1
pip install peft==0.11.1
pip install datasets==2.20.0
考虑到部分同学配置环境可能会遇到一些问题,我们在AutoDL平台准备了Qwen1.5的环境镜像,该镜像适用于该仓库除Qwen-GPTQ和vllm外的所有部署环境。点击下方链接并直接创建Autodl示例即可。 https://www.codewithgpu.com/i/datawhalechina/self-llm/self-llm-Qwen1.5
指令集构建
LLM 的微调一般指指令微调过程。所谓指令微调,是说我们使用的微调数据形如:
{
"instruction":"回答以下用户问题,仅输出答案。",
"input":"1+1等于几?",
"output":"2"
}
其中,instruction 是用户指令,告知模型其需要完成的任务;input 是用户输入,是完成用户指令所必须的输入内容;output 是模型应该给出的输出。
即我们的核心训练目标是让模型具有理解并遵循用户指令的能力。因此,在指令集构建时,我们应针对我们的目标任务,针对性构建任务指令集。例如,在本节我们使用由笔者合作开源的 Chat-甄嬛 项目作为示例,我们的目标是构建一个能够模拟甄嬛对话风格的个性化 LLM,因此我们构造的指令形如:
{
"instruction": "你是谁?",
"input":"",
"output":"家父是大理寺少卿甄远道。"
}
我们所构造的全部指令数据集在根目录下。
数据格式化
Lora 训练的数据是需要经过格式化、编码之后再输入给模型进行训练的,如果是熟悉 Pytorch 模型训练流程的同学会知道,我们一般需要将输入文本编码为 input_ids,将输出文本编码为 labels,编码之后的结果都是多维的向量。我们首先定义一个预处理函数,这个函数用于对每一个样本,编码其输入、输出文本并返回一个编码后的字典:
def process_func(example):
MAX_LENGTH = 384 # Llama分词器会将一个中文字切分为多个token,因此需要放开一些最大长度,保证数据的完整性
input_ids, attention_mask, labels = [], [], []
instruction = tokenizer(f"<|im_start|>system\n现在你要扮演皇帝身边的女人--甄嬛<|im_end|>\n<|im_start|>user\n{example['instruction'] + example['input']}<|im_end|>\n<|im_start|>assistant\n", 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] # 因为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
}
Qwen1.5 采用的Prompt Template格式如下:
<|im_start|>system
You are a helpful assistant.<|im_end|>
<|im_start|>user
你是谁?<|im_end|>
<|im_start|>assistant
我是一个有用的助手。<|im_end|>
加载tokenizer和半精度模型
模型以半精度形式加载,如果你的显卡比较新的话,可以用torch.bfolat形式加载。对于自定义的模型一定要指定trust_remote_code参数为True。
tokenizer = AutoTokenizer.from_pretrained('./qwen/Qwen1.5-7B-Chat/', use_fast=False, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained('./qwen/Qwen1.5-7B-Chat/', device_map="auto",torch_dtype=torch.bfloat16)
定义LoraConfig
LoraConfig这个类中可以设置很多参数,但主要的参数没多少,简单讲一讲,感兴趣的同学可以直接看源码。
task_type:模型类型
target_modules:需要训练的模型层的名字,主要就是attention部分的层,不同的模型对应的层的名字不同,可以传入数组,也可以字符串,也可以正则表达式。
r:lora的秩,具体可以看Lora原理
lora_alpha:Lora alaph,具体作用参见 Lora 原理
Lora的缩放是啥嘞?当然不是r(秩),这个缩放就是lora_alpha/r, 在这个LoraConfig中缩放就是4倍。
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 比例
)
自定义 TrainingArguments 参数
TrainingArguments这个类的源码也介绍了每个参数的具体作用,当然大家可以来自行探索,这里就简单说几个常用的。
output_dir:模型的输出路径
per_device_train_batch_size:顾名思义 batch_size
gradient_accumulation_steps: 梯度累加,如果你的显存比较小,那可以把 batch_size 设置小一点,梯度累加增大一些。
logging_steps:多少步,输出一次log
num_train_epochs:顾名思义 epoch
gradient_checkpointing:梯度检查,这个一旦开启,模型就必须执行model.enable_input_require_grads(),这个原理大家可以自行探索,这里就不细说了。
args = TrainingArguments(
output_dir="./output/Qwen1.5-7B-Chat",
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
logging_steps=10,
num_train_epochs=3,
save_steps=100,
learning_rate=1e-4,
save_on_each_node=True,
gradient_checkpointing=True
)
使用 Trainer 训练
trainer = Trainer(
model=model,
args=args,
train_dataset=tokenized_id,
data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),
)
trainer.train()
加载 lora 权重推理
训练好了之后可以使用如下方式加载lora权重进行推理:
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
from peft import PeftModel
model_path = './qwen/Qwen1.5-7B-Chat/'
lora_path = 'lora_path'
# 加载tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_path)
# 加载模型
model = AutoModelForCausalLM.from_pretrained(model_path, device_map="auto",torch_dtype=torch.bfloat16)
# 加载lora权重
model = PeftModel.from_pretrained(model, model_id=lora_path, config=config)
prompt = "你是谁?"
messages = [
{"role": "system", "content": "现在你要扮演皇帝身边的女人--甄嬛"},
{"role": "user", "content": prompt}
]
text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
model_inputs = tokenizer([text], return_tensors="pt").to('cuda')
generated_ids = model.generate(
model_inputs.input_ids,
max_new_tokens=512
)
generated_ids = [
output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
print(response)
Qwen1.5-7B-Chat-GPTQ-Int4 部署环境
说明
Qwen1.5-72b 版本有BF16、INT8、INT4三个版本,三个版本性能接近。由于BF16版本需要144GB的显存,让普通用户忘却止步,而INT4版本只需要48GB即可推理,给普通用户本地化部署创造了机会。(建议使用4×24G显存的机器)
但由于Qwen1.5-72B-Chat-GPTQ-Int4其使用了GPTQ量化技术,对环境依赖要求严格,需要较为复杂的环境准备步骤。
在此提供环境准备教程。本教程以 Qwen1.5-7B-Chat-GPTQ-Int4为例,同样适用于其他大小的GPTQ-Int4版本。
环境准备
说明:
确保显卡驱动支持cuda11.8
过程需要严格满足nvcc-pytorch-GPTQ的版本对应关系,否则GPTQ无法编译成功。
(原因见Qwen库Quantization部分,由于GPTQ工具需要严格的cuda+torch对应关系,且由于近期的升级可能带来的bug。我们保险起见选择cuda11.8,并且根据qwen库所要求的torch2.1,安装了其对应的torch,并在后面使用GPTQ源码构建以确保cuda的正确运行。)
确保nvcc可以正常工作:
nvcc -V
# 查看输出若为Cuda compilation tools, release 11.8 则跳过平台及cuda部分
可通过以下方式自行搭建cuda11.8环境。该方法已经通过测试。
# 下载驱动并安装
wget https://developer.download.nvidia.com/compute/cuda/11.8.0/local_installers/cuda_11.8.0_520.61.05_linux.run
# 勾选cudatoolkit并安装
sudo sh cuda_11.8.0_520.61.05_linux.run
# 添加nvcc环境变量
vim ~/.bashrc
# 添加如下两行
export PATH=/usr/local/cuda-11.8/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda-11.8/lib64:$LD_LIBRARY_PATH
# 重新载入
source ~/.bashrc
nvcc -V
虚拟环境配置
由于base环境的torch不一定满足要求,创建虚拟环境。
# 创建虚拟环境
conda create -n qwen python==3.10
# 安装指定版本pytorch
pip install torch==2.1.1 torchvision==0.16.1 torchaudio==2.1.1 --index-url https://download.pytorch.org/whl/cu118
# 安装Qwen1.5所需依赖
pip install accelerate tiktoken einops transformers_stream_generator==0.0.4 scipy optimum peft transformers streamlit modelscope
从源码安装GPTQ(auto-gptq>=0.5.1),否则极易出现GPTQ无法使用cuda的情况
# 从源码安装量化所需GPTQ库
pip install "git+https://github.com/PanQiWei/AutoGPTQ.git@v0.7.1"
Note: The pre-compiled
auto-gptqpackages strongly depend on the version oftorchand its CUDA version. Moreover, due to recent update, you may also encounter unsupported version errors fromtransformers,optimum, orpeft. We recommend using the latest versions meeting the following requirements: - torch==2.1 auto-gptq>=0.5.1 transformers>=4.35.0 optimum>=1.14.0 peft>=0.6.1 - torch>=2.0,=0.5.0,<0.6.0
至此,环境部分准备完成。
模型下载
使用 modelscope 中的 snapshot_download 函数下载模型,第一个参数为模型名称,参数 cache_dir 为自定义的模型下载路径,参数revision为模型仓库分支版本,master代表主分支,也是一般模型上传的默认分支。
先切换到 autodl-tmp 目录,cd /root/autodl-tmp
然后新建名为 model_download.py 的 python 文件,并在其中输入以下内容并保存
# model_download.py
from modelscope import snapshot_download
model_dir = snapshot_download('qwen/Qwen1.5-7B-Chat-GPTQ-Int4', cache_dir='/root/autodl-tmp', revision='master')
然后在终端中输入 python model_download.py 执行下载,注意该模型权重文件比较大,因此这里需要耐心等待一段时间直到模型下载完成。
注意:记得修改
cache_dir为你的模型下载路径哦~
说明:下载后需要确认下载的
Qwen1.5-7B-Chat-GPTQ-Int4文件名称,可能由于解码问题不正确导致后续bug。
代码准备
在/root/autodl-tmp路径下新建 chatBot.py 文件并在其中输入以下内容,粘贴代码后记得保存文件。下面的代码有很详细的注释,大家如有不理解的地方,欢迎提出issue。
# 导入所需的库
from transformers import AutoTokenizer, AutoModelForCausalLM, GenerationConfig
import torch
import streamlit as st
# 在侧边栏中创建一个标题和一个链接
with st.sidebar:
st.markdown("## Qwen1.5 LLM")
"[开源大模型食用指南 self-llm](https://github.com/datawhalechina/self-llm.git)"
# 创建一个滑块,用于选择最大长度,范围在0到1024之间,默认值为512
max_length = st.slider("max_length", 0, 1024, 512, step=1)
# 创建一个标题和一个副标题
st.title("💬 Qwen1.5 Chatbot")
st.caption("🚀 A streamlit chatbot powered by Self-LLM")
# 定义模型路径
mode_name_or_path = '/root/autodl-tmp/qwen/Qwen1.5-7B-Chat-GPTQ-Int4'
# 定义一个函数,用于获取模型和tokenizer
@st.cache_resource
def get_model():
# 从预训练的模型中获取tokenizer
tokenizer = AutoTokenizer.from_pretrained(mode_name_or_path, use_fast=False)
# 从预训练的模型中获取模型,并设置模型参数,特别注意torch_dtype为auto,否则送入device数据类型不一致
model = AutoModelForCausalLM.from_pretrained(mode_name_or_path, torch_dtype="auto", device_map="auto")
return tokenizer, model
# 加载Qwen1.5-4B-Chat的model和tokenizer
tokenizer, model = get_model()
# 如果session_state中没有"messages",则创建一个包含默认消息的列表
if "messages" not in st.session_state:
st.session_state["messages"] = [{"role": "assistant", "content": "有什么可以帮您的?"}]
# 遍历session_state中的所有消息,并显示在聊天界面上
for msg in st.session_state.messages:
st.chat_message(msg["role"]).write(msg["content"])
# 如果用户在聊天输入框中输入了内容,则执行以下操作
if prompt := st.chat_input():
# 将用户的输入添加到session_state中的messages列表中
st.session_state.messages.append({"role": "user", "content": prompt})
# 在聊天界面上显示用户的输入
st.chat_message("user").write(prompt)
# 构建输入
input_ids = tokenizer.apply_chat_template(st.session_state.messages,tokenize=False,add_generation_prompt=True)
model_inputs = tokenizer([input_ids], return_tensors="pt").to('cuda')
generated_ids = model.generate(model_inputs.input_ids, max_new_tokens=512)
generated_ids = [
output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
# 将模型的输出添加到session_state中的messages列表中
st.session_state.messages.append({"role": "assistant", "content": response})
# 在聊天界面上显示模型的输出
st.chat_message("assistant").write(response)
# print(st.session_state)
运行 demo
在终端中运行以下命令,启动streamlit服务,并按照 autodl 的指示将端口映射到本地,然后在浏览器中打开链接 http://localhost:6006/ ,即可看到聊天界面。
streamlit run /root/autodl-tmp/chatBot.py --server.address 127.0.0.1 --server.port 6006
最终效果:
Qwen1.5-MoE-Chat Transformers 部署调用
Qwen1.5-MoE-Chat介绍
Qwen1.5-MoE-chat仅使用2.7B激活参数,达到和目前最先进的7B模型如Mistral 7B和Qwen1.5-7B模型相当的能力。相比Qwen1.5-7B,Qwen1.5-MoE-A2.7B的训练成本降低了75%,推理速度则提升至1.74倍。
相较于Mixtral-MoE模型,Qwen1.5-MoE在模型结构上有以下改进:使用了finegrained experts,利用已有的Qwen1.8B初始化模型,使用shared experts和routing experts等新的routing机制。
讲讲显存计算
显存计算的考虑会随着模型类型不同,任务不同而变化
这里的Transformers部署调用是推理任务,因而只需要考虑模型参数、KV Cache、中间结果和输入数据。这里的模型为MoE模型,考虑完整模型参数(14.3B);使用了bf16加载,再考虑中间结果、输入数据和KV Cache等,大概是 2x1.2x14.3 的显存需求,所以我们后面会选择双卡共48G显存
环境准备
本文基础环境如下:
----------------
ubuntu 22.04
python 3.12
cuda 12.1
pytorch 2.3.0
----------------
本文默认学习者已安装好以上 PyTorch(cuda) 环境,如未安装请自行安装。
接下来开始环境配置、模型下载和运行演示 ~
pip 换源加速下载并安装依赖包
# 因为涉及到访问github因此最好打开autodl的学术镜像加速
source /etc/network_turbo
# 升级pip
python -m pip install --upgrade pip
# 更换 pypi 源加速库的安装
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
# 从transformers的github仓库中安装包含qwen1.5-moe的新版本
pip install git+https://github.com/huggingface/transformers
# 安装需要的python包
pip install modelscope sentencepiece accelerate fastapi uvicorn requests streamlit transformers_stream_generator
# 安装flash-attention
pip install https://github.com/Dao-AILab/flash-attention/releases/download/v2.4.2/flash_attn-2.4.2+cu122torch2.1cxx11abiFALSE-cp310-cp310-linux_x86_64.whl
模型下载
使用 modelscope 中的 snapshot_download 函数下载模型,第一个参数为模型名称,参数 cache_dir 为自定义的模型下载路径,参数revision为模型仓库分支版本,master代表主分支,也是一般模型上传的默认分支。
先切换到 autodl-tmp 目录,cd /root/autodl-tmp
然后新建名为 model_download.py 的 python 文件,并在其中输入以下内容并保存
# model_download.py
from modelscope import snapshot_download
model_dir = snapshot_download('qwen/Qwen1.5-MoE-A2.7B-Chat', cache_dir='/root/autodl-tmp', revision='master')
然后在终端中输入 python model_download.py 执行下载,注意该模型权重文件比较大,因此这里需要耐心等待一段时间直到模型下载完成。
注意:记得修改
cache_dir为你的模型下载路径哦~
代码准备
在/root/autodl-tmp路径下新建 trains.py 文件并在其中输入以下内容
import torch # 导入torch库,用于深度学习相关操作
from transformers import AutoTokenizer, AutoModelForCausalLM, GenerationConfig # 三个类分别用于加载分词器、加载因果语言模型和加载生成配置
# 将模型路径设置为刚刚下载的模型路径
model_name = "/root/autodl-tmp/qwen/Qwen1.5-MoE-A2.7B-Chat"
# 加载语言模型,设置数据类型为bfloat16即混合精度格式以优化性能并减少显存使用,将推理设备设置为`auto`自动选择最佳的设备进行推理,如果没有可用的GPU,它可能会回退到CPU
model = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype=torch.bfloat16, device_map="auto")
# 加载分词器
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 定义input字符串
prompt = "Give me a short introduction to large language model."
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": prompt}
]
# 使用分词器的apply_chat_template方法来处理messages,转换格式
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True # 在消息前添加生成提示
)
# 将text变量中的文本转换为模型输入的格式,指定返回的张量为PyTorch张量("pt")
model_inputs = tokenizer([text], return_tensors="pt").to(device)
# 使用模型的generate方法来生成文本
generated_ids = model.generate(
model_inputs.input_ids,
max_new_tokens=512
)
# 从生成的ID中提取出除了原始输入之外的新生成的token
generated_ids = [
output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]
# 使用分词器的batch_decode方法将生成的token ID转换回文本
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
# 显示生成的回答
print(response)
运行结果如下图所示,模型生成了与代码中prompt对应的结果:
Qwen1.5-7B-Chat vLLM 部署调用
vLLM 简介
vLLM 框架是一个高效的大型语言模型(LLM)推理和部署服务系统,具备以下特性:
高效的内存管理:通过 PagedAttention 算法,vLLM 实现了对 KV 缓存的高效管理,减少了内存浪费,优化了模型的运行效率。
高吞吐量:vLLM 支持异步处理和连续批处理请求,显著提高了模型推理的吞吐量,加速了文本生成和处理速度。
易用性:vLLM 与 HuggingFace 模型无缝集成,支持多种流行的大型语言模型,简化了模型部署和推理的过程。兼容 OpenAI 的 API 服务器。
分布式推理:框架支持在多 GPU 环境中进行分布式推理,通过模型并行策略和高效的数据通信,提升了处理大型模型的能力。
开源:vLLM 是开源的,拥有活跃的社区支持,便于开发者贡献和改进,共同推动技术发展。
环境准备
在 Autodl 平台中租赁一个 3090 等 24G 显存的显卡机器,如下图所示镜像选择 PyTorch-->2.1.0-->3.10(ubuntu22.04)-->12.1
接下来打开刚刚租用服务器的 JupyterLab,并且打开其中的终端开始环境配置、模型下载和运行演示。
pip 换源加速下载并安装依赖包
# 升级pip
python -m pip install --upgrade pip
# 更换 pypi 源加速库的安装
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
pip install modelscope==1.11.0
pip install openai==1.17.1
pip install torch==2.1.2+cu121
pip install tqdm==4.64.1
pip install transformers==4.39.3
pip install flash-attn==2.5.7 --no-build-isolation
pip install vllm==0.4.0.post1
直接安装 vLLM 会安装 CUDA 12.1 版本。
pip install vllm
如果我们需要在 CUDA 11.8 的环境下安装 vLLM,可以使用以下命令,指定 vLLM 版本和 python 版本下载。
export VLLM_VERSION=0.4.0
export PYTHON_VERSION=38
pip install https://github.com/vllm-project/vllm/releases/download/v${VLLM_VERSION}/vllm-${VLLM_VERSION}+cu118-cp${PYTHON_VERSION}-cp${PYTHON_VERSION}-manylinux1_x86_64.whl --extra-index-url https://download.pytorch.org/whl/cu118
考虑到部分同学配置环境可能会遇到一些问题,我们在 AutoDL 平台准备了 vLLM 的环境镜像,该镜像适用于任何需要 vLLM 的部署环境。点击下方链接并直接创建 AutoDL 示例即可。(vLLM 对 torch 版本要求较高,且越高的版本对模型的支持更全,效果更好,所以新建一个全新的镜像。) https://www.codewithgpu.com/i/datawhalechina/self-llm/self-llm-vllm
模型下载
使用 modelscope 中的 snapshot_download 函数下载模型,第一个参数为模型名称,参数 cache_dir 为模型的下载路径。
在 /root/autodl-tmp 路径下新建 model_download.py 文件并在其中输入以下内容,粘贴代码后请及时保存文件,如下图所示。并运行 python /root/autodl-tmp/model_download.py 执行下载,模型大小为 14GB,下载模型大概需要 2 分钟。
import torch
from modelscope import snapshot_download, AutoModel, AutoTokenizer
import os
model_dir = snapshot_download('qwen/Qwen1.5-7B-Chat', cache_dir='/root/autodl-tmp', revision='master')
代码准备
python 文件
在 /root/autodl-tmp 路径下新建 vllm_model.py 文件并在其中输入以下内容,粘贴代码后请及时保存文件。下面的代码有很详细的注释,大家如有不理解的地方,欢迎提出 issue。
首先从 vLLM 库中导入 LLM 和 SamplingParams 类。LLM类是使用vLLM引擎运行离线推理的主要类。SamplingParams类指定采样过程的参数,用于控制和调整生成文本的随机性和多样性。
vLLM 提供了非常方便的封装,我们直接传入模型名称或模型路径即可,不必手动初始化模型和分词器。
我们可以通过这个 demo 熟悉下 vLLM 引擎的使用方式。被注释的部分内容可以丰富模型的能力,但不是必要的,大家可以按需选择。
from vllm import LLM, SamplingParams
from transformers import AutoTokenizer
import os
import json
# 自动下载模型时,指定使用modelscope。不设置的话,会从 huggingface 下载
# os.environ['VLLM_USE_MODELSCOPE']='True'
def get_completion(prompts, model, tokenizer=None, max_tokens=512, temperature=0.8, top_p=0.95, max_model_len=2048):
# 创建采样参数。temperature 控制生成文本的多样性,top_p 控制核心采样的概率
sampling_params = SamplingParams(temperature=temperature, top_p=top_p, max_tokens=max_tokens)
# 初始化 vLLM 推理引擎
llm = LLM(model=model, tokenizer=tokenizer, max_model_len=max_model_len)
outputs = llm.generate(prompts, sampling_params)
return outputs
if __name__ == "__main__":
# 初始化 vLLM 推理引擎
model='/root/autodl-tmp/qwen/Qwen1.5-7B-Chat' # 指定模型路径
# model="Qwen/Qwen1.5-MoE-A2.7B-Chat" # 指定模型名称,自动下载模型
tokenizer = None
# tokenizer = AutoTokenizer.from_pretrained(model, use_fast=False) # 加载分词器后传入vLLM 模型,但不是必要的。
text = ["给我介绍一下大型语言模型。",
"告诉我如何变强。"]
# messages = [
# {"role": "system", "content": "你是一个有用的助手。"},
# {"role": "user", "content": prompt}
# ]
# 作为聊天模板的消息,不是必要的。
# text = tokenizer.apply_chat_template(
# messages,
# tokenize=False,
# add_generation_prompt=True
# )
outputs = get_completion(text, model, tokenizer=tokenizer, max_tokens=512, temperature=1, top_p=1, max_model_len=2048)
# 输出是一个包含 prompt、生成文本和其他信息的 RequestOutput 对象列表。
# 打印输出。
for output in outputs:
prompt = output.prompt
generated_text = output.outputs[0].text
print(f"Prompt: {prompt!r}, Generated text: {generated_text!r}")
vLLM 模型部署 (该图片在源文档中已缺失或失效)
创建兼容 OpenAI API 接口的服务器
Qwen 兼容 OpenAI API 协议,所以我们可以直接使用 vLLM 创建 OpenAI API 服务器。vLLM 部署实现 OpenAI API 协议的服务器非常方便。默认会在 http://localhost:8000 启动服务器。服务器当前一次托管一个模型,并实现列表模型、completions 和 chat completions 端口。
completions:是基本的文本生成任务,模型会在给定的提示后生成一段文本。这种类型的任务通常用于生成文章、故事、邮件等。
chat completions:是面向对话的任务,模型需要理解和生成对话。这种类型的任务通常用于构建聊天机器人或者对话系统。
在创建服务器时,我们可以指定模型名称、模型路径、聊天模板等参数。
--host 和 --port 参数指定地址。
--model 参数指定模型名称。
--chat-template 参数指定聊天模板。
--served-model-name 指定服务模型的名称。
--max-model-len 指定模型的最大长度。
这里指定 --max-model-len=2048 是因为 Qwen1.5-7B-Chat 模型的最大长度过长 32768,导致 vLLM 初始化 KV 缓存时消耗资源过大。
python -m vllm.entrypoints.openai.api_server --model /root/autodl-tmp/qwen/Qwen1.5-7B-Chat --served-model-name Qwen1.5-7B-Chat --max-model-len=2048
curl http://localhost:8000/v1/models
通过 curl 命令查看当前的模型列表。
得到的返回值如下所示:
{"object":"list","data":[{"id":"Qwen1.5-7B-Chat","object":"model","created":1713201531,"owned_by":"vllm","root":"Qwen1.5-7B-Chat","parent":null,"permission":[{"id":"modelperm-b676428b47cb4ca19187876663da5eb3","object":"model_permission","created":1713201531,"allow_create_engine":false,"allow_sampling":true,"allow_logprobs":true,"allow_search_indices":false,"allow_view":true,"allow_fine_tuning":false,"organization":"*","group":null,"is_blocking":false}]}]}
curl http://localhost:8000/v1/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen1.5-7B-Chat",
"prompt": "你好",
"max_tokens": 7,
"temperature": 0
}'
使用 curl 命令测试 OpenAI Completions API 。
得到的返回值如下所示:
{"id":"cmpl-ca4722e3c92a4e578da8f1f8fe378b35","object":"text_completion","created":1713201551,"model":"Qwen1.5-7B-Chat","choices":[{"index":0,"text":",我有一个问题需要帮助解决","logprobs":null,"finish_reason":"length","stop_reason":null}],"usage":{"prompt_tokens":1,"total_tokens":8,"completion_tokens":7}}
也可以用 python 脚本请求 OpenAI Completions API 。
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="token-abc123", # 随便设,只是为了通过接口参数校验
)
completion = client.chat.completions.create(
model="Qwen1.5-7B-Chat",
messages=[
{"role": "user", "content": "你好"}
]
)
print(completion.choices[0].message)
得到的返回值如下所示:
ChatCompletionMessage(content='你好!有什么我能帮助你的吗?', role='assistant', function_call=None, tool_calls=None)
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen1.5-7B-Chat",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "你好"}
]
}'
用 curl 命令测试 OpenAI Chat Completions API 。
得到的返回值如下所示:
{"id":"cmpl-6f002a1ddfa2420e83808032ed912809","object":"chat.completion","created":1713201596,"model":"Qwen1.5-7B-Chat","choices":[{"index":0,"message":{"role":"assistant","content":"你好!很高兴为你提供帮助。有什么问题或者需要咨询的吗?"},"logprobs":null,"finish_reason":"stop","stop_reason":null}],"usage":{"prompt_tokens":20,"total_tokens":36,"completion_tokens":16}}
也可以用 python 脚本请求 OpenAI Chat Completions API 。
from openai import OpenAI
openai_api_key = "EMPTY" # 随便设,只是为了通过接口参数校验
openai_api_base = "http://localhost:8000/v1"
client = OpenAI(
api_key=openai_api_key,
base_url=openai_api_base,
)
chat_outputs = client.chat.completions.create(
model="Qwen1.5-7B-Chat",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "你好"},
]
)
print(chat_outputs)
得到的返回值如下所示:
ChatCompletion(id='cmpl-1889c8c4e11240e3a6cab367b26d32b5', choices=[Choice(finish_reason='stop', index=0, logprobs=None, message=ChatCompletionMessage(content='你好!很高兴能为你提供帮助。有什么问题或需要咨询的吗?', role='assistant', function_call=None, tool_calls=None), stop_reason=None)], created=1713201854, model='Qwen1.5-7B-Chat', object='chat.completion', system_fingerprint=None, usage=CompletionUsage(completion_tokens=17, prompt_tokens=20, total_tokens=37))
在处理请求时 API 后端也会打印一些日志和统计信息。
速度测试
既然说 vLLM 是一个高效的大型语言模型推理和部署服务系统,那么我们就来测试一下模型的生成速度。看看和原始的速度有多大的差距。这里直接使用 vLLM 自带的 benchmark_throughput.py 脚本进行测试。可以将当前文件夹 benchmark_throughput.py 脚本放在 /root/autodl-tmp/ 下。或者大家可以自行下载脚本。
下面是一些 benchmark_throughput.py 脚本的参数说明:
--model 参数指定模型路径或名称。
--backend 推理后端,可以是 vllm、hf 和 mii。分布对应 vLLM、HuggingFace 和 Mii 推理后端。
--input-len 输入长度
--output-len 输出长度
--num-prompts 生成的 prompt 数量
--seed 2024 随机种子
--dtype float16 浮点数精度
--max-model-len 模型最大长度
--hf_max_batch_size transformers 库的最大批处理大小(只有 hf 推理后端有效,且必须)
--dataset 数据集路径。(未设置会自动生成数据)
测试 vLLM 的速度:
python benchmark_throughput.py \
--model /root/autodl-tmp/qwen/Qwen1.5-7B-Chat \
--backend vllm \
--input-len 64 \
--output-len 128 \
--num-prompts 25 \
--seed 2024 \
--dtype float16 \
--max-model-len 512
得到的结果如下所示:
Throughput: 6.34 requests/s, 1216.34 tokens/s
测试原始方式(使用 hunggingface 的 transformers 库)的速度:
python benchmark_throughput.py \
--model /root/autodl-tmp/qwen/Qwen1.5-7B-Chat \
--backend hf \
--input-len 64 \
--output-len 128 \
--num-prompts 25 \
--seed 2024 \
--dtype float16 \
--hf-max-batch-size 25
得到的结果如下所示:
Throughput: 4.03 requests/s, 773.74 tokens/s
对比两者的速度,在本次测试中 vLLM 的速度要比原始的速度快 50% 左右(本次测试相对比较随意,仅供本 case 参考,不对其他 case 有参考意义)。
| 推理框架 | Throughput | tokens/s |
| vllm | 6.34 requests/s | 1216.34 tokens/s |
| hf | 4.03 requests/s | 773.74 tokens/s |
| diff | 57.32% | 57.10% |
Qwen1.5-7B-chat Lora 微调 接入SwanLab实验管理平台
本节我们会在前面微调训练的基础上,来加入模型实验管理工具,实现实验训练情况的检测。简要介绍如何基于swanlab、transformers、peft 等框架,对 Qwen1.5-7B-chat 模型进行 Lora 微调。Lora 是一种高效微调方法,深入了解其原理可参见博客:知乎|深入浅出Lora。
环境配置
本文基础环境如下:
----------------
ubuntu 22.04
python 3.12
cuda 12.1
pytorch 2.3.0
----------------
本文默认学习者已安装好以上 Pytorch(cuda) 环境,如未安装请自行安装。
首先 pip 换源加速下载并安装依赖包
# 升级pip
python -m pip install --upgrade pip
# 更换 pypi 源加速库的安装
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
pip install modelscope==1.16.1
pip install transformers==4.43.2
pip install accelerate==0.32.1
pip install peft==0.11.1
pip install datasets==2.20.0
考虑到部分同学配置环境可能会遇到一些问题,我们在AutoDL平台准备了Qwen1.5的环境镜像,该镜像适用于该仓库除Qwen-GPTQ和vllm外的所有部署环境。点击下方链接并直接创建Autodl示例即可。 https://www.codewithgpu.com/i/datawhalechina/self-llm/self-llm-Qwen1.5
指令集构建
LLM 的微调一般指指令微调过程。所谓指令微调,是说我们使用的微调数据形如:
{
"instruction":"回答以下用户问题,仅输出答案。",
"input":"1+1等于几?",
"output":"2"
}
其中,instruction 是用户指令,告知模型其需要完成的任务;input 是用户输入,是完成用户指令所必须的输入内容;output 是模型应该给出的输出。
即我们的核心训练目标是让模型具有理解并遵循用户指令的能力。因此,在指令集构建时,我们应针对我们的目标任务,针对性构建任务指令集。例如,在本节我们使用由笔者合作开源的 Chat-甄嬛 项目作为示例,我们的目标是构建一个能够模拟甄嬛对话风格的个性化 LLM,因此我们构造的指令形如:
{
"instruction": "你是谁?",
"input":"",
"output":"家父是大理寺少卿甄远道。"
}
我们所构造的全部指令数据集在根目录下。
数据格式化
Lora 训练的数据是需要经过格式化、编码之后再输入给模型进行训练的,如果是熟悉 Pytorch 模型训练流程的同学会知道,我们一般需要将输入文本编码为 input_ids,将输出文本编码为 labels,编码之后的结果都是多维的向量。我们首先定义一个预处理函数,这个函数用于对每一个样本,编码其输入、输出文本并返回一个编码后的字典:
def process_func(example):
MAX_LENGTH = 384 # Llama分词器会将一个中文字切分为多个token,因此需要放开一些最大长度,保证数据的完整性
input_ids, attention_mask, labels = [], [], []
instruction = tokenizer(f"<|im_start|>system\n现在你要扮演皇帝身边的女人--甄嬛<|im_end|>\n<|im_start|>user\n{example['instruction'] + example['input']}<|im_end|>\n<|im_start|>assistant\n", 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] # 因为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
}
Qwen1.5 采用的Prompt Template格式如下:
<|im_start|>system
You are a helpful assistant.<|im_end|>
<|im_start|>user
你是谁?<|im_end|>
<|im_start|>assistant
我是一个有用的助手。<|im_end|>
加载tokenizer和半精度模型
模型以半精度形式加载,如果你的显卡比较新的话,可以用torch.bfolat形式加载。对于自定义的模型一定要指定trust_remote_code参数为True。
tokenizer = AutoTokenizer.from_pretrained('./qwen/Qwen1.5-7B-Chat/', use_fast=False, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained('./qwen/Qwen1.5-7B-Chat/', device_map="auto",torch_dtype=torch.bfloat16)
定义LoraConfig
LoraConfig这个类中可以设置很多参数,但主要的参数没多少,简单讲一讲,感兴趣的同学可以直接看源码。
task_type:模型类型
target_modules:需要训练的模型层的名字,主要就是attention部分的层,不同的模型对应的层的名字不同,可以传入数组,也可以字符串,也可以正则表达式。
r:lora的秩,具体可以看Lora原理
lora_alpha:Lora alaph,具体作用参见 Lora 原理
Lora的缩放是啥嘞?当然不是r(秩),这个缩放就是lora_alpha/r, 在这个LoraConfig中缩放就是4倍。
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 比例
)
自定义 TrainingArguments 参数
TrainingArguments这个类的源码也介绍了每个参数的具体作用,当然大家可以来自行探索,这里就简单说几个常用的。
output_dir:模型的输出路径
per_device_train_batch_size:顾名思义 batch_size
gradient_accumulation_steps: 梯度累加,如果你的显存比较小,那可以把 batch_size 设置小一点,梯度累加增大一些。
logging_steps:多少步,输出一次log
num_train_epochs:顾名思义 epoch
gradient_checkpointing:梯度检查,这个一旦开启,模型就必须执行model.enable_input_require_grads(),这个原理大家可以自行探索,这里就不细说了。
args = TrainingArguments(
output_dir="./output/DeepSeek",
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
logging_steps=10,
num_train_epochs=3,
save_steps=100,
learning_rate=1e-4,
save_on_each_node=True,
gradient_checkpointing=True
)
利用SwanLab实现模型实验管理
在配置完参数后我们推荐使用模型实验管理工具来记录实验的训练情况,这样我们就不需要待在服务器命令行前盯着打印的结果了。
SwanLab的使用流程也非常简单,只需以下三步即可:
首先我们需要通过以下命令下载SwanLab库
pip install swanlab
然后我们需要去SwanLab的官方网站注册一个账号(通过手机注册即可,非常方便)
接着进入我们的个人设置界面获取 API-Key(这是将我们的数据上传到云端的关键)
目前SwanLab已经对十几个主流的开源框架进行了集成,让我们可以通过几行代码轻松实现日志记录。我们通过查询它的官方文档可以发现,SwanLab目前已经支持了Transformers Trainer的支持。
使用 Trainer 训练
引入SwanLab后,我们只需要实例化一个回调类,再将其传入Trainer中,即可实现微调训练的实验追踪
from swanlab.integration.huggingface import SwanLabCallback
swanlab_callback = SwanLabCallback(project="hf-visualization")
trainer = Trainer(
model=model,
args=args,
train_dataset=tokenized_id,
data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),
callbacks=[swanlab_callback],
)
trainer.train()
训练启动后会要求我们输入SwanLab的API-key,只需要输入我们在个人设置界面获得的API-key即可。
查看训练进展
SwanLab目前支持了在Jupyter界面中直接开启看板,来很方便的了解训练的情况。
当然也可以直接登录官网,在我们的个人账号下进行查看。
加载 lora 权重推理
训练好了之后可以使用如下方式加载lora权重进行推理:
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
from peft import PeftModel
mode_path = './qwen/Qwen1.5-7B-Chat/'
lora_path = 'lora_path'
# 加载tokenizer
tokenizer = AutoTokenizer.from_pretrained(mode_path)
# 加载模型
model = AutoModelForCausalLM.from_pretrained(mode_path, device_map="auto",torch_dtype=torch.bfloat16)
# 加载lora权重
model = PeftModel.from_pretrained(model, model_id=lora_path, config=config)
prompt = "你是谁?"
messages = [
{"role": "system", "content": "现在你要扮演皇帝身边的女人--甄嬛"},
{"role": "user", "content": prompt}
]
text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
model_inputs = tokenizer([text], return_tensors="pt").to('cuda')
generated_ids = model.generate(
model_inputs.input_ids,
max_new_tokens=512
)
generated_ids = [
output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
print(response)