二次开发langchain chat chat 加载自定义p_tuning模型、rag数据集准备、整体优化策略

213 阅读13分钟

最近公司产品忽然看上了langchain chat chat 让我们进行二次开发。分到博主头上的任务是让langchain chat chat 具有更强的能力。

第一部分工作,让模型支持自定义微调模型加载。

熟悉chatglm的小伙伴们都知道,chatglm有两种微调模式,第一种微调模式是全参数量微调模式。也是相对较为简单地模式。只需要在chatchat项目的modelconfig中修改模型加载地址,因为全参数量微调后结果与chatglm模型文件是一致的。

但是很不幸我们使用的是第二种chatglm微调模式底资源prompt tuning模式进行的chatglm模型的微调。在chatchat的github日志中也看到了一些小伙伴们需要加载自定义模型。

首先我们需要运行模型参数初始化

运行模型初始化之后会生成configs/model_config.py文件。

在这个文件的MODEL_PATH字典中增加自定义模型。

MODEL_PATH = {
    "embed_model": {
        "ernie-tiny": "nghuyong/ernie-3.0-nano-zh",
    },

    "llm_model": {
        "/home/gpu/models/chatglm2-6b": "THUDM/chatglm2-6b",

    },

    "reranker": {
        "bge-reranker-large": "BAAI/bge-reranker-large",
    },
    "p_tuning_checkpoint": "p_tuning模型在服务器上的绝对地址"
}

这里我们将部分模型内容省略用以节约文章篇幅。

接下来找到 langchain chat chat的server/utils.py文件,将以下代码

def get_model_worker_config(model_name: str = None) -> dict:
    '''
    加载model worker的配置项。
    优先级:FSCHAT_MODEL_WORKERS[model_name] > ONLINE_LLM_MODEL[model_name] > FSCHAT_MODEL_WORKERS["default"]
    '''
    from configs.model_config import ONLINE_LLM_MODEL, MODEL_PATH
    from configs.server_config import FSCHAT_MODEL_WORKERS
    from server import model_workers

    config = FSCHAT_MODEL_WORKERS.get("default", {}).copy()
    config.update(ONLINE_LLM_MODEL.get(model_name, {}).copy())
    config.update(FSCHAT_MODEL_WORKERS.get(model_name, {}).copy())

    if model_name in ONLINE_LLM_MODEL:
        config["online_api"] = True
        if provider := config.get("provider"):
            try:
                config["worker_class"] = getattr(model_workers, provider)
            except Exception as e:
                msg = f"在线模型 ‘{model_name}’ 的provider没有正确配置"
                logger.error(f'{e.__class__.__name__}: {msg}',
                             exc_info=e if log_verbose else None)
    # 本地模型
    if model_name in MODEL_PATH["llm_model"]:
        config["model_path"] = get_model_path(model_name)
        config["device"] = llm_device(config.get("device"))
    return config

替换为

def get_model_worker_config(model_name: str = None) -> dict:
    '''
    加载model worker的配置项。
    优先级:FSCHAT_MODEL_WORKERS[model_name] > ONLINE_LLM_MODEL[model_name] > FSCHAT_MODEL_WORKERS["default"]
    '''
    from configs.model_config import ONLINE_LLM_MODEL, MODEL_PATH
    from configs.server_config import FSCHAT_MODEL_WORKERS
    from server import model_workers

    config = FSCHAT_MODEL_WORKERS.get("default", {}).copy()
    config.update(ONLINE_LLM_MODEL.get(model_name, {}).copy())
    config.update(FSCHAT_MODEL_WORKERS.get(model_name, {}).copy())

    if model_name in ONLINE_LLM_MODEL:
        config["online_api"] = True
        if provider := config.get("provider"):
            try:
                config["worker_class"] = getattr(model_workers, provider)
            except Exception as e:
                msg = f"在线模型 ‘{model_name}’ 的provider没有正确配置"
                logger.error(f'{e.__class__.__name__}: {msg}',
                             exc_info=e if log_verbose else None)
    # 本地模型
    if model_name in MODEL_PATH["llm_model"]:
        path = get_model_path(model_name)
        config["model_path"] = path
        if path and os.path.isdir(path):
            config["model_path_exists"] = True
        config["device"] = llm_device(config.get("device"))
    if "p_tuning_checkpoint" in MODEL_PATH:
        config['p_tuning_checkpoint'] = MODEL_PATH["p_tuning_checkpoint"]
    return config

也就是在最后加入了配置文件中指定的自定义模型的地址

