MS-Swift 实战:四阶段微调炼成行业 Agent

Feng 6 阅读 AI开发

学习大模型二次开发的阶段,亲手写训练代码能帮我们摸透微调的底层机制,做到既知其然也知其所以然。可一旦进入企业生产环境,大家更看重的就是标准化流程、高复用性和稳扎稳打的工程化方案。MS-Swift 是阿里达摩院魔搭社区开源的一站式大模型开发框架,把训练、微调、推理、量化部署的全链路能力打包在一起,兼容数百种主流模型和多种微调、对齐算法。这一章就借 MS-Swift 这样的成熟工具,看看怎么用标准化流程快速完成大模型的二次训练,把模型能力顺畅地嵌进企业业务系统。

配图

本章借助 MS‑Swift 框架,完整跑通一条面向行业 Agent 的大模型定制流水线。案例选 Qwen3.5‑0.8B‑Base 作为基座,参数量只有 0.8B,硬件门槛很低,能把企业 Agent 微调的全流程走完整;真实业务里可以换成 Qwen‑8B、14B 这类更大的基座。

整个定制过程采用四阶段递进式微调,一层层把模型能力打磨出来,完整流程如下:

Qwen3.5‑0.8B‑Base
    ↓
CPT‑LoRA 增量预训练 → 推理测试 → LoRA合并(领域基座)
    ↓
SFT‑LoRA(基础对话能力) → 推理测试 → LoRA合并
    ↓
Agent SFT‑LoRA(工具调用能力) → 推理测试 → LoRA合并
    ↓
DPO‑LoRA 偏好对齐,修正Agent行为 → 推理测试 → LoRA合并(最终HF模型)
    ↓
转换为GGUF格式 → 权重量化 → GGUF回归测试 → 部署

工程实践中的一般顺序是:先用增量预训练灌入无标签领域知识,再做指令微调,接着 Agent 微调,最后做偏好对齐。

  • CPT 增量预训练:把大批量无标签领域文档喂给基座,让它学会行业术语、业务行文逻辑和领域事实;这一步只做文本续写,不学对话;产出领域增强基座
  • SFT 监督指令微调:让领域基座听懂人类指令,学会问答对话、遵守 system 提示词,掌握对话模板;产出领域问答模型
  • Agent SFT 工具调用微调:训练模型判断什么时候该调外部工具,输出格式合法的工具调用 JSON;收到工具返回结果后整理成自然语言答案
  • DPO 偏好对齐:修掉 Agent 的毛病——工具误调用、非法 JSON、回答啰嗦、编造事实的幻觉;用好坏成对样本矫正输出行为

每个训练阶段收尾时,都要做人工推理测试,确认效果达标后再合并 LoRA、进入下一阶段。别跳过测试直接往下训,否则问题会层层叠加,最后很难定位 bug 出在哪一步。

一、环境配置

这一章完成系统底层依赖、Python 虚拟环境、适配 CUDA 的 PyTorch、MS-Swift 源码部署和基座模型下载的全套流程,全程走国内镜像加速,避开海外源下载慢、频繁超时的问题。

1、先装基础系统依赖并搭好虚拟环境,对齐 MS-Swift 官方推荐配置:Python3.13、CUDA12.8、Torch2.8.0,微调、量化、推理功能全都能用。

root@localhost:~# apt install build-essential gcc g++
root@localhost:~# apt install -y software-properties-common
root@localhost:~# add-apt-repository -y ppa:deadsnakes/ppa

root@localhost:~# apt install -y python3.13 python3.13-venv python3.13-dev
root@localhost:~# python3.13 -m venv myvenv
root@localhost:~# source myvenv/bin/activate

2、安装 PyTorch 与工具库,走国内清华源和上海交大 PyTorch 镜像,解决国内网络下载慢的痛点。

root@localhost:~# pip install torch==2.8.0 torchvision==0.23.0 torchaudio==2.8.0 transformers==4.57.6 accelerate gradio \
-i  \
--extra-index-url 

3、安装 ms‑swift 框架,用源码本地可编辑模式 ‑e ".[all]" 装上全部可选依赖,方便日后看源码、改框架逻辑。

root@localhost:~# git clone 
Cloning into 'ms-swift'...
remote: Enumerating objects: 82161, done.
remote: Counting objects: 100% (82161/82161), done.
remote: Compressing objects: 100% (17883/17883), done.
remote: Total 82161 (delta 62894), reused 82161 (delta 62894), pack-reused 0 (from 0)
Receiving objects: 100% (82161/82161), 84.64 MiB | 4.83 MiB/s, done.
Resolving deltas: 100% (62894/62894), done.

root@localhost:~# cd ms-swift/
root@localhost:~/ms-swift# pip install -i  -e ".[all]"

