ACP 大模型应用开发之用蒸馏让小模型掌握专业能力实战下

写在前面:接上篇之后要做什么

上篇走完「教师模型打标 → 过滤 → Baseline」,基座 0.6B 的 JSON 合规率和路由准确率往往惨不忍睹,而教师模型已经稳定。本篇要做的事很直白:

  1. 搞懂微调在学什么(损失、梯度、LoRA 为什么省参)
  2. 用 LoRA 把教师能力灌进 0.6B(ms-swift + GPU)
  3. 同一套尺子复评,看蒸馏模型能否逼近教师
  4. 算一笔账,决定 API 还是自建小模型

读完本篇,你能学会什么?

主题 你会学到
微调原理 损失函数、梯度下降、学习率对收敛的影响
LoRA 为什么只训 0.4% 参数仍能学会任务、如何避免灾难性遗忘
训练状态判断 用 train/eval loss 区分欠拟合、过拟合、训练失败
LoRA 实战 ms-swift 参数含义、调参方向、loss 曲线解读
蒸馏复评 基座 / 教师 / 学生三方对比,JSON 合规率与路由准确率的意义
能力边界 什么任务适合蒸馏、什么该用 RAG 或大模型
成本决策 API 按 token 计费 vs GPU 按实例时长计费的盈亏平衡点

一、微调原理:模型是怎么学会「读懂请求」的

在开始训练之前,先弄清模型是如何通过训练数据学习的。蒸馏在数据侧与微调不同(教师生成 vs 人工标注),训练侧仍是标准的监督微调(SFT)

1.1 模型如何学习

损失函数

模型训练的核心是一个迭代优化过程:给定输入,模型生成预测;将预测与期望输出对比,计算损失(loss);然后调整模型参数,使损失变小。损失函数量化了「预测与期望的差距」——损失越小,说明模型输出越接近训练数据中的期望输出(本场景即 assistant 角色的结构化 JSON)。

梯度下降

如何找到让损失最小的参数?梯度下降从一个随机起点出发,每一步沿「损失下降最快的方向」调整参数,逐步逼近较优解。

每一步的调整幅度由学习率(learning rate)决定:

学习率 现象 后果
合适 损失稳步下降 在较短时间内找到可用参数
过低 能收敛,但步幅小 训练轮次多、耗时与算力消耗大
过高 在最优解附近震荡或跳过最低点 损失不降甚至上升,训练失败
image.png

1.2 LoRA:高效微调

全参数微调需要更新模型的所有权重。大模型动辄数十亿参数,计算和存储成本都很高。LoRA(Low-Rank Adaptation) 是一种高效的替代方案。

LoRA 的核心思想:不直接修改原始权重矩阵 W,而是在旁边「挂」一对小的低秩矩阵来学习变化量 ΔW。

image.png

假设原始权重矩阵 W 的维度是 d × d,LoRA 将变化量 ΔW 分解为两个小矩阵的乘积:

ΔW = A × B

其中 A 的维度是 d × r,B 的维度是 r × d,r 远小于 d(r 即 lora_rank)。

