文章目录
-
- [1. 先给你们看看最终成品](#1. 先给你们看看最终成品)
-
- [1.1 四个核心产物](#1.1 四个核心产物)
- [1.2 先把定位说清楚](#1.2 先把定位说清楚)
- [2. 我的本地环境家底](#2. 我的本地环境家底)
-
- [2.1 硬件软件配置](#2.1 硬件软件配置)
- [2.2 文件存放思路](#2.2 文件存放思路)
- [3. 整个流程就是一条流水线](#3. 整个流程就是一条流水线)
-
- [3.1 四个核心脚本](#3.1 四个核心脚本)
- [3.2 完整数据流](#3.2 完整数据流)
- [4. 数据清洗:先定好模型该学啥](#4. 数据清洗:先定好模型该学啥)
-
- [4.1 原始数据啥成色](#4.1 原始数据啥成色)
- [4.2 我是怎么筛数据的](#4.2 我是怎么筛数据的)
- [4.3 两个不能省的细节](#4.3 两个不能省的细节)
- [5. LoRA微调:花小钱办大事](#5. LoRA微调:花小钱办大事)
-
- [5.1 LoRA到底是啥逻辑](#5.1 LoRA到底是啥逻辑)
- [5.2 我的参数配置](#5.2 我的参数配置)
- [6. 训练参数是怎么琢磨出来的](#6. 训练参数是怎么琢磨出来的)
-
- [6.1 核心配置拆解](#6.1 核心配置拆解)
- [6.2 为啥要这么设](#6.2 为啥要这么设)
- [6.3 为啥只训不到半个epoch](#6.3 为啥只训不到半个epoch)
- [7. 训练结果到底咋样](#7. 训练结果到底咋样)
-
- [7.1 耗时和指标](#7.1 耗时和指标)
- [7.2 实际效果啥水平](#7.2 实际效果啥水平)
- [8. 为啥要把LoRA合并进去](#8. 为啥要把LoRA合并进去)
-
- [8.1 LoRA文件不能单独跑](#8.1 LoRA文件不能单独跑)
- [8.2 合并就是一步到位](#8.2 合并就是一步到位)
- [9. 转成GGUF格式干啥用](#9. 转成GGUF格式干啥用)
-
- [9.1 GGUF有啥好处](#9.1 GGUF有啥好处)
- [9.2 转换过程很简单](#9.2 转换过程很简单)
- [10. 怎么塞进Ollama里](#10. 怎么塞进Ollama里)
-
- [10.1 先写个Modelfile](#10.1 先写个Modelfile)
- [10.2 注册模型就完事](#10.2 注册模型就完事)
- [11. Docker部署是怎么玩的](#11. Docker部署是怎么玩的)
-
- [11.1 卷映射是核心技巧](#11.1 卷映射是核心技巧)
- [11.2 端口和GPU配置](#11.2 端口和GPU配置)
- [12. Open WebUI怎么接上去](#12. Open WebUI怎么接上去)
-
- [12.1 配置有个小坑](#12.1 配置有个小坑)
- [12.2 完整访问链路](#12.2 完整访问链路)
- [13. 踩过的那些坑,说多了都是泪](#13. 踩过的那些坑,说多了都是泪)
-
- [13.1 Docker路径格式坑](#13.1 Docker路径格式坑)
- [13.2 上下文Token莫名暴涨](#13.2 上下文Token莫名暴涨)
- [13.3 上下文长度的隐形坑](#13.3 上下文长度的隐形坑)
- [13.4 最容易踩的认知坑](#13.4 最容易踩的认知坑)
- [14. 折腾完这一圈,我真正收获了啥](#14. 折腾完这一圈,我真正收获了啥)
-
- [14.1 值钱的不是模型,是链路](#14.1 值钱的不是模型,是链路)
- [14.2 给新手的真心话建议](#14.2 给新手的真心话建议)
- [15. 接下来打算咋迭代](#15. 接下来打算咋迭代)
-
- [15.1 先把数据质量提上去](#15.1 先把数据质量提上去)
- [15.2 扩充能力慢慢迭代](#15.2 扩充能力慢慢迭代)

P.S. 无意间发现了一个巨牛的人工智能教程,非常通俗易懂,对AI感兴趣的朋友强烈推荐去看看, 传送门https://blog.csdn.net/qq_34419312
前言
最近手痒,想完整走一遍大模型从训练到部署的全流程。
不是调个API那种过家家,也不是下个模型跑两下就完事,是从数据清洗到Docker部署,一条龙全给它跑通。
选了中医领域试水,拿Qwen3-0.6B搞了个试点模型。先把丑话说在前头:这就是个纯学习用的模型,不能看病开药方,别回头有人拿着输出的方子去药店抓药,再来找我报销医药费,我这显卡钱还没赚回来呢。
1. 先给你们看看最终成品
1.1 四个核心产物
折腾完一圈,手里攥着四个东西,各有各的用处。
第一个是LoRA适配器,就40.4兆,相当于给原模型打了个中医补丁,只存了参数差值,不能脱离底座单独跑。
第二个是合并后的完整Hugging Face模型,1.2G左右,把补丁和底座焊死在了一起。
第三个是BF16格式的GGUF文件,专门给本地推理工具用的,单文件便携得很。
最后一个是注册好的Ollama模型,叫wo-x-tcm:pilot,配个网页界面就能直接聊天。
1.2 先把定位说清楚
这不是从零训出来的基础大模型,说准确点,是基于Qwen3-0.6B的中医领域LoRA指令微调模型。
说白了就是给通用模型补了点中医专业课,不是从头培养个老中医。只能用来学习中医文献,但凡涉及医疗决策,一律靠边站,别拿它当在线问诊用。
2. 我的本地环境家底
2.1 硬件软件配置
操作系统是Windows,靠Docker Desktop跑Linux容器。
显卡是RTX 5070,说不上顶配,但折腾个0.6B的小模型完全够用。总不能为了个试点项目直接上旗舰卡吧,钱包第一个不同意。
Python单独开了个conda环境,PyTorch 2.13.0配CUDA 13.0,Transformers、PEFT、TRL这些常用库都拉到了对应版本,避免版本不兼容踩坑。
2.2 文件存放思路
所有大体积文件全塞D盘,C盘那点空间,装个系统都够呛,再塞模型分分钟红盘给你看。
分了四个文件夹各司其职:数据集放一起,模型文件放一起,项目代码放一起,conda环境单独放。
分门别类省得找文件找半小时,训练还没开始,先给自己整emo了。
3. 整个流程就是一条流水线
3.1 四个核心脚本
整个项目核心就四个Python文件,各司其职,一点不乱。
prepare_data.py管数据清洗整理,train_lora.py负责微调训练,evaluate.py做效果对比,merge_lora.py用来合并权重。
就像工厂流水线,一个环节接一个环节,哪个环节出问题,直接定位到对应脚本,不用到处瞎找。
3.2 完整数据流
整条链路顺下来特别清晰:原始数据洗干净,划分成训练验证测试集,丢给底座模型加LoRA训练,产出适配器,合并成全量模型,转成GGUF格式,最后塞进Ollama,用Docker部署,配个网页交互界面。
跟搭乐高似的,一块一块往上拼,拼完还能拆下来调整,灵活得很。
4. 数据清洗:先定好模型该学啥
4.1 原始数据啥成色
原始数据用的是公开的Baize TCM V3,一共十五万七千多条记录,看着量挺大,其实水分不小。
就跟你买了一大包零食,拆开一看一半都是包装袋,真正能吃的没多少。
4.2 我是怎么筛数据的
第一步先卡长度,太短太长的都直接pass。
第二步筛领域,明显不是中医内容的,直接踢出去,一下就干掉了七万多条。
第三步最关键,但凡涉及开药、处方、剂量、选穴位这种行动性医疗问题,全给过滤掉。我可不想训出个上来就给人开药方的"江湖郎中"模型,第一步先学理论,别上来就想着行医。
最后再去重,十五万多条洗下来,只剩三万九出头的合格数据。
首轮实验我也没贪多,就挑了两千条训练,两百条验证,五十条测试。先跑通流程,比啥都强。
4.3 两个不能省的细节
所有合格数据统一转成聊天格式,系统提示词加用户问题加助手回答,模型就照着这个结构学,知道看到问题该输出啥。
随机种子必须固定死。不然每次训练数据集划分都不一样,结果根本没法对比,就跟考试每次换题库,你根本不知道自己进步没进步一样。
5. LoRA微调:花小钱办大事
5.1 LoRA到底是啥逻辑
要是全量微调,所有参数都要算梯度存状态,大模型直接能把显存干冒烟。
LoRA就聪明多了,原模型参数全冻住不动,只训练两组低秩小矩阵。相当于给模型打补丁,改动小,见效快,显存占用还低,典型的四两拨千斤。
5.2 我的参数配置
秩设了16,lora_alpha设32,dropout0.05,注意力层和前馈网络的全量模块都加上了LoRA。
算下来可训练参数才一千万出头,只占总参数的1.6%左右,这点参数对显卡来说完全没压力。
这次用的是普通BF16精度,没上QLoRA。毕竟模型小,显卡扛得住,没必要搞量化徒增麻烦。
6. 训练参数是怎么琢磨出来的
6.1 核心配置拆解
核心训练参数我给你们列一下,都是踩坑踩出来的经验。
最大训练步数120步,单卡批次大小1,梯度累积步数8,算下来有效批次大小是8。学习率1e-4,用余弦调度,最大序列长度512。
打开了assistant_only_loss,也开了梯度检查点,BF16混合精度也安排上。
6.2 为啥要这么设
批次大小设1纯粹是为了省显存,靠梯度累积凑有效批次,属于是显存不够,步数来凑。
只算助手回答的损失更合理,总不能让模型学着复述用户的问题吧,那不成复读机了。
梯度检查点就是用计算换显存,反正显卡闲着也是闲着,多算点总比显存溢出强。整套配置下来,峰值显存才2.3G,离谱的省。
6.3 为啥只训不到半个epoch
两千条训练数据,有效批次8,完整跑一遍要250步。我就训了120步,算下来才0.48个epoch,连半圈都没跑完。
没办法,这就是个试点版本,先验证整条流程能不能跑通。总不能第一次就训个几天几夜,最后发现部署环节出问题,那可真是竹篮打水一场空。
7. 训练结果到底咋样
7.1 耗时和指标
整个训练跑下来花了不到7分钟,喝杯茶的功夫就完事了,速度比我预想的快多了。
训练损失从最开始的2.38降到了1.96,验证损失也稳步往下走,没出现发散的情况。说明训练没崩,模型确实在学东西。
7.2 实际效果啥水平
拿同一批问题分别测了原始模型和微调后的模型,差别还是挺明显的。
微调后的模型说话确实更有中医那味儿,专业术语用得也到位,风格一下就对齐了。但冷门知识点还是会胡说八道,也就是大家常说的幻觉。
说白了就是,你问它基础理论,它能跟你头头是道唠半天;你要是问个偏门的草药学名,它可能当场就给你现编一个,跟酒桌上吹牛皮的人似的,不懂也能给你说的跟真的一样。
所以说,少量SFT只能改个说话风格,真要知识靠谱,还得靠可靠知识库和专家审核。别以为损失降了就是模型学会了,它可能只是学会了怎么装得像学会了。
8. 为啥要把LoRA合并进去
8.1 LoRA文件不能单独跑
训出来的LoRA适配器就40兆,看着小巧便携,但它不能脱离底座模型自己运行。
就像游戏补丁不能脱离游戏本体玩一样,你总不能拿着个补丁文件到处跑,到哪都得先装个底座模型,太麻烦。
8.2 合并就是一步到位
把LoRA学到的参数变化合并到底座模型里,就生成了一个完整的独立模型。
后续转换格式、部署都方便,不用每次都额外加载适配器。代码也简单,几行调用就搞定,合并完直接保存成标准的Hugging Face格式。
9. 转成GGUF格式干啥用
9.1 GGUF有啥好处
Hugging Face格式是一堆零散文件,权重、配置、分词器分开装,传起来、部署起来都麻烦。
GGUF就不一样了,一个文件把所有东西都装进去了,模型张量、结构元数据、词表、聊天模板,应有尽有。特别适合llama.cpp、Ollama这类本地推理工具,拎包就能用。
9.2 转换过程很简单
用llama.cpp自带的转换脚本,一行命令的事。指定好输入的模型目录和输出文件,精度选BF16就行。
这次没做量化压缩,先保持完整精度,等后续再慢慢对比不同量化级别的速度和效果差异。
10. 怎么塞进Ollama里
10.1 先写个Modelfile
Ollama靠Modelfile来定义模型。里面要写清楚GGUF文件的路径,设置生成参数:温度0.3,top_p 0.8,重复惩罚1.05,上下文长度拉到8192。
最后再写上系统提示词,给模型定好规矩,明确它只能用来学习文献,不能做医疗建议。就跟给模型写个员工手册似的,告诉它该怎么干活,什么不能干。
10.2 注册模型就完事
写好Modelfile,一条ollama create命令就搞定注册,给模型起个名字,绑定好配置。
注意啊,这一步不是重新训练模型,就是给模型登个记上户口,以后调用名字就能直接用。很多新手以为这步会训模型,站那等半天,纯纯浪费时间。
11. Docker部署是怎么玩的
11.1 卷映射是核心技巧
很多人用Docker跑Ollama,上来就想把模型拷进容器里,那多傻啊。
直接用卷映射,把Windows本地的目录映射到容器内部,容器直接读本地文件,不用复制来复制去,省时间还省空间。我一开始差点犯傻,想把1个多G的模型拷进容器,后来才反应过来,白瞎我差点等半天传输进度。
GGUF目录和Ollama模型目录分别映射,只读权限给模型文件,安全得很。
11.2 端口和GPU配置
GPU必须直通给容器,不然纯CPU推理能急死人,说句话等半分钟。
端口也做了映射,本地原生Ollama用11434,Docker版用11435,互不干扰,想用哪个用哪个。
12. Open WebUI怎么接上去
12.1 配置有个小坑
Open WebUI也直接跑在Docker里,不用单独装,特别省事。
配置的时候要注意,环境变量里的Ollama地址,得写Docker内部的服务名,不能写127.0.0.1。不然它找的是自己容器里的地址,根本连不上Ollama。很多人第一次配都栽在这。
12.2 完整访问链路
浏览器访问本地3000端口就能打开界面。请求链路也很清晰:浏览器发请求给Open WebUI,它通过Docker内部网络转发给Ollama,最后交给显卡推理,结果再原路返回。
用起来跟在线大模型没啥区别,但是所有数据全在本地,安全感直接拉满。
13. 踩过的那些坑,说多了都是泪
13.1 Docker路径格式坑
Windows的路径是反斜杠,容器里是Linux的正斜杠,同一个文件,两边的路径写法完全不一样。
我一开始图省事,直接把Windows的路径写进Docker用的Modelfile里,结果死活找不到文件,排查了半天才反应过来。合着俩系统路径格式不一样,白折腾半天。
13.2 上下文Token莫名暴涨
有次我就输了"咽喉痛吃什么"五个字,结果界面显示输入快六千Token。我当时都懵了,难道我输入的是无字天书?
后来才发现,是Open WebUI偷偷给我塞了一堆内置工具的定义,知识库、搜索、日历、笔记啥的,全算进上下文里了。合着我聊个天,后台还带了个工具人团队是吧,我没叫它们啊,自己就跑进来占位置了。
解决办法也简单,给模型建个专属预设,把内置工具关了,瞬间就清净了。
13.3 上下文长度的隐形坑
Qwen3-0.6B本身支持四万多Token的上下文,但Ollama默认只给4096。
一开始我输入长点的内容就报错,还以为模型不行,后来才发现是配置没改。把Modelfile里的num_ctx调到8192,重新注册模型,立马就好了。这个参数藏得深,很多人容易忽略。
13.4 最容易踩的认知坑
很多人觉得训练损失一直降,模型就越来越厉害。大错特错!
损失下降只能说明模型越来越会模仿训练数据里的回答,不代表答案就是对的。训练数据里要是有错的,模型学的比谁都快。
就跟学生背题一样,答案背得滚瓜烂熟,你换个题型他就不会了,甚至错题他也照背不误。所以别光看损失曲线好看就自我感动,真要靠谱,还得专家评测。
14. 折腾完这一圈,我真正收获了啥
14.1 值钱的不是模型,是链路
说实话,这个模型本身没啥大不了的,就是个试点玩具,能力有限。
真正值钱的是,我把从数据处理到模型训练,再到格式转换、容器部署、问题排查的整条工程链路,完完整整跑通了一遍。每个环节是干啥的,会出啥问题,怎么解决,门儿清了。
14.2 给新手的真心话建议
要是你也想从零学本地大模型,别上来就整7B、14B的大模型,纯纯给自己找罪受。
先整个0.5B到1B的小模型,把全流程跑通再说。能跑通、能复现、能评测,比一开始就追求大参数重要多了。不然上来就整个大模型,显存不够报错,部署也不会,最后打击信心,得不偿失。
15. 接下来打算咋迭代
15.1 先把数据质量提上去
现在的数据都是公开的,没经过人工审核,质量参差不齐。下一步打算人工抽检修订样本,再加一些安全相关的内容,比如儿童、孕产妇用药禁忌这些。
安全永远是第一位的,尤其是和医疗沾边的东西,马虎不得。
15.2 扩充能力慢慢迭代
打算建一个完全隔离的专家测试集,再加个中医古籍RAG知识库,弥补模型知识不准的问题。训练数据也打算扩到一万条以上,再试试继续预训练的效果。
后面也会对比下LoRA、QLoRA不同方式,还有不同量化格式的差异,慢慢优化。反正路已经走通了,剩下的就是慢慢填内容了。
最后给想动手的朋友提个醒,整个项目的核心文件顺序就是:数据准备脚本、训练脚本、评测脚本、合并脚本,再加上Modelfile和compose配置文件。顺着这个顺序看,就能把整个流程摸得明明白白。
踩坑不可怕,踩过一次下次就会了,动手试试比看十篇教程都管用。
P.S. 无意间发现了一个巨牛的人工智能教程,非常通俗易懂,对AI感兴趣的朋友强烈推荐去看看,传送门https://blog.csdn.net/qq_34419312