现场是这样的。训练集里混进了几份合同全文,单条三万多 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 曲线更早发现问题。拖得越久,排查成本越高,因为现场状态会被后续操作覆盖。
