ChatGLM

对标题的评论会显示在这里

ChatGLM3-6B Transformers部署调用

对这一段的评论会显示在这里

环境准备

对这一段的评论会显示在这里

autodl平台中租一个3090等24G显存的显卡机器,如下图所示镜像选择PyTorch-->2.0.0-->3.8(ubuntu20.04)-->11.8

对这一段的评论会显示在这里
Alt text
Alt text
对这一段的评论会显示在这里

接下来打开刚刚租用服务器的JupyterLab,并且打开其中的终端开始环境配置、模型下载和运行demo

对这一段的评论会显示在这里

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
pip install transformers
pip install sentencepiece
对这一段的评论会显示在这里

模型下载

对这一段的评论会显示在这里

使用 modelscope 中的snapshot_download函数下载模型,第一个参数为模型名称,参数cache_dir为模型的下载路径。

对这一段的评论会显示在这里

/root/autodl-tmp 路径下新建 download.py 文件并在其中输入以下内容,粘贴代码后记得保存文件,如下图所示。并运行 python /root/autodl-tmp/download.py执行下载,模型大小为 14 GB,下载模型大概需要 10~20 分钟

对这一段的评论会显示在这里
import torch
from modelscope import snapshot_download, AutoModel, AutoTokenizer
import os
model_dir = snapshot_download('ZhipuAI/chatglm3-6b', cache_dir='/root/autodl-tmp', revision='master')
对这一段的评论会显示在这里

代码准备

对这一段的评论会显示在这里

在/root/autodl-tmp路径下新建trans.py文件并在其中输入以下内容

对这一段的评论会显示在这里
# 使用Hugging Face中'transformer'库中的AutoTokenizer和AutoModelForCausalLM以加载分词器和对话模型
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch
# 使用模型下载到的本地路径以加载
model_dir = '/root/autodl-tmp/ZhipuAI/chatglm3-6b'
# 分词器的加载,本地加载,trust_remote_code=True设置允许从网络上下载模型权重和相关的代码
tokenizer = AutoTokenizer.from_pretrained(model_dir, trust_remote_code=True)
# 模型加载,本地加载,使用AutoModelForCausalLM类
model = AutoModelForCausalLM.from_pretrained(model_dir, trust_remote_code=True)
# 将模型移动到GPU上进行加速(如果有GPU的话)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
# 使用模型的评估模式来产生对话
model.eval()
# 第一轮对话
response, history = model.chat(tokenizer, "你好", history=[])
print(response)
# 第二轮对话
response, history = model.chat(tokenizer, "请介绍一下你自己", history=history)
print(response)
# 第三轮对话
response, history = model.chat(tokenizer, "请帮我使用python语言写一段冒泡排序的代码", history=history)
print(response)
对这一段的评论会显示在这里

部署

对这一段的评论会显示在这里

在终端输入以下命令运行trans.py,即实现ChatGLM3-6B的Transformers部署调用

对这一段的评论会显示在这里
cd /root/autodl-tmp
python trans.py
对这一段的评论会显示在这里

观察命令行中loading checkpoint表示模型正在加载,等待模型加载完成产生对话,如下图所示

对这一段的评论会显示在这里
对这一段的评论会显示在这里

ChatGLM3-6B FastApi 部署调用

对这一段的评论会显示在这里

环境准备

对这一段的评论会显示在这里

autodl平台中租一个3090等24G显存的显卡机器,如下图所示镜像选择PyTorch-->1.11.0-->3.8(ubuntu20.04)-->11.3

对这一段的评论会显示在这里
Alt text
Alt text
对这一段的评论会显示在这里

接下来打开刚刚租用服务器的JupyterLab,并且打开其中的终端开始环境配置、模型下载和运行demo

对这一段的评论会显示在这里

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.104.1
pip install uvicorn==0.24.0.post1
pip install requests==2.25.1
pip install modelscope==1.9.5
pip install transformers==4.37.2
pip install streamlit==1.24.0
pip install sentencepiece==0.1.99
pip install accelerate==0.24.1
对这一段的评论会显示在这里

模型下载

对这一段的评论会显示在这里

使用 modelscope 中的snapshot_download函数下载模型,第一个参数为模型名称,参数cache_dir为模型的下载路径。

对这一段的评论会显示在这里

/root/autodl-tmp 路径下新建 download.py 文件并在其中输入以下内容,粘贴代码后记得保存文件,如下图所示。并运行 python /root/autodl-tmp/download.py执行下载,模型大小为 14 GB,下载模型大概需要 10~20 分钟

对这一段的评论会显示在这里
import torch
from modelscope import snapshot_download, AutoModel, AutoTokenizer
import os
model_dir = snapshot_download('ZhipuAI/chatglm3-6b', cache_dir='/root/autodl-tmp', revision='master')
对这一段的评论会显示在这里

代码准备

对这一段的评论会显示在这里

/root/autodl-tmp路径下新建api.py文件并在其中输入以下内容,粘贴代码后记得保存文件。下面的代码有很详细的注释,大家如有不理解的地方,欢迎提出issue。

对这一段的评论会显示在这里
from fastapi import FastAPI, Request
from transformers import AutoTokenizer, AutoModelForCausalLM
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')  # 获取请求中的提示
    history = json_post_list.get('history')  # 获取请求中的历史记录
    max_length = json_post_list.get('max_length')  # 获取请求中的最大长度
    top_p = json_post_list.get('top_p')  # 获取请求中的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,  # 如果未提供最大长度,默认使用2048
        top_p=top_p if top_p else 0.7,  # 如果未提供top_p参数,默认使用0.7
        temperature=temperature if temperature else 0.95  # 如果未提供温度参数,默认使用0.95
    )
    now = datetime.datetime.now()  # 获取当前时间
    time = now.strftime("%Y-%m-%d %H:%M:%S")  # 格式化时间为字符串
    # 构建响应JSON
    answer = {
        "response": response,
        "history": history,
        "status": 200,
        "time": time
    }
    # 构建日志信息
    log = "[" + time + "] " + '", prompt:"' + prompt + '", response:"' + repr(response) + '"'
    print(log)  # 打印日志
    torch_gc()  # 执行GPU内存清理
    return answer  # 返回响应

# 主函数入口
if __name__ == '__main__':
    # 加载预训练的分词器和模型
    tokenizer = AutoTokenizer.from_pretrained("/root/autodl-tmp/ZhipuAI/chatglm3-6b", trust_remote_code=True)
    model = AutoModelForCausalLM.from_pretrained("/root/autodl-tmp/ZhipuAI/chatglm3-6b", trust_remote_code=True).to(torch.bfloat16).cuda()
    model.eval()  # 设置模型为评估模式
    # 启动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": "你好", "history": []}'
对这一段的评论会显示在这里

调用示例结果如下图所示

对这一段的评论会显示在这里
Alt text
Alt text
对这一段的评论会显示在这里

也可以使用python中的requests库进行调用,如下所示:

对这一段的评论会显示在这里
import requests
import json

def get_completion(prompt):
    headers = {'Content-Type': 'application/json'}
    data = {"prompt": prompt, "history": []}
    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": "你好👋!我是人工智能助手 ChatGLM3-6B,很高兴见到你,欢迎问我任何问题。",
"history": [{"role": "user", "content": "你好"}, {"role": "assistant", "metadata": "", "content": "你好👋!我是人工智能助手 ChatGLM3-6B,很高兴见到你,欢迎问我任何问题。"}],
"status": 200,
"time": "2023-11-28 11:16:06"
}
对这一段的评论会显示在这里

调用结果如下图所示

对这一段的评论会显示在这里
Alt text
Alt text
对这一段的评论会显示在这里

ChatGLM3-6B-chat

对这一段的评论会显示在这里

环境准备

对这一段的评论会显示在这里

autodl平台中租一个3090等24G显存的显卡机器,如下图所示镜像选择PyTorch-->2.0.0-->3.8(ubuntu20.04)-->11.8

对这一段的评论会显示在这里
Alt text
Alt text
对这一段的评论会显示在这里

接下来打开刚刚租用服务器的JupyterLab,并且打开其中的终端开始环境配置、模型下载和运行demo

对这一段的评论会显示在这里

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
pip install transformers
对这一段的评论会显示在这里

模型下载

对这一段的评论会显示在这里

使用 modelscope 中的snapshot_download函数下载模型,第一个参数为模型名称,参数cache_dir为模型的下载路径。

对这一段的评论会显示在这里

/root/autodl-tmp 路径下新建 download.py 文件并在其中输入以下内容,粘贴代码后记得保存文件,如下图所示。并运行 python /root/autodl-tmp/download.py执行下载,模型大小为 14 GB,下载模型大概需要 10~20 分钟

对这一段的评论会显示在这里
import torch
from modelscope import snapshot_download, AutoModel, AutoTokenizer
import os
model_dir = snapshot_download('ZhipuAI/chatglm3-6b', cache_dir='/root/autodl-tmp', revision='master')
对这一段的评论会显示在这里

代码准备

对这一段的评论会显示在这里

首先clone代码,打开autodl平台自带的学术镜像加速。学术镜像加速详细使用请看:https://www.autodl.com/docs/network_turbo/

对这一段的评论会显示在这里
source /etc/network_turbo
对这一段的评论会显示在这里

然后切换路径, clone代码.

对这一段的评论会显示在这里
cd /root/autodl-tmp
git clone https://github.com/THUDM/ChatGLM3.git
对这一段的评论会显示在这里

切换commit版本,与教程commit版本保持一致,可以让大家更好的复现。

对这一段的评论会显示在这里
cd ChatGLM3
git checkout f823b4a3be9666b9b2a9daa43b29659e876a040d
对这一段的评论会显示在这里

