🧠 第五章 模型微调:让AI训练效果达到预期
📄 一句话概括

3000 条、每条约 250 token、训 3 轮 = 225 万 token、约 68 元; 同样数据训 10 轮就是 750 万 token、225 元——费用随轮数线性涨。 上传前有四道校验, 任何一道不过,平台都会在训练中途失败。 上传后是异步任务:pending → running → succeeded/failed。

📝 本节和前面几节的关系

上一节把密钥验通了。这一节真正动数据:先算钱、再查格式、然后才是点上传,最后盯状态机。

一、先算钱:费用跟轮数成正比

训练费 = 样本数 × 每条 token × 轮数 × 单价。 四个乘数里,前两个在数据准备好时就定了,后两个是你要拍的板。

训练轮数 token 总量 费用(30 元/百万)
1 轮 75.0 万 22 元
3 轮 225.0 万 68 元
5 轮 375.0 万 112 元
10 轮 750.0 万 225 元
这张表是线性的,没什么意外。
真正要记住的是「多训几轮总没坏处」这句话是错的:多训不只多花钱,
还会过拟合——第 4 轮往后,验证损失往往已经在涨了。
所以合理的做法是:先训 3 轮看验证损失,不够再训。而不是一上来就 10 轮,把预算烧在过拟合上。
顺带一句:不同平台对「训练 token」的计价方式不完全一样,有的按训练集 token 乘轮数,
有的把验证集也算进去。下单前看一眼计价说明,差一倍都有可能。

二、四道校验:任何一道不过,都会在中途失败

① 文件大小。 太小说明数据没传全,太大可能超了平台限制。 ② 行数。 平台通常要求至少 10 条,实际项目建议 100 条以上才有意义。 ③ 编码。 UTF-8,且不能带 BOM。 带 BOM 的文件第一行 JSON 会解析失败—— 而报错只说「第一行格式错误」,很容易误判成数据本身有问题。 ④ 每行独立 JSON。 一行一个完整对象,不能是数组、不能跨行。 这个错误最常见的来源是「用 Excel 导出的 jsonl」。 这四项的共同点是:不检查也能传上去,训到一半才失败。而异步任务的失败往往要等十几分钟才报出来, 上传前跑一遍脚本,比等报错快得多。

三、上传之后是一个异步任务

四个状态,含义很明确:

状态 含义 该做什么
pending 排队中,还没开始 等,别重复提交
running 训练中,有中间指标 可以看损失曲线
succeeded 完成,拿到模型名 先评估,再切流量
failed 失败,错误在事件列表里 读事件,改数据重提
最关键的一条:不要在 running 的时候就切流量。训练完成前那个模型名是调不通的,
切过去就是全站故障。
还有一条运维细节:提交任务后把任务 ID 记下来。因为控制台只会显示最近若干个任务,
一周后回头找「那次训的到底是哪份数据」,没有 ID 就只能靠猜。

四、训练完要登记什么

一次微调会产出一堆东西:数据文件、超参、任务 ID、模型名、评估结果。 不登记,三个月后没人说得清线上那个模型是怎么来的。 建议的登记项:数据版本(哪份文件、多少条、校验通过率)、超参(轮数、学习率、批大小)、 任务 ID 与完成时间、评估结果(验证损失、抽检通过率)、模型名与调用方式。 这张表还有个副产品:下次要复现或回滚,直接照着它重跑。没有它, 回滚只能靠「找找看还有没有旧模型名」。 最后:给模型名加可读后缀。 ft:gpt-4o-mini:myorg::abc123 这种平台自动生成的名字, 三个月后谁也认不出来。在登记表里写清「这版是客服话术 v2」,比名字本身更重要。

五、动手 Demo:3000 条训 3 轮 68 元:钱随轮数线性涨,上传前先过四道校验

按「样本数 × 每条 token × 轮数 × 单价」算训练费,扫 1/3/5/10 四档轮数; 再写一个 50 行的 jsonl,逐个查文件大小、行数、编码、每行能否解析。 单价 30 元/百万训练 token 是显式假设。

n, tok_per, epochs = 3000, 250, 3
p_train = 30.0                       # 元/百万训练 token,显式假设
tot = n * tok_per * epochs
print(tot / 1e4, tot / 1e6 * p_train)   # 225.0 万,68 元

