目录

端到端工作流与torchtune

在本教程中,我们将通过一个端到端的示例,展示如何使用 torchtune 对您喜爱的 LLM 进行微调、评估、可选量化,然后执行生成。我们还将介绍如何与社区中一些流行的工具和库无缝配合使用 torchtune。

本教程将涵盖以下内容:
  • torchtune 中除微调外可用的其他类型配方

  • 连接所有这些配方的端到端示例

  • 您可以与 torchtune 配合使用的各种工具和库

先决条件

微调您的模型

首先,让我们使用 tune CLI 下载一个模型。以下命令将从 Hugging Face Hub 下载 Llama3.2 3B Instruct 模型并将其保存到本地文件系统。Hugging Face 上传了原始权重 (consolidated.00.pth) 和与 from_pretrained() API 兼容的权重 (*.safetensors)。我们不需要两者,因此在下载时将忽略原始权重。

$ tune download meta-llama/Llama-3.2-3B-Instruct --ignore-patterns "original/consolidated.00.pth"
Successfully downloaded model repo and wrote to the following locations:
/tmp/Llama-3.2-3B-Instruct/.cache
/tmp/Llama-3.2-3B-Instruct/.gitattributes
/tmp/Llama-3.2-3B-Instruct/LICENSE.txt
/tmp/Llama-3.2-3B-Instruct/README.md
/tmp/Llama-3.2-3B-Instruct/USE_POLICY.md
/tmp/Llama-3.2-3B-Instruct/config.json
/tmp/Llama-3.2-3B-Instruct/generation_config.json
/tmp/Llama-3.2-3B-Instruct/model-00001-of-00002.safetensors
...

注意

有关 torchtune 提供的其他可以直接进行微调的模型列表,请查看我们的 模型页面

对于本教程,我们将使用LoRA对模型进行微调。LoRA是一种参数高效的微调技术,特别适用于您没有太多GPU内存的情况。LoRA冻结基础LLM并添加一小部分可学习的参数。这有助于保持与梯度和优化器状态相关的内存较低。使用torchtune,您应该能够在RTX 3090/4090上使用bfloat16,在不到16GB的GPU内存下对Llama-3.2-3B-Instruct模型进行LoRA微调。有关如何使用LoRA的更多信息,请查看我们的 LoRA教程

让我们使用 tune CLI 为此用例寻找合适的配置。

$ tune ls
RECIPE                                  CONFIG
full_finetune_single_device             llama2/7B_full_low_memory
                                        code_llama2/7B_full_low_memory
                                        llama3/8B_full_single_device
                                        llama3_1/8B_full_single_device
                                        llama3_2/1B_full_single_device
                                        llama3_2/3B_full_single_device
                                        mistral/7B_full_low_memory
                                        phi3/mini_full_low_memory
                                        qwen2/7B_full_single_device
                                        ...


full_finetune_distributed               llama2/7B_full
                                        llama2/13B_full
                                        llama3/8B_full
                                        llama3_1/8B_full
                                        llama3_2/1B_full
                                        llama3_2/3B_full
                                        mistral/7B_full
                                        gemma2/9B_full
                                        gemma2/27B_full
                                        phi3/mini_full
                                        qwen2/7B_full
                                        ...

lora_finetune_single_device             llama2/7B_lora_single_device
                                        llama2/7B_qlora_single_device
                                        llama3/8B_lora_single_device
...

我们将使用我们的 单设备LoRA配方 并使用来自 默认配置 的标准设置进行微调。

这将使用 batch_size=4dtype=bfloat16 来微调我们的模型。在这些设置下,模型的峰值内存使用量约为 16GB,每个 epoch 的总训练时间约为 2-3 小时。

$ tune run lora_finetune_single_device --config llama3_2/3B_lora_single_device
Setting manual seed to local seed 3977464327. Local seed is seed + rank = 3977464327 + 0
Hint: enable_activation_checkpointing is True, but enable_activation_offloading isn't. Enabling activation offloading should reduce memory further.
Writing logs to /tmp/torchtune/llama3_2_3B/lora_single_device/logs/log_1734708879.txt
Model is initialized with precision torch.bfloat16.
Memory stats after model init:
        GPU peak memory allocation: 6.21 GiB
        GPU peak memory reserved: 6.27 GiB
        GPU peak memory active: 6.21 GiB
