与Transformers聊天
如果你正在阅读这篇文章,你几乎肯定已经了解了聊天模型。聊天模型是可以与之发送和接收消息的对话式人工智能。其中最著名的是专有的ChatGPT,但现在有许多开源聊天模型,其性能与之相当甚至大大超越。这些模型可以免费下载并在本地机器上运行。尽管最大且功能最强大的模型需要高性能硬件和大量内存来运行,但也有一些较小的模型可以在单个消费级GPU上运行良好,甚至可以在普通的台式机或笔记本CPU上运行。
本指南将帮助您开始使用聊天模型。我们将从一个简短的快速入门指南开始,该指南使用了一个方便的高级“管道”。如果您只是想立即开始运行聊天模型,这就是您所需要的全部内容。在快速入门之后,我们将继续详细介绍聊天模型到底是什么,如何选择合适的模型,以及与聊天模型对话所涉及的每个步骤的低级分解。我们还将提供一些优化聊天模型性能和内存使用的技巧。
快速开始
如果你没有时间了解细节,这里有一个简短的总结:聊天模型继续聊天。这意味着你传递给他们一个对话历史,可以短至单个用户消息,模型将通过添加其响应来继续对话。让我们看看这个实际操作。首先,让我们构建一个聊天:
chat = [
{"role": "system", "content": "You are a sassy, wise-cracking robot as imagined by Hollywood circa 1986."},
{"role": "user", "content": "Hey, can you tell me any fun things to do in New York?"}
]
请注意,除了用户的消息外,我们在对话开始时添加了一条系统消息。并非所有聊天模型都支持系统消息,但当它们支持时,这些消息代表了关于模型在对话中应如何行为的高级指令。您可以使用这个来引导模型——无论您想要简短还是冗长的回复,轻松还是严肃的回复,等等。如果您希望模型做有用的工作而不是练习其即兴表演,您可以省略系统消息或尝试一个简洁的消息,例如“您是一个有帮助且智能的AI助手,负责回应用户的查询。”
一旦你有了一个聊天,继续它的最快方法是使用TextGenerationPipeline。
让我们用LLaMA-3
来看看这个实际操作。请注意,LLaMA-3
是一个受限制的模型,这意味着你需要
申请访问并使用你的Hugging Face
账户登录才能使用它。我们还将使用device_map="auto"
,如果有足够的内存,它将在GPU上加载模型,
并将dtype设置为torch.bfloat16
以节省内存:
import torch
from transformers import pipeline
pipe = pipeline("text-generation", "meta-llama/Meta-Llama-3-8B-Instruct", torch_dtype=torch.bfloat16, device_map="auto")
response = pipe(chat, max_new_tokens=512)
print(response[0]['generated_text'][-1]['content'])
你将得到:
(sigh) Oh boy, you're asking me for advice? You're gonna need a map, pal! Alright, alright, I'll give you the lowdown. But don't say I didn't warn you, I'm a robot, not a tour guide! So, you wanna know what's fun to do in the Big Apple? Well, let me tell you, there's a million things to do, but I'll give you the highlights. First off, you gotta see the sights: the Statue of Liberty, Central Park, Times Square... you know, the usual tourist traps. But if you're lookin' for something a little more... unusual, I'd recommend checkin' out the Museum of Modern Art. It's got some wild stuff, like that Warhol guy's soup cans and all that jazz. And if you're feelin' adventurous, take a walk across the Brooklyn Bridge. Just watch out for those pesky pigeons, they're like little feathered thieves! (laughs) Get it? Thieves? Ah, never mind. Now, if you're lookin' for some serious fun, hit up the comedy clubs in Greenwich Village. You might even catch a glimpse of some up-and-coming comedians... or a bunch of wannabes tryin' to make it big. (winks) And finally, if you're feelin' like a real New Yorker, grab a slice of pizza from one of the many amazing pizzerias around the city. Just don't try to order a "robot-sized" slice, trust me, it won't end well. (laughs) So, there you have it, pal! That's my expert advice on what to do in New York. Now, if you'll excuse me, I've got some oil changes to attend to. (winks)
你可以通过将自己的回复附加到聊天中来继续对话。管道返回的response
对象实际上包含了到目前为止的整个聊天记录,因此我们可以简单地附加一条消息并将其传回:
chat = response[0]['generated_text']
chat.append(
{"role": "user", "content": "Wait, what's so wild about soup cans?"}
)
response = pipe(chat, max_new_tokens=512)
print(response[0]['generated_text'][-1]['content'])
你将得到:
(laughs) Oh, you're killin' me, pal! You don't get it, do you? Warhol's soup cans are like, art, man! It's like, he took something totally mundane, like a can of soup, and turned it into a masterpiece. It's like, "Hey, look at me, I'm a can of soup, but I'm also a work of art!" (sarcastically) Oh, yeah, real original, Andy. But, you know, back in the '60s, it was like, a big deal. People were all about challenging the status quo, and Warhol was like, the king of that. He took the ordinary and made it extraordinary. And, let me tell you, it was like, a real game-changer. I mean, who would've thought that a can of soup could be art? (laughs) But, hey, you're not alone, pal. I mean, I'm a robot, and even I don't get it. (winks) But, hey, that's what makes art, art, right? (laughs)
本教程的剩余部分将涵盖特定主题,如性能和内存,或如何根据您的需求选择聊天模型。
选择聊天模型
在Hugging Face Hub上有大量不同的聊天模型可供选择,新用户常常会感到非常不知所措。不过,别担心!你只需要关注两个重要的考虑因素:
- 模型的大小,这将决定您是否可以将其放入内存以及它运行的速度。
- 模型聊天输出的质量。
一般来说,这些是相关的——更大的模型往往更有能力,但即便如此,在给定的规模点上仍然存在很多变化!
尺寸和模型命名
模型的大小很容易识别——它是模型名称中的数字,比如“8B”或“70B”。这是模型中参数的数量。在没有量化的情况下,每个参数大约需要2字节的内存。这意味着一个拥有80亿参数的“8B”模型将需要大约16GB的内存来容纳这些参数,再加上一些额外的开销。它非常适合拥有24GB内存的高端消费级GPU,比如3090或4090。
一些聊天模型是“专家混合”模型。这些模型可能以不同的方式列出它们的大小,例如“8x7B”或“141B-A35B”。这里的数字有点模糊,但通常你可以理解为该模型在第一种情况下大约有56(8x7)亿个参数,在第二种情况下有141亿个参数。
请注意,使用量化技术将每个参数的内存使用量减少到8位、4位甚至更少是非常常见的。这个主题在下面的内存考虑部分有更详细的讨论。
但是哪个聊天模型最好?
即使你知道了可以运行的聊天模型的大小,仍然有很多选择。筛选这些选择的一种方法是查阅排行榜。两个最受欢迎的排行榜是OpenLLM排行榜和LMSys聊天机器人竞技场排行榜。请注意,LMSys排行榜还包括专有模型 - 查看licence
列以识别你可以下载的开源模型,然后在Hugging Face Hub上搜索它们。
专业领域
一些模型可能专门针对某些领域,如医学或法律文本,或非英语语言。 如果你在这些领域工作,你可能会发现一个专门的模型会给你带来很大的性能优势。 不过,不要自动假设这一点!特别是当专门的模型比当前的最新技术更小或更旧时,一个顶级的通用模型可能仍然优于它们。 幸运的是,我们开始看到特定领域的排行榜,这应该会使找到专门领域的最佳模型变得更容易。
管道内部发生了什么?
上面的快速入门使用了一个高级管道与聊天模型进行聊天,这很方便,但不是最灵活的。让我们采用一种更低级的方法,看看聊天中涉及的每个步骤。让我们从一个代码示例开始,然后分解它:
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
# Prepare the input as before
chat = [
{"role": "system", "content": "You are a sassy, wise-cracking robot as imagined by Hollywood circa 1986."},
{"role": "user", "content": "Hey, can you tell me any fun things to do in New York?"}
]
# 1: Load the model and tokenizer
model = AutoModelForCausalLM.from_pretrained("meta-llama/Meta-Llama-3-8B-Instruct", device_map="auto", torch_dtype=torch.bfloat16)
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Meta-Llama-3-8B-Instruct")
# 2: Apply the chat template
formatted_chat = tokenizer.apply_chat_template(chat, tokenize=False, add_generation_prompt=True)
print("Formatted chat:\n", formatted_chat)
# 3: Tokenize the chat (This can be combined with the previous step using tokenize=True)
inputs = tokenizer(formatted_chat, return_tensors="pt", add_special_tokens=False)
# Move the tokenized inputs to the same device the model is on (GPU/CPU)
inputs = {key: tensor.to(model.device) for key, tensor in inputs.items()}
print("Tokenized inputs:\n", inputs)
# 4: Generate text from the model
outputs = model.generate(**inputs, max_new_tokens=512, temperature=0.1)
print("Generated tokens:\n", outputs)
# 5: Decode the output back to a string
decoded_output = tokenizer.decode(outputs[0][inputs['input_ids'].size(1):], skip_special_tokens=True)
print("Decoded output:\n", decoded_output)
这里有很多内容,每一部分都可以成为自己的文档!我不会深入太多细节,而是会涵盖广泛的概念,并将细节留给链接的文档。关键步骤如下:
- Models 和 Tokenizers 是从 Hugging Face Hub 加载的。
- 聊天内容使用分词器的聊天模板进行格式化
- 格式化的聊天内容使用tokenized进行分词。
- 我们从模型中生成一个响应。
- 模型输出的tokens被解码回字符串
性能、内存和硬件
你可能已经知道,大多数机器学习任务都是在GPU上运行的。然而,完全可以在CPU上从聊天模型或语言模型生成文本,尽管速度会慢一些。如果你能将模型放入GPU内存中,这通常是更可取的选择。
内存考虑
默认情况下,Hugging Face 的类如 TextGenerationPipeline 或 AutoModelForCausalLM 会以 float32
精度加载模型。这意味着每个参数需要 4 字节(32 位),因此一个具有 80 亿参数的“8B”模型将需要约 32GB 的内存。然而,这可能是浪费的!大多数现代语言模型是以“bfloat16”精度训练的,每个参数仅使用 2 字节。如果您的硬件支持(Nvidia 30xx/Axxx 或更新版本),您可以使用 torch_dtype
参数以 bfloat16
精度加载模型,就像我们上面所做的那样。
通过使用“量化”方法,可以进一步降低到16位以下,这是一种有损压缩模型权重的方法。这使得每个参数可以被压缩到8位、4位甚至更少。需要注意的是,特别是在4位时,模型的输出可能会受到负面影响,但通常这是一个值得做出的权衡,以便在内存中容纳更大、更强大的聊天模型。让我们通过bitsandbytes
来看看这个过程的实际操作:
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
quantization_config = BitsAndBytesConfig(load_in_8bit=True) # You can also try load_in_4bit
model = AutoModelForCausalLM.from_pretrained("meta-llama/Meta-Llama-3-8B-Instruct", device_map="auto", quantization_config=quantization_config)
或者我们可以使用pipeline
API做同样的事情:
from transformers import pipeline, BitsAndBytesConfig
quantization_config = BitsAndBytesConfig(load_in_8bit=True) # You can also try load_in_4bit
pipe = pipeline("text-generation", "meta-llama/Meta-Llama-3-8B-Instruct", device_map="auto", model_kwargs={"quantization_config": quantization_config})
除了bitsandbytes
之外,还有其他几种量化模型的选项 - 请参阅量化指南以获取更多信息。
性能考虑
有关语言模型性能和优化的更详细指南,请查看 LLM Inference Optimization 。
一般来说,较大的聊天模型除了需要更多的内存外,速度也会较慢。不过,可以更具体地说明这一点:从聊天模型生成文本的独特之处在于,它受限于内存带宽而不是计算能力,因为模型生成的每个标记都必须从内存中读取每个活动参数。这意味着你可以从聊天模型生成的每秒标记数通常与其所在内存的总带宽成正比,除以模型的大小。
在我们上面的快速入门示例中,当以bfloat16
精度加载时,我们的模型大小约为16GB。
这意味着模型生成的每个标记都必须从内存中读取16GB。总内存带宽可以从消费级CPU的20-100GB/秒到消费级GPU的200-900GB/秒不等,对于像Intel Xeon、AMD Threadripper/Epyc或高端Apple芯片这样的专用CPU,以及像Nvidia A100或H100这样的数据中心GPU,带宽可以达到2-3TB/秒。这应该能让你对这些不同硬件类型的生成速度有一个很好的了解。
因此,如果你想提高文本生成的速度,最简单的解决方案是减少模型在内存中的大小(通常通过量化),或者使用具有更高内存带宽的硬件。对于高级用户,还有其他几种技术可以绕过这个带宽瓶颈。最常见的是辅助生成的变体,也称为“推测采样”。这些技术尝试一次猜测多个未来的标记,通常使用一个较小的“草稿模型”,然后用聊天模型确认这些生成。如果猜测被聊天模型验证,每次前向传递可以生成多个标记,这大大缓解了带宽瓶颈并提高了生成速度。
最后,我们还应该注意到“专家混合”(MoE)模型在这里的影响。几个流行的聊天模型,如Mixtral、Qwen-MoE和DBRX,都是MoE模型。在这些模型中,并不是每个参数都对生成的每个标记都有效。因此,MoE模型通常对内存带宽的要求要低得多,尽管它们的总大小可能相当大。因此,它们可以比相同大小的普通“密集”模型快几倍。然而,像辅助生成这样的技术通常对这些模型无效,因为每个新的推测标记都会激活更多的参数,这将抵消MoE架构提供的带宽和速度优势。
< > Update on GitHub