.chat
调用分析:
In [1]: q = '你好' In [2]: r, his = model.chat(tokenizer, q) In [3]: r Out[3]: '你好👋!我是人工智能助手 ChatGLM2-6B,很高兴见到你,欢迎问我任何问题。' In [4]: his Out[4]: [('你好', '你好👋!我是人工智能助手 ChatGLM2-6B,很高兴见到你,欢迎问我任何问题。')] In [5]: q = '你可以做什么?' In [6]: r, his = model.chat(tokenizer, q, his) In [7]: r Out[7]: '我是一个大型语言模型,可以进行自然语言处理和生成。具体来说,我可以:\n\n1. 回答问题:像人类一样回答您的问题,或者提供 相关信息。\n\n2. 提供建议:根据您的问题提供一些建议,或者提供一些参考信息。\n\n3. 进行翻译:将一种语言翻译成另一种语言,或者将一种语言的文本翻译成另一种语言的文本。\n\n4. 生成文本:根据您的问题生成一些文本,比如文章、故事、新闻报道等。\n\n5. 自动文本摘要:自动概括文本的内容,并生成摘要。\n\n6. 情感分析:判断文本中情感的程度,并返回相应的情感信息。\n\n7. 智能对话:进行智能对话,与人类交流并完成任务。\n\n请注意,我是一个机器,我的回答可能不够准确,也可能会有所误导。' In [8]: his Out[8]: [('你好', '你好👋!我是人工智能助手 ChatGLM2-6B,很高兴见到你,欢迎问我任何问题。'), ('你可以做什么?', '我是一个大型语言模型,可以进行自然语言处理和生成。具体来说,我可以:\n\n1. 回答问题:像人类一样回答您的问题,或者提供相关信息 。\n\n2. 提供建议:根据您的问题提供一些建议,或者提供一些参考信息。\n\n3. 进行翻译:将一种语言翻译成另一种语言,或者将一种语言的文本翻译成另一种语言的文本。\n\n4. 生成文本:根据您的问题生成一些文本,比如文章、故事、新闻报道等。\n\n5. 自动文本摘要:自动概括文本的内容,并生成摘要。\n\n6. 情感分析:判断文本中情感的程度,并返回相应的情感信息。\n\n7. 智能对话:进行智能对话,与人类交流并完成任务。\n\n请注意,我是一个机器,我的回答可能不够准确,也可能会有所误导。')]
源码:
@torch.inference_mode() def chat(self, tokenizer, query: str, history: List[Tuple[str, str]] = None, max_length: int = 8192, num_beams=1, do_sample=True, top_p=0.8, temperature=0.8, logits_processor=None, **kwargs): if history is None: history = [] if logits_processor is None: logits_processor = LogitsProcessorList() logits_processor.append(InvalidScoreLogitsProcessor()) # 组织模型配置项 gen_kwargs = {"max_length": max_length, "num_beams": num_beams, "do_sample": do_sample, "top_p": top_p, "temperature": temperature, "logits_processor": logits_processor, **kwargs} # 将历史问答和当前提问组成整个提问,然后传给分词器得到单词ID inputs = self.build_inputs(tokenizer, query, history=history) # 提问的单词 ID 输入模型得到回答的单词概率 outputs = self.generate(**inputs, **gen_kwargs) # 取第一个回答,并截断回答中的提问部分 ''' prompt: '你好, output: tensor([[64790, 64792, 790, 30951, 517, 30910, 30939, 30996, 13, 13, 54761, 31211, 39701, 13, 13, 55437, 31211, 36474, 54591, 243, 162, 148, 142, 31404, 33030, 34797, 42481, 22011, 10461, 30944, 30943, 30941, 30978, 30949, 31123, 48895, 35214, 54622, 31123, 32616, 39905, 31901, 31639, 31155, 2]], device='cuda:0') tokenizer.decode(output[0]): '[Round 1]\n\n问:你好\n\n答: 你好👋!我是人工智能助手 ChatGLM2-6B,很高兴见到你,欢迎问我任何问题。' ''' outputs = outputs.tolist()[0][len(inputs["input_ids"][0]):] # 单词概率解码得到单词 response = tokenizer.decode(outputs) # 裁剪空白,替换训练时间 response = self.process_response(response) # 记录历史问答 history = history + [(query, response)] return response, history def build_inputs(self, tokenizer, query: str, history: List[Tuple[str, str]] = None): ''' 将历史问答和当前提问组装成整个输入 In [1]: tokenizer.build_prompt('Q3', [('Q1', 'A1'),('Q2', 'A2')]) Out[1]: '[Round 1]\n\n问:Q1\n\n答:A1\n\n[Round 2]\n\n问:Q2\n\n答:A2\n\n[Round 3]\n\n问:Q3\n\n答:' ''' prompt = tokenizer.build_prompt(query, history=history) ''' 整个提问传给分词器得到单词ID In [2]: tokenizer(['你好'], return_tensors="pt") Out[2]: { 'input_ids': tensor([[64790, 64792, 36474, 54591]]), 'attention_mask': tensor([[1, 1, 1, 1]]), 'position_ids': tensor([[0, 1, 2, 3]]) } ''' inputs = tokenizer([prompt], return_tensors="pt") inputs = inputs.to(self.device) return inputs
.stream_chat
调用分析:
In [133]: q = '你好' In [134]: it = model.stream_chat(tokenizer, q) In [135]: for r, his in it: print(r); print(his) 你 [('你好', '你')] 你好 [('你好', '你好')] 你好👋 [('你好', '你好👋')] ... 你好👋!我是人工智能助手 ChatGLM2-6B,很高兴见到你,欢迎问我任何问题 [('你好', '你好👋!我是人工智能助手 ChatGLM2-6B,很高兴见到你,欢迎问我任何问题')] 你好👋!我是人工智能助手 ChatGLM2-6B,很高兴见到你,欢迎问我任何问题。 [('你好', '你好👋!我是人工智能助手 ChatGLM2-6B,很高兴见到你,欢迎问我任何问题。')] 你好👋!我是人工智能助手 ChatGLM2-6B,很高兴见到你,欢迎问我任何问题。 [('你好', '你好👋!我是人工智能助手 ChatGLM2-6B,很高兴见到你,欢迎问我任何问题。')] In [136]: q = '你可以做什么?' In [137]: it = model.stream_chat(tokenizer, q, his) In [138]: for r, his in it: print(r); print(his) 我 [('你好', '你好👋!我是人工智能助手 ChatGLM2-6B,很高兴见到你,欢迎问我任何问题。'), ('你可以做什么?', '我')] 我是一款 [('你好', '你好👋!我是人工智能助手 ChatGLM2-6B,很高兴见到你,欢迎问我任何问题。'), ('你可以做什么?', '我是一款')] 我是一款大型 [('你好', '你好👋!我是人工智能助手 ChatGLM2-6B,很高兴见到你,欢迎问我任何问题。'), ('你可以做什么?', '我是一款大型')] ... 我是一款大型语言模型,可以进行自然语言处理和生成,以及提供各种服务和咨询。我的目标是帮助人们更方便、高效地获取信息、解决问题和交流沟通 [('你好', '你好👋!我是人工智能助手 ChatGLM2-6B,很高兴见到你,欢迎问我任何问题。'), ('你可以做什么?', '我是一款大型语言模型,可 以进行自然语言处理和生成,以及提供各种服务和咨询。我的目标是帮助人们更方便、高效地获取信息、解决问题和交流沟通')] 我是一款大型语言模型,可以进行自然语言处理和生成,以及提供各种服务和咨询。我的目标是帮助人们更方便、高效地获取信息、解决问题和交流沟通。 [('你好', '你好👋!我是人工智能助手 ChatGLM2-6B,很高兴见到你,欢迎问我任何问题。'), ('你可以做什么?', '我是一款大型语言模型,可 以进行自然语言处理和生成,以及提供各种服务和咨询。我的目标是帮助人们更方便、高效地获取信息、解决问题和交流沟通。')] 我是一款大型语言模型,可以进行自然语言处理和生成,以及提供各种服务和咨询。我的目标是帮助人们更方便、高效地获取信息、解决问题和交流沟通。 [('你好', '你好👋!我是人工智能助手 ChatGLM2-6B,很高兴见到你,欢迎问我任何问题。'), ('你可以做什么?', '我是一款大型语言模型,可 以进行自然语言处理和生成,以及提供各种服务和咨询。我的目标是帮助人们更方便、高效地获取信息、解决问题和交流沟通。')]
源码:
@torch.inference_mode() def stream_chat(self, tokenizer, query: str, history: List[Tuple[str, str]] = None, past_key_values=None, max_length: int = 8192, do_sample=True, top_p=0.8, temperature=0.8, logits_processor=None, return_past_key_values=False, **kwargs): # 为历史和 logit 处理器设置默认值 if history is None: history = [] if logits_processor is None: logits_processor = LogitsProcessorList() logits_processor.append(InvalidScoreLogitsProcessor()) gen_kwargs = {"max_length": max_length, "do_sample": do_sample, "top_p": top_p, "temperature": temperature, "logits_processor": logits_processor, **kwargs} if past_key_values is None and not return_past_key_values: # 如果 PKV 为空,就需要使用完整的历史对话记录构建模型输入 inputs = self.build_inputs(tokenizer, query, history=history) else: # 如果 PKV 不为空,它是历史对话记录的 KV 缓存, # 只需要使用当前问题构建模型输入 inputs = self.build_stream_inputs(tokenizer, query, history=history) if past_key_values is not None: # 得到之前输入的长度 past_length = past_key_values[0][0].shape[0] # 如果有PSL, 从中减去 if self.transformer.pre_seq_len is not None: past_length -= self.transformer.pre_seq_len # 位置 ID 都后移指定长度 inputs.position_ids += past_length # attention_mask 前面添加 PL 个 1 attention_mask = inputs.attention_mask attention_mask = torch.cat((attention_mask.new_ones(1, past_length), attention_mask), dim=1) inputs['attention_mask'] = attention_mask for outputs in self.stream_generate(**inputs, past_key_values=past_key_values, 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]):] ''' q: '你好' iter1 response: '你' iter2 response: '你好' ... iterN response: '你好👋!我是人工智能助手 ChatGLM2-6B,很高兴见到你,欢迎问我任何问题。' ''' response = tokenizer.decode(outputs) # 如果回答最后一个字不是终止符 if response and response[-1] != "�": # 处理时间 response = self.process_response(response) # 将问题和当前回答加入历史 new_history = history + [(query, response)] if return_past_key_values: yield response, new_history, past_key_values else: yield response, new_history def build_stream_inputs(self, tokenizer, query: str, history: List[Tuple[str, str]] = None): # PKV 不为空的时候调用这个函数,使用当前问题构建输入 if history: # 历史不为空,只使用最后一轮的提问构建输入 # 为了和之前的问答历史衔接,需要添加换行符 # query = '你好', prompt = "\n\n[Round x]\n\n问:你好\n\n答:" prompt = "\n\n[Round {}]\n\n问:{}\n\n答:".format(len(history) + 1, query) ''' 将 prompt 转成单词 ID,去掉开头的 ID64790、ID64792 In [147]: tokenizer.encode('\n\n你好', add_special_tokens=False) Out[147]: [30910, 13, 13, 39701] In [149]: tokenizer.encode('\n\n你好') Out[149]: [64790, 64792, 30910, 13, 13, 39701] ''' input_ids = tokenizer.encode(prompt, add_special_tokens=False) # 去掉开头的 ID30910 input_ids = input_ids[1:] ''' 为 input_ids 生成相应的 attention_mask 和 position_ids In [151]: tokenizer.batch_encode_plus( [([13,13,39701], None)], return_tensors="pt", add_special_tokens=False ) Out[151]: { 'input_ids': tensor([[ 13, 13, 39701]]), 'attention_mask': tensor([[1, 1, 1]]), 'position_ids': tensor([[0, 1, 2]]) } ''' inputs = tokenizer.batch_encode_plus([(input_ids, None)], return_tensors="pt", add_special_tokens=False) else: # 历史为空,仅仅使用第一轮的提问构建输入 prompt = "[Round {}]\n\n问:{}\n\n答:".format(len(history) + 1, query) inputs = tokenizer([prompt], return_tensors="pt") inputs = inputs.to(self.device) return inputs