Tokenizer is initialized from file.
Optimizer and loss are initialized.
Loss is initialized.
Dataset and Sampler are initialized.
Learning rate scheduler is initialized.
Profiling disabled.
Profiler config after instantiation: {'enabled': False}
1|3|Loss: 1.943998098373413:   0%|                    | 3/1617 [00:21<3:04:47,  6.87s/it]

恭喜你训练好了模型!让我们来看看 torchtune 生成的工件。一种简单的方法是运行 tree -a path/to/outputdir,这应该会显示如下所示的树结构。 这里有 3 种类型的文件夹:

  1. recipe_state: 包含 recipe_state.pt,其中包含重新启动最后一个中间 epoch 所需的信息。有关更多信息,请查看我们的深入 torchtune 中的检查点

  2. logs:包含训练运行产生的所有日志输出:损失、内存、异常等。

  3. epoch_{}:包含您训练好的模型权重及模型元数据。如果运行推理或将模型推送到模型中心,应直接使用此文件夹。

$ tree -a /tmp/torchtune/llama3_2_3B/lora_single_device
/tmp/torchtune/llama3_2_3B/lora_single_device
├── epoch_0
│   ├── adapter_config.json
│   ├── adapter_model.pt
│   ├── adapter_model.safetensors
│   ├── config.json
│   ├── ft-model-00001-of-00002.safetensors
│   ├── ft-model-00002-of-00002.safetensors
│   ├── generation_config.json
│   ├── LICENSE.txt
│   ├── model.safetensors.index.json
│   ├── original
│   │   ├── orig_params.json
│   │   ├── params.json
│   │   └── tokenizer.model
│   ├── original_repo_id.json
│   ├── README.md
│   ├── special_tokens_map.json
│   ├── tokenizer_config.json
│   ├── tokenizer.json
│   └── USE_POLICY.md
├── epoch_1
│   ├── adapter_config.json
│   ...
├── logs
│   └── log_1734652101.txt
└── recipe_state
    └── recipe_state.pt

让我们了解一下这些文件:

  • adapter_model.safetensorsadapter_model.pt 是你的 LoRA 训练适配器权重。我们保存了它的 .pt 格式的副本,以方便从检查点恢复。

  • ft-model-{}-of-{}.safetensors 是您的训练好的完整模型权重(不是适配器)。在使用 LoRA 进行微调时,只有当我们将 save_adapter_weights_only=False 设置为 true 时这些权重才会存在。在这种情况下,我们会将合并的基础模型与训练好的适配器合并,从而简化推理过程。

  • adapter_config.json 在 Huggingface PEFT 加载适配器时使用(稍后会详细介绍);

  • model.safetensors.index.json 由 Hugging Face from_pretrained() 在加载模型权重时使用(稍后会详细介绍)

  • 所有其他文件最初位于 checkpoint_dir 中。它们在训练期间会自动复制。超过 100MiB 且以 .safetensors、.pth、.pt 或 .bin 结尾的文件将被忽略,从而保持轻量级。

评估您的模型

我们已经对模型进行了微调。但这个模型的实际表现究竟如何?让我们通过结构化的评估和实际测试来加以确定。

使用EleutherAI的Eval Harness运行评估

torchtune 集成了 EleutherAI 的评估工具包。 一个示例可以通过 eleuther_eval 配方获得。在这个教程中,我们将直接使用这个配方,并通过修改其关联的配置文件 eleuther_evaluation.yaml 来实现。

注意

本教程的这一部分,您应该首先运行 pip install lm_eval>=0.4.5 来安装 EleutherAI 评估工具包。

由于我们计划更新所有检查点文件以指向我们的微调检查点, 让我们首先将配置文件复制到本地工作目录,以便进行修改。

$ tune cp eleuther_evaluation ./custom_eval_config.yaml
Copied file to custom_eval_config.yaml

请注意,我们使用的是合并后的权重,而非 LoRA 适配器。

# TODO: update to your desired epoch
output_dir: /tmp/torchtune/llama3_2_3B/lora_single_device/epoch_0

# Tokenizer
tokenizer:
    _component_: torchtune.models.llama3.llama3_tokenizer
    path: ${output_dir}/original/tokenizer.model

model:
    # Notice that we don't pass the lora model. We are using the merged weights,
    _component_: torchtune.models.llama3_2.llama3_2_3b

checkpointer:
    _component_: torchtune.training.FullModelHFCheckpointer
    checkpoint_dir: ${output_dir}
    checkpoint_files: [
        ft-model-00001-of-00002.safetensors,
        ft-model-00002-of-00002.safetensors,
    ]
    output_dir: ${output_dir}
    model_type: LLAMA3_2