接下来到文件中进行修改 将以下部分代码

worker = ModelWorker(
                controller_addr=args.controller_address,
                worker_addr=args.worker_address,
                worker_id=worker_id,
                model_path=args.model_path,
                model_names=args.model_names,
                limit_worker_concurrency=args.limit_worker_concurrency,
                no_register=args.no_register,
                device=args.device,
                num_gpus=args.num_gpus,
                max_gpu_memory=args.max_gpu_memory,
                load_8bit=args.load_8bit,
                cpu_offloading=args.cpu_offloading,
                gptq_config=gptq_config,
                awq_config=awq_config,
                stream_interval=args.stream_interval,
                conv_template=args.conv_template,
                embed_in_truncate=args.embed_in_truncate,
            )

更改为

            worker = ModelWorker(
                controller_addr=args.controller_address,
                worker_addr=args.worker_address,
                worker_id=worker_id,
                model_path=args.model_path,
                ptuning_checkpoint = args.p_tuning_checkpoint,
                model_names=args.model_names,
                limit_worker_concurrency=args.limit_worker_concurrency,
                no_register=args.no_register,
                device=args.device,
                num_gpus=args.num_gpus,
                max_gpu_memory=args.max_gpu_memory,
                load_8bit=args.load_8bit,
                cpu_offloading=args.cpu_offloading,
                gptq_config=gptq_config,
                awq_config=awq_config,
                stream_interval=args.stream_interval,
                conv_template=args.conv_template,
                embed_in_truncate=args.embed_in_truncate,
            )

将fastchat源码复制到项目跟目录下。

进入fastchat源码在fastchat/serve/model_worker.py文件中修改ModelWorker类,加入参数ptuning_checkpoint: str =None, 接下来将以下代码

        # 加载语言模型
        self.model, self.tokenizer = load_model(
            model_path,
            revision=revision,
            device=device,
            num_gpus=num_gpus,
            max_gpu_memory=max_gpu_memory,
            dtype=dtype,
            load_8bit=load_8bit,
            cpu_offloading=cpu_offloading,
            gptq_config=gptq_config,
            awq_config=awq_config,
            exllama_config=exllama_config,
            xft_config=xft_config,
            debug=debug,
        )

更改为

        # 加载语言模型
        self.model, self.tokenizer = load_model(
            model_path,
            ptuning_checkpoint=ptuning_checkpoint,
            revision=revision,
            device=device,
            num_gpus=num_gpus,
            max_gpu_memory=max_gpu_memory,
            dtype=dtype,
            load_8bit=load_8bit,
            cpu_offloading=cpu_offloading,
            gptq_config=gptq_config,
            awq_config=awq_config,
            exllama_config=exllama_config,
            xft_config=xft_config,
            debug=debug,
        )

进入fastchat/model/model_adapter.py修改load_model方法加入参数ptuning_checkpoint=None 接下来将代码

        model, tokenizer = adapter.load_model(model_path, kwargs)

修改为

    if ptuning_checkpoint:
        model, tokenizer = adapter.load_ptuning_model(model_path, ptuning_checkpoint, kwargs)
        # return model, tokenizer
    else:
        # Load model
        model, tokenizer = adapter.load_model(model_path, kwargs)

在类BaseModelAdapter中加入以下实现方法

    def load_ptuning_model(self, model_path: str, ptuning_checkpoint: dict, from_pretrained_kwargs: dict):
        revision = from_pretrained_kwargs.get("revision", "main")
        print(model_path)
        try:
            tokenizer = AutoTokenizer.from_pretrained(
                model_path,
                use_fast=self.use_fast_tokenizer,
                revision=revision,
                trust_remote_code=True,
            )
        except TypeError:
            tokenizer = AutoTokenizer.from_pretrained(
                model_path, use_fast=False, revision=revision, trust_remote_code=True
            )
        try:
            config = AutoConfig.from_pretrained(model_path, trust_remote_code=True)
            config.pre_seq_len = 128
            config.prefix_projection = False
            model = AutoModel.from_pretrained(
                model_path,
                config=config,
                low_cpu_mem_usage=True,
                trust_remote_code=True,
                **from_pretrained_kwargs,
            )
        except NameError:
            model = AutoModel.from_pretrained(
                model_path,
                low_cpu_mem_usage=True,
                trust_remote_code=True,
                **from_pretrained_kwargs,
            )
        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.transformer.prefix_encoder.float()

        return model, tokenizer

接下来我们启动项目就可以加载我们通过ptuning方式进行微调后的chatglm模型了。

