欢迎留言,每条留言都会精选、本人当天回复,文章错误内容也会在回复中更新。本文关于LLaMa-Factory 分为两部分,第一部分是Lora微调源码逻辑,第二部分是部署教程

一、LLaMa-Factory LoRA微调源码逻辑梳理

git clone --depth 1 https://github.com/hiyouga/LLaMA-Factory.git

在仓库LLaMa-Factory中,我找到了关于lora微调的源码,包括包括adapter.py、finetuning_args.py、loader.py、trainer_utils.py、tuner.py,其中只注释了部分涉及关键逻辑的代码,有错误欢迎评论区指出。

1. adapter.py

仓库中路径:

\LLaMA-Factory\src\llamafactory\model\adapter.py

代码及逐行注释:

import re
from typing import TYPE_CHECKING
import torch
from peft import LoraConfig, LoraModel, OFTConfig, PeftModel, TaskType, get_peft_model
from transformers.integrations import is_deepspeed_zero3_enabled
from ..extras import logging
from .model_utils.misc import find_all_linear_modules, find_expanded_modules
from .model_utils.quantization import QuantizationMethod
from .model_utils.unsloth import get_unsloth_peft_model, load_unsloth_peft_model
from .model_utils.visual import COMPOSITE_MODELS, get_forbidden_modules, patch_target_modules
if TYPE_CHECKING:
    # 类型提示:只有在类型检查时才需导入
    from transformers import PretrainedConfig, PreTrainedModel
    from ..hparams import FinetuningArguments, ModelArguments
logger = logging.get_logger(__name__)
def _setup_full_tuning(...):
    """全参数微调设置,此处代码省略"""
    
    # 检查模型是否可训练
    # 获取需要冻结的模块列表
    # 遍历模型参数,解冻非禁止模块并转换数据类型
def _setup_freeze_tuning(...):
    """部分参数冻结微调设置"""
    # 1、计算可训练层ID
    # 2、识别隐藏层和非隐藏层模块
    # 3、构建可训练层列表
    # 4、解冻指定层参数
def _setup_lora_tuning(...) -> "PeftModel":
    """LoRA微调核心设置,代码省略"""
    # 检查微调类型(OFT/LoRA)
    # 处理适配器恢复/合并逻辑
    # 配置LoRA目标模块
    # 创建LoRA配置(即LoraConfig)
    # 使用get_peft_model包装模型
    # 转换可训练参数数据类型
def init_adapter(...) -> "PreTrainedModel":
    """初始化适配器的主函数逻辑"""
    # 检查量化兼容性
    # 确定参数数据类型转换逻辑
    # 根据微调类型分发设置:
    #   full -> _setup_full_tuning
    #   freeze -> _setup_freeze_tuning
    #   lora/oft -> _setup_lora_tuning
    return model

2. finetuning_args.py

仓库中路径:

\LLaMA-Factory\src\llamafactory\hparams\finetuning_args.py

代码及逐行注释:

确定LoRA参数,lora_rank, lora_alpha, lora_dropout, lora_target

from dataclasses import asdict, dataclass, field
from typing import Any, Literal, Optional

@dataclass
class FreezeArguments:
    """冻结微调参数配置"""
    freeze_trainable_layers: int = field(...)  # 这里是可训练层数
    freeze_trainable_modules: str = field(...)  # 可训练模块名
    freeze_extra_modules: Optional[str] = field(...)  # 额外可训练模块

@dataclass
class LoraArguments:
    """LoRA微调参数配置"""
    additional_target: Optional[str] = field(...)  # 额外训练目标
    lora_alpha: Optional[int] = field(...)  # LoRA的缩放因子
    lora_dropout: float = field(...)  # LoRA 中的dropout率
    lora_rank: int = field(...)  # LoRA秩
    lora_target: str = field(...)  # LoRA目标模块
    # 其他LoRA+、DoRA、PiSSA相关参数,。。。

