本次部署为简单概述流程,本地配置为4060Ti-16G,采用7b模型部署。

下载模型

注:模型实际上可以直接在线使用,此处只为便于离线使用。

模型通常开源于 huggingface,一个大模型开源网站,开源了大量模型,本次模型地址:Qwen-math-7b。模型地址通常类似于github即是一个在线的git仓库,也就是能使用git进行克隆。

git lfs

对于大模型而言,拥有较大的模型文件,git对于版本的管理需要存储文件的存储拷贝,对于多个历史版本,较大的文件在本地有多份拷贝会使git管理繁琐,并会使文件体积大幅度膨胀。git lfs作为git的拓展提供了一个解决方法,其将大文件的历史存储在远程服务器上,git仓库仅存储大文件的指针,在需要对应历史的大文件的时候,就会自动从远程服务器中获取。

对于本次的模型克隆,即

git lfs clone https://huggingface.co/Qwen/Qwen2.5-Math-7B

等待下载完即可

huggingface_hub

这是 Hugging Face 提供的一个 Python 库,用于与 Hugging Face Hub 进行交互。Hugging Face Hub 是一个用于存储和共享模型、数据集、预训练权重等资源的平台,广泛应用于自然语言处理(NLP)和其他深度学习任务。重要的是,它可以轻松地下载 Hugging Face 上的模型、数据集和文件。例如,你可以从 Hub 下载预训练的 Transformer 模型,或者自定义的模型和数据集。

官方文档

hf_hub_download

这是其提供的一个函数,用于下载单个文件

主要有以下的参数(*必须参数):

snapshot_download

用于下载整个仓库的快照

主要有以下的参数(*必须参数):

  • repo_id(*):仓库名
  • local_dir:下载保存的目录
  • endpoint:可以填入镜像站。

huggingface-cli

可以使用快速下载的脚手架,例如以下:

huggingface-cli download gpt2 config.json

镜像站

对于国内的网络环境,为节省流量以及提高下载效率,故使用huggingface的镜像站对于国内用户更加合适:

镜像站 hf-mirror镜像站 modelscope

镜像站进行前面的替换即可

部署模型

部署模型采用Transformers调用,Hugging Face 开发的 Transformers 库是一个由 Hugging Face 提供的开源库,旨在简化和统一深度学习模型,特别是基于 Transformer 架构的模型(如 BERT、GPT、T5 等)的使用。它为各种自然语言处理(NLP)任务提供了预训练模型和方便的 API,使得开发者能够轻松地进行模型的训练、微调和推理。

载入模型

from transformers import AutoModelForCausalLM, AutoTokenizer

def load_model(model_path):
    device = "cuda:0"

    model = AutoModelForCausalLM.from_pretrained(
        model_path,
        torch_dtype="auto",
        device_map={"": 0},
        use_safetensors=True)

    tokenizer = AutoTokenizer.from_pretrained(model_path)

    return device, tokenizer, model

Tokenizer

分词器(Tokenizer)是自然语言处理(NLP)中的一个重要组件,它的主要作用是将原始文本(例如句子或段落)转换为模型可以理解的形式。对于深度学习模型,尤其是基于 Transformer 架构的模型(如 BERT、GPT 等),分词器将文本切分为更小的单元(通常是单词或子词),并将这些单元转换为对应的数字 ID,作为模型的输入。

分词器通常和模型是配对的,因此下载来的模型文件夹包含了模型文件和分词器配置,可以载入

载入模型参数介绍

注:AutoTokenizer的载入并不需要大量显存也不属于模型,故载入的时候不需要后两个参数。

仅介绍一部分参数

  • model_name_or_path:指定预训练模型的名称或路径。可以是 Hugging Face Model Hub 上的模型标识符,也可以是本地文件系统上的路径。
  • cache_dir:缓存目录
  • force_download:即使在缓存中已有模型的情况下,也强制重新下载模型
  • resume_download:如果下载被中断,则从中断的地方继续下载。
  • proxies:代理
  • torch_dtype:设置加载模型时使用的 PyTorch dtype。这对于模型精度和性能调优特别有用
  • device_map:控制模型的计算图如何分配到不同的设备(如GPU、CPU)上,
    • "sequential”模型按顺序加载到多个GPU上
    • "auto”会自动优化分配
    • device_map={“”: “cuda:0”}:手动分配到GPU0

使用模型输入输出(Quick Start)

def chat_qwen(device, tokenizer, model, messages: list):

    # 使用分词器的 apply_chat_template 方法将消息格式化为模型可理解的输入格式
    text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
    model_inputs = tokenizer(text, return_tensors="pt").to(device)

    # 生成模型输出
    generated_ids = model.generate(
        model_inputs.input_ids,
        max_new_tokens=512
    )

    # 由于模型输出包括输入模型,这里切去输入部分
    generated_ids = (output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids))
    # 将模型输出解码为文本
    response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]

    return response

对于模型的使用而言,需要知道最基础的一部分知识。

对于模型,其接受的文本首先要经过分词器处理,才能得出结果,并且其结果也要经过分词器解码,类似于和外星人聊天之间的翻译官,对于本模型而言,即给他一段对话环境作为输入,他将输出一句新的话作为补充回复。

设置输入消息

