记一次小模型微调/蒸馏学习(Qwen3-0.6B 从收货地址中提取结构化信息)
无意间,看见阿里云提供了针对 Qwen3-0.6B 的蒸馏教程,本文章本质上是跟着阿里云提供的教程学习微调过程,官方教程地址。
借用下阿里云教程的介绍:
大参数模型效果好,但成本高、响应慢。为了在保障效果的同时提升推理速度、降低成本,可首先借助大参数模型完成目标任务的数据生成,并使用这些数据微调小参数模型,使其在特定任务中达到接近大参数模型的表现,这一过程也被称为模型蒸馏。
本方案将以从一句话中提取结构化信息(如收件人、地址、电话)为例,演示如何通过模型蒸馏,让 Qwen3-0.6B 模型在此任务上达到大参数模型的表现。
方案信息
教师模型: GLM4.7
样本信息: 由 GLM4.7 生成约1500+条虚拟数据
微调框架: ms-swift
模型推理: vllm
效果

改动部分
- 新增了
postal_code、remark字段,但postal_code在样本训练时没有特地添加,而是在 system 提示词内加的,所以不一定准确,最主要的还是“从地址推邮编”本质是 地理编码/检索问题,仅靠 0.6B 蒸馏感觉还不如由应用层处理。 - 我重新试用GLM4.7生成了一批 2K 训练样本,没有使用阿里云提供的,主要目的是想提取备注。
样本重新生成
- 包含功能区/开发区/高新区/未来科技城/管委会等
- 包含自治区/自治州/盟/旗等多级行政
- 包含英文/拼音混排行政区(extracted 中要映射为中文规范名称)
- 包含括号备注/路线提示/near地铁口/(原xxx)/收件要求/门禁暗号/时间要求
- 电话号码带空格、全角破折号、+86、分机/转接/ext(phone 规范化且保留转接信息)
- 只有城市,没有省份的地址
- 只有某个区,但没有城市、省份的地址
教程
注意:为了方便起见,建议直接使用阿里云教程内的使用环境(老鸟略过),可领取试用点后可以使用9个小时的环境,主要是已经安装好了 CUDA 等驱动,显卡是A10,内存也给得挺大的。
下载训练数据
mkdir /root/qwen-0.6b
cd /root/qwen-0.6b && \
# 下载训练数据 train.jsonl
curl -f -o train.jsonl "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250610/azvmpb/train_with_system.jsonl"
安装依赖
- ms-swift 魔搭社区提供的训练框架,支持模型的下载、微调和权重合并,极大简化了微调流程。
- vllm 用于部署微调后的模型,支持高性能推理服务,不仅方便验证微调效果,还可用于生成 API,供业务方直接调用。
pip3 install vllm==0.9.0.1 ms-swift==3.5.0
ps: 建议分开安装,不然会卡很久。
pip3 install vllm==0.9.0.1
pip3 install ms-swift==3.5.0
模型微调
cd /root/qwen-0.6b && \
# 下载微调脚本 sft.sh
curl -f -o sft.sh "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250623/cggwpz/sft.sh" && \
# 执行微调脚本
bash sft.sh
微调的核心代码为:
swift sft \
--model Qwen/Qwen3-0.6B \
--train_type lora \
--dataset 'train.jsonl' \
--torch_dtype bfloat16 \
--num_train_epochs 10 \
--per_device_train_batch_size 20 \
--per_device_eval_batch_size 20 \
--learning_rate 1e-4 \
--lora_rank 8 \
--lora_alpha 32 \
--target_modules all-linear \
--gradient_accumulation_steps 16 \
--save_steps 1 \
--save_total_limit 2 \
--logging_steps 2 \
--max_length 2048 \
--output_dir output \
--warmup_ratio 0.05 \
--dataloader_num_workers 4
参数解释(AI)
| 参数 | 值 | 理解 | 影响/直觉 | 常见注意点 |
|---|---|---|---|---|
--model |
Qwen/Qwen3-0.6B |
选择要微调的“底座模型” | 模型越大越吃显存/更强;0.6B 属于小模型 | 要确认与 tokenizer、架构兼容(一般没问题) |
--train_type |
lora |
用 LoRA 方式微调(只训练少量“外挂”参数) | 显存更省、训练更快、也更不容易把底座“练坏” | LoRA 能力上限受 rank 等影响 |
--dataset |
train.jsonl |
训练数据文件 | 数据质量决定上限 | 格式不对/字段错会直接训练失败或学偏 |
--torch_dtype |
bfloat16 |
用 bfloat16 精度训练 | 更省显存,通常更稳(比 fp16 更不容易溢出) | 需要硬件支持(多数新 GPU/部分 CPU 支持) |
--num_train_epochs |
10 |
数据集会被“完整看完”10遍 | 轮数越多越容易记住训练数据 | 小数据集 + 10 epoch 很容易过拟合 |
--per_device_train_batch_size |
20 |
每张卡一次喂给模型 20 条样本 | 越大越吃显存;越大梯度越稳定 | OOM(爆显存)就得降它或降 max_length |
--per_device_eval_batch_size |
20 |
验证时每次跑 20 条 | 影响验证速度/显存 | eval 也可能 OOM |
--learning_rate |
1e-4 |
学习率:模型“改参数的步子有多大” | 大=学得快但容易不稳定;小=稳但慢 | LoRA 常用 1e-4 ~ 2e-4,但仍要看数据量/长度 |
--lora_rank |
8 |
LoRA 的“容量/可学习空间” | rank 越大越能学复杂变化,也越吃显存/更易过拟合 | 8 偏保守;16 常见;32 更强但更贵 |
--lora_alpha |
32 |
LoRA 的“放大系数/强度” | 影响 LoRA 更新对模型的作用力度 | 常见搭配:rank 8/16 配 alpha 16/32/64(看任务) |
--target_modules |
all-linear |
把 LoRA 挂到所有线性层上 | 学得更全面,参数更多,显存/时间更高 | 小模型+all-linear 一般可行,但有时会更易过拟合 |
--gradient_accumulation_steps |
16 |
梯度累积:攒 16 次再更新一次参数 | 等价于“更大的总 batch”,但不额外吃太多显存 | 训练更慢(因为更新变少、步数变长),但更稳 |
--save_steps |
1 |
每 1 step 保存一次 checkpoint | 方便回滚/对比 | 会非常频繁写盘、训练变慢、占 I/O;一般不建议这么小 |
--save_total_limit |
2 |
最多保留 2 份 checkpoint | 防止硬盘爆炸 | 配合 save_steps=1 会疯狂覆盖/删写 |
--logging_steps |
2 |
每 2 step 打一次日志 | 便于观察 loss | 日志太密也会拖慢一点点 |
--max_length |
2048 |
每条样本最长截到 2048 token | 能学更长上下文,但显存/耗时暴涨 | 最吃显存的参数之一;很多地址抽取任务不需要这么长 |
--output_dir |
output |
训练产物输出目录 | 保存 checkpoint、日志等 | 注意磁盘空间 |
--warmup_ratio |
0.05 |
前 5% 训练步数学习率从小慢慢升到设定值 | 避免一上来学太猛导致发散 | 数据少时 warmup 过长会浪费步数;过短可能不稳 |
--dataloader_num_workers |
4 |
用 4 个进程加载数据 | 加快数据读取/预处理 | Windows/某些环境可能 worker 太多反而卡;也可能导致随机性差异 |
会自动下载 Qwen3-0.6B,我本地用的3080 12G跑的,大概在10分钟的样子。


