在大模型(LLM)领域,微调是一个关键步骤,它能让通用模型适应特定任务的需求。本文将深入探讨如何利用Google Colab的强大算力,对拥有70亿参数的Mistral 7B模型进行微调,使其具备自主玩Minecraft的能力,最终目标是打造一个AI 智能体。我们将详细解析微调过程中的关键技术,例如LoRA、QLoRA等,以及如何针对Minecraft任务进行数据准备和模型优化。
1. Minecraft AI智能体:微调的动机与挑战
最初的想法是创建一个能够自主在Minecraft世界中生存和探索的AI 智能体,摆脱手动挖掘钻石的繁琐。这个设想并非异想天开,已经有相关的学术研究。实现这一目标的关键在于让LLM能够理解Minecraft的游戏指令,并根据游戏环境做出决策。微调模型,使其适应Minecraft的特定指令和环境,是实现这一目标的可行途径。挑战在于如何构建高质量的训练数据,以及如何优化模型使其具备足够的推理能力,完成复杂的Minecraft任务。
2. Mistral 7B:小而强大的选择
在众多大模型中,作者最终选择了Mistral 7B,而非参数量更大的LLaMA 13B。选择的原因在于Mistral 7B在某些基准测试中表现优于LLaMA,并且模型体积更小,更适合在Google Colab等资源有限的环境下进行实验。Mistral 7B的开源特性也为微调提供了便利。实践证明,小型大模型在资源受限的情况下,通过合理的微调也能达到令人满意的效果。
3. Google Colab环境搭建:准备微调的土壤
在开始微调之前,需要在Google Colab上搭建必要的环境。首先,需要安装必要的Python库,包括:
- transformers:Hugging Face的核心库,提供大模型、分词器和生成API。
- accelerate:抽象硬件细节,简化分布式训练。
- bitsandbytes:实现8-bit/4-bit量化,降低VRAM和内存使用。
- peft:Parameter-Efficient Fine-Tuning,允许仅训练部分参数,节省时间和GPU资源。例如,LoRA。
- trl:Transformers Reinforcement Learning,方便使用RLHF(Reinforcement Learning with Human Feedback)。
- datasets:下载、流式处理和预处理数据集。
此外,还需要在Hugging Face上创建一个账户并生成一个访问令牌(Token),用于从Hugging Face Hub下载模型和上传微调后的模型。将Token添加到Google Colab的“Secrets”中,命名为HF_TOKEN
,以便程序能够安全地访问Hugging Face Hub。
4. 数据准备:Minecraft指令的语言表达
为了让Mistral 7B理解Minecraft的指令,需要构建合适的训练数据集。作者设计了一种包含[TAREFA]
(任务)、[INVENTÁRIO]
(库存)和[AÇÃO]
(动作)三种Token的指令格式。例如:
{"text":"[TAREFA] 创建工作台 [INVENTÁRIO] {\"橡木原木\":6} [AÇÃO] 合成 木板"}
其中,[TAREFA]
描述了需要完成的任务,[INVENTÁRIO]
描述了当前库存,[AÇÃO]
描述了应该执行的动作。这种结构化的数据格式有助于模型理解不同Token的含义,并建立任务、库存和动作之间的联系。
作者建议使用Snake_case或Kebab-case命名物品,以减少Token数量,并使用有效的JSON格式方便后续的数据解析。使用</s>
(或Tokenizers的EOS Token)结束每一个条目,以避免上下文泄露。当数据集增大时,可以考虑使用.jsonl
格式,其中包含一个名为"text"
的字段,包含完整的字符串。虽然作者最初使用的是TXT文件,但后来切换到了JSONL文件。
训练数据的质量直接影响微调的效果,因此需要精心设计数据格式,并准备足够数量的训练样本。作者发现,大约一千个示例之后,模型才开始产生较为合理的结果。
5. Tokenizer:理解语言的桥梁
Tokenizer 是 大模型理解语言的关键组成部分。它负责将文本转换为模型可以处理的数字 Token。 在 微调 中,需要特别注意 Tokenizer 的配置,特别是当向模型添加新的特殊 Token 时。
tokenizer = AutoTokenizer.from_pretrained(model_id, use_fast=True, trust_remote_code=True)
special = {"additional_special_tokens": [
"[TAREFA]", "[INVENTÁRIO]", "[AÇÃO]",
"[ITEM]", "[QUANTIDADE]", "[MATERIAL]",
"[FERRAMENTA]", "[CRAFT]", "[COLETAR]"
]}
tokenizer.add_special_tokens(special)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"
这段代码首先加载预训练模型的 Tokenizer,然后添加自定义的特殊 Token,例如 [TAREFA]
、[INVENTÁRIO]
和 [AÇÃO]
。 这些 Token 将帮助模型更好地理解 Minecraft 指令的结构。 tokenizer.pad_token = tokenizer.eos_token
设置填充 Token 与结束 Token 相同,这是一种常见的 微调 实践。tokenizer.padding_side = "right"
指定填充位置在右侧。
6. QLoRA:在有限资源下的高效微调
QLoRA (Quantization-aware Low-Rank Adaptation) 是一种参数高效的微调技术,它通过量化模型权重并引入低秩适配器来实现。 这种技术可以在不显著降低模型性能的情况下显著减少 VRAM 的使用。
bnb_cfg = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True
)
model = AutoModelForCausalLM.from_pretrained(
model_id,
quantization_config=bnb_cfg,
device_map="auto",
trust_remote_code=True,
)
model.resize_token_embeddings(len(tokenizer))
这段代码使用 BitsAndBytesConfig
配置 QLoRA。load_in_4bit=True
启用 4-bit 量化, bnb_4bit_quant_type="nf4"
指定量化类型为 NF4, bnb_4bit_compute_dtype=torch.bfloat16
指定计算数据类型为 bfloat16, bnb_4bit_use_double_quant=True
启用双重量化。 AutoModelForCausalLM.from_pretrained
加载量化后的模型, device_map="auto"
自动将模型分配到可用的 GPU 设备上。最后,model.resize_token_embeddings(len(tokenizer))
调整模型的嵌入层大小,以匹配新添加的特殊 Token。 如果没有添加新的 Token,则不需要调整。
7. LoRA:精确调整模型的能力
LoRA (Low-Rank Adaptation) 是一种参数高效的微调技术,它通过在预训练模型的关键层中注入可训练的低秩矩阵来实现。 这种方法可以显著减少需要训练的参数数量,从而降低计算成本和内存需求。
lora_config = LoraConfig(
r=16,
lora_alpha=32,
target_modules=[
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"
],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
model = get_peft_model(model, lora_config)
print("🔧 Lora Config Applied:")
model.print_trainable_parameters()
r=16
:LoRA 的秩,控制可训练参数的数量。 较高的秩会增加模型的表达能力,但也需要更多的 VRAM 和训练时间。lora_alpha=32
:LoRA 的缩放因子。 输出的 LoRA 适配器乘以一个lora_alpha / r
的系数。将lora_alpha
设置为r
的两倍是一种常见的做法,这有助于避免新信息被冻结模型的原始权重所掩盖。target_modules=[...]
:指定要在其中注入 LoRA 适配器的模型层。 这些层通常是 Transformer 块内的注意力和前馈网络中的线性层。lora_dropout=0.05
:应用于 LoRA 适配器的 dropout 概率。这有助于防止过拟合。bias="none"
:指定是否训练偏差参数。将其设置为"none"
可以减少需要训练的参数数量。task_type="CAUSAL_LM"
:指定任务类型为因果语言建模。
该代码使用 LoraConfig
配置 LoRA。r=16
设置 LoRA 的秩, lora_alpha=32
设置缩放因子, target_modules
指定要注入 LoRA 适配器的目标模块。 最后,get_peft_model(model, lora_config)
将 LoRA 适配器添加到模型中, model.print_trainable_parameters()
打印可训练参数的数量。
8. 训练过程:步步为营的优化
在 微调 过程中,作者使用了自定义的 ValidationCallbackV03
回调函数,该函数在每个训练步骤之后评估模型在关键任务上的性能。 如果模型开始在基本任务上表现不佳,则回调函数将停止训练,以防止灾难性遗忘。
class ValidationCallbackV03(TrainerCallback):
def __init__(self, tokenizer):
self.tokenizer = tokenizer
self.best_base_rate = 0
self.valid_count= 0
def on_evaluate(self, args, state, control, model, **kwargs):
if state.global_step > args.warmup_steps and state.global_step % 75 == 0:
self.valid_count += 1
print(f"\n🧪 Validation v0.3 #{self.valid_count} - Step {state.global_step}")
print("=" * 60)
base_rate = self.validate_model(model)
if self.valid_count > 2:
if base_rate < 45 and self.best_base_rate > 70:
print("🛑 Regression Detected! Stopping...")
control.should_stop = True
elif base_rate < 25:
print("🛑 Critical Regression Detected! Stopping...")
control.should_stop = True
self.best_base_rate = max(self.best_base_rate, base_rate)
def validate_model (self, model):
CRITICAL_TESTS = [
# Basic Tests
("创建 木镐", {"木板": 3, "木棍": 2}, "合成 木镐", "basic"),
("创建 床", {"木板": 3, "羊毛": 3}, "合成 床", "basic"),
("创建 铁剑", {"铁锭": 2, "木棍": 1}, "合成 铁剑", "basic"),
("获取 铁锭", {"粗铁矿": 5, "煤炭": 2, "熔炉": 1}, "熔炼 粗铁矿", "basic"),
("创建 工作台", {"橡木原木": 3}, "合成 木板", "basic"),
# Reasoning Tests
("创建 铁镐", {"木棍": 2}, "获取 铁锭", "reasoning"),
("创建 钻石剑", {"木棍": 1}, "采集 钻石", "reasoning"),
("创建 熔炉", {"圆石": 7}, "采集 圆石", "reasoning"),
]
basic_fails = 0
total_basic = 0
reasoning_fails = 0
total_reasoning = 0
for task, inventory, expected, category in CRITICAL_TESTS:
inventory_str = str(inventory).replace("'", '"')
prompt = f"[TAREFA] {task} [INVENTÁRIO] {inventory_str} [AÇÃO]"
inputs = self.tokenizer(prompt, return_tensors="pt", add_special_tokens=False)
inputs = {k: v.to(model.device) for k, v in inputs.items()}
with torch.no_grad():
outputs = model.generate(
**inputs,
max_new_tokens=25,
do_sample=False,
pad_token_id=self.tokenizer.eos_token_id,
eos_token_id=self.tokenizer.eos_token_id,
repetition_penalty=1.05
)
new_tokens = outputs[0][inputs['input_ids'].shape[1]:]
response = self.tokenizer.decode(new_tokens, skip_special_tokens=True)
response = response.split('<|endoftext|>')[0].strip()
right = expected in response
status = "✅" if right else "❌"
print(f"{status} {category.upper()}: {task}")
print(f" Expected: {expected}")
print(f" Response: {response}")
if category == "basic":
total_basic += 1
if not right:
basic_fails += 1
else:
total_reasoning += 1
if not right:
reasoning_fails += 1
base_basic_rate = (total_basic - basic_fails) / total_basic * 100
base_reason_rate = (total_reasoning - reasoning_fails) / total_reasoning * 100 if total_reasoning > 0 else 0.0
print(f"\n📊 RESULTS v0.3:")
print(f" Basic Hit Rate: {base_basic_rate:.1f}% ({total_basic - basic_fails}/{total_basic})")
print(f" Reasoning Hit Rate: {base_reason_rate:.1f}% ({total_reasoning - reasoning_fails}/{total_reasoning})")
print(f" Best History Rate: {self.best_base_rate:.1f}%")
return base_basic_rate
此外,作者还使用了 SFTConfig
和 SFTTrainer
来简化 微调 过程。 SFTConfig
用于配置训练参数,例如学习率、批量大小和训练周期数。 SFTTrainer
用于管理训练循环并保存 微调 后的模型。
作者选择按步骤进行验证,而不是按 Epoch 进行验证,这在训练大型语言模型时可能更有效。 通过每 50 步进行评估,可以快速获得有关进度的反馈并捕获最佳检查点。
training_args = SFTConfig(
output_dir=OUTPUT_DIR,
per_device_train_batch_size=2,
gradient_accumulation_steps=6,
learning_rate=8e-5,
bf16=True,
tf32=True,
num_train_epochs=15,
save_steps=50,
eval_steps=50,
logging_steps=15,
save_strategy="steps",
eval_strategy="steps",
warmup_steps=125,
load_best_model_at_end=True,
metric_for_best_model="eval_loss",
greater_is_better=False,
save_total_limit=4,
warmup_ratio=0.18,
lr_scheduler_type="cosine",
report_to="none",
dataloader_pin_memory=False,
remove_unused_columns=False,
prediction_loss_only=True,
packing=False,
max_seq_length=512,
dataloader_num_workers=4,
group_by_length=True,
weight_decay=0.02,
max_grad_norm=0.5
)
trainer = SFTTrainer(
model=model,
train_dataset=tokenized["train"],
eval_dataset=tokenized["test"],
peft_config=lora_config,
args=training_args,
processing_class = tokenizer,
callbacks=[validation_callback]
)
9. 模型保存与评估:检验微调的成果
训练完成后,需要保存 微调 后的模型和 Tokenizer。 这将允许您在以后加载模型并将其用于 Minecraft 智能体推理。
trainer.model.save_pretrained(OUTPUT_DIR)
tokenizer.save_pretrained(OUTPUT_DIR)
作者还建议保存训练配置,以便以后可以重现训练过程。
最后,使用自定义的 compare_tokenizer()
函数评估 Tokenizer 的质量和效率。 此函数将测试几个示例,并打印 Token 的数量、每个 Token 的字符数以及 Token 化过程是否可逆。
10. 结论与展望:AI Steve的未来
本文详细介绍了如何在 Google Colab 上 微调 Mistral 7B 大模型,并将其应用于 Minecraft 智能体。 通过使用 QLoRA、LoRA 和自定义的回调函数,可以在资源有限的环境中有效地训练模型。 虽然作者的最终目标是创建一个能够自主玩 Minecraft 的 AI 智能体,但本文中介绍的技术和概念可以应用于广泛的其他任务。
虽然微调可以提升模型在特定任务上的性能,但更大的模型可能更适合使用Prompt工程技术。 在 Minecraft Malmo 项目的上下文中,使用 OpenAI API 或 Qwen-30B-A3B 等模型可能比训练模型更好。
未来的工作可能包括探索不同的训练策略、改进训练数据集以及将 微调 后的模型集成到 Minecraft 游戏中。 也许在下一个教程中,作者就可以用他们的 AI Steve 挖掘一些钻石。