• Docs >
  • Fine-Tuning Llama3 with Chat Data
Shortcuts

使用聊天数据微调Llama3

Llama3 Instruct 引入了一种新的提示模板,用于使用聊天数据进行微调。在本教程中,我们将介绍您需要了解的内容,以便快速开始准备您自己的自定义聊天数据集,用于微调 Llama3 Instruct。

You will learn:
  • Llama3 指令格式与 Llama2 有何不同

  • 所有关于提示模板和特殊标记的内容

  • 如何使用您自己的聊天数据集来微调Llama3 Instruct

Prerequisites

从Llama2到Llama3的模板更改

Llama2聊天模型在提示预训练模型时需要特定的模板。由于聊天模型是用这个提示模板进行预训练的,如果你想在模型上运行推理,你需要使用相同的模板以获得在聊天数据上的最佳性能。否则,模型只会执行标准的文本补全,这可能与你的预期用例一致,也可能不一致。

官方Llama2提示模板指南中,我们可以看到为Llama2聊天模型添加了特殊标签:

<s>[INST] <<SYS>>
You are a helpful, respectful, and honest assistant.
<</SYS>>

Hi! I am a human. [/INST] Hello there! Nice to meet you! I'm Meta AI, your friendly AI assistant </s>

Llama3 Instruct overhauled 模板从 Llama2 进行了改进,以更好地支持多轮对话。相同文本在 Llama3 Instruct 格式下将如下所示:

<|begin_of_text|><|start_header_id|>system<|end_header_id|>

You are a helpful, respectful, and honest assistant.<|eot_id|><|start_header_id|>user<|end_header_id|>

Hi! I am a human.<|eot_id|><|start_header_id|>assistant<|end_header_id|>

Hello there! Nice to meet you! I'm Meta AI, your friendly AI assistant<|eot_id|>

标签完全不同,它们的编码方式实际上与Llama2不同。让我们通过一个例子来了解如何使用Llama2模板和Llama3模板进行标记化。

注意

Llama3 Base 模型使用与 Llama3 Instruct 不同的提示模板,因为它尚未进行指令调优,且额外的特殊标记未经训练。如果您在没有微调的情况下对 Llama3 Base 模型进行推理,我们建议使用基础模板以获得最佳性能。通常,对于指令和聊天数据,我们建议使用 Llama3 Instruct 及其提示模板。本教程的其余部分假设您正在使用 Llama3 Instruct。

标记化提示模板和特殊标记

假设我有一个包含系统提示的单用户-助手回合的样本:

sample = [
    {
        "role": "system",
        "content": "You are a helpful, respectful, and honest assistant.",
    },
    {
        "role": "user",
        "content": "Who are the most influential hip-hop artists of all time?",
    },
    {
        "role": "assistant",
        "content": "Here is a list of some of the most influential hip-hop "
        "artists of all time: 2Pac, Rakim, N.W.A., Run-D.M.C., and Nas.",
    },
]

现在,让我们使用Llama2ChatTemplate类来格式化这个内容,并看看它是如何被标记化的。Llama2ChatTemplate是一个提示模板的例子,它简单地用一些提示文本构建一个提示,以指示某个任务。

from torchtune.data import Llama2ChatTemplate, Message

messages = [Message.from_dict(msg) for msg in sample]
formatted_messages = Llama2ChatTemplate.format(messages)
print(formatted_messages)
# [
#     Message(
#         role='user',
#         content='[INST] <<SYS>>\nYou are a helpful, respectful, and honest assistant.\n<</SYS>>\n\nWho are the most influential hip-hop artists of all time? [/INST] ',
#         ...,
#     ),
#     Message(
#         role='assistant',
#         content='Here is a list of some of the most influential hip-hop artists of all time: 2Pac, Rakim, N.W.A., Run-D.M.C., and Nas.',
#         ...,
#     ),
# ]

Llama2 还使用了一些特殊的标记,这些标记不在提示模板中。 如果你查看我们的 Llama2ChatTemplate 类,你会注意到 我们没有包含 标记。这些是序列开始 (BOS) 和序列结束 (EOS) 标记,它们在分词器中的表示方式与提示模板的其余部分不同。让我们使用 Llama2 使用的 llama2_tokenizer() 对这个示例进行分词,看看 原因。

from torchtune.models.llama2 import llama2_tokenizer

tokenizer = llama2_tokenizer("/tmp/Llama-2-7b-hf/tokenizer.model")
user_message = formatted_messages[0].text_content
tokens = tokenizer.encode(user_message, add_bos=True, add_eos=True)
print(tokens)
# [1, 518, 25580, 29962, 3532, 14816, 29903, 6778, ..., 2]

我们在编码示例文本时添加了BOS和EOS标记。这显示为ID 1和2。我们可以验证这些是我们的BOS和EOS标记。

print(tokenizer._spm_model.spm_model.piece_to_id("<s>"))
# 1
print(tokenizer._spm_model.spm_model.piece_to_id("</s>"))
# 2

BOS和EOS标记是我们称之为特殊标记的,因为它们有自己的保留标记ID。这意味着它们将在模型学习到的嵌入表中索引到它们自己的向量。其余的提示模板标签,[INST]<>,则被标记化为普通文本,而不是它们自己的ID。

print(tokenizer.decode(518))
# '['
print(tokenizer.decode(25580))
# 'INST'
print(tokenizer.decode(29962))
# ']'
print(tokenizer.decode([3532, 14816, 29903, 6778]))
# '<<SYS>>'

