现场是这样的。训练集里混进了几份合同全文,单条三万多 token,job 正常跑完但效果很差,事后才发现超长样本被从尾部截断,assistant 的答案正好落在被切掉的那一段。

中间走过弯路,把「文件校验只检查 JSON 结构,完全不看长度」当成主因,改完现象没变。

真正麻烦的地方在于,几个原因的现象几乎一样。单条样本超过模型上下文时按 token 截断,不会报错也不会告警;截断从尾部开始,assistant 的回答常常整段被丢掉;打包上传时把多份文档拼成了一条 messages。

上来先用 tiktoken 找出超过阈值的样本行号

python -c "import json,tiktoken;e=tiktoken.get_encoding('cl100k_base');print([i for i,l in enumerate(open('train.jsonl')) if len(e.encode(l))>16000])"

然后按段落把长样本切成多条

python split_samples.py --max-tokens 6000 --input train.jsonl --out train_split.jsonl

接着确认每条最后一段都是 assistant 且非空

python -c "import json;print(sum(1 for l in open('train_split.jsonl') if l.strip() and json.loads(l)['messages'][-1]['role']!='assistant'))"

末了重新上传并核对实际训练的 token 数

curl -s https://api.openai.com/v1/fine_tuning/jobs/ftjob_new -H "Authorization: Bearer $OPENAI_API_KEY" | jq '.trained_tokens,.status'

恢复的判定其实很直接。超长行号列表为空,新 job 的 trained_tokens 与按文件估算的量接近,重新训练后在同类问题上不再输出半截答案。

截断是静默的,日志里不会有任何提示,训练前用本地 tokenizer 过一遍长度,比看 job 的 loss 曲线更早发现问题。拖得越久,排查成本越高,因为现场状态会被后续操作覆盖。

作者 9823189