for ep in (1, 3, 5, 10):             # 费用随轮数线性涨
    t = n * tok_per * ep
    print(ep, t / 1e4, t / 1e6 * p_train)

size = os.path.getsize(p)
lines = sum(1 for _ in open(p, encoding='utf-8'))
for i, ln in enumerate(open(p, encoding='utf-8')):
    json.loads(ln)                    # 每行独立 JSON

实跑输出:

--------------------------------------------------------------------
上传训练数据:先算钱、再查格式,最后才是点上传
--------------------------------------------------------------------
  样本数                   3000 条
  每条约                   250 token
  训练轮数                 3
  训练 token 总量          225.0 万
  估算训练费用             68 元

  这个数**随轮数线性涨**。同一份数据,训 3 轮和训 10 轮差三倍多:
  轮数   token 总量     费用
  -------------------------
     1      75.0 万    22 元
     3     225.0 万    68 元
     5     375.0 万   112 元
    10     750.0 万   225 元

  **所以「多训几轮总没坏处」是错的**:多训不只是多花钱,还会过拟合。
  第 4 轮往后,验证损失往往已经在涨了。

  上传前要过的四道校验(任何一道不过,平台都会在训练中途失败):
  ① 文件大小               5.1 KB(平台通常限制几百 MB)
  ② 行数                   50 行(平台通常要求至少 10 条)
  ③ 编码                   UTF-8,无 BOM
  ④ 每行独立 JSON          50 行全部解析通过
  这四项都是「不检查也能传上去、训到一半才失败」的类型。**上传前跑一遍,比上传后等报错快得多。**

  传上去之后是一个异步任务,状态会依次经过:
  pending                  排队中,还没开始
  running                  训练中,会有中间指标
  succeeded                完成,拿到模型名
  failed                   失败,错误信息在事件列表里
  **不要在 running 的时候就切流量**——训练完成前,那个模型名是调不通的。

第一屏三个数:样本 3000、每条 250 token、3 轮 225 万 token、68 元。 这四个数是乘出来的,改任何一个都能立刻重算。 第二张表是 1/3/5/10 轮:22、68、112、225 元。完全线性——因为轮数就是乘在 token 量上的。 所以「多训几轮保险」这个想法很贵: 从 3 轮改到 10 轮,钱涨 3.3 倍,而多出来的 7 轮多半是在过拟合。 第三屏四道校验:文件 5.1 KB、50 行、UTF-8 无 BOM、50 行全部解析通过。 这四项都是「不查也能传、训一半才失败」的类型。 最后一屏的四个状态里,succeeded 之后还有一句话: 先评估,再切流量。 模型训完不等于能用——验证损失好看、抽检一塌糊涂的情况并不罕见。

30 秒实验

  1. 把每条 token 从 250 改成 800(长回答场景),看 3 轮的费用涨到多少。 回答越长,同样条数的账单越贵,这是长文本任务的通病。
  2. 把样本数从 3000 改成 300,看费用和打平点一起怎么变。 数据少的时候训练费便宜,但标注费的占比会变得更高。
  3. 故意在 jsonl 第一行前面加一个 BOM,再跑解析,看报什么错。 「第一行格式错误」这个报错,九成是 BOM 或空行。

常见误区

  1. 「轮数越多效果越好」。4 轮往后验证损失常常已经在涨,多训的每一轮都在过拟合。 先 3 轮,不够再加。
  2. 「训完了就切流量」。训练完成只说明有模型名,能不能用要看评估和抽检。
  3. 「模型名是平台给的,不用管」。自动生成的模型名不可读,三个月后没人认得出哪版是哪版。 必须配一份登记表。
自测三问(点开对答案) 1. 训练费由哪四个乘数决定?哪两个由数据决定? —— 样本数 × 每条 token × 轮数 × 单价。前两个由数据决定,后两个是你要拍的板(轮数和平台报价)。 2. 为什么 jsonl 带 BOM 会失败? —— 因为 BOM 会出现在第一行的开头,导致第一行不是合法 JSON。 报错只说第一行格式错误,容易误判成数据有问题。 3. 训练完成后为什么不能立刻切流量? —— 因为训完只是「有了模型名」,效果好不好还没验证。要先看验证损失、再抽检样例,通过了才切。

数据传上去了,最后一个要拍板的东西是超参数。下一节把学习率和轮数放在一起扫,看它们怎么互相牵制。

相关笔记