【腾讯云 HAI域探秘】利用HAI+ChatGLM-6B轻松创作个人专属的知识宇宙-基于腾讯云CloudStudio和HAI

2023-10-26 09:29:48 浏览数 (3)

一、背景介绍1.1 ChatGPT and ChatGLM-6B

ChatGPT(全名:Chat Generative Pre-trained Transformer),是OpenAI研发的一款聊天机器人程序,于2022年11月30日发布。ChatGPT是人工智能技术驱动的自然语言处理工具,它能够基于在预训练阶段所见的模式和统计规律,来生成回答,还能根据聊天的上下文进行互动,真正像人类一样来聊天交流,甚至能完成撰写邮件、视频脚本、文案、翻译、代码,写论文等任务。

但是自己想要部署一套ChatGPT的话,首先数据集没有开源,其次对于硬件的要求,不可估量。所幸清华大学知识工程和数据挖掘小组(Knowledge Engineering Group (KEG) & Data Mining at Tsinghua University)发布的一个开源的对话机器人ChatGLM-6B。

ChatGLM 参考了 ChatGPT 的设计思路,在千亿基座模型 GLM-130B 中注入了代码预训练,通过有监督微调(Supervised Fine-Tuning)等技术实现人类意图对齐。ChatGLM 当前版本模型的能力提升主要来源于独特的千亿基座模型 GLM-130B。它是不同于 BERT、GPT-3 以及 T5 的架构,是一个包含多目标函数的自回归预训练模型。2022年8月,我们向研究界和工业界开放了拥有1300亿参数的中英双语稠密模型 GLM-130B,该模型有一些独特的优势:

  • 双语: 同时支持中文和英文。
  • 高精度(英文): 在公开的英文自然语言榜单 LAMBADA、MMLU 和 Big-bench-lite 上优于 GPT-3 175B(API: davinci,基座模型)、OPT-175B 和 BLOOM-176B。
  • 高精度(中文): 在7个零样本 CLUE 数据集和5个零样本 FewCLUE 数据集上明显优于 ERNIE TITAN 3.0 260B 和 YUAN 1.0-245B。
  • 快速推理: 首个实现 INT4 量化的千亿模型,支持用一台 4 卡 3090 或 8 卡 2080Ti 服务器进行快速且基本无损推理。
  • 可复现性: 所有结果(超过 30 个任务)均可通过我们的开源代码和模型参数复现。
  • 跨平台: 支持在国产的海光 DCU、华为昇腾 910 和申威处理器及美国的英伟达芯片上进行训练与推理。

2022年11月,斯坦福大学大模型中心对全球30个主流大模型进行了全方位的评测,GLM-130B 是亚洲唯一入选的大模型。在与 OpenAI、谷歌大脑、微软、英伟达、脸书的各大模型对比中,评测报告显示 GLM-130B 在准确性和恶意性指标上与 GPT-3 175B (davinci) 接近或持平,鲁棒性和校准误差在所有千亿规模的基座大模型(作为公平对比,只对比无指令提示微调模型)中表现不错(下图)。

图1. 斯坦福大学基础模型中心对全球 30 个大模型的评测结果(2022年11月)图1. 斯坦福大学基础模型中心对全球 30 个大模型的评测结果(2022年11月)

关于 GLM-130B 的学术文章已被国际深度学习会议 ICLR'23 接收。自2022年8月开放以来,收到53个国家369个研究机构(截至2023年2月1日)的下载使用需求,包括谷歌、微软、脸书、AI2、华为、阿里巴巴、百度、腾讯、头条、小冰、小度、小米以及斯坦福、麻省理工、伯克利、卡耐基梅隆、哈佛、剑桥、牛津、北大、浙大、上交、复旦、中科大、国科大等国内外人工智能研究机构和高校。

通过使用与 ChatGLM(chatglm.cn)相同的技术,ChatGLM-6B 初具中文问答和对话功能,并支持在单张 2080Ti 上进行推理使用。具体来说,ChatGLM-6B 有如下特点:

  • 充分的中英双语预训练: ChatGLM-6B 在 1:1 比例的中英语料上训练了 1T 的 token 量,兼具双语能力。
  • 优化的模型架构和大小: 吸取 GLM-130B 训练经验,修正了二维 RoPE 位置编码实现,使用传统FFN结构。6B(62亿)的参数大小,也使得研究者和个人开发者自己微调和部署 ChatGLM-6B 成为可能。
  • 较低的部署门槛: FP16 半精度下,ChatGLM-6B 需要至少 13GB 的显存进行推理,结合模型量化技术,这一需求可以进一步降低到 10GB(INT8) 和 6GB(INT4), 使得 ChatGLM-6B 可以部署在消费级显卡上。
  • 更长的序列长度: 相比 GLM-10B(序列长度1024),ChatGLM-6B 序列长度达 2048,支持更长对话和应用。
  • 人类意图对齐训练: 使用了监督微调(Supervised Fine-Tuning)、反馈自助(Feedback Bootstrap)、人类反馈强化学习(Reinforcement Learning from Human Feedback) 等方式,使模型初具理解人类指令意图的能力。输出格式为 markdown,方便展示。