如何需要验证chatchat核心功能是否可以符合垂直场景应用,这里我们有几条实现路径。

第一条实现路径 首先获取到垂直领域的段落语聊。这里我用了目前langchain中推荐的一个富文档非结构化文本的分割工具。首先准备一个安装了pytorch环境的服务器。接下来安装unstructured。

pip install "unstructured[all-docs]"

新建文件夹 命名为 pdf 将pdf文件传输到pdf文件夹下。

import glob
import os
import json

file_list = glob.glob("pdf/*.pdf")
d = []
for file_one in file_list:
    try:
        elements = partition(filename=file_one)
    except:
        continue
    # print("\n\n".join([str(el) for el in elements if len(str(el)) > 256 ]))
    filename = os.path.split(file_one)[-1]
    d.append({"filename":filename,"content":[str(el) for el in elements if len(str(el)) > 256 ]})
    json.dump(d,open("plane.json","w"),ensure_ascii=False)

数据案例

filename: "002880卫光生物_2021年年度报告_2022-04-15_SZZB.PDF"
0: "2021 年度,本公司对募集资金投资项目累计投入募集资金 54,658.59 万元,其中公司于募集资金到位前利用自有资 金先期投入募集项目 18,646.79 万元,从 2017 年 6 月 8 日起至 2021 年 12 月 31 日止使用募集资金 36,011.80 万元;2021 年 8 月 19 日公司第二届董事会第四十次会议、第二届监事会第二十九次会议分别审议通过了《关于继续使用部分闲置 募集资金暂时补充流动资金的议案》,同意公司使用 4,000 万元闲置募集资金暂时补充流动资金,使用期限自董事会审 议通过之日起不超过十二个月;募集资金账户累计利息(扣除手续费)为 411.67 万元。截至 2021 年 12 月 31 日,募集资 金余额为 4,048.78 万元。"
1: "钟山浆站新建项目预计总投资为 8,691.48 万元,其中一期建设预计投资 5,600 万元,二期 3,091.48 万元。截至 2021 年 12 月 31 日一期工程已投入 3,510.62 万元,其中原募投项目变更资金 723.54 万元全部投入一期建设,已基本使用完毕;二期项目正在规划中,未投入资金。投资进度 尚未完成的主要原因是:受原项目实施地点被政府征地影响以及 2020 年初爆发新冠疫情,项目于 2020 年 8 月方正式施工。项目为购置土地并新建采浆业务用房,项目一期主体工程基本完成,现 处于装修招投标阶段,二期建设正在规划中。"

接下来使用zhipu提供的glm4接口进行生成构建rag数据集

from zhipuai import ZhipuAI
client = ZhipuAI(api_key="填写您自己的APIKey") # 填写您自己的APIKey
output=json.load(open("glm-4-turbo_plane.json","r"))
for i in plane_text:
    for content in i['content']:
        if "cid:" in content:
            continue
        response = client.chat.completions.create(
            model="glm-4", # 填写需要调用的模型名称
            messages=[
                {"role": "user", "content": "根据文档"+i['filename']+"提到的以下内容 "+content+"进行提问  要求所提出的问题可以在基于这段内容的信息进行回复,如果提出的问题无法在内容中获取到有效则不需要回复。"},
            ],
        )
        print(response.choices[0].message)
        output.append({"filename":i['filename'],"content":content,"query_content":"根据文档"+i['filename']+"提到的以下内容 "+content+"进行提问  要求所提出的问题可以在基于这段内容的信息进行回复,如果提出的问题无法在内容中获取到有效则不需要回复。","message":response.choices[0].message.content})
        json.dump(output,open("glm-4-turbo_plane.json","w"),ensure_ascii=False)

生成了一套基于模型的结果

