loading

Loading

探索与发现

首页 AI 开发实战

批量结构化输出失败:推理模型出错先别急着重试

字数: (2491)
阅读: (6)
0
摘要:批量任务里重试率从 1% 涨到 8%,不一定是模型变差了。截断、严格模式覆盖不到、Schema 编译退回非受限,三类失败的病因不同,混在一起调重试参数只会把问题推向模型偏移。

前段时间帮一个团队排查批量标注流水线的问题。他们用推理模型对四十万条文本做结构化抽取,每条输出一个含十二个字段的 JSON。上线第一周重试率 1.2%,看着还行;第三周飙到 8%,之后有一批任务直接整批落了空。打开日志一看,崩溃点散布得很散:有的是 JSON 尾部缺了闭合括号,有的是字段类型漂移,还有的是标准 schema 校验全部通过、内容却完全不对。这三类问题看着都是"解析失败"四个字,病因完全不在一个层面,修法也完全不同。

先说结论:批量结构化输出失败,最大的成本不是失败本身,而是把不同类型的失败混在一起当一件事处理。很多团队的第一反应是把 max_retries 从 2 调到 5,重试率曲线确实降了一截,但同时也把真正的系统性问题埋得更深了。

第一层:先看 finish_reason,它不是给监控看的

静默截断在批量任务里最常见,也最容易误判。模型在生成中途耗光 max_tokens,finish_reason 返回 length,输出是一段缺了闭合引号或闭合括号的残句。表面上这是一个解析错误,实质是一个配额问题——调大配额或者拆小任务就能解决,重试同样的参数只会得到同样的截断。

批量场景比单条请求更危险:开发者往往写一个 try/except 包住整个解析过程,把所有异常归成一个笼统的 ParseError,截断和真正的格式错误混在同一类里。排查的第一步应该是在解析之前先单独读 finish_reason,把它作为一个独立维度打到日志上。不然你连"该加大 max_tokens 还是该报警"都分不清。

第二层:严格模式的覆盖范围是否真的覆盖到了

现在很多 API 在严格结构化输出模式下,Schema 层面的故障率已压到千分之几。问题出在很多人以为自己用了严格模式:支持严格模式的是特定版本之后的模型,如果批量任务里仍有个别旧模型或跨供应商路由(比如部分任务被路由到只提供 JSON 模式的端点),这些请求仍处于老年代——语法没问题、结构未必是你要的,额外的字段、缺失的必填项、该是整数的值变字符串,都不触发任何解析错误,静默进下游。批量环境里,这类混杂请求量一旦大了,查异常流量往往会漏掉它们,因为具体异常只在哪一个时刻产生,根本没有显现出来。

这里有一个值得在批量侧特别核查的点:在统计里每一个请求的模型版本和路由端点,不然严格模式和 JSON 模式的能力边界根本看不出来。很多人只看拿到手里的模型名字,实际路由是网关配的,中间经过了好几层路由呢。

第三层:Schema 的复杂度是开发阶段看不出来的

还有一个容易被忽略的事实:开发阶段你的 Schema 只有八个字段、两层嵌套,一切很好;几个月后加进来嵌套数组,再嵌一层对象,底层受限解码这边就可能静默退回到非受限生成,或者编译超时。据业界对大量真实 Schema 的测试数据看到过一些有意思的记录,不同后端在复杂模式的覆盖率和编译耗时差得很远;一个 Schema 能在某框架里 0.01 秒编译完成,换另一个后端就得编译几十秒,甚至几分钟。

再加上把深度超过 5 层的 Schema 送给一些严格模式会直接吃掉这条能力边界,这几点都在提示同一件事:批量跑起来前先用任务真实的一批样本对 你实际使用的后端 做验证,而不是拿玩具例子跑通就上线。业务真实的文本往往就有复杂的嵌套关联,这一步省不得。

批量场景下的字段统计几乎很少有人碰

解析层验证只能保证语法。语义对不对需要单独一层监控:跟踪枚举值的分布。一个包含五类的分类器原来在五类之间均匀分布,模型更新后 80% 集中在一个标签上,JSON 解析一切正常,可答案层面大概率已经不中不中了。在一次性有十几万条批量任务的场景下,这件事还提供了一种额外红利:把整批中失败的样本量和字段分布直观地对比出来,即哪个字段缺失最多、枚举值在什么块发生偏移,快得出奇。

另外,严格模式遇到了内容过滤时并不总是返回错误,可能是带 refusal 字段、parsed 为响的空对象。若批量任务的处理代码从不区分"解析成功"和"拒绝回答",这种分支会被当成普通空值丢到下游轮询语义混报里——这是批量流水线里很容易发生且极难复现的脏数据源。

重试和拆分该放在哪里

批量任务的回退策略与在线服务不一样。在线服务更看重单条延迟,可以用到多轮重试;批处理更值得先计算失败占比和外部结构,而不是连"样本总量"一起重放失败任务。每个大任务的中间结果并不一定重跑整份;以 custom_id 将失败集合显式登记出来,下次发起批量发只需要附带失败子集;这又反过来要求你用一份很稳定的标识找到哪些是原生的、哪些是重试的、哪些是执行中的。在很多批量 API(如 PPIO 的批量推理文档描述的那样)的最初发现中这只是一个稳定的 unique 参数功能,但在大规模验证下它是整个回退流程中唯一的主链。

在线式验证 - 重试遵循下来对单个请求也有成本。重试意味着延迟翻倍、token 翻倍,但比断送整批强。重试超过两三回还有原生问题的时候就该退回来查生成之外的东西:数据是否存在真实的重复背景(比如当批材料中本身混有缺失类型)等测试型输入异常。输入异常这个条件,是提示词修补还是枚举值预校验一下来处理会更实在的做法。

小结:分层排查的顺序

  1. 先检查截断——读 finish_reason,截断和格式错误从来不是一件事。
  2. 再看能力边界——核对每条批量请求模型代际和 Schema 约束能力是否相符,别以为用了就是用了、或者一个路由就真的能管到每一股支流里。
  3. 然后用真实 Schema 复杂度完成步测试。
  4. 最后开启枚举值分布观测与少部分抽查路径或格式,前一层只能抓语法,后一层才有机会抓到被夸大的那一部分。

每一步都不是很大的工程投入。难点不在于技术,而在于很多团队的总监控信号只有一条"重试率"曲线。这条曲线本质上可看的东西是二元的兼容,但是很多时候我们并不需要像别人一样按单条延迟排列队列,把失败当成一个位置精确、原因清楚、可以指向一条具体请求处理层的问题那样抓出来,自然就能走通这条路。

转载请注明出处: 皮哈皮阿

本文的链接地址: https://blog.phpat.com/ai-dev/668.html

手机扫码阅读

微信或手机浏览器扫一扫,随时随地随心阅读与分享

您可能对以下文章感兴趣
1 / 1
预览图