最后取消镜像加速,因为该加速可能对正常网络造成一定影响,避免对后续下载其他模型造成困扰。

对这一段的评论会显示在这里
unset http_proxy && unset https_proxy
对这一段的评论会显示在这里

修改代码路径,将 /root/autodl-tmp/ChatGLM3/basic_demo/web_demo2.py中 13 行的模型更换为本地的/root/autodl-tmp/ZhipuAI/chatglm3-6b

对这一段的评论会显示在这里
Alt text
Alt text
对这一段的评论会显示在这里

demo运行

对这一段的评论会显示在这里

修改requirements.txt文件,将其中的torch删掉,环境中已经有了torch,不需要再安装。然后执行下面的命令:

对这一段的评论会显示在这里
cd /root/autodl-tmp/ChatGLM3
pip install -r requirements.txt
对这一段的评论会显示在这里

运行以下命令即可启动推理服务

对这一段的评论会显示在这里
cd /root/autodl-tmp/ChatGLM3
streamlit run ./basic_demo/web_demo2.py --server.address 127.0.0.1 --server.port 6006
对这一段的评论会显示在这里

autodl的端口映射到本地的 http://localhost:6006 即可看到demo界面。

对这一段的评论会显示在这里

注意:要在浏览器打开http://localhost:6006页面后,模型才会加载,如下图所示:

对这一段的评论会显示在这里
Alt text
Alt text
对这一段的评论会显示在这里

ChatGLM3-6B Code Interpreter

对这一段的评论会显示在这里

请注意,本项目需要 Python 3.10 或更高版本。

对这一段的评论会显示在这里

环境准备

对这一段的评论会显示在这里

由于项目需要python 3.10或更高版本,所以我们在在autodl平台中租一个3090等24G显存的显卡机器,如下图所示镜像选择Miniconda-->conda3-->3.10(ubuntu20.04)-->11.8

对这一段的评论会显示在这里
Alt text
Alt text
对这一段的评论会显示在这里

接下来打开刚刚租用服务器的JupyterLab,并且打开其中的终端开始环境配置、模型下载和运行demo

对这一段的评论会显示在这里

pip换源和安装依赖包

对这一段的评论会显示在这里
# 更换 pypi 源加速库的安装
# 升级pip
python -m pip install --upgrade pip

pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

pip install modelscope==1.9.5
pip install transformers==4.35.2

pip install torch==2.0.1+cu117 torchvision==0.15.2+cu117 torchaudio==2.0.2 --index-url https://download.pytorch.org/whl/cu117
对这一段的评论会显示在这里

模型下载

对这一段的评论会显示在这里

使用 modelscope 中的snapshot_download函数下载模型,第一个参数为模型名称,参数cache_dir为模型的下载路径。

对这一段的评论会显示在这里

/root/autodl-tmp 路径下新建 download.py 文件并在其中输入以下内容,粘贴代码后记得保存文件,如下图所示。并运行 python /root/autodl-tmp/download.py执行下载,模型大小为 14 GB,下载模型大概需要 10~20 分钟

对这一段的评论会显示在这里
import torch
from modelscope import snapshot_download, AutoModel, AutoTokenizer
import os
model_dir = snapshot_download('ZhipuAI/chatglm3-6b', cache_dir='/root/autodl-tmp', revision='master')
对这一段的评论会显示在这里

代码准备

对这一段的评论会显示在这里

首先clone代码,打开autodl平台自带的学术镜像加速。学术镜像加速详细使用请看:https://www.autodl.com/docs/network_turbo/

对这一段的评论会显示在这里
source /etc/network_turbo
对这一段的评论会显示在这里

然后切换路径, clone代码.

对这一段的评论会显示在这里
cd /root/autodl-tmp
git clone https://github.com/THUDM/ChatGLM3.git
对这一段的评论会显示在这里

切换commit版本,与教程commit版本保持一致,可以让大家更好的复现。

对这一段的评论会显示在这里
cd ChatGLM3
git checkout f823b4a3be9666b9b2a9daa43b29659e876a040d
对这一段的评论会显示在这里

最后取消镜像加速,因为该加速可能对正常网络造成一定影响,避免对后续下载其他模型造成困扰。

对这一段的评论会显示在这里
unset http_proxy && unset https_proxy
对这一段的评论会显示在这里

然后切换路径到composite_demo目录,并安装相关依赖

对这一段的评论会显示在这里
cd /root/autodl-tmp/ChatGLM3/composite_demo
pip install -r requirements.txt
对这一段的评论会显示在这里

demo 运行

对这一段的评论会显示在这里

在终端运行以下代码,导入模型路径和Jupyter内核:

对这一段的评论会显示在这里
export MODEL_PATH=/root/autodl-tmp/ZhipuAI/chatglm3-6b
export IPYKERNEL=python3
对这一段的评论会显示在这里

然后运行以下代码,运行demo:

对这一段的评论会显示在这里
streamlit run /root/autodl-tmp/ChatGLM3/composite_demo/main.py --server.address 127.0.0.1 --server.port 6006
对这一段的评论会显示在这里

如下图所示,可以愉快的玩耍代码解释器了~

对这一段的评论会显示在这里
Alt text
Alt text
对这一段的评论会显示在这里

基于 ChatGLM3 和 LangChain 搭建知识库助手

对这一段的评论会显示在这里

环境配置

对这一段的评论会显示在这里

在已完成 ChatGLM3 的部署基础上,还需要安装以下依赖包:

对这一段的评论会显示在这里
pip install langchain==0.0.292
pip install gradio==4.4.0
pip install chromadb==0.4.15
pip install sentence-transformers==2.2.2
pip install unstructured==0.10.30
pip install markdown==3.3.7
对这一段的评论会显示在这里

同时,我们需要使用到开源词向量模型 Sentence Transformer(HuggingFace 链接名为:sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2),可以将其模型参数以类似于下载 ChatGLM3 模型参数的方式下载到本地 /root/autodl-tmp/sentence-transformer。同时,在本节中,ChatGLM3-6B 的模型参数文件存储在本地 autodl-tmp/ZhipuAI/chatglm3-6b。

对这一段的评论会显示在这里

知识库搭建

对这一段的评论会显示在这里

我们选择 Datawhale 的一系列 LLM 开源教程作为语料库来源,包括:

对这一段的评论会显示在这里

Self LLM:一个围绕开源大模型、针对国内初学者、基于 AutoDL 平台的中国宝宝专属大模型教程。
LLM Universe:一个面向小白开发者的大模型应用开发教程,旨在结合个人知识库助手项目,通过一个课程完成大模型开发的重点入门。
LLM tutorial for Developers:一个面向开发者的 LLM 入门教程,基于吴恩达老师大模型系列课程内容实现。
So Large LLM:一个大规模预训练语言模型的教程,从数据准备、模型构建、训练策略到模型评估与改进,以及模型在安全、隐私、环境和法律道德方面的方面来提供开源知识。
Hugging LLM:介绍 ChatGPT 原理、使用和应用,降低使用门槛,让更多感兴趣的非NLP或算法专业人士能够无障碍使用LLM创造价值。

对这一段的评论会显示在这里

首先我们需要将上述远程开源仓库 Clone 到本地,可以使用以下命令:

对这一段的评论会显示在这里
# 进入到数据库盘
cd /root/autodl-tmp
# 打开学术资源加速
source /etc/network_turbo
# clone 上述开源仓库
git clone https://github.com/datawhalechina/self-llm.git
git clone https://github.com/datawhalechina/llm-universe.git
git clone https://github.com/datawhalechina/prompt-engineering-for-developers.git
git clone https://github.com/datawhalechina/so-large-lm.git
git clone https://github.com/datawhalechina/hugging-llm.git
# 关闭学术资源加速
unset http_proxy && unset https_proxy
对这一段的评论会显示在这里

接着,为语料处理方便,我们将选用上述仓库中所有的 markdown、txt 文件作为示例语料库。注意,也可以选用其中的代码文件加入到知识库中,但需要针对代码文件格式进行额外处理。

对这一段的评论会显示在这里

我们首先将上述仓库中所有满足条件的文件路径找出来,我们定义一个函数,该函数将递归指定文件夹路径,返回其中所有满足条件(即后缀名为 .md 或者 .txt 的文件)的文件路径:

对这一段的评论会显示在这里
import os 
def get_files(dir_path):
    # args:dir_path,目标文件夹路径
    file_list = []
    for filepath, dirnames, filenames in os.walk(dir_path):
        # os.walk 函数将递归遍历指定文件夹
        for filename in filenames:
            # 通过后缀名判断文件类型是否满足要求
            if filename.endswith(".md"):
                # 如果满足要求,将其绝对路径加入到结果列表
                file_list.append(os.path.join(filepath, filename))
            elif filename.endswith(".txt"):
                file_list.append(os.path.join(filepath, filename))
    return file_list
对这一段的评论会显示在这里

得到所有目标文件路径之后,我们可以使用 LangChain 提供的 FileLoader 对象来加载目标文件,得到由目标文件解析出的纯文本内容。由于不同类型的文件需要对应不同的 FileLoader,我们判断目标文件类型,并针对性调用对应类型的 FileLoader,同时,调用 FileLoader 对象的 load 方法来得到加载之后的纯文本对象:

对这一段的评论会显示在这里
from tqdm import tqdm
from langchain.document_loaders import UnstructuredFileLoader
from langchain.document_loaders import UnstructuredMarkdownLoader