filename: "001205盛航股份_2021年年度报告_2022-03-10_SZZB.PDF"
content: "2022年1月13日,公司以自有资金出资人民币1,000万元与专业机构西藏金缘投资管理有限公司(以下 简称“基金管理人”)、公司高级管理人员李广红先生、陈书筛先生,以及其他13名自然人共同投资设立基 金,并签署《嘉兴纯素股权投资合伙企业(有限合伙)合伙协议》(以下简称“合伙协议”),全体合伙人 均已完成实缴出资。该基金以可转债形式专项投资于青海丽豪半导体材料有限公司,青海丽豪半导体材料 有限公司是一家从事高纯度硅晶等半导体材料的生产工艺研发、生产和销售的大型民营科技型企业。基金 设立规模为人民币5,350万元,其中基金管理人为普通合伙人,认缴出资人民币150万元;公司为有限合伙 人,认缴出资人民币1,000万元;陈书筛为有限合伙人,认缴出资人民币360万元;李广红为有限合伙人, 认缴出资人民币260万元,其他13名自然人均为有限合伙人,合计认缴出资人民币3,580万元。"
query_content: "根据文档001205盛航股份_2021年年度报告_2022-03-10_SZZB.PDF提到的以下内容 2022年1月13日,公司以自有资金出资人民币1,000万元与专业机构西藏金缘投资管理有限公司(以下 简称“基金管理人”)、公司高级管理人员李广红先生、陈书筛先生,以及其他13名自然人共同投资设立基 金,并签署《嘉兴纯素股权投资合伙企业(有限合伙)合伙协议》(以下简称“合伙协议”),全体合伙人 均已完成实缴出资。该基金以可转债形式专项投资于青海丽豪半导体材料有限公司,青海丽豪半导体材料 有限公司是一家从事高纯度硅晶等半导体材料的生产工艺研发、生产和销售的大型民营科技型企业。基金 设立规模为人民币5,350万元,其中基金管理人为普通合伙人,认缴出资人民币150万元;公司为有限合伙 人,认缴出资人民币1,000万元;陈书筛为有限合伙人,认缴出资人民币360万元;李广红为有限合伙人, 认缴出资人民币260万元,其他13名自然人均为有限合伙人,合计认缴出资人民币3,580万元。进行提问 要求所提出的问题可以在基于这段内容的信息进行回复,如果提出的问题无法在内容中获取到有效则不需要回复。"
message: "以下是基于提供内容的潜在问题: 1. 盛航股份在2022年1月13日设立了哪个基金? 2. 该基金的全体合伙人是否已经完成实缴出资? 3. 盛航股份在基金中认缴了多少资金? 4. 基金的主要投资对象是哪家公司? 5. 西藏金缘投资管理有限公司在该基金中扮演什么角色? 6. 李广红和陈书筛个人在基金中分别认缴了多少资金? 7. 除了公司、基金管理人和李广红、陈书筛之外,还有多少个自然人参与了该基金的投资? 8. 这些自然人在基金中认缴的总资金是多少? 9. 基金的设立规模是多少? 10. 青海丽豪半导体材料有限公司主要生产什么类型的半导体材料? 以上问题都可以从所提供的内容中找到答案。如果需要具体回答,请告知我进行回复。"

面向这个结果进行标注就可以让我们快速构建一套垂直领域的指令数据集。

这里我才用的指令模板是

"根据文档"+文档名称+"提到的以下内容 "+文档内容+"进行提问 要求所提出的问题可以在基于这段内容的信息进行回复,如果提出的问题无法在内容中获取到有效则不需要回复。"

如果有更多的模板设计也可以遵循这种模式来进行操作,从而快速的构建领域初始数据集。

在自然语言处理领域,RAG(Reading-to-Answering with General Language Model)是一种阅读理解模型,它可以理解自然语言文本并生成答案。这种模型通常需要大量的数据集来训练,以确保它能够准确地理解和回答各种问题。 如果您想要在特定领域或针对特定任务进行数据集的生成,您可能需要遵循以下步骤:

  1. 定义Prompt模板:创建一个或多个 prompt 模板,这些模板定义了您希望模型生成的文本的格式和内容。例如,如果您正在为医疗咨询生成数据,您的 prompt 可能会要求模型提供关于特定症状的建议。
  2. 生成数据:使用 GLM-4 或其他合适的语言模型,根据您定义的 prompt 模板生成数据。模型将基于模板提供答案,这些答案可能需要后续的审核和编辑。
  3. 数据审核:由于模型生成的数据可能包含不准确或不符合逻辑的内容,因此需要对生成的数据进行审核。这可以通过人工审核或使用其他模型(如 RAG)来完成。
  4. 小模型微调:一旦生成的数据被审核和清理,您可以使用这些数据来微调小模型,例如 GLM-4 的子集或专门为您的任务设计的模型。微调将帮助模型更好地适应您的特定领域。
  5. 领域生成式任务数据集:最终,您将拥有一个针对特定领域和任务优化的数据集,可以用于训练和测试阅读理解模型,如 RAG。

请注意,这个过程需要对自然语言处理和机器学习有一定的了解,以确保正确地设计和实施每一步。如果您不熟悉这些技术,可能需要与经验丰富的数据科学家或开发人员合作。