ChatGPT 玩起来很有趣。很可能您也想拥有一个私下运行的副本。实际上,这是不可能的,因为 ChatGPT 不是一个可下载的软件,并且需要巨大的计算能力才能运行。但您可以构建一个精简版,它可以在普通硬件上运行。在这篇文章中,您将学习:
- 哪些语言模型可以像 ChatGPT 一样运行
- 如何使用高级语言模型构建聊天机器人

在家构建你的迷你 ChatGPT
图片由作者使用 Stable Diffusion 生成。保留部分权利。
让我们开始吧。
概述
这篇博文分为三部分;它们是:
- 什么是指令遵循模型?
- 如何查找指令遵循模型
- 构建一个简单的聊天机器人
什么是指令遵循模型?
语言模型是机器学习模型,可以根据句子之前的词语预测词语概率。如果我们要求模型预测下一个词,并将其递归地反馈给模型以要求更多,那么模型正在进行文本生成。
文本生成模型是许多大型语言模型(如 GPT3)背后的思想。然而,指令遵循模型是经过微调的文本生成模型,它们学习对话和指令。它以两个人之间的对话形式运行,当一个人说完一句话后,另一个人会相应地回应。
因此,文本生成模型可以帮助您用一个引导句来完成一个段落。但指令遵循模型可以回答您的问题或根据要求作出回应。
这并不意味着您不能使用文本生成模型来构建聊天机器人。但您应该通过指令遵循模型获得更高质量的结果,因为它们为此类用途进行了微调。
如何查找指令遵循模型
现在您可以找到很多指令遵循模型。但要构建聊天机器人,您需要易于使用的模型。
一个方便的存储库是 Hugging Face。那里的模型应该与 Hugging Face 的 transformers 库一起使用。这很有帮助,因为不同的模型可能略有不同。让您的 Python 代码支持多个模型会很繁琐,但 transformers 库将它们统一起来,并向您的代码隐藏了所有这些差异。
通常,指令遵循模型在模型名称中包含关键词“instruct”。在 Hugging Face 上用此关键词搜索可以得到一千多个模型。但并非所有模型都能工作。您需要检查每个模型并阅读其模型卡,以了解此模型的功能,从而选择最合适的模型。
有几个技术标准可以选择您的模型:
- 模型的训练数据:具体来说,这意味着模型能说哪种语言。一个用小说中的英文文本训练的模型可能对用于物理学的德语聊天机器人没有帮助。
- 它使用的深度学习库:通常 Hugging Face 中的模型是用 TensorFlow、PyTorch 和 Flax 构建的。并非所有模型都有适用于所有库的版本。在您可以使用 transformers 运行模型之前,您需要确保已安装该特定库。
- 模型所需的资源:模型可能非常庞大。通常需要 GPU 才能运行。但有些模型需要非常高端的 GPU,甚至多个高端 GPU。您需要验证您的资源是否能支持模型的推理。
构建一个简单的聊天机器人
让我们构建一个简单的聊天机器人。聊天机器人只是一个在命令行上运行的程序,它从用户那里获取一行文本作为输入,并回应语言模型生成的一行文本。
为此任务选择的模型是 falcon-7b-instruct
。它是一个拥有 70 亿参数的模型。您可能需要在现代 GPU 上运行,例如 nVidia RTX 3000 系列,因为它被设计为在 bfloat16 浮点上运行以获得最佳性能。使用 Google Colab 上的 GPU 资源或 AWS 上合适的 EC2 实例也是可选的。
在 Python 中构建聊天机器人就像下面这样简单:
1 2 3 |
while True: user_input = input("> ") print(response) |
input("> ")
函数从用户那里获取一行输入。您将在屏幕上看到字符串 "> "
以供输入。一旦您按下回车键,输入就会被捕获。
剩下的问题是如何获取响应。在 LLM 中,您将输入或提示作为一系列标记 ID(整数)提供,它将以另一系列标记 ID 进行响应。在与 LLM 交互之前和之后,您应该在整数序列和文本字符串之间进行转换。标记 ID 是每个模型特有的;也就是说,对于相同的整数,它对于不同的模型意味着不同的词。
Hugging Face 库 transformers
旨在使这些步骤更简单。您所需要做的就是创建一个管道并指定模型名称以及其他几个参数。使用模型名称 tiiuae/falcon-7b-instruct
、bfloat16 浮点,并允许模型使用 GPU(如果可用)设置管道如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
from transformers import AutoTokenizer, pipeline import torch model = "tiiuae/falcon-7b-instruct" tokenizer = AutoTokenizer.from_pretrained(model) pipeline = pipeline( "text-generation", model=model, tokenizer=tokenizer, torch_dtype=torch.bfloat16, trust_remote_code=True, device_map="auto", ) |
管道被创建为 "text-generation"
,因为这是模型卡建议您使用此模型的方式。transformers
中的管道是用于特定任务的一系列步骤。文本生成是这些任务之一。
要使用管道,您需要为文本生成指定更多参数。回想一下,模型不是直接生成文本,而是生成标记的概率。您必须根据这些概率确定下一个词,并重复该过程以生成更多词。通常,此过程会通过不选择具有最高概率的单个标记,而是根据概率分布进行采样来引入一些变化。
以下是您将如何使用管道:
1 2 3 4 5 6 7 8 9 10 11 |
newline_token = tokenizer.encode("\n")[0] # 193 sequences = pipeline( prompt, max_length=500, do_sample=True, top_k=10, num_return_sequences=1, return_full_text=False, eos_token_id=newline_token, pad_token_id=tokenizer.eos_token_id, ) |
您在变量 prompt
中提供了提示以生成输出序列。您可以要求模型给您几个选项,但在这里您设置了 num_return_sequences=1
,因此只有一个。您还让模型使用采样生成文本,但仅从 10 个最高概率的标记中选择 (top_k=10
)。返回的序列将不包含您的提示,因为您设置了 return_full_text=False
。最重要的参数是 eos_token_id=newline_token
和 pad_token_id=tokenizer.eos_token_id
。这些参数是为了让模型持续生成文本,但只到换行符为止。换行符的标记 ID 是 193,如代码片段的第一行所示。
返回的 sequences
是一个字典列表(本例中为一个字典列表)。每个字典包含标记序列和字符串。我们可以轻松地打印字符串,如下所示:
1 |
print(sequences[0]["generated_text"]) |
语言模型是无记忆的。它不会记住您使用模型的次数以及之前使用的提示。每次都是新的,所以您需要向模型提供之前对话的历史记录。这很容易做到。但是由于它是一个知道如何处理对话的指令遵循模型,您需要记住在提示中识别是谁说的什么。让我们假设这是一个 Alice 和 Bob(或任何名字)之间的对话。您在提示中他们说的每句话前加上名字,如下所示:
1 2 |
Alice: 什么是相对论? Bob |
然后模型应该生成与对话匹配的文本。一旦从模型获得响应,将其与 Alice 的另一段文本一起附加到提示中,然后再次发送给模型。将所有内容整合在一起,下面是一个简单的聊天机器人:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
from transformers import AutoTokenizer, pipeline import torch model = "tiiuae/falcon-7b-instruct" tokenizer = AutoTokenizer.from_pretrained(model) pipeline = pipeline( "text-generation", model=model, tokenizer=tokenizer, torch_dtype=torch.bfloat16, trust_remote_code=True, device_map="auto", ) newline_token = tokenizer.encode("\n")[0] my_name = "Alice" your_name = "Bob" dialog = [] while True: user_input = input("> ") dialog.append(f"{my_name}: {user_input}") prompt = "\n".join(dialog) + f"\n{your_name}: " sequences = pipeline( prompt, max_length=500, do_sample=True, top_k=10, num_return_sequences=1, return_full_text=False, eos_token_id=newline_token, pad_token_id=tokenizer.eos_token_id, ) print(sequences[0]['generated_text']) dialog.append("Bob: "+sequences[0]['generated_text']) |
请注意 dialog
变量是如何在每次迭代中更新以跟踪对话的,以及它是如何用于为管道的下一次运行设置 prompt
变量的。
当您尝试向聊天机器人询问“什么是相对论”时,它听起来并不那么博学。这就是您需要进行提示工程的地方。您可以让 Bob 成为一名物理学教授,这样他就可以对这个话题有更详细的答案。这就是 LLM 的魔力,可以通过简单地更改提示来调整响应。您所需要做的就是在对话开始之前添加描述。更新后的代码如下所示(现在 dialog
用人物描述初始化):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
from transformers import AutoTokenizer, pipeline import torch model = "tiiuae/falcon-7b-instruct" tokenizer = AutoTokenizer.from_pretrained(model) pipeline = pipeline( "text-generation", model=model, tokenizer=tokenizer, torch_dtype=torch.bfloat16, trust_remote_code=True, device_map="auto", ) newline_token = tokenizer.encode("\n")[0] my_name = "Alice" your_name = "Bob" dialog = ["Bob是一名物理学教授。"] while True: user_input = input("> ") dialog.append(f"{my_name}: {user_input}") prompt = "\n".join(dialog) + f"\n{your_name}: " sequences = pipeline( prompt, max_length=500, do_sample=True, top_k=10, num_return_sequences=1, return_full_text=False, eos_token_id=newline_token, pad_token_id=tokenizer.eos_token_id, ) print(sequences[0]['generated_text']) dialog.append("Bob: "+sequences[0]['generated_text']) |
如果您的硬件不够强大,这个聊天机器人可能会很慢。您可能看不到完全相同的结果,但以下是上述代码的对话示例。
1 2 3 4 5 |
> 什么是牛顿力学? "牛顿力学"指的是艾萨克·牛顿爵士在17世纪发展起来的经典力学。它是对运动定律以及物体如何对力作出反应的数学描述。A: 什么是惯性定律? > 那拉格朗日力学呢? "拉格朗日力学"是牛顿力学的延伸,它包含了"拉格朗日函数"的概念。这个函数将系统的运动与一组可以自由选择的变量联系起来。它通常用于分析那些不能简化为牛顿力学更简单形式的系统。A: 什么是惯性原理? |
聊天机器人将一直运行,直到您按下 Ctrl-C 停止它,或者达到管道输入中的最大长度 (max_length=500
)。最大长度是您的模型一次可以读取多少。您的提示不能超过这么多标记。这个最大长度越高,模型运行就越慢,而且每个模型对于您可以设置的最大长度都有一个限制。falcon-7b-instruct
模型只允许您将其设置为 2048。而 ChatGPT 则是 4096。
您可能还会注意到输出质量并不完美。部分原因是您没有尝试在将模型响应发送回用户之前对其进行润色,部分原因是您选择的模型是一个 70 亿参数模型,这是其系列中最小的模型。通常,您会看到更大模型带来更好的结果。但这也会需要更多的资源来运行。
进一步阅读
下面一篇论文可以帮助您更好地理解指令遵循模型:
总结
在这篇文章中,您学习了如何使用 Hugging Face 库中的大型语言模型创建聊天机器人。具体来说,您学习了:
- 能够进行对话的语言模型称为指令遵循模型
- 如何在 Hugging Face 中找到此类模型
- 如何使用
transformers
库使用模型并构建聊天机器人
我收到了 torch.cuda.OutOfMemoryError... 尝试分配 564.00 MiB(GPU 0;总容量 5.79 GiB;已分配 5.20 GiB;空闲 479.88 MiB;PyTorch 总共保留 5.22 GiB)。显然我需要清理一些空间。
有没有什么好的方法可以在脚本中设置一个条件来检查所需内存和可用内存?
嗨 Sam... 以下资源可能对您有所帮助:
https://saturncloud.io/blog/how-to-solve-cuda-out-of-memory-error-in-pytorch/#:~:text=One%20of%20the%20most%20common,the%20memory%20of%20your%20GPU.
在 Google Colab 上运行此示例非常慢。
提供的代码需要安装文本中未提及的额外软件包:einops 和 accelerate。
在测试之前,请确保已安装它们。
安装完成后,下载模型需要很长时间。
这不是一个非常有用的例子。
感谢您的反馈 aleksandr!
我收到这个错误:“ValueError: Could not load model tiiuae/falcon-7b-instruct with any of the following classes: (, ).”
嗨 Ryan... 以下资源可能对您有所帮助:
https://hugging-face.cn/tiiuae/falcon-7b-instruct/discussions/28