def get_text(dir_path):
    # args:dir_path,目标文件夹路径
    # 首先调用上文定义的函数得到目标文件路径列表
    file_lst = get_files(dir_path)
    # docs 存放加载之后的纯文本对象
    docs = []
    # 遍历所有目标文件
    for one_file in tqdm(file_lst):
        file_type = one_file.split('.')[-1]
        if file_type == 'md':
            loader = UnstructuredMarkdownLoader(one_file)
        elif file_type == 'txt':
            loader = UnstructuredFileLoader(one_file)
        else:
            # 如果是不符合条件的文件,直接跳过
            continue
        docs.extend(loader.load())
    return docs
对这一段的评论会显示在这里

使用上文函数,我们得到的 docs 为一个纯文本对象对应的列表。得到该列表之后,我们就可以将它引入到 LangChain 框架中构建向量数据库。由纯文本对象构建向量数据库,我们需要先对文本进行分块,接着对文本块进行向量化。

对这一段的评论会显示在这里

LangChain 提供了多种文本分块工具,此处我们使用字符串递归分割器,并选择分块大小为 500,块重叠长度为 150:

对这一段的评论会显示在这里
from langchain.text_splitter import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=500, chunk_overlap=150)
split_docs = text_splitter.split_documents(docs)
对这一段的评论会显示在这里

接着我们选用开源词向量模型 Sentence Transformer来进行文本向量化。LangChain 提供了直接引入 HuggingFace 开源社区中的模型进行向量化的接口:

对这一段的评论会显示在这里
from langchain.embeddings.huggingface import HuggingFaceEmbeddings

embeddings = HuggingFaceEmbeddings(model_name="autodl-tmp/sentence-transformer")
对这一段的评论会显示在这里

同时,我们选择 Chroma 作为向量数据库,基于上文分块后的文档以及加载的开源向量化模型,将语料加载到指定路径下的向量数据库:

对这一段的评论会显示在这里
from langchain.vectorstores import Chroma

# 定义持久化路径
persist_directory = 'data_base/vector_db/chroma'
# 加载数据库
vectordb = Chroma.from_documents(
    documents=split_docs,
    embedding=embeddings,
    persist_directory=persist_directory  # 允许我们将persist_directory目录保存到磁盘上
)
# 将加载的向量数据库持久化到磁盘上
vectordb.persist()
对这一段的评论会显示在这里

将上述代码整合在一起为知识库搭建的脚本:

对这一段的评论会显示在这里
# 首先导入所需第三方库
from langchain.document_loaders import UnstructuredFileLoader
from langchain.document_loaders import UnstructuredMarkdownLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
from tqdm import tqdm
import os

# 获取文件路径函数
def get_files(dir_path):
    # args:dir_path,目标文件夹路径
    file_list = []
    for filepath, dirnames, filenames in os.walk(dir_path):
        # os.walk 函数将递归遍历指定文件夹
        for filename in filenames:
            # 通过后缀名判断文件类型是否满足要求
            if filename.endswith(".md"):
                # 如果满足要求,将其绝对路径加入到结果列表
                file_list.append(os.path.join(filepath, filename))
            elif filename.endswith(".txt"):
                file_list.append(os.path.join(filepath, filename))
    return file_list

# 加载文件函数
def get_text(dir_path):
    # args:dir_path,目标文件夹路径
    # 首先调用上文定义的函数得到目标文件路径列表
    file_lst = get_files(dir_path)
    # docs 存放加载之后的纯文本对象
    docs = []
    # 遍历所有目标文件
    for one_file in tqdm(file_lst):
        file_type = one_file.split('.')[-1]
        if file_type == 'md':
            loader = UnstructuredMarkdownLoader(one_file)
        elif file_type == 'txt':
            loader = UnstructuredFileLoader(one_file)
        else:
            # 如果是不符合条件的文件,直接跳过
            continue
        docs.extend(loader.load())
    return docs

# 目标文件夹
tar_dir = [
    "/root/autodl-tmp/self-llm",
    "/root/autodl-tmp/llm-universe",
    "/root/autodl-tmp/prompt-engineering-for-developers",
    "/root/autodl-tmp/so-large-lm",
    "/root/autodl-tmp/hugging-llm",
]

# 加载目标文件
docs = []
for dir_path in tar_dir:
    docs.extend(get_text(dir_path))

# 对文本进行分块
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=500, chunk_overlap=150)
split_docs = text_splitter.split_documents(docs)

# 加载开源词向量模型
embeddings = HuggingFaceEmbeddings(model_name="autodl-tmp/sentence-transformer")

# 构建向量数据库
# 定义持久化路径
persist_directory = 'data_base/vector_db/chroma'
# 加载数据库
vectordb = Chroma.from_documents(
    documents=split_docs,
    embedding=embeddings,
    persist_directory=persist_directory  # 允许我们将persist_directory目录保存到磁盘上
)
# 将加载的向量数据库持久化到磁盘上
vectordb.persist()
对这一段的评论会显示在这里

运行上述脚本,即可在本地构建已持久化的向量数据库,后续直接导入该数据库即可,无需重复构建。

对这一段的评论会显示在这里

InternLM 接入 LangChain

对这一段的评论会显示在这里

为便捷构建 LLM 应用,我们需要基于本地部署的 ChatGLM3-6B,自定义一个 LLM 类,将 ChatGLM 接入到 LangChain 框架中。完成自定义 LLM 类之后,可以以完全一致的方式调用 LangChain 的接口,而无需考虑底层模型调用的不一致。

对这一段的评论会显示在这里

基于本地部署的 ChatGLM3-6B 自定义 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

class ChatGLM_LLM(LLM):
    # 基于本地 InternLM 自定义 LLM 类
    tokenizer : AutoTokenizer = None
    model: AutoModelForCausalLM = None

    def __init__(self, model_path :str):
        # model_path: InternLM 模型路径
        # 从本地初始化模型
        super().__init__()
        print("正在从本地加载模型...")
        self.tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
        self.model = AutoModelForCausalLM.from_pretrained(model_path, trust_remote_code=True).to(torch.bfloat16).cuda()
        self.model = self.model.eval()
        print("完成本地模型的加载")

    def _call(self, prompt : str, stop: Optional[List[str]] = None,
                run_manager: Optional[CallbackManagerForLLMRun] = None,
                **kwargs: Any):
        # 重写调用函数
        response, history = self.model.chat(self.tokenizer, prompt , history=[])
        return response
        
    @property
    def _llm_type(self) -> str:
        return "ChatGLM3-6B"
对这一段的评论会显示在这里

在上述类定义中,我们分别重写了构造函数和 _call 函数:对于构造函数,我们在对象实例化的一开始加载本地部署的 ChatGLM3-6B 模型,从而避免每一次调用都需要重新加载模型带来的时间过长;_call 函数是 LLM 类的核心函数,LangChain 会调用该函数来调用 LLM,在该函数中,我们调用已实例化模型的 chat 方法,从而实现对模型的调用并返回调用结果。

对这一段的评论会显示在这里

在整体项目中,我们将上述代码封装为 llm.py,后续将直接从该文件中引入自定义的 LLM 类。

对这一段的评论会显示在这里

构建检索问答链

对这一段的评论会显示在这里

LangChain 通过提供检索问答链对象来实现对于 RAG 全流程的封装。即我们可以调用一个 LangChain 提供的 RetrievalQA 对象,通过初始化时填入已构建的数据库和自定义 LLM 作为参数,来简便地完成检索增强问答的全流程,LangChain 会自动完成基于用户提问进行检索、获取相关文档、拼接为合适的 Prompt 并交给 LLM 问答的全部流程。

对这一段的评论会显示在这里

首先我们需要将上文构建的向量数据库导入进来,我们可以直接通过 Chroma 以及上文定义的词向量模型来加载已构建的数据库:

对这一段的评论会显示在这里
from langchain.vectorstores import Chroma
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
import os

# 定义 Embeddings
embeddings = HuggingFaceEmbeddings(model_name="autodl-tmp/sentence-transformer")

# 向量数据库持久化路径
persist_directory = 'data_base/vector_db/chroma'

# 加载数据库
vectordb = Chroma(
    persist_directory=persist_directory, 
    embedding_function=embeddings
)
对这一段的评论会显示在这里

上述代码得到的 vectordb 对象即为我们已构建的向量数据库对象,该对象可以针对用户的 query 进行语义向量检索,得到与用户提问相关的知识片段。

对这一段的评论会显示在这里

接着,我们实例化一个基于 ChatGLM3-6B 自定义的 LLM 对象:

对这一段的评论会显示在这里
from LLM import ChatGLM_LLM
llm = ChatGLM_LLM(model_path = "autodl-tmp/ZhipuAI/chatglm3-6b")
llm.predict("你是谁")
对这一段的评论会显示在这里

构建检索问答链,还需要构建一个 Prompt Template,该 Template 其实基于一个带变量的字符串,在检索之后,LangChain 会将检索到的相关文档片段填入到 Template 的变量中,从而实现带知识的 Prompt 构建。我们可以基于 LangChain 的 Template 基类来实例化这样一个 Template 对象:

对这一段的评论会显示在这里
from langchain.prompts import PromptTemplate

# 我们所构造的 Prompt 模板
template = """使用以下上下文来回答最后的问题。如果你不知道答案,就说你不知道,不要试图编造答案。尽量使答案简明扼要。总是在回答的最后说“谢谢你的提问!”。
{context}
问题: {question}
有用的回答:"""

# 调用 LangChain 的方法来实例化一个 Template 对象,该对象包含了 context 和 question 两个变量,在实际调用时,这两个变量会被检索到的文档片段和用户提问填充
QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context","question"],template=template)
对这一段的评论会显示在这里

最后,可以调用 LangChain 提供的检索问答链构造函数,基于我们的自定义 LLM、Prompt Template 和向量知识库来构建一个基于 InternLM 的检索问答链:

对这一段的评论会显示在这里
from langchain.chains import RetrievalQA

qa_chain = RetrievalQA.from_chain_type(llm,retriever=vectordb.as_retriever(),return_source_documents=True,chain_type_kwargs={"prompt":QA_CHAIN_PROMPT})
对这一段的评论会显示在这里

得到的 qa_chain 对象即可以实现我们的核心功能,即基于 InternLM 模型的专业知识库助手。我们可以对比该检索问答链和纯 LLM 的问答效果:

对这一段的评论会显示在这里
# 检索问答链回答效果
question = "什么是 Self LLM"
result = qa_chain({"query": question})
print("检索问答链回答 question 的结果:")
print(result["result"])

# 仅 LLM 回答效果
result_2 = llm(question)
print("大模型回答 question 的结果:")
print(result_2)
对这一段的评论会显示在这里

部署 Web Demo

对这一段的评论会显示在这里

在完成上述核心功能后,我们可以基于 Gradio 框架将其部署到 Web 网页,从而搭建一个小型 Demo,便于测试与使用。

对这一段的评论会显示在这里

我们首先将上文的代码内容封装为一个返回构建的检索问答链对象的函数,并在启动 Gradio 的第一时间调用该函数得到检索问答链对象,后续直接使用该对象进行问答对话,从而避免重复加载模型:

对这一段的评论会显示在这里
from langchain.vectorstores import Chroma
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
import os
from LLM import ChatGLM_LLM
from langchain.prompts import PromptTemplate
from langchain.chains import RetrievalQA

def load_chain():
    # 加载问答链
    # 定义 Embeddings
    embeddings = HuggingFaceEmbeddings(model_name="autodl-tmp/sentence-transformer")

    # 向量数据库持久化路径
    persist_directory = 'data_base/vector_db/chroma'

    # 加载数据库
    vectordb = Chroma(
        persist_directory=persist_directory,  # 允许我们将persist_directory目录保存到磁盘上
        embedding_function=embeddings
    )

    # 加载自定义 LLM
    llm = ChatGLM_LLM(model_path = "autodl-tmp/ZhipuAI/chatglm3-6b")

    # 定义一个 Prompt Template
    template = """使用以下上下文来回答最后的问题。如果你不知道答案,就说你不知道,不要试图编造答
    案。尽量使答案简明扼要。总是在回答的最后说“谢谢你的提问!”。
    {context}
    问题: {question}
    有用的回答:"""

    QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context","question"],template=template)

    # 运行 chain
    qa_chain = RetrievalQA.from_chain_type(llm,retriever=vectordb.as_retriever(),return_source_documents=True,chain_type_kwargs={"prompt":QA_CHAIN_PROMPT})
    
    return qa_chain
对这一段的评论会显示在这里

接着我们定义一个类,该类负责加载并存储检索问答链,并响应 Web 界面里调用检索问答链进行回答的动作:

对这一段的评论会显示在这里
class Model_center():
    """
    存储检索问答链的对象 
    """
    def __init__(self):
        # 构造函数,加载检索问答链
        self.chain = load_chain()

    def qa_chain_self_answer(self, question: str, chat_history: list = []):
        """
        调用问答链进行回答
        """
        if question == None or len(question) < 1:
            return "", chat_history
        try:
            chat_history.append(
                (question, self.chain({"query": question})["result"]))
            # 将问答结果直接附加到问答历史中,Gradio 会将其展示出来
            return "", chat_history
        except Exception as e:
            return e, chat_history
对这一段的评论会显示在这里

然后我们只需按照 Gradio 的框架使用方法,实例化一个 Web 界面并将点击动作绑定到上述类的回答方法即可:

对这一段的评论会显示在这里
import gradio as gr

# 实例化核心功能对象
model_center = Model_center()
# 创建一个 Web 界面
block = gr.Blocks()
with block as demo:
    with gr.Row(equal_height=True):   
        with gr.Column(scale=15):
            # 展示的页面标题
            gr.Markdown("""<h1><center>Self LLM</center></h1>
                <center>Self LLM</center>
                """)

    with gr.Row():
        with gr.Column(scale=4):
            # 创建一个聊天机器人对象
            chatbot = gr.Chatbot(height=450, show_copy_button=True)
            # 创建一个文本框组件,用于输入 prompt。
            msg = gr.Textbox(label="Prompt/问题")

            with gr.Row():
                # 创建提交按钮。
                db_wo_his_btn = gr.Button("Chat")
            with gr.Row():
                # 创建一个清除按钮,用于清除聊天机器人组件的内容。
                clear = gr.ClearButton(
                    components=[chatbot], value="Clear console")
                
        # 设置按钮的点击事件。当点击时,调用上面定义的 qa_chain_self_answer 函数,并传入用户的消息和聊天历史记录,然后更新文本框和聊天机器人组件。
        db_wo_his_btn.click(model_center.qa_chain_self_answer, inputs=[
                            msg, chatbot], outputs=[msg, chatbot])
        
    gr.Markdown("""提醒:<br>
    1. 初始化数据库时间可能较长,请耐心等待。
    2. 使用中如果出现异常,将会在文本输入框进行展示,请不要惊慌。 <br>
    """)
gr.close_all()
# 直接启动
demo.launch()
对这一段的评论会显示在这里

通过将上述代码封装为 run_gradio.py 脚本,直接通过 python 命令运行,即可在本地启动知识库助手的 Web Demo,默认会在 7860 端口运行,使用类似于部署的方式将服务器端口映射到本地端口即可访问:

对这一段的评论会显示在这里
对这一段的评论会显示在这里

06-ChatGLM3-6B-chat Lora 微调

对这一段的评论会显示在这里

概述

对这一段的评论会显示在这里

本节我们简要介绍如何基于 transformers、peft 等框架,对 ChatGLM3-6B-chat 模型进行 Lora 微调。Lora 是一种高效微调方法,深入了解其原理可参见博客:知乎|深入浅出Lora

对这一段的评论会显示在这里

本节所讲述的代码脚本在同级目录 ChatGLM3-6B-chat Lora 微调 下,运行该脚本来执行微调过程,但注意,本文代码未使用分布式框架,微调 ChatGLM3-6B-Chat 模型至少需要 21G 及以上的显存,且需要修改脚本文件中的模型路径和数据集路径。

对这一段的评论会显示在这里

这个教程会在同目录下给大家提供一个 nodebook 文件,来让大家更好的学习。 鸣谢大佬的详细讲解与教程: transformers-code

对这一段的评论会显示在这里

环境配置

对这一段的评论会显示在这里

在完成基本环境配置和本地模型部署的情况下,你还需要安装一些第三方库,可以使用以下命令:

对这一段的评论会显示在这里
pip install transformers==4.37.2
pip install peft==0.4.0.dev0
pip install datasets==2.10.1
pip install accelerate==0.21.0
对这一段的评论会显示在这里

在本节教程里,我们将微调数据集放置在根目录 /dataset

对这一段的评论会显示在这里

指令集构建

对这一段的评论会显示在这里

LLM 的微调一般指指令微调过程。所谓指令微调,是说我们使用的微调数据形如:

对这一段的评论会显示在这里
{
    "instrution":"回答以下用户问题,仅输出答案。",
    "input":"1+1等于几?",
    "output":"2"
}
对这一段的评论会显示在这里

其中,instruction 是用户指令,告知模型其需要完成的任务;input 是用户输入,是完成用户指令所必须的输入内容;output 是模型应该给出的输出。

对这一段的评论会显示在这里

即我们的核心训练目标是让模型具有理解并遵循用户指令的能力。因此,在指令集构建时,我们应针对我们的目标任务,针对性构建任务指令集。例如,在本节我们使用由笔者合作开源的 Chat-甄嬛 项目作为示例,我们的目标是构建一个能够模拟甄嬛对话风格的个性化 LLM,因此我们构造的指令形如:

对这一段的评论会显示在这里
{
    "instruction": "",
    "input":"你是谁?",
    "output":"家父是大理寺少卿甄远道。"
}
对这一段的评论会显示在这里

我们所构造的全部指令数据集在根目录下。

对这一段的评论会显示在这里

QA和Instruction的区别与联系

对这一段的评论会显示在这里

QA是指一问一答的形式,通常是用户提问,模型给出回答。而Instruction则源自于Prompt Engineering,将问题拆分为两个部分:Instruction用于描述任务,Input用于描述待处理的对象。

对这一段的评论会显示在这里

问答(QA)格式的训练数据通常用于训练模型回答基于知识的问题,而指令(Instruction)格式的训练数据更适用于训练模型执行具体任务。例如,对于问题"请解释VC银翘片和双黄连口服液之间的区别"

对这一段的评论会显示在这里
    指令(Instruction): 
    输入(Input):VC银翘片和双黄连口服液之间的区别是什么?
对这一段的评论会显示在这里

问答(QA)格式:

对这一段的评论会显示在这里
    指令(Instruction):请解释下面两个药品之间的区别。
    输入(Input):VC银翘片和双黄连口服液。
对这一段的评论会显示在这里

指令(Instruction)格式:

对这一段的评论会显示在这里

指令的形式可能使模型具有更好的泛化能力,因为它强调了任务的性质,而不仅仅是特定的输入。通常指令格式和问答格式可以相互转化。

对这一段的评论会显示在这里

数据格式化

对这一段的评论会显示在这里

Lora 训练的数据是需要经过格式化、编码之后再输入给模型进行训练的,如果是熟悉 Pytorch 模型训练流程的同学会知道,我们一般需要将输入文本编码为 input_ids,将输出文本编码为 labels,编码之后的结果都是多维的向量。我们首先定义一个预处理函数,这个函数用于对每一个样本,编码其输入、输出文本并返回一个编码后的字典:

对这一段的评论会显示在这里
def process_func(example):
    MAX_LENGTH = 512
    input_ids, labels = [], []
    instruction = tokenizer.encode(text="\n".join(["<|system|>", "现在你要扮演皇帝身边的女人--甄嬛", "<|user|>", 
                                    example["instruction"] + example["input"] + "<|assistant|>"]).strip() + "\n",
                                    add_special_tokens=True, truncation=True, max_length=MAX_LENGTH)

    response = tokenizer.encode(text=example["output"], add_special_tokens=False, truncation=True,
    max_length=MAX_LENGTH)

    input_ids = instruction + response + [tokenizer.eos_token_id]
    labels = [tokenizer.pad_token_id] * len(instruction) + response + [tokenizer.eos_token_id]
    pad_len = MAX_LENGTH - len(input_ids)
    input_ids += [tokenizer.pad_token_id] * pad_len
    labels += [tokenizer.pad_token_id] * pad_len
    labels = [(l if l != tokenizer.pad_token_id else -100) for l in labels]

    return {
        "input_ids": input_ids,
        "labels": labels
    }
对这一段的评论会显示在这里

经过格式化的数据,也就是送入模型的每一条数据,都是一个字典,包含了 input_idslabels 两个键值对,其中 input_ids 是输入文本的编码,labels 是输出文本的编码。decode之后应该是这样的:

对这一段的评论会显示在这里
[gMASK]sop <|system|>
现在你要扮演皇帝身边的女人--甄嬛
<|user|>
这个温太医啊,也是古怪,谁不知太医不得皇命不能为皇族以外的人请脉诊病,他倒好,十天半月便往咱们府里跑。<|assistant|>
 你们俩话太多了,我该和温太医要一剂药,好好治治你们。
对这一段的评论会显示在这里

为什么会是这个形态呢?好问题!不同模型所对应的格式化输入都不一样,所以需要我们深度模型的训练源码来查看,因为按照原本模型指令微调的形式进行Lora微调效果应该是最好的,所以我们依然遵循原本模型的输入格式。OK,这里我给大家放一下源码的链接,各位如果感兴趣可以自行探索一下:

对这一段的评论会显示在这里

hugging face ChatGLM3仓库:其中的InputOutputDataset类。 此外,还可以参考这个仓库对ChatGLM的数据处理LLaMA-Factory

对这一段的评论会显示在这里

加载tokenizer和半精度模型

对这一段的评论会显示在这里

模型以半精度形式加载,如果你的显卡比较新的话,可以用torch.bfolat形式加载。对于自定义的模型一定要指定trust_remote_code参数为True

对这一段的评论会显示在这里
tokenizer = AutoTokenizer.from_pretrained('./model/chatglm3-6b', use_fast=False, trust_remote_code=True)

# 模型以半精度形式加载,如果你的显卡比较新的话,可以用torch.bfolat形式加载
model = AutoModelForCausalLM.from_pretrained('./model/chatglm3-6b', trust_remote_code=True, torch_dtype=torch.half, device_map="auto")
对这一段的评论会显示在这里

定义LoraConfig

对这一段的评论会显示在这里

LoraConfig这个类中可以设置很多参数,但主要的参数没多少,简单讲一讲,感兴趣的同学可以直接看源码。

对这一段的评论会显示在这里

task_type:模型类型
target_modules:需要训练的模型层的名字,主要就是attention部分的层,不同的模型对应的层的名字不同,可以传入数组,也可以字符串,也可以正则表达式。
rlora的秩,具体可以看Lora原理
lora_alphaLora alaph,具体作用参见 Lora 原理
modules_to_save指定的是除了拆成lora的模块,其他的模块可以完整的指定训练。

对这一段的评论会显示在这里

Lora的缩放是啥嘞?当然不是r(秩),这个缩放就是lora_alpha/r, 在这个LoraConfig中缩放就是4倍。 这个缩放的本质并没有改变LoRa的参数量大小,本质在于将里面的参数数值做广播乘法,进行线性的缩放。

对这一段的评论会显示在这里
config = LoraConfig(
    task_type=TaskType.CAUSAL_LM, 
    target_modules=["query_key_value"],
    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(),这个原理大家可以自行探索,这里就不细说了。

对这一段的评论会显示在这里
# Data collator GLM源仓库从新封装了自己的data_collator,在这里进行沿用。

data_collator = DataCollatorForSeq2Seq(
    tokenizer,
    model=model,
    label_pad_token_id=-100,
    pad_to_multiple_of=None,
    padding=False
)

args = TrainingArguments(
    output_dir="./output/ChatGLM",
    per_device_train_batch_size=4,
    gradient_accumulation_steps=2,
    logging_steps=10,
    num_train_epochs=3,
    gradient_checkpointing=True,
    save_steps=100,
    learning_rate=1e-4,
)
对这一段的评论会显示在这里

使用 Trainer 训练

对这一段的评论会显示在这里

把 model 放进去,把上面设置的参数放进去,数据集放进去,OK!开始训练!

对这一段的评论会显示在这里
trainer = Trainer(
    model=model,
    args=args,
    train_dataset=tokenized_id,
    data_collator=data_collator,
)
trainer.train()
对这一段的评论会显示在这里

模型推理

对这一段的评论会显示在这里

可以用这种比较经典的方式推理。

对这一段的评论会显示在这里
model.eval()
model = model.cuda()
ipt = tokenizer("<|system|>\n现在你要扮演皇帝身边的女人--甄嬛\n<|user|>\n {}\n{}".format("你是谁?", "").strip() + "<|assistant|>\n", return_tensors="pt").to(model.device)
tokenizer.decode(model.generate(**ipt, max_length=128, do_sample=True)[0], skip_special_tokens=True)
对这一段的评论会显示在这里

从新加载

对这一段的评论会显示在这里

通过PEFT所微调的模型,都可以使用下面的方法进行重新加载,并推理:

对这一段的评论会显示在这里

加载源model与tokenizer;
使用PeftModel合并源model与PEFT微调后的参数。

对这一段的评论会显示在这里
from peft import PeftModel

model = AutoModelForCausalLM.from_pretrained("./model/chatglm3-6b", trust_remote_code=True, low_cpu_mem_usage=True)
tokenizer = AutoTokenizer.from_pretrained("./model/chatglm3-6b", use_fast=False, trust_remote_code=True)

p_model = PeftModel.from_pretrained(model, model_id="./output/ChatGLM/checkpoint-1000/")  # 将训练所得的LoRa权重加载起来

ipt = tokenizer("<|system|>\n现在你要扮演皇帝身边的女人--甄嬛\n<|user|>\n {}\n{}".format("你是谁?", "").strip() + "<|assistant|>\n", return_tensors="pt").to(model.device)
tokenizer.decode(p_model.generate(**ipt, max_length=128, do_sample=True)[0], skip_special_tokens=True)
对这一段的评论会显示在这里

ChatGLM3-6B Pipeline

对这一段的评论会显示在这里

本文档基于ChatGLM的前六节内容做整合,构建pipeline版本,修复过程中的版本不匹配问题,及在05节中的编码问题,目的是快速形成对大模型部署的体感。

对这一段的评论会显示在这里

环境配置

对这一段的评论会显示在这里

模型选择:ChatGLM3-6B,模型大小14G
部署平台:AutoML,环境配置4090单卡24G显寸
镜像选择:pytorch 2.1.0+python 3.10+cuda 12.1

对这一段的评论会显示在这里

部署方式

对这一段的评论会显示在这里

为方便管理各个模块的版本,我们采用uv方式进行部署:

对这一段的评论会显示在这里
curl -LsSf https://astral.sh/uv/install.sh | shsource $HOME/.cargo/env
对这一段的评论会显示在这里

安装uv
创建项目环境

对这一段的评论会显示在这里
mkdir -p /root/autodl-tmp/chatglm && cd /root/autodl-tmp/chatglm
对这一段的评论会显示在这里
uv venv --python 3.10 chatglm-env
对这一段的评论会显示在这里

初始化虚拟环境

对这一段的评论会显示在这里
source chatglm-env/bin/activate
对这一段的评论会显示在这里

激活虚拟环境

对这一段的评论会显示在这里
# AutoDL开启镜像加速
source /etc/network_turbo

# 一键安装
uv pip install -r requirements.txt
对这一段的评论会显示在这里

安装requirements.txt

对这一段的评论会显示在这里

Transformer 基础部署

对这一段的评论会显示在这里

打开jupyter切换ipykernel,选择我们新创建的环境

对这一段的评论会显示在这里
1.jpg
1.jpg
对这一段的评论会显示在这里

模型下载

对这一段的评论会显示在这里

模型大小为14GB,开镜像加速后下载。

对这一段的评论会显示在这里
import torch
from modelscope import snapshot_download, AutoModel, AutoTokenizer
import os
model_dir = snapshot_download('ZhipuAI/chatglm3-6b', cache_dir='/root/autodl-tmp', revision='master')
对这一段的评论会显示在这里
2.jpg
2.jpg
对这一段的评论会显示在这里

导入库和配置

对这一段的评论会显示在这里
# 使用Hugging Face中'transformer'库中的AutoTokenizer和AutoModelForCausalLM以加载分词器和对话模型
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch

# 使用模型下载到的本地路径以加载
model_dir = '/root/autodl-tmp/ZhipuAI/chatglm3-6b'
print(f"模型路径: {model_dir}")
对这一段的评论会显示在这里
# 分词器的加载,本地加载,trust_remote_code=True设置允许从网络上下载模型权重和相关的代码
print("正在加载分词器...")
tokenizer = AutoTokenizer.from_pretrained(model_dir, trust_remote_code=True)

# 模型加载,本地加载,使用AutoModelForCausalLM类
print("正在加载模型...")
model = AutoModelForCausalLM.from_pretrained(model_dir, trust_remote_code=True)

# 将模型移动到GPU上进行加速(如果有GPU的话)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"使用设备: {device}")
model.to(device)

# 使用模型的评估模式来产生对话
model.eval()
print("模型加载完成!")
对这一段的评论会显示在这里

加载分词器和模型

对这一段的评论会显示在这里
3.jpg
3.jpg
对这一段的评论会显示在这里
# 第一轮对话
print("=== 第一轮对话 ===")
response, history = model.chat(tokenizer, "你好", history=[])
print(f"用户: 你好")
print(f"ChatGLM: {response}")
print()

# 第二轮对话
print("=== 第二轮对话 ===")
response, history = model.chat(tokenizer, "请介绍一下你自己", history=history)
print(f"用户: 请介绍一下你自己")
print(f"ChatGLM: {response}")
print()

# 第三轮对话
print("=== 第三轮对话 ===")
response, history = model.chat(tokenizer, "请帮我使用python语言写一段冒泡排序的代码", history=history)
print(f"用户: 请帮我使用python语言写一段冒泡排序的代码")
print(f"ChatGLM: {response}")
对这一段的评论会显示在这里

对话测试

对这一段的评论会显示在这里
4.jpg
4.jpg
对这一段的评论会显示在这里

FastApi服务化部署

对这一段的评论会显示在这里

通过FastAPI部署,让本地的ChatGLM3-6B模型变成一个服务,可以被任何支持HTTP的客户端调用,这样其他系统只需要通过HTTP接口,就可以有使用AI的能力。

对这一段的评论会显示在这里

在AutoDL里,通过终端运行服务:

对这一段的评论会显示在这里
# api.py
from fastapi import FastAPI, Request
from transformers import AutoTokenizer, AutoModelForCausalLM
import uvicorn
import json
import datetime
import torch

# 设置设备参数
DEVICE = "cuda"
DEVICE_ID = "0"
CUDA_DEVICE = f"{DEVICE}:{DEVICE_ID}" if DEVICE_ID else DEVICE

# 创建FastAPI应用
app = FastAPI()

# 处理POST请求的端点
@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")
    
    # 构建响应JSON
    answer = {
        "response": response,
        "history": history,
        "status": 200,
        "time": time
    }
    
    # 构建日志信息
    log = "[" + time + "] " + '", prompt:"' + prompt + '", response:"' + repr(response) + '"'
    print(log)
    return answer

# 主函数入口
if __name__ == '__main__':
    # 加载预训练的分词器和模型 - PyTorch 2.0优化版本
    tokenizer = AutoTokenizer.from_pretrained(
        "/root/autodl-tmp/ZhipuAI/chatglm3-6b", 
        trust_remote_code=True
    )
    
    model = AutoModelForCausalLM.from_pretrained(
        "/root/autodl-tmp/ZhipuAI/chatglm3-6b", 
        trust_remote_code=True,
        torch_dtype=torch.float16,  # PyTorch 2.0对float16支持更好
        device_map="auto"  # 利用PyTorch 2.0的自动设备映射
    )
    
    model.eval()  # 设置模型为评估模式
    
    # 启动FastAPI应用
    uvicorn.run(app, host='0.0.0.0', port=6006, workers=1)
对这一段的评论会显示在这里

在uv环境下运行上面的代码,并新起一个终端做测试,看回复效果:

对这一段的评论会显示在这里
curl -X POST http://localhost:6006 -H "Content-Type: application/json" -d '{
  "prompt": "你好",
  "history": [],
  "max_length": 2048,
  "top_p": 0.7,
  "temperature": 0.95
}'
对这一段的评论会显示在这里
5.jpg
5.jpg
对这一段的评论会显示在这里