@dataclass
class FinetuningArguments(...):
    """所有微调参数的集成配置"""
    pure_bf16: bool = field(...)  # 是否纯bf16精度
    stage: Literal["pt", "sft", "rm", "ppo", "dpo", "kto"] = field(...)  # 训练阶段
    finetuning_type: Literal["lora", "freeze", "full"] = field(...)  # 微调类型
    # 其他集成参数...

    def __post_init__(self):
        """参数后处理逻辑"""
        # 分割多模块字符串为列表
        # 设置LoRA alpha默认值
        # 验证参数兼容性
        # 检查冲突配置

3. loader.py

仓库中路径:

\LLaMA-Factory\src\llamafactory\model\loader.py

代码及逐行注释:

import os
from typing import TYPE_CHECKING, Any, Optional, TypedDict

import torch
from transformers import AutoConfig, AutoModelForCausalLM, ... 
from trl import AutoModelForCausalLMWithValueHead

from ..extras import logging
from .adapter import init_adapter
from .model_utils import ...  # 各种模型工具

if TYPE_CHECKING:
    # 依旧是类型提示
    from transformers import PretrainedConfig, PreTrainedModel, PreTrainedTokenizer
    from ..hparams import FinetuningArguments, ModelArguments

logger = logging.get_logger(__name__)

class TokenizerModule(TypedDict):
    tokenizer: "PreTrainedTokenizer"
    processor: Optional["ProcessorMixin"]

def load_tokenizer(model_args: "ModelArguments") -> "TokenizerModule":
    """加载tokenizer和processor逻辑"""
    # 初始化参数配置
    # 尝试不同方式加载tokenizer
    # 修补tokenizer配置
    return {"tokenizer": tokenizer, "processor": processor}

def load_config(model_args: "ModelArguments") -> "PretrainedConfig":
    """加载模型配置"""
    return AutoConfig.from_pretrained(...)

def load_model(...) -> "PreTrainedModel":
    """加载预训练模型的核心函数"""
    # 初始化配置
    # 特殊模型处理(Unsloth/MoD)
    # 根据配置选择合适的模型类
    # 模型修补和适配器初始化。
    # 值头模型处理(RLHF场景)
    # 训练/评估模式设置
    # 参数统计和日志
    return model

4. trainer_utils.py

仓库中路径:

\LLaMA-Factory\src\llamafactory\train\trainer_utils.py

代码及逐行注释:


import json
import os
from collections.abc import Mapping
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, Optional, Union

import torch
from transformers import Trainer
from transformers.integrations import is_deepspeed_zero3_enabled

from ..extras import logging
from .model import load_model, load_tokenizer, load_valuehead_params

if TYPE_CHECKING:
    from transformers import PreTrainedModel
    from ..hparams import FinetuningArguments, ModelArguments

# GaLore/APOLLO/BAdam优化器的条件导入...

def create_modelcard_and_push(...):
    """创建模型卡片并推送至Hub"""

def create_ref_model(...):
    """创建参考模型(PPO/DPO训练)"""

def create_reward_model(...):
    #创建奖励模型(PPO训练)

def _create_galore_optimizer(...):
    """GaLore优化器创建"""

def _create_apollo_optimizer(...):
    """APOLLO优化器创建"""

def _create_loraplus_optimizer(...):
    """LoRA+优化器创建"""

def create_custom_optimizer(...):
    """自定义优化器选择入口"""
    # 根据配置选择GaLore/APOLLO/LoRA+/BAdam优化器

def get_batch_logps(...):
    """计算批次log概率(RLHF关键函数)"""

def nested_detach(...):
    """嵌套张量分离(为了防止梯度传播)"""

def get_swanlab_callback(...):
    """SwanLab日志回调"""

def get_ray_trainer(...):
    #Ray分布式训练器创建

5. tuner.py

仓库中路径:

\LLaMA-Factory\src\llamafactory\train\tuner.py

代码及逐行注释:

import os
import shutil
from typing import TYPE_CHECKING, Any, Optional

import torch
import torch.distributed as dist
from transformers import EarlyStoppingCallback, PreTrainedModel

from ..data import get_template_and_fix_tokenizer
from ..extras import logging
from .trainer_utils import get_ray_trainer, get_swanlab_callback

