ChatGLM2 源码分析:`ChatGLMForConditionalGeneration.chat, .stream_chat`

简介: ChatGLM2 源码分析:`ChatGLMForConditionalGeneration.chat, .stream_chat`

.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
相关文章
|
7月前
|
自然语言处理 安全 机器人
什么是Chat GPT3
随着 Chat GPT 技术的进一步发展,有几个关键方面值得关注。 首先是模型的扩展和改进。尽管 Chat GPT 在生成对话方面取得了很大的进展,但仍然存在一些局限性。模型在处理复杂问题和多轮对话时可能存在困难,很容易陷入回答模棱两可或不相关的内容。因此,改进模型在上下文理解和对话逻辑方面的能力是很重要的。 其次是对话的多模态处理。目前的 Chat GPT 模型主要基于文本输入和生成。然而,与人类对话经常伴随着语音、图像和其他非文本的元素不同,模型在多模态对话中的表现仍然较弱。因此,将多模态信息整合到 Chat GPT 中,使其能够更好地处理多媒体对话,将是一个有挑战性但有前景的方向。
151 0
|
2天前
|
数据安全/隐私保护
Hbnu_DingLi Chat
Hbnu_DingLi Chat
25 0
Hbnu_DingLi Chat
|
2天前
|
人工智能
LangChain:1. Prompt基本使用
LangChain:1. Prompt基本使用
28 1
|
2天前
|
人工智能 自然语言处理
Kimi Chat是什么模型?一文让你顿悟
Kimi Chat是什么模型?一文让你顿悟
|
2天前
|
人工智能 开发框架 搜索推荐
23.3k Star!推荐一款非常强大的GPT网页客户端:Lobe Chat
23.3k Star!推荐一款非常强大的GPT网页客户端:Lobe Chat
|
6月前
|
缓存
langchain中的chat models介绍和使用
之前我们介绍了LLM模式,这种模式是就是文本输入,然后文本输出。 chat models是基于LLM模式的更加高级的模式。他的输入和输出是格式化的chat messages。 一起来看看如何在langchain中使用caht models吧。
|
7月前
|
机器学习/深度学习 人工智能 搜索推荐
Chat GPT
* 导读 1 ChatGPT是什么?
127 0
|
7月前
|
数据采集 自然语言处理 数据可视化
chat GPT 在数据处理中的应用
ChatGPT在数据处理中有多种应用,包括数据清洗、数据转换、数据合并和数据采样等。下面将详细介绍ChatGPT在数据处理中的应用。 1. 数据清洗: 数据清洗是数据处理中的重要步骤,用于处理缺失值、异常值和重复值等问题。ChatGPT可以帮助你理解和应用各种数据清洗技术。你可以向ChatGPT询问如何处理缺失值,它可以给出建议,例如删除包含缺失值的行或使用插补方法填充缺失值。你还可以询问如何检测和处理异常值,ChatGPT可以提供一些统计方法和可视化技巧,帮助你识别和处理异常值。此外,ChatGPT还可以为你提供处理重复值的方法,例如使用去重函数或删除重复行。 2. 数据转换: 在数据处
93 0
|
7月前
|
自然语言处理 搜索推荐 机器人
什么是Chat GPT5
Chat GPT的未来发展 随着人工智能技术的不断发展,Chat GPT将会越来越成熟和普及。未来,Chat GPT可能会在以下几个方面得到进一步的发展: 首先,Chat GPT可能会更加智能化和个性化。它可以通过分析用户的对话数据和行为模式,为用户提供更加个性化的服务和体验。 其次,Chat GPT可能会更加人性化和情感化。它可以通过模拟人类的情感和语言风格,使得对话更加自然和流畅。 最后,Chat GPT可能会更加广泛地应用于各种领域,如医疗、金融、教育等。它可以为这些领域提供更加智能化和高效的服务和解决方案。 总的来说,Chat GPT的未来发展前景非常广阔,它将会为人们的生活
165 0
|
7月前
|
机器学习/深度学习 自然语言处理 搜索推荐
什么是Chat GPT4
Chat GPT是一种非常有前途的技术,它具有许多优点,但同时也存在一些缺点。 首先,Chat GPT能够生成非常自然的对话,这是它最大的优点之一。它可以在对话中自动完成填空,回答问题,提供建议等,使得对话变得更加流畅和自然。 其次,Chat GPT可以自主学习和调整,从而更好地适应各种对话场景和语言风格。这使得它可以为不同的用户提供个性化的服务和体验。 但是,Chat GPT也存在一些缺点。首先是它的可靠性问题。由于Chat GPT是基于机器学习的技术,它的生成结果可能会出现一些错误或不准确的情况。这对于一些需要高度准确性的场景来说是不可接受的。 其次,Chat GPT还存在一些隐私
272 0