使用LLMs生成
LLMs,即大型语言模型,是文本生成背后的关键组件。简而言之,它们由大型预训练的变压器模型组成,这些模型经过训练,可以根据给定的输入文本预测下一个单词(或更准确地说,是标记)。由于它们一次只预测一个标记,因此你需要做一些更复杂的事情来生成新句子,而不仅仅是调用模型——你需要进行自回归生成。
自回归生成是在推理过程中,给定一些初始输入,通过迭代调用模型并使用其自身生成的输出。在🤗 Transformers中,这是由generate()方法处理的,该方法适用于所有具有生成能力的模型。
本教程将向您展示如何:
- 使用LLM生成文本
- 避免常见的陷阱
- 下一步帮助您充分利用您的LLM
在开始之前,请确保您已安装所有必要的库:
pip install transformers bitsandbytes>=0.39.0 -q
生成文本
一个为因果语言建模训练的语言模型将一系列文本标记作为输入,并返回下一个标记的概率分布。
使用LLMs进行自回归生成的一个关键方面是如何从这个概率分布中选择下一个标记。只要最终得到下一个迭代的标记,这一步可以采取任何方法。这意味着它既可以简单到从概率分布中选择最可能的标记,也可以复杂到在从结果分布中采样之前应用多种变换。
上述过程会反复迭代,直到达到某个停止条件。理想情况下,停止条件由模型决定,模型应该学会何时输出序列结束(EOS
)标记。如果不是这种情况,生成过程会在达到预定义的最大长度时停止。
正确设置令牌选择步骤和停止条件对于使模型在任务中按预期行为至关重要。这就是为什么我们有一个与每个模型关联的GenerationConfig文件,它包含了一个良好的默认生成参数化设置,并与您的模型一起加载。
让我们来谈谈代码!
如果您对基本的LLM使用感兴趣,我们的高级Pipeline
接口是一个很好的起点。然而,LLM通常需要量化等高级功能以及对标记选择步骤的精细控制,这最好通过generate()来完成。使用LLM进行自回归生成也是资源密集型的,应在GPU上执行以确保足够的吞吐量。
首先,你需要加载模型。
>>> from transformers import AutoModelForCausalLM
>>> model = AutoModelForCausalLM.from_pretrained(
... "mistralai/Mistral-7B-v0.1", device_map="auto", load_in_4bit=True
... )
您会注意到在from_pretrained
调用中有两个标志:
device_map
确保模型被移动到您的GPU上load_in_4bit
应用 4-bit 动态量化 来大幅减少资源需求
还有其他方法可以初始化模型,但这是一个很好的基线,可以从LLM开始。
接下来,你需要使用tokenizer对你的文本输入进行预处理。
>>> from transformers import AutoTokenizer
>>> tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-v0.1", padding_side="left")
>>> model_inputs = tokenizer(["A list of colors: red, blue"], return_tensors="pt").to("cuda")
model_inputs
变量保存了分词后的文本输入以及注意力掩码。虽然 generate() 在未传递注意力掩码时会尽力推断,但我们建议尽可能传递它以获得最佳结果。
在对输入进行分词后,您可以调用generate()方法来返回生成的标记。生成的标记在打印之前应转换为文本。
>>> generated_ids = model.generate(**model_inputs)
>>> tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
'A list of colors: red, blue, green, yellow, orange, purple, pink,'
最后,你不需要一次只处理一个序列!你可以批量处理你的输入,这将在很小的延迟和内存成本下大大提高吞吐量。你只需要确保正确地对输入进行填充(更多内容见下文)。
>>> tokenizer.pad_token = tokenizer.eos_token # Most LLMs don't have a pad token by default
>>> model_inputs = tokenizer(
... ["A list of colors: red, blue", "Portugal is"], return_tensors="pt", padding=True
... ).to("cuda")
>>> generated_ids = model.generate(**model_inputs)
>>> tokenizer.batch_decode(generated_ids, skip_special_tokens=True)
['A list of colors: red, blue, green, yellow, orange, purple, pink,',
'Portugal is a country in southwestern Europe, on the Iber']
就是这样!只需几行代码,你就可以利用LLM的强大功能。
常见陷阱
有许多生成策略,有时默认值可能不适合您的使用场景。如果您的输出与预期不符,我们创建了一个最常见陷阱的列表以及如何避免它们。
>>> from transformers import AutoModelForCausalLM, AutoTokenizer
>>> tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-v0.1")
>>> tokenizer.pad_token = tokenizer.eos_token # Most LLMs don't have a pad token by default
>>> model = AutoModelForCausalLM.from_pretrained(
... "mistralai/Mistral-7B-v0.1", device_map="auto", load_in_4bit=True
... )
生成的输出太短/太长
如果未在GenerationConfig文件中指定,generate
默认返回最多20个令牌。我们强烈建议在generate
调用中手动设置max_new_tokens
以控制它可以返回的新令牌的最大数量。请记住,LLMs(更准确地说,仅解码器模型)也会将输入提示作为输出的一部分返回。
>>> model_inputs = tokenizer(["A sequence of numbers: 1, 2"], return_tensors="pt").to("cuda")
>>> # By default, the output will contain up to 20 tokens
>>> generated_ids = model.generate(**model_inputs)
>>> tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
'A sequence of numbers: 1, 2, 3, 4, 5'
>>> # Setting `max_new_tokens` allows you to control the maximum length
>>> generated_ids = model.generate(**model_inputs, max_new_tokens=50)
>>> tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
'A sequence of numbers: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,'
错误的生成模式
默认情况下,除非在GenerationConfig文件中指定,generate
在每次迭代中选择最可能的标记(贪婪解码)。根据你的任务,这可能是不希望的;像聊天机器人或写作这样的创造性任务受益于采样。另一方面,像音频转录或翻译这样的输入基础任务则受益于贪婪解码。通过do_sample=True
启用采样,你可以在这个博客文章中了解更多关于这个主题的内容。
>>> # Set seed for reproducibility -- you don't need this unless you want full reproducibility
>>> from transformers import set_seed
>>> set_seed(42)
>>> model_inputs = tokenizer(["I am a cat."], return_tensors="pt").to("cuda")
>>> # LLM + greedy decoding = repetitive, boring output
>>> generated_ids = model.generate(**model_inputs)
>>> tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
'I am a cat. I am a cat. I am a cat. I am a cat'
>>> # With sampling, the output becomes more creative!
>>> generated_ids = model.generate(**model_inputs, do_sample=True)
>>> tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
'I am a cat. Specifically, I am an indoor-only cat. I'
错误的填充边
LLMs 是 仅解码器 架构,这意味着它们会继续迭代你的输入提示。如果你的输入长度不一致,它们需要进行填充。由于 LLMs 没有训练过从填充标记继续生成,你的输入需要左填充。确保你也不要忘记传递注意力掩码来生成!
>>> # The tokenizer initialized above has right-padding active by default: the 1st sequence,
>>> # which is shorter, has padding on the right side. Generation fails to capture the logic.
>>> model_inputs = tokenizer(
... ["1, 2, 3", "A, B, C, D, E"], padding=True, return_tensors="pt"
... ).to("cuda")
>>> generated_ids = model.generate(**model_inputs)
>>> tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
'1, 2, 33333333333'
>>> # With left-padding, it works as expected!
>>> tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-v0.1", padding_side="left")
>>> tokenizer.pad_token = tokenizer.eos_token # Most LLMs don't have a pad token by default
>>> model_inputs = tokenizer(
... ["1, 2, 3", "A, B, C, D, E"], padding=True, return_tensors="pt"
... ).to("cuda")
>>> generated_ids = model.generate(**model_inputs)
>>> tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
'1, 2, 3, 4, 5, 6,'
错误的提示
某些模型和任务需要特定的输入提示格式才能正常工作。如果未应用此格式,您将遇到性能的无声下降:模型似乎可以工作,但不如遵循预期提示时表现得好。有关提示的更多信息,包括哪些模型和任务需要特别注意,请参阅此指南。让我们看一个使用聊天模板的聊天LLM示例:
>>> tokenizer = AutoTokenizer.from_pretrained("HuggingFaceH4/zephyr-7b-alpha")
>>> model = AutoModelForCausalLM.from_pretrained(
... "HuggingFaceH4/zephyr-7b-alpha", device_map="auto", load_in_4bit=True
... )
>>> set_seed(0)
>>> prompt = """How many helicopters can a human eat in one sitting? Reply as a thug."""
>>> model_inputs = tokenizer([prompt], return_tensors="pt").to("cuda")
>>> input_length = model_inputs.input_ids.shape[1]
>>> generated_ids = model.generate(**model_inputs, max_new_tokens=20)
>>> print(tokenizer.batch_decode(generated_ids[:, input_length:], skip_special_tokens=True)[0])
"I'm not a thug, but i can tell you that a human cannot eat"
>>> # Oh no, it did not follow our instruction to reply as a thug! Let's see what happens when we write
>>> # a better prompt and use the right template for this model (through `tokenizer.apply_chat_template`)
>>> set_seed(0)
>>> messages = [
... {
... "role": "system",
... "content": "You are a friendly chatbot who always responds in the style of a thug",
... },
... {"role": "user", "content": "How many helicopters can a human eat in one sitting?"},
... ]
>>> model_inputs = tokenizer.apply_chat_template(messages, add_generation_prompt=True, return_tensors="pt").to("cuda")
>>> input_length = model_inputs.shape[1]
>>> generated_ids = model.generate(model_inputs, do_sample=True, max_new_tokens=20)
>>> print(tokenizer.batch_decode(generated_ids[:, input_length:], skip_special_tokens=True)[0])
'None, you thug. How bout you try to focus on more useful questions?'
>>> # As we can see, it followed a proper thug style 😎
更多资源
虽然自回归生成过程相对简单,但要充分利用你的LLM可能是一项具有挑战性的任务,因为有许多动态部分。为了帮助你更深入地了解和使用LLM,以下是你的下一步:
高级生成用法
- 关于如何控制不同的生成方法,如何设置生成配置文件,以及如何流式输出;
- 加速文本生成;
- Prompt templates for chat LLMs;
- Prompt设计指南;
- 关于GenerationConfig、generate()和生成相关类的API参考。大多数类,包括logits处理器,都有使用示例!
LLM排行榜
- Open LLM Leaderboard,专注于开源模型的质量;
- Open LLM-Perf Leaderboard,专注于LLM吞吐量。
延迟、吞吐量和内存利用率
- 关于如何优化LLMs的速度和内存的指南;
- 关于量化的指南,例如bitsandbytes和autogptq,它展示了如何大幅减少内存需求。
相关库
optimum
,🤗 Transformers 的扩展,针对特定硬件设备进行优化。outlines
,一个可以约束文本生成的库(例如生成JSON文件);SynCode
,一个用于上下文无关语法引导生成的库。(例如:JSON、SQL、Python)text-generation-inference
, 一个用于LLMs的生产就绪服务器;text-generation-webui
, 一个用于文本生成的用户界面;