
简介LexiLaw中文法律大模型微调资源包基于ChatGLM-6B架构在法律数据集上微调而成面向法律从业者、法学生及普通用户提供法律咨询、条款解读、案例解析与法规解读等智能问答支持。资源共60个文件、压缩包1.48MB以Python脚本为主28个py涵盖模型微调finetune_lora.py、finetune_ptuning.py、推理inference_lora.py及ChatGLM模型定义、配置等代码并含Markdown说明、启动脚本、JSON配置与指令数据示例其中启动脚本覆盖多种微调训练方式便于快速上手训练与部署。已有339人学习下载。借助此资源可深入理解中文法律大模型的微调流程、数据组织与推理实现同时作者还分享了在大模型基础上微调的经验与最佳实践适合人工智能研究者、法律科技开发者和NLP入门者据此复现实验、改进模型或拓展到其他垂直领域。1. 中文法律大模型 LexiLaw 到底解决什么问题先回答效力层级与法条时效这两个真需求做中文法律大模型这事最早踩的坑不是算力是“你以为它懂法”。把一份劳动合同纠纷扔给通用大模型它能给出三段式分析但不会提醒你《劳动合同法》第十九条试用期条款的强制属性你问它民间借贷利息上限它答得出数字却说不清这个结论在 2020 年修法后已经换了依据。LexiLaw 这类中文法律大模型要解决的就是把预训练大语言模型的通用语言能力压到中国法律体系的事实认定、规范索引和效力层级上。这篇笔记写给三类人想把通用底座往法律领域调参的算法工程师、要给律所搭知识中台的研发以及评估法律 AI 产品值不值得投入的技术负责人。2. 预训练还是微调为什么中文法律大模型要站在通用底座上继续训练从零预训练一个法律大模型听起来很“正统”但算一笔账就明白不划算中文预训练语言模型已经把语法、常识、推理能力都练好了法律领域缺的只是“专业分布”。真正可靠的从业方案是站在通用底座上做继续预训练加指令微调。这一章把基座选型、继续预训练的参数设计和脚本写法一次讲透。2.1 基座模型怎么选中文词表、上下文长度和开源许可选底座时我看四个东西中文 token 效率、上下文窗口、显存占用、开源许可。中文 token 效率直接决定训练和推理成本——同样是“本院认为”词表里带常用法律词汇的模型可能按一个词元切词表偏英文的模型可能切成三四个碎片长文本场景下成本差距能到 30% 以上。目前常见的中文底座选择集中在 Qwen 系列、Baichuan2、ChatGLM 系列、Yi 系列以及 Llama 系加中文词表扩展的变体。我的习惯是列一张对比表再决定选型维度检查重点踩坑提醒中文词表法律术语切分后 token 数是否有明显膨胀用“合同解除权”做一次 tokenizer 实测上下文窗口原始长度最好 ≥ 8K后续可外推4K 窗口处理判决书全文会频繁截断显存占用7B 全量微调至少需要 4×80G先用 1 条样本跑通前向再开训开源许可是否有商用限制、是否需要开源衍生权重商用前让法务过一遍模型卡这里插一句为什么不做从零预训练通用中文语料加法律语料的总量级在万亿 token 级别训练成本是千万级起步而继续预训练只需把法律语料按 1:9 到 2:8 的比例混入通用数据用 1e-5 量级的学习率练几十亿 token就能把法律实体、文书风格、法条表述的分布拉进模型。对绝大多数团队这是性价比最高的路径。2.2 继续预训练阶段数据配比、学习率与序列长度继续预训练的目标不是“让模型背法条”而是让模型在生成时更偏好法律表达。数据配比是这里最容易翻车的地方。早期我试过 100% 纯法律语料继续预训练结果模型开始“法言法语”到连日常指令都听不懂回答任何问题都像在写判决书。后来调整为通用数据与法律数据 8:2 或 7:3通用能力才稳住。法律数据内部也有结构裁判文书占比最高50% 左右其次法规、法考题目、法律咨询问答。学习率要压得比普通预训练低一个量级。全量预训练常见学习率在 1e-4 到 3e-4继续预训练我一般取 1e-5 到 2e-5配合 cosine 衰减。批次大小方面7B 模型在 8 卡 A100 上设 global batch size 512 到 1024 都算合理关键看 loss 曲线是否平滑下降。序列长度不要迁就显存而设成 512法律条款和判决书的论证段落动辄上千字建议至少 2048有条件直接上 4096。序列太短的后果是模型在长文本上训练不充分后续做指令微调时一遇到长输入就乱.2.3 用 DeepSpeed 跑继续预训练启动脚本与参数说明下面是一个我用 DeepSpeed ZeRO-2 跑 7B 继续预训练的启动脚本骨架。它不绑定特定框架换成 Megatron 或 PaddlePaddle 也可以关键是参数语义要对齐。deepspeed --num_gpus8 train_continue_pretrain.py \ --model_name_or_path Qwen/Qwen-7B \ --train_data_path ./data/law_corpus_jsonl \ --bf16 True \ --output_dir ./outputs/law_base_v1 \ --num_train_epochs 2 \ --per_device_train_batch_size 8 \ --gradient_accumulation_steps 8 \ --learning_rate 1.5e-5 \ --lr_scheduler_type cosine \ --warmup_ratio 0.03 \ --max_length 4096 \ --save_steps 2000 \ --logging_steps 10 \ --deepspeed ./ds_config.json逐个说参数bf16 True在 A100/H100 上能省显存且数值稳定如果只有 V100 就得换fp16。per_device_train_batch_size8 加上gradient_accumulation_steps8等效总 batch 是 8 卡乘 8 再乘 8等于 512 条样本。learning_rate取 1.5e-5比普通预训练低是因为底座已经收敛过学习率太大会把通用能力冲掉。warmup_ratio 0.03的意思是前 3% 的 step 线性升温避免开局 loss 震荡。max_length 4096是这条命令里最影响显存的参数显存不够时优先把它降到 2048而不是动 batch size。这里特别提醒继续预训练阶段不要用 LoRA。LoRA 适合指令微调因为它改变的是行为风格继续预训练需要更新底层知识分布低秩适配会限制模型吸收新语料的能力。全量微调慢但这一步慢得值。3. 法律语料工程把裁判文书与法规库变成可训练数据的完整流水线法律大模型效果的上限不在模型结构在数据。这一章写语料从哪里来、怎么清洗、怎么去重、怎么配比。每一步都有具体脚本和参数照着改就能跑。3.1 语料来源与合规边界法律语料按优先级排第一梯队是国家法律法规数据库和裁判文书公开网。法规库结构化程度高直接能转成条文对裁判文书则要处理大量格式噪声。第二梯队是法考真题、法学教材、法律咨询社区问答。第三梯队才是法律百科和新闻噪声高只能做补充。合规是硬门槛。裁判文书即使公开也包含当事人姓名、身份证号、住址、银行账号。我处理文书的固定流程是先做实体脱敏再进清洗管线。脱敏规则上姓名用随机替换姓氏加“某”身份证号用正则匹配 18 位数字后整体打码手机号保留前三位和后两位其余星号。这一步不做数据不能出内网更不能拿去训练。3.2 从 PDF 判决书到干净文本清洗脚本与正则规则import pdfplumber import re def extract_pdf_text(pdf_path): full_text [] with pdfplumber.open(pdf_path) as pdf: for page in pdf.pages: text page.extract_text() if text: full_text.append(text) return \n.join(full_text) def clean_judgment(text): # 去掉页眉页脚多为法院名称与页码 text re.sub(r^\s*第\s*\d\s*页.*$, , text, flagsre.MULTILINE) text re.sub(r^\s*※{3,}.*$, , text, flagsre.MULTILINE) # 去掉案号行之外的空行与多余空格 text re.sub(r[ \t], , text) text re.sub(r\n{3,}, \n\n, text) # 按审判程序切分主文与说理部分 text text.replace(本院认为, \n【本院认为】\n) text text.replace(判决如下, \n【判决如下】\n) return text def desensitize(text): # 身份证号18 位支持 X 结尾 text re.sub(r\b[1-9]\d{5}(?:18|19|20)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])\d{3}[\dXx]\b, 【身份证号已隐藏】, text) # 手机号 text re.sub(r\b1[3-9]\d{9}\b, 【手机号已隐藏】, text) return text逻辑说明pdfplumber负责把 PDF 逐页抽成文本这一步跑不快10 万份文书建议用多进程并行。clean_judgment里的三个正则分别处理页眉页脚、多余空白和段落切分。把“本院认为”和“判决如下”标记成特殊段落头是为了后续构造指令数据时能精准切出“裁判说理”和“裁判结果”两个片段。desensitize里的身份证正则看着长其实只做一件事匹配 18 位身份证号并整体替换。注意顺序不能反先脱敏再清洗否则原始文本里的脱敏标记会被清洗规则误删。清洗后的文本长度分布一定要看。我踩过的坑是一次性把所有文书拼成长文本导致单条样本超过 10 万 token训练时只能硬截断法官的说理逻辑被切成两半。正确做法是按“一案一文”切分一份判决书作为一条独立样本超长的再按“本院认为”和“判决如下”切成上下两段。3.3 去重与配比防止模型被某一类文书带偏法律语料的重复率比想象中高。同一个案由的判决书模板部分几乎一样同一部法规的解读在不同网站被反复转载。不去重模型会过度拟合高频模板生成时不断复读“依照《中华人民共和国民法典》第五百七十七条”却说不清违约责任的构成要件。去重我用两层。第一层是 MinHash 做全文近似去重阈值设 0.85也就是相似度 85% 以上的文本对只保留一条。第二层是对“本院认为”段落单独做 SimHash 去重因为这部分是模型最需要学的推理内容重复了影响最大。两层的脚本都不复杂核心参数就是哈希函数数量和阈值前者影响召回后者影响精度不必调太细先跑一遍看重复率再决定要不要收紧。配比上我当前的默认配比是裁判文书 50%、法律法规 20%、法考题目与解析 15%、法律咨询问答 10%、法学教材 5%。这个比例的核心逻辑是裁判文书占比最高因为它包含了法律事实、证据认定、条文引用的完整体现模型从中学到的是“怎么把规范用到事实上”法规是骨架但纯背条例无法形成推理能力法考题目和咨询问答负责教会模型“提问-回答”的格式。教材占比最低因为它和通用语料的知识重叠度高加太多收益有限。4. 指令微调与对齐让模型学会论证而不是复读法条继续预训练做完模型只是“法律语料概率分布模拟器”。要让它像一个法律助手那样回答问题必须做指令微调。这一章讲指令数据怎么构造、微调超参怎么定、以及法律场景下特有的对齐问题。4.1 从裁判文书构建议问对把“本院认为”变成训练数据import re import json def build_qa_from_judgment(clean_text): # 提取裁判说理部分 reasoning_match re.search(r【本院认为】\s*(.*?)(?:\s*【判决如下】|$), clean_text, re.S) decision_match re.search(r【判决如下】\s*(.*?)(?:\s*$), clean_text, re.S) if not reasoning_match: return None reasoning reasoning_match.group(1) decision decision_match.group(1) if decision_match else # 按案由生成问题模板 cause_match re.search(r((?:民间借贷|劳动合同|买卖合同|离婚|侵权)纠纷), clean_text) cause cause_match.group(1) if cause_match else 民事纠纷 qa_pairs [ { instruction: f请基于以下案情分析{cause}中双方主要的争议焦点。, output: reasoning, }, { instruction: f案件判决结果是什么, output: decision, }, ] return qa_pairs with open(judgments_clean.jsonl, r) as f: with open(law_instructions.jsonl, w) as out: for line in f: doc json.loads(line) pairs build_qa_from_judgment(doc[text]) if pairs: for p in pairs: out.write(json.dumps(p, ensure_asciiFalse) \n)这个脚本的逻辑是从清洗后的判决书里定位“本院认为”和“判决如下”两个关键段落再把它们映射成“分析争议焦点”和“陈述判决结果”两类指令。问题模板虽然机械但胜在稳定、不易跑偏。这里有一条主动加的噪声在问题里保留案由是为了让模型学会按案由类型调整回答结构是民间借贷就谈利息与还款期是劳动合同就谈解除与赔偿金。指令数据的质量比数量重要。我见过有人为了凑数据量把“本院认为”整段复制到输出里结果模型学会的是把案情复述一遍再给结论而不是抽象出裁判规则。正确做法是至少让一个懂法律的人对 100 条指令输出做改写把“法院认为”的口吻改成“法律分析”的口吻去掉具体案号和人名让结论更通用。这 100 条改写后的数据放进训练集能明显改变模型的回答风格。4.2 微调超参与 loss 观察全量微调还是 LoRAmodel: Qwen/Qwen-7B method: lora lora: r: 32 lora_alpha: 64 lora_dropout: 0.05 target_modules: [q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj] train: batch_size: 4 gradient_accumulation_steps: 16 learning_rate: 2e-4 num_epochs: 3 max_length: 2048 warmup_ratio: 0.03 scheduler: cosine这份配置是 LoRA 微调的典型参数。r表示低秩矩阵的秩32 在 7B 模型上是经验值太小欠拟合太大显存压力高且容易过拟合。learning_rate用 2e-4比全量微调的 1e-5 高一个量级这是 LoRA 的特性——它只更新少量参数需要更大的步长才能收敛到有效区域。target_modules把注意力 QKV 和 MLP 投影都覆盖了因为法律论证需要模型在两个层面同时适配注意层决定它关注哪些案情要素MLP 层决定它怎么组织论证逻辑。观察 loss 曲线时不要只盯训练 loss。常见情况是训练 loss 降到 0.6但验证 loss 在某个点开始反弹说明过拟合了。法律指令数据量通常在几万到几十万条3 个 epoch 已经偏多我一般先训 2 个 epoch 存一次中间权重再对比验证集效果决定要不要继续。另外要盯生成结果的“重复率”——如果模型输出里反复出现“根据《中华人民共和国民法典》”这样的套话说明过拟合到模板上了应该立刻停训。4.3 对齐法律模型里的“人工智能偏见”怎么处理法律场景的对齐和通用助手的“安全无害”不太一样。我把它拆成三个具体要求立场中立、不教唆规避法律、不编造法条来源。立场中立是最难实现的。裁判文书天然带立场——法官做出了判决模型学到的就是“判决书式结论”。当你问“这笔借款该不该还”时模型可能直接给出判决式回答而不是列出双方主张与法律依据。处理办法是在指令数据里故意加入对立视角的问答对比如同一案情分别问“债权人如何主张”和“债务人如何抗辩”让模型学会站在不同位置分析。这个做法很像对抗训练数据量不用多几千条就能见效。不教唆规避法律这条需要在对齐阶段加入拒答模板。训练数据里要配一批“如何在借条上不写借款金额”这类问题的安全回答明确告诉模型这类问题不提供操作建议只做法律风险提示。我在实践中会单独做一次 DPO直接偏好优化偏好数据就是从这些边界问答里构造的。效果比单纯在 prompt 里加安全提示词稳定得多。5. 法律大模型训练避坑五个翻车点与可落地的排查手段这一章是从多次训练和部署里攒出来的踩坑记录每条都是“现象、原因、解决”三步。遇到同类问题直接对照排查。5.1 现象模型把“应当”和“可以”搞混回答没有效力层级微调后测试模型在回答“合同未约定履行期限债权人能否随时要求履行”时回答成“债权人应当随时要求履行”。问题很严重“应当”是强制性规范“可以”是授权性规范法律效力完全不同。原因在于训练数据没有强调规范词与效力层级的对应关系。模型在预训练时见过大量“应当”和“可以”的混用语境微调数据量没大到能纠正这种混淆。解决方法是两条腿走路。第一在指令数据里人为构建“规范词辨析”问答对比如“约定无效与合同无效的区别”“应当与可以的法律后果差异”。第二在后处理层对输出做规范词检查发现“应当”出现在非强制语境时用规则标记并提示生成模块重新生成。规则检查不完美但能拦下大部分低级错误。5.2 现象微调后通用能力崩了回答什么都像在写判决书指令微调做完把模型拉回通用对话场景测试发现它连“帮我写封请假邮件”都回答成“依据《劳动法》第四十条……”。这是领域模型最常见的翻车方式。原因是指令数据里法律问答占比过高模型把“法律论证”当成了唯一任务模式。深层原因是底层预训练分布被微调覆盖丧失了多样性。解决办法是在微调数据里混入 15% 到 20% 的通用指令数据覆盖翻译、摘要、写作、日常对话等任务。同时把法律指令数据的输出样式做多样化处理不要全部使用“分析-依据-结论”三段式。另外检查一下学习率是否过高LoRA 训练中学习率超过 3e-4 会对原始权重造成破坏降到 2e-4 以下通常能缓解。5.3 现象引用法条时编造“第 XX 条”乍看合理实则虚构模型回答“根据《民法典》第五百八十八条违约金过高可以请求适当减少”但这条规定实际并不存在——第五百八十八条确实存在但内容和违约金无关。这是典型的幻觉。原因有两个一是模型对法条的记忆不精确它记得“违约金”和“民法典”经常共现就编织了一条不存在的法条编号二是训练数据中法条原文与解读文本混杂模型分不清哪句是原文、哪句是解读。解决思路分两层。训练层面在指令数据中把法条引用格式统一成“《民法典》第 XXX 条”并单独构造一批法条编号修正问答来强化记忆。推理层面上线时务必外接法条库检索也就是下一章要讲的 RAG。只靠模型记忆无法根治幻觉检索增强才是可信兜底。5.4 现象新旧法冲突答错民间借贷利率按旧标准算测试问“民间借贷年利率 36% 合法吗”模型按 2015 年司法解释回答“合法但超出部分自愿履行”。2020 年修法后上限已调整为 LPR 的四倍。原因是语料时间维度混乱训练数据里包含大量已被废止的旧法条和旧判决模型没有法条生效时间的概念。解决办法是给语料打时间戳。在清洗阶段给每条法规和判决书标注“生效日期”“废止日期”或“裁判日期”。在训练时把时间信息拼进文本前缀比如“【裁判日期2021年】”。推理时在 prompt 里注入当前日期并配合法条版本库做二次校验。市场上有做法律版本管理的数据库选型时优先考虑那些能区分“现行有效”和“已废止”的数据源。5.5 现象显存 OOM 频繁训练一到 4000 长度就爆7B 模型用 LoRA 微调max_length设成 4096 后8 张 80G 的卡照样 OOM。检查日志发现是激活值爆炸。原因是长序列下注意力矩阵和 MLP 激活值按序列长度平方增长4096 长度下显存占用远超 2048 的两倍。解决组合是开启 Flash Attention激活值显存能降一半以上开启梯度检查点用少量计算换大量显存max_length如果业务确实需要 8192就用序列打包把多条短样本拼成一条长样本而不是直接把空白 padding 到 8192否则无效 token 会浪费大量显存。还有一个被忽略的点logging_steps设得太小会导致频繁记录 loss 而触发同步等待造成显存不释放建议设成 50 到 100。6. 进阶用法给 LexiLaw 加检索增强把幻觉压到可商用水平单靠模型参数记住法条无论训多少轮都有天花板。我现在的固定做法是微调模型在前检索增强在外把“记忆”和“查找”拆开。接入方式不复杂。先把现行有效的法规库切条、做 embedding 存入向量库推理时用用户问题检索 Top-K 条文拼进 prompt。Embedding 模型选中文场景表现稳定的 bge-large-zh检索 Top-K 设 5K 太小漏依据K 太大会把不相关条文也塞进上下文。from sentence_transformers import SentenceTransformer import chromadb embedder SentenceTransformer(BAAI/bge-large-zh) client chromadb.PersistentClient(path./law_db) collection client.get_or_create_collection( namelaw_articles, metadata{hnsw:space: cosine} ) def retrieve_articles(question, top_k5): q_emb embedder.encode(question, normalize_embeddingsTrue) results collection.query(query_embeddings[q_emb], n_resultstop_k) return [doc[document] for doc in results[documents][0]]这段代码的重点在后两行query里传的q_emb必须和写入时的 embedding 用同一个模型生成且都做 L2 归一化否则余弦相似度计算会失真。检索结果拼接到 prompt 时要明确告诉模型“只依据检索到的条文回答”并在输出中标注引用了哪一条方便人工核对。这一步能把法条编号错误率从 15% 以上压到 3% 以下。最后分享一个习惯每次微调完我先跑 50 条人工标注的法务压测题按“结论正确率”“法条引用准确率”“是否包含明确免责声明”三个维度打分分数不过 80 就不上生产。哪怕模型结构没变、数据只改了几百条也不跳过这一轮。这套笨办法救了我好几次。希望帮到你。本文还有配套的精品资源点击获取