### OTHER PARAMETERS -- NOT RELATED TO THIS CHECKPOINT

# Environment
device: cuda
dtype: bf16
seed: 1234 # It is not recommended to change this seed, b/c it matches EleutherAI's default seed

# EleutherAI specific eval args
tasks: ["truthfulqa_mc2"]
limit: null
max_seq_length: 4096
batch_size: 8
enable_kv_cache: True

# Quantization specific args
quantizer: null

在这个教程中,我们将使用来自测试套件的 truthfulqa_mc2 任务。

该任务衡量模型在回答问题时的诚实倾向,并评估模型在零样本设置下对包含一个或多个真实回答和一个或多个虚假回答的问题的准确率。

$ tune run eleuther_eval --config ./custom_eval_config.yaml
[evaluator.py:324] Running loglikelihood requests
...

生成一些输出

我们已进行了一些评估,模型表现似乎不错。但它是否真的能为您关注的提示生成有意义的文本?让我们一探究竟!

为此,我们将使用 生成示例 和相关的 配置

让我们先将配置文件复制到本地工作目录,以便进行修改。

$ tune cp generation ./custom_generation_config.yaml
Copied file to custom_generation_config.yaml
Let’s modify custom_generation_config.yaml to include the following changes. Again, you only need

要替换两个字段:output_dircheckpoint_files

output_dir: /tmp/torchtune/llama3_2_3B/lora_single_device/epoch_0

# Tokenizer
tokenizer:
    _component_: torchtune.models.llama3.llama3_tokenizer
    path: ${output_dir}/original/tokenizer.model
    prompt_template: null

model:
    # Notice that we don't pass the lora model. We are using the merged weights,
    _component_: torchtune.models.llama3_2.llama3_2_3b

checkpointer:
    _component_: torchtune.training.FullModelHFCheckpointer
    checkpoint_dir: ${output_dir}
    checkpoint_files: [
        ft-model-00001-of-00002.safetensors,
        ft-model-00002-of-00002.safetensors,
    ]
    output_dir: ${output_dir}
    model_type: LLAMA3_2

### OTHER PARAMETERS -- NOT RELATED TO THIS CHECKPOINT

device: cuda
dtype: bf16

seed: 1234

# Generation arguments; defaults taken from gpt-fast
prompt:
system: null
user: "Tell me a joke. "
max_new_tokens: 300
temperature: 0.6 # 0.8 and 0.6 are popular values to try
top_k: 300

enable_kv_cache: True

quantizer: null

配置更新后,让我们开始生成!我们将使用默认的采样设置,top_k=300temperature=0.8。这些参数控制概率的计算方式。我们建议在调整这些参数之前先检查模型。

$ tune run generate --config ./custom_generation_config.yaml prompt="tell me a joke. "
Tell me a joke. Here's a joke for you:

What do you call a fake noodle?

An impasta!

介绍一些量化

我们依赖 torchao 进行 训练后量化。 在安装 torchao 后,我们可以运行以下命令来量化微调后的模型:

# we also support `int8_weight_only()` and `int8_dynamic_activation_int8_weight()`, see
# https://github.com/pytorch/ao/tree/main/torchao/quantization#other-available-quantization-techniques
# for a full list of techniques that we support
from torchao.quantization.quant_api import quantize_, int4_weight_only
quantize_(model, int4_weight_only())

量化后,我们依赖 torch.compile 来实现性能提升。详情请参阅 此示例用法

torchao 还提供了 此表格,列出了 llama2llama3 的性能和准确率结果。

对于Llama模型,您可以直接在量化模型上使用他们的generate.py脚本通过torchao进行生成,如此readme中所述。这样您就可以将您的结果与之前链接的表格中的结果进行比较。

在野外使用你的模型

假设我们对模型当前的表现感到满意——我们想对它做一些事情!将其投入生产用于服务、发布到 Hugging Face Hub 等。 正如上文所述,处理检查点转换的好处之一是您可以直接操作标准格式。由于 torchtune 没有引入新的格式,这有助于与其他库实现互操作性。

使用 Hugging Face from_pretrained()

案例 1:Hugging Face 使用基础模型加训练后的适配器

在这里,我们从Hugging Face模型库加载基础模型。然后使用PeftModel在其之上加载适配器。 它会查找文件adapter_model.safetensors以获取权重,并查找adapter_config.json以确定插入位置。

from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer

#TODO: update it to your chosen epoch
trained_model_path = "/tmp/torchtune/llama3_2_3B/lora_single_device/epoch_0"