根据脚本内容,训练完成后,会合并LoRA权重,生成合并后模型在output/v0-xxx-xxx/checkpoint-50-merged目录下。
教程内容:
在output/v0-xxx-xxx路径下有 images 文件夹,打开 train_loss.png(反映训练集损失) 与 eval_loss.png(反映验证集损失),根据损失值的变化趋势初步判断当前模型的训练效果:
-
在结束训练前 train_loss 与 eval_loss 仍有下降趋势(欠拟合) 可以增加 num_train_epochs(训练轮次,与训练深度正相关) 参数,或适当增大 lora_rank(低秩矩阵的秩,秩越大,模型能表达更复杂的任务,但更容易过度训练)的值后再进行训练,加大模型的对训练数据的拟合程度;
-
在结束训练前 train_loss 持续下降,eval_loss 开始变大(过拟合) 可以减少 num_train_epochs 参数,或适当减小lora_rank的值后再进行训练,防止模型过度训练;
-
在结束训练前 train_loss 与 eval_loss 均处于平稳状态(良好拟合) 模型处于该状态时,您可以进行后续步骤。本方案的 train_loss 与 eval_loss 变化如下表所示:
验证微调后模型效果(可选)
# 进入 /root 目录
cd /root/qwen-0.6b && \
# 下载并执行验证脚本
curl -o evaluate.py "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250612/bzszyc/evaluate.py" && \
curl -o evaluate.sh "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250612/xtgxql/evaluate.sh" && \
bash evaluate.sh
部署为 API 服务
# 下载部署脚本 deploy.sh
curl -o deploy.sh "https://help-static-aliyun-doc.aliyuncs.com/file-manage-files/zh-CN/20250613/hbojjv/deploy.sh" && \
# 后台运行部署脚本
bash deploy.sh