if TYPE_CHECKING:
    from transformers import TrainerCallback

logger = logging.get_logger(__name__)

def _training_function(config: dict[str, Any]) -> None:
    """实际训练执行函数"""
    # 获取所有参数配置
    # 添加各种回调(日志、PiSSA转换、SwanLab等)
    # 根据训练阶段分发任务,包括:
    #   pt -> run_pt
    #   sft -> run_sft
    #   rm -> run_rm
    #   ppo -> run_ppo
    #   dpo -> run_dpo
    #   kto -> run_kto

def run_exp(...) -> None:
    """执行训练入口"""
    # 1 参数解析
    # 2 Ray分布式/单机训练分发
    # 3 调用_training_function

def export_model(...) -> None:
    """模型导出函数"""
    # 1、加载tokenizer和模板
    # 2、加载模型,接着量化处理
    # 3、保存模型和tokenizer
    # 4、做一些特殊处理(值头/Ollama格式)

6. 整体Lora微调逻辑梳理

首先解析配置参数确定LoRA的超参数(秩、alpha值等),在模型加载阶段通过init_adapter函数动态注入LoRA层;该函数会调用_setup_lora_tuning方法,这个方法自动检测目标线性层(比如q_proj/v_proj),创建LoraConfig配置,并使用get_peft_model将基础模型转换为PeftModel;训练阶段根据配置可选配优化器增强技术(LoRA+学习率比例),最终在SFT任务执行器中完成微调,另外,整个过程支持PiSSA初始化、DeepSpeed集成和分布式训练。具体分为下面五个阶段:

1、配置解析阶段,参数初始化:

  • 解析FinetuningArguments中的LoRA参数:lora_ranklora_alphalora_dropoutlora_target
  • 确定微调类型为lora,训练阶段为sft
2. 模型准备(加载):
  • 通过load_model加载基础模型
  • 使用init_adapter初始化LoRA适配器
  • 核心操作在下面_setup_lora_tuning中:
target_modules = find_all_linear_modules(model)  # 查找所有线性层
peft_config = LoraConfig(
    r=args.lora_rank,
    target_modules=target_modules,
    lora_alpha=args.lora_alpha,
    lora_dropout=args.lora_dropout
)
model = get_peft_model(model, peft_config)
4. 训练执行阶段
  • 优化器选择:

    • 优先检查use_galore/use_apollo等高级优化器
    • 默认使用AdamW,支持LoRA+ (loraplus_lr_ratio)
  • 训练分发:
    • run_sft处理监督微调
    • 核心训练循环由HuggingFace Trainer管理
4. 回调处理
  • 关键回调:

    • LogCallback:训练指标记录
    • PissaConvertCallback:PiSSA适配器转换
    • ReporterCallback:训练报告生成
    • SwanLabCallback:实验跟踪
5. 模型导出
  • 保存逻辑:

model.save_pretrained(export_dir)
tokenizer.save_pretrained(export_dir)

二、Lora微调部署教程

1. Lora微调结果展示

一个简单问题测试,这个问题在微调数据集identity中出现过:

图片

可以看到,Lora微调是有用的,qwen2.5-0.5b通过数据集identity学会了这个问题。

2. Lora微调教程

下列命令都在powershell中执行:

(一)建一个python环境

conda create --name SFT python=3.10
conda activate SFT

(二)安装gcc

# 检查是否安装了gcc
gcc --version
#如果没安装,安装 GCC 11
sudo apt update
sudo apt install gcc-11 g++-11
#创建符号链接,让 gcc 命令指向 gcc-11
sudo ln -s /usr/bin/gcc-11 /usr/bin/gcc
sudo ln -s /usr/bin/g++-11 /usr/bin/g++
# 再次验证 GCC 安装成功
gcc --version

(三)安装 dkms

sudo apt update
sudo apt install dkms -y

(四)安装cuda

wget https://developer.download.nvidia.com/compute/cuda/12.2.0/local_installers/cuda_12.2.0_535.54.03_linux.run
sudo sh cuda_12.2.0_535.54.03_linux.run

按空格键取消Driver、Kernel Objects两项勾选,点击install