root@localhost:~# pip list
Package                       Version
----------------------------- ------------
absl-py                       2.5.0
accelerate                    1.15.0
addict                        2.4.0
aiohappyeyeballs              2.7.1
aiohttp                       3.14.3
aiosignal                     1.4.0
aliyun-python-sdk-core        2.16.1
aliyun-python-sdk-kms         2.16.5
annotated-doc                 0.0.5
annotated-types               0.8.0
anyio                         4.15.1
async-timeout                 5.0.1
attrdict                      2.0.1
attrs                         26.1.0
av                            17.1.0
binpacking                    2.0.1
certifi                       2026.7.22
cffi                          2.1.1
charset-normalizer            3.5.1
click                         8.5.0
cloudpickle                   3.1.2
contourpy                     1.3.2
cpm-kernels                   1.0.11
crcmod                        1.7
cryptography                  50.0.1
cuda-bindings                 12.9.4
cuda-pathfinder               1.8.1
cuda-toolkit                  12.6.3
cycler                        0.12.1
dacite                        1.9.2
datasets                      4.8.4
defusedxml                    0.7.1
dill                          0.4.1
einops                        0.8.2
exceptiongroup                1.3.1
fastapi                       0.141.1
filelock                      3.32.3
fonttools                     4.65.0
frozenlist                    1.8.0
fsspec                        2026.2.0
grpcio                        1.84.0
h11                           0.16.0
hf-xet                        1.6.0
httpcore                      1.0.9
httpcore2                     2.12.0
httpx                         0.28.1
httpx2                        2.12.0
huggingface_hub               1.31.0
idna                          3.19
importlib_metadata            9.0.1
Jinja2                        3.1.6
jiter                         0.17.0
jmespath                      1.1.0
joblib                        1.6.0
json_repair                   0.63.4
kiwisolver                    1.5.1
Markdown                      3.10.3
markdown-it-py                4.2.0
MarkupSafe                    3.0.3
matplotlib                    3.10.9
mdurl                         0.1.2
modelscope                    1.40.0
modelscope-hub                0.4.2
mpmath                        1.3.0
ms_swift                      4.6.0.dev0
multidict                     6.8.0
multiprocess                  0.70.19
networkx                      3.4.2
nltk                          3.10.3
numpy                         2.2.6
nvidia-cublas-cu12            12.6.4.1
nvidia-cuda-cupti-cu12        12.6.80
nvidia-cuda-nvrtc-cu12        12.6.85
nvidia-cuda-runtime-cu12      12.6.77
nvidia-cudnn-cu12             9.10.2.21
nvidia-cufft-cu12             11.3.0.4
nvidia-cufile-cu12            1.11.1.6
nvidia-curand-cu12            10.3.7.77
nvidia-cusolver-cu12          11.7.1.2
nvidia-cusparse-cu12          12.5.4.2
nvidia-cusparselt-cu12        0.7.1
nvidia-nccl-cu12              2.29.3
nvidia-nvjitlink-cu12         12.9.86
nvidia-nvshmem-cu12           3.4.5
nvidia-nvtx-cu12              12.6.77
openai                        3.13.0
oss2                          2.19.1
packaging                     26.3
pandas                        2.3.3
peft                          0.20.0
pillow                        12.3.0
pip                           22.0.2
propcache                     0.5.2
protobuf                      7.36.1
psutil                        7.2.2
pyarrow                       25.0.1
pycparser                     3.0
pycryptodome                  3.23.0
pydantic                      2.13.5
pydantic_core                 2.46.5
Pygments                      2.21.0
pyparsing                     3.3.2
python-dateutil               2.9.0.post0
pytz                          2026.3.post1
PyYAML                        6.0.3
qwen-vl-utils                 0.0.14
regex                         2026.9.10
requests                      2.34.2
rich                          15.0.0
rouge                         1.0.1
safetensors                   0.8.0
scipy                         1.15.3
sentencepiece                 0.2.2
setuptools                    78.1.0
shellingham                   1.5.4
simplejson                    4.1.2
six                           1.17.0
sniffio                       1.3.1
sortedcontainers              2.4.0
starlette                     1.6.0
sympy                         1.14.0
tensorboard                   2.21.0
tensorboard-data-server       0.7.2
tiktoken                      0.14.0
tokenizers                    0.23.2
torch                         2.14.0+cu126
torchvision                   0.29.0+cu126
tqdm                          4.70.1
transformers                  5.16.1
transformers-stream-generator 0.0.5
triton                        3.8.0
trl                           0.29.1
truststore                    0.10.4
typer                         0.27.2
typing_extensions             4.16.0
typing-inspection             0.4.4
tzdata                        2026.4
urllib3                       2.7.0
uvicorn                       0.53.0
Werkzeug                      3.1.8
xxhash                        4.0.1
yarl                          1.24.5
zipp                          4.1.0
zstandard                     0.25.0

4、创建工作目录 /root/qwen 并下载模型权重。数据集、模型权重、训练输出统一收在这个目录里,便于集中管理。

root@localhost:~/qwen# mkdir -p /root/qwen/output
root@localhost:~/qwen# pip install -i  modelscope

root@localhost:~/qwen# modelscope download --model Qwen/Qwen3.5-0.8B-Base --local_dir /root/qwen/Qwen3.5-0.8B-Base
Downloading: 100%|█████████████████████████████████| 13/13 [04:52<00:00, 22.47s/file]
✓ Snapshot ready at /root/qwen/Qwen3.5-0.8B-Base

root@localhost:~/qwen# ls -lh
total 8.8M
drwxr-xr-x 2 root root 4.0K Sep 14 00:47 Qwen3.5-0.8B-Base

二、增量预训练(CPT-LoRA)

CPT(Continual Pre-Training,持续增量预训练)是在通用基座之上,用无标签的行业长文本继续跑 next-token 续写训练。它不需要人工标注,只要有原始行业文档、合同、手册、白皮书这类数据,就能让通用模型快速补上垂直领域的知识体系,解决通用模型行业认知不足的问题。

这种路线适合的场景:手头有大量无标签行业文档、产品手册、合同、病历、行业白皮书,不需要问答标注,只要原始文本。不适合已经对齐过的 Instruct/chat 模型和对话问答样本。

标准数据集格式:

{"text":"这里是领域文档段落,例如合同条文、行业手册、产品说明长文本……"}
{"text":"第二篇领域长文本……"}
{"text":"第三篇领域长文本……"}

自己准备一批清洗过的文本,按比例切成训练集和验证集——比如前 80% 做训练、后 20% 做验证——分别写入 train_cpt.jsonl 和 val_cpt.jsonl 两个文件。

接着跑 PT LoRA 训练,用 bf16 精度、不启用量化、以 peft 后端运行。整个训练大约半小时能跑完,实际耗时随数据集规模浮动,本轮训出的最优版本是 V7。

