• CP03大语言模型ChatGLM3-6B特性代码解读(1)


    CP03大语言模型ChatGLM3-6B特性代码解读(1)

    总述

    对话模式、工具模式、代码解释器模式例程阅读理解。
    ChatGLM3-6B已经进行了中文场景的训练,可以直接运用于中文场景。本次学习的示例,提供了三种模式。包括:

    • Chat: 对话模式,在此模式下可以与模型进行对话;
    • Tool: 工具模式,模型除了对话外,还可以通过工具进行其他操作;
    • Code Interpreter: 代码解释器模式,模型可以在一个 Jupyter 环境中执行代码并获取结果,以完成复杂任务。

    对话模式下,可以直接修改 top_p, temperature, System Prompt 等参数来调整模型的行为;工具模式下,可以通过在 tool_registry.py 中注册新的工具来增强模型的能力;代码解释器模式下,模型能够执行更为复杂的任务,例如绘制图表、执行符号运算等等。

    本示例代码包括以下7个:

    conversation.py
    demo_ci.py
    main.py
    client.py
    demo_chat.py
    demo_tool.py
    tool_registry.py

    受篇幅影响,本文先解读client.py和conversation.py。

    提示词及UI交互基础conversation.py

    在conversation.py中处理streamlit相关以及prompt提示词处理相关的内容。使之符合本次demo的逻辑和结构。

    提示词相关角色Role的处理

    在之前的文章中介绍过,LLM的提示词角色一般包括system、user、assistant、obervation。以下代码是例程中的条件处理:

    # Get the message block for the given role
        def get_message(self):
            # Compare by value here, because the enum object in the session state
            # is not the same as the enum cases here, due to streamlit's rerunning
            # behavior.
            match self.value:
                case Role.SYSTEM.value:
                    return
                case Role.USER.value:
                    return st.chat_message(name="user", avatar="user")
                case Role.ASSISTANT.value:
                    return st.chat_message(name="assistant", avatar="assistant")
                case Role.TOOL.value:
                    return st.chat_message(name="tool", avatar="assistant")
                case Role.INTERPRETER.value:
                    return st.chat_message(name="interpreter", avatar="assistant")
                case Role.OBSERVATION.value:
                    return st.chat_message(name="observation", avatar="user")
                case _:
                    st.error(f'Unexpected role: {self}')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    对话内容字符里的提示词处理

    对包含system、user、assistant、obervation等字符的处理:

    def preprocess_text(
        system: str | None,
        tools: list[dict] | None,
        history: list[Conversation],
    ) -> str:
        if tools:
            tools = json.dumps(tools, indent=4, ensure_ascii=False)
    
        prompt = f"{Role.SYSTEM}\n"
        prompt += system if not tools else TOOL_PROMPT
        if tools:
            tools = json.loads(tools)
            prompt += json.dumps(tools, ensure_ascii=False)
        for conversation in history:
            prompt += f'{conversation}'
        prompt += f'{Role.ASSISTANT}\n'
        return prompt
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    对话基础client.py

    在client.py中import transformers相关接口,包括AutoModel, AutoTokenizer, AutoConfig,LogitsProcessor,LogitsProcessorList等。

    模型路径等参数设置

    MODEL_PATH = os.environ.get('MODEL_PATH', 'THUDM/chatglm3-6b')
    PT_PATH = os.environ.get('PT_PATH', None)
    PRE_SEQ_LEN = int(os.environ.get("PRE_SEQ_LEN", 128))
    TOKENIZER_PATH = os.environ.get("TOKENIZER_PATH", MODEL_PATH)
    
    • 1
    • 2
    • 3
    • 4

    创建processor,并定义EOS token

    LogitsProcessor的作用就是在生成过程中修改score,改变模型输出的概率分布的工具。

    if logits_processor is None:
    logits_processor = LogitsProcessorList()
    logits_processor.append(InvalidScoreLogitsProcessor())
    eos_token_id = [tokenizer.eos_token_id, tokenizer.get_command("<|user|>"),
    tokenizer.get_command("<|observation|>")]
    
    • 1
    • 2
    • 3
    • 4
    • 5

    定义stream_chat对话函数

    此方法其实就是ChatGLM3-6B的model.stream_chat实现。不太理解为什么挪到一个demo里使用。参考:

    ChatGLM3/THUDM/chatglm3-6b/modeling_chatglm.py

    def stream_chat(
            self, tokenizer, query: str,
            history: list[tuple[str, str]] = None,
            role: str = "user",
            past_key_values=None,
            max_new_tokens: int = 256,
            do_sample=True, top_p=0.8,
            temperature=0.8,
            repetition_penalty=1.0,
            length_penalty=1.0, num_beams=1,
            logits_processor=None,
            return_past_key_values=False,
            **kwargs
    ):
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在该方法中,调用的方法不是之前的model.chat、model.streamchat,而是tokenizer.build_chat_input等更底层的方法:
    处理输入:

        if past_key_values is None:
            inputs = tokenizer.build_chat_input(query, history=history, role=role)
        else:
            inputs = tokenizer.build_chat_input(query, role=role)
    
    • 1
    • 2
    • 3
    • 4

    处理输出:

        for outputs in self.stream_generate(**inputs, past_key_values=past_key_values,
                                            eos_token_id=eos_token_id, return_past_key_values=return_past_key_values,
                                            **gen_kwargs):
            if return_past_key_values:
                outputs, past_key_values = outputs
            outputs = outputs.tolist()[0][len(inputs["input_ids"][0]):]
            response = tokenizer.decode(outputs)
            if response and response[-1] != "�":
                new_history = history
                if return_past_key_values:
                    yield response, new_history, past_key_values
                else:
                    yield response, new_history
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    相关参数备查:

    inputs (torch.Tensor of varying shape depending on the modality,optional):
    生成使用的序列或模型输入到编码器。如果None,方法将它初始化为bos_token_id和一个大小为1的批次大小。对于只包含解码器的模型,inputs应该以input_ids的形式输入。对于编码器-解码器模型,inputs可以代表input_ids,input_values,input_features或pixel_values的任何一种。
    generation_config (~generation.GenerationConfig,optional):
    用于生成的基参数化。如果generation_config不可用,则默认值将使用模型配置中的默认值。如果提供的参数与generation_config中的参数匹配,则将使用这些参数。如果不提供generation_config,则将使用以下加载顺序:1)从generation_config.json模型文件中获取;2)从模型配置中获取。请注意,未指定的参数将继承~generation.GenerationConfig的默认值,其文档应该用于参数化生成。
    logits_processor (LogitsProcessorList,optional):
    用于补充默认logits处理器的自定义logits处理器。如果提供的logits处理器已经使用了相同的参数或生成配置,则会引发错误。此功能旨在为高级用户提供便利。
    stopping_criteria (StoppingCriteriaList,optional):
    用于补充默认停止准则的自定义停止准则。如果提供的停止准则已经使用了相同的参数或生成配置,则会引发错误。此功能旨在为高级用户提供便利。
    prefix_allowed_tokens_fn (Callable[[int, torch.Tensor], List[int]],optional):
    如果提供,则此函数仅约束搜索到的令牌。如果未提供,则不应用任何约束。此函数需要两个参数:批次IDbatch_id和input_ids。它应该返回一个条件为batch_id和以前生成的令牌inputs_ids的令牌列表。此功能可用于约束带前缀的生成,如自回归实体检索中所述。
    synced_gpus (bool,*optional,默认为False):
    是否继续运行循环直到最大长度(需要ZeRO阶段3)
    kwargs:
    随机参数化generate_config和/或特定于模型的
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    补充知识,辅助理解上述stream_chat实现

    导入模型并合并:

    
    from transformers import AutoTokenizer, AutoModel
    from peft import LoraConfig, PeftModel, get_peft_model
    
    tokenizer = AutoTokenizer.from_pretrained("./chatglm3-6b-base", trust_remote_code=True)
    model = AutoModel.from_pretrained("./chatglm3-6b-base", trust_remote_code=True).half().cuda()
    
    peft_model_id = './trained_model/checkpoint-35'
    model = PeftModel.from_pretrained(model, peft_model_id)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    LLM的文本续写步骤:

    history = []
    query = "你是谁"
    role = "user"
    inputs = tokenizer.build_chat_input(query, history=history, role=role)
    inputs = inputs.to('cuda')
    eos_token_id = [tokenizer.eos_token_id, tokenizer.get_command("<|user|>"),
                            tokenizer.get_command("<|observation|>")]
    gen_kwargs = {"max_length": 500, "num_beams": 1, "do_sample": True, "top_p": 0.8,
                          "temperature": 0.8}
    outputs = model.generate(**inputs, **gen_kwargs, eos_token_id=eos_token_id)
    outputs = outputs.tolist()[0][len(inputs["input_ids"][0]):-1]
    response = tokenizer.decode(outputs)
    history = []
    history.append({"role": "user", "content": "你是谁"})
    response, history = model.process_response(response, history)
    print(response)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    继续问答:

    query = "你能干什么"
    role = "user"
    inputs = tokenizer.build_chat_input(query, history=history, role=role)
    inputs = inputs.to('cuda')
    outputs = model.generate(**inputs, **gen_kwargs, eos_token_id=eos_token_id)
    outputs = outputs.tolist()[0][len(inputs["input_ids"][0]):-1]
    response = tokenizer.decode(outputs)
    history.append({"role": role, "content": query})
    response, history = model.process_response(response, history)
    print(response)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  • 相关阅读:
    allure
    touchGFX综合学习八、touchGFX在其他线程直接更新控件(不使用邮箱、队列、信号量等IPC)
    gcc 和 g++的区别
    怎么把文件全部重命名并排序递增
    论文翻译:《Phosvardeep:使用序列信息对磷酸变化的深度学习预测》
    Makefile template
    测试环境不稳定&复杂的必然性及其对策
    二层广播风暴(产生原因+判断+解决)
    C语言K&R圣经笔记 2.1变量名 2.2 数据类型和大小
    嵌入式软件工程师面试题(九)
  • 原文地址:https://blog.csdn.net/qq_39813001/article/details/136149277