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 秒实验
- 把每条 token 从 250 改成 800(长回答场景),看 3 轮的费用涨到多少。 回答越长,同样条数的账单越贵,这是长文本任务的通病。
- 把样本数从 3000 改成 300,看费用和打平点一起怎么变。 数据少的时候训练费便宜,但标注费的占比会变得更高。
- 故意在 jsonl 第一行前面加一个 BOM,再跑解析,看报什么错。 「第一行格式错误」这个报错,九成是 BOM 或空行。
常见误区
- 「轮数越多效果越好」。4 轮往后验证损失常常已经在涨,多训的每一轮都在过拟合。 先 3 轮,不够再加。
- 「训完了就切流量」。训练完成只说明有模型名,能不能用要看评估和抽检。
- 「模型名是平台给的,不用管」。自动生成的模型名不可读,三个月后没人认得出哪版是哪版。 必须配一份登记表。
自测三问(点开对答案)
1. 训练费由哪四个乘数决定?哪两个由数据决定? —— 样本数 × 每条 token × 轮数 × 单价。前两个由数据决定,后两个是你要拍的板(轮数和平台报价)。 2. 为什么 jsonl 带 BOM 会失败? —— 因为 BOM 会出现在第一行的开头,导致第一行不是合法 JSON。 报错只说第一行格式错误,容易误判成数据有问题。 3. 训练完成后为什么不能立刻切流量? —— 因为训完只是「有了模型名」,效果好不好还没验证。要先看验证损失、再抽检样例,通过了才切。数据传上去了,最后一个要拍板的东西是超参数。下一节把学习率和轮数放在一起扫,看它们怎么互相牵制。
- ☐ (本节完,下一节 5.2.5 模型微调的超参数配置策略)