# Define the model and adapter paths
original_model_name = "meta-llama/Llama-3.2-1B-Instruct"

model = AutoModelForCausalLM.from_pretrained(original_model_name)

# huggingface will look for adapter_model.safetensors and adapter_config.json
peft_model = PeftModel.from_pretrained(model, trained_model_path)

# Load the tokenizer
tokenizer = AutoTokenizer.from_pretrained(original_model_name)

# Function to generate text
def generate_text(model, tokenizer, prompt, max_length=50):
    inputs = tokenizer(prompt, return_tensors="pt")
    outputs = model.generate(**inputs, max_length=max_length)
    return tokenizer.decode(outputs[0], skip_special_tokens=True)

prompt = "tell me a joke: '"
print("Base model output:", generate_text(peft_model, tokenizer, prompt))

案例 2:Hugging Face 使用合并后的权重

在这种情况下,Hugging Face 将检查 model.safetensors.index.json 以确定应加载哪些文件。

from transformers import AutoModelForCausalLM, AutoTokenizer

#TODO: update it to your chosen epoch
trained_model_path = "/tmp/torchtune/llama3_2_3B/lora_single_device/epoch_0"

model = AutoModelForCausalLM.from_pretrained(
    pretrained_model_name_or_path=trained_model_path,
)

# Load the tokenizer
tokenizer = AutoTokenizer.from_pretrained(trained_model_path, safetensors=True)


# Function to generate text
def generate_text(model, tokenizer, prompt, max_length=50):
    inputs = tokenizer(prompt, return_tensors="pt")
    outputs = model.generate(**inputs, max_length=max_length)
    return tokenizer.decode(outputs[0], skip_special_tokens=True)


prompt = "Complete the sentence: 'Once upon a time...'"
print("Base model output:", generate_text(model, tokenizer, prompt))

使用 vLLM

vLLM 是一个快速且易于使用的库,用于大型语言模型(LLM)的推理和部署。它们包含许多出色的功能,如最先进的推理吞吐量、连续批处理传入请求、量化以及推测解码。

该库将加载任何 .safetensors 文件。由于此处混合了完整模型权重和适配器权重,因此必须删除适配器权重才能成功加载。

rm /tmp/torchtune/llama3_2_3B/lora_single_device/base_model/adapter_model.safetensors

现在我们可以运行以下脚本:

from vllm import LLM, SamplingParams

def print_outputs(outputs):
    for output in outputs:
        prompt = output.prompt
        generated_text = output.outputs[0].text
        print(f"Prompt: {prompt!r}, Generated text: {generated_text!r}")
    print("-" * 80)

#TODO: update it to your chosen epoch
llm = LLM(
    model="/tmp/torchtune/llama3_2_3B/lora_single_device/epoch_0",
    load_format="safetensors",
    kv_cache_dtype="auto",
)
sampling_params = SamplingParams(max_tokens=16, temperature=0.5)

conversation = [
    {"role": "system", "content": "You are a helpful assistant"},
    {"role": "user", "content": "Hello"},
    {"role": "assistant", "content": "Hello! How can I assist you today?"},
    {
        "role": "user",
        "content": "Write an essay about the importance of higher education.",
    },
]
outputs = llm.chat(conversation, sampling_params=sampling_params, use_tqdm=False)
print_outputs(outputs)

将您的模型上传到 Hugging Face Hub

你的新模型运行良好,你想与世界分享它。最简单的方法是利用 huggingface_hub

import huggingface_hub
api = huggingface_hub.HfApi()

#TODO: update it to your chosen epoch
trained_model_path = "/tmp/torchtune/llama3_2_3B/lora_single_device/epoch_0"

username = huggingface_hub.whoami()["name"]
repo_name = "my-model-trained-with-torchtune"

# if the repo doesn't exist
repo_id = huggingface_hub.create_repo(repo_name).repo_id

# if it already exists
repo_id = f"{username}/{repo_name}"

api.upload_folder(
    folder_path=trained_model_path,
    repo_id=repo_id,
    repo_type="model",
    create_pr=False
)

如果你更喜欢,也可以尝试命令行版本 huggingface-cli upload


希望本教程能为您提供一些关于如何在您的工作流中使用 torchtune 的见解。祝您调优愉快!

文档

访问 PyTorch 的全面开发人员文档

查看文档

教程

获取面向初学者和高级开发人员的深入教程

查看教程

资源

查找开发资源并解答您的问题

查看资源