基于LLaMA-Factory与DISC-Law-SFT-Pair微调Qwen2.5-7B-Instruct法律大模型实战
简介这份资源面向希望入门大语言模型微调的开发者与法律科技方向的学习者聚焦在Qwen2.5-7B-Instruct架构上借助LLaMA-Factory框架完成一次完整的领域微调实践。核心亮点是引入DISC-Law-SFT-Pair专业法律数据集让模型在法条理解、案例问答、合同审查等场景中具备更强的领域适应性适合作为NLP进阶与垂直领域落地的练手项目。压缩包共11个文件约35KB包含3个yaml训练配置、3个jsonl数据集文件、1个Python推理脚本以及docx、txt、md等说明文档覆盖LoRA、QLoRA、合并权重等不同微调策略的配置模板方便读者按需切换训练方式。目前已有97人学习下载。通过这份资料读者可以拿到可直接复用的微调配置、法律领域指令数据样例与推理脚本快速理解从数据准备到模型合并的完整链路并在此基础上迁移到其他垂直领域。1. 法律大模型微调从 Qwen2.5-7B-Instruct 到 DISC-Law-SFT-Pair 的落地路径法律咨询场景对模型输出有硬性要求法条引用不能编、判决逻辑不能跳步、当事人权利义务的表述不能含糊。直接拿 Qwen2.5-7B-Instruct 这类通用指令模型去回答“民间借贷利率超过 LPR 四倍是否受保护”它大概率会给你一段听起来合理但法条编号对不上的回答。这不是模型能力不够而是通用语料里法律领域的监督信号太稀疏。LLaMA-Factory 配合 DISC-Law-SFT-Pair 这套组合解决的就是把通用底座快速对齐到法律问答格式的问题。DISC-Law-SFT-Pair 提供的是成对的指令-回复法律数据覆盖民事、刑事、行政等场景适合做 SFT 阶段的指令跟随训练。整条链路在单卡 24GB 显存上就能跑通不需要多机多卡。这篇文章面向的是想在自己业务里落地法律问答、又不想从零搭训练框架的工程师从环境配置、数据格式转换、LoRA 参数设置到训练后验证每一步都给出可复现的命令和参数。2. 环境搭建与 LLaMA-Factory 的安装配置2.1 为什么选 LLaMA-Factory 而不是手写训练循环手写一个 SFT 训练脚本并不难难的是把数据加载、tokenizer 对齐、梯度累积、混合精度、LoRA 注入、checkpoint 保存这些环节都写对。LLaMA-Factory 把这些都封装成了配置驱动的流程你只需要改 YAML 或命令行参数。它对 Qwen2.5 系列的支持在源码里已经内置了对应的 template不需要自己写 chat template 的拼接逻辑。另一个实际考虑是法律数据集的格式往往不统一LLaMA-Factory 的dataset_info.json机制允许你用映射的方式把原始字段对到instruction、input、output三个槽位省去大量预处理代码。安装方式我一般用源码安装方便改配置和看日志git clone https://github.com/hiyouga/LLaMA-Factory.git cd LLaMA-Factory pip install -e .[torch,metrics]装完之后用llamafactory-cli version确认版本。如果这一步报torch相关的 CUDA 版本不匹配先检查nvidia-smi里的驱动版本和pip show torch里的 CUDA 版本是否兼容。我遇到过驱动 535 配 torch 2.4 的 cu121 包没问题但配 cu124 会报libcudart.so找不到降级 torch 到 cu121 就好了。2.2 Qwen2.5-7B-Instruct 的下载与本地路径确认模型权重从 ModelScope 或 HuggingFace 拉都可以国内环境用 ModelScope 更稳pip install modelscope modelscope download --model Qwen/Qwen2.5-7B-Instruct --local_dir ./models/Qwen2.5-7B-Instruct下载完成后确认目录下有config.json、tokenizer.json、model.safetensors等文件。注意 Qwen2.5 的 tokenizer 有tokenizer_config.json里的chat_template字段LLaMA-Factory 会读取这个字段来做对话拼接。如果你手动改过 tokenizer 配置训练时的 prompt 格式可能和推理时不一致导致模型输出重复或截断。2.3 显存与精度配置的取舍7B 模型全量微调在 FP16 下需要约 80GB 显存单卡 24GB 必须用 LoRA 或 QLoRA。LoRA 只训练低秩矩阵显存占用降到 16-18GBQLoRA 在此基础上把底座量化到 4bit显存降到 10-12GB但训练速度会慢 30% 左右。我一般先用 LoRA 跑一版看 loss 曲线如果显存不够再切 QLoRA。LLaMA-Factory 里通过quantization_bit: 4开启 QLoRA同时lora_target: all把 LoRA 注入到所有线性层。注意QLoRA 训练时per_device_train_batch_size不要超过 2否则容易 OOM。梯度累积步数设 8 或 16 来补偿等效 batch size。3. DISC-Law-SFT-Pair 数据集的格式转换与配置3.1 数据集字段结构与 LLaMA-Factory 的映射关系DISC-Law-SFT-Pair 的原始格式通常是 JSON 或 JSONL每条样本包含instruction、input、output三个字段部分版本还有history字段用于多轮对话。LLaMA-Factory 的dataset_info.json里需要注册这个数据集并指定字段映射。假设你把原始数据放在data/disc_law_sft_pair.json在dataset_info.json里加一段{ disc_law_sft_pair: { file_name: disc_law_sft_pair.json, columns: { prompt: instruction, query: input, response: output } } }这里prompt对应系统指令或任务描述query对应用户输入的具体法律问题response对应期望的模型输出。如果原始数据里input为空字符串LLaMA-Factory 会自动只拼接instruction和output不会报错。3.2 数据清洗去重、截断与非法字符处理法律数据集里常见的问题是重复样本和超长样本。重复样本会导致模型过拟合到特定表述超长样本超过 2048 token会被截断截断位置如果在法条中间模型学到的就是残缺法条。我一般用下面这段脚本做预处理import json from transformers import AutoTokenizer tokenizer AutoTokenizer.from_pretrained(./models/Qwen2.5-7B-Instruct) seen set() cleaned [] with open(data/disc_law_sft_pair.json, r, encodingutf-8) as f: for line in f: item json.loads(line) # 用 instruction input 的哈希去重 key hash(item[instruction] item.get(input, )) if key in seen: continue seen.add(key) # 检查 token 长度超过 2048 的丢弃 text item[instruction] item.get(input, ) item[output] if len(tokenizer.encode(text)) 2048: continue # 去掉不可见字符 item[output] item[output].replace(\u200b, ).strip() cleaned.append(item) with open(data/disc_law_sft_pair_clean.json, w, encodingutf-8) as f: for item in cleaned: f.write(json.dumps(item, ensure_asciiFalse) \n)这段脚本做了三件事用instruction input的哈希去重避免同一问题不同表述被重复学习用 tokenizer 实际编码长度过滤超长样本比按字符数截断更准去掉零宽空格这类不可见字符这些字符在训练时会被 tokenizer 切成未知 token干扰 loss 计算。3.3 数据集注册与训练配置中的引用清洗后的数据放到data/目录下更新dataset_info.json里的file_name指向新文件。然后在训练配置 YAML 里通过dataset: disc_law_sft_pair引用。如果你同时用多个数据集可以写成dataset: disc_law_sft_pair,other_datasetLLaMA-Factory 会按顺序拼接并打乱。提示数据清洗后先跑一遍wc -l看剩余样本数。如果从 10 万条降到 3 万条说明原始数据里重复或超长的比例很高这时候要回头检查数据来源而不是直接拿清洗后的数据训练。4. LoRA 微调的关键参数与训练启动4.1 LoRA 秩、alpha 与 target module 的选择LoRA 的核心参数是lora_rank和lora_alpha。秩决定低秩矩阵的维度alpha 是缩放因子。经验值是 rank 设 8 或 16alpha 设 rank 的两倍。法律领域任务相对垂直rank 16 足够捕捉法条引用和逻辑推理的模式再大容易过拟合。lora_target我一般设all把 LoRA 注入到 q_proj、k_proj、v_proj、o_proj、gate_proj、up_proj、down_proj 所有线性层。只注入 q_proj 和 v_proj 的话模型对法律术语的适配会慢很多。model_name_or_path: ./models/Qwen2.5-7B-Instruct stage: sft do_train: true finetuning_type: lora lora_rank: 16 lora_alpha: 32 lora_target: all dataset: disc_law_sft_pair template: qwen cutoff_len: 2048 overwrite_cache: true preprocessing_num_workers: 8template必须设成qwen这样 LLaMA-Factory 会用 Qwen2.5 的 chat template 拼接对话。如果设成default模型看到的 prompt 格式和预训练时不一致loss 会异常高。4.2 学习率、batch size 与梯度累积的配合学习率是 SFT 阶段最玄学的参数。LoRA 微调一般用 1e-4 到 5e-5我习惯从 1e-4 开始如果 loss 震荡就降到 5e-5。batch size 受显存限制单卡 24GB 下per_device_train_batch_size设 2gradient_accumulation_steps设 8等效 batch size 是 16。如果显存还有余量把 batch size 提到 4梯度累积降到 4训练速度会快一些。per_device_train_batch_size: 2 gradient_accumulation_steps: 8 learning_rate: 1.0e-4 num_train_epochs: 3.0 lr_scheduler_type: cosine warmup_ratio: 0.1 bf16: true logging_steps: 10 save_steps: 500 output_dir: ./output/qwen2.5-7b-law-lorabf16: true在 A100 和 30 系以上显卡上开启比 fp16 更稳定不容易出现 loss 为 nan。warmup_ratio: 0.1让学习率在前 10% 步数里线性上升避免一开始就大梯度更新破坏预训练权重。4.3 启动训练与日志监控配置写好后用llamafactory-cli train启动llamafactory-cli train configs/qwen2.5_7b_law_lora.yaml启动后看日志里的loss和learning_rate。正常情况 loss 从 2.0 左右开始下降3 个 epoch 后降到 0.8-1.2 之间。如果 loss 一直不降检查template是否设对、数据里output字段是否为空。如果 loss 降到 0.3 以下大概率过拟合了减少 epoch 或增大 dropout。注意训练过程中如果看到grad_norm突然跳到 10 以上说明有异常样本导致梯度爆炸。可以在配置里加max_grad_norm: 1.0做梯度裁剪。5. 训练后的模型合并、推理验证与效果排查5.1 LoRA 权重合并到基座模型训练完成后output_dir下会有adapter_model.safetensors和adapter_config.json。推理时可以直接加载 LoRA adapter也可以合并到基座模型导出完整权重。合并命令llamafactory-cli export \ --model_name_or_path ./models/Qwen2.5-7B-Instruct \ --adapter_name_or_path ./output/qwen2.5-7b-law-lora \ --template qwen \ --finetuning_type lora \ --export_dir ./output/qwen2.5-7b-law-merged \ --export_size 2 \ --export_legacy_format falseexport_size设 2 表示每个 safetensors 文件最大 2GB方便后续加载。合并后的模型可以直接用 vLLM 或 transformers 加载推理。5.2 用法律问题做推理验证的脚本验证不能只看 loss要拿具体法律问题测。下面这段脚本加载合并后的模型跑几条测试样本from transformers import AutoModelForCausalLM, AutoTokenizer model_path ./output/qwen2.5-7b-law-merged tokenizer AutoTokenizer.from_pretrained(model_path, trust_remote_codeTrue) model AutoModelForCausalLM.from_pretrained( model_path, device_mapauto, trust_remote_codeTrue, torch_dtypeauto ) questions [ 民间借贷利率超过LPR四倍是否受法律保护, 劳动合同到期不续签用人单位需要支付经济补偿吗, 交通事故中无责方可以要求哪些赔偿 ] for q in questions: messages [{role: user, content: q}] text tokenizer.apply_chat_template(messages, tokenizeFalse, add_generation_promptTrue) inputs tokenizer(text, return_tensorspt).to(model.device) outputs model.generate(**inputs, max_new_tokens512, temperature0.1, do_sampleFalse) response tokenizer.decode(outputs[0][inputs[input_ids].shape[1]:], skip_special_tokensTrue) print(fQ: {q}\nA: {response}\n{-*60})temperature0.1和do_sampleFalse让输出尽量确定方便对比不同版本模型的表现。重点看模型是否引用了具体法条编号、逻辑是否连贯、有没有编造不存在的司法解释。5.3 效果不达预期时的排查顺序如果模型回答仍然编法条按这个顺序排查先看训练数据里output是否包含法条编号如果原始数据就没有模型学不会再看cutoff_len是否截断了包含法条的样本然后检查template是否和推理时一致最后看 LoRA rank 是否太小尝试提到 32 再训一版。我遇到过template设成default导致训练和推理的 prompt 格式差一个换行符模型输出全是重复的“根据根据根据”改成qwen后正常。6. 法律微调的进阶技巧数据配比与多轮对话构造6.1 通用指令数据与法律数据的配比策略纯法律数据训练 3 个 epoch 后模型在法律问答上表现不错但通用对话能力会下降比如问它“今天天气怎么样”它会往法律上扯。常见做法是掺入 10%-20% 的通用指令数据比如 alpaca 或 sharegpt 格式的中文指令集。LLaMA-Factory 支持多数据集混合dataset: disc_law_sft_pair,alpaca_zh然后在dataset_info.json里给alpaca_zh注册对应的文件。配比上我一般按样本数算法律数据 8 万条通用数据掺 1 万条左右。如果通用数据太多法律任务的 loss 下降会变慢。6.2 多轮法律咨询对话的构造方法DISC-Law-SFT-Pair 主要是单轮指令对但实际法律咨询往往是多轮的用户先问“借款利息怎么算”模型回答后再追问“那逾期利息呢”。要支持多轮需要把数据构造成history字段。LLaMA-Factory 的 sharegpt 格式支持多轮{ conversations: [ {from: human, value: 借款利息怎么算}, {from: gpt, value: 根据《民间借贷司法解释》...}, {from: human, value: 那逾期利息呢}, {from: gpt, value: 逾期利息按...} ] }在dataset_info.json里注册时用formatting: sharegpt并指定conversations字段。这样训练时模型会学到多轮上下文的衔接而不是每轮独立回答。6.3 用验证集 loss 和人工抽检做双重验证训练时从数据里切 5% 做验证集配置里加val_size: 0.05和evaluation_strategy: steps。验证集 loss 如果连续 3 次不降就提前停止避免过拟合。人工抽检我一般从验证集里随机抽 20 条逐条看模型输出是否满足三个标准法条编号正确、逻辑链完整、没有编造不存在的司法解释。这三个标准里法条编号正确是最硬的指标如果这条不达标其他都不用看了。我自己的习惯是每次训练完先跑一遍验证集 loss再抽 20 条人工看两个都过了才部署到测试环境。法律场景没有后悔药模型编一条法条就可能让用户做出错误决策所以验证环节不能省。希望帮到你。本文还有配套的精品资源点击获取