微调和部署对话模型ChatGLM2-6B#
ChatGLM2-6B是中英文对话模型ChatGLM-6B 的第二代版本,在保留了初代模型对话流畅、部署门槛较低等众多优秀特性的基础之上,ChatGLM2-6B 引入了多项升级,包括更强大的性能、更长的上下文、更高效的推理。
在本示例中,我们将展示:
将ChatGLM2-6B部署到PAI创建推理服务,基于推理服务API和Gradio实现一个简易对话机器人。
在PAI对ChatGLM2-6B进行微调训练,并将微调的模型部署创建推理服务。
准备工作#
前提条件#
已获取阿里云账号的鉴权AccessKey ID和AccessKey Secret,详情请参见:获取AccessKey。
已创建或是加入一个PAI AI工作空间,详情请参见:创建工作空间。
已创建OSS Bucket,详情请参见:控制台创建存储空间。
安装和配置PAI Python SDK#
我们将使用PAI提供的Python SDK,提交训练作业,部署模型。可以通过以下命令安装PAI Python SDK。
!python -m pip install --upgrade alipai
!python -m pip install gradio
SDK需要配置访问阿里云服务需要的 AccessKey,以及当前使用的工作空间和OSS Bucket。在PAI Python SDK安装之后,通过在 命令行终端 中执行以下命令,按照引导配置密钥,工作空间等信息。
# 以下命令,请在 命令行终端 中执行.
python -m pai.toolkit.config
我们可以通过以下代码验证当前的配置。
import pai
from pai.session import get_default_session
print(pai.__version__)
sess = get_default_session()
assert sess.workspace_name is not None
print(sess.workspace_name)
直接部署ChatGLM2#
ChatGLM2-6B
是一个对话语言模型,能够基于历史对话信息,和用户的Prompt输入,进行反馈。通过HuggingFace的transformers库用户可以直接使用ChatGLM2-6B
提供的对话能力,示例如下:
>>> from transformers import AutoTokenizer, AutoModel
>>> tokenizer = AutoTokenizer.from_pretrained("THUDM/chatglm2-6b", trust_remote_code=True)
>>> model = AutoModel.from_pretrained("THUDM/chatglm2-6b", trust_remote_code=True).half().cuda()
>>> model = model.eval()
>>> response, history = model.chat(tokenizer, "你好", history=[])
>>> print(response)
你好👋!我是人工智能助手 ChatGLM2-6B,很高兴见到你,欢迎问我任何问题。
>>> response, history = model.chat(tokenizer, "晚上睡不着应该怎么办", history=history)
>>> print(response)
晚上睡不着可能会让你感到焦虑或不舒服,但以下是一些可以帮助你入睡的方法:
1. 制定规律的睡眠时间表:保持规律的睡眠时间表可以帮助你建立健康的睡眠习惯,使你更容易入睡。尽量在每天的相同时间上床,并在同一时间起床。
2. 创造一个舒适的睡眠环境:确保睡眠环境舒适,安静,黑暗且温度适宜。可以使用舒适的床上用品,并保持房间通风。
3. 放松身心:在睡前做些放松的活动,例如泡个热水澡,听些轻柔的音乐,阅读一些有趣的书籍等,有助于缓解紧张和焦虑,使你更容易入睡。
4. 避免饮用含有咖啡因的饮料:咖啡因是一种刺激性物质,会影响你的睡眠质量。尽量避免在睡前饮用含有咖啡因的饮料,例如咖啡,茶和可乐。
5. 避免在床上做与睡眠无关的事情:在床上做些与睡眠无关的事情,例如看电影,玩游戏或工作等,可能会干扰你的睡眠。
6. 尝试呼吸技巧:深呼吸是一种放松技巧,可以帮助你缓解紧张和焦虑,使你更容易入睡。试着慢慢吸气,保持几秒钟,然后缓慢呼气。
如果这些方法无法帮助你入睡,你可以考虑咨询医生或睡眠专家,寻求进一步的建议。
以下的流程中,我们将ChatGLM2-6B
部署到PAI创建一个推理服务,然后基于推理服务的API,使用Gradio创建一个对话机器人。
获取ChatGLM2模型#
推理服务和训练作业中都需要加载使用模型,PAI在部分region上提供模型缓存,支持用户能够更快地获取到相应的模型。用户可以通过以下代码获取相应的模型,然后在训练作业和推理服务中加载使用。
from pai.model import RegisteredModel
m = RegisteredModel(
"THUDM/chatglm2-6b",
model_provider="huggingface",
)
model_uri = m.model_data
print(model_uri)
创建推理服务#
PAI-EAS是阿里云PAI提供模型在线服务平台,支持用户一键部署推理服务或是AIWeb应用,支持异构资源,弹性扩缩容。PAI-EAS支持使用镜像的方式部署模型,以下的流程,我们将使用PAI提供的PyTorch推理镜像,将以上的模型部署为推理服务。
在部署推理服务之前,我们需要准备相应的推理服务程序,他负责加载模型,提供对应的HTTP API服务。
!mkdir -p server_src
完整的推理服务代码如下:
%%writefile server_src/run.py
# source: https://github.com/THUDM/ChatGLM-6B/blob/main/api.py
import os
from fastapi import FastAPI, Request
from transformers import AutoTokenizer, AutoModel, AutoConfig
import uvicorn, json, datetime
import torch
model = None
tokenizer = None
# 默认的模型保存路径
chatglm_model_path = "/eas/workspace/model/"
# ptuning checkpoints保存路径
ptuning_checkpoint = "/ml/ptuning_checkpoints/"
pre_seq_len = 128
app = FastAPI()
def load_model():
global model, tokenizer
tokenizer = AutoTokenizer.from_pretrained(chatglm_model_path, trust_remote_code=True)
if os.path.exists(ptuning_checkpoint):
# P-tuning v2
print(f"Loading model/ptuning_checkpoint weight...")
config = AutoConfig.from_pretrained(chatglm_model_path, trust_remote_code=True)
config.pre_seq_len = pre_seq_len
config.prefix_projection = False
model = AutoModel.from_pretrained(chatglm_model_path, config=config, trust_remote_code=True)
tokenizer = AutoTokenizer.from_pretrained(chatglm_model_path, trust_remote_code=True)
prefix_state_dict = torch.load(os.path.join(ptuning_checkpoint, "pytorch_model.bin"))
new_prefix_state_dict = {}
for k, v in prefix_state_dict.items():
if k.startswith("transformer.prefix_encoder."):
new_prefix_state_dict[k[len("transformer.prefix_encoder."):]] = v
model.transformer.prefix_encoder.load_state_dict(new_prefix_state_dict)
model = model.half().cuda()
model.transformer.prefix_encoder.float().cuda()
model.eval()
else:
print(f"Loading model weight...")
model = AutoModel.from_pretrained(chatglm_model_path, trust_remote_code=True)
model.half().cuda()
model.eval()
@app.post("/")
async def create_item(request: Request):
global model, tokenizer
json_post_raw = await request.json()
json_post = json.dumps(json_post_raw)
json_post_list = json.loads(json_post)
prompt = json_post_list.get('prompt')
history = json_post_list.get('history')
max_length = json_post_list.get('max_length')
top_p = json_post_list.get('top_p')
temperature = json_post_list.get('temperature')
response, history = model.chat(tokenizer,
prompt,
history=history,
max_length=max_length if max_length else 2048,
top_p=top_p if top_p else 0.7,
temperature=temperature if temperature else 0.95)
now = datetime.datetime.now()
time = now.strftime("%Y-%m-%d %H:%M:%S")
answer = {
"response": response,
"history": history,
"status": 200,
"time": time
}
log = "[" + time + "] " + '", prompt:"' + prompt + '", response:"' + repr(response) + '"'
print(log)
return answer
if __name__ == '__main__':
load_model()
uvicorn.run(app, host='0.0.0.0', port=8000, workers=1)
我们将使用PyTorch镜像运行相应的推理服务,在启动服务之前需要安装模型依赖的相关的依赖。我们可以在server_src
下准备依赖的requirements.txt
,对应的requirements.txt
会在推理服务启动之前被安装到环境中。
%%writefile server_src/requirements.txt
# 模型需要的依赖
transformers==4.30.2
accelerate
icetk
cpm_kernels
torch>=2.0,<2.1
gradio
mdtex2html
sentencepiece
accelerate
# 推理服务Server的依赖
fastapi
uvicorn
基于以上的推理服务程序,我们将使用PyTorch镜像和OSS上的模型在PAI创建一个推理服务,代码如下。
对于如何使用SDK创建推理服务的详细介绍,请见文档:创建推理服务
from pai.model import container_serving_spec, Model
from pai.image import retrieve, ImageScope
from pai.common.utils import random_str
# InferenceSpec用于描述如何创建推理服务
infer_spec = container_serving_spec(
# 使用PAI提供的最新PyTorch的推理镜像
image_uri=retrieve(
"PyTorch",
"latest",
accelerator_type="GPU",
image_scope=ImageScope.INFERENCE,
),
source_dir="./server_src",
command="python run.py",
)
m = Model(
# 模型的OSS路径,默认模型会通过挂载的方式挂载到`/eas/workspace/model/`路径下。
model_data=model_uri,
inference_spec=infer_spec,
)
# 部署模型,创建推理服务.
p = m.deploy(
service_name="chatglm_demo_{}".format(random_str(6)),
instance_type="ecs.gn6i-c8g1.2xlarge", # 8vCPU 31GB NVIDIA T4×1(GPU Mem 16GB)
options={
# 配置EAS RPC框架的超时时间, 单位为毫秒
"metadata.rpc.keepalive": 20000,
},
)
print(p.service_name)
print(p.service_status)
m.deploy
返回一个Predictor对象,可以用于向创建的推理服务程序发送预测请求。
from pai.predictor import RawResponse
resp: RawResponse = p.raw_predict(
{
"prompt": "你好",
}
)
print(resp.json()["response"])
resp = p.raw_predict(
{
"prompt": "晚上睡不着应该怎么办",
"history": resp.json()["history"],
},
timeout=20,
)
print(resp.json())
基于以上的推理服务,我们可以使用Gradio创建一个简单的对话机器人demo。
import gradio as gr
import random
import time
with gr.Blocks() as demo:
chatbot = gr.Chatbot()
msg = gr.Textbox()
clear = gr.Button("Clear")
submit = gr.Button("Submit")
def respond(message, chat_history):
print(f"Message: {message}")
print(f"ChatHistory: {chat_history}")
resp = p.raw_predict(
{
"prompt": message,
"history": chat_history,
}
).json()
print(f"Response: {resp['response']}")
chat_history.append((message, resp["response"]))
return "", chat_history
submit.click(respond, [msg, chatbot], [msg, chatbot])
demo.launch(share=True)
通过以上创建的Gradio应用,我们可以在页面上与部署的ChatGLM模型进行对话。
在测试完成之后,我们可以通过以下的代码删除推理服务,释放资源。
请注意,删除在线推理服务之后,对应的Gradio的应用将无法使用。
p.delete_service()
微调ChatGLM2-6B#
我们可以使用领域数据对ChatGLM进行微调,从而使得模型在特定领域和任务下有更好的表现。ChatGLM团队提供了使用P-Tuning v2方式对模型进行微调的方案,我们将基于此方案展示如何将微调训练作业提交到PAI的训练服务执行。
准备训练数据集#
我们将使用了广告生成数据集,对ChatGLM进行微调。我们首先需要准备数据到OSS,供后续微调训练作业使用。
from pai.common.oss_utils import download, OssUriObj, upload
import zipfile
# 下载数据
data = download(
# 当前的数据集在上海region,跨region下载,我们需要传递对应OSS Bucket所在Endpoint.
OssUriObj(
"oss://atp-modelzoo-sh.oss-cn-shanghai.aliyuncs.com/release/tutorials/chatGLM/AdvertiseGen_Simple.zip"
),
local_path="./",
)
# 解压缩数据
with zipfile.ZipFile(data, "r") as zip_ref:
zip_ref.extractall("./train_data/")
# 上传数据到OSS
train_data = "./train_data/AdvertiseGen_Simple/"
train_data_uri = upload(
"./train_data/AdvertiseGen_Simple/", oss_path="chatglm_demo/data/advertisegen/"
)
print(train_data_uri)
相应的数据集数据格式如下:
!head -n 5 ./train_data/AdvertiseGen_Simple/train.json
准备微调训练作业脚本#
ChatGLM的官方提供微调训练脚本,支持使用P-Tuning v2的方式对ChatGLM模型进行微调。我们将基于相应的微调训练脚本,修改训练作业的拉起Shell脚本(train.sh
),然后使用PAI Python SDK将微调训练作业提交到PAI执行。
# 下载ChatGLM代码
!git clone https://github.com/THUDM/ChatGLM2-6B.git
当训练作业提交到PAI执行时,需要按一定规范读取输入数据,以及将需要保存的模型写出到指定路径下,更加具体介绍请见文档:提交训练作业。
修改后的训练作业拉起脚本如下:
%%writefile ChatGLM2-6B/ptuning/train.sh
PRE_SEQ_LEN=128
LR=2e-2
NUM_GPUS=`nvidia-smi --list-gpus | wc -l`
torchrun --standalone --nnodes=1 --nproc-per-node=$NUM_GPUS main.py \
--do_train \
--train_file /ml/input/data/train/train.json \
--validation_file /ml/input/data/train/dev.json \
--preprocessing_num_workers 10 \
--prompt_column content \
--response_column summary \
--overwrite_cache \
--model_name_or_path /ml/input/data/model \
--output_dir /ml/output/model/ \
--overwrite_output_dir \
--max_source_length 64 \
--max_target_length 128 \
--per_device_train_batch_size 4 \
--per_device_eval_batch_size 4 \
--gradient_accumulation_steps 32 \
--predict_with_generate \
--num_train_epochs 10 \
--save_strategy epoch \
--learning_rate $LR \
--pre_seq_len $PRE_SEQ_LEN \
--quantization_bit 4
这里我们将使用PAI提供的PyTorch GPU训练镜像运行训练作业,需要安装部分第三方依赖包。用户可以通过提供requirements.txt
的方式提供,相应的依赖会在训练作业执行前被安装到环境中
%%writefile ChatGLM2-6B/ptuning/requirements.txt
# 模型需要的依赖
transformers==4.30.2
accelerate
icetk
cpm_kernels
torch>=2.0,<2.1
sentencepiece
accelerate
rouge_chinese
nltk
jieba
datasets
提交训练作业#
我们将通过PAI Python SDK,将以上的训练作业提交到PAI执行。SDK在提交训练作业之后,会打印训练作业的链接,用户可以通过对应的链接查看作业的执行详情,输出日志。
Note:按当前示例教程使用的训练配置、数据集和机器规格,训练作业运行约10分钟左右。
from pai.estimator import Estimator
from pai.image import retrieve
# 使用PAI提供的最新的PyTorch推理镜像
image_uri = retrieve(
"PyTorch",
"latest",
accelerator_type="GPU",
).image_uri
est = Estimator(
command="bash train.sh", # 启动命令
source_dir="./ChatGLM2-6B/ptuning", # 训练代码目录.
image_uri=image_uri, # 训练镜像
instance_type="ecs.gn6e-c12g1.3xlarge", # 使用的机器规格示例,V100(32G)
base_job_name="chatglm2_finetune_",
)
# 提交训练作业
est.fit(
inputs={
"model": model_uri,
"train": train_data_uri,
}
)
默认estimator.fit
会等待到作业执行完成。作业执行成功之后,用户可以通过est.model_data()
获取输出模型在OSS上的路径地址。
print(est.model_data())
用户可以通过ossutil
或是SDK提供的便利方法将模型下载到本地:
from pai.common.oss_util import download
# 使用SDK的便利方法下载模型到本地.
download(
oss_path=est.model_data(),
local_path="./output_model",
)
部署微调之后的模型#
微调训练之后获得的checkpoints
,需要和原始的模型配合一起使用。我们需要通过以下代码获得对应的checkpoint路径.
用户通过修改微调训练的代码,使用
Trainer.save_model()
显式的保存相应的checkpoints,则可以直接通过estimator.model_data()
下获得相应的checkpoints.
import os
# 以上的训练作业超参设置中,我们设置`epochs=2`, checkpoints保存的策略是`每一个epochs保存`。
# 默认最后一个checkpoint会被保存到`{output_dir}/checkpoint-2`路径下.
# 通过以下路径,我们可以获得模型训练获得的最后一个checkpoint的OSS路径.
checkpoint_uri = os.path.join(est.model_data(), "checkpoint-10/")
print(checkpoint_uri)
我们将复用ChatGLM2部署的推理服务程序创建推理服务。与直接部署ChatGLM2的不同点在于我们还需要提供微调之后获得的checkpoints。
通过InferenceSpec.mount
API,我们可以将相应的OSS模型路径挂载到服务容器中,供推理服务程序使用。
import os
from pai.model import container_serving_spec, Model
from pai.image import retrieve, ImageScope
# InferenceSpec用于描述如何创建推理服务
infer_spec = container_serving_spec(
image_uri=retrieve( # 使用PAI提供的最新PyTorch的推理镜像
"PyTorch",
"latest",
accelerator_type="GPU",
image_scope=ImageScope.INFERENCE,
),
source_dir="./server_src", # 代码目录
command="python run.py", # 启动命令
)
# 将相应的checkpoints挂载到服务中,推理服务的程序通过检查目录(/ml/ptuning_checkpoints/)是否存在加载checkpoints
infer_spec.mount(checkpoint_uri, "/ml/ptuning_checkpoints")
print(infer_spec.to_dict())
from pai.common.utils import random_str
m = Model(
model_data=model_uri,
inference_spec=infer_spec,
)
# 部署模型
p = m.deploy(
service_name="chatglm_ft_{}".format(random_str(6)),
instance_type="ecs.gn6i-c16g1.4xlarge", # 1 * T4
options={
# 配置EAS RPC框架的超时时间, 单位为毫秒
"metadata.rpc.keepalive": 20000,
},
)
向推理服务发送请求,测试推理服务是否正常启动。
resp = p.raw_predict(
{
"prompt": "你好",
},
)
print(resp.json())
基于以上微调后模型的推理服务,我们可以使用Gradio创建一个新的机器人。
import gradio as gr
import random
import time
with gr.Blocks() as demo:
chatbot = gr.Chatbot()
msg = gr.Textbox()
clear = gr.Button("Clear")
submit = gr.Button("Submit")
def respond(message, chat_history):
print(f"Message: {message}")
print(f"ChatHistory: {chat_history}")
resp = p.raw_predict(
{
"prompt": message,
"history": chat_history,
}
).json()
print(f"Response: {resp['response']}")
chat_history.append((message, resp["response"]))
return "", chat_history
submit.click(respond, [msg, chatbot], [msg, chatbot])
demo.launch(share=True)
在测试完成之后,可以通过p.delete_service()
删除服务,释放资源。
请注意,删除在线推理服务之后,对应的Gradio的应用将无法使用。
p.delete_service()