需要注意的是,您不应手动将特殊保留标记放置在输入提示中,因为它将被视为普通文本,而不是特殊标记。

print(tokenizer.encode("<s>", add_bos=False, add_eos=False))
# [529, 29879, 29958]

现在让我们来看看Llama3的格式化方式,看看它与Llama2在分词上有何不同。

from torchtune.models.llama3 import llama3_tokenizer

tokenizer = llama3_tokenizer("/tmp/Meta-Llama-3-8B-Instruct/original/tokenizer.model")
messages = [Message.from_dict(msg) for msg in sample]
tokens, mask = tokenizer.tokenize_messages(messages)
print(tokenizer.decode(tokens))
# '<|start_header_id|>system<|end_header_id|>\n\nYou are a helpful, respectful,
# and honest assistant.<|eot_id|><|start_header_id|>user<|end_header_id|>\n\nWho
# are the most influential hip-hop artists of all time?<|eot_id|><|start_header_id|>
# assistant<|end_header_id|>\n\nHere is a list of some of the most influential hip-hop
# artists of all time: 2Pac, Rakim, N.W.A., Run-D.M.C., and Nas.<|eot_id|>'

注意

我们使用了tokenize_messages API来处理Llama3,这与编码不同。它只是在编码单个消息后,简单地管理在正确的位置添加所有特殊标记。

我们可以看到,分词器在没有我们指定提示模板的情况下处理了所有的格式化。事实证明,所有的附加标签都是特殊标记,我们不需要单独的提示模板。我们可以通过检查这些标签是否被编码为它们自己的标记ID来验证这一点。

print(tokenizer.special_tokens["<|begin_of_text|>"])
# 128000
print(tokenizer.special_tokens["<|eot_id|>"])
# 128009

最棒的部分是——所有这些特殊标记都由标记器纯粹处理。这意味着你不必担心搞乱任何必需的提示模板!

我应该在什么时候使用提示模板?

是否使用提示模板取决于您期望的推理行为。如果您在基础模型上运行推理并且该模型在预训练时使用了提示模板,或者您希望微调模型在特定任务的推理中期望某种提示结构,则应使用提示模板。

严格来说,使用提示模板进行微调并不是必须的,但通常特定任务需要特定的模板。例如,SummarizeTemplate 提供了一个轻量级的结构,用于为要求总结文本的提示微调模型。这将围绕用户消息进行包装,而助手消息保持不变。

f"Summarize this dialogue:\n{dialogue}\n---\nSummary:\n"

即使模型最初是使用Llama2ChatTemplate进行预训练的,您仍然可以使用此模板对Llama2进行微调,只要这是模型在推理过程中看到的内容。模型应该足够强大,能够适应新的模板。

在自定义聊天数据集上进行微调

让我们通过尝试使用自定义聊天数据集微调Llama3-8B指令模型来测试我们的理解。我们将逐步介绍如何设置我们的数据,以便它可以正确地进行标记化并输入到我们的模型中。

假设我们有一个本地数据集保存为JSON文件,其中包含与AI模型的对话。我们如何将其转换为Llama3能够理解并正确标记的格式?

# data/my_data.json
[
    {
        "dialogue": [
            {
                "from": "human",
                "value": "What is your name?"
            },
            {
                "from": "gpt",
                "value": "I am an AI assistant, I don't have a name."
            },
            {
                "from": "human",
                "value": "Pretend you have a name."
            },
            {
                "from": "gpt",
                "value": "My name is Mark Zuckerberg."
            }
        ]
    },
]

首先,我们来看一下通用数据集构建器,看看哪个适合我们的用例。由于我们有对话数据,chat_dataset()似乎是一个不错的选择。对于任何自定义的本地数据集,我们总是需要为torchtune中的任何数据集构建器指定sourcedata_filessplit。对于chat_dataset(),我们还需要指定conversation_columnconversation_style。我们的数据遵循"sharegpt"格式,因此我们可以在这里指定。总的来说,我们的chat_dataset()调用应该如下所示:

from torchtune.datasets import chat_dataset
from torchtune.models.llama3 import llama3_tokenizer

tokenizer = llama3_tokenizer("/tmp/Meta-Llama-3-8B-Instruct/original/tokenizer.model")
ds = chat_dataset(
    tokenizer=tokenizer,
    source="json",
    data_files="data/my_data.json",
    split="train",
    conversation_column="dialogue",
    conversation_style="sharegpt",
)
# In config
tokenizer:
  _component_: torchtune.models.llama3.llama3_tokenizer
  path: /tmp/Meta-Llama-3-8B-Instruct/original/tokenizer.model

dataset:
  _component_: torchtune.datasets.chat_dataset
  source: json
  data_files: data/my_data.json
  split: train
  conversation_column: dialogue
  conversation_style: sharegpt

注意

你可以将任何关键字参数传递给load_dataset到我们所有的数据集类中,它们将会遵守这些参数。这对于常见参数非常有用,例如使用split指定数据分割或使用name进行配置。

如果你需要添加一个提示模板,你只需将其传递给分词器。 由于我们正在微调Llama3,分词器将为我们处理所有格式化, 提示模板是可选的。其他模型如Mistral的MistralTokenizer, 默认使用聊天模板(MistralChatTemplate)来根据他们的推荐格式化所有消息。

现在我们准备好开始微调了!我们将使用内置的LoRA单设备配方。 使用调整 cp命令获取8B_lora_single_device.yaml 配置的副本,并使用您的数据集配置进行更新。

启动微调!

$ tune run lora_finetune_single_device --config custom_8B_lora_single_device.yaml epochs=15