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微调。关键要点包括:

  1. 环境配置:正确安装transformers和相关依赖库,注意信任远程代码的设置
  2. 数据准备:构建适合代码任务的指令-代码对数据,并正确设置loss mask
  3. 微调技巧:使用合适的超参数,可以考虑LoRA等高效微调方法
  4. 效果评估:通过实际代码生成任务测试微调效果

下一步学习建议

  • 尝试使用更大规模、更多样化的代码数据集进行微调
  • 探索不同的微调策略,如多任务学习、课程学习等
  • 考虑使用人类反馈强化学习(RLHF)进一步优化模型表现

记住,成功的微调需要高质量的训练数据和适当的超参数调优。多实验、多调整,你会得到越来越好的代码生成模型。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