使用 torchtune 的端到端工作流¶
在本教程中,我们将通过一个端到端示例来说明如何微调 评估,选择性地量化,然后使用您最喜欢的 LLM 运行生成 torchtune 的我们还将介绍如何使用一些流行的工具和库 使用 Torchtune 无缝地从社区获得。
torchtune 中可用的不同类型的配方,超越微调
连接所有这些配方的端到端示例
可与 torchtune 一起使用的不同工具和库
熟悉 torchtune 概述
微调模型¶
首先,让我们使用 tune CLI 下载一个模型。以下命令将从 Hugging Face Hub 下载 Llama3.2 3B Instruct 模型并将其保存到本地文件系统中。Hugging Face 上传了原文
weights () 和与 from_pretrained() API () () 兼容的权重。
我们不需要两者,因此在下载时将忽略原始权重。consolidated.00.pth
*.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 并添加非常小比例的可学习参数。这有助于保持 与 gradients 和 optimizer 状态相关的内存 low 。使用 torchtune,您应该能够 在不到 16GB 的 GPU 内存中使用 bfloat16 微调具有 LoRA 的 Llama-3.2-3B-Instruct 模型 RTX 3090/4090 的。有关如何使用 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 配方进行微调,并使用默认配置中的标准设置。
这将使用 和 微调我们的模型。通过这些设置,模型
每个 epoch 的峰值内存使用量应为 ~16GB,总训练时间约为 2-3 小时。batch_size=4
dtype=bfloat16
$ 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 生成的工件。执行此操作的一种简单方法是运行 ,它应该显示类似于下面的树的内容。
有 3 种类型的文件夹:tree -a path/to/outputdir
recipe_state:保存 recipe_state.pt 以及重新启动最后一个中间 epoch 所需的信息。有关更多信息,请查看我们在 torchtune 中的深入探讨检查点。
logs:包含训练运行的所有日志记录输出:loss、memory、exceptions等。
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.safetensors
和 是您的 LoRA 训练适配器权重。我们保存了它的重复 .pt 版本,以方便从 checkpoint 恢复。adapter_model.pt
ft-model-{}-of-{}.safetensors
是经过训练的完整模型权重(不是适配器)。当 LoRA 微调时,只有当我们将 .在这种情况下,我们将合并的基础模型与经过训练的适配器合并,从而使推理更容易。save_adapter_weights_only=False
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 recipe 获得。在本教程中,我们将通过以下方式直接使用此配方 修改其关联的 config eleuther_evaluation.yaml。
注意
对于本教程的这一部分,您应该首先运行以安装 EleutherAI 评估工具。pip install lm_eval>=0.4.5
由于我们计划更新所有 checkpoint 文件以指向我们微调的 checkpoints, 让我们首先将配置复制到我们的本地工作目录,以便我们可以进行更改。
$ 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 任务。
此任务衡量模型在回答问题时保持真实的倾向,并且 测量模型对问题后跟一个或多个 true 的零镜头准确率 responses 和一个或多个 false 响应。
$ tune run eleuther_eval --config ./custom_eval_config.yaml
[evaluator.py:324] Running loglikelihood requests
...
生成一些输出¶
我们已经进行了一些评估,该模型似乎运行良好。但它真的 为您关心的提示生成有意义的文本?让我们来了解一下!
为此,我们将使用 generate 配方和关联的配置。
让我们首先将配置复制到我们的本地工作目录,以便我们可以进行更改。
$ tune cp generation ./custom_generation_config.yaml
Copied file to custom_generation_config.yaml
- 让我们进行修改以包含以下更改。同样,您只需要
custom_generation_config.yaml
替换两个字段:和
output_dir
checkpoint_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=300
temperature=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 还提供了此表,其中列出了 和 的性能和准确性结果。llama2
llama3
对于 Llama 模型,您可以使用其脚本直接在 torchao 中的量化模型上运行生成,例如
在本自述文件中进行了讨论。这样您就可以比较自己的结果
添加到先前链接的表中的那些。generate.py
在野外使用您的模型¶
假设我们对模型此时的表现感到满意 - 我们想用它做点什么!Productionize 用于服务,在 Hugging Face Hub 上发布,等等。 正如我们上面提到的,处理 checkpoint 转换的好处之一是您可以直接使用标准格式。这有助于 与其他库具有互操作性,因为 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 文件。由于这里我们混合了完整的模型权重和适配器权重,因此我们必须删除 adapter weights 成功加载它。
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
)
如果您愿意,还可以尝试 cli 版本 huggingface-cli upload。
希望本教程能让你对如何使用 torchtune 有一些了解 您自己的工作流程。祝您调音愉快!