例如,当 d = 1024、r = 8 时:

  • 全参数微调:约 1024 × 1024 ≈ 100 万 个参数要更新
  • LoRA:约 1024 × 8 × 2 ≈ 1.6 万 个参数 → 约占该层全参的 1.56%(全模型只对部分层挂 LoRA 时,整体可训练参数占比可低至约 0.4%

Java:单层 LoRA 参数量估算

/** LoRA 可训练参数量 ≈ 2×d×r;全参微调 ≈ d² */
public static LoraParamEstimate estimateParams(int matrixDim, int loraRank) {
    long full = (long) matrixDim * matrixDim;
    long lora = 2L * matrixDim * loraRank;
    double pct = full > 0 ? lora * 100.0 / full : 0;
    return new LoraParamEstimate(matrixDim, loraRank, full, lora, pct);
}

// 使用:d=1024, r=8 → full=1_048_576, lora=16_384, 占该层≈1.56%
var est = estimateParams(1024, 8);
System.out.printf("全参=%d, LoRA=%d, 占该层=%.2f%%%n",
    est.fullFineTuneParams(), est.loraTrainableParams(), est.loraRatioPercent());

lora_rank(即 r) 控制低秩矩阵的维度:r 越大,可学习的变化量越多,训练成本也越高。对「请求理解」这类结构化提取任务,r = 8 通常够用。LoRA 还有一个重要优势:只训练「补丁」参数,原始模型权重不变,能保留基座绝大部分通用能力,有效减轻灾难性遗忘

1.3 训练状态判断

训练过程中关注两个指标:

指标 含义
training loss 模型在训练数据上的表现
evaluation loss 模型在未参与训练的验证集上的表现(本示例用 split_dataset_ratio 0.1 自动切分)

结合两者变化趋势判断状态:

image.png

二、LoRA 训练实战

环境依赖与基座模型下载见上篇「动手前:环境怎么配」。训练流程分四步:

步骤 说明
1. 准备数据 确认 resources/4_1/train.jsonl 可访问(messages 格式)
2. 启动训练 运行下方 swift sft 命令;A10 级 GPU 上约 10–15 分钟
3. 监控 Loss 观察训练日志中的损失变化,判断训练状态
4. 查看结果 模型和日志保存在 output/model/<训练目录>/(见下方说明),loss 曲线图在其 images/ 子目录,TensorBoard 日志在 runs/ 子目录

ms-swift 会为每次训练自动建目录,,命名格式为 v{版本号}-{日期}-{时间}。命名形如 v1-20260415-145518。训练完成后在 output/model/ 下找到对应目录即可。

2.1 准备训练数据

确保 train.jsonl 在当前工作目录下可访问。每条样本为 system / user / assistant 三角色对话(格式见上篇 §3.6)。

2.2 开始训练

使用 ms-swift 进行 LoRA 微调。关键参数说明:

参数 本示例取值 含义
--model 基座模型本地路径 上篇下载的 Qwen3-0.6B
--model_type qwen3 显式指定类型 从本地路径加载时 ms-swift 可能无法自动识别
--split_dataset_ratio 0.1 10% 作验证集 监控过拟合
--lora_rank 8 LoRA 秩 结构化多字段任务,8 比 4 更稳
--num_train_epochs 5 训练轮次 验证 loss 上升则减少
--learning_rate 5e-5 学习率 loss 不降可试 1e-4,震荡可试 1e-5
--per_device_train_batch_size 8 每卡 batch 显存不足改为 4
--max_length 512 最大序列长 由 token 统计决定
--eval_steps 20 每 20 步算 eval loss 观察过拟合

调参方向参考(非固定值,需结合数据规模与任务复杂度):

参数 调优方向
lora_rank 数据不足 200 条且输出字段简单可试 4;超过 2000 条或任务更复杂可试 16
learning_rate loss 不降 → 调高;loss 震荡 → 调低
num_train_epochs eval loss 上升 → 减轮次;仍在下降 → 可加轮次
per_device_train_batch_size 显存不足 → 减小

本示例训练集约百条,但「请求理解」需同时学习 8 类多选意图、部门、紧急度、实体、路由 五维联合映射,复杂度高于单标签分类,故选 r=8 而非 r=4。

Java:生成训练命令(不执行,便于复制到 GPU 机器)

public static String buildSwiftSftShell(LoraTrainConfig c) {
    return """
        swift sft \\
            --model %s \\
            --model_type %s \\
            --dataset %s \\
            --split_dataset_ratio %s \\
            --learning_rate %s \\
            --lora_rank %d \\
            --num_train_epochs %d \\
            --per_device_train_batch_size %d \\
            --max_length %d \\
            --eval_steps %d \\
            --save_strategy %s \\
            --save_steps %d
        """.formatted(
            c.modelPath(), c.modelType(), c.datasetPath(),
            "0.1", "5e-5", c.loraRank(), c.numTrainEpochs(),
            c.perDeviceTrainBatchSize(), c.maxLength(),
            c.evalSteps(), c.saveStrategy(), c.saveSteps())
        .strip();
}

LoraTrainConfig cfg = new LoraTrainConfig(
    "/mnt/workspace/model", "resources/4_1/train.jsonl",
    "qwen3", 0.1, 5e-5, 8, 5, 8, 512, 20, "steps", 20);
System.out.println(buildSwiftSftShell(cfg));

启动命令(Python / GPU,需在 shell 中执行)

# LoRA 微调 Qwen3-0.6B(A10 约 10–15 分钟)
# 建议在 GPU 机器上后台运行,避免终端长时间刷屏
cd /path/to/your/workspace
nohup swift sft \
    --model /mnt/workspace/model \
    --model_type qwen3 \
    --dataset resources/4_1/train.jsonl \
    --split_dataset_ratio 0.1 \
    --learning_rate 5e-5 \
    --lora_rank 8 \
    --num_train_epochs 5 \
    --per_device_train_batch_size 8 \
    --max_length 512 \
    --eval_steps 20 \
    --save_strategy steps \
    --save_steps 20 \
    > /tmp/swift_train.log 2>&1 &

echo "训练 PID: $!"
# 训练中: tail -20 /tmp/swift_train.log
# 是否结束: pgrep -f "swift sft"

2.3 训练结果观察

训练完成后,ms-swift 会在训练日志末尾打印输出目录路径(images_dir),例如:

images_dir: /mnt/workspace/aliyun_acp_learning/大模型ACP认证教程/C4_交付上线/output/model/vx-xxxxxxxx-xxxxxx/images

其中 vx-xxxxxxxx-xxxxxx 是 ms-swift 自动生成的训练目录名(格式 v{版本}-{YYYYMMDD}-{HHMMSS}),每次训练的目录名不同,以你实际看到的为准。

有三种方式查看 loss 变化:

  • 曲线图(最直观):在 DSW 文件浏览器中打开上述 images/ 目录,双击 loss 曲线图即可查看。
  • TensorBoard 日志:同一训练目录下的 runs/ 子目录保存了 TensorBoard 日志。在 Terminal 中运行。
    tensorboard --logdir output/model/ 可启动交互式面板。
  • 训练日志:上方 cell 的输出中包含每一步的 loss 值,可以直接观察数字是否在下降。

对照 §1.3:train loss 与 eval loss 同步下降 为正常学习;eval loss 先降后升 为过拟合信号。

2.4 合并 LoRA 参数

训练完成后,将 LoRA 权重与基座合并,得到可独立部署的完整模型(输出目录一般为 checkpoint-xxx-merged)。

Python(等价 glob 逻辑,在 GPU 环境执行 merge)

import glob

# 自动查找最新的 LoRA checkpoint(排除已合并的目录)
all_ckpts = sorted(glob.glob("output/model/*/checkpoint-*"))
ckpts = [c for c in all_ckpts if not c.endswith('-merged')]

if not ckpts:
    print("未找到 checkpoint,请先运行 Section 6.2 的训练命令。")
else:
    latest_ckpt = ckpts[-1]
    print(f"找到 checkpoint:{latest_ckpt}")
    print(f"开始合并 LoRA 参数...")
    !swift export --adapters {latest_ckpt} --merge_lora true

2.5 常见问题

问题 可能原因 处理方法
训练损失不降 学习率过高 降至 1e-5 重试
验证损失上升 过拟合 减少 num_train_epochs 或增加数据
JSON 格式崩坏 训练数据 JSON 不统一 检查过滤与 messages 格式
意图遗漏 多意图样本不足 训练集中多意图样本建议 ≥ 30%
显存不足 batch 或 max_length 过大 per_device_train_batch_size 改为 4

二、6、试试用 Qwen Code 完成训练(可选的另一种方式约定好边界,使用Qwen Code去实现)

你可以直接运行下方代码 cell 手动启动训练,也可以试试另一种方式:把提示词复制到 Qwen Code 中,让它帮你完成训练,并在过程中逐步讲解每个阶段正在发生什么。

启动后,将以下提示词粘贴到 Qwen Code 中:

我正在学习模型蒸馏,需要你帮我完成一次 LoRA 微调训练。请按以下步骤执行,每一步都向我解释你在做什么、为什么这么做。

环境信息

  • 基座模型路径:/mnt/workspace/model(Qwen3-0.6B,0.6B 参数)
  • 训练数据:resources/4_1/train.jsonl(messages 格式,每条包含 system/user/assistant 三个角色)
  • 框架:ms-swift(系统环境已安装,直接使用 swift 命令即可)
  • GPU:A10(30GB 显存)
  • 工作目录:/mnt/workspace/aliyun_acp_learning/大模型ACP认证教程/C4_交付上线/
  • ⚠️ 不要激活 llm_learn 虚拟环境,不要运行 source activate。venv 中的 PyTorch 版本与系统 CUDA 不兼容,激活后 GPU 不可用。直接使用系统默认环境。

重要约束

  • 不要读取 notebook 文件(.ipynb)来查看已有的训练命令或参数。请根据你自己对数据的分析来独立决定参数值。
  • 不要运行 swift --version(会报错)、不要运行 swift sft --help(输出不完整,没有参考价值)。
  • 不要访问 GitHub 或其他外部网站查文档(当前环境无法访问外网)。
  • 训练参数的决策依据应来自你对训练数据的实际分析(样本数、token 长度等),而非查阅文档。

请执行以下步骤

第 1 步:验证环境

  • 确认当前没有激活任何虚拟环境(命令行提示符前不应有 (llm_learn) 等前缀)
  • 验证 GPU 是否可用:运行 python3 -c "import torch; print('GPU可用:', torch.cuda.is_available(), '| 设备数:', torch.cuda.device_count())"
  • 如果输出 False,检查是否误激活了虚拟环境,运行 deactivate 退出后重试
  • GPU 验证通过后才能继续下一步
  • 确认 swift 命令存在(运行 which swift,不要运行 swift --version)
  • 切换到工作目录
  • 确认基座模型文件存在
  • 确认训练数据文件存在并统计样本条数

第 2 步:分析训练数据并决定训练参数

先对训练数据做以下分析(用 Python 脚本,不要用 swift 命令):

  1. 统计总样本数
  2. 读取 5-10 条样本,了解数据格式,向我解释 system/user/assistant 三个角色各自的内容
  3. 用 tokenizer 计算每条样本的 token 长度分布(最小值、最大值、平均值、P95),代码示例:
    from transformers import AutoTokenizer
    tokenizer = AutoTokenizer.from_pretrained("/mnt/workspace/model", trust_remote_code=True)
    # 对每条样本拼接 system+user+assistant 内容后 tokenize,统计长度
    
    然后根据分析结果,为以下参数选择值,并向我解释每个参数的含义和你选择该值的理由:
  • --lora_rank:LoRA 低秩矩阵维度。考虑因素:任务需要同时学习 5 个字段(intents/department/urgency/entities/route)的联合映射

  • --learning_rate:学习率。考虑因素:数据规模和模型大小

  • --num_train_epochs:训练轮次。考虑因素:数据量大小,轮次过多会过拟合

  • --per_device_train_batch_size:每张 GPU 的批次大小。考虑因素:A10 显存 30GB,模型 0.6B 参数

  • --max_length:最大序列长度。根据你上面统计的 token 长度 P95 来决定,留出一定余量即可

    以下参数请固定使用这些值(用于确保生成 loss 曲线图和验证集评估):
    --model /mnt/workspace/model
    --model_type qwen3
    --dataset resources/4_1/train.jsonl
    --split_dataset_ratio 0.1
    --eval_steps 20
    --save_strategy steps
    --save_steps 20

第 3 步:启动训练

⚠️ 训练约需 10-15 分钟。必须以后台方式运行,否则日志持续输出会导致终端闪屏,且命令可能因超时被终止。

按以下方式启动:

cd /mnt/workspace/aliyun_acp_learning/大模型ACP认证教程/C4_交付上线/
nohup swift sft [你决定的参数] > /tmp/swift_train.log 2>&1 &
echo "训练 PID: $!"

启动后,每隔 2-3 分钟运行 tail -20 /tmp/swift_train.log 查看最新日志,向我汇报:

  • 当前训练进度(第几个 epoch、第几步)
  • loss 的变化趋势(是否在正常下降)
  • 如果出现异常(如 loss 突然飙升),提醒我并建议处理方式

判断训练是否结束:运行 pgrep -f "swift sft" 检查进程是否还在。进程消失说明训练已完成,此时再查看日志末尾确认最终状态。

第 4 步:训练完成后的分析

训练结束后:

  1. 找到输出目录(output/model/ 下最新的目录)
  2. 在该目录的 images/ 子目录下找到 loss 曲线图片
  3. 分别展示 train/loss 和 eval/loss 的曲线图
  4. 对曲线进行解读:
    • 训练是否正常收敛?
    • 有没有过拟合迹象?(eval/loss 是否出现上升)
    • 最终的 loss 值是否合理?
  5. 根据 loss 曲线给出你的判断:这次训练算成功吗?

第 5 步:准备下一步

  • 告诉我合并后的模型会保存在哪里
  • 简要说明接下来可以做什么(合并 LoRA 参数、评测效果)
    如果你选择使用 Qwen Code 完成训练,可以跳过下方代码 cell。如果你更习惯手动执行,直接运行下方命令即可,参数含义已在上文说明。

LoRA 微调 Qwen3-0.6B(A10 GPU 约 10 分钟)

!swift sft
--model /mnt/workspace/model
--model_type qwen3
--dataset resources/4_1/train.jsonl
--split_dataset_ratio 0.1
--learning_rate 5e-5
--lora_rank 8
--num_train_epochs 5
--per_device_train_batch_size 8
--max_length 512
--eval_steps 20
--save_strategy steps
--save_steps 20

三、蒸馏效果评测

训练并合并 LoRA 后,用与上篇相同的测试集与指标评测蒸馏模型,并与基座、教师对比。

3.1 评测流程

推理阶段(加载合并后的小模型)依赖 ModelScope + PyTorch,保留 Python:

import glob
import json
from modelscope import AutoModelForCausalLM, AutoTokenizer
import torch

merged_dirs = sorted(glob.glob("output/model/*/checkpoint-*-merged"))
if not merged_dirs:
    print("未找到合并后的 checkpoint,请先执行 §2.4 合并。")
else:
    student_path = merged_dirs[-1]
    print(f"加载蒸馏模型:{student_path}")

    student_tokenizer = AutoTokenizer.from_pretrained(student_path, trust_remote_code=True)
    student_model = AutoModelForCausalLM.from_pretrained(
        student_path, dtype=torch.float16, device_map="auto", trust_remote_code=True
    )

    test_data = []
    with open("resources/4_1/test_30.jsonl", "r", encoding="utf-8") as f:
        for line in f:
            test_data.append(json.loads(line))

    student_preds = []
    for i, item in enumerate(test_data):
        messages = [
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": item["query"]},
        ]
        text = student_tokenizer.apply_chat_template(
            messages, tokenize=False, add_generation_prompt=True, enable_thinking=False
        )
        inputs = student_tokenizer(text, return_tensors="pt").to(student_model.device)
        with torch.no_grad():
            outputs = student_model.generate(
                **inputs, max_new_tokens=512, temperature=0.1, do_sample=True
            )
        response = student_tokenizer.decode(
            outputs[0][inputs.input_ids.shape[1]:], skip_special_tokens=True
        )
        parsed = validate_label(response)
        student_preds.append({"raw": response, "parsed": parsed})
        print(f"  [{i+1}/{len(test_data)}] {'OK' if parsed else 'FAIL'} | {item['query'][:50]}...")

    ground_truths = [item["ground_truth"] for item in test_data]
    student_results = evaluate_predictions(student_preds, ground_truths)
    print_eval_results(student_results, "蒸馏模型")

打分与对比表(与上篇同一套指标逻辑):

public static double computeIntentF1(List<String> pred, List<String> truth) {
    Set<String> p = new HashSet<>(pred), t = new HashSet<>(truth);
    if (p.isEmpty() || t.isEmpty()) return 0.0;
    Set<String> inter = new HashSet<>(p);
    inter.retainAll(t);
    double precision = inter.size() * 1.0 / p.size();
    double recall = inter.size() * 1.0 / t.size();
    return 2 * precision * recall / (precision + recall);
}

public static EvalResult evaluatePredictions(
        List<Prediction> predictions, List<Map<String, Object>> groundTruths) {
    int jsonValid = 0, routeCorrect = 0;
    double intentF1Sum = 0;
    for (int i = 0; i < groundTruths.size(); i++) {
        Map<String, Object> parsed = predictions.get(i).parsed();
        if (parsed == null) continue;
        jsonValid++;
        intentF1Sum += computeIntentF1(
            (List<String>) parsed.get("intents"),
            (List<String>) groundTruths.get(i).get("intents"));
        if (Objects.equals(parsed.get("route"), groundTruths.get(i).get("route"))) {
            routeCorrect++;
        }
    }
    int n = groundTruths.size();
    return new EvalResult(jsonValid, intentF1Sum / n, routeCorrect, n);
}

public static String renderComparisonMarkdown(List<EvalResult> results, List<String> names) {
    StringBuilder sb = new StringBuilder();
    sb.append("| 指标 | ").append(String.join(" | ", names)).append(" |\n");
    sb.append("| --- | ").append(" --- |".repeat(names.size())).append("\n");
    // 各行:JSON 合规率、intents F1、route 准确率
    return sb.toString();
}

// 博客参考值(无 GPU 时也可先出表)
EvalResult base = new EvalResult(15, 0.237, 5, 31);
EvalResult teacher = new EvalResult(31, 0.839, 28, 31);
EvalResult student = new EvalResult(31, 0.704, 24, 31);
System.out.println(renderComparisonMarkdown(
    List.of(base, teacher, student),
    List.of("基座 (0.6B)", "教师 (qwen3.6-plus)", "蒸馏 (0.6B)")));

3.2 三方对比(参考结果)

若尚未在本地跑完全部评测,可参考下表(与上篇 Baseline 同一测试集):

指标 基座模型 (0.6B) 教师模型 (qwen3.6-plus) 蒸馏模型 (0.6B)
JSON 合规率 15/31 (48%) 31/31 (100%) 31/31 (100%)
intents F1 23.7% 83.9% 70.4%
route 准确率 5/31 (16%) 28/31 (90%) 24/31 (77%)

解读摘要

  • JSON 合规率:蒸馏后几乎都能解析为合法 JSON,格式问题从「最大短板」变为「基本达标」。
  • intents F1 / route 准确率:相较基座大幅提升,说明学到了教师的判断逻辑;路由准确率对生产最关键——决定请求能否进对处理模块。
  • 与教师的差距:0.6B 尚未完全追平教师,但已接近该参数量级上的能力天花板;若需更高精度,可换 1.5B / 4B 作学生。

3.3 蒸馏的能力边界

蒸馏不是万能的。以下情况蒸馏后仍容易出错:

类型 示例 原因
意图间有依赖 「请假后自动调整考勤」 多意图有因果关系,小模型难捕捉
训练集未覆盖的组合 「IT支持 + 差旅申请」且数据中少见 模型会猜错
极度含糊 「那个事情帮我处理一下」 缺少可抓取信号

0.6B 适合:高频、固定域、单步判断的专用任务。在系统中的角色是预处理器,不是小号通用助手。

3.4 你的任务适合蒸馏吗

投入算力前先快速自检:

适合蒸馏

  • 输出格式固定,可用规则 / Schema 自动校验
  • 不依赖实时外部知识,只看输入文本
  • 调用频次高,成本敏感
  • 教师表现稳定,错误有规律(非随机噪声)

不适合及替代

快速验证流程:正式训练前,建议先用少量样本做一次快速实验。如果基座模型已经能基本完成需求,或微调后提升不明显,可以重新评估任务选择,或调整优化策略(比如增加训练数据的多样性、调整教师 prompt)。

数据分布与生产不匹配也是常见陷阱。合成数据的表达模式可能与真实用户的提问方式存在差异,导致模型在生产环境中效果下降。可以适当混入真实数据,帮助模型适应实际分布。

四、成本收益与部署决策

蒸馏的终极目标之一是降低生产推理成本

4.1 成本对比

假设典型「请求理解」场景:日均 2 万次,平均输入 200 token(system + 提问),输出 100 token(JSON)。

方案 A:大模型 API(qwen3.6-plus 档)

按 DashScope 量级估算(输入约 2 元/百万 token,输出约 12 元/百万 token):

计算 日成本
输入 2 万 × 200 ÷ 10⁶ × 2 元 ≈ 8 元
输出 2 万 × 100 ÷ 10⁶ × 12 元 ≈ 24 元
合计 ≈ 32 元/天(约 960 元/月)

API 成本与请求量严格线性:量翻倍,钱翻倍。

方案 B:蒸馏小模型 GPU 部署

T4 4GB、按量约 3.6 元/小时 为例。0.6B FP16 权重约 1.2GB,4GB 显存可跑。GPU 按在线时长计费,不按 token:

部署方式 日成本 月成本 说明
工作时间 8h 28.8 元 864 元 定时伸缩,仅工作时段在线
24h 常驻 86.4 元 2592 元 跨时区 / 全天候服务

4.2 盈亏平衡点

单次 API 调用成本约 0.0016 元(按上例均摊)。

部署模式 GPU 日成本 与 API 打平的日均请求量(量级)
8h 运行 28.8 元 1.8 万
24h 常驻 86.4 元 5.4 万

日均 2 万 时,8h GPU(约 864 元/月)已略低于 API(约 960 元/月)。日均 10 万 时,API 月成本可达约 4800 元,而 8h GPU 仍约 864 元——量越大,自建越划算

成本不是唯一维度。以下场景即使单价不占优,蒸馏仍值得考虑:

因素 说明
延迟 本地推理约 50ms vs API 约 500ms,实时链路差异明显
数据安全 员工提问可能含人事信息,避免出域
离线 / 内网 无法调用云端 API
稳定性 不依赖外部限流与可用性

4.3 部署决策与混合架构

场景特征 推荐方案 原因
高频、固定域、结构化输出 蒸馏小模型 成本低、延迟低、格式稳
知识每天更新 RAG 蒸馏权重不会自动更新
复杂推理 保留大模型 小模型推理能力有限
既要结构化又要最新知识 蒸馏 + RAG 各司其职

在答疑机器人中的典型混合流程:

员工提问:"调岗到 AI 部门需要什么条件?"
  ↓
[蒸馏模型] 结构化理解 → {intents: ["制度咨询"], department: "HR", route: "rag_query"}
  ↓
[RAG] 按意图与部门检索政策文档
  ↓
[大模型](可选)基于检索结果生成最终回复
  • 蒸馏模型:结构化提取的稳定与低延迟
  • RAG:知识实时性(政策变更无需重训)
  • 大模型:需要复杂推理的最终回答

合规提醒:若教师为开源模型(如 Qwen,Apache 2.0),蒸馏产出一般可商用。部分商业 API 的服务条款限制用其输出训练竞品,使用前请阅读协议。

五、本节小结

本篇文章围绕"请求理解"这个生产场景,完整走通了数据合成蒸馏的全流程。蒸馏的本质是把大模型的判断能力固化到小模型的权重中,与微调的区别在于训练数据来源不同:蒸馏用教师模型生成,微调用人工标注。

在数据合成阶段,通过"生成多样化查询 → 教师模型标注 → 质量过滤 → 格式化训练数据"的流水线构建了高质量训练集,数据质量比数量更重要。在训练阶段,我在 PAI-DSW 上使用 ms-swift 框架以 LoRA 方法训练 Qwen3-0.6B 模型,LoRA 只训练 0.4% 的参数,保留了模型 97% 的基础能力。最终通过 JSON 合规率、意图 F1、路由准确率三项指标量化验证了蒸馏效果:蒸馏模型从基座模型的"几乎不可用"大幅提升到接近教师模型的水平。

蒸馏不是替代 RAG,而是把系统中"可预测的、高频的、结构化的"工作环节交给专用小模型。它和 RAG、Agent 是互补关系,各自负责系统的不同环节。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容