因此,ChatGLM-6B 具备了一定条件下较好的对话与问答能力。当然,ChatGLM-6B 也有相当多已知的局限和不足:

  • 模型容量较小: 6B 的小容量,决定了其相对较弱的模型记忆和语言能力。在面对许多事实性知识任务时,ChatGLM-6B 可能会生成不正确的信息;她也不擅长逻辑类问题(如数学、编程)的解答。
  • 可能会产生有害说明或有偏见的内容:ChatGLM-6B 只是一个初步与人类意图对齐的语言模型,可能会生成有害、有偏见的内容。
  • 较弱的多轮对话能力:ChatGLM-6B 的上下文理解能力还不够充分,在面对长答案生成,以及多轮对话的场景时,可能会出现上下文丢失和理解错误的情况。
  • 英文能力不足:训练时使用的指示大部分都是中文的,只有一小部分指示是英文的。因此在使用英文指示时,回复的质量可能不如中文指示的回复,甚至与中文指示下的回复矛盾。
  • 易被误导:ChatGLM-6B 的“自我认知”可能存在问题,很容易被误导并产生错误的言论。例如当前版本模型在被误导的情况下,会在自我认知上发生偏差。即使该模型经过了1万亿标识符(token)左右的双语预训练,并且进行了指令微调和人类反馈强化学习(RLHF),但是因为模型容量较小,所以在某些指示下可能会产生有误导性的内容。

1.2 腾讯云HAI背景介绍

高性能应用服务 HAI:澎湃算力,即开即用。以应用为中心,匹配GPU云算力资源,助力中小企业及开发者快速部署LLM、AI作画、数据科学等高性能应用。是为开发者量身打造的澎湃算力平台。无需复杂配置,便可享受即开即用的GPU云服务体验。在 HAI 中,根据应用智能匹配并推选出最适合的GPU算力资源,以确保您在数据科学、LLM、AI作画等高性能应用中获得最佳性价比。

HAI 服务优势

智能选型 :根据应用匹配推选GPU算力资源,实现最高性价比。同时,打通必备云服务组件,大幅简化云服务配置流程。undefined 一键部署 :分钟级自动构建LLM、AI作画等应用环境。提供多种预装模型环境,包含如StableDiffusion、ChatGLM等热门模型。undefined 可视化界面 :友好的图形界面,AI调试更为简单

即插即用 · 轻松上手

基于腾讯云GPU云服务器底层算力,提供即插即用的高性能云服务。

智能选型

根据应用匹配推选GPU算力资源,实现最高性价比。同时,打通必备云服务组件,大幅简化云服务配置流程。

一键部署

分钟级自动构建LLM、AI作画等应用环境。提供多种预装模型环境,包含如StableDiffusion、ChatGLM等热门模型。

可视化界面

提供开发者友好的图形界面,支持jupyterlab、webui等多种算力连接方式,AI研究调试超低门槛。

横向对比 · 青出于蓝

大幅降低GPU云服务器使用门槛,多角度优化产品使用体验,开箱即用

应用场景

AI作画(视觉设计、游戏)

基于StableDiffusion开源模型进行AI绘画

场景介绍

AI绘画是一种利用深度学习算法进行创作的绘图方式。广泛应用于数字媒体、游戏、动画、电影、广告等领域。

业务痛点

  • GPU卡型多样,算力、显存差异大,选型困难
  • 环境配置复杂、模型安装和调试门槛高
  • 各类插件迭代频繁,难以在进行环境管理

产品优势

  • 智能匹配算力,多种算力套餐满足不同需求的绘图性能。
  • 预置主流AI作画模型及常用插件,无需手动部署,支持即开即用。
  • 动态更新模型版本,确保模型版本与时俱进,无需频繁操作。

AI对话/写作(Agent、企业知识库)大语言模型

基于开源大语言模型,创作属于自己的Agent、企业知识库

场景介绍

大语言模型在广泛的文本数据上进行训练,可以执行广泛的任务,包括文本总结、翻译、情感分析等等。

业务痛点

  • 环境配置复杂,部署难度大。
  • 模型效果调试难,无可视化界面。
  • 模型、数据集储存流程繁琐,难以快捷保存

业务痛点

  • 预置国内外主流LLM大语言模型。
  • 支持可视化界面一键登录,方便调优
  • 打通云上存储组件,支持模型、数据集快捷存储

AI开发/测试(学术研究、论文)算法研发

学术研究、论文

场景介绍

面向高校、研究所等大量科研场景,需针对深度学习、机器学习等前沿算法进行开发探索。

业务痛点

  • 需要大量的计算资源进行验证和优化。
  • 涉及多种框架、模型及开发库,环境配置复杂。

业务痛点

  • 提供多种高性能GPU云服务器,满足算法验证和测试的需求。
  • 提供公共模型、数据集文件存储桶,优化资源拉取效率。

二、动手实验

基于HAI部署的 ChatGLM2-6B 快速进行AI对话 基于HAI部署的 ChatGLM2-6B API 快速实现开发者所需的相关API服务 基于HAI部署的 ChatGLM2-6B API 快速实现模型的微调

2.1 实验介绍

本次我们使用 腾讯云高性能应用服务 HAI 体验快速搭建并使用AI模型 ChatGLM2-6B ,实现思路如下:

1、体验 高性能应用服务HAI 一键部署 ChatGLM2-6B 2、启动 ChatGLM2-6B WebUI 进行简单的对话 3、开发者体验 JupyterLab 、 Cloud Studio 进行 ChatGLM2-6B API 的配置调用 4、开发者使用 Cloud Studio 应用推荐 ChatGPT Next Web 快速开发调用 ChatGLM2-6B OpenAI API 服务,搭建自己的GPT 5、开发者使用 高性能应用服务HAI 快速部署 ChatGLM2-6B-int4 本地模型及基于 P-Tuning v2 的微调

2.2 效果展示

使用 ChatGLM2-6B WebUI 进行简单的对话

Cloud Studio 进行 ChatGLM2-6B API 的调用

Cloud Studio 调用 ChatGLM2-6B 流式 API 接口

Cloud Studio 创建应用 ChatGPT Next Web 并快速调用 ChatGLM2-6B OpenAI API 接口

开发者使用 高性能应用服务HAI 训练 ChatGLM2-6B 本地模型

开发者使用 高性能应用服务HAI 测试训练完成后的ChatGLM2-6B 模型

三、实操指导

1、申请高性能应用服务 HAI

① . 点击链接进入 高性能应用服务 HAI 申请体验资格