服务返回的状态如下:

对这一段的评论会显示在这里
6.jpg
6.jpg
对这一段的评论会显示在这里

官方chat界面交互

对这一段的评论会显示在这里

在学习了基础的 Transformer 模型调用和 FastAPI 服务部署之后,我们可以进一步体验官方提供的交互式 Chat 界面。在 ChatGLM3 的官方示例 中,提供了两种主流的轻量级 Web 交互方案:

对这一段的评论会显示在这里

web_demo_gradio.py(基于 Gradio)
web_demo_streamlit.py(基于 Streamlit)

对这一段的评论会显示在这里

这两种框架都支持快速构建模型前端界面,适合本地调试与演示。本例中我们以 Streamlit 为例进行部署,因其布局灵活,适合构建功能丰富的交互界面。通常我们在做应用时,可以参考官网提供的代码,来寻找最佳实践。

对这一段的评论会显示在这里

由于需要在 AutoDL 平台上从外部访问服务,我们可通过端口 6006 进行服务映射,实现 Web 界面的远程访问。

对这一段的评论会显示在这里

clone该项目,修改模型路径到本地

对这一段的评论会显示在这里
git clone https://github.com/THUDM/ChatGLM3.git

cd ChatGLM3/basic_demo

vim web_demo_streamlit.py 
对这一段的评论会显示在这里

修改MODEL_PATH和TOKENIZER_PATH为:'/root/autodl-tmp/ZhipuAI/chatglm3-6b'

对这一段的评论会显示在这里
7.jpg
7.jpg
对这一段的评论会显示在这里

运行启动该streamlit应用:

对这一段的评论会显示在这里
streamlit run web_demo_streamlit.py --server.address 127.0.0.1 --server.port 6006
对这一段的评论会显示在这里
8.jpg
8.jpg
对这一段的评论会显示在这里

在AutoDL中,需要通过实例的自定义服务,把接口映射到本地,具体操作参考如下步骤:

对这一段的评论会显示在这里
9.jpg
9.jpg
对这一段的评论会显示在这里

在本地通过http://localhost:6006访问demo,效果如下:

对这一段的评论会显示在这里
10.jpg
10.jpg
对这一段的评论会显示在这里

接入LangChain搭建知识库,部署RAG应用

对这一段的评论会显示在这里

无论是FastAPI部署还是刚才的Streamlit web交互,ChatGLM3-6B都只能基于它的 预训练知识 来回答问题。但如果我想让它回答关于我们公司内部文档、最新技术资料或者特定领域知识的问题呢?

对这一段的评论会显示在这里

这就需要用到RAG(Retrieval-Augmented Generation),简单来说,就是"检索增强生成"——先从知识库中检索相关信息,再让大模型基于这些信息生成回答。接下来,本小节将构建一个完整的知识库助手,基于Sentence Transformer和Chroma向量数据库构建语料库,并将ChatGLM3-6B接入LangChain框架实现完整的RAG流程。

对这一段的评论会显示在这里

下载语料库内容

对这一段的评论会显示在这里

到数据存储目录中,下载知识库源码:

对这一段的评论会显示在这里
cd /root/autodl-tmp
# 下载所有知识库源码
git clone https://github.com/datawhalechina/self-llm.git
git clone https://github.com/datawhalechina/llm-universe.git
git clone https://github.com/datawhalechina/prompt-engineering-for-developers.git
git clone https://github.com/datawhalechina/so-large-lm.git
git clone https://github.com/datawhalechina/hugging-llm.git
对这一段的评论会显示在这里
11.jpg
11.jpg
对这一段的评论会显示在这里
# 完整的ChatGLM3-6B LangChain集成测试
import sys
print(f"Python版本: {sys.version}")

try:
    # 核心依赖测试
    from sentence_transformers import SentenceTransformer
    from huggingface_hub import cached_download
    from langchain.embeddings.huggingface import HuggingFaceEmbeddings
    from langchain.vectorstores import Chroma
    from langchain.chains import RetrievalQA
    from langchain.text_splitter import RecursiveCharacterTextSplitter
    
    print("✅ 所有核心组件导入成功!")
    
    # 测试实际功能
    embeddings = HuggingFaceEmbeddings(
        model_name="/root/autodl-tmp/sentence-transformer"
    )
    print("✅ 向量化模型加载成功!")
    
    print("🎉 ChatGLM3-6B LangChain环境配置完成!")
    
except Exception as e:
    print(f"❌ 错误详情: {e}")
    import traceback
    traceback.print_exc()
对这一段的评论会显示在这里

环境验证测试

对这一段的评论会显示在这里
12.jpg
12.jpg
对这一段的评论会显示在这里
# 创建向量模型目录
mkdir -p /root/autodl-tmp/sentence-transformer
cd /root/autodl-tmp

# 下载多语言向量模型
modelscope download --model=sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 --local_dir=./sentence-transformer
对这一段的评论会显示在这里

下载Sentence-Transformer模型,用于向量化数据库
创建向量数据库

对这一段的评论会显示在这里
# dbinit.py
# 首先导入所需第三方库
from langchain.document_loaders import UnstructuredFileLoader
from langchain.document_loaders import UnstructuredMarkdownLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
from langchain.schema import Document
from tqdm import tqdm
import os
import chardet

# 获取文件路径函数
def get_files(dir_path):
    file_list = []
    for filepath, dirnames, filenames in os.walk(dir_path):
        for filename in filenames:
            if filename.endswith(".md") or filename.endswith(".txt"):
                file_list.append(os.path.join(filepath, filename))
    return file_list

# 检测文件编码
def detect_encoding(file_path):
    with open(file_path, 'rb') as f:
        raw_data = f.read(10000)  # 读取前10KB用于检测
        result = chardet.detect(raw_data)
        return result['encoding']

