简介:这份资源面向希望入门大语言模型微调的开发者与法律科技方向的学习者,聚焦在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-18GB;QLoRA 在此基础上把底座量化到 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", encoding="utf-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", encoding="utf-8") as f: for item in cleaned: f.write(json.dumps(item, ensure_ascii=False) + "\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_dataset,LLaMA-Factory 会按顺序拼接并打乱。
提示:数据清洗后先跑一遍
wc -l看剩余样本数。如果从 10 万条降到 3 万条,说明原始数据里重复或超长的比例很高,这时候要回头检查数据来源,而不是直接拿清洗后的数据训练。
4. LoRA 微调的关键参数与训练启动
4.1 LoRA 秩、alpha 与 target module 的选择
LoRA 的核心参数是lora_rank和lora_alpha。秩决定低秩矩阵的维度,alpha 是缩放因子。经验值是 rank 设 8 或 16,alpha 设 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设 2,gradient_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_code=True) model = AutoModelForCausalLM.from_pretrained( model_path, device_map="auto", trust_remote_code=True, torch_dtype="auto" ) questions = [ "民间借贷利率超过LPR四倍是否受法律保护?", "劳动合同到期不续签,用人单位需要支付经济补偿吗?", "交通事故中,无责方可以要求哪些赔偿?" ] for q in questions: messages = [{"role": "user", "content": q}] text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) inputs = tokenizer(text, return_tensors="pt").to(model.device) outputs = model.generate(**inputs, max_new_tokens=512, temperature=0.1, do_sample=False) response = tokenizer.decode(outputs[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True) print(f"Q: {q}\nA: {response}\n{'-'*60}")temperature=0.1和do_sample=False让输出尽量确定,方便对比不同版本模型的表现。重点看模型是否引用了具体法条编号、逻辑是否连贯、有没有编造不存在的司法解释。
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 条人工看,两个都过了才部署到测试环境。法律场景没有后悔药,模型编一条法条就可能让用户做出错误决策,所以验证环节不能省。希望帮到你。
本文还有配套的精品资源,点击获取