微调与评测
通用模型的问题往往不是「不够聪明」,而是不按你的业务口径说话。 微调解决口径问题,评测解决「怎么证明它真的变好了」的问题。 这两件事通常要一起做 —— 没有评测集的微调,效果只能靠感觉。
什么时候需要微调
先说什么时候不需要。以下情况用提示词或 Skill 更快也更便宜:
- 只是想改变回答的语气、长度或格式 —— 写进 system prompt 即可
- 需要模型知道你的课程库、会员权益这类会变的数据 —— 用检索或 tool calling
- 只有几十条样本 —— 数据量不足以稳定改变模型行为,放进提示词当少样本示例
真正值得微调的是这几类:
- 专属的判断标准。比如你的品牌规定「BMI 超过 28 的用户一律不推荐跳跃类动作」, 这是业务规则不是通识,模型默认不知道
- 特定的话术体系。训练营教练的说话方式、品牌的内容调性, 提示词能模仿个七分,微调能到九分
- 成本优化。把 pro 上验证过的能力蒸馏到 lite 上, 多数客户在这一项上拿到的收益最大
准备数据
训练文件是 JSONL,每行一个完整对话。至少 500 条,1000 到 3000 条是效果与成本的 平衡点。
{"messages":[{"role":"system","content":"你是「晨跑营」的带队教练。"},{"role":"user","content":"下雨天还跑吗"},{"role":"assistant","content":"小雨照跑,注意换双抓地好的鞋,跑完立刻换干衣服。雷雨就停,我们群里会提前通知改期。"}]}
{"messages":[{"role":"system","content":"你是「晨跑营」的带队教练。"},{"role":"user","content":"我 BMI 30,能跟着跳绳课练吗"},{"role":"assistant","content":"先不上跳绳。你这个体重下膝踝的冲击太大,我们先用快走加固定单车打四周基础,等体重降下来再加跳跃类。"}]}
1000 条由真人教练审核过的对话,效果远好于 10000 条从日志里随便捞的。 尤其要保证「拒绝类」样本的比例 —— 模型该说不的时候说不, 这部分样本缺失是微调后翻车最常见的原因。
上传文件
train = client.files.create(
file=open("train.jsonl", "rb"),
purpose="fine-tune",
)
valid = client.files.create(
file=open("valid.jsonl", "rb"),
purpose="fine-tune",
)
print(train.id) # file_01JQ9C7T4M
curl "$KOACH_BASE_URL/files" \
-H "Authorization: Bearer $KOACH_API_KEY" \
-F purpose="fine-tune" \
-F file="@train.jsonl"
创建微调任务
| 参数 | 类型 | 说明 | |
|---|---|---|---|
model | string | 必填 | 基座模型。keepace-lite 或 keepace-pro |
training_file | string | 必填 | 上一步返回的文件 ID |
validation_file | string | 选填 | 验证集。强烈建议提供,否则无法判断是否过拟合 |
method | string | 默认 lora | lora 快且便宜,适合话术与口径对齐;
sft 全参微调,适合需要改变知识边界的场景 |
suffix | string | 选填 | 产出模型名的后缀,便于区分。如 morning-run-v2 |
hyperparameters | object | 选填 | n_epochs / learning_rate_multiplier /
batch_size,不传时自动推断 |
eval_suite | string | 选填 | 训练完成后自动跑一次领域评测,见下一节 |
job = client.fine_tuning.jobs.create(
model="keepace-lite",
training_file=train.id,
validation_file=valid.id,
suffix="morning-run-v2",
extra_body={
"method": "lora",
"eval_suite": "sport.safety.v3", # 训练后自动跑安全性评测
},
)
print(job.id, job.status) # ftjob_01JQ9D2K8R queued
查询进度
状态依次为 queued → running →
evaluating → succeeded。
LoRA 在千条量级通常 40 分钟内完成,全参 SFT 要几个小时。
{
"id": "ftjob_01JQ9D2K8R",
"object": "fine_tuning.job",
"status": "succeeded",
"model": "keepace-lite",
"fine_tuned_model": "ft:keepace-lite:acme:morning-run-v2:9dK2",
"method": "lora",
"trained_tokens": 1284310,
"metrics": {
"train_loss": 0.412,
"valid_loss": 0.487
},
"eval_report": "evalrun_01JQ9F1X3B"
}
拿到 fine_tuned_model 后当作普通模型 ID 用即可,
不需要改任何调用代码:
resp = client.chat.completions.create(
model="ft:keepace-lite:acme:morning-run-v2:9dK2",
messages=[{"role": "user", "content": "我 BMI 30,能跟着跳绳课练吗"}],
)
领域评测
这是 L2 交付物里最容易被低估的一块。通用榜单分数说明不了模型在你的业务里能不能用; 能进内部评审会的是一份写清楚了评分口径、样本构成和失败案例的报告。
内置评测集
| 套件 | 题量 | 考察 |
|---|---|---|
sport.safety.v3 | 860 | 损伤禁忌、疾病边界、特殊人群(孕产、青少年、高龄)的拒答与转诊准确率 |
sport.prescription.v2 | 1,240 | 训练处方的科学性:强度分区、周期化、加量幅度、恢复安排 |
sport.metrics.v2 | 720 | 运动数据解读:心率、配速、功率、VO₂max、HRV 的口径与归因 |
nutrition.basic.v1 | 540 | 营养建议、热量估算、补剂边界 |
发起评测
可以同时评多个模型做横向对比 —— 这是最常见的用法: 拿你现在在用的通用模型、keepace 原版、微调后的版本跑同一套题。
run = client.evaluations.create(
suite="sport.safety.v3",
models=[
"keepace-lite",
"ft:keepace-lite:acme:morning-run-v2:9dK2",
],
extra_body={
"judge": "hybrid", # llm | human | hybrid
"human_sample_rate": 0.1, # 抽 10% 交专业团队人工复核
"report_format": "pdf",
},
)
print(run.id) # evalrun_01JQ9F1X3B
curl "$KOACH_BASE_URL/evaluations" \
-H "Authorization: Bearer $KOACH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"suite": "sport.safety.v3",
"models": ["keepace-lite", "ft:keepace-lite:acme:morning-run-v2:9dK2"],
"judge": "hybrid",
"human_sample_rate": 0.1,
"report_format": "pdf"
}'
| 参数 | 类型 | 说明 | |
|---|---|---|---|
suite | string | 必填 | 内置套件 ID,或你自己上传的评测集文件 ID |
models | array | 必填 | 待评模型,最多 5 个。可以混入非 koach 的模型做对照, 此时需要额外提供其 endpoint 与密钥 |
judge | string | 默认 llm | llm 全自动;human 全人工;
hybrid 自动评分 + 抽样人工复核 |
human_sample_rate | number | 默认 0.05 | 人工复核比例。hybrid 时生效 |
rubric | object | 选填 | 覆盖默认评分维度与权重,用于对齐你自己的验收标准 |
评测结果
{
"id": "evalrun_01JQ9F1X3B",
"suite": "sport.safety.v3",
"status": "completed",
"results": [
{
"model": "keepace-lite",
"overall": 0.842,
"dimensions": {
"contraindication_recall": 0.871,
"referral_precision": 0.906,
"special_population": 0.764,
"false_refusal_rate": 0.058
}
},
{
"model": "ft:keepace-lite:acme:morning-run-v2:9dK2",
"overall": 0.913,
"dimensions": {
"contraindication_recall": 0.948,
"referral_precision": 0.921,
"special_population": 0.882,
"false_refusal_rate": 0.041
}
}
],
"human_reviewed": 86,
"report_url": "https://api.koach.ai/v1/evaluations/evalrun_01JQ9F1X3B/report.pdf",
"failures_url": "https://api.koach.ai/v1/evaluations/evalrun_01JQ9F1X3B/failures.jsonl"
}
总分从 0.84 提到 0.91 只是一个数字,真正有用的是失败样本文件 —— 它会告诉你模型具体在哪一类问题上还会出错。 我们见过总分很高但在孕产人群上系统性出错的模型, 这种问题只有翻失败样本才发现得了。
批量推理
给全量用户生成周报、给整个课程库打标签这类离线任务,不要用在线接口硬跑。 批量接口有独立额度池,单价按五折计, 代价是延迟按小时计算。
# 每行是一个标准的 chat/completions 请求体
batch_file = client.files.create(
file=open("weekly_reports.jsonl", "rb"),
purpose="batch",
)
batch = client.batches.create(
input_file_id=batch_file.id,
endpoint="/v1/chat/completions",
completion_window="24h",
)
print(batch.id, batch.status) # batch_01JQ9G8M2V validating
私有化下的微调
私有化部署里这套接口完全一致,但训练发生在客户自己的机器上, 数据与产出的权重都不出域。差别只有两点:
- 需要在部署时预留训练资源,具体规格由 FDE 在实施阶段与你的基础设施团队确认
- 评测的人工复核环节,如果涉及把样本给到 Keep 的专业团队,
需要单独走数据出域审批;也可以只用
judge: "llm"全自动模式, 全程不出域