How to Fine-Tune Llama 3 for Custom Tool Calling with Unsloth in Python
TL;DR · AI 摘要
How to Fine-Tune Llama 3 for Custom Tool Calling with Unsloth in Python - MachineLearningMastery.com How to Fine-Tune Ll...
核心要点
- 主题聚焦:How to Fine-Tune Llama 3 for Custom Tool Calling
- 来源:Machine Learning Mastery,建议结合原文判断细节。
- AI 分析暂不可用,本条为保底评分与摘要。
如何使用 Unsloth 在 Python 中对 Llama 3 进行微调以实现自定义工具调用 - MachineLearningMastery.com
如何使用 Unsloth 在 Python 中对 Llama 3 进行微调以实现自定义工具调用
作者:
Vinod Chugani
于
2026年10月8日
分类:
语言模型
0
分享
发布
在本文中,你将学习如何使用 Unsloth 和 QLoRA 对 Llama 3 8B 进行微调,以实现自定义工具调用,从而使模型能够可靠地输出符合特定 API 模式结构的 JSON 数据负载。
我们将涵盖的主题包括:
- 为什么仅依赖提示工程不足以实现可靠的工具调用,以及 QLoRA 如何在权重层面解决该问题。
- 如何使用 Llama 3 的聊天模板构建和格式化工具调用数据集,以及高质量训练样本的标准。
- 如何在不到 10 分钟的时间内,使用免费的 Google Colab T4 GPU 训练、测试并保存一个微调后的工具调用适配器。
如何使用 Unsloth 在 Python 中对 Llama 3 进行微调以实现自定义工具调用(图片由作者提供)
介绍
Llama 3 是一个功能强大的通用模型,但当你需要一个能可靠输出特定 API 结构化 JSON 的代理时,这恰恰是问题所在。提示工程可以引导模型采用某种格式,但无法保证其一致性,而一个格式错误的响应可能会破坏整个代理流程。
微调在权重层面改变模型的行为。如《机器学习实践者的语言模型微调指南》所述,当需要深度专业化时(如自定义语气、严格输出格式或全新能力),微调所付出的代价是值得的。工具调用正好满足这三个需求。
在本教程中,你将使用 Unsloth 和 QLoRA 对 Llama 3 8B 进行微调,使其具备解读用户查询并返回完美匹配你 API 模式的 JSON 数据负载的能力。整个工作流程可在免费的 Google Colab T4 GPU 上运行。
为什么工具调用需要微调
在编写任何代码之前,理解为什么仅靠提示工程不足以实现工具调用是很有必要的。当你要求基础模型返回 {"name": "get_weather", "arguments": {"location": "Tokyo"}} 时,它在测试中通常会配合,但在面对复杂查询或长时间对话时,它会产生偏差。它会添加冗余文本、更换引号样式或遗漏必填字段。
微调解决了这个问题,因为它是在教模型一种新的行为模式,而不仅仅是传授新知识。这种区别很重要:当需要外部事实时,检索增强生成(RAG)有效;当需要一致行为时,微调有效。为自定义 API 模式生成严格 JSON 输出是一个行为问题。
QLoRA 使这成为可能。通过冻结基础模型的权重并在注意力层中注入小型可训练矩阵,你只需更新约 1% 的参数,同时保持内存消耗足够低,以便在免费的 Colab T4 上运行。如《如何在自己的数据集上对本地 Mistral 或 Llama 3 模型进行微调》所述,这种方法在计算成本仅为完整微调几分之一的情况下,实现了相当的结果。
设置环境和安装依赖项
在解释了“为什么”之后,让我们开始设置工具。打开一个新的 Google Colab 笔记本,将运行时更改为 T4 GPU(运行时 → 更改运行时类型 → T4 GPU),然后运行安装代码块。
!pip install "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git" !pip install --no-deps xformers trl peft accelerate bitsandbytes
1
2
!
pip
install
"unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git"
--
no
-
deps
xformers
trl
peft
accelerate
bitsandbytes
安装完成后,确认环境是否已准备好:
from unsloth import FastLanguageModel import torch print(f"CUDA available: {torch.cuda.is_available()}") print(f"GPU: {torch.cuda.get_device_name(0)}")
3
4
5
from
unsloth
import
FastLanguageModel
torch
(
f
"CUDA available: {torch.cuda.is_available()}"
)
"GPU: {torch.cuda.get_device_name(0)}"
你应该会看到T4 GPU被确认。Unsloth会自动处理CUDA配置,因此在加载模型前无需进行额外设置。
加载模型并配置LoRA
现在使用Unsloth的预量化模型加载Llama 3 8B。此版本已存储为4位格式,这意味着不需要Hugging Face的受控令牌,下载速度大约是原始权重的四倍。
model, tokenizer = FastLanguageModel.from_pretrained( model_name="unsloth/llama-3-8b-Instruct-bnb-4bit", max_seq_length=1024, dtype=None, load_in_4bit=True, )
6
model
,
tokenizer
=
.
from_pretrained
model_name
"unsloth/llama-3-8b-Instruct-bnb-4bit"
max_seq_length
1024
dtype
None
load_in_4bit
True
在加载基础模型和分词器后,附加LoRA适配器。这就是QLoRA方法的具体实现:不是更新所有模型权重,而是向特定层注入小型可训练矩阵。r参数(秩)控制训练的参数数量,较小的值可以降低在小数据集上过拟合的风险。注意Unsloth要求lora_dropout=0以保持其优化的训练内核活跃;设置非零值会导致回退到较慢的代码路径。
model = FastLanguageModel.get_peft_model( model, r=8, target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"], lora_alpha=16, lora_dropout=0, bias="none", use_gradient_checkpointing="unsloth", random_state=42, ) print("LoRA 适配器已附加 — 训练约1%的参数")
7
8
9
10
11
12
get_peft_model
r
target_modules
[
"q_proj"
"k_proj"
"v_proj"
"o_proj"
"gate_proj"
"up_proj"
"down_proj"
]
lora_alpha
16
lora_dropout
bias
"none"
use_gradient_checkpointing
"unsloth"
random_state
42
"LoRA 适配器已附加 — 训练约1%的参数"
对于工具调用等专注行为任务,秩8表现良好。更高的秩会赋予模型更强的表达能力,但会增加在小数据集上过拟合的风险。将lora_alpha设置为r的两倍是一个可靠的起点。
到目前为止,模型已加载、量化并连接了LoRA适配器,但它仍然不了解你的工具。这就是数据集的作用所在。
构建工具调用数据集
这是工作流程中最重要的步骤。模型和LoRA配置为你提供了工具,但数据集教会模型如何使用这些工具。在这里正确设置数据结构决定了你的微调模型是生成干净的JSON还是产生不完整的幻觉回答。
每个工具调用数据示例需要三个必要组件:定义可用工具及其JSON模式的系统提示、自然语言的用户查询,以及模型必须生成的精确JSON输出。
以下代码构建了一个小型示例数据集,演示了两个工具:get_weather 和 fetch_stock_price。模型会首先加载,因为需要分词器来正确应用 Llama 3 的聊天模板。
tool_calling_data = [
{
"system": """You have access to these tools: get_weather(location: str) -> dict fetch_stock_price(ticker: str) -> dict Respond ONLY with a valid JSON tool call. No other text.""",
"user": "What is the weather like in Tokyo right now?",
"output": '{"name": "get_weather", "arguments": {"location": "Tokyo"}}'
},
{
"system": """You have access to these tools: get_weather(location: str) -> dict fetch_stock_price(ticker: str) -> dict Respond ONLY with a valid JSON tool call. No other text.""",
"user": "Get me the current stock price for Apple.",
"output": '{"name": "fetch_stock_price", "arguments": {"ticker": "AAPL"}}'
},
]13 14 15 17 18
tool_calling_data
{
"system": "You have access to these tools:\nget_weather(location: str) -> dict\nfetch_stock_price(ticker: str) -> dict\nRespond ONLY with a valid JSON tool call. No other text.",
"user": "What is the weather like in Tokyo right now?",
"output": '{"name": "get_weather", "arguments": {"location": "Tokyo"}}'
}"Get me the current stock price for Apple."
'{"name": "fetch_stock_price", "arguments": {"ticker": "AAPL"}}'在生产环境中,您需要数百个涵盖边缘情况的示例:模糊查询、多参数工具以及看似属于某工具但实际属于另一工具的查询。质量远比数量重要;200 个精心构建的示例通常优于 2000 个嘈杂的示例。
接下来,使用 tokenizer.apply_chat_template() 对每个示例进行格式化。此方法能精确处理 Llama 3 的特殊标记布局,并在后续切换到更新的模型变体时保持代码的可移植性。
from datasets import Dataset
def format_example(item):
messages = [
{"role": "system", "content": item["system"]},
{"role": "user", "content": item["user"]},
{"role": "assistant", "content": item["output"]},
]
return {"text": tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=False
)}
formatted = [format_example(ex) for ex in tool_calling_data]
dataset = Dataset.from_list(formatted)
print(f"Dataset ready: {len(dataset)} examples")datasets
Dataset
def
format_example
item
messages
"role"
"content"
"assistant"
return
"text"
apply_chat_template
tokenize
False
add_generation_prompt
formatted
ex
for
from_list
"Dataset ready: {len(dataset)} examples"每个格式化字符串都会训练模型将工具模式和自然语言查询与精确的 JSON 响应相关联。请注意三部分结构(系统上下文、用户意图、助手输出)如何直接映射到您希望模型学习的行为模式:给定这些工具和这个问题,生成确切的 JSON。
训练和测试工具调用器
准备好并格式化数据集后,配置 TRL 的 SFTTrainer。以下设置适用于带有小型数据集的 Colab T4。max_steps 用于演示目的,将训练时间控制在 10 分钟以内;使用更大的数据集时,请改用基于 epoch 的训练。
from trl import SFTTrainer from transformers import TrainingArguments trainer = SFTTrainer( model=model, tokenizer=tokenizer, train_dataset=dataset, dataset_text_field="text", max_seq_length=1024, args=TrainingArguments( per_device_train_batch_size=2, gradient_accumulation_steps=4, learning_rate=2e-4, max_steps=60, warmup_steps=5, lr_scheduler_type="linear", optim="adamw_8bit", weight_decay=0.01, output_dir="tool_caller_output", report_to="none", ), ) trainer_stats = trainer.train() print(f"Training complete in {trainer_stats.metrics['train_runtime']:.0f}s")
19
20
21
22
23
24
SFTTrainer
transformers
TrainingArguments
trainer
train_dataset
dataset_text_field
args
per_device_train_batch_size
gradient_accumulation_steps
learning_rate
2e
max_steps
60
warmup_steps
lr_scheduler_type
"linear"
optim
"adamw_8bit"
weight_decay
0.01
output_dir
"tool_caller_output"
report_to
trainer_stats
train
"Training complete in {trainer_stats.metrics['train_runtime']:.0f}s"
观察训练过程中的损失值变化。健康的趋势是逐渐下降至0.1-0.3区间。如果在小数据集上损失值迅速接近零,模型可能在记忆而非泛化;部署前应增加更多样化的示例。
训练完成后,将模型切换为推理模式并用训练中未见过的查询进行测试。使用apply_chat_template时设置add_generation_prompt=True会自动添加助手头,仅解码新生成的token可获得干净的响应,无需重新解析。
FastLanguageModel.for_inference(model) messages = [ {"role": "system", "content": ( "You have access to these tools:\n" "get_weather(location: str) -> dict\n" "fetch_stock_price(ticker: str) -> dict\n" "Respond ONLY with a valid JSON tool call. No other text." )}, {"role": "user", "content": "What's the stock price of Tesla?"}, ] inputs = tokenizer.apply_chat_template( messages, tokenize=True, add_generation_prompt=True, return_tensors="pt" ).to("cuda") output = model.generate(inputs, max_new_tokens=64, temperature=0.1, do_sample=True) response = tokenizer.decode(output[0][inputs.shape[1]:], skip_special_tokens=True) print(response)
for_inference
"You have access to these tools:\n"
"get_weather(location: str) -> dict\n"
"fetch_stock_price(ticker: str) -> dict\n"
"Respond ONLY with a valid JSON tool call. No other text."
"What's the stock price of Tesla?"
inputs
return_tensors
"pt"
to
"cuda"
output
generate
max_new_tokens
64
temperature
0.1
do_sample
response
decode
shape
skip_special_tokens
成功的微调会生成类似{"name": "fetch_stock_price", "arguments": {"ticker": "TSLA"}}的输出:干净的JSON格式,没有多余文字。与基模型相比,基模型即使使用相同系统提示,也常会用"Sure! Here’s the tool call:"等对话填充包裹JSON。这种差异正是导致下游解析失败的典型漂移现象。如果微调后模型仍添加对话文本,说明训练数据格式不一致,或需要增加更多强化系统提示中"no other text"约束的示例。
最后保存LoRA适配器以备后续使用:
model.save_pretrained("llama3_tool_caller") tokenizer.save_pretrained("llama3_tool_caller")
save_pretrained
"llama3_tool_caller"
保存的适配器文件仅几MB大小。你可以在任何时候在冻结的Llama 3基模型上重新加载,无需再次训练。
进一步阅读
/think
- 机器学习从业者微调语言模型指南
- 如何在自己的数据集上微调本地 Mistral 或 Llama 3 模型
- Unsloth GitHub
- Hugging Face TRL 文档
- Meta Llama 3 模型页面
结论
你已成功在不到10分钟的训练时间内,使用免费GPU将 Llama 3 8B 微调为一个自定义工具调用代理,该代理能根据特定API模式生成结构化JSON输出。完整流程涵盖了设置 QLoRA 以保持训练效率、构建教授模型保持一致行为模式的数据集,以及在未见过的查询上验证干净的JSON输出。
塑造此工作流的关键决策包括:数据集结构(系统提示、用户查询、JSON目标)、LoRA秩(小数据集保持低位)、以及通过训练损失监控早期发现过拟合。这些也是在将此方法适配到你自己的用例时,首先需要调整的参数。
接下来,使用涵盖数十个工具和模糊查询的更大更多样化数据集将提升泛化能力。一旦获得可靠的工具调用器,将其集成到 LangChain 或 LlamaIndex 等代理框架中,就能构建出能根据结构化输出将查询路由到真实API的完整代理系统。
更多相关内容
- 本地工具调用对比:Gemma 4 与 Llama 3…
- 在Python代理中实现权限控制的工具调用
- 如何使用 Gemma 4 和 Python 实现工具调用
- 构建类似…的纯解码器Transformer模型
- 工具调用与代码执行在AI代理中的对比…
- 掌握AI代理工具调用的路线图