② . 等待审核通过后,进入 高性能应用服务 HAI

③ . 点击前往体验HAI,登录 高性能应用服务 HAI 控制台

③ . 点击 新建 选择 AI模型,输入实例名称

④ . 等待创建完成 (预计等待3-8分钟,等待时间不计费)

⑤ . 创建完成,查看相关状态

⑥ . 查看配置详情

2、启动 高性能应用服务HAI 配置的 ChatGLM2-6B WebUI 进行简单的对话

① . 选择 chatglm2_gradio 进入 WebUI 页面

② . 体验与 ChatGLM2-6B 简单的对话

3、高性能应用服务HAI 快速为开发者提供 ChatGLM2-6B API 服务

① .使用 JupyterLab 启动 ChatGLM2-6B 提供的 API 服务

(1) .在 算力管理 页面,选择进入 jupyter_lab 页面

选择 终端命令

输入命令 用于开启 API 服务:

代码语言:javascript复制
cd ./ChatGLM2-6B
python api.py

(2) .新增服务器端口规则

选择 编辑规则

选择 入站规则 中的添加规则

添加入站规则 (来源: 0.0.0.0/0 协议端口: TCP:8000)

② .使用 Cloud Studio 快速调用测试 ChatGLM2-6B 提供的 API 服务

(1) .打开 Cloud Studio 并创建开发空间

新建工作空间 输入 空间名称 ,选择 代码来源为空 ,开发环境 Python 即可

等待数十秒 工作空间启动完成

(2) .编写调用代码并运行测试

workspace 下右键选择 新建文件

创建 get_api.py 代码文件,并复制以下代码 用于测试请求

注意: 请确保将代码中的地址和端口更改为您的API服务器的实际地址和端口

代码语言:javascript复制
import requests

# 定义测试数据,以及FastAPI服务器的地址和端口
server_url = "http://0.0.0.0:8000"  # 请确保将地址和端口更改为您的API服务器的实际地址和端口
test_data = {
    "prompt": "'你好,发热了怎么办?'",
    "history": [],
    "max_length": 50,
    "top_p": 0.7,
    "temperature": 0.95
}

# 发送HTTP POST请求
response = requests.post(server_url, json=test_data)

# 处理响应
if response.status_code == 200:
    result = response.json()
    print("Response:", result["response"])
    print("History:", result["history"])
    print("Status:", result["status"])
    print("Time:", result["time"])
else:
    print("Failed to get a valid response. Status code:", response.status_code)

查看 ChatGLM2-6B 实例 公网地址,并修改代码中 服务器地址 部分

修改完代码文件,可点击右上角的运行按钮进行测试

返回请求结果:

服务器端可查看记录并使用 Ctrl C 关闭服务:

③ .使用 JupyterLab 开发 ChatGLM2-6B 流式返回 API 接口并使用 Cloud Studio 快速调用测试

(1) .使用 JupyterLab 编写基于 FastAPI 编写的服务器端代码并开启服务

JupyterLab 中选择文件夹操作界面,依次打开 root 文件夹下的 ChatGLM2-6B 文件夹,并创建一个 Python File ,拷贝一下代码并保存,同时将文件名修改为 chatglm2-6b-stream-api.py ,最后开启API服务

创建Python文件

粘贴 chatglm2-6b-stream-api.py 代码

代码语言:javascript复制
# -*-coding:utf-8-*-
'''
File Name:chatglm2-6b-stream-api.py
Author:Luofan
Time:2023/6/26 13:33
'''

import os
import sys
import json
import torch
import uvicorn
import logging
import argparse
from fastapi import FastAPI
from transformers import AutoTokenizer, AutoModel
from fastapi.middleware.cors import CORSMiddleware
from sse_starlette.sse import ServerSentEvent, EventSourceResponse


def getLogger(name, file_name, use_formatter=True):
    logger = logging.getLogger(name)
    logger.setLevel(logging.INFO)
    console_handler = logging.StreamHandler(sys.stdout)
    formatter = logging.Formatter('%(asctime)s    %(message)s')
    console_handler.setFormatter(formatter)
    console_handler.setLevel(logging.INFO)
    logger.addHandler(console_handler)
    if file_name:
        handler = logging.FileHandler(file_name, encoding='utf8')
        handler.setLevel(logging.INFO)
        if use_formatter:
            formatter = logging.Formatter('%(asctime)s - %(name)s - %(message)s')
            handler.setFormatter(formatter)
        logger.addHandler(handler)
    return logger


logger = getLogger('ChatGLM', 'chatlog.log')

MAX_HISTORY = 3


class ChatGLM():
    def __init__(self) -> None:
        logger.info("Start initialize model...")
        self.tokenizer =  AutoTokenizer.from_pretrained("THUDM/chatglm2-6b", revision="v1.0", trust_remote_code=True)
        self.model = AutoModel.from_pretrained("THUDM/chatglm2-6b", revision="v1.0", trust_remote_code=True).cuda()
        self.model.eval()
        logger.info("Model initialization finished.")

    def clear(self) -> None:
        if torch.cuda.is_available():
            with torch.cuda.device(f"cuda:{args.device}"):
                torch.cuda.empty_cache()
                torch.cuda.ipc_collect()

    def answer(self, query: str, history):
        response, history = self.model.chat(self.tokenizer, query, history=history)
        history = [list(h) for h in history]
        return response, history

    def stream(self, query, history):
        if query is None or history is None:
            yield {"query": "", "response": "", "history": [], "finished": True}
        size = 0
        response = ""
        for response, history in self.model.stream_chat(self.tokenizer, query, history):
            this_response = response[size:]
            history = [list(h) for h in history]
            size = len(response)
            yield {"delta": this_response, "response": response, "finished": False}
        logger.info("Answer - {}".format(response))
        yield {"query": query, "delta": "[EOS]", "response": response, "history": history, "finished": True}


