CP03大语言模型ChatGLM3-6B特性代码解读(1)
文章目录
- CP03大语言模型ChatGLM3-6B特性代码解读(1)
-
- 总述
- 提示词及UI交互基础conversation.py
- 对话基础client.py
-
- 模型路径等参数设置
- [创建processor,并定义EOS token](#创建processor,并定义EOS token)
- 定义stream_chat对话函数
- 相关参数备查:
- 补充知识,辅助理解上述stream_chat实现
总述
对话模式、工具模式、代码解释器模式例程阅读理解。
ChatGLM3-6B已经进行了中文场景的训练,可以直接运用于中文场景。本次学习的示例,提供了三种模式。包括:
- Chat: 对话模式,在此模式下可以与模型进行对话;
- Tool: 工具模式,模型除了对话外,还可以通过工具进行其他操作;
- Code Interpreter: 代码解释器模式,模型可以在一个 Jupyter 环境中执行代码并获取结果,以完成复杂任务。
对话模式下,可以直接修改 top_p, temperature, System Prompt 等参数来调整模型的行为;工具模式下,可以通过在 tool_registry.py
中注册新的工具来增强模型的能力;代码解释器模式下,模型能够执行更为复杂的任务,例如绘制图表、执行符号运算等等。
本示例代码包括以下7个:
demo_ci.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}')
对话内容字符里的提示词处理
对包含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
对话基础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)
创建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|>")]
定义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
):
在该方法中,调用的方法不是之前的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)
处理输出:
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
相关参数备查:
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和/或特定于模型的
补充知识,辅助理解上述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)
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)
继续问答:
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)