1k+ 小数据集 SFT + KTO 微调与评测完整攻略
基于 Qwen3.5-9B + RTX 4090D (24GB) 实战经验总结
一、项目概述
1.1 目标
使用 1,000+ 条 高质量训练数据,通过 SFT + KTO 两阶段微调,让模型学会从需求描述中生成结构化测试用例(JSON 格式)。
1.2 最终成果(测试集评测)
| 指标 | 结果 |
|---|---|
| 格式正确率 | 100% |
| 语义相似度(DeepSeek) | 91.1/100 |
| 采纳率(≥80分) | 86.0% |
| 召回率 | 86.14% |
| 准确率 | 85.68% |
| F1 | 0.859 |
| 隐藏需求覆盖 | 73.7% |
✅ 结论:模型在未训练需求上表现出色,具备直接部署价值。
二、环境准备
2.1 硬件配置
| 组件 | 规格 |
|---|---|
| GPU | NVIDIA RTX 4090D (24GB) × 1 |
| CPU | 16 vCPU Intel Xeon Platinum 8352S |
| 内存 | 62GB |
| 系统盘 | 30GB(⚠️ 仅放系统,模型和数据放数据盘) |
| 数据盘 | 50GB SSD |
2.2 软件版本
| 组件 | 版本 |
|---|---|
| CUDA | 12.8 |
| PyTorch | 2.10.0 |
| Transformers | 5.3.0 |
| ms-swift | 4.4.2 |
| vLLM | 0.17.1(如兼容性问题可改用 Transformers 直接推理) |
2.3 环境安装
# 安装 ms-swift
pip install ms-swift
# 评测依赖(本地/云实例)
pip install openai sentence-transformers scikit-learn tqdm
2.4 ⚠️ 磁盘空间管理(关键)
# 模型文件约 18-20GB,务必设置缓存到数据盘
export MODELSCOPE_CACHE=/your_data_disk/.cache/modelscope
export HF_HOME=/your_data_disk/.cache/huggingface
mkdir -p $MODELSCOPE_CACHE $HF_HOME
否则 30GB 系统盘会被模型下载撑爆,导致容器崩溃。
三、数据准备(阶段0)
3.1 数据格式
{
"messages": [
{"role": "system", "content": "你的任务是帮我生成功能测试用例"},
{"role": "user", "content": "模块:XXX,功能点:XXX..."},
{"role": "assistant", "content": "[{\"testpoint\": \"...\", \"priority\": \"..\", \"steps\": \"...\", \"expectation\": \"...\"}]"}
]
}
3.2 数据拆分(⚠️ 关键)
# 9:1 拆分
训练集: ~900 条 → 用于 SFT + KTO 训练
测试集: ~100 条 → 永久封印,仅用于最终评估
测试集绝不参与任何训练或生成过程,否则泛化评估失效。
四、阶段一:SFT(监督微调)
4.1 核心理念
让模型学会遵循指令、输出 JSON 格式。此阶段模型可能生成质量一般的测试用例,没关系------这正是阶段二(KTO)要解决的。
4.2 参数配置(1k 小数据集专属)
| 参数 | 值 | 说明 |
|---|---|---|
num_train_epochs |
1 ~ 1.5 | 小数据集跑多轮必过拟合 |
learning_rate |
1e-5 | 比常规略低 |
weight_decay |
0.1 | 强正则化防过拟合 |
per_device_train_batch_size |
1 | 9B模型+24GB显存,显存紧张 |
gradient_accumulation_steps |
8 | 等效 batch_size = 8 |
max_length |
2048 | 足够覆盖测试用例长度 |
max_new_tokens |
1536 | 避免截断 |
bf16 |
true | 节省显存 |
4.3 训练命令(ms-swift)
CUDA_VISIBLE_DEVICES=0 \
swift sft \
--model /path/to/Qwen3.5-9B \
--tuner_type lora \
--dataset /path/to/sft_train.json \
--torch_dtype bfloat16 \
--num_train_epochs 1 \
--per_device_train_batch_size 1 \
--learning_rate 1e-5 \
--weight_decay 0.1 \
--warmup_ratio 0.1 \
--lora_rank 8 \
--lora_alpha 32 \
--target_modules all-linear \
--gradient_accumulation_steps 8 \
--save_steps 100 \
--save_total_limit 2 \
--logging_steps 10 \
--max_length 2048 \
--output_dir ./output_sft_v1 \
--dataloader_num_workers 4
4.4 检查点选择
-
训练完成后取 最后一个 checkpoint(如
checkpoint-134)。 -
使用
swift export合并 LoRA 权重为完整模型:
swift export \
--model /path/to/Qwen3.5-9B \
--adapters ./output_sft_v1/checkpoint-134 \
--output_dir ./model_merged \
--merge_lora true
|---------------|----------|----------|---------------|
| 指标 | 开始 | 结束 | 结论 |
| Loss | 1.297 | 0.946 | ✅ 稳步下降,没有过拟合 |
| Token Acc | 71.0% | 77.3% | ✅ 持续提升 |
| 显存占用 | 22.58 GB | 23.07 GB | ✅ 稳定,未超24GB上限 |
| 训练步数 | 134步 | 134步 | ✅ 跑满1个Epoch |
五、阶段二:KTO 数据准备(动态打标)
5.1 核心理念
用 SFT 模型对训练集重新生成回答,对比生成结果与标准答案的相似度,自动打上 label: true/false 标签------这就是 KTO 所需的"好/坏"样本。
5.2 ⚠️ 关键原则
必须用 SFT 模型自己生成,不能用更强的 API 模型替换!
5.3 生成参数(高温制造差异)
| 参数 | 值 | 说明 |
|---|---|---|
temperature |
0.9 | 强制模型产生变异,暴露弱点 |
max_new_tokens |
1536 | 完整生成 |
top_p |
0.9 | 适度多样化 |
5.4 评分与打标
-
使用deepseek推理模型对原回答和SFT模型回答进行打分设置分数阈值,打标签false
首先筛选json格式,若非json格式,打标签flase
若为json格式,进行语义相似度打分,设置阈值筛选flase评分标准:- 90-100 分:测试用例覆盖的核心功能点几乎完全一致,测试场景设计合理且相似
- 80-89 分:核心功能点覆盖完整,测试场景设计基本一致
- 70-79 分:主要功能点有覆盖,但测试场景设计有差异
- 60-69 分:部分功能点有覆盖,测试场景设计差异较大
- 50-59 分:仅有少量功能点重叠
- 0-49 分:测试用例差异很大,几乎没有重叠
5.5 数据输出格式
{
"messages": [...],
"label": true // 或 false
}
六、阶段三:KTO
6.1 核心理念
用二元标签(好/坏)让模型更倾向于生成 true 样本的风格,同时厌恶 false 样本中的错误模式。
6.2 参数配置(1k 小数据集专属)
| 参数 | 值 | 说明 |
|---|---|---|
num_train_epochs |
2 | KTO 收敛快 |
learning_rate |
5e-5 | 比 SFT 低一个数量级 |
| undesirable_weight | 1.2 | ⚠️ 小数据集需加大奖惩力度! |
per_device_train_batch_size |
2 | 根据显存调整 |
6.3 训练命令(ms-swift)
CUDA_VISIBLE_DEVICES=0 \
swift rlhf \
--rlhf_type kto \
--model /root/autodl-tmp/model_merged \
--template qwen \
--tuner_type lora \
--dataset '/root/autodl-tmp/data/kto-train.json' \
--load_from_cache_file true \
--split_dataset_ratio 0.07 \
--num_train_epochs 2 \
--per_device_train_batch_size 1 \
--per_device_eval_batch_size 1 \
--learning_rate 5e-5 \
--lora_rank 8 \
--lora_alpha 32 \
--target_modules all-linear \
--gradient_accumulation_steps 16 \
--eval_steps 100 \
--save_steps 100 \
--save_total_limit 2 \
--logging_steps 5 \
--output_dir output \
--warmup_ratio 0.05 \
--dataloader_num_workers 4 \
--dataset_num_proc 4 \
--beta 0.1 \
--undesirable_weight 1.2
6.4 合并最终模型
|---------------|------------------------|
| 指标 | 数值 |
| 总步数 | 184 steps(2 epochs) |
| 训练时长 | 1小时37分45秒 |
| 训练集大小 | 1469 条 |
| 验证集大小 | 110 条 |
| 平均训练 Loss | 0.242 (良好) |
| 最佳验证 Loss | 0.291 (在 step 100) |
| 可训练参数量 | 21.64M(占全量 0.39%) |
使用 checkpoint-100****进行模型导出和推理
swift export \
--model /root/autodl-tmp/model_merged \
--adapters /root/autodl-tmp/output/v5-20260813-202529/checkpoint-100 \
--output_dir /root/autodl-tmp/model_kto_final \
--merge_lora true
七、模型推理
7.1 方案选择
| 方案 | 速度 | 稳定性 | 推荐场景 |
|---|---|---|---|
| Transformers 直接推理 | 慢(~2-3小时/1000条) | ✅ 高 | 小批量、稳定性优先 |
| vLLM API 服务 | 快(~10-20分钟) | ⚠️ 需环境兼容 | 大批量、生产环境 |
7.2 Transformers 推理命令
使用之前准备好的未经训练的数据集进行推理,便于后续评测
查看官方推荐,设置参数 官方推荐使用以下采样参数组合进行生成:
通用任务的思考模式:temperature=1.0, top_p=0.95, top_k=20, min_p=0.0, presence_penalty=1.5, repetition_penalty=1.0
精确编码任务(如 WebDev)的思考模式:temperature=0.6, top_p=0.95, top_k=20, min_p=0.0, presence_penalty=0.0, repetition_penalty=1.0
通用任务的指令(或非思考)模式:temperature=0.7, top_p=0.8, top_k=20, min_p=0.0, presence_penalty=1.5, repetition_penalty=1.0
推理任务的指令(或非思考)模式:temperature=1.0, top_p=0.95, top_k=20, min_p=0.0, presence_penalty=1.5, repetition_penalty=1.0
请注意,不同推理框架对采样参数的支持情况可能有所不同。任务定位:属于“精确编码任务”
让模型从提问的需求中提取功能点并生成结构化的JSON数组,这本质上是信息提取 + 结构化输出
因此,参数设置如下:
temperature=0.1
top_p=0.95
top_k=20
min_p=0.0
presence_penalty=1.5
repetition_penalty=1.0
7.3 vLLM 推理(如果兼容)
CUDA_VISIBLE_DEVICES=0 python -m vllm.entrypoints.openai.api_server \
--model ./model_kto_final \
--served-model-name testcase-generator \
--dtype bfloat16 \
--port 6006
八、评测体系
8.1 评测框架
使用 eval_results.jsonl(每条包含 expected 和 actual 数组),通过 DeepSeek API 进行语义评分。
8.2 核心指标
数据规模与格式
| 指标 | 计算方式 |
|---|---|
| 格式正确率 | 有效解析数 / 总样本数 |
| 平均测试点数 | 总测试点数 / 总样本数 |
| 生成/标准比例 | 生成总数 / 标准总数 |
语义质量(样本级)
| 指标 | 计算方式 | 阈值 |
|---|---|---|
| 平均相似度 | 所有样本 DeepSeek 评分的均值 | --- |
| 采纳率 | 得分 ≥ 80 的样本数 / 总样本数 |
80分 |
召回率与准确率(测试点级)
| 指标 | 计算方式 | 含义 |
|---|---|---|
| 召回率 | 匹配成功数 / 标准测试点总数 |
标准测试点被覆盖的比例 |
| 准确率 | 匹配成功数 / 生成测试点总数 |
生成测试点中有效的比例 |
| F1 | 2 × P × R / (P + R) |
综合平衡指标 |
隐藏需求检测
通过关键词匹配检测模型是否识别隐含约束:
关键词: 权限、安全、兼容、性能、异常、边界、并发、超时、加密、解密、日志、缓存、隔离、容错
8.3 综合评级标准
# 针对测试用例生成任务(低温度,高精度)
if precision > 0.9 and recall > 0.65 and adoption_rate > 0.8:
⭐⭐⭐ 优秀:可直接部署
elif precision > 0.8 and recall > 0.5 and adoption_rate > 0.7:
⭐⭐ 良好:基本可用,可优化
elif precision > 0.7 and recall > 0.35:
⭐ 一般:需要改进
else:
❌ 需改进:重新评估策略
以上评级标准基于本项目业务场景(测试用例生成)设定,供参考。
九、常见问题与避坑指南
9.1 vLLM 启动失败
现象:ValueError: Model architectures [...] are not supported
原因:vLLM 版本与 Transformers 版本不兼容。
解决:改用 Transformers 直接推理(稳定,虽然慢但兼容性好)。
9.2 磁盘空间不足
现象:系统盘 100%,容器异常。
解决:
-
将
MODELSCOPE_CACHE和HF_HOME指向数据盘。 -
定期清理
~/.cache。
9.3 显存不足(OOM)
解决:
-
降低
per_device_train_batch_size(从 4 降到 2 或 1) -
降低
max_length(从 2048 降到 1024) -
启用
bf16或fp16
9.4 所有测试点都被标为 false
原因:生成内容与标准答案格式差异巨大(如自然语言 vs JSON)。
解决:
-
在 Prompt 中强制要求 JSON 格式输出
-
关闭思考模式
-
增加上下文长度
十、总结
| 阶段 | 核心动作 | 关键产出 |
|---|---|---|
| 阶段0(数据准备) | 9:1 拆分,格式化为 messages | sft_train.json + dev.json |
| 阶段1(SFT) | 1 轮 LoRA 微调 | SFT 模型(checkpoint-134) |
| 阶段2(动态打标) | 高温生成 + 评分打标 | KTO 数据(含 label) |
| 阶段3(KTO) | undesirable_weight=1.2+ 2 轮训练 | KTO 最终模型 |
| 阶段4(推理) | Transformers/vLLM 生成 | generated.jsonl |
| 阶段5(评测) | DeepSeek 语义评分 + 贪心匹配 | 评测报告(召回率/准确率/F1) |
核心心法:小数据集 = 防过拟合 + 强制挖负样本 + 加大奖惩。跑通全流程后,86% 的采纳率证明这套方案完全可行!🚀