调用方式
经过我测试,温度设置为 0 ,TOP P设置为 1 好一点。
1、curl
- Authorization需要输入Token
curl --location 'http://localhost:8000/v1/chat/completions' \
--header 'Authorization: Bearer TOKEN' \
--header 'Content-Type: application/json' \
--data '{
"model": "Qwen3-0.6B-SFT",
"messages": [
{
"role": "system",
"content": "你是一个专业的信息抽取助手,专门负责从中文文本中提取收件人的JSON信息,包含的Key有province(省份)、city(城市名称)、district(区县名称)、specific_location(街道、门牌号、小区、楼栋等详细信息)、name(收件人姓名)、phone(联系电话)、postal_code(邮编)、remark(备注)。必须遵守的规则:1、如果用户输入的文本没有明确 province/city/district/name,则对应字段输出空字符串,不准编造。2、remark不允许出现 province/city/district/name/specific_location 字段内的内容"
},
{
"role": "user",
"content": "西安,长安区,郭杜街道,樱花一路,茅坡新城,6号楼2单元 301室,联系人:周杰,电话:18666667777 (备用:13588889999)"
}
],
"chat_template_kwargs": {
"enable_thinking": false
},
"guided_json": {
"title": "Labels",
"type": "object",
"required": ["province", "city", "district", "specific_location", "name", "phone"],
"properties": {
"province": {
"title": "Province",
"type": "string"
},
"city": {
"title": "City",
"type": "string"
},
"district": {
"title": "District",
"type": "string"
},
"specific_location": {
"title": "Specific Location",
"type": "string"
},
"name": {
"title": "Name",
"type": "string"
},
"phone": {
"title": "Phone",
"type": "string"
},
"remark": {
"title": "Remark",
"type": "string"
}
}
}
}'
2、Chatbox客户端
添加模型提供方

新会话