root@localhost:~/qwen# python build_cpt_jsonl.py 
加载原始数据总行数:2407
字段提取+文本清洗完成,共 2407 条样本
训练集样本数:1925,验证集样本数:482

root@localhost:~/qwen# ls -lh
total 9.1M
drwxr-xr-x 2 root root 4.0K Sep 14 00:47 Qwen3.5-0.8B-Base
-rw-r--r-- 1 root root 2.8K Sep 14 00:44 build_cpt_jsonl.py
-rw-r--r-- 1 root root 237K Sep 14 00:51 train_cpt.jsonl
-rw-r--r-- 1 root root  60K Sep 14 00:51 val_cpt.jsonl
root@localhost:~/qwen# export TRITON_KERNEL_BUILD=0
root@localhost:~/qwen# CUDA_VISIBLE_DEVICES=0 swift pt \
--model /root/qwen/Qwen3.5-0.8B-Base \
--check_model false \
--tuner_type lora \
--dataset /root/qwen/train_cpt.jsonl \
--val_dataset /root/qwen/val_cpt.jsonl \
--torch_dtype bfloat16 \
--num_train_epochs 1 \
--per_device_train_batch_size 2 \
--gradient_accumulation_steps 4 \
--learning_rate 1e-4 \
--lora_rank 16 \
--lora_alpha 32 \
--target_modules all-linear \
--max_length 2048 \
--warmup_ratio 0.05 \
--save_steps 100 \
--eval_steps 100 \
--logging_steps 10 \
--save_total_limit 2 \
--output_dir /root/qwen/output/output_cpt_qwen35_08b \
--seed 42

Train: 100%|████████████████████████████████████████| 241/241 [19:59<00:00,  3.63s/it]
Val: 100%|██████████████████████████████████████████| 482/482 [01:54<00:00,  4.21it/s]
[INFO:swift] last_model_checkpoint: /root/qwen/output/output_cpt_qwen35_08b/v7-20260914-012237/checkpoint-241
[INFO:swift] best_model_checkpoint: /root/qwen/output/output_cpt_qwen35_08b/v7-20260914-012237/checkpoint-241
[INFO:swift] images_dir: /root/qwen/output/output_cpt_qwen35_08b/v7-20260914-012237/images
[INFO:swift] End time of running main: 2026-09-14 01:44:38.834684

