本节摘要:高基数场景的第一个事实是算力事实:选项数直接进入前向的计算量与输入长度,选项越多、越慢、越贵——option budget(官方口径提供的能力)就是给这件事画预算:一次决策最多认真评多少个选项,超预算的候选怎么办。本节讲三层:预算机制本身——budget 参数怎么截断候选集合;超预算的三种裁法——按外部信号截(业务规则定的优先级)、按召回分截(粗排模型给的分数)、按分层配额截(重要类别保底名额);预算与延迟的关系——近似线性、可自测标定(示意值口径)。最后强调定位:budget 是止损机制,不是提分机制——它让高基数场景「跑得动、慢得可控」,精度问题的答案在 6.3 节的两段式。接口写法以官方仓库 README 为准。
先看算力事实。第 1.2 节说过 choice 的答案空间是选项表——选项文本本身是模型输入的一部分。选项数增长会从两头吃算力:输入侧,N 个选项的文本都要拼进题面,序列长度随之增长;输出侧,决策头要在 N 个选项上分配概率。两头叠加的结果是延迟随选项数上升(近似关系,具体以自测为准),而官方对精度的口径更直白:约 20 个以上选项的场景 Jev 更强(官方口径)。
把两个事实放在一起,预算的存在理由就清楚了:既然选项多到一定程度后 laya 又慢又未必准,那就该在「多到一定程度」之前拦一道——option budget 就是这道闸门。它回答的问题是止损性质的:让高基数请求不要以失控的延迟与算力硬闯决策头。
无预算 vs 有预算 无预算:硬闯 有预算:先过闸门 ┌───────────────────────┐ ┌───────────────────────┐ │ 120 个选项直接进题面 │ │ 120 个候选 │ │ 序列暴涨·延迟不可控 │ │ │ budget = 16(示意)│ │ 精度还不占优(官方口径)│ │ ▼ 裁剪 │ │ │ │ │ 16 个选项进题面 │ │ ▼ │ │ 延迟可控·题面干净 │ │ 慢而不准 │ └───────────────────────┘ └───────────────────────┘
接口层面的用法很直接(参数名以官方文档为准):调用决策接口时带上 budget,候选超出预算的部分按机制裁掉。写法示意:
# budget_call.py —— 带预算的决策调用(写法示意,接口以官方仓库 README 为准) from laya import Router router = Router() result = router.predict( context="工单:企业版采购咨询,需要报价单与实施周期。", questions=[{ "id": "route", "type": "choice", "question": "这条工单应路由给哪个处理组?", "options": all_teams, # 全量候选:120 个组(示意规模) }], budget=16, # 预算线:最多认真评 16 个(示意值) ) # 超出预算的候选不进决策头;返回的 probabilities 只覆盖进入预算的选项 # (字段形态以官方文档为准)
使用上的第一条纪律是「兜底项必须在预算内」:选项表里的「其他」或兜底路由目标,无论预算怎么裁都必须保留——否则被裁掉的选项失去了代表,落在它们身上的流量会被迫挤进错误选项,这是预算机制最隐蔽的坑。
第二条纪律是预算要出现在监控里:每次调用实际裁掉了多少候选、裁掉的候选里事后有多少「本来该选中」,这两个数字是预算线定得合不合理的唯一直接证据(采集方式示意:把被裁候选与最终人工结果对账,第 7.3 节的弃权复核管道可以复用)。
预算机制本身不决定裁谁,裁法是工程决策,三种常见做法:
| 裁法 | 依据 | 强项 | 弱项 | 适用 |
|---|---|---|---|---|
| 外部信号截断 | 业务规则给的优先级(VIP 客户对应组优先) | 可解释、可审计 | 规则陈旧则失效 | 有成熟业务规则的存量场景 |
| 召回分截断 | 粗排信号(关键词命中、历史路由频率、向量召回) | 自适应流量 | 粗排质量决定上限 | 无明确规则、候选动态的场景 |
| 分层配额 | 按类别保底名额(每大类至少 2 个,示意) | 长尾不饿死 | 名额设计要维护 | 候选有天然分组结构 |
裁法选择的一个实用判断:召回分截断是默认解,因为它不需要人工维护规则;分层配额是召回截断的补丁,当监控显示某类候选总是被裁光时加上它。三种裁法可以叠加——先分层保底、层内按召回分排序截断,这是高基数场景里很常见的组合(示意设计)。
值得强调的是裁法与第 5.3 节 browser-agent 案例的呼应:那里「候选动作即选项表」的动态选项,在高基数页面上同样会撞预算线——按元素可见性与历史点击率先截一轮,再交给决策头,是同一思想在两个场景的复用。
延迟与选项数的关系最稳妥的说法是「近似单调上升,斜率与硬件和文本长度有关」——所以曲线要自己标。方法与第 3.2 节批量基准同款:
# bench_budget.py —— 预算对延迟的标定(方法示意,数字以自测为准) import time def bench_options(router, n_options, repeat=3): options = [f"候选组{i}" for i in range(n_options)] question = [{"id": "route", "type": "choice", "question": "应路由给哪个组?", "options": options}] times = [] for _ in range(repeat): t0 = time.perf_counter() router.predict(context="工单:示例内容。", questions=question) times.append(time.perf_counter() - t0) return sorted(times)[repeat // 2] # 中位数 # for n in (4, 8, 16, 32, 64): # print(n, round(bench_options(router, n) * 1000, 1), "ms")
标定产出一张「选项数对延迟」的小表(示意形态:4 个选项数毫秒级、几十个开始爬升、逼近上限明显变慢——具体拐点以你的硬件为准),预算线从这张表上读:先定延迟上限(业务能接受的单次决策耗时),再反查曲线找到对应的选项数,那就是这台机器上这个题面的预算。延迟上限变了(业务加码)、硬件变了(换机器)、题面变了(选项文本变长),三件事任一发生都要重标——预算线是环境相关的运行参数,不是写死在代码里的常量。
把 budget 的定位说死:它解决「跑得动、慢得可控」,不解决「评得准」。预算裁剪本质是放弃了部分候选的精细评估,被裁掉的候选如果恰是正确答案,预算机制对此无能为力——这正是 6.3 节两段式存在的理由:shortlist 用更聪明的粗筛把「该进决赛的候选」选进来,tournament 把决赛圈的排序做扎实。一句话分工:budget 是闸门,shortlist 加 tournament 是赛制;闸门管流量,赛制管名次。两者叠加才是高基数场景在 laya 侧的完整答案——而如果连这套组合都追不上精度要求,第 9 章的三向选型会告诉你什么时候该换 Jev。
预算线画好了,下一节把闸门升级成赛制:shortlist 负责把上百候选筛成决赛圈,tournament 负责在决赛圈里两两对比排出可信的名次。