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

本文涉及的产品
NLP自然语言处理_基础版,每接口每天50万次
NLP自然语言处理_高级版,每接口累计50万次
NLP 自学习平台,3个模型定制额度 1个月
简介: 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
相关文章
|
自然语言处理 安全 机器人
什么是Chat GPT3
随着 Chat GPT 技术的进一步发展,有几个关键方面值得关注。 首先是模型的扩展和改进。尽管 Chat GPT 在生成对话方面取得了很大的进展,但仍然存在一些局限性。模型在处理复杂问题和多轮对话时可能存在困难,很容易陷入回答模棱两可或不相关的内容。因此,改进模型在上下文理解和对话逻辑方面的能力是很重要的。 其次是对话的多模态处理。目前的 Chat GPT 模型主要基于文本输入和生成。然而,与人类对话经常伴随着语音、图像和其他非文本的元素不同,模型在多模态对话中的表现仍然较弱。因此,将多模态信息整合到 Chat GPT 中,使其能够更好地处理多媒体对话,将是一个有挑战性但有前景的方向。
203 0
|
3天前
|
人工智能 C++ iOS开发
ollama + qwen2.5-coder + VS Code + Continue 实现本地AI 辅助写代码
本文介绍在Apple M4 MacOS环境下搭建Ollama和qwen2.5-coder模型的过程。首先通过官网或Brew安装Ollama,然后下载qwen2.5-coder模型,可通过终端命令`ollama run qwen2.5-coder`启动模型进行测试。最后,在VS Code中安装Continue插件,并配置qwen2.5-coder模型用于代码开发辅助。
198 2
|
5天前
|
人工智能 前端开发 API
一种基于通义千问prompt辅助+Qwen2.5-coder-32b+Bolt.new+v0+Cursor的无代码对话网站构建方法
本文介绍了当前大模型应用的趋势,从单纯追求参数量转向注重实际应用效果与效率,重点探讨了结合大模型的开发工具,如Bolt.new、v0、Cursor等,如何形成完整的AI工具链,助力开发者高效构建、优化和部署应用。通过实例演示了从项目创建、前端优化到后端代码改写的全过程,强调了提示词设计的重要性,并推荐了适用于不同场景的工具组合方案。
|
2天前
Cursor + qwen2.5-coder 32b 的配置方式
安装Cursor后,进入设置修改OpenAI基础URL为阿里云的DashScope接口,并添加Qwen2.5-Coder 32B模型。需先访问阿里云百灵控制台申请免费Key。配置完成后,即可使用该模型进行开发和测试。
27 1
|
1月前
|
人工智能 自然语言处理 PyTorch
Text2Video Huggingface Pipeline 文生视频接口和文生视频论文API
文生视频是AI领域热点,很多文生视频的大模型都是基于 Huggingface的 diffusers的text to video的pipeline来开发。国内外也有非常多的优秀产品如Runway AI、Pika AI 、可灵King AI、通义千问、智谱的文生视频模型等等。为了方便调用,这篇博客也尝试了使用 PyPI的text2video的python库的Wrapper类进行调用,下面会给大家介绍一下Huggingface Text to Video Pipeline的调用方式以及使用通用的text2video的python库调用方式。
|
6月前
|
数据安全/隐私保护
Hbnu_DingLi Chat
Hbnu_DingLi Chat
54 0
Hbnu_DingLi Chat
|
3月前
|
前端开发 Go 开发者
用 Go + WebSocket 快速实现一个 chat 服务
用 Go + WebSocket 快速实现一个 chat 服务
|
3月前
预训练模型STAR问题之Doc2Bot数据的问题如何解决
预训练模型STAR问题之Doc2Bot数据的问题如何解决
|
4月前
|
API Docker 微服务
开发个人Ollama-Chat--1 项目介绍
**开发个人Ollama-Chat** 是一系列教程,聚焦于使用`go-zero`框架构建聊天应用后端,并通过`Docker`部署至公网。涉及`Ollama`API调用、`Docker`本地环境、`cloudflare`免费部署、内网穿透及阿里云域名绑定。项目包括服务拆分、用户&模型管理、UI设计及多步骤部署。
开发个人Ollama-Chat--1 项目介绍
|
6月前
|
人工智能 开发框架 搜索推荐
23.3k Star!推荐一款非常强大的GPT网页客户端:Lobe Chat
23.3k Star!推荐一款非常强大的GPT网页客户端:Lobe Chat
259 2