def start_server(http_address: str, port: int, gpu_id: str):
    os.environ['CUDA_DEVICE_ORDER'] = 'PCI_BUS_ID'
    os.environ['CUDA_VISIBLE_DEVICES'] = gpu_id

    bot = ChatGLM()

    app = FastAPI()
    app.add_middleware(CORSMiddleware,
                       allow_origins=["*"],
                       allow_credentials=True,
                       allow_methods=["*"],
                       allow_headers=["*"]
                       )

    @app.get("/")
    def index():
        return {'message': 'started', 'success': True}

    @app.post("/chat")
    async def answer_question(arg_dict: dict):
        result = {"query": "", "response": "", "success": False}
        try:
            text = arg_dict["query"]
            ori_history = arg_dict["history"]
            logger.info("Query - {}".format(text))
            if len(ori_history) > 0:
                logger.info("History - {}".format(ori_history))
            history = ori_history[-MAX_HISTORY:]
            history = [tuple(h) for h in history]
            response, history = bot.answer(text, history)
            logger.info("Answer - {}".format(response))
            ori_history.append((text, response))
            result = {"query": text, "response": response,
                      "history": ori_history, "success": True}
        except Exception as e:
            logger.error(f"error: {e}")
        return result

    @app.post("/stream")
    def answer_question_stream(arg_dict: dict):
        def decorate(generator):
            for item in generator:
                #yield ServerSentEvent(json.dumps(item, ensure_ascii=False), event='delta')
                yield ServerSentEvent(json.dumps(item, ensure_ascii=False))

        try:
            text = arg_dict["query"]
            ori_history = arg_dict["history"]
            logger.info("Query - {}".format(text))
            if len(ori_history) > 0:
                logger.info("History - {}".format(ori_history))
            history = ori_history[-MAX_HISTORY:]
            history = [tuple(h) for h in history]
            return EventSourceResponse(decorate(bot.stream(text, history)))
        except Exception as e:
            logger.error(f"error: {e}")
            return EventSourceResponse(decorate(bot.stream(None, None)))

    @app.get("/free_gc")
    def free_gpu_cache():
        try:
            bot.clear()
            return {"success": True}
        except Exception as e:
            logger.error(f"error: {e}")
            return {"success": False}

    logger.info("starting server...")
    uvicorn.run(app=app, host=http_address, port=port, workers=1)


if __name__ == '__main__':

    parser = argparse.ArgumentParser(description='Stream API Service for ChatGLM2-6B')
    parser.add_argument('--device', '-d', help='device,-1 means cpu, other means gpu ids', default='0')
    parser.add_argument('--host', '-H', help='host to listen', default='0.0.0.0')
    parser.add_argument('--port', '-P', help='port of this service', default=8000)
    args = parser.parse_args()
    start_server(args.host, int(args.port), args.device)

JupyterLab 中完成文件的创建并重命名 chatglm2-6b-stream-api.py 成功:

JupyterLab终端界面 中输入命令开启 chatglm2-6b-stream-api.py 服务:

代码语言:javascript复制
python chatglm2-6b-stream-api.py

注意: 请将上一个 API服务 关闭,否则服务无法启动成功

(2) .使用 Cloud Studio 编写客户端代码

使用普通Http请求调用 /chat 接口

Cloud Studio 工作空间下继续创建 Python 代码文件 use_chatglm2-6b-stream-api.py

注意: 请将代码中的地址和端口更改为实际的服务器地址和端口

use_chatglm2-6b-stream-api.py 代码文件:

代码语言:javascript复制
import requests
import json

# 设置服务器地址和端口
server_address = "http://0.0.0.0"  # 请将地址和端口更改为实际的服务器地址和端口
server_port = 8000

# 构造请求数据
request_data = {
    "query": "你好,发热了怎么办?",
    "history": []
}

# 发送HTTP POST请求到聊天端点
response = requests.post(f"{server_address}:{server_port}/chat", json=request_data)

# 处理响应
if response.status_code == 200:
    result = response.json()
    if result["success"]:
        print("Response:", result["response"])
        print("History:", result["history"])
    else:
        print("Failed to get a valid response.")
else:
    print("Failed to connect to the server. Status code:", response.status_code)

创建完成:

运行并查看返回结果:

在菜单栏中点击 终端 选择 新建终端,输入命令执行代码

代码语言:javascript复制
python use_chatglm2-6b-stream-api.py

调用接口成功

服务端查看记录:

使用AioHttp调用 /stream 流式接口

Cloud Studio 工作空间下 继续创建 Python 代码文件 use_stream_chatglm2-6b-stream-api.py

注意: 请将代码中的地址和端口更改为实际的服务器地址和端口

use_stream_chatglm2-6b-stream-api.py 代码文件:

代码语言:javascript复制
import aiohttp  
import json  
import asyncio  
  
async def main():  
    async with aiohttp.ClientSession() as session:  
        server_address = "http://0.0.0.0"  # 请将地址更改为实际的服务器地址  
        server_port = 8000  
        endpoint = f"{server_address}:{server_port}/stream"  # 流式处理端点  
  
        request_data = {  
            "query": "你好,发热怎么办?",  
            "history": []  
        }  
  
        try:  
            async with session.post(endpoint, json=request_data, timeout=None) as response:  
                if response.status == 200:  
                    async for line in response.content.iter_any():  
                        data = line.decode('utf-8')  
                        data = data.replace('event: deltarndata: ','')
                        data = data.replace('rn','')
                        data = data.replace('data: ','')
                        try:  
                            result = json.loads(data)  
                            if result.get("finished"):  
                                    print("Response:", result["response"])  
                                    print('rn')
                                    print("History:", result["history"])  
                            #else:  
                            #    print("Delta:", result["delta"])  
                        except json.JSONDecodeError:  
                            print("Failed to parse JSON:")  
                else:  
                    print(f"Failed to connect to the server. Status code: {response.status}")  
        except aiohttp.ClientError as e:  
            print(f"Client error occurred: {e}")  
            await asyncio.sleep(1)  # Avoid continuous failures, wait a bit before retrying  
            print(f"Unexpected error occurred: {e}")  
            await asyncio.sleep(1)  # Avoid continuous failures, wait a bit before retrying  
  
