过去两年,我一直在干一件事:不满足于只当大模型 API 的搬运工,而是把一套 LLM 从预训练一路带到领域适配,再装进自己项目里跑稳定。最初这个选择看起来有点傻,毕竟市面上现成模型可以直接调。但当你反复遇到同样的问题——行业文档里的事实持续变化、领域问答永远带着通用模型的泛泛腔、以及最终必须部署在某种受控的本地环境里——你会意识到,自己掌握从数据到权重的整条链路,不是可选项,而是刚需。
这篇文章不是教你复现一篇预训练论文,而是把我按个人开发者资源条件走完“数据准备—继续预训练—指令微调—知识增强—部署评测”全流程的决策逻辑、操作细节和踩坑点讲透。适合已经跑通几个开源大模型 demo,但还想更进一步的人。
1. 个人开发者为什么需要一套“全流程”的底层逻辑
1.1 从“调用者”变成“流程掌握者”
大多数个人开发者的起点,是从调用 API 开始的。输入 prompt,拿回文本,封装成产品。这个阶段其实没什么问题,很多成功应用就是这么起来的。但当你开始做垂直场景,问题会接踵而至:API 不能改权重,prompt 工程做到极限也压不住模型在专业名词上的幻觉;数据要出域,你又不能把内部文档反复往外送;更麻烦的是,某个行业里的事实更新非常快,模型内部固化下来的知识永远慢半拍。
我说的“全流程”,不是要求每个人都去复现一个几百亿参数的预训练实验,而是建立一套从数据、训练到交付的完整掌控力。权重在自己手里,后面所有领域适配动作才是可叠加的、可回滚的、可评测的。这也是开源社区里大量关于 llm 的资料最近被反复整理成 wiki 知识库的原因——所有人都意识到,模型本身会持续迭代,但掌握流程的能力不会过期。
1.2 先把“预训练”和“领域适配”落到具体动作上
在开始之前,我建议把几个术语彻底对齐,因为它们经常被混着用。
- 预训练(Pre-training):在海量通用语料上做自监督学习,让模型学会语言规律和世界知识。对个人开发者来说,从零预训练一个模型通常不现实,这一步的产出可以直接借用开源基座。
- 继续预训练(Continual Pre-training):在已有基座模型基础上,用领域语料继续训练,让模型吸收特定的行业知识、术语体系和写作风格。
- 领域适配(Domain Adaptation):一个更大的概念,包含继续预训练、指令微调、偏好对齐,也可能配合检索增强和知识库,把通用模型改造成某个具体业务里真正好用的模型。
我一直跟朋友强调一个观点:个人开发者的“从预训练到领域适配”,现实路径其实是“选一个开源基座 → 做领域继续预训练 → 通过指令微调让模型变得听话 → 再用外部知识库解决时效性和专有知识问题”。这里面每一步都可以独立执行,但串联起来的整体设计能力,才是最值钱的。
1.3 我实际跑通的全流程链路
我在这套实践里反复使用的链路大致是:
- 语料工程:清洗、去重、配比领域数据,留出评测集。
- 基座模型选择:根据语言能力、上下文长度、许可证和生态选择底座。
- 继续预训练:让模型吸收领域知识(这一阶段有时可以跳过,取决于数据量)。
- 指令微调(SFT):用高质量的指令数据训练模型在具体任务上的表现。
- 偏好对齐(可选):在有配对偏好数据时用 DPO 等手段调整模型的输出偏好。
- 知识库 + RAG:把更新频繁、精确性要求高的信息放到外部检索链路里。
- 量化部署与评测:把模型压缩到能跑的硬件上,建立持续回归评测。
这个链路听起来步骤很多,但每一步的产出物很清晰:语料文件、模型权重、指令数据集、外部知识索引、量化后的模型包、评测报告。只要每一步都有明确的输入输出,整个过程就是可管理、可迭代的。
2. 上手前先把账算清楚:算力、数据与工具链
2.1 消费级显卡能做到什么程度
先解决最现实的问题:硬件到底要什么水平。我自己日常手上的消费级显卡是 24GB 显存,这个配置足够覆盖个人全流程的大部分场景。
- 24GB 显存:可以跑 7B ~ 8B 模型的 QLoRA 微调,也能用 4-bit 量化做继续预训练的小步长实验,推理部署 7B 模型时余量很充足。
- 16GB 显存:主要用于 7B 模型的量化推理,以及 3B ~ 4B 模型的微调;继续预训练会比较吃力。
- 48GB 及以上:如果是双卡 24GB 或单卡 48GB,基本可以把 13B 模型的 LoRA 微调纳入考虑范围,推理部署的可选范围也宽很多。
训练显存占用的估算逻辑并不复杂:权重 + 梯度 + 优化器状态 + 激活值。7B 模型全参数微调光优化器状态就是大头,所以个人场景我建议优先用 QLoRA。它的思路是把底座冻结成 4-bit 精度,只训练插入的低秩适配器,显存需求瞬间降一大截。
没有本地显卡也不必卡在这一步。现在云上按小时租卡的方案已经很成熟,把训练任务打包上去,跑完再释放,比买卡灵活得多。我的经验是:高频小实验在本地做,大规模正式训练再上云端资源,性价比最高。
2.2 数据比算力更值得先花时间
很多人一上来就问“我用什么显卡”,但真正决定项目生死的其实是数据。我在这套流程里有一个非常明确的排序:评测集设计 > 训练数据构造 > 模型选型 > 参数调整。
刚开始做领域适配的人最容易犯的错误,是直接收集一堆相关文档就开训,训完才发现模型变“油”了,但具体问题回答得对不对劲完全说不上来。所以我建议动手之前先做一件事:从真实业务场景里挑出 100 到 200 个问题,做成基准测试集。这些问题不用多,但必须覆盖最常见的任务类型和最容易出错的知识点。训练前后都跑一遍这组问题,效果好坏一眼就知道。
数据量也不是越大越好。领域继续预训练时,两三 GB 左右的干净领域语料已经足以让模型明显“变味”;指令微调更夸张,几千条高质量的“问题—答案”对都能看到行为变化。关键是质量、代表性和与真实任务的贴近度。
2.3 工具链选型:训练框架与推理框架分开看
全流程的工程环节很多,工具链我会按训练、推理和辅助工程三条线来配。主流 llm 框架这几年成熟得非常快,个人开发者完全没必要自己造轮子。
| 环节 | 常用工具/框架 | 我选择它的理由 |
|---|---|---|
| 数据清洗 | pandas、datasets、自写脚本 | 灵活,适合处理非结构化文本 |
| 继续预训练/SFT | Transformers + PEFT、TRL、LLaMA-Factory | 生态好,LoRA/QLoRA 支持度高,改动少 |
| 偏好对齐 | TRL 里的 DPOTrainer | 不需要自己写强化学习环境 |
| 向量检索 | Chroma、Milvus、FAISS | 轻量起步用 Chroma,量大换 Milvus |
| 推理部署 | llama.cpp、Ollama、vLLM | 单机 Ollama,高并发 vLLM |
| 实验追踪 | wandb / TensorBoard / 本地 CSV | 训练指标、评测结果都要留档 |
两个容易忽略的小建议:一是把训练环境做成一次性容器或脚本,避免依赖漂移;二是每个阶段的模型检查点都导出到一个统一目录,命名带上日期和数据版本,不然过两周你自己都分不清哪个权重用了哪套数据。
3. 预训练阶段,真正的坑都在数据准备里
3.1 基座模型选择:不要只看参数规模
继续预训练的第一步不是准备数据,而是选底座。我在实际项目里判断基座模型主要看四个方面:
- 语言与领域贴合度:如果你的业务以中文为主,优先选择中文语料占比高、中文评测表现稳定的开源基座,而不是拿英文模型硬改。
- 上下文长度:领域文档往往很长,需要模型能处理足够长的上下文。至少要 8K 以上,低于 4K 的底座用在文档问答里会很痛苦。
- 版权风险控制:我在选择时会避开数据来源不清晰的模型,尽量使用协议清晰、允许商用修改的开放权重模型。
- 社区生态:生态好的模型意味着踩坑时能找到大量现成方案,微调工具、量化脚本、部署样例都很齐全。
一个常见误区是“模型越大越好”。个人开发者拿 70B 级别模型做领域适配,光显存和推理速度就够喝一壶。7B 到 14B 这个区间才是性价比最合适的甜点区:能力足够支撑复杂任务,单卡又能跑得动。
3.2 数据清洗、去重与配比的操作细则
继续预训练里我最想强调的,是数据工程远比训练命令本身复杂。所谓“领域语料”,从网上爬下来之后通常是混乱的:有 HTML 标签、重复段落、编码乱码、无意义字符。不洗干净就喂给模型,轻则指标抖动,重则学到一堆错误格式。
我的清洗流程大致是:
- 格式还原:把 HTML 标签、markdown 标记、无意义符号全部剥掉,只留正文。
- 编码检查:处理乱码字符和异常 Unicode,否则训练时会出现大量无效 token。
- 精确去重:先跑一遍全文 MD5 去重,再用 MinHash 做近似去重,把相似度极高的段落筛掉,避免模型对重复内容过拟合。
- 质量过滤:可以用困惑度规则或者关键词黑名单过滤低质内容;更省力的做法是用一个现成的强模型给语料打分,低于阈值的段落直接剔除。
- 配比控制:领域语料与通用语料的比例,我一般控制在 1:3 到 1:5。纯领域语料训练容易出现过拟合和灾难性遗忘,掺入一定量通用数据能保持模型的开放能力。
这里有一个不得不提的细节:分词器对领域新词的处理。很多垂直领域的专有名词在预训练词表里不存在,模型会把它们拆成一串没意义的碎片。解决思路有两个,一是继续预训练时让模型见足够的上下文来“重组”这些碎片,二是数据里反复出现完整术语,让模型逐渐习惯。不要轻易扩词表重训 embedding,个人规模下很容易得不偿失。
3.3 训练控制与指标观察:loss 不是唯一信号
继续预训练的训练强度要比预训练小得多。我的经验是,在高质量领域语料上跑 0.5 到 1 个 epoch 就已经有明显效果,完全不必要重复多轮。
训练时要同时盯住三个指标:
- 领域验证集 loss:这个下降说明模型在吸收领域知识。
- 通用能力验证集 loss:这个如果明显上升,说明出现了灾难性遗忘,需要调整数据配比或降低学习率。
- 学习率曲线:我习惯用一个比较小的峰值学习率,例如 1e-4 到 2e-4 量级,配合同步的 warmup 和余弦衰减。继续预训练不是从零学语言,步子迈太大会把原有能力冲掉。
很多人只看训练 loss 一路下降就觉得万事大吉,但领域 loss 下降和通用能力劣化经常同时发生。所以训练前分离评测数据,训练后马上跑一遍,比任何花哨的可视化都管用。
3.4 一个可复用的继续预训练配置文件思路
在继续预训练阶段,我用过一个比较通用的 YAML 配置结构,核心参数大概是下面这样:
model: base_model: "Qwen/Qwen2.5-7B" # 占位示例,实际按需替换 load_in_4bit: true bf16: true data: train_file: "domain_corpus_clean.jsonl" eval_file: "domain_eval_text.jsonl" max_seq_length: 4096 # 按显卡显存调整 packing: true # 把短文档拼接,减少 padding 浪费 training: learning_rate: 2e-4 num_epochs: 1 warmup_ratio: 0.03 per_device_train_batch_size: 2 gradient_accumulation_steps: 8 save_steps: 500 logging_steps: 20这里有几个值得解释的细节。packing: true意味着把多个短样本拼到一条序列里,训练效率会高很多,但要注意用 attention mask 把不同文档之间的注意力隔开。梯度累积是为了弥补单卡 batch 太小的限制,实际有效 batch 大小等于每卡 batch乘上累积步数,我一般控制在 16 到 64 之间。数据量大时,单 epoch 加一次对数验证是性价比最高的做法。
4. 领域适配的重心不在参数量,而在数据构造
4.1 为什么说 SFT 数据是分水岭
继续预训练解决的是“知识灌入”的问题,但模型不一定知道怎么把知识用在你想要的任务上。指令微调解决的就是这个“行为对齐”问题:让模型看到指令后,能按你期望的格式和口吻作答。
这个阶段我特别反对一个观念:“我的模型有几十亿参数,喂它几万条指令数据,效果一定好。”实际上 SFT 数据的质量直接决定了最终能力的上限。二十万条从网上批量生成的低质指令数据,可能不如五千条认真撰写、覆盖全面、经过人工校验的样本。因为 SFT 不只是教模型知识,更是在教模型“在什么情况下做什么反应”,杂乱无章的数据会让模型学到错误的触发条件。
我自己的数据构造原则是:宁可少,不可脏。每一条指令数据都要明确是什么任务、输入是什么、期望输出是什么、边界在哪里。这样训练出来的模型行为才可控。
4.2 指令模板、多轮对话与负样本的设计
指令数据的组织方式,直接影响模型的稳定程度。我统一使用 ChatML 风格的对话模板,把角色和内容结构化成结构化消息:
<|im_start|>system 你是某个垂直领域的智能助手,回答需要准确、简洁、有依据。 <|im_end|> <|im_start|>user 某某产品的保修政策是什么? <|im_end|> <|im_start|>assistant 根据产品文档,该产品的保修期是... <|im_end|>模板的好处是明确区分系统、用户、助手三种角色,模型对上下文边界非常清楚。训练时所有指令数据都必须保持完全一致的模板,千万不能混用,否则模型会在生成时把格式搞乱。
多轮对话数据也很重要。真实用户在问答时会追问、会纠正、会改变话题,单轮指令数据训练出来的模型往往接不住多轮上下文。我通常会在数据集中保留 30% 左右的多轮对话样本,每轮都带清晰的任务意图。
容易被忽略的是负样本。所谓负样本,就是告诉模型“当信息不足或超出知识范围时,要明确拒绝或承认不知道”。很多通用模型幻觉严重,就是因为在训练阶段被强行要求永远回答,没有学会“不知道也是一种正确回答”。我在 SFT 数据集里会特意加入 5% 到 10% 的拒答样本,例如:
某产品在某个非公开场景下的具体参数是多少? assistant: 该场景属于内部非公开信息,我无法给出准确参数,建议你查阅内部文档或咨询相关负责人。刚开始构造指令数据时,可以用人工方式把领域文档改写为问答对;量上去之后,可以借助基础模型做合成数据——让模型依据现有文档的顺序生成候选问答,再做人工或规则过滤。合成数据是手段,不是目的,最终所有训练数据都必须经过质量和合规性的检查。
4.3 微调策略选择:全参、LoRA 还是 QLoRA
数据准备完毕之后,才是微调策略的选择。三种主流路线各有适用场景:
| 方案 | 显存需求 | 训练速度 | 效果上限 | 我的使用场景 |
|---|---|---|---|---|
| 全参数微调 | 极高 | 慢 | 理论上限最高 | 数据量很大、领域语言风格差异显著时 |
| LoRA | 中 | 中 | 接近全参 | 通用领域适配首选 |
| QLoRA | 低 | 较快 | 略低于 LoRA | 24GB 及以下显存的主力方案 |
我的默认选择是 QLoRA:把基座冻结成 4-bit,只训练插入的 LoRA 适配器。LoRA 的秩和缩放系数我一般设置得比较保守,r=16,alpha=32,dropout=0.05。秩不是越大越好,过大的秩会引入更多噪声,反而容易过拟合训练集。
训练超参方面,我的经验值参考如下:学习率在有监督微调阶段一般用 1e-5 到 2e-5,要比继续预训练小一个数量级;训练轮数 2 到 3 轮,数据少就减轮、数据多也别超过 5 轮。SFT 阶段的最优轮数往往比较敏感,可以留一个验证集,每隔几百步测一次领域指标,找到峰值就提前停止。
4.4 偏好对齐这一步的现实取舍
偏好对齐指的是让模型学会“哪个回答更好”的建模过程。按理说这是完整领域适配的一部分,但我对个人开发者的建议是:可选项,不是必选项。我在项目里通常先看 SFT 结果,如果模型输出已经符合要求,就直接跳过偏好对齐。
需要引入偏好对齐的典型信号是:模型输出在正确性上没有大问题,但“语气、体例、详细程度”不符合业务偏好,或者同一类问题回答风格不稳定。如果你决定要做,DPO 是比 RLHF 现实得多的方案。它的核心思想是让模型学习“更喜欢的回答”和“不太喜欢的回答”之间的差异,不需要搭建复杂的强化学习环境,更贴近个人场景。
DPO 需要偏好数据,一般是同一问题下的“好回答”和“次回答”配成对。1000 对起量就能看到效果,质量比数量重要得多。如果只是为了对齐风格,几百对也够用。我的原则是:没有清晰偏好数据就宁可不做,为了流程完整性强行加偏好阶段,反而可能把稳定的 SFT 结果搞乱。
5. 微调不是万能的,知识库与 RAG 要补在正确的位置
5.1 微调失效的典型场景
我必须坦白一件事:在个人项目里,微调解决不了的场景比能解决的多。最典型的几类:
- 信息更新太快:文档每个月都在改版本,每次改版本都重新微调一遍模型,成本和延迟都受不了。
- 要求精确可溯源:很多业务问答需要“引第三段第一句话”这种明确来源,参数记忆根本做不到这一点。
- 知识零散且量大:几千份本地文档全塞进权重里,模型记不住,还容易互相污染。
RAG 的定位不是微调的替代品,而是互补。微调负责让模型“会做事”,RAG 负责让模型“有资料可用”。两者配合,才能兼顾行为和知识两件事。
5.2 个人 wiki 知识库的完整落地路径
我实践得最多的一块,就是把个人和团队的文档体系整理成一个 wiki 知识库,然后接进 RAG 链路。流程大致是:
- 解析与清洗:把 Markdown、PDF、Word、网页文档全部解析成纯文本,按标题结构拆分章节,保留元信息。
- 分块:每块长度我控制在 300 到 800 个 token 之间,块间重叠 50 到 80 个 token。块太短会导致上下文不全,太长会稀释检索相关度。
- 向量化:选择一个对中文友好的 embedding 模型,把每个块编码成向量索引。
- 存储检索:个人规模用 Chroma 起步,量大了换 Milvus。检索时取 TopK 候选,再用阈值过滤掉相关度太低的片段。
- 重排:初检索结果往往不够精确,我通常会在中间加一个重排层,用交叉编码器对 TopK 候选重新打分,把最相关的三五个片段送到模型手里。
链路搭起来之后,知识库的更新就变成了一件事:新文档进库、重新分块、增量索引。不需要重新训练模型,也不影响已训练好的行为习惯。这也是为什么我一直建议把“外部知识”和“参数知识”分层管理。
5.3 从向量检索到混合检索,再到 GraphRAG 的取舍
很多人在 RAG 上容易一上来就追求高大上的方案,我建议从向量检索开始跑通闭环,再逐步加复杂度。
向量检索的盲区也很明确:专有名词、缩写、实体关系,语义相近但文本形态完全不同的情况,单靠向量很容易漏。我的做法是走混合检索:同时跑关键词 BM25 和向量召回,再用 RRF(Reciprocal Rank Fusion)或者简单的分数加权把两路结果合并。这个组合对绝大多数个人知识库场景都够用。
GraphRAG 最近讨论很多,很多方案试图用知识图谱把实体和关系结构化,再结合向量检索做回答。我的判断是:这个方向有前景,但个人规模默认不必上。除非你的数据实体关系极强,比如本地 ERP 里的产品检索、零部件参数关联这种场景,值得构建一个轻量本体/图谱来提升精确召回;否则,普通业务文档先用混合检索,已经能解决大部分问题。先做基础,再加复杂度,永远比一步到位更安全。
6. 部署与评测闭环:让领域模型在本地真正可用
6.1 量化:从精度到显存的取舍
训练完成不等于项目完成,最后一步是把模型部署到实际运行环境。显存不够时,量化是第一选择。
量化的本质是用更低的数值精度表示权重,换取更小的显存和更快的推理。常见选择包括:
| 量化方案 | 显存占用 | 精度损失 | 典型场景 |
|---|---|---|---|
| BF16/FP16 | 高 | 无 | 有足够显存时保底使用 |
| INT8 | 中 | 极低 | 对精度敏感的场景 |
| INT4(GGUF/AWQ) | 低 | 可接受 | 消费级显卡部署的主流方案 |
| 混合精度量化 | 中 | 低 | 关键层保留 BF16,其余量化 |
我在部署时遵循一个原则:能跑 BF16 就不降 INT8,能跑 INT8 就不降到 INT4。量化省下来的显存,应该服务于更长的上下文和更高并发,而不是单纯为了把模型塞进显存。很多领域任务的输出质量对量化很敏感,尤其是指令跟随和格式生成。用量化后的模型跑一遍自己搭的评测集,是最直接的有效性验证。
6.2 推理框架与并发评估
部署框架的选择取决于你的使用形态:
- 单人本地使用:Ollama 或者直接用 llama.cpp,配置极简,一条命令起服务,够用且稳定。
- 偏生产、多用户并发:vLLM 是更好的选择,它对连续批处理优化得很好,能明显提升吞吐。
需要特别注意的是KV cache 显存。输入和生成过程都要缓存历史 token 的键值向量,上下文越长、并发越高,KV cache 占用越大。所以在服务端配置里,要根据最大上下文长度和并发数预留 KV cache 的空间,否则跑一段时间就会 OOM。
一个可参考的容量公式:模型权重显存 + KV cache 显存 + 激活显存余量总和,必须小于总显存。比如 7B 模型 INT4 后权重约 4GB,4K 上下文下单路 KV cache 通常不到 1GB,那么 24GB 显存跑二三十路并发是有余量的。
6.3 领域适配效果评测:给自己建一个回归测试集
终于到了整个流程里最容易被人跳过的部分。很多个人开发者训完模型,随便问两个问题觉得“好像可以了”,就上线了。这样不仅无法判断适配效果,后续改数据、改参数时也完全找不到参照物。
我的评测设计分三层:
- 通用能力回归集:从基座模型原有的能力评测里抽取一部分通用题目,确保领域适配后没有明显退化。
- 领域能力评测集:从真实业务场景里整理 100 到 200 条问题,覆盖知识问答、文档提取、格式生成、多轮对话等典型任务。
- 输出稳定性测试:同一问题跑多次,观察输出的结构一致性、格式正确性和随机波动性。
评测方式上,我倾向于“自动化粗筛 + 人工精评”结合。先用规则或自动指标把明显不合格的答案筛掉,再对模糊地带做人工判断。这里有一个很关键的实践经验:把每次评测中失败的案例都追加到回归集里,下次训练完继续跑,确保老问题不复发。这样你的评测集就会像一个守门员一样,越用越可靠。
部署前后的一致性测试也别忘了。量化后的模型和训练时的 BF16 权重,在同样的 seed 下输出可能有差异,务必用回归集跑一遍量化版本,确认差异在可接受范围内。
7. 走完全流程之后的几点个人体会
整套流程走下来,我最深的体会是:个人开发者的竞争优势不在“我跑得动多大的模型”,而在“我能把一个模型调教得有多贴合自己的业务”。模型底座来自开源社区,大家都能拿,真正拉开差距的是语料工程、数据构造、评测迭代这些脏活累活。
第二个体会是,一定要把训练过程当成实验来管理。我一开始也是随手一个脚本,跑完就忘,结果两周后想复现一个结果,连当时用的数据版本都找不到了。后来我强迫自己给每一项实验记录三个东西:数据版本、训练参数、评测结果。这件事尤其关键,领域适配是一个不断迭代的过程,没有档案就没有迭代的基础。
第三个体会是不贪大。从一个小范围的领域切入,比如只做产品参数问答,把 7B 模型调明白,拿到的经验完全可以迁移到更复杂的场景里。社区里那些被反复整理的 llm wiki 知识库之所以有价值,就是因为它们把零散的训练技巧、工具选型和踩坑记录串成了完整的方法论。照着一套可复用的流程走,比反复换底座、换框架重要得多。
如果你现在刚好站在“想自己动手做领域模型”的门口,我的建议很简单:先把 100 个评测问题写好,再开始准备数据。剩下的路,走一遍就知道坑在哪里了。