# 安全读取文件内容
def safe_read_file(file_path):
    encodings = ['utf-8', 'gbk', 'gb2312', 'latin-1', 'cp1252']
    
    for encoding in encodings:
        try:
            with open(file_path, 'r', encoding=encoding) as f:
                content = f.read()
                return content, encoding
        except UnicodeDecodeError:
            continue
        except Exception as e:
            print(f"读取文件 {file_path} 时出错: {e}")
            continue
    
    # 如果所有编码都失败,尝试自动检测
    try:
        detected_encoding = detect_encoding(file_path)
        if detected_encoding:
            with open(file_path, 'r', encoding=detected_encoding) as f:
                content = f.read()
                return content, detected_encoding
    except:
        pass
    
    return None, None

# 改进的文件加载函数
def get_text_robust(dir_path):
    file_lst = get_files(dir_path)
    docs = []
    failed_files = []
    
    print(f"\n开始处理文件夹: {dir_path}")
    print(f"找到 {len(file_lst)} 个文件")
    
    for one_file in tqdm(file_lst, desc=f"处理 {os.path.basename(dir_path)}"):
        try:
            file_type = one_file.split('.')[-1]
            
            # 首先尝试使用原始加载器
            try:
                if file_type == 'md':
                    loader = UnstructuredMarkdownLoader(one_file)
                elif file_type == 'txt':
                    loader = UnstructuredFileLoader(one_file)
                else:
                    continue
                docs.extend(loader.load())
                
            except UnicodeDecodeError:
                # 如果编码错误,使用安全读取方法
                print(f"\n编码错误,尝试安全读取: {one_file}")
                content, encoding = safe_read_file(one_file)
                if content:
                    docs.append(Document(
                        page_content=content, 
                        metadata={"source": one_file, "encoding": encoding}
                    ))
                    print(f"成功读取,使用编码: {encoding}")
                else:
                    failed_files.append(one_file)
                    print(f"跳过文件: {one_file}")
                    
        except Exception as e:
            failed_files.append(one_file)
            print(f"\n处理文件失败: {one_file}, 错误: {e}")
            continue
    
    print(f"\n文件夹 {dir_path} 处理完成:")
    print(f"- 成功处理: {len(file_lst) - len(failed_files)} 个文件")
    print(f"- 失败文件: {len(failed_files)} 个")
    if failed_files:
        print("失败文件列表:")
        for f in failed_files[:5]:  # 只显示前5个
            print(f"  - {f}")
        if len(failed_files) > 5:
            print(f"  - ... 还有 {len(failed_files) - 5} 个文件")
    
    return docs

# 目标文件夹
tar_dir = [
    "/root/autodl-tmp/self-llm",
    "/root/autodl-tmp/llm-universe",
    "/root/autodl-tmp/prompt-engineering-for-developers",
    "/root/autodl-tmp/so-large-lm",
    "/root/autodl-tmp/hugging-llm",
]

# 加载目标文件
docs = []
for i, dir_path in enumerate(tar_dir):
    print(f"\n=== 处理第 {i+1}/{len(tar_dir)} 个文件夹 ===")
    try:
        folder_docs = get_text_robust(dir_path)
        docs.extend(folder_docs)
        print(f"累计文档数量: {len(docs)}")
    except Exception as e:
        print(f"处理文件夹 {dir_path} 时出错: {e}")
        continue

print(f"\n=== 文档加载完成 ===")
print(f"总文档数量: {len(docs)}")

# 对文本进行分块
print("\n开始文本分块...")
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=500, chunk_overlap=150)
split_docs = text_splitter.split_documents(docs)
print(f"分块后文档数量: {len(split_docs)}")

# 加载开源词向量模型
print("\n加载词向量模型...")
embeddings = HuggingFaceEmbeddings(model_name="/root/autodl-tmp/sentence-transformer")

# 构建向量数据库
print("\n构建向量数据库...")
persist_directory = 'data_base/vector_db/chroma'
vectordb = Chroma.from_documents(
    documents=split_docs,
    embedding=embeddings,
    persist_directory=persist_directory
)

# 持久化
vectordb.persist()
print("\n=== 向量数据库构建完成! ===")
print(f"数据库位置: {persist_directory}")
print(f"处理的文档数量: {len(docs)}")
print(f"向量化的文本块数量: {len(split_docs)}")
对这一段的评论会显示在这里
13.jpg
13.jpg
对这一段的评论会显示在这里

最终共加载了450个文档、25768个文本块,平均每个文档被分割成约57个文本块,设置的chunk_size=500, chunk_overlap=150,这个配置适合文本快的检索,不至于过大或过小。

对这一段的评论会显示在这里

Failed to send telemetry event只是ChromaDB尝试发送使用统计信息时的版本兼容问题告警,重要的是向量数据库本身工作正常。

对这一段的评论会显示在这里
14.jpg
14.jpg
对这一段的评论会显示在这里

ChatGLM接入LangChain

对这一段的评论会显示在这里

LangChain是一个框架,它要求所有 LLM 必须遵循统一的接口规范,使大模型(本例为ChatGLM)能够作为标准组件,参与提示工程、记忆管理、检索增强、智能代理等高级流程。

对这一段的评论会显示在这里

我们先通过统一的LLM类,实现ChatGLM的LangChain封装:

对这一段的评论会显示在这里
# LLM.py
from langchain.llms.base import LLM
from typing import Any, List, Optional
from langchain.callbacks.manager import CallbackManagerForLLMRun
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch

class ChatGLM_LLM(LLM):
    # 基于本地 ChatGLM 自定义 LLM 类
    tokenizer : AutoTokenizer = None
    model: AutoModelForCausalLM = None

    def __init__(self, model_path :str):
        # model_path: ChatGLM 模型路径
        # 从本地初始化模型
        super().__init__()
        print("正在从本地加载模型...")
        self.tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
        self.model = AutoModelForCausalLM.from_pretrained(model_path, trust_remote_code=True).to(torch.bfloat16).cuda()
        self.model = self.model.eval()
        print("完成本地模型的加载")

    def _call(self, prompt : str, stop: Optional[List[str]] = None,
                run_manager: Optional[CallbackManagerForLLMRun] = None,
                **kwargs: Any):
        # 重写调用函数
        response, history = self.model.chat(self.tokenizer, prompt , history=[])
        return response
        
    @property
    def _llm_type(self) -> str:
        return "ChatGLM3-6B"
对这一段的评论会显示在这里

验证ChatGLM是否做了向量知识库的增强,和LLM.py放在同目录下:

对这一段的评论会显示在这里
# test_qa_chain.py
from langchain.vectorstores import Chroma
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
from LLM import ChatGLM_LLM
from langchain.prompts import PromptTemplate
from langchain.chains import RetrievalQA
import os

def load_chain():
    """加载检索问答链"""
    print("正在加载向量数据库...")
    
    # 定义 Embeddings
    embeddings = HuggingFaceEmbeddings(model_name="/root/autodl-tmp/sentence-transformer")

    # 向量数据库持久化路径
    persist_directory = 'data_base/vector_db/chroma'

    # 加载数据库
    vectordb = Chroma(
        persist_directory=persist_directory,
        embedding_function=embeddings
    )
    
    print("正在加载ChatGLM模型...")
    # 加载自定义 LLM
    llm = ChatGLM_LLM(model_path="/root/autodl-tmp/ZhipuAI/chatglm3-6b")

    # 定义 Prompt Template
    template = """使用以下上下文来回答最后的问题。如果你不知道答案,就说你不知道,不要试图编造答案。尽量使答案简明扼要。总是在回答的最后说"谢谢你的提问!"。
{context}
问题: {question}
有用的回答:"""

    QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context","question"],template=template)

    # 构建检索问答链
    qa_chain = RetrievalQA.from_chain_type(
        llm,
        retriever=vectordb.as_retriever(),
        return_source_documents=True,
        chain_type_kwargs={"prompt":QA_CHAIN_PROMPT}
    )
    
    return qa_chain

def test_qa_chain():
    """测试检索问答链效果"""
    # 加载问答链
    qa_chain = load_chain()
    
    # 测试问题
    questions = [
        "什么是 Self LLM?",
        "ChatGLM3-6B 有什么特点?",
        "如何部署 ChatGLM 模型?"
    ]
    
    print("\n=== 开始测试检索问答链 ===")
    
    for i, question in enumerate(questions, 1):
        print(f"\n--- 测试问题 {i} ---")
        print(f"问题:{question}")
        
        # 检索问答链回答
        result = qa_chain({"query": question})
        print(f"检索问答链回答:{result['result']}")
        
        # 显示检索到的相关文档数量
        print(f"检索到相关文档数量:{len(result['source_documents'])}")
        print("-" * 50)

if __name__ == "__main__":
    test_qa_chain()
对这一段的评论会显示在这里
15.jpg
15.jpg
对这一段的评论会显示在这里

Lora微调部署

对这一段的评论会显示在这里

微调技术是个很重要的技能,举个例子,腾讯动漫中有一个漫画角色AI助手,让用户可以直接与漫画角色做对话。如果我们使用原版ChatGLM3-6B模型来做,模型依然只记住自己是ChatGLM3-6B,而不是自己是某个角色的设定。

对这一段的评论会显示在这里

我们需要只训练一小部分新增的参数,更新该模型的设定,可以用Lora(Low-Rank Adaptation)来构建个性化风格的LLM。

对这一段的评论会显示在这里

下载训练数据集

对这一段的评论会显示在这里

格式为:instruction-input-output三元结构(参考Self-Instruct论文)