if __name__ == '__main__':  
    asyncio.run(main())

终端 中输入命令 安装 aiohttp 模块

代码语言:javascript复制
pip install aiohttp 

运行并查看返回结果:

终端 中输入命令执行代码

代码语言:javascript复制
python use_stream_chatglm2-6b-stream-api.py

4、开发者使用 Cloud Studio 创建应用推荐 ChatGPT Next Web 开源项目 并快速开发调用 ChatGLM2-6B OpenAI API 服务

① .使用 JupyterLab 修改 openai_api.py 代码并开启服务

打开 JupyterLab 后关闭上一次开启的 API 服务

使用 Ctrl C 关闭服务

打开右边文件夹下的 openai_api.py 文件

如果直接使用会在调用时报错,复制以下 openai_api.py 代码直接覆盖源文件并 Ctrl S 保存代码

代码语言:javascript复制
# coding=utf-8
# Implements API for ChatGLM2-6B in OpenAI's format. (https://platform.openai.com/docs/api-reference/chat)
# Usage: python openai_api.py
# Visit http://localhost:8000/docs for documents.

import time
import torch
import uvicorn
from pydantic import BaseModel, Field
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from contextlib import asynccontextmanager
from typing import Any, Dict, List, Literal, Optional, Union
from transformers import AutoTokenizer, AutoModel
from sse_starlette.sse import ServerSentEvent, EventSourceResponse


@asynccontextmanager
async def lifespan(app: FastAPI): # collects GPU memory
    yield
    if torch.cuda.is_available():
        torch.cuda.empty_cache()
        torch.cuda.ipc_collect()


app = FastAPI(lifespan=lifespan)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

class ModelCard(BaseModel):
    id: str
    object: str = "model"
    created: int = Field(default_factory=lambda: int(time.time()))
    owned_by: str = "owner"
    root: Optional[str] = None
    parent: Optional[str] = None
    permission: Optional[list] = None


class ModelList(BaseModel):
    object: str = "list"
    data: List[ModelCard] = []


class ChatMessage(BaseModel):
    role: Literal["user", "assistant", "system"]
    content: str


class DeltaMessage(BaseModel):
    role: Optional[Literal["user", "assistant", "system"]] = None
    content: Optional[str] = None


class ChatCompletionRequest(BaseModel):
    model: str
    messages: List[ChatMessage]
    temperature: Optional[float] = None
    top_p: Optional[float] = None
    max_length: Optional[int] = None
    stream: Optional[bool] = False


class ChatCompletionResponseChoice(BaseModel):
    index: int
    message: ChatMessage
    finish_reason: Literal["stop", "length"]


class ChatCompletionResponseStreamChoice(BaseModel):
    index: int
    delta: DeltaMessage
    finish_reason: Optional[Literal["stop", "length"]]


class ChatCompletionResponse(BaseModel):
    model: str
    object: Literal["chat.completion", "chat.completion.chunk"]
    choices: List[Union[ChatCompletionResponseChoice, ChatCompletionResponseStreamChoice]]
    created: Optional[int] = Field(default_factory=lambda: int(time.time()))


@app.get("/v1/models", response_model=ModelList)
async def list_models():
    global model_args
    model_card = ModelCard(id="gpt-3.5-turbo")
    return ModelList(data=[model_card])


@app.post("/v1/chat/completions", response_model=ChatCompletionResponse)
async def create_chat_completion(request: ChatCompletionRequest):
    global model, tokenizer

    if request.messages[-1].role != "user":
        raise HTTPException(status_code=400, detail="Invalid request")
    query = request.messages[-1].content

    prev_messages = request.messages[:-1]
    if len(prev_messages) > 0 and prev_messages[0].role == "system":
        query = prev_messages.pop(0).content   query

    history = []
    if len(prev_messages) % 2 == 0:
        for i in range(0, len(prev_messages), 2):
            if prev_messages[i].role == "user" and prev_messages[i 1].role == "assistant":
                history.append([prev_messages[i].content, prev_messages[i 1].content])

    if request.stream:
        generate = predict(query, history, request.model)
        return EventSourceResponse(generate, media_type="text/event-stream")

    response, _ = model.chat(tokenizer, query, history=history)
    choice_data = ChatCompletionResponseChoice(
        index=0,
        message=ChatMessage(role="assistant", content=response),
        finish_reason="stop"
    )

    return ChatCompletionResponse(model=request.model, choices=[choice_data], object="chat.completion")


async def predict(query: str, history: List[List[str]], model_id: str):
    global model, tokenizer

    choice_data = ChatCompletionResponseStreamChoice(
        index=0,
        delta=DeltaMessage(role="assistant"),
        finish_reason=None
    )
    chunk = ChatCompletionResponse(model=model_id, choices=[choice_data], object="chat.completion.chunk")
    #yield "{}".format(chunk.json(exclude_unset=True, ensure_ascii=False))
    yield "{}".format(chunk.model_dump_json(exclude_unset=True))

    current_length = 0

    for new_response, _ in model.stream_chat(tokenizer, query, history):
        if len(new_response) == current_length:
            continue

        new_text = new_response[current_length:]
        current_length = len(new_response)

        choice_data = ChatCompletionResponseStreamChoice(
            index=0,
            delta=DeltaMessage(content=new_text),
            finish_reason=None
        )
        chunk = ChatCompletionResponse(model=model_id, choices=[choice_data], object="chat.completion.chunk")
        #yield "{}".format(chunk.json(exclude_unset=True, ensure_ascii=False))
        yield "{}".format(chunk.model_dump_json(exclude_unset=True))

    choice_data = ChatCompletionResponseStreamChoice(
        index=0,
        delta=DeltaMessage(),
        finish_reason="stop"
    )
    chunk = ChatCompletionResponse(model=model_id, choices=[choice_data], object="chat.completion.chunk")
    #yield "{}".format(chunk.json(exclude_unset=True, ensure_ascii=False))
    yield "{}".format(chunk.model_dump_json(exclude_unset=True))
    yield '[DONE]'