root@localhost:~/qwen# tree -d
.
|-- Qwen3.5-0.8B-Base
`-- output
    `-- output_cpt_qwen35_08b
        |-- v0-20260914-005354
        |-- v1-20260914-005730
        |-- v2-20260914-005847
        |   |-- images
        |   `-- runs
        |-- v3-20260914-010038
        |   |-- images
        |   `-- runs
        |-- v4-20260914-010107
        |   |-- images
        |   `-- runs
        |-- v5-20260914-010316
        |   |-- images
        |   `-- runs
        |-- v6-20260914-011234
        |   |-- images
        |   `-- runs
        `-- v7-20260914-012237
            |-- checkpoint-200
            |-- checkpoint-241
            |-- images
            |-- last-checkpoint -> checkpoint-241
            `-- runs

然后做推理测试,用文本续写的方式验证效果。CPT 本质是续写任务而不是对话任务,测试重点只在于检查模型有没有掌握行业术语和领域行文逻辑;把输出和原始 Base 模型对比,看续写内容在领域相关性上有没有提升。

root@localhost:~/qwen# CUDA_VISIBLE_DEVICES=0 swift infer \
--model /root/qwen/Qwen3.5-0.8B-Base \
--adapters /root/qwen/output/output_cpt_qwen35_08b/v7-20260914-012237/checkpoint-241 \
--max_new_tokens 512 \
--temperature 0.7

[INFO:swift] Start time of running main: 2026-09-14 01:48:42.634761
[INFO:swift] swift.__version__: 4.6.0.dev0
[INFO:swift] Input `exit` or `quit` to exit the conversation.
[INFO:swift] Input `multi-line` to switch to multi-line input mode.
[INFO:swift] Input `reset-system` to reset the system and clear the history.
[INFO:swift] The current template only supports single-round dialogues.
<<< 你好
,请根据以下信息创建一个关于“基于文本的推荐系统”的学术论文,同时考虑文本相似度作为推荐系统中的重要因素。
--------------------------------------------------
<<< 很好
,接下来我们考虑如何将上述结果与量子化学模拟结合起来,特别是对于不同性质的分子。
--------------------------------------------------

之后执行 LoRA 合并。这一步只在两种情况下才需要:模型要部署,或者要用合并后的模型继续跑后续 SFT 训练。

root@localhost:~/qwen# CUDA_VISIBLE_DEVICES=0 swift export \
--adapters /root/qwen/output/output_cpt_qwen35_08b/v7-20260914-012237/checkpoint-241 \
--merge_lora true \
--output_dir /root/qwen/output/Qwen3.5-0.8B-Base-CPT-Merged

[INFO:swift] model_kwargs: {'device_map': 'cuda:0', 'dtype': torch.bfloat16}
Loading weights: 100%|████████████████████████████████████████████| 473/473 [00:00<00:00, 1209.27it/s]
[INFO:swift] default_system: None
[INFO:swift] max_length: 262144
[INFO:swift] response_prefix: None
[INFO:swift] agent_template: None
[INFO:swift] norm_bbox: norm1000
[INFO:swift] Merge LoRA...
[INFO:swift] Saving merged weights...
Writing model shards: 100%|████████████████████████████████████████| 1/1 [00:02<00:00,  2.20s/it]
[INFO:swift] Successfully merged LoRA and saved in `/root/qwen/output/Qwen3.5-0.8B-Base-CPT-Merged`.
[INFO:swift] End time of running main: 2026-09-14 01:51:05.791494

root@localhost:~/qwen# ls -lh output/
total 8.0K
drwxr-xr-x  2 root root 4.0K Sep 14 01:51 Qwen3.5-0.8B-Base-CPT-Merged
drwxr-xr-x 10 root root 4.0K Sep 14 01:22 output_cpt_qwen35_08b

产出模型路径:/root/qwen/output/Qwen3.5-0.8B-Base-CPT-Merged。这个领域基座会作为阶段 2 训练时 –model 参数指定的模型。

三、监督指令微调(SFT-LoRA)

刚做完 CPT 的模型虽然攒下了领域知识,却听不懂人类指令、不会规范对话、认不出系统提示词。SFT 监督指令微调的核心目标就是:教会模型接收用户的自然语言指令、输出标准化的领域问答、适配 chat_template 对话格式。

数据采用行业通用的 ShareGPT 对话格式,包含 system 系统提示、human 用户提问、gpt 标准回答三部分,同样按 8:2 切分训练集和验证集。

标准样本格式:

{
    "conversations":
    [
        {"from":"system","value":"你是领域助手,基于领域知识回答用户问题。"},
        {"from":"human","value":"解释合同生效条件"},
        {"from":"gpt","value":"合同生效需要满足主体适格、内容合法、双方达成合意。"}
    ]
}

需要准备两份数据集,分别对应训练集与验证集,文件路径为 /root/qwen/train_sft.jsonl 和 /root/qwen/val_sft.jsonl。

root@localhost:~/qwen# python build_sft_jsonl.py 
加载原始数据总行数:2407
对话样本构建完成,共 2407 条
训练集样本数:1925,验证集样本数:482

root@localhost:~/qwen# ls -lh
total 12M
drwxr-xr-x 2 root root 4.0K Sep 14 00:47 Qwen3.5-0.8B-Base
-rw-r--r-- 1 root root 2.8K Sep 14 00:44 build_cpt_jsonl.py
-rw-r--r-- 1 root root 3.0K Sep 14 01:52 build_sft_jsonl.py
drwxr-xr-x 4 root root   83 Sep 14 01:51 output
-rw-r--r-- 1 root root 237K Sep 14 00:51 train_cpt.jsonl
-rw-r--r-- 1 root root 2.0M Sep 14 01:52 train_sft.jsonl
-rw-r--r-- 1 root root  60K Sep 14 00:51 val_cpt.jsonl
-rw-r--r-- 1 root root 497K Sep 14 01:52 val_sft.jsonl

跑训练命令,在 Qwen3.5-0.8B-Base-CPT-Merged 的基础上继续 SFT 训练,本轮大约耗时 30 分钟。

root@localhost:~/qwen# CUDA_VISIBLE_DEVICES=0 swift sft \
--model /root/qwen/output/Qwen3.5-0.8B-Base-CPT-Merged \
--check_model false \
--tuner_type lora \
--dataset /root/qwen/train_sft.jsonl \
--val_dataset /root/qwen/val_sft.jsonl \
--torch_dtype bfloat16 \
--num_train_epochs 1 \
--per_device_train_batch_size 2 \
--gradient_accumulation_steps 4 \
--learning_rate 2e-4 \
--lora_rank 16 \
--lora_alpha 32 \
--target_modules all-linear \
--max_length 2048 \
--warmup_ratio 0.05 \
--save_steps 100 \
--eval_steps 100 \
--logging_steps 10 \
--save_total_limit 2 \
--output_dir /root/qwen/output/output_sft_qwen35_08b \
--seed 42

[INFO:swift] Saving model checkpoint to /root/qwen/output/output_sft_qwen35_08b/v0-20260914-015402/checkpoint-200
Train: 100%|██████████████████████████████████████████████| 241/241 [22:39<00:00,  4.11s/it]
Val: 100%|████████████████████████████████████████████████| 482/482 [02:08<00:00,  3.76it/s]
[INFO:swift] Saving model checkpoint to /root/qwen/output/output_sft_qwen35_08b/v0-20260914-015402/checkpoint-241
Train: 100%|██████████████████████████████████████████████| 241/241 [24:48<00:00,  6.18s/it]
[INFO:swift] last_model_checkpoint: /root/qwen/output/output_sft_qwen35_08b/v0-20260914-015402/checkpoint-241
[INFO:swift] best_model_checkpoint: /root/qwen/output/output_sft_qwen35_08b/v0-20260914-015402/checkpoint-241
[INFO:swift] images_dir: /root/qwen/output/output_sft_qwen35_08b/v0-20260914-015402/images
[INFO:swift] End time of running main: 2026-09-14 02:19:00.188422

训练结束后做推理测试,用普通对话指令验证模型。

root@localhost:~/qwen# CUDA_VISIBLE_DEVICES=0 swift infer \
--model /root/qwen/output/Qwen3.5-0.8B-Base-CPT-Merged \
--adapters /root/qwen/output/output_sft_qwen35_08b/v0-20260914-015402/checkpoint-241 \
--max_new_tokens 512 \
--temperature 0.7

[INFO:swift] Start time of running main: 2026-09-14 02:21:17.722209
[INFO:swift] swift.__version__: 4.6.0.dev0
[INFO:swift] Input `exit` or `quit` to exit the conversation.
[INFO:swift] Input `multi-line` to switch to multi-line input mode.
[INFO:swift] Input `reset-system` to reset the system and clear the history.
[INFO:swift] Input `clear` to clear the history.
<<< 你好
<think>
</think>

您好,请问有什么可以帮助您吗
--------------------------------------------------
<<< 回答的很好
<think>
</think>

您不必担心,我会根据您的情况提供详细的解答和建议。
--------------------------------------------------

测试要点:能不能听懂指令、回答是否贴合领域知识、对话格式是否正常。

测试合格后再执行 LoRA 合并。

root@localhost:~/qwen# CUDA_VISIBLE_DEVICES=0 swift export \
--model /root/qwen/output/Qwen3.5-0.8B-Base-CPT-Merged \
--adapters /root/qwen/output/output_sft_qwen35_08b/v0-20260914-015402/checkpoint-241 \
--merge_lora true \
--output_dir /root/qwen/Qwen3.5-0.8B-SFT-Merged

[INFO:swift] Global seed set to 42
[INFO:swift] Start time of running main: 2026-09-14 02:22:04.693962
[INFO:swift] swift.__version__: 4.6.0.dev0
[INFO:swift] merge_device_map: None
[WARNING:swift] Please install the package: `pip install "decord" -U`.
[INFO:swift] model_kwargs: {'device_map': 'cuda:0', 'dtype': torch.bfloat16}
Loading weights: 100%|██████████████████████████████████████████████████████████████| 473/473 [00:00<00:00, 1350.90it/s]
[INFO:swift] default_system: None
[INFO:swift] max_length: 262144
[INFO:swift] response_prefix: None
[INFO:swift] agent_template: qwen3_5
[INFO:swift] norm_bbox: norm1000
[INFO:swift] Merge LoRA...
[INFO:swift] Saving merged weights...
Writing model shards: 100%|█████████████████████████████████████████████████████████| 1/1 [00:02<00:00,  2.25s/it]
[INFO:swift] Successfully merged LoRA and saved in `/root/qwen/Qwen3.5-0.8B-SFT-Merged`.
[INFO:swift] End time of running main: 2026-09-14 02:22:10.411946

root@localhost:~/qwen# ls -lh Qwen3.5-0.8B-SFT-Merged/
total 1.7G
-rw-r--r-- 1 root root  18K Sep 14 02:22 args.json
-rw-r--r-- 1 root root 7.6K Sep 14 02:22 chat_template.jinja
-rw-r--r-- 1 root root 2.6K Sep 14 02:22 config.json
-rw-r--r-- 1 root root  138 Sep 14 02:22 generation_config.json
-rw------- 1 root root 1.6G Sep 14 02:22 model.safetensors
-rw-r--r-- 1 root root  390 Sep 14 02:22 preprocessor_config.json
-rw-r--r-- 1 root root 1.2K Sep 14 02:22 processor_config.json
-rw-r--r-- 1 root root  20M Sep 14 02:22 tokenizer.json
-rw-r--r-- 1 root root 1.2K Sep 14 02:22 tokenizer_config.json

产出模型路径:/root/qwen/output/Qwen3.5-0.8B-SFT-Merged,它是阶段 3 Agent SFT 训练的基座模型。

四、工具调用微调(Agent SFT-LoRA)

SFT 模型只有基础问答能力,联动不了外部工具、接口和数据库。Agent SFT 微调要解决的是:让模型学会自主判断何时触发工具调用——问题需要实时数据或外部资源时,输出标准化 JSON 格式的工具调用参数;靠自己已有的知识就能答的,则直接给自然语言回复。

要留意一点:Agent-SFT 的作用只是让模型能输出合规的工具调用 JSON 字符串,模型自己并不会发起请求或执行函数。

完整的 Agent 业务链路是这样的:

  • 用户提问送入模型
  • 模型输出工具调用 JSON
  • 业务后端 Python 代码解析 JSON,调用真实工具并拿到返回结果
  • 把工具返回结果以 tool 角色回填对话历史
  • 把完整对话再次喂给模型,模型整理出自然语言答案返回给用户

数据集有硬性要求:样本必须混合两类——一类需要调用工具,另一类不用调工具、可以直接作答。如果数据集全是工具调用样本,模型遇到任何问题都会无脑输出 tool-call,丧失直接回答的能力。

数据集一般沿用 ShareGPT 格式并新增 tool 角色,使用 Qwen 原生的工具对话 messages 格式:

{"messages": [
    {"role": "system", "content": "你是工具调用助手,可以使用search工具。search工具用来检索外部实时信息。当无法凭已有知识回答时,调用search。不需要工具就直接回答。"},
    {"role": "user", "content": "北京今天天气怎么样?"},
    {"role": "assistant", "content": "[{\"name\":\"search\",\"parameters\":{\"query\":\"北京今日天气\"}}]"},
    {"role": "tool", "content": "{\"result\":\"北京今日晴,气温15~24℃,微风\"}"},
    {"role": "assistant", "content": "北京今天天气晴朗,气温15到24摄氏度,有微风,适合外出。"}
]}

同时必须加入不需要调用工具、直接回答的样本:

{"messages": [
    {"role": "system", "content": "你是工具调用助手,可以使用search工具。search工具用来检索外部实时信息。当无法凭已有知识回答时,调用search。不需要工具就直接回答。"},
    {"role": "user", "content": "1+1等于几?"},
    {"role": "assistant", "content": "1+1等于2。"}
]}

生成数据集文件:/root/qwen/train_agent_sft.jsonl(训练集)与 /root/qwen/train_agent_val.jsonl(验证集)。

执行训练命令,基于前面得到的基座开展 Agent SFT 训练。

root@localhost:~/qwen# CUDA_VISIBLE_DEVICES=0 swift sft \
--model /root/qwen/Qwen3.5-0.8B-SFT-Merged \
--check_model false \
--tuner_type lora \
--dataset /root/qwen/train_agent_sft.jsonl \
--val_dataset /root/qwen/train_agent_val.jsonl \
--torch_dtype bfloat16 \
--num_train_epochs 1 \
--per_device_train_batch_size 2 \
--gradient_accumulation_steps 4 \
--learning_rate 2e-4 \
--lora_rank 16 \
--lora_alpha 32 \
--target_modules all-linear \
--max_length 2048 \
--warmup_ratio 0.05 \
--save_steps 100 \
--eval_steps 100 \
--logging_steps 10 \
--save_total_limit 2 \
--output_dir /root/qwen/output/output_agent_sft_qwen35_08b \
--dataloader_num_workers 4 \
--dataloader_prefetch_factor 2 \
--seed 42

[INFO:swift] model_parameter_info: PeftModelForCausalLM: 863.8086M Params (10.8227M Trainable [1.2529%]), 0.0001M Buffers.
[INFO:swift] use_reentrant: True
[INFO:swift] The logging file will be saved in: /root/qwen/output/output_agent_sft_qwen35_08b/v3-20260914-023552/logging.jsonl
[INFO:swift] Successfully registered post_encode hook: ['PeftModelForCausalLM'].
Val: 100%|███████████████████████████████████████████████████| 5/5 [00:00<00:00,  5.37it/s]
Train: 100%|███████████████████████████████████████████████████| 3/3 [00:13<00:00,  4.64s/it]
[INFO:swift] last_model_checkpoint: /root/qwen/output/output_agent_sft_qwen35_08b/v3-20260914-023552/checkpoint-3
[INFO:swift] best_model_checkpoint: /root/qwen/output/output_agent_sft_qwen35_08b/v3-20260914-023552/checkpoint-3
[INFO:swift] images_dir: /root/qwen/output/output_agent_sft_qwen35_08b/v3-20260914-023552/images
[INFO:swift] End time of running main: 2026-09-14 02:36:15.498939

root@localhost:~/qwen# tree -d
.
|-- Qwen3.5-0.8B-Base
|-- Qwen3.5-0.8B-SFT-Merged
`-- output
    |-- Qwen3.5-0.8B-Base-CPT-Merged
    |-- output_agent_sft_qwen35_08b
    |   |-- v0-20260914-022419
    |   |-- v1-20260914-022515
    |   |-- v2-20260914-022752
    |   |   |-- checkpoint-2
    |   |   |-- images
    |   |   |-- last-checkpoint -> checkpoint-2
    |   |   `-- runs
    |   `-- v3-20260914-023552
    |       |-- checkpoint-3
    |       |-- images
    |       |-- last-checkpoint -> checkpoint-3
    |       `-- runs
    |-- output_cpt_qwen35_08b
    |   |-- v0-20260914-005354
    |   |-- v1-20260914-005730
    |   |-- v2-20260914-005847
    |   |   |-- images
    |   |   `-- runs
    |   |-- v3-20260914-010038
    |   |   |-- images
    |   |   `-- runs
    |   |-- v4-20260914-010107
    |   |   |-- images
    |   |   `-- runs
    |   |-- v5-20260914-010316
    |   |   |-- images
    |   |   `-- runs
    |   |-- v6-20260914-011234
    |   |   |-- images
    |   |   `-- runs
    |   `-- v7-20260914-012237
    |       |-- checkpoint-200
    |       |-- checkpoint-241
    |       |-- images
    |       |-- last-checkpoint -> checkpoint-241
    |       `-- runs
    `-- output_sft_qwen35_08b
        `-- v0-20260914-015402
            |-- checkpoint-200
            |-- checkpoint-241
            |-- images
            |-- last-checkpoint -> checkpoint-241
            `-- runs

训练完成后做 Agent 推理测试,重点验证工具调用能力,尤其要防 JSON 输出乱码。

当前输入文本量偏少时,少量样本容易出现工具误触发、JSON 缺引号、json 截断、多轮 tool 调用状态错乱等问题。Agent-SFT 建议样本量至少几千条,工具调用样本与直接回答样本按比例搭配(比如 1:1 或 6:4)。

root@localhost:~/qwen# CUDA_VISIBLE_DEVICES=0 swift infer \
--model /root/qwen/Qwen3.5-0.8B-SFT-Merged \
--adapters /root/qwen/output/output_agent_sft_qwen35_08b/v2-20260914-022752/checkpoint-2 \
--max_new_tokens 512 \
--temperature 0.25

[INFO:swift] Start time of running main: 2026-09-14 02:38:29.684566
[INFO:swift] swift.__version__: 4.6.0.dev0
[INFO:swift] Input `exit` or `quit` to exit the conversation.
[INFO:swift] Input `multi-line` to switch to multi-line input mode.
[INFO:swift] Input `reset-system` to reset the system and clear the history.
[INFO:swift] Input `clear` to clear the history.
<<< 北京今天天气怎么样?
<think>
</think>
北京今天天气晴,气温22℃,湿度50,紫外线指数为2。
--------------------------------------------------

模型测试合格后再执行 LoRA 合并,合并后的模型将作为 DPO 训练阶段的基座。

root@localhost:~/qwen# CUDA_VISIBLE_DEVICES=0 swift export \
--model /root/qwen/Qwen3.5-0.8B-SFT-Merged \
--adapters /root/qwen/output/output_agent_sft_qwen35_08b/v2-20260914-022752/checkpoint-2 \
--merge_lora true \
--output_dir /root/qwen/Qwen3.5-0.8B-Agent-SFT-Merged

Loading weights: 100%|█████████████████████████████████████| 473/473 [00:00<00:00, 1325.33it/s]
Writing model shards: 100%|████████████████████████████████| 1/1 [00:02<00:00,  2.18s/it]
[INFO:swift] Successfully merged LoRA and saved in `/root/qwen/Qwen3.5-0.8B-Agent-SFT-Merged`.
[INFO:swift] End time of running main: 2026-09-14 02:40:23.453382

root@localhost:~/qwen# ls -lh Qwen3.5-0.8B-Agent-SFT-Merged/
total 1.7G
-rw-r--r-- 1 root root  18K Sep 14 02:40 args.json
-rw-r--r-- 1 root root 7.6K Sep 14 02:40 chat_template.jinja
-rw-r--r-- 1 root root 2.6K Sep 14 02:40 config.json
-rw-r--r-- 1 root root  138 Sep 14 02:40 generation_config.json
-rw------- 1 root root 1.6G Sep 14 02:40 model.safetensors
-rw-r--r-- 1 root root  390 Sep 14 02:40 preprocessor_config.json
-rw-r--r-- 1 root root 1.2K Sep 14 02:40 processor_config.json
-rw-r--r-- 1 root root  20M Sep 14 02:40 tokenizer.json
-rw-r--r-- 1 root root 1.2K Sep 14 02:40 tokenizer_config.json

产出模型路径:/root/qwen/output/Qwen3.5-0.8B-Agent-SFT-Merged。

五、偏好对齐(DPO-LoRA)

DPO(Direct Preference Optimization,直接偏好优化)是 RLHF 的轻量方案,不需要单独训 reward 奖励模型。输入是成对偏好样本:同一个用户 prompt,一条高质量的正确输出 chosen,一条差劲的错误输出 rejected。训练的目标是抬高模型生成 chosen 的概率、压低生成 rejected 的概率,从而矫正模型行为。

经过 Agent‑SFT 之后,模型已经会输出工具调用 JSON,但行为上仍有不少毛病:

  • 不该调工具的时候,错误地触发了 search 工具;
  • 输出的 JSON 存在语法错误;
  • 回答啰嗦、重复,甚至编造幻觉信息。

数据集格式对应的文件是 /root/qwen/train_dpo.jsonl 与 /root/qwen/train_dpo_val.jsonl。

DPO 训练使用成对样本,包含 prompt 提示词、chosen 优选正确输出、rejected 劣选错误输出三部分。

{
  "messages": [
    {
      "role": "system",
      "content": "你是工具调用助手,可以使用search工具。search工具用来检索外部实时信息。当无法凭已有知识回答时,调用search。不需要工具就直接回答。"
    },
    {
      "role": "user",
      "content": "查询青岛今日气温和青岛今日湿度"
    }
  ],
  "chosen_response": "[{\"name\":\"search\",\"parameters\":{\"query\":\"青岛今日气温和湿度\"}}]",
  "rejected_response": "[{\"name\":\"search\",\"parameters\":{\"query\":\"青岛今日气温\"}}],[{\"name\":\"search\",\"parameters\":{\"query\":\"青岛今日湿度\"}}]"
}

跑 DPO 训练命令,基于 Qwen3.5-0.8B-Agent-SFT-Merged 基座开展偏好对齐训练。

root@localhost:~/qwen# CUDA_VISIBLE_DEVICES=0 swift rlhf \
--rlhf_type dpo \
--model /root/qwen/Qwen3.5-0.8B-Agent-SFT-Merged \
--check_model false \
--tuner_type lora \
--dataset /root/qwen/train_dpo.jsonl \
--val_dataset /root/qwen/train_dpo_val.jsonl \
--torch_dtype bfloat16 \
--num_train_epochs 1 \
--per_device_train_batch_size 2 \
--gradient_accumulation_steps 4 \
--learning_rate 5e-5 \
--lora_rank 16 \
--lora_alpha 32 \
--target_modules all-linear \
--max_length 2048 \
--warmup_ratio 0.05 \
--save_steps 100 \
--eval_steps 100 \
--logging_steps 10 \
--save_total_limit 2 \
--beta 0.1 \
--output_dir /root/qwen/output_agent_dpo_qwen35_08b \
--seed 42

[INFO:swift] model_parameter_info: PeftModelForCausalLM: 863.8086M Params (10.8227M Trainable [1.2529%]), 0.0001M Buffers.
[INFO:swift] use_reentrant: True
[INFO:swift] The logging file will be saved in: /root/qwen/output_agent_dpo_qwen35_08b/v6-20260914-025709/logging.jsonl
[INFO:swift] Successfully registered post_encode hook: ['PeftModelForCausalLM'].
Train: 100%|███████████████████████████████████████████████████████████████| 1/1 [00:02<00:00,  2.56s/it]
Val: 100%|████████████████████████████████████████████████████████████████| 1/1 [00:00<00:00, 221.96it/s]
[INFO:swift] last_model_checkpoint: /root/qwen/output_agent_dpo_qwen35_08b/v6-20260914-025709/checkpoint-1
[INFO:swift] best_model_checkpoint: /root/qwen/output_agent_dpo_qwen35_08b/v6-20260914-025709/checkpoint-1
[INFO:swift] images_dir: /root/qwen/output_agent_dpo_qwen35_08b/v6-20260914-025709/images
[INFO:swift] End time of running main: 2026-09-14 02:57:21.987156

root@localhost:~/qwen# tree -d
.
|-- Qwen3.5-0.8B-Agent-SFT-Merged
|-- Qwen3.5-0.8B-Base
|-- Qwen3.5-0.8B-SFT-Merged
|-- output
|   |-- Qwen3.5-0.8B-Base-CPT-Merged
|   |-- output_agent_sft_qwen35_08b
|   |   |-- v0-20260914-022419
|   |   |-- v1-20260914-022515
|   |   |-- v2-20260914-022752
|   |   |   |-- checkpoint-2
|   |   |   |-- images
|   |   |   |-- last-checkpoint -> checkpoint-2
|   |   |   `-- runs
|   |   `-- v3-20260914-023552
|   |       |-- checkpoint-3
|   |       |-- images
|   |       |-- last-checkpoint -> checkpoint-3
|   |       `-- runs
|   |-- output_cpt_qwen35_08b
|   |   |-- v0-20260914-005354
|   |   |-- v1-20260914-005730
|   |   |-- v2-20260914-005847
|   |   |   |-- images
|   |   |   `-- runs
|   |   |-- v3-20260914-010038
|   |   |   |-- images
|   |   |   `-- runs
|   |   |-- v4-20260914-010107
|   |   |   |-- images
|   |   |   `-- runs
|   |   |-- v5-20260914-010316
|   |   |   |-- images
|   |   |   `-- runs
|   |   |-- v6-20260914-011234
|   |   |   |-- images
|   |   |   `-- runs
|   |   `-- v7-20260914-012237
|   |       |-- checkpoint-200
|   |       |-- checkpoint-241
|   |       |-- images
|   |       |-- last-checkpoint -> checkpoint-241
|   |       `-- runs
|   `-- output_sft_qwen35_08b
|       `-- v0-20260914-015402
|           |-- checkpoint-200
|           |-- checkpoint-241
|           |-- images
|           |-- last-checkpoint -> checkpoint-241
|           `-- runs
`-- output_agent_dpo_qwen35_08b
    |-- v0-20260914-024708
    |-- v1-20260914-024843
    |-- v2-20260914-025000
    |-- v3-20260914-025101
    |-- v4-20260914-025430
    |-- v5-20260914-025601
    `-- v6-20260914-025709
        |-- checkpoint-1
        |-- images
        |-- last-checkpoint -> checkpoint-1
        `-- runs

root@localhost:~/qwen# ls -lh /root/qwen/output_agent_dpo_qwen35_08b/v6-20260914-025709/checkpoint-1
total 125M
-rw-r--r-- 1 root root 5.2K Sep 14 02:57 README.md
-rw-r--r-- 1 root root 1.2K Sep 14 02:57 adapter_config.json
-rw------- 1 root root  42M Sep 14 02:57 adapter_model.safetensors
-rw-r--r-- 1 root root   67 Sep 14 02:57 additional_config.json
-rw-r--r-- 1 root root  23K Sep 14 02:57 args.json
-rw-r--r-- 1 root root  83M Sep 14 02:57 optimizer.pt
-rw-r--r-- 1 root root  15K Sep 14 02:57 rng_state.pth
-rw-r--r-- 1 root root 1.4K Sep 14 02:57 scheduler.pt
-rw-r--r-- 1 root root 1.8K Sep 14 02:57 trainer_state.json
-rw-r--r-- 1 root root 7.2K Sep 14 02:57 training_args.bin

DPO 训练完成后执行推理测试,验证偏好对齐带来的实际变化。

root@localhost:~/qwen# CUDA_VISIBLE_DEVICES=0 swift infer \
--model /root/qwen/Qwen3.5-0.8B-Agent-SFT-Merged \
--adapters /root/qwen/output_agent_dpo_qwen35_08b/v6-20260914-025709/checkpoint-1 \
--max_new_tokens 512 \
--temperature 0.25

[INFO:swift] Start time of running main: 2026-09-14 02:59:49.806803
[INFO:swift] swift.__version__: 4.6.0.dev0
[INFO:swift] Input `exit` or `quit` to exit the conversation.
[INFO:swift] Input `multi-line` to switch to multi-line input mode.
[INFO:swift] Input `reset-system` to reset the system and clear the history.
[INFO:swift] Input `clear` to clear the history.
<<< 你好
<think>
</think>
您好,请问有什么我可以帮您吗
--------------------------------------------------

测试重点:对比 DPO 前后,工具误触发率、JSON 错误率有没有降下来。

确认效果后执行 DPO LoRA 合并,合并完成即得到最终成品模型。

root@localhost:~/qwen# CUDA_VISIBLE_DEVICES=0 swift export \
--adapters /root/qwen/output_agent_dpo_qwen35_08b/v6-20260914-025709/checkpoint-1 \
--merge_lora true \
--output_dir /root/qwen/Qwen3.5-0.8B-Agent-Final-Merged

[INFO:swift] Global seed set to 42
[INFO:swift] Start time of running main: 2026-09-14 03:02:55.548430
[INFO:swift] swift.__version__: 4.6.0.dev0
[INFO:swift] merge_device_map: None
Loading weights: 100%|████████████████████████████████████████████| 473/473 [00:00<00:00, 1333.95it/s]
Writing model shards: 100%|███████████████████████████████████████| 1/1 [00:02<00:00,  2.37s/it]
[INFO:swift] Successfully merged LoRA and saved in `/root/qwen/Qwen3.5-0.8B-Agent-Final-Merged`.
[INFO:swift] End time of running main: 2026-09-14 03:03:01.458472

root@localhost:~/qwen# ls -lh /root/qwen/Qwen3.5-0.8B-Agent-Final-Merged/
total 1.7G
-rw-r--r-- 1 root root  23K Sep 14 03:03 args.json
-rw-r--r-- 1 root root 7.6K Sep 14 03:03 chat_template.jinja
-rw-r--r-- 1 root root 2.6K Sep 14 03:02 config.json
-rw-r--r-- 1 root root  138 Sep 14 03:02 generation_config.json
-rw------- 1 root root 1.6G Sep 14 03:03 model.safetensors
-rw-r--r-- 1 root root  390 Sep 14 03:03 preprocessor_config.json
-rw-r--r-- 1 root root 1.2K Sep 14 03:03 processor_config.json
-rw-r--r-- 1 root root  20M Sep 14 03:03 tokenizer.json
-rw-r--r-- 1 root root 1.2K Sep 14 03:03 tokenizer_config.json

最终产出模型文件 Qwen3.5-0.8B-Agent-Final-Merged,可以直接转换成 GGUF 格式。

root@localhost:~/qwen# CUDA_VISIBLE_DEVICES=0 swift export \
--model /root/qwen/Qwen3.5-0.8B-Agent-Final-Merged \
--export_format gguf \
--gguf_quantization q4_k_m \
--output_dir /root/qwen/gguf_output

说明:常用量化里,q4_k_m 兼顾速度与效果;q5_k_m 精度更高;q2_k 体积最小。

Feng
这位作者很神秘,还没有填写简介。