对这一段的评论会显示在这里
wget https://raw.githubusercontent.com/datawhalechina/self-llm/master/dataset/huanhuan.json
对这一段的评论会显示在这里
# lora_finetune.py
import torch
from datasets import Dataset
from transformers import AutoTokenizer, AutoModelForCausalLM, DataCollatorForSeq2Seq, TrainingArguments, Trainer
import pandas as pd
from peft import TaskType, get_peft_model, LoraConfig
import json
import os

# 数据处理函数
def process_func(example):
    MAX_LENGTH = 512
    input_ids, labels = [], []
    
    instruction_text = "\n".join([
        "<|system|>",
        "现在你要扮演皇帝身边的女人--甄嬛",
        "<|user|>",
        example["instruction"] + example["input"] + "<|assistant|>"
    ]).strip() + "\n"
    
    instruction = tokenizer(
        instruction_text,
        add_special_tokens=True,
        truncation=True,
        max_length=MAX_LENGTH,
        return_tensors=None
    )["input_ids"]
    
    response = tokenizer(
        example["output"],
        add_special_tokens=False,
        truncation=True,
        max_length=MAX_LENGTH,
        return_tensors=None
    )["input_ids"]
    
    input_ids = instruction + response + [tokenizer.eos_token_id]
    labels = [tokenizer.pad_token_id] * len(instruction) + response + [tokenizer.eos_token_id]
    
    # 确保长度一致
    if len(input_ids) > MAX_LENGTH:
        input_ids = input_ids[:MAX_LENGTH]
        labels = labels[:MAX_LENGTH]
    else:
        pad_len = MAX_LENGTH - len(input_ids)
        input_ids += [tokenizer.pad_token_id] * pad_len
        labels += [tokenizer.pad_token_id] * pad_len
    
    labels = [(l if l != tokenizer.pad_token_id else -100) for l in labels]

    return {
        "input_ids": input_ids,
        "labels": labels
    }

# 🔥 修改训练参数配置 - 更新保存路径
args = TrainingArguments(
    output_dir="/root/output/ChatGLM-Lora",  # 🔥 修改为指定路径
     per_device_train_batch_size=1,  # 增加批次大小
    gradient_accumulation_steps=8,
    logging_steps=10,
    num_train_epochs=3,  # 增加到3个epoch
    learning_rate=2e-4,  # 稍微提高学习率
    save_steps=100,
    save_total_limit=2,
    dataloader_pin_memory=False,
    remove_unused_columns=False
)

if __name__ == "__main__":
    print("🚀 开始ChatGLM3-6B Lora微调...")
    
    # 1. 加载数据集
    print("📊 加载数据集...")
    df = pd.read_json('./huanhuan.json')
    ds = Dataset.from_pandas(df)
    print(f"数据集大小: {len(ds)}")
    
    # 2. 加载tokenizer
    print("🔤 加载tokenizer...")
    tokenizer = AutoTokenizer.from_pretrained(
        "/root/autodl-tmp/ZhipuAI/chatglm3-6b",
        trust_remote_code=True
    )
    
    # 3. 数据预处理
    print("⚙️ 数据预处理...")
    tokenized_ds = ds.map(process_func, remove_columns=ds.column_names)
    
    # 4. 加载模型
    print("🤖 加载ChatGLM3-6B模型...")
    model = AutoModelForCausalLM.from_pretrained(
        "/root/autodl-tmp/ZhipuAI/chatglm3-6b",
        torch_dtype=torch.half,
        trust_remote_code=True,
        low_cpu_mem_usage=True
    )
    
    # 5. 创建LoRA参数
    print("🔧 配置LoRA参数...")
    config = LoraConfig(
        task_type=TaskType.CAUSAL_LM, 
        target_modules=["query_key_value", "dense", "dense_h_to_4h", "dense_4h_to_h"],  # 更多模块
        r=16,
        lora_alpha=32,
        lora_dropout=0.1
    )
    
    # 6. 模型合并
    print("🔗 应用LoRA适配器...")
    model = get_peft_model(model, config)
    
    # 确保LoRA参数可训练
    model.train()
    for name, param in model.named_parameters():
        if 'lora' in name.lower():
            param.requires_grad = True
            print(f"✅ 激活LoRA参数: {name}")
    
    # 打印可训练参数统计
    model.print_trainable_parameters()
    
    # 7. 配置数据整理器
    data_collator = DataCollatorForSeq2Seq(
        tokenizer,
        model=model,
        label_pad_token_id=-100,
        pad_to_multiple_of=None,
        padding=True,
        return_tensors="pt"
    )
    
    # 8. 创建训练器
    print("🏃 创建训练器...")
    trainer = Trainer(
        model=model,
        args=args,
        train_dataset=tokenized_ds,
        data_collator=data_collator,
    )
    
    # 9. 开始训练
    print("🎯 开始训练...")
    trainer.train()
    
    # 10. 保存模型 - 🔥 增强版保存逻辑
    print("💾 保存LoRA权重...")
    
    # 确保目录存在
    save_path = "/root/output/ChatGLM-Lora"
    os.makedirs(save_path, exist_ok=True)
    
    # 保存LoRA权重
    trainer.save_model(save_path)
    
    # 直接使用model.save_pretrained确保保存成功
    model.save_pretrained(save_path)
    
    # 验证保存是否成功
    required_files = ["adapter_config.json", "adapter_model.bin"]
    all_files_exist = all(os.path.exists(os.path.join(save_path, f)) for f in required_files)
    
    if all_files_exist:
        print(f"✅ LoRA权重已成功保存到: {save_path}")
        print(f"📁 保存的文件:")
        for file in os.listdir(save_path):
            file_path = os.path.join(save_path, file)
            file_size = os.path.getsize(file_path)
            print(f"   - {file} ({file_size} bytes)")
    else:
        print("❌ 保存失败!缺少必要文件:")
        for f in required_files:
            if not os.path.exists(os.path.join(save_path, f)):
                print(f"   ❌ 缺少: {f}")
    
    print("✅ 微调完成!")
对这一段的评论会显示在这里

训练LoRA权重

对这一段的评论会显示在这里
16.jpg
16.jpg
对这一段的评论会显示在这里

加载Lora权重测试效果:

对这一段的评论会显示在这里
# lora_models.py
from transformers import AutoTokenizer, AutoModelForCausalLM
from peft import PeftModel
import torch

# 加载基础模型和tokenizer
model = AutoModelForCausalLM.from_pretrained("/root/autodl-tmp/ZhipuAI/chatglm3-6b", 
                                           trust_remote_code=True, 
                                           low_cpu_mem_usage=True,
                                           torch_dtype=torch.half,
                                           device_map="auto")
tokenizer = AutoTokenizer.from_pretrained("/root/autodl-tmp/ZhipuAI/chatglm3-6b", 
                                         use_fast=False, 
                                         trust_remote_code=True)

# 加载LoRA权重
p_model = PeftModel.from_pretrained(model, model_id="/root/output/ChatGLM-Lora")

# 测试函数
def test_model(question):
    # 按照训练时的格式构造输入
    prompt = "<|system|>\n现在你要扮演皇帝身边的女人--甄嬛\n<|user|>\n{}\n<|assistant|>\n".format(question)
    
    inputs = tokenizer(prompt, return_tensors="pt").to(p_model.device)
    
    with torch.no_grad():
        outputs = p_model.generate(
            **inputs,
            max_length=128,
            do_sample=True,
            temperature=0.7,
            top_p=0.9,
            pad_token_id=tokenizer.eos_token_id
        )
    
    response = tokenizer.decode(outputs[0], skip_special_tokens=True)
    return response

# 测试问题
test_questions = [
    "你是谁?",
    "你的父亲是谁?",
    "你觉得皇上怎么样?",
    "你最喜欢什么?"
]

print("=== LoRA微调后的甄嬛模型测试 ===")
for question in test_questions:
    print(f"\n问题: {question}")
    answer = test_model(question)
    print(f"甄嬛: {answer.split('<|assistant|>')[-1].strip()}")
对这一段的评论会显示在这里
17.jpg
17.jpg
对这一段的评论会显示在这里

注:受限于数据盘大小,Prompt也会对效果起到影响,更好的效果可以在自行训练中加深训练轮次。

对这一段的评论会显示在这里

Code Interpreter部署使用

对这一段的评论会显示在这里

想象一下,如果ChatGLM不仅能帮你写代码,还能直接运行这些代码并给你结果,这会带来什么样的体验?

对这一段的评论会显示在这里

当你使用ChatGLM普通模式,问"帮我计算1到100的和",模型会返回代码sum(range(1,101)), 而当你使用Code Interpreter模式,问同样问题,模型不仅返回代码,还执行代码,最终直接告诉你结果是:5050。

对这一段的评论会显示在这里

那么通过Interpreter,就可以完成程序的自动调试和修正,而非一次次的输入交互做重试。

对这一段的评论会显示在这里

下面我们通过官方的demo做使用,在前面的步骤中已经clone了ChatGLM3仓库代码,所以这里我们之间进入:

对这一段的评论会显示在这里
cd /root/autodl-tmp/ChatGLM3/composite_demo
对这一段的评论会显示在这里

设置环境变量:

对这一段的评论会显示在这里
export MODEL_PATH=/root/autodl-tmp/ZhipuAI/chatglm3-6b
export IPYKERNEL=python3
对这一段的评论会显示在这里

依旧使用streamlit启动6006的服务:

对这一段的评论会显示在这里
streamlit run main.py --server.port 6006
对这一段的评论会显示在这里
18.jpg
18.jpg
对这一段的评论会显示在这里

运行界面为:

对这一段的评论会显示在这里
19.jpg
19.jpg
对这一段的评论会显示在这里

切换为Code Interpreter模式做提问,可以运行代码生成结果:

对这一段的评论会显示在这里
20.jpg
20.jpg
对这一段的评论会显示在这里