if __name__ == "__main__":
    tokenizer = AutoTokenizer.from_pretrained("THUDM/chatglm2-6b", revision="v1.0", trust_remote_code=True)
    model = AutoModel.from_pretrained("THUDM/chatglm2-6b", revision="v1.0", trust_remote_code=True).cuda()
    # 多显卡支持,使用下面两行代替上面一行,将num_gpus改为你实际的显卡数量
    # from utils import load_model_on_gpus
    # model = load_model_on_gpus("THUDM/chatglm2-6b", num_gpus=2)
    model.eval()

    uvicorn.run(app, host='0.0.0.0', port=8000, workers=1)

修改后的 openai_api.py 示意图:

服务端开启服务:

代码语言:javascript复制
python openai_api.py

② .使用 Cloud Studio 快速创建 应用推荐 下的 ChatGPT Next Web 开源项目

打开 Cloud Studio 开发空间下,我们创建的项目,并停止服务。

成功后,打开 应用推荐 选择 ChatGPT Next Web 项目

选择 Fork

等待数秒后,工作空间创建完毕

③ .使用 Cloud Studio 快速配置并启动项目

Fork 完成后,选择 .env.template 文件

修改配置信息如下:

修改文件 后缀名.env

新建 终端命令

输入命令:

代码语言:javascript复制
npm install 

依赖安装完成后,输入命令开启服务

代码语言:javascript复制
yarn dev

点击 端口 ,可使用 浏览器或标签页 两种方式运行项目

如遇到这个报错提醒,可点击关闭,不影响体验

web浏览器测试:

Cloud Studio 标签页查看:

服务端可查看相关的请求记录:

停止 Cloud Studio 工作空间服务:

服务端停止服务:

5、高性能应用服务HAI 快速部署 ChatGLM2-6B-int4 本地模型及基于 P-Tuning v2 的微调

① .使用 JupyterLab 快速部署 ChatGLM2-6B-int4 本地模型

输入命令下载 ChatGLM2-6B-int4 相关源码:

仓库地址

  • 下载方式一:https下载方式
代码语言:javascript复制
cd ./ChatGLM2-6B
git clone https://gitee.com/dadac/chatglm2-6b-int4.git
# 输入你的gitee账号密码
cd chatglm2-6b-int4
  • 下载方式二:ssh下载方式

在服务器上生成ssh密钥,在Jupyter终端中依次执行以下命令

代码语言:javascript复制
ssh-keygen -t ed25519 -C ""  #双引号里面填ssh key,一般填自己邮箱
# 回车一直默认即可,会在家目录生成 ~/.ssh 文件夹,存放密钥信息
cat ~/.ssh/id_ed25519.pub  # 查看生成的公钥

  1. 复制红色区域内容
  2. 打开gitee登录,并添加密钥
代码语言:javascript复制
在Jupyter终端中依次执行以下命令
git clone git@gitee.com:dadac/chatglm2-6b-int4.git 
# 输入yes 回车确认

代码下载完成:

由于访问外网下载速度很慢,这里我们使用清华大学云盘下载 chatglm2-6b-int4 的相关模型文件

注意:下载链接 存在时效问题,需要手动找一下文件真实地址,方式如下:

  1. 打开清华大学云盘
  2. 选择模型文件

打开浏览器控制台(window使用 F12 ,macOs使用command option i, 或者右键点击选择检查)

在控制台中选择网络选项卡(NetWork),筛选类型选择All,并清空当前网络请求记录

点击下载,在网络请求记录中找到文件下载链接并复制(防止给您的电脑产生垃圾文件,记得取消本地下载或删掉在您电脑上已下载的文件)

同样的方式找到 pytorch_model.bintokenizer.model文件链接 找到链接地址后在 JupyterLab 中输入命令:

代码语言:javascript复制
rm pytorch_model.bin #删除旧文件
wget http://xxxxxx   # 复制的pytorch_model.bin 文件链接
rm tokenizer.model   #删除旧文件
wget http://xxxxxx   # 复制的tokenizer.model 文件链接

由于 pytorch_model.bin 模型文件较大下载大约需要13分钟左右,请您耐心等待。

完成下载后,修改代码来加载本地模型

返回到 ChatGLM2-6B 文件夹,找到 web_demo.py 文件,修改代码文件中第6、7行

代码语言:javascript复制
tokenizer = AutoTokenizer.from_pretrained("./chatglm2-6b-int4", revision="v1.0", trust_remote_code=True)
model = AutoModel.from_pretrained("./chatglm2-6b-int4", revision="v1.0", trust_remote_code=True).half().cuda()

在web服务开启之前,为了优化服务器的性能,关闭 HAI 提供的 chatglm2_gradio webui,命令如下:

代码语言:javascript复制
apt-get update && apt-get install sudo
sudo apt-get update  
sudo apt-get install psmisc
sudo fuser -k 6889/tcp #执行这条命令将关闭 HAI提供的 chatglm2_gradio webui功能

关闭后输入命令可以启动web服务测试:

代码语言:javascript复制
cd /root/ChatGLM2-6B
python web_demo.py --listen --port 8000

注意:为避免重复的配置,这里我们将端口配置为我们已经开启的 8000 端口

体验使用本地模型对话:

② .使用 JupyterLab 微调 ChatGLM2-6B 模型(基于 P-Tuning v2 ),创建个人的专属知识库

打开 JupyterLab 选择 ChatGLM2-6B 文件夹下的 ptuning 文件夹

添加训练集、验证集文件(您也可配置自己的训练集,添加的内容越多训练时间越长)

本地创建 train.json 文件

代码语言:javascript复制
[	
	{
        "content": "你好,你是谁",
        "summary": "你好,我是腾讯云高性能应用HAI的助手小hai。"
    },
	{
        "content": "你是谁",
        "summary": "你好,我是HAI的助手小hai。"
    },
    {
        "content": "HAI是谁",
        "summary": "HAI是为开发者量身打造的澎湃算力平台。无需复杂配置,便可享受即开即用的GPU云服务体验。在HAI中,根据应用智能匹配并推选出最适合的GPU算力资源,以确保您在数据科学、LLM、AI作画等高性能应用中获得最佳性价比。"
    }
]

在目录 /ChatGLM2-6B/ptuning/ 下点击上传按钮将本地已保存的 train.json 文件上传至 ptuning 目录

同样用相同的方法 本地创建 verify.json 文件,并上传至 ptuning 目录

代码语言:javascript复制
[	
	{
        "content": "你好,你是谁",
        "summary": "你好,我是腾讯云高性能应用HAI的助手小hai。"
    },
	{
        "content": "你是谁",
        "summary": "你好,我是HAI的助手小hai。"
    },
    {
        "content": "HAI是谁",
        "summary": "HAI是为开发者量身打造的澎湃算力平台。无需复杂配置,便可享受即开即用的GPU云服务体验。在HAI中,根据应用智能匹配并推选出最适合的GPU算力资源,以确保您在数据科学、LLM、AI作画等高性能应用中获得最佳性价比。"
    }
]

train.jsonverify.json 上传成功

选择 train_chat.sh 文件复制以下配置信息替换并保存文件

代码语言:javascript复制
PRE_SEQ_LEN=128
LR=1e-2
NUM_GPUS=1

torchrun --standalone --nnodes=1 --nproc-per-node=$NUM_GPUS main.py 
    --do_train 
    --train_file train.json 
    --validation_file verify.json 
    --preprocessing_num_workers 10 
    --prompt_column content 
    --response_column summary 
    --overwrite_cache 
    --model_name_or_path /root/ChatGLM2-6B/chatglm2-6b-int4 
    --output_dir /root/ChatGLM2-6B/output/chatglm2-6b-int4−$LR 
    --overwrite_output_dir 
    --max_source_length 512 
    --max_target_length 512 
    --per_device_train_batch_size 1 
    --per_device_eval_batch_size 1 
    --gradient_accumulation_steps 16 
    --predict_with_generate 
    --max_steps 3000 
    --logging_steps 10 
    --save_steps 1000 
    --learning_rate $LR 
    --pre_seq_len $PRE_SEQ_LEN 
    --quantization_bit 4

参数

描述

PRE_SEQ_LEN=128

定义了一个名为PRE_SEQ_LEN的变量,并将其设置为128。这个变量的作用在后续的代码中会用到

LR=2e-2

定义了一个名为LR的变量,并将其设置为2e-2,即0.02。这个变量表示学习率。

train_file

指定训练数据文件的路径和文件名为"train.json"。

validation_file

指定验证数据文件的路径和文件名为"verify.json"

prompt_column

指定输入数据中作为提示的列名为"content"

response_column

指定输入数据中作为响应的列名为"summary"

overwrite_cache

一个命令行参数,指示在缓存存在的情况下覆盖缓存

model_name_or_path

指定使用的模型的名称或路径为"/root/ChatGLM2-6B/chatglm2-6b-int4"

output_dir

: 指定输出目录的路径和名称为"/root/ChatGLM2-6B/output/chatglm2-6b-int4−$LR "。这是训练结果和日志的保存位置。

overwrite_output_dir

一个命令行参数,指示在输出目录存在的情况下覆盖输出目录。

max_source_length

指定输入序列的最大长度为512

max_target_length

指定输出序列的最大长度为512

per_device_train_batch_size

指定每个训练设备的训练批次大小为1

per_device_eval_batch_size

指定每个评估设备的评估批次大小为1

gradient_accumulation_steps

指定梯度累积的步数为16。在每个更新步骤之前,将计算并累积一定数量的梯度。

predict_with_generate

一个命令行参数,指示在生成模型的预测时使用生成模式

max_steps

指定训练的最大步数为3000

logging_steps

指定每隔10个步骤记录一次日志

save_steps

指定每隔1000个步骤保存一次模型

learning_rate

指定学习率为之前定义的LR变量的值

pre_seq_len

指定预设序列长度为之前定义的PRE_SEQ_LEN变量的值

quantization_bit

指定量化位数为4。这个参数可能是与模型相关的特定设置。

保存相关后截图:

在正式微调之前 我们还需要安装以下依赖, 新建 终端命令窗口

输入命令安装依赖,这里我们使用 腾讯云 提供的镜像:

代码语言:javascript复制
pip install -i https://mirrors.cloud.tencent.com/pypi/simple rouge_chinese nltk jieba datasets

安装完成之后 执行下面的命令进行训练:

代码语言:javascript复制
cd ChatGLM2-6B/ptuning
sh train_chat.sh

模型开始训练,数据集越多耗时越长,目前测试的三条训练集、验证集大约需要1个小时左右:

训练完成:

③ .使用 JupyterLab 测试微调后的模型

完成训练后 修改 ChatGLM2-6B 文件下的 web_demo.py 文件用来测试新训练的模型文件

修改 web_demo.py 文件

代码语言:javascript复制
from transformers import AutoModel, AutoTokenizer,AutoConfig
import gradio as gr
import mdtex2html
import torch
import os
from utils import load_model_on_gpus