总结
对于识别效果,个人感官如果总分给10分,我给8分,可能是0.6B参数量比较小,或者是训练样本的问题,对于 行政冲突、功能区映射与定位、复杂纠错与翻译,处理还是不行,而且还是建立在我重新使用新样本微调的情况下。
自带的样本微调出来只能给到6分左右。但处理常见的地址还是没有问题的。
其次未做 字段提取准确率、召回率、错误率 量化指标,也未对比原大模型(GLM4.7)的效果。
2026-03-04 更新
针对以上行政冲突等问题,我重新利用GLM4.7生成了2.8万条样本数据,其中包含
- 行政区冲突样本
- 缺失补全样本
- 定位,新增了字段 zone (功能区),
- 多地址/冲突
- 非“路号”型地址:村镇组/队/屯/号;市场档口/铺位/摊位;或学校/医院/园区内部楼宇(楼/座/科室/院区等)
- 数字混淆:取件码/门禁码 等
- 多号码/分机
- 英文/拼音片段
- 行政区错别字
- 偏远地区
- 行政区缩写与别称
- 模糊POI与硬门牌
- 路名干扰
不再将 功能区 放入 remark 字段中,单独放入 zone 字段。但最好还是模型后置省市区数据库做校验。
训练参数重新调整为
swift sft \
--model Qwen/Qwen3-0.6B \
--train_type lora \
--dataset 'train.jsonl' \
--torch_dtype bfloat16 \
--num_train_epochs 2 \
--per_device_train_batch_size 24 \
--per_device_eval_batch_size 24 \
--learning_rate 5e-5 \
--lora_rank 16 \
--lora_alpha 32 \
--target_modules all-linear \
--gradient_accumulation_steps 4 \
--save_steps 100 \
--save_total_limit 3 \
--logging_steps 10 \
--max_length 512 \
--output_dir output \
--warmup_ratio 0.05 \
--dataloader_num_workers 4;
目前发现不足:
1、 把地标当 name。 如: 内蒙古自治区呼和浩特市赛罕区敕勒川大街1号内蒙古自治区政府旁,"{""province"": ""内蒙古自治区"", ""city"": ""呼和浩特市"", ""district"": ""赛罕区"", ""zone"": """", ""specific_location"": ""敕勒川大街1号"", ""name"": ""内蒙古自治区政府旁"", ""phone"": """", ""postal_code"": """", ""remark"": """"}"
2、行政区规范仍不稳定。如 广东省深圳市深汕特别合作区 输出 district="南山区"。
3、单元号符号丢失。如 2号楼-1单元 802 输出:2号楼1单元802(丢了 -)
4、地址片段粘连。观日路18号 B10 5层 → 输出 观日路18号B105层(B10 和 5层粘一起)
还是需要针对性生成200条样本左右,基于新的模型再次蒸馏进行纠正,这一块后续再做。
优点:
- 对于一般的正常地址已经能做到不出错
- 抗干扰与清洗能力
- 复杂的逻辑拆分,对 zone(功能区/开发区)和 remark(备注)的拆分非常灵动。比如能把“与海德三道交汇处”准确放入备注,把“高新区”和“武侯区”分别归类。
- 行政区划补全与规范,能将“新疆”自动扩写为“新疆维吾尔自治区”。
测试地址(虚拟,有难度的情况)(忽略json格式):
原文:浙江省舟山市普陀区朱家尖街道东沙路100号
解析结果: "{""province"": ""浙江省"", ""city"": ""舟山市"", ""district"": ""普陀区"", ""zone"": """", ""specific_location"": ""朱家尖街道东沙路100号"", ""name"": """", ""phone"": """", ""postal_code"": """", ""remark"": """"}"
原文:山东省潍坊市寿光市圣城街道圣阳街与幸福路交叉口
解析结果: "{""province"": ""山东省"", ""city"": ""潍坊市"", ""district"": ""寿光市"", ""zone"": """", ""specific_location"": ""圣城街道圣阳街与幸福路交叉口"", ""name"": """", ""phone"": """", ""postal_code"": """", ""remark"": """"}"
原文:河南新乡市长垣市蒲西街道蒲城大道1号行政中心
解析结果: "{""province"": ""河南省"", ""city"": ""新乡市"", ""district"": ""长垣市"", ""zone"": """", ""specific_location"": ""蒲西街道蒲城大道1号行政中心"", ""name"": """", ""phone"": """", ""postal_code"": """", ""remark"": """"}"
原文:湖北省神农架林区木鱼镇木鱼路1号神农架游客中心
解析结果: "{""province"": ""湖北省"", ""city"": """", ""district"": ""神农架林区"", ""zone"": """", ""specific_location"": ""木鱼镇木鱼路1号神农架游客中心"", ""name"": """", ""phone"": """", ""postal_code"": """", ""remark"": """"}"
原文:广东省深圳市深汕特别合作区鹅埠镇创新大道1号
解析结果: "{""province"": ""广东省"", ""city"": ""深圳市"", ""district"": ""南山区"", ""zone"": ""深汕特别合作区"", ""specific_location"": ""鹅埠镇创新大道1号"", ""name"": """", ""phone"": """", ""postal_code"": """", ""remark"": """"}"
原文:上海 浦东 张江高科(中区) 科苑路捌拾捌号 3#楼501 张三/前台收 13800138000(备注:别按门铃,电话可能打不通)
解析结果: "{""province"": ""上海市"", ""city"": ""上海市"", ""district"": ""浦东新区"", ""zone"": ""张江高科(中区)"", ""specific_location"": ""科苑路捌拾捌号3#楼501"", ""name"": ""张三"", ""phone"": ""13800138000"", ""remark"": ""前台收;别按门铃,电话可能打不通""}"
原文:北京市海淀区 上地十街10号院(辉煌国际)2号楼-1单元 802,李四 186-0000-1234;备用:010-6299 7788(快递别放驿站)
解析结果: "{""province"": ""北京市"", ""city"": ""北京市"", ""district"": ""海淀区"", ""zone"": """", ""specific_location"": ""上地十街10号院辉煌国际2号楼1单元802"", ""name"": ""李四"", ""phone"": ""18600001234"", ""postal_code"": """", ""remark"": ""备用号:010-62997788;快递别放驿站""}"
原文:广东省深圳市南山粤海街道 科技园(南区)科苑南路2666号 中国华润大厦T2 23F 2306室 收件:王五 13900001111 备注:到前台说“会议资料”
解析结果: "{""province"": ""广东省"", ""city"": ""深圳市"", ""district"": ""南山区"", ""zone"": ""科技园(南区)"", ""specific_location"": ""科苑南路2666号中国华润大厦T2 23F 2306室"", ""name"": ""王五"", ""phone"": ""13900001111"", ""remark"": ""到前台说“会议资料”""}"
原文:浙江杭州 余杭区(未来科技城)文一西路 969号 阿里巴巴西溪园区 3期 D区 5号楼 3-201 赵* 15100002222(门口保安不让进)
解析结果: "{""province"": ""浙江省"", ""city"": ""杭州市"", ""district"": ""余杭区"", ""zone"": ""未来科技城;阿里巴巴西溪园区"", ""specific_location"": ""文一西路969号3期D区5号楼3-201"", ""name"": ""赵*"", ""phone"": ""15100002222"", ""remark"": ""门口保安不让进""}"
原文:重庆 渝北 回兴(两路)金开大道 1881 号 融科金开中心 1栋 19-7 收:陈晨 133 3333 3333 备注:周末别送
解析结果: "{""province"": ""重庆市"", ""city"": ""重庆市"", ""district"": ""渝北区"", ""zone"": """", ""specific_location"": ""金开大道1881号融科金开中心1栋19-7"", ""name"": ""陈晨"", ""phone"": ""13333333333"", ""remark"": ""周末别送""}"
原文:江苏省南京市鼓楼区 中山北路 8O 号(注意是字母O不是零)德基广场二期 A座 1608,周先生 18000000000(发顺丰)
解析结果: "{""province"": ""江苏省"", ""city"": ""南京市"", ""district"": ""鼓楼区"", ""zone"": """", ""specific_location"": ""中山北路8O号德基广场二期A座1608"", ""name"": ""周先生"", ""phone"": ""18000000000"", ""postal_code"": """", ""remark"": ""发顺丰""}"
原文:四川成都 高新区(其实是武侯?不确定)天府二街 138号 腾讯大厦 B座 12层 1203 张老师 177-7777-7777 备注:到了打座机 028-88886666
解析结果: "{""province"": ""四川省"", ""city"": ""成都市"", ""district"": """", ""zone"": ""高新区"", ""specific_location"": ""天府二街138号腾讯大厦B座12层1203"", ""name"": ""张老师"", ""phone"": ""17777777777"", ""postal_code"": """", ""remark"": ""备注:到了打座机028-88886666""}"
原文:湖北武汉 江岸区 台北一路 9号 云林街坊? 4栋2单元502(原502现504) 收件人:刘女士 15200003333(别写“台北路”会丢件)
解析结果: "{""province"": ""湖北省"", ""city"": ""武汉市"", ""district"": ""江岸区"", ""zone"": """", ""specific_location"": ""台北一路9号云林街坊4栋2单元502"", ""name"": ""刘女士"", ""phone"": ""15200003333"", ""postal_code"": """", ""remark"": ""原502现504;别写“台北路”会丢件""}"
原文:陕西西安 雁塔 曲江新区(功能区)芙蓉西路 99号 曲江大悦城 写字楼 1号楼 1402 杨洋 18700004444 备注:停车场入口旁
解析结果: "{""province"": ""陕西省"", ""city"": ""西安市"", ""district"": ""雁塔区"", ""zone"": ""曲江新区"", ""specific_location"": ""芙蓉西路99号曲江大悦城写字楼1号楼1402"", ""name"": ""杨洋"", ""phone"": ""18700004444"", ""remark"": ""停车场入口旁""}"
原文:福建 厦门 思明区 软件园二期 观日路 18号 B10 5层(前台转) 收:林- 13600005555(上午送)
解析结果: "{""province"": ""福建省"", ""city"": ""厦门市"", ""district"": ""思明区"", ""zone"": ""软件园二期"", ""specific_location"": ""观日路18号B105层"", ""name"": ""林"", ""phone"": ""13600005555"", ""remark"": ""前台转;上午送""}"
原文:山东省青岛市 市北区(原四方)黑龙江南路 2号 万科中心 A座 1301,收件:孙悟空 17000006666(公司名别写,写了会被退)
解析结果: "{""province"": ""山东省"", ""city"": ""青岛市"", ""district"": ""市北区"", ""zone"": """", ""specific_location"": ""黑龙江南路2号万科中心A座1301"", ""name"": ""孙悟空"", ""phone"": ""17000006666"", ""postal_code"": """", ""remark"": ""公司名别写,写了会被退""}"
原文:河南郑州 郑东新区(功能区)商务外环路 5号 国际金融中心 IFC 2号楼 9-903 赵六 15900007777 备注:门牌有两个,以“2号楼”为准
解析结果: "{""province"": ""河南省"", ""city"": ""郑州市"", ""district"": ""郑东新区"", ""zone"": ""郑东新区"", ""specific_location"": ""商务外环路5号国际金融中心IFC2号楼9-903"", ""name"": ""赵六"", ""phone"": ""15900007777"", ""remark"": ""门牌有两个,以“2号楼”为准""}"
本机微调过程(可忽略)
这里记录一下我自己使用本机训练的过程。
ps: 后续涉及到执行python命令,请自行按照自己的python 环境管理器运行,例如我使用的uv,那么执行python前加一个前缀uv / uv run等。
运行环境:ubuntu 20.04.6(WSL2)
显卡: NVIDIA GeForce RTX 3080 12G
CUDA Version: 12.9
Driver Version: 576.88
NVIDIA-SMI 576.88
python 环境管理器: uv
安装uv
curl -LsSf https://astral.sh/uv/install.sh | sh
安装完成后,可用以下命令验证安装
uv --version
创建虚拟环境
mkdir qwen-0.6b && cd qwen-0.6b
uv venv --python 3.12.7
默认会在当前目录创建 .venv 虚拟环境。
安装vllm
官方版本 根据自己的CUDA版本来。
uv pip install vllm==0.15.1 --extra-index-url https://wheels.vllm.ai/0.15.1/cu129 --extra-index-url https://download.pytorch.org/whl/cu129 --index-strategy unsafe-best-match
更多安装教程,可自行搜索(VLLM 部署的一些细节,关于CUDA对应版本的问题)
安装ms-swift
uv pip install ms-swift==3.5.0
脚本改动
将sft.sh、deploy.sh 执行的python脚本改为 uv 环境运行。如: sft.sh
#!/bin/bash
# =============================================================================
# Qwen3 0.6B 模型LoRA微调脚本
#
# 功能:
# 1. 自动下载Qwen3-0.6B模型
# 2. 使用物流填单数据集进行LoRA微调
# 3. 保存微调后的权重
# 4. 合并LoRA权重
# =============================================================================
if CUDA_VISIBLE_DEVICES=0 \
uv run swift sft \
--model Qwen/Qwen3-0.6B \
--train_type lora \
--dataset 'train.jsonl' \
--torch_dtype bfloat16 \
--num_train_epochs 10 \
--per_device_train_batch_size 20 \
--per_device_eval_batch_size 20 \
--learning_rate 1e-4 \
--lora_rank 8 \
--lora_alpha 32 \
--target_modules all-linear \
--gradient_accumulation_steps 16 \
--save_steps 1 \
--save_total_limit 2 \
--logging_steps 2 \
--max_length 2048 \
--output_dir output \
--warmup_ratio 0.05 \
--dataloader_num_workers 4; then
# 检查输出目录是否存在checkpoint
echo "检查训练结果..."
if [ -d "output" ] && [ "$(find output -name "checkpoint-*" -type d | wc -l)" -gt 0 ]; then
echo "✓ 找到训练checkpoint文件"
echo ""
echo "✓ 模型微调完成!"
echo "输出目录: output/"
else
echo "✗ 未找到训练checkpoint文件"
echo "训练可能未成功完成"
exit 1
fi
else
echo ""
echo "✗ 模型微调失败!"
echo "请检查错误信息"
exit 1
fi
echo "检测最新的checkpoint路径..."
LATEST_CHECKPOINT=$(find output -name "checkpoint-*" -type d | sort -V | tail -1)
if [ -z "$LATEST_CHECKPOINT" ]; then
echo "✗ 错误: 未找到checkpoint文件"
echo "请确保已完成模型微调,并且output目录中存在checkpoint文件"
exit 1
else
echo "✓ 找到checkpoint: $LATEST_CHECKPOINT"
fi
echo "开始合并LoRA权重..."
# 执行权重合并
# --ckpt_dir: checkpoint目录路径(需要根据实际情况修改)
# --merge_lora: 启用LoRA权重合并
if uv run swift export \
--ckpt_dir "$LATEST_CHECKPOINT" \
--merge_lora true; then
echo "✓ swift export 命令执行成功"
else
echo "✗ swift export 命令执行失败"
exit 1
fi
# 检查合并是否成功
echo "检查合并结果..."
MERGED_DIR="${LATEST_CHECKPOINT}-merged"
if [ -d "$MERGED_DIR" ]; then
echo "✓ 合并目录创建成功: $MERGED_DIR"
echo ""
echo "✓ LoRA权重合并完成!"
echo "合并后的模型路径: $MERGED_DIR"
else
echo "✗ 权重合并失败,未找到合并目录: $MERGED_DIR"
echo "请检查错误信息"
exit 1
fi
deploy.sh
此处我固定了API KEY。
#!/bin/bash
# =============================================================================
# vLLM API服务部署脚本
#
# 功能:
# 使用vLLM部署OpenAI兼容的API服务
#
# 可自定义参数:
# - --model: 模型路径(需要根据merge.sh的输出修改)
# - --host: 服务监听地址(0.0.0.0表示所有网络接口)
# - --port: 服务端口(默认8000)
# - --api-key: API访问密钥(每次运行时自动生成)
# - --gpu-memory-utilization: GPU显存使用率(0.8表示80%)
# - --max-model-len: 最大模型长度
#
# 安全提示:
# 1. API密钥每次启动时自动生成
# 2. 如果在生产环境使用,建议配置防火墙规则
# =============================================================================
# 生成随机API密钥
echo "生成API密钥..."
if API_KEY="sk-$(openssl rand -hex 24)"; then
echo "✓ API密钥生成成功"
else
echo "✗ API密钥生成失败"
exit 1
fi
API_KEY="sk-23faf2fd47f91300d7a6c6656128e7b53f036408195acc77"
# 自动检测最新的合并模型路径
echo "检测合并模型路径..."
MERGED_MODEL=$(find output -name "*-merged" -type d | sort -V | tail -1)
if [ -z "$MERGED_MODEL" ]; then
echo "✗ 错误: 未找到合并后的模型"
echo "请确保已运行merge.sh并成功合并LoRA权重"
echo "或者手动指定模型路径"
exit 1
else
echo "✓ 找到合并模型: $MERGED_MODEL"
fi
echo ""
echo "✓ 所有检查通过,启动vLLM API服务..."
echo ""
echo "服务配置:"
echo "- 模型路径: $MERGED_MODEL"
echo "- 监听地址: 0.0.0.0:8000"
echo "- API密钥: $API_KEY"
echo "- 模型名称: Qwen3-0.6B-SFT"
echo ""
echo "请保存上面的API密钥,用于客户端连接!"
echo ""
# 启动vLLM API服务器
echo "正在后台启动vLLM API服务器..."
# 启动服务器并获取进程ID
uv run python -m vllm.entrypoints.openai.api_server \
--model "$MERGED_MODEL" \
--host 0.0.0.0 \
--port 8000 \
--served-model-name Qwen3-0.6B-SFT \
--trust-remote-code \
--gpu-memory-utilization 0.8 \
--max-model-len 2048 \
--api-key "$API_KEY" > vllm.log 2>&1 &
VLLM_PID=$!
# 等待服务启动
echo "等待服务启动中..."
# 循环检查服务是否启动成功
MAX_WAIT=120 # 最大等待2分钟
WAIT_TIME=0
SERVICE_READY=false
while [ $WAIT_TIME -lt $MAX_WAIT ]; do
if kill -0 $VLLM_PID 2>/dev/null; then
# 进程存在,检查API是否可用
if curl -s http://localhost:8000/health > /dev/null 2>&1; then
SERVICE_READY=true
break
fi
else
echo "✗ vLLM进程意外停止"
break
fi
echo "等待中... ($WAIT_TIME/$MAX_WAIT 秒)"
sleep 5
WAIT_TIME=$((WAIT_TIME + 5))
done
# 检查服务是否正常运行
if [ "$SERVICE_READY" = true ]; then
echo ""
echo "✓ API服务启动成功!"
echo "✓ 服务进程ID: $VLLM_PID"
echo "✓ 日志文件: vllm.log"
# 注意事项说明
echo ""
echo "重要提示:"
echo "1. API密钥: $API_KEY"
echo "2. 服务地址: http://0.0.0.0:8000"
echo "3. 日志查看: tail -f vllm.log"
echo "4. 停止服务: kill $VLLM_PID"
echo "5. 如需外网访问,请配置防火墙规则"
echo ""
echo "服务部署完成!按 Ctrl+C 停止服务。"
# 示例:保存API密钥到文件
echo "$API_KEY" > api_key.txt
echo "✓ API密钥已保存到 api_key.txt"
# 示例:检查服务状态
echo ""
echo "服务状态检查:"
if curl -s http://localhost:8000/health > /dev/null 2>&1; then
echo "✓ API服务健康检查通过"
else
echo "⚠ API服务可能还在启动中,请稍后检查"
fi
# 信号处理函数
cleanup() {
echo ""
echo "正在停止vLLM服务..."
kill $VLLM_PID 2>/dev/null
wait $VLLM_PID 2>/dev/null
echo "✓ 服务已停止"
exit 0
}
# 注册信号处理
trap cleanup SIGINT SIGTERM
echo ""
echo "服务正在运行中... (使用 Ctrl+C 停止)"
echo "============================================"
# 保持脚本运行,等待用户中断
while true; do
if ! kill -0 $VLLM_PID 2>/dev/null; then
echo "✗ vLLM服务意外停止,退出..."
exit 1
fi
sleep 10
done
else
echo ""
echo "✗ API服务启动失败"
echo "请检查日志文件: vllm.log"
exit 1
fi