无意间,看见阿里云提供了针对 Qwen3-0.6B 的蒸馏教程,本文章本质上是跟着阿里云提供的教程学习微调过程,官方教程地址

借用下阿里云教程的介绍:

大参数模型效果好,但成本高、响应慢。为了在保障效果的同时提升推理速度、降低成本,可首先借助大参数模型完成目标任务的数据生成,并使用这些数据微调小参数模型,使其在特定任务中达到接近大参数模型的表现,这一过程也被称为模型蒸馏。

本方案将以从一句话中提取结构化信息(如收件人、地址、电话)为例,演示如何通过模型蒸馏,让 Qwen3-0.6B 模型在此任务上达到大参数模型的表现。

方案信息

教师模型: GLM4.7
样本信息: 由 GLM4.7 生成约1500+条虚拟数据
微调框架: ms-swift
模型推理: vllm

效果

改动部分

  1. 新增了postal_coderemark字段,但postal_code在样本训练时没有特地添加,而是在 system 提示词内加的,所以不一定准确,最主要的还是“从地址推邮编”本质是 地理编码/检索问题,仅靠 0.6B 蒸馏感觉还不如由应用层处理。
  2. 我重新试用GLM4.7生成了一批 2K 训练样本,没有使用阿里云提供的,主要目的是想提取备注。

样本重新生成

  1. 包含功能区/开发区/高新区/未来科技城/管委会等
  2. 包含自治区/自治州/盟/旗等多级行政
  3. 包含英文/拼音混排行政区(extracted 中要映射为中文规范名称)
  4. 包含括号备注/路线提示/near地铁口/(原xxx)/收件要求/门禁暗号/时间要求
  5. 电话号码带空格、全角破折号、+86、分机/转接/ext(phone 规范化且保留转接信息)
  6. 只有城市,没有省份的地址
  7. 只有某个区,但没有城市、省份的地址

教程

注意:为了方便起见,建议直接使用阿里云教程内的使用环境(老鸟略过),可领取试用点后可以使用9个小时的环境,主要是已经安装好了 CUDA 等驱动,显卡是A10,内存也给得挺大的。

下载训练数据

1
2
3
4
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"

安装依赖

  1. ms-swift 魔搭社区提供的训练框架,支持模型的下载、微调和权重合并,极大简化了微调流程。
  2. vllm 用于部署微调后的模型,支持高性能推理服务,不仅方便验证微调效果,还可用于生成 API,供业务方直接调用。
1
pip3 install vllm==0.9.0.1 ms-swift==3.5.0

ps: 建议分开安装,不然会卡很久。

1
2
pip3 install vllm==0.9.0.1
pip3 install ms-swift==3.5.0

模型微调

1
2
3
4
5
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

微调的核心代码为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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

会自动下载 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 变化如下表所示:

验证微调后模型效果(可选)

1
2
3
4
5
6
# 进入 /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 服务

1
2
3
4
# 下载部署脚本 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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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)的效果。

PS:从目前的学习体验来看,蒸馏的整体流程和深度学习模型训练其实有不少相似之处。基本思路都是基于一批样本数据进行训练,通过不断迭代和微调来优化模型效果,并结合召回率、准确率等指标对模型性能进行评估,然后再根据评估结果继续调整训练策略或模型结构。从实践角度来看,本质上也是一个“数据 → 训练 → 评估 → 调整”的循环过程,只是蒸馏更多是在已有模型能力的基础上进行知识迁移和压缩。

2026-03-04 更新

针对以上行政冲突等问题,我重新利用GLM4.7生成了2.8万条样本数据,其中包含

  • 行政区冲突样本
  • 缺失补全样本
  • 定位,新增了字段 zone (功能区),
  • 多地址/冲突
  • 非“路号”型地址:村镇组/队/屯/号;市场档口/铺位/摊位;或学校/医院/园区内部楼宇(楼/座/科室/院区等)
  • 数字混淆:取件码/门禁码 等
  • 多号码/分机
  • 英文/拼音片段
  • 行政区错别字
  • 偏远地区
  • 行政区缩写与别称
  • 模糊POI与硬门牌
  • 路名干扰

不再将 功能区 放入 remark 字段中,单独放入 zone 字段。但最好还是模型后置省市区数据库做校验。

训练参数重新调整为

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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
2
3
4
5
6
7
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格式):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
原文:浙江省舟山市普陀区朱家尖街道东沙路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等。

1
2
3
4
5
6
运行环境: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

1
curl -LsSf https://astral.sh/uv/install.sh | sh

安装完成后,可用以下命令验证安装

1
uv --version

创建虚拟环境

1
2
mkdir qwen-0.6b && cd qwen-0.6b
uv venv --python 3.12.7

默认会在当前目录创建 .venv 虚拟环境。

安装vllm

官方版本 根据自己的CUDA版本来。

1
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

1
uv pip install ms-swift==3.5.0

脚本改动

将sft.sh、deploy.sh 执行的python脚本改为 uv 环境运行。如: sft.sh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#!/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。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#!/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