MODEL_PATH = "./chatglm2-6b-int4"
CHECKPOINT_PATH = "./output/chatglm2-6b-int4−1e-2/checkpoint-3000"
 
# 载入Tokenizer
tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH, trust_remote_code=True)
 
config = AutoConfig.from_pretrained(MODEL_PATH, trust_remote_code=True, pre_seq_len=128)
model = AutoModel.from_pretrained(MODEL_PATH, config=config, trust_remote_code=True).cuda()
 
prefix_state_dict = torch.load(os.path.join(CHECKPOINT_PATH, "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)
 
print(f"Quantized to 4 bit")
model = model.quantize(4)
model = model.half().cuda()
model.transformer.prefix_encoder.float()
model = model.eval()

#tokenizer = AutoTokenizer.from_pretrained("./chatglm2-6b-int4", revision="v1.0", trust_remote_code=True)
#model = AutoModel.from_pretrained("./chatglm2-6b-int4", revision="v1.0", trust_remote_code=True).half().cuda()

# 多显卡支持,使用下面两行代替上面一行,将num_gpus改为你实际的显卡数量
# from utils import load_model_on_gpus
# model = load_model_on_gpus("THUDM/chatglm2-6B", num_gpus=2)
#model = model.eval()

"""Override Chatbot.postprocess"""


def postprocess(self, y):
    if y is None:
        return []
    for i, (message, response) in enumerate(y):
        y[i] = (
            None if message is None else mdtex2html.convert((message)),
            None if response is None else mdtex2html.convert(response),
        )
    return y


gr.Chatbot.postprocess = postprocess


def parse_text(text):
    """copy from https://github.com/GaiZhenbiao/ChuanhuChatGPT/"""
    lines = text.split("n")
    lines = [line for line in lines if line != ""]
    count = 0
    for i, line in enumerate(lines):
        if "```" in line:
            count  = 1
            items = line.split('`')
            if count % 2 == 1:
                lines[i] = f'<pre><code class="language-{items[-1]}">'
            else:
                lines[i] = f'<br></code></pre>'
        else:
            if i > 0:
                if count % 2 == 1:
                    line = line.replace("`", "`")
                    line = line.replace("<", "&lt;")
                    line = line.replace(">", "&gt;")
                    line = line.replace(" ", "&nbsp;")
                    line = line.replace("*", "&ast;")
                    line = line.replace("_", "&lowbar;")
                    line = line.replace("-", "&#45;")
                    line = line.replace(".", "&#46;")
                    line = line.replace("!", "&#33;")
                    line = line.replace("(", "&#40;")
                    line = line.replace(")", "&#41;")
                    line = line.replace("$", "&#36;")
                lines[i] = "<br>" line
    text = "".join(lines)
    return text


def predict(input, chatbot, max_length, top_p, temperature, history, past_key_values):
    chatbot.append((parse_text(input), ""))
    for response, history, past_key_values in model.stream_chat(tokenizer, input, history, past_key_values=past_key_values,
                                                                return_past_key_values=True,
                                                                max_length=max_length, top_p=top_p,
                                                                temperature=temperature):
        chatbot[-1] = (parse_text(input), parse_text(response))

        yield chatbot, history, past_key_values


def reset_user_input():
    return gr.update(value='')


def reset_state():
    return [], [], None


if __name__ == '__main__':
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("--listen", action='store_true',
                        help="launch gradio with 0.0.0.0 as server name, allowing to respond to network requests")
    parser.add_argument("--port", type=int,
                        help="launch gradio with given server port, you need root/admin rights for ports < 1024, defaults to 7860 if available",
                        default=None)
    args = parser.parse_args()
    server_name = "0.0.0.0" if args.listen else None
    server_port = args.port

    with gr.Blocks() as demo:
        gr.HTML("""<h1 align="center">ChatGLM2-6B</h1>""")

        chatbot = gr.Chatbot()
        with gr.Row():
            with gr.Column(scale=4):
                with gr.Column(scale=12):
                    user_input = gr.Textbox(show_label=False, placeholder="Input...", lines=10).style(
                        container=False)
                with gr.Column(min_width=32, scale=1):
                    submitBtn = gr.Button("Submit", variant="primary")
            with gr.Column(scale=1):
                emptyBtn = gr.Button("Clear History")
                max_length = gr.Slider(0, 32768, value=8192, step=1.0, label="Maximum length", interactive=True)
                top_p = gr.Slider(0, 1, value=0.8, step=0.01, label="Top P", interactive=True)
                temperature = gr.Slider(0, 1, value=0.95, step=0.01, label="Temperature", interactive=True)

        history = gr.State([])
        past_key_values = gr.State(None)

        submitBtn.click(predict, [user_input, chatbot, max_length, top_p, temperature, history, past_key_values],
                        [chatbot, history, past_key_values], show_progress=True)
        submitBtn.click(reset_user_input, [], [user_input])

        emptyBtn.click(reset_state, outputs=[chatbot, history, past_key_values], show_progress=True)

        demo.queue().launch(share=False, inbrowser=True, server_name=server_name, server_port=server_port)

重新启动服务,命令:

代码语言:javascript复制
python web_demo.py --listen --port 8000

训练前的:

训练后的:

四、销毁资源

1、清理 Cloud Studio 中创建的工作空间

2、销毁 高性能应用服务HAI 创建的 ChatGLM2 6B 服务

五、实验小结

本次实验主要是引导大家如何使用 高性能应用服务 HAI 部署 ChatGLM2-6B 运行环境进行AI对话并创作个人专属的知识宇宙,开箱即用,可以快速上手;同时,也使用了 Cloud Studio 快速开发部署简单的应用程序。最后也欢迎大家一起探索 高性能应用服务 HAI 更多的功能,为工作中赋能增效降本!

0 人点赞