(五) 配置环境变量,让 nvcc 等命令可以直接使用    

echo 'export PATH=/usr/local/cuda-12.2/bin:$PATH' >> ~/.bashrc
echo 'export LD_LIBRARY_PATH=/usr/local/cuda-12.2/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc
source ~/.bashrc

完成后输入 nvcc -V 检查是否出现对应的版本号,若出现则安装完成。

(六)使用代理魔法后,安装 LLaMA-Factory 及其依赖​​​​​​​

# 使用代理梯子,接着克隆仓库
git clone --depth 1 https://github.com/hiyouga/LLaMA-Factory.git
# 如果Git无法通过 HTTPS 连接到 GitHub 服务器,则为 Git 配置 SOCKS5 代理
git config --global http.proxy socks5://127.0.0.1:33211
git config --global https.proxy socks5://127.0.0.1:33211
# 接着重新克隆仓库
git clone --depth 1 https://github.com/hiyouga/LLaMA-Factory.git
# 安装依赖
cd LLaMA-Factory
pip install -e ".[torch,metrics]"

(七)下载Qwen2.5-0.5B模型到指定路径,可以自行更改​​​​​​​

pip install modelscope 
modelscope download --model 'Qwen/Qwen2.5-0.5B-Instruct' --local_dir '/home/chenyi/LLM/models/qwen2.5/Qwen2.5-0.5B-Instruct'

(八)修改 examples/train_lora/qwen2.5_lora_sft_ds3.yaml 配置文件中的模型路径及输出路径​​​​​​​

model_name_or_path: /home/chenyi/LLM/models/qwen2.5/Qwen2.5-0.5B-Instruct
output_dir: saves/Qwen2.5-0.5B/lora/sft

(九)执行 Lora 微调​​​​​​​

# 使用 DeepSpeed 分布式训练,因此前缀为FORCE_TORCHRUN=1
FORCE_TORCHRUN=1 llamafactory-cli train examples/train_lora/qwen2.5_lora_sft_ds3.yaml

​​​​​​​(十)微调结果​​​​​​​

***** train metrics *****
  epoch                    =        3.0
  total_flos               =     5698GF
  train_loss               =     1.1008
  train_runtime            = 0:36:49.86
  train_samples_per_second =       1.48
  train_steps_per_second   =       0.74

这是我的pc机配置,微调用了36分钟

图片

(十一)结果测试及代码

测试代码如下:

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

# 原始模型:
base_model_path = r"E:\VScode-Projects\SFT-test\models\qwen2.5\Qwen2.5-0.5B-Instruct"
tokenizer = AutoTokenizer.from_pretrained(base_model_path)
original_model = AutoModelForCausalLM.from_pretrained(base_model_path).eval() # .eval() 确保模型处于评估模式

# 微调后的新模型:
from peft import PeftModel

lora_adapter_path = r"E:\VScode-Projects\SFT-test\Qwen2.5-0.5B\lora\sft"# 这是存放 adapter_model.safetensors 的目录
finetuned_model = PeftModel.from_pretrained(original_model, lora_adapter_path).eval()
# 注意:这里我们基于 original_model 加载适配器,而不是重新加载基础模型。

defgenerate_response(model, tokenizer, prompt, max_new_tokens=100):
    inputs = tokenizer(prompt, return_tensors="pt")
    outputs = model.generate(**inputs, max_new_tokens=max_new_tokens, do_sample=True, top_p=0.9, temperature=0.7)
return tokenizer.decode(outputs[0], skip_special_tokens=True)

# 使用之前定义的 generate_response 函数
test_instructions = [
"OpenAI为什么要制作你?",
"你是哪个团队开发的?",
"谁是你的开发者?你叫什么名字?"
]

for instruction in test_instructions:
    orig_response = generate_response(original_model, tokenizer, instruction)
    ft_response = generate_response(finetuned_model, tokenizer, instruction)

print(f"Instruction: {instruction}")
print(f"Original Model: {orig_response}")
print(f"Finetuned Model: {ft_response}")
print("-" * 80)
    Logo

    有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

    更多推荐