Qwen2.5-Coder-1.5B基础教程:使用transformers加载进行SFT微调
Qwen2.5-Coder-1.5B基础教程:使用transformers加载进行SFT微调
1. 学习目标与前置准备
如果你正在寻找一个轻量级但功能强大的代码生成模型,Qwen2.5-Coder-1.5B绝对值得关注。这个拥有15亿参数的模型专门为代码相关任务设计,在代码生成、代码推理和代码修复方面表现出色。
本教程将手把手教你如何使用transformers库加载Qwen2.5-Coder-1.5B模型,并进行监督微调(SFT)。学完本文,你将掌握:
- 如何正确安装和配置所需环境
- 使用transformers加载预训练模型的方法
- 准备适合代码任务的训练数据
- 进行有效的SFT微调实践
- 测试微调后的模型效果
前置知识要求:只需要基本的Python编程经验,了解一些深度学习概念会更轻松,但不是必须的。我们将从最基础的步骤开始讲解。
2. 环境准备与模型加载
2.1 安装必要的库
首先确保你的Python环境是3.8或更高版本,然后安装必需的依赖库:
pip install transformers>=4.37.0
pip install datasets
pip install accelerate
pip install peft
pip install torch>=2.0.0
这些库分别用于模型加载、数据处理、训练加速和参数高效微调。建议使用虚拟环境来管理依赖。
2.2 加载Qwen2.5-Coder-1.5B模型
使用transformers库加载模型非常简单:
from transformers import AutoTokenizer, AutoModelForCausalLM
model_name = "Qwen/Qwen2.5-Coder-1.5B"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16, # 使用半精度减少内存占用
device_map="auto", # 自动分配设备
trust_remote_code=True # 信任远程代码
)
# 设置padding token
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
这里有几个关键点需要注意:
- 使用
torch.float16可以显著减少内存使用,适合消费级GPU device_map="auto"让transformers自动选择最佳设备分配- 必须设置
trust_remote_code=True因为Qwen模型需要执行一些自定义代码
2.3 验证模型加载成功
加载完成后,简单测试一下模型是否正常工作:
# 测试代码生成能力
test_input = "写一个Python函数计算斐波那契数列:"
inputs = tokenizer(test_input, return_tensors="pt").to(model.device)
with torch.no_grad():
outputs = model.generate(
**inputs,
max_new_tokens=100,
temperature=0.7,
do_sample=True
)
generated_code = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(generated_code)
如果看到模型生成了合理的Python代码,说明模型加载成功!
3. 准备SFT训练数据
3.1 理解代码任务的训练数据格式
对于代码生成模型的SFT微调,我们需要准备指令-代码对的数据。格式通常如下:
{
"instruction": "写一个Python函数来反转字符串",
"input": "",
"output": "def reverse_string(s):\n return s[::-1]"
}
或者更复杂的多轮对话格式:
{
"conversations": [
{"role": "user", "content": "如何用Python读取文件?"},
{"role": "assistant", "content": "可以使用open函数:\n```python\nwith open('file.txt', 'r') as f:\n content = f.read()\n```"}
]
}
3.2 创建示例训练数据集
由于获取大量高质量的代码训练数据可能比较困难,我们先创建一个小的示例数据集:
from datasets import Dataset
# 示例训练数据
training_examples = [
{
"instruction": "写一个Python函数计算阶乘",
"input": "",
"output": "def factorial(n):\n if n == 0:\n return 1\n else:\n return n * factorial(n-1)"
},
{
"instruction": "实现一个二分查找算法",
"input": "",
"output": "def binary_search(arr, target):\n low, high = 0, len(arr) - 1\n while low <= high:\n mid = (low + high) // 2\n if arr[mid] == target:\n return mid\n elif arr[mid] < target:\n low = mid + 1\n else:\n high = mid - 1\n return -1"
},
{
"instruction": "写一个函数检查字符串是否是回文",
"input": "",
"output": "def is_palindrome(s):\n s = s.lower().replace(' ', '')\n return s == s[::-1]"
}
]
# 创建数据集
train_dataset = Dataset.from_list(training_examples)
在实际应用中,你应该使用更大规模的数据集,比如从CodeXGLUE、APPS或者其他代码数据集整理而来。
3.3 数据预处理函数
我们需要编写一个函数来处理训练数据:
def preprocess_function(examples):
# 构建训练文本格式
texts = []
for i in range(len(examples["instruction"])):
instruction = examples["instruction"][i]
input_text = examples["input"][i]
output = examples["output"][i]
text = f"<|im_start|>user\n{instruction}{input_text}<|im_end|>\n<|im_start|>assistant\n{output}<|im_end|>"
texts.append(text)
# 对文本进行tokenize
tokenized = tokenizer(
texts,
truncation=True,
padding=False,
max_length=1024,
return_tensors=None
)
# 设置标签,只计算assistant部分的loss
labels = []
for i in range(len(tokenized["input_ids"])):
input_ids = tokenized["input_ids"][i]
attention_mask = tokenized["attention_mask"][i]
# 找到assistant开始的位置
assistant_start = False
label_mask = [0] * len(input_ids)
for j, token_id in enumerate(input_ids):
if tokenizer.decode([token_id]) == "<|im_start|>":
if j + 1 < len(input_ids) and tokenizer.decode([input_ids[j+1]]) == "assistant":
assistant_start = True
label_mask[j] = 0 # 不计算<|im_start|>的loss
label_mask[j+1] = 0 # 不计算"assistant"的loss
else:
assistant_start = False
label_mask[j] = -100
elif assistant_start:
label_mask[j] = input_ids[j] # 计算assistant内容的loss
else:
label_mask[j] = -100 # 不计算其他部分的loss
labels.append(label_mask)
tokenized["labels"] = labels
return tokenized
# 应用预处理
tokenized_dataset = train_dataset.map(
preprocess_function,
batched=True,
remove_columns=train_dataset.column_names
)
这个预处理函数的关键在于正确设置loss mask,确保模型只在学习生成assistant的回复时计算损失。
4. SFT微调实践
4.1 配置训练参数
现在我们使用Hugging Face的Trainer来进行微调:
from transformers import TrainingArguments, Trainer
training_args = TrainingArguments(
output_dir="./qwen-coder-sft",
per_device_train_batch_size=2, # 根据GPU内存调整
gradient_accumulation_steps=4, # 模拟更大的batch size
learning_rate=2e-5, # 适合SFT的学习率
num_train_epochs=3, # 训练轮数
logging_dir="./logs",
logging_steps=10,
save_steps=500,
eval_steps=500,
fp16=True, # 使用混合精度训练
warmup_steps=100,
weight_decay=0.01,
save_total_limit=2,
prediction_loss_only=True,
remove_unused_columns=False
)
4.2 创建Trainer并开始训练
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_dataset,
data_collator=lambda data: {
"input_ids": torch.stack([torch.tensor(d["input_ids"]) for d in data]),
"attention_mask": torch.stack([torch.tensor(d["attention_mask"]) for d in data]),
"labels": torch.stack([torch.tensor(d["labels"]) for d in data])
}
)
# 开始训练
trainer.train()
# 保存微调后的模型
trainer.save_model()
tokenizer.save_pretrained("./qwen-coder-sft")
4.3 使用LoRA进行高效微调
如果你的计算资源有限,可以考虑使用LoRA(Low-Rank Adaptation)进行参数高效微调:
from peft import LoraConfig, get_peft_model, TaskType
# 配置LoRA
lora_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
inference_mode=False,
r=8, # LoRA秩
lora_alpha=32, # 缩放参数
lora_dropout=0.1, # Dropout率
target_modules=["q_proj", "v_proj", "k_proj", "o_proj"] # 目标模块
)
# 应用LoRA到模型
model = get_peft_model(model, lora_config)
model.print_trainable_parameters() # 查看可训练参数数量
使用LoRA后,可训练参数数量会大幅减少,通常只有原模型的1-10%,大大降低了显存需求和训练时间。
5. 测试微调效果
训练完成后,让我们测试一下微调后的模型效果:
# 加载微调后的模型
from transformers import pipeline
coder_pipeline = pipeline(
"text-generation",
model="./qwen-coder-sft",
tokenizer=tokenizer,
device=0 if torch.cuda.is_available() else -1
)
# 测试代码生成能力
test_instructions = [
"写一个Python函数计算圆的面积",
"实现一个快速排序算法",
"写一个函数来合并两个字典"
]
for instruction in test_instructions:
prompt = f"<|im_start|>user\n{instruction}<|im_end|>\n<|im_start|>assistant\n"
result = coder_pipeline(
prompt,
max_new_tokens=200,
temperature=0.7,
do_sample=True,
pad_token_id=tokenizer.eos_token_id
)
print(f"指令: {instruction}")
print(f"生成的代码: {result[0]['generated_text'][len(prompt):]}")
print("-" * 50)
观察模型生成的代码质量,如果微调成功,你应该看到相比原始模型,微调后的模型更能理解你的指令意图,生成更符合要求的代码。
6. 常见问题与解决方案
6.1 内存不足问题
如果遇到CUDA内存不足的错误,可以尝试以下解决方案:
# 方案1:使用梯度检查点
model.gradient_checkpointing_enable()
# 方案2:使用更小的batch size和梯度累积
training_args.per_device_train_batch_size = 1
training_args.gradient_accumulation_steps = 8
# 方案3:使用LoRA等参数高效方法
6.2 过拟合问题
如果发现模型在训练数据上表现很好,但在新数据上表现差:
# 增加dropout
from transformers import GPT2Config
config = GPT2Config.from_pretrained("Qwen/Qwen2.5-Coder-1.5B")
config.resid_pdrop = 0.2 # 增加残差dropout
config.attn_pdrop = 0.2 # 增加注意力dropout
# 或者使用早停策略
training_args.load_best_model_at_end = True
training_args.metric_for_best_model = "eval_loss"
training_args.greater_is_better = False
6.3 生成质量不佳
如果生成的代码质量不理想:
# 调整生成参数
def generate_code(prompt, max_length=200):
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
outputs = model.generate(
**inputs,
max_length=max_length,
temperature=0.8, # 降低温度减少随机性
top_p=0.9, # 使用核采样
repetition_penalty=1.1, # 减少重复
do_sample=True,
pad_token_id=tokenizer.eos_token_id
)
return tokenizer.decode(outputs[0], skip_special_tokens=True)
7. 总结
通过本教程,你学会了如何使用transformers库对Qwen2.5-Coder-1.5B进行SFT微调。关键要点包括:
- 环境配置:正确安装transformers和相关依赖库,注意信任远程代码的设置
- 数据准备:构建适合代码任务的指令-代码对数据,并正确设置loss mask
- 微调技巧:使用合适的超参数,可以考虑LoRA等高效微调方法
- 效果评估:通过实际代码生成任务测试微调效果
下一步学习建议:
- 尝试使用更大规模、更多样化的代码数据集进行微调
- 探索不同的微调策略,如多任务学习、课程学习等
- 考虑使用人类反馈强化学习(RLHF)进一步优化模型表现
记住,成功的微调需要高质量的训练数据和适当的超参数调优。多实验、多调整,你会得到越来越好的代码生成模型。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。
更多推荐
所有评论(0)