这两年,“大模型微调”这个词从论文里走进了开发者的日常,但真正亲手把通用大模型变成自己专属助手的程序员其实不多。原因无非三个:不知道数据怎么准备、怕显存不够、以及部署链路太长。我最近用 LLaMA Factory 配合 Ollama 完整跑过几轮项目,可以负责任地说,这条路径对普通开发者已经非常友好了。这篇文章按照我实际操作的顺序,把微调实战拆开讲清楚,从数据准备、参数设置到最终本地部署,全程可以在消费级显卡上完成,适合想做私有化助手、垂直领域问答、角色扮演对话的同学参考。
1. 为什么是 LLaMA Factory + Ollama 这套组合
1.1 先想清楚:微调到底解决什么问题
很多同学一上来就问“我要不要微调”。我一般会反问:你需要的到底是“记住新知识”,还是“学会一种新的表达方式”?
如果你只是想让模型知道你们公司的产品文档,那用 RAG(检索增强生成)就够了,把文档切片、向量化、检索后塞进上下文,成本低、更新也方便。但如果你希望模型在对话时始终保持某种语气、严格遵守某种业务规则、按照固定流程回答问题,比如客服助手必须先用“您好”开头、遇到售后问题必须优先引导退款流程,那光靠提示词是压不住的。这时候微调才有意义。
微调的本质,是让模型在原有能力基础上,针对你的数据分布再做一次定向学习。它并不会把模型变成另一个人,但会让模型在你给的样本空间内表现得越来越像“你想要的助手”。用 LLaMA Factory 做这件事,最大的好处是它把数据格式、训练脚本、多卡支持、量化训练、WebUI 全部封装好了,你不需要自己写 Trainer 循环,也不需要在不同框架之间来回折腾。
1.2 LLaMA Factory 解决了什么痛点
LLaMA Factory 是一个开源的大模型训练框架,名字里虽然带 LLaMA,但实际支持很多主流模型架构。它把微调流程简化成了“准备数据 + 选参数 + 点开始”三个步骤,尤其适合个人开发者和小团队。
我对比过用原生 Transformers 脚本和 LLaMA Factory 的区别。原生的写法需要自己处理数据集类、tokenizer padding、label mask、梯度 checkpointing、混合精度、日志记录,一套代码调下来至少半天。而 LLaMA Factory 把这些都内置了,命令行工具和 WebUI 都只是调参入口,底层基于成熟的训练生态,可靠度足够高。
它还内置了 LoRA、QLoRA、DoRA、全参微调等多种训练方式。对于大多数个人项目,LoRA 和 QLoRA 就是最常用的两条路。LoRA 通过在注意力矩阵上插入低秩适配器,大幅减少可训练参数量;QLoRA 则进一步把基座模型量化到 4bit,显存占用能压缩到 8GB 级别。这意味着普通人手里的显卡也能跑得动 7B 甚至更小参数的模型。
1.3 Ollama 在部署环节的价值
微调完模型只是第一步,怎么把它变成能对外提供服务的东西,往往是更麻烦的一步。Ollama 在这里的价值,是让“本地部署模型”变成一条命令的事。
ollama serve 启动后,它会在本地暴露一个兼容 OpenAI 接口的服务,你可以直接通过 HTTP 请求来对话。模型的管理也非常简单,ollama list 查看本地模型,ollama pull 拉取外部模型,ollama create 用于导入自定义模型,全程不需要手写推理服务、不需要维护 GPU 进程、不需要处理并行请求。
我用 Ollama 最大的感受是:它把“模型文件”和“运行环境”做了一个很干净的隔离。你只要把一个微调好的模型转成 GGUF 格式,再写一个很小的 Modelfile,就能让模型跑在统一的运行时里。后面不管是做命令行测试、接 Web 后端还是接客服系统,都非常顺手。
1.4 完整流程先混个脸熟
为了不让你在后面的细节里迷路,我先把整体链路摆出来:
原始语料 → 数据清洗 → 构建 Alpaca 格式数据集 → 放在 LLaMA Factory 的 data 目录并注册 → 命令行或 WebUI 发起 LoRA/QLoRA 训练 → 训练完成后导出合并权重 → 转换成 GGUF 格式 → 编写 Modelfile → 使用 ollama create 导入 → 通过 ollama run 或 API 测试。
这个流程看起来长,但每一步都有现成工具支撑。我自己第一次跑通时,从零开始大约用了四个小时,其中大部分时间花在数据准备和参数调试上。后面的项目如果数据已经是现成的,半小时就能完成训练到部署。
2. 准备阶段:硬件、软件与数据集
2.1 硬件门槛到底有多高
先说结论:一张 8GB 显存的 NVIDIA 显卡,配合 QLoRA,就能微调一个 7B 参数的中文模型;如果你只有 CPU,也能跑,但训练速度会比较感人,适合简单实验。
具体来说,微调 7B 模型时,模型权重、梯度、优化器状态、中间激活共同决定显存占用。QLoRA 把基座模型量化到 4bit 后,模型权重本身只需要 4GB 左右,额外加上 LoRA 适配器参数和激活值,8GB 显存可以勉强放下,配合梯度检查点后效果更好。如果你有 12GB、16GB 显存,那就能比较从容地调整批次大小和序列长度。
Mac 用户也别慌,M 系列芯片可以直接用 CPU/GPU 跑小模型,4bit 量化后的 7B 模型推理速度还不错,训练虽然慢,但 1000 条以内的数据也能接受。我不建议新手一上来就追求 14B 或更大模型,先用自己的数据在小模型上跑通链路,比什么都重要。
2.2 安装 LLaMA Factory
安装过程没有什么黑魔法,建议用 conda 隔离环境,避免和项目依赖打架。示例命令如下:
git clone https://github.com/hiyouga/LLaMA-Factory.git cd LLaMA-Factory conda create -n llama_factory python=3.10 -y conda activate llama_factory pip install -e .[torch]如果 GPU 是 NVIDIA 且显存比较紧张,推荐装带 flash attention 的版本,会减少显存占用和加速训练。具体可以看项目 README 里的安装说明。安装完成后,命令行里会有 llamafactory-cli 命令,这是后续操作的主要入口。
这一步常见的坑是 torch 版本和 CUDA 版本不匹配。我的习惯是先单独安装好 torch,确认 GPU 能被识别,再安装框架依赖,这样排查问题会容易很多。
2.3 数据集准备:Alpaca 格式与数据质量
LLaMA Factory 默认支持多种数据集格式,最常见的是 Alpaca 格式。一条样本有两个关键字段:instruction 表示指令,output 表示期望输出。如果涉及上下文材料,可以加 input 字段;如果需要多轮对话,可以用 conversations 字段。
我举一个单轮问答的例子,文件格式是 JSON,每行一条:
{"instruction": "请介绍一下贵公司的退款政策", "input": "", "output": "我们支持7天无理由退款,但定制类产品除外。您可以在订单页面提交申请,审核通过后1-3个工作日到账。"} {"instruction": "我的订单已经发货,如何修改收货地址", "input": "", "output": "订单发货后地址无法直接修改,建议您联系客服并提供新地址,我们会尝试联系物流拦截。若拦截失败,您可以在包裹送达后拒收,系统会自动安排退款。"}放好数据文件后,需要把数据集登记到 data/dataset_info.json 里。比如文件名叫 my_qa.json,就添加如下条目:
{ "my_qa": { "file_name": "my_qa.json", "columns": { "prompt": "instruction", "query": "input", "response": "output" } } }数据质量方面我强调几点:第一,每条样本的业务答案必须准确,因为模型学的是你的答案,不是网上的通用知识;第二,要有足够的多样性,不能一百条里都是退款问题;第三,要避免敏感内容和隐私信息,模型会把这些内容学进去。我一般会拿 50 条种子数据先人工跑一遍,确认标注口径统一,再扩大规模。
2.4 基座模型怎么选
选基座模型的核心原则是:中文任务优先中文语料强的模型,代码任务优先代码能力强的模型,英文场景可以选欧美的开源模型。如果显卡显存一般,7B 参数左右是性价比最高的区间,既保留了较强的语义能力,又不会让显存爆掉。
这里要特别注意用 Base 模型还是 Instruct 模型。Base 模型没有经过对话指令微调,需要你用大量对话数据来“教会”它对话格式,对数据量和质量要求较高。Instruct 模型已经具备聊天能力,你继续微调是让它适配你的业务风格,通常更容易出效果。
我个人的推荐:个人项目数据量少于 5000 条时,使用指令微调过的开源模型作为起点,把训练重点放在风格迁移和规则学习上;如果数据量足够大,又希望模型彻底去除通用对话痕迹,再考虑从 Base 模型开始。
3. 微调实操:参数是灵魂
3.1 命令行训练还是 WebUI
LLaMA Factory 提供了两种操作方式:WebUI 适合第一次尝试的人,页面里能直观地选择模型、数据集、训练参数,还能实时看到损失曲线;命令行适合需要批量复现和自动化流程的情况。
我前两次用的 WebUI,后来切换到了命令行,原因是我经常需要记录参数组合,命令可以完整保存到脚本里,比页面截图方便。WebUI 的启动方式也很简单:
llamafactory-cli webui启动后在浏览器里按表单填写即可。下面我以命令行方式演示一次完整的 LoRA 训练,这是最核心的环节。
3.2 一次命令搞懂核心参数
假设你已经把基座模型下载到本地目录,路径为 /data/base_model,数据集名为 my_qa,训练命令可以写成这样:
llamafactory-cli train \ --model_name_or_path /data/base_model \ --dataset my_qa \ --template auto \ --finetuning_type lora \ --lora_rank 8 \ --lora_alpha 16 \ --output_dir outputs/my_lora \ --num_train_epochs 3 \ --per_device_train_batch_size 2 \ --gradient_accumulation_steps 4 \ --learning_rate 2e-4 \ --max_seq_length 2048 \ --bf16逐个看关键参数:
lora_rank 是低秩矩阵的秩。秩越高,可学习的参数越多,模型就越容易记住训练集中的细节,但也越容易过拟合。8 到 32 是常见范围,第一轮先用 8 是比较稳的。
lora_alpha 是缩放系数,通常设置为 lora_rank 的两倍左右。它和 lora_rank 的比例会影响实际学习幅度,16/8 的组合在多数场景下表现均衡。
per_device_train_batch_size 是每张卡的批次大小。显存不够就往下调,比如 1 或 2。gradient_accumulation_steps 用来“虚拟地”增大批次大小,每次累积若干个梯度步再更新一次参数。上面命令里 batch size 2 + 累计 4 步,等效 global batch size 为 8。
learning_rate 是微调里最值得调的参数。LoRA 通常使用 1e-4 到 5e-4,比全参微调的 1e-5 到 3e-5 高一截,因为只更新少量参数。如果你的模型是 Base 模型,建议从 1e-4 开始;如果是指令模型,2e-4 到 3e-4 比较常见。
max_seq_length 控制最大输入长度。训练数据如果普遍在几百字左右,没必要设置得太长,2048 够用,太长会明显增加显存占用。
bf16 是混合精度开关。在较新的 GPU 上推荐优先使用;显卡不支持 bf16 时改用 fp16。Ampere 架构之前的卡对 bf16 支持不好,需要留意。
3.3 训练过程中盯哪些指标
训练启动后,终端会不断打印 loss 值。前几个 step loss 快速下降是正常的,后面会在一个区间内波动。我一般会把日志保存下来,训练结束后回看“最终 loss”和“验证集 loss”。
如果训练集 loss 降得很快,但验证集 loss 开始回升,说明过拟合了。解决思路是减少 epoch、增大数据量、降低 lora_rank,或者加入早停策略。如果训练 loss 一直下不去,可能是数据格式问题、学习率太高,或者模型输出长度和标签长度不匹配。
LLaMA Factory 默认会保存多个 checkpoint 到 output_dir。你可以每隔一段时间中断训练,加载最新 checkpoint 样例测试,不必等全部跑完。这个“先测再练”的习惯能帮你少走很多弯路。
3.4 模型导出:合并 LoRA 权重
训练完成后,outputs/my_lora 里保存的是 LoRA 适配器权重,它还不能直接部署,需要和基座模型合并,导出一个完整的模型文件。
llamafactory-cli export \ --model_name_or_path /data/base_model \ --adapter_name_or_path outputs/my_lora \ --template auto \ --finetuning_type lora \ --export_dir /data/my_merged_model \ --export_size 5 \ --export_legacy_format falseexport_dir 就是合并后的模型目录,可以直接用 Transformers 加载。如果你用的是 QLoRA 训练的模型,导出的同样可以合并回原始精度,不需要担心量化残留问题。
导出的模型是 HuggingFace 格式,Ollama 不能直接加载,所以下一步要转换成 GGUF。我习惯了用 llama.cpp 提供的转换脚本,建议单独建一个工作目录:
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp pip install -r requirements.txt python convert_hf_to_gguf.py /data/my_merged_model --outfile /data/my_merged.gguf --outtype f16这样会先生成一个 fp16 的 GGUF 文件,文件体积可能接近 14GB。如果我们希望部署时轻量一些,再做一次量化,生成 4bit 文件:
./llama-quantize /data/my_merged.gguf /data/my_merged_q4_k_m.gguf q4_k_m如果你的环境里没有编译过 llama.cpp,可以先只编译 quantize 部分,或者用系统里已有的 llama-quantize。这一步得到 q4_k_m 文件后,就轮到 Ollama 登场了。
4. 用 Ollama 把模型变成可对话的服务
4.1 Ollama 安装与基本操作
Ollama 的安装非常简单,到官网下载对应的安装包,或者用一键脚本安装。Linux 服务端场景下,安装完成后先确认服务在跑:
ollama serve新开一个终端,查看本地模型列表:
ollama list如果列表为空,说明还没有导入任何模型。Ollama 也支持直接拉取社区里的模型,但对于微调后的专属模型,我们需要本地导入。
4.2 Modelfile 编写与导入模型
Ollama 导入自定义模型的载体是 Modelfile,它定义了模型文件位置、对话模板和推理参数。为刚才的 GGUF 文件写一个 Modelfile:
FROM /data/my_merged_q4_k_m.gguf TEMPLATE """{{- if .System }}<|im_start|>system {{ .System }}<|im_end|> {{- end }}<|im_start|>user {{ .Prompt }}<|im_end|> <|im_start|>assistant """ SYSTEM """你是小助手,请用简洁、专业的中文回答用户问题。""" PARAMETER temperature 0.7 PARAMETER top_p 0.9TEMPLATE 里的尖括号格式必须和基座模型原来的对话模板保持一致。如果基座模型用了其他模板,比如包含特殊分隔符的模板,就直接替换成那套模板,否则模型输出的格式会变得非常奇怪。SYSTEM 是系统提示词,可以根据业务需求调整。
然后执行导入:
ollama create my-assistant -f Modelfile无报错后,ollama list 里就会出现 my-assistant。
4.3 对话测试与 API 接入
命令行直接测试:
ollama run my-assistant --verbose输入几个和训练数据类似的问题,看回答风格是否贴近预期。确认没问题后,就可以接 API 了。ollama serve 默认监听 11434 端口,并且兼容 OpenAI 的接口格式。
curl http://localhost:11434/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{"model": "my-assistant", "messages": [{"role": "user", "content": "退款需要多久到账?"}]}'Python 里也可以直接用 requests:
import requests resp = requests.post( "http://localhost:11434/v1/chat/completions", json={ "model": "my-assistant", "messages": [{"role": "user", "content": "退款需要多久到账?"}], "temperature": 0.5 } ) print(resp.json()["choices"][0]["message"]["content"])这样你的专属助手就已经具备对外服务能力了。后续无论是做前端聊天框、客服系统还是内部工具,只需要调用这个接口即可。
5. 常见问题与排查实录
5.1 显存不够怎么办
显存不足是最常见的问题。优先做三件事:第一,开启 QLoRA,用 4bit 量化基座模型,显存占用能下降一半以上;第二,开启 gradient checkpointing,以少量训练时间换显存;第三,调小 per_device_train_batch_size 和 max_seq_length。
如果 8GB 显存依然爆,可以考虑把模型换成 3B 或 1.5B 参数,然后在数据质量上下功夫。小模型在垂直任务上不一定比大模型差,前提是你的数据分布足够集中。
5.2 数据集格式报错
很多新手会踩 dataset_info.json 配置坑。如果训练时提示找不到数据集字段,先检查三处:JSON 文件是否每一行都是合法 JSON;dataset_info.json 中的 columns 是否和 JSON 字段名一致;数据集中不能有空字符串或 null 值。我自己碰到过处理导出的 CSV 时把数字写成了整数,导致模型把输出当成 token id,这个一定要避免。
5.3 中文能力变差或胡言乱语
微调后模型开始说胡话,通常是学习率过高或数据量过少。你可以把 learning_rate 从 2e-4 降到 1e-4,同时增加 epoch 数时要谨慎。还有个常见原因是训练数据里混入了大量超出模型能力的复杂问题,模型学不会就开始乱编。建议先人工喂几条简单问题确认模型能稳定回答,再慢慢增加难度。
5.4 Ollama 导入后回答格式错乱
如果导入 Ollama 后回答缺少系统提示、出现多余特殊符号,十有八九是 TEMPLATE 和模型原模板不一致。解决办法是回到微调前的模型,把原始 tokenizer 里的 chat_template 打印出来,照着写进 Modelfile。另外,temperature 和 top_p 设置过高也会导致回答飘,可以先调成 0.3 和 0.8 再观察。
5.5 如何判断过拟合了
想尽早发现过拟合,最好把数据集分成训练集和验证集。LLaMA Factory 支持按比例切分,训练日志里也会输出验证集指标。如果训练 loss 持续下降,验证集 loss 却不降或反弹,就要考虑降低 lora_rank、减少 epoch、增加数据多样性。微调不是“学得越狠越好”,模型的通用能力如果被覆盖得太多,反而会显得呆板。
6. 实操心得与几个进阶玩法
6.1 数据质量比数量重要
做过三轮微调之后,我最大的体会是:500 条高质量数据的效果,可能比 5000 条粗糙数据还好。高质量意味着答案准确、格式统一、覆盖典型场景。我见过身边的朋友为了凑数量从网上爬了一堆问答,结果模型学到了大量错误信息和重复句式,越调越差。数据清洗和人工抽检不是浪费时间,而是整个项目里性价比最高的步骤。
6.2 参数调试的几个小技巧
第一,先用 50~100 条数据跑一个极短的训练,比如 0.1 个 epoch,确认链路没问题,再上全量数据。第二,每次实验只改一个参数,并把参数记录到表格里,否则你很难判断到底是哪个修改起了作用。第三,尽量固定随机种子,保证不同实验之间可比较。第四,训练结束前,在验证集上多试几个和业务强相关的问题,不要只看 loss。
6.3 后续可以怎么扩展
如果你的专属助手已经稳定,可以再往前走一步:把多轮对话历史接进 API,让模型具备上下文记忆;结合外部知识库做 RAG,让模型既能保持业务风格,又能回答实时信息;还可以在 Modelfile 里调整 system prompt,快速切换客服、顾问、创作助手等不同人设。
我个人比较推荐的做法是:先做一个最小闭环,也就是“小数据微调 + 本地 Ollama 部署”,把训练、导出、导入、API 调用这条链路跑通。跑通之后,你自然知道下一步该优化数据还是优化参数,也更有底气去尝试更大的模型。微调这件事,最大的门槛其实不是技术,而是动手做的决心。照着这个流程试一试,你也能拥有一个真正属于自己的 AI 助手。