大模型中通常有三个角色:system,user,assistant。在openai给出的GPT文档中,还有developer类的角色。

  • system类消息:开发人员提供的模型应该遵循的指令,而不管用户发送的消息是什么。
  • user类消息:由最终用户发送的消息,包含提示或其他上下文信息。
  • assistent类消息:模型为响应用户消息而发送的消息。

由这些消息组成以作为输入提供给大模型。

消息格式化编码化

apply_chat_template 函数就用于格式化上面设置的消息

[{'role': 'system', 'content': 'You are a helpful assistant.'}, {'role': 'user', 
'content': '你是谁?'}]

通过格式化化变为

<|im_start|>system
 You are a helpful assistant.<|im_end|>
 <|im_start|>user
你是谁?<|im_end|>
 <|im_start|>assistant

再通过tokenizer来对消息进行编码

将处理后的文本通过分词器转换为模型需要的输入格式,return_tensors=“pt” 表示返回 PyTorch 张量格式,并将输入传送到前面指定的设备上

获取输出并解码

模型的输出实际包含输入的数据,因此将输入的数据切片后解码即可。

使用例

if __name__ == '__main__':
    MODEL = "Qwen2.5-Math-7B"
    pak = load_model(MODEL)
    msgs = [{"role": "system",
             "content": "你是一个数学方面的助手,需要帮助用户解决数学问题,要求答案正确,并且要提供思考步骤。"}]
    while True:
        ques = input()
        msgs.append({"role": "user", "content": ques})

        print("接受到问题开始思考")
        res = chat_qwen(*pak, msgs)
        print("思考完毕")
        print("回答:", res)
        msgs.append({"role": "assistant", "content": res})

        print("当前问答链")
        pprint(msgs)

这个例子可以多次和模型进行问答,并且还可以记录历史的问答,让模型具有记忆。

结合到Longchain

文档

按照文档,把定制模型置入即可,以下供参考

class QwenMath(LLM):
    # 模型参数
    max_new_tokens: int = 1920
    temperature: float = 0.9
    top_p: float = 0.8
    tokenizer: object = None
    model: object = None
    history: List = []
    device: object = torch.device("cuda" if torch.cuda.is_available() else "cpu")

    def __init__(self, max_new_tokens=1920,
                 temperature=0.9,
                 top_p=0.8):
        super().__init__()
        self.max_new_tokens = max_new_tokens
        self.temperature = temperature
        self.top_p = top_p

    @property
    def _llm_type(self) -> str:
        return "Qwen2"

    def load_model(self, model_name_or_path=None):
        self.tokenizer = AutoTokenizer.from_pretrained(
            model_name_or_path,
            trust_remote_code=True
        )
        self.model = AutoModelForCausalLM.from_pretrained(
            model_name_or_path,
            torch_dtype="auto",
            # torch_dtype="torch.float16",
            device_map={"": 0},  # sequential/auto/balanced_low_0
        )

    def chat_stream(self, model, tokenizer, query: str, history: list):
        with torch.no_grad():
            # 历史整理
            messages = [
                {'role': 'system', 'content': '你是一个数学方面的助手,需要帮助用户解决数学问题,要求答案正确,并且要提供思考步骤。'},
            ]
            # 将之前的history内容重新组合
            for item in history:
                if item['role'] == 'user':
                    if item.get('content'):
                        messages.append({'role': 'user', 'content': item['content']})
                if item['role'] == 'assistant':
                    if item.get('content'):
                        messages.append({'role': 'assistant', 'content': item['content']})
            # 最新的用户问题
            messages.append({'role': 'user', 'content': query})
            # 模型推理
            text = tokenizer.apply_chat_template(
                messages,
                tokenize=False,
                add_generation_prompt=True
            )

            model_inputs = tokenizer([text], return_tensors="pt").to(self.device)

            generated_ids = model.generate(
                **model_inputs,
                max_new_tokens=self.max_new_tokens,
                do_sample=False,
                top_p=self.top_p,
                temperature=self.temperature
            )
            generated_ids = [
                output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
            ]
            # 模型根据messages的内容后的输出
            response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
            # 将模型输出组合到messages中
            messages.append({'role': 'assistant', 'content': response})

        return response, messages

        # Langchain调用

    def _call(self, prompt: str, stop=["<|user|>"], run_manager=None, **kwargs):
        # 主要调用chat_stream实现
        response, self.history = self.chat_stream(self.model, self.tokenizer, prompt, self.history)

        return response

    def query_only(self, query):
        # 当使用RAG技术时会出现用户输入存在大量的参考资料,导致模型难以理解整体上下文内容。
        if self.history[-2]['role'] == 'user':
            self.history[-2]['content'] = query

    def get_history(self) -> List:
        return self.history

    def delete_history(self):
        del self.history
        self.history = []

其中增加了一些采样参数:

  • temperature:控制生成文本的随机性。温度高时,生成的文本更加多样、创意,但也可能不太连贯;温度低时,文本更稳定,但可能缺乏创意。
  • top_p:控制生成词汇的范围。模型会选择一些最有可能的词,直到这些词的概率总和达到top_p指定的阈值。top_p值越小,生成的内容越保守,越大则生成的内容更有变化。

使用时应当先设定参数:do_sample=True,代表启用,否则不使用参数。

上面的例子并没有使用参数。

使用例:

if __name__ == '__main__':
    MODEL = "Qwen2.5-Math-7B"
    llm = QwenMath()
    llm.load_model(MODEL)

    query_ = "3加上5等于几"
    res = llm.invoke(query_)
    print(res)