1. 先搞清楚:为什么要在推理阶段做超参数优化
“Bandits in Prod: Hyperparameter Optimization at Inference Time”这个标题,第一次看到的人大概率会先愣一下。
超参数优化不是训练阶段的事吗?模型训练的时候调学习率、调 batch size,用网格搜索、贝叶斯优化,这些我都熟。可到了推理阶段,模型已经固定了,权重已经定了,还有什么超参数好调的?
这个直觉没有错,但它的前提是“推理就是一次前向计算”。放到真实生产系统里,这个前提往往不成立。
真实生产环境里的推理,尤其是大规模推荐、搜索、广告、内容分发这类系统,根本不是一个模型单独在跑。一个完整的推理链路里往往有几十个模块:召回模型、粗排模型、精排模型、重排策略、多样性控制、流量调控、竞价策略、文案生成、图片生成、标题改写、内容打散……每个模块内部都有一堆可以影响最终结果的控制参数。
这些参数不是模型权重,但它们的取值会直接改变系统行为。比如推荐列表里多样性阈值调高了,内容就会更分散;竞价系数调高了,高价值流量就会被更快消耗;生成模型的温度参数调高了,文案就更大胆,调低了就更保守。这些参数在传统工作流里,通常是运营或者算法工程师靠经验手动配置的,最多做一轮离线 A/B 测试,然后定死一个全局默认值。
问题就出在这里:线上环境是动态的,但超参数是静态的。
一段时间的流量分布、用户兴趣、内容供给、广告主预算,都会发生变化。一个在凌晨表现好的多样性参数,到了晚高峰可能就不合适了;一个在活动大促期间生效的谨慎策略,放到平时可能过于保守。如果靠人工盯盘、手动改参数,反应速度太慢;如果重新走 A/B 测试,周期太长,而且没法针对每个请求做个性化调整。
所以这套方案的核心思路,是在推理阶段引入一个“动态调参器”:对于每次请求,系统能基于当前上下文,实时决定这一轮推理该用什么样的超参数组合。而用来做这个动态调参决策的算法,不是复杂的深度学习模型,而是多臂老虎机(Multi-Armed Bandit,简称 Bandit)。
这个思路真正解决的不是“把参数调得更准”,而是把超参数从静态配置变成了在线决策问题。它改变的不是模型本身,而是模型周围那层控制逻辑。
对这个方向的理解,得从三个层面展开:一是为什么推理阶段的超参数值得单独优化;二是为什么选 Bandit 而不是更复杂的方案;三是落地生产环境时,真正的难点在哪里。
2. 为什么是 Bandit:在探索和利用之间做实时决策
2.1 不是所有参数调整都需要一个完整模型
先回答一个自然会冒出来的问题:既然要做动态决策,为什么不用强化学习,或者直接上一个可学习的策略网络?理论上当然可以,但在生产系统里,复杂度是要算账的。
一个可学习的在线策略模型意味着:
- 需要状态设计、动作空间定义、奖励函数标定;
- 需要持续采集数据、离线训练、回放验证、灰度上线;
- 需要处理特征漂移、分布漂移、奖励延迟;
- 需要监控模型是否收敛、是否震荡、是否被对抗样本攻击。
如果只是给当前推理链路里几个关键控制参数做动态调整,这套基础设施有点重了。而且很多时候,调参本身的收益幅度并不需要推理模型级别的建模能力,它只需要在大盘可能的方向上做快速、稳健、可解释的试错。
Bandit 在这个场景里的定位,是“轻量决策层”:它不预测未来,不建模复杂状态,只做一件事——在当前环境里,维护一组候选动作的收益估计,然后根据收益估计选择动作,并根据新反馈更新估计。
这本质上是一种“在不确定中做选择”的方法。推理阶段的超参数调整,恰好是这种问题:
- 可选择的超参数组合是离散的、数量可控的(比如温度参数取 0.6、0.8、1.0 三档);
- 每次选择的收益可以定义(点击率、转化率、停留时长、收益额,或者一个综合业务指标);
- 反馈是延迟的但延迟周期通常可控(几秒到几分钟);
- 环境是非平稳的,需要持续做小幅探索来感知变化。
Bandit 模型正好匹配这几个条件。
2.2 几种常见 Bandit 策略的适用差异
在工程落地时,常见的选择有这么几类:
- Epsilon-Greedy:以 ε 的概率随机探索,以 1-ε 的概率选择当前收益估计最高的动作。优点是实现极简单,适合冷启动或作为 baseline;缺点是对动作收益差异不敏感,探索是盲目的。
- UCB(Upper Confidence Bound):在每个动作上维护收益均值和置信区间,选择“上置信界”最高的动作。它能自动平衡探索和利用,且对样本量少的动作天然有更高的探索权重。适合动作数量少、奖励稳定、需要快速收敛的场景。
- Thompson Sampling(汤普森采样):给每个动作的收益分布建模,从分布中采样,按采样值选择动作。它处理不确定性更平滑,在奖励有波动、需要频繁更换最优动作的场景里表现通常更好,实现也不复杂,是生产系统里很常用的选择。
- Bayes UCB 或 Contextual Bandit(上下文老虎机):在 UCB 或 Thompson 基础上引入特征向量,让决策能感知当前请求的上下文,比如时段、用户群体、内容类别、流量渠道。这是从“全局统一参数”走向“按场景动态参数”的关键一步。
在推理阶段做超参调整,我个人的判断是:如果没有额外特征,或者刚开始做,用 Thompson Sampling 是综合成本最低的起点;如果已经有一些可分群的上下文信号,Contextual Bandit 是更有上限的方向。网格搜索和随机搜索在这里不太适用,因为它们没有记忆,每次重新抽样的效率太低了。
2.3 为什么说这是“推理时超参优化”,而不是普通的 A/B 测试
A/B 测试的思路是:把流量随机分成两组,一组走旧参数,一组走新参数,跑一段时间后对比指标,留下胜者。
这种思路在“验证一个新参数是否更好”时是对的。但它在生产调参场景里有几个硬伤:
- 周期长,通常需要几天到几周才能积累显著样本;
- 滞后性大,环境已经变了,结论可能已经过期;
- 无法针对每个请求动态差异化,只能给整个流量池切一刀;
- 无法处理多个参数之间的交互效应,A 参数和 B 参数单独测都一般,但组合在一起可能很好。
推理阶段的 Bandit 调参,可以理解成一个持续、自动、个体化的 A/B 测试。它不需要人为切分固定实验组,而是系统自动为每次请求选择当前认为最优的组合,同时保留一定概率探索其他组合,并通过在线反馈持续刷新对每个组合的估计。
这种机制特别适合那些“参数本身不是黄金法则,而是根据当前环境动态浮动更有效”的场景。它不是取代 A/B 测试,而是在 A/B 测试周期之外,增加了一条可动态响应环境的优化路径。
3. 生产系统里真正要跑的链路是什么
3.1 一个最小可行的推理调参架构
如果要在生产系统里落地一套“推理阶段 Bandit 超参调优”,架构上不需要推倒重来,通常是在现有推理服务外面加一个轻量决策层。整个链路可以这么画:
- 请求进入推理服务,先经过一个调参决策模块;
- 决策模块根据当前上下文(请求特征、场景标识、候选动作池),用 Bandit 策略选择一个超参数组合;
- 推理模块按这个超参数组合执行完整的推理链路,包括模型前向计算和后处理策略;
- 推理结果返回后,系统在合适的时间点收集反馈指标(点击、转化、时长、GMV 等);
- 反馈指标写入日志,异步回传特征构造器,构造出“该动作在观测时间窗内的奖励”;
- 奖励数据回到 Bandit 模块,更新对应动作的统计量或模型参数;
- 下一轮请求到来时,决策模块基于更新后的统计量重新做选择。
这个流程看起来简单,但有几个必须确认的细节。
难点一:动作池怎么定义。
不建议把每个超参数都变成连续变量让 Bandit 自己搜。连续空间会让采样和收敛都变得复杂。更稳妥的做法是先把关键超参数离散化,形成固定的候选组合。比如:
# 示例:候选动作池结构,常见做法 param_pool = [ {"temperature": 0.6, "diversity_threshold": 0.3, "bid_boost": 0.0}, {"temperature": 0.8, "diversity_threshold": 0.4, "bid_boost": 0.1}, {"temperature": 1.0, "diversity_threshold": 0.5, "bid_boost": 0.2}, # ... 根据业务需求定义组合 ]为什么用离散组合?因为生产环境的安全性要求优先。离散组合可以被人工审核、日志化、回滚;连续参数一旦出了问题,很难定位是哪个参数导致指标异常。
难点二:奖励函数怎么定义。
这是一个决定整个系统走向的核心问题。如果奖励定义得太窄,比如只看点击率,系统可能会为了点击而牺牲用户体验,推荐一些标题党内容;如果奖励定义得太宽,又可能带来较高的计算和延迟代价。
比较通用的做法是定义一个复合式业务指标,或者对多个指标做加权组合。但这里要特别谨慎:复合指标里的权重是多少,直接影响 Bandit 的优化方向。比如权重偏向 GMV,系统会把流量更多地分配给低价高转化商品;如果偏向停留时长,系统又会变得过于保守。
在实际项目里,我建议先定义一个“能代表当前阶段核心目标”的奖励信号,不要搞太多指标加权。等跑通以后再逐步扩展。
难点三:反馈延迟和归因。
反馈不是立即得到的。一个推荐请求可能在几秒后产生点击,也可能在几分钟后产生转化。如果 Bandit 在下一轮请求时立刻根据不完整的反馈更新参数,可能造成错误的信号。
常见处理办法有两个:
- 设定一个观察窗口,比如 5 分钟或 30 分钟后,再把这个请求对应的反馈计入奖励统计;
- 使用延迟反馈修正方法,在样本里给未完成转化的负例打折扣,等延迟奖励到达后再补充修正。
在落地初期,不用追求太复杂的延迟反馈模型。先确保反馈日志带有“请求 ID”和“实际决策的动作标识”,然后按时间窗口聚合,就能解决 80% 的问题。
3.2 单任务验证:先跑通再谈规模化
如果只是第一次验证这个方案,不建议直接上全链路。我更建议先从“单场景、单模块、少动作”开始。
一个最小验证的步骤大概是:
- 选定一个业务场景,比如信息流推荐里的多样性控制参数。
- 定义 3 到 5 个候选参数组合,差异要足够明显。
- 接入一个 Thompson Sampling 策略。
- 只对一小部分流量生效,比如 5%,其余流量走默认配置。
- 观察至少 2 个完整的业务周期(比如包含工作日和周末)。
- 对比实验组和默认组的核心业务指标,并同时观察辅助指标。
为什么要用 5% 流量跑而不是全量?因为 Bandit 虽然是自适应算法,但它也有探索成本。刚开始时它对动作收益的估计是不准确的,如果直接全量,一旦选择了次优参数,损失会被放大到整个业务。小流量验证,能够以较低成本确认方案的可行性和奖励信号的质量。
验证期间需要重点看几个指标:
- 各动作的分配占比是否出现集中趋势;
- 收益估计的置信区间是否收窄;
- 每个动作对应的业务指标是否稳定;
- 有没有出现“某个动作长期被忽略但实际很好”的情况。
如果小流量跑了 2 周以上,动作分配基本稳定,收益提升方向对,再考虑扩量到 20%、50%,最后全量。
3.3 从单模块到全链路:参数会相互影响
单模块验证通过之后,如果想把 Bandit 调参用在多个模块上,就会遇到一个新的复杂度:参数交互。
例如,内容生成模块的温度参数调高,生成了更多样化的文案;同时重排模块的多样性阈值调高,展示内容变得更分散。两个参数独立看都有提升,但组合在一起可能造成用户感知的混乱,导致负面反馈。
处理这种交互,有两种思路:
- 思路一:把多个模块的超参数组合成一个更大的动作池,让 Bandit 直接在组合空间里学。这种方法能建模交互效应,但动作空间会爆炸。比如 3 个模块每个有 5 档参数,组合就是 125 种,冷启动阶段样本不够。
- 思路二:分层调参,每个模块独立一个 Bandit,但通过上下文特征把另一个模块的当前参数值作为上下文传递进去。这样能间接建模交互效应,同时不扩大动作空间。实际落地中,我见过的大多数系统走的是思路二。
不过,这里必须做一个提醒:不要贪多。一次只调 1 到 2 个关键参数,跑稳了再加新的。调参系统的复杂度,比业务模型本身更隐蔽。
4. 最容易踩的坑:不是算法,而是基础工程
4.1 动作分配日志是后续所有分析的底账
这个点很关键,但很多团队容易忽略。
Bandit 的价值不只在决策那一刻,更在于它积累了“什么上下文下、选择了什么动作、获得了什么奖励”的完整日志。这份日志不仅是衡量 Bandit 收益的依据,也是后续做离线分析、策略回放、模型评审的基础。
实际项目中,有些团队只在决策服务里打了日志,但没有记录奖励回传,或者奖励回传时没有关联到当时的上下文,导致后面想分析“为什么某个动作在某个场景下最优”时,数据是断的。
一个可复用的日志结构可以这样设计:
{ "request_id": "req_20250125_001", "timestamp": 1737753600, "scene": "feed_recommend", "context": { "user_group": "new_user", "hour": 14, "channel": "android" }, "action_id": "temp_0.8_div_0.4", "params": { "temperature": 0.8, "diversity_threshold": 0.4 }, "reward": { "click": 1, "convert": 0, "observed_at": 1737753900 }, "algo": "thompson_sampling", "version": "v1.2" }有了这样的日志,后续可以做很多高阶分析:离线模拟新的 Bandit 策略、评估不同动作池设计的收益上限、判断上下文分群是否带来了增益。
4.2 冷启动和阶段切换,是稳定性风险最高的时候
在系统刚上线时,每个动作的收益估计都是先验值,没有足够的样本。如果先验设置得和真实分布偏差很大,Bandit 可能需要较长时间才能纠正。
解决冷启动的方式有几种:
- 先用历史日志估计各动作的初始收益均值和方差,再启动 Bandit;
- 设定最小探索率,保证每个动作在前 N 次都有被选中的机会;
- 使用 UCB 初期策略,让探索更主动。
阶段切换也需要注意。比如从每日更新奖励切换到实时反馈更新,或者从黑盒更新切换到模型更新,都会出现一段统计量不稳定的过程。这时候降流量、拉长观察窗口,比急于做判断更稳妥。
4.3 要设安全护栏,Bandit 不是不会出错
很多人有一个误解,觉得 Bandit 是自适应算法,所以它会自己找到最优解。实际上,Bandit 只能保证“在当前奖励信号定义下,在长期范围内趋向于选择高收益动作”。
它并不能保证:
- 单个动作在短期内不会出现指标劣化;
- 奖励信号失真的情况下不会盲目放大次优动作;
- 完全不受样本选择偏差影响;
- 在环境剧烈变化时能立刻反应过来。
所以生产环境里需要加几道护栏:
- 最小流量保护:每个动作不管收益如何,都保留一个最小流量占比,避免某些组合长期无人尝试,万一环境变化后错过新最优。
- 指标红线:设置核心业务指标的阈值,一旦某个动作对应指标跌破阈值,立即将流量切回默认参数,并进入人工排查。
- 紧急回滚开关:Bandit 模块本身要支持一键关闭,系统回到固定参数模式。
- 版本化运行:每次调整参数池、奖励权重或策略逻辑,都要带上版本标识,方便在线上对照历史表现。
这些护栏不是过度设计,而是生产系统里必须有的底线。因为 Bandit 调参是循环反馈链路,一旦错误信号进入更新循环,问题会被逐渐放大,而不是自动纠正。
5. 推理时调参真正改变的是什么
说到底,这套方案的价值不只是一个算法替换。
它把过去“离线定参、线上跑量”的工作方式,变成了“线上实时决策、持续探索反馈”的闭环。它让推理系统从固定配置的机器,变成了一个能感知环境变化、动态调整策略的执行器。
它不是万能的。它不会在模型本身没有能力时创造能力,不会在奖励信号混乱时看清方向,也不会在数据质量差时自动修正。它适合的,是那些业务目标清晰、动作空间可控、反馈可及时观测、环境有变化但变化不是无规律的系统。
如果你的业务里有一批“靠人工经验维护的动态参数”,而且这些参数对最终指标影响很大,那么用 Bandit 在推理阶段做在线调参,比继续手动调参或者做周期性的 A/B 测试,都要更接近生产系统的真实需求。
落地顺序上,我建议这样走:
- 先找 1 个场景、3 到 5 个候选参数组合;
- 接入最小化的 Bandit 决策模块,小流量跑通;
- 重点验证奖励信号和日志的完整度;
- 确认策略能稳定收敛后,再逐步扩展到多模块、多场景;
- 最后把安全护栏和状态监控做完整。
这个方向真正的上线门槛,不在算法理解,而在工程细节:日志能不能对得上,奖励能不能算得清,流量能不能割得开,异常能不能回滚。这些细节磨平了,Bandit 才会真正变成生产系统里的那个低成本、可扩展的在线调参引擎。