Bid2X:以基础模型视角刻画竞价广告环境建模实践
做竞价广告的同学应该都有过这种体验调价的时候对着报表反复试好不容易在某个场景下看到效果提升换个流量环境又完全失效。我们经常把“环境”挂在嘴边说“这个环境太复杂”“那个环境变化太快”但真要问一句“环境到底是什么、怎么把它明确刻画出来”大多数时候是说不清的。这篇文章想聊的其实是我们在某次内部讨论里纠结了两个月的问题如果不能把竞价环境本身作为一个可计算的对象来建模那所有的出价策略都只是在“赌环境不变”。基于这个出发点我们做了一个叫 Bid2X 的模拟环境建模方案试着换一个视角来回答“广告竞价环境到底怎么描述”这个问题。标题挂在 KDD‘25 的投稿系统里但说实话更让我兴奋的是这个思路本身——它把环境从一个被动的背景变成了一个可以被显式训练的主动变量。1. 项目的整体设计与思路拆解1.1 为什么要把“环境”单独拎出来建模先回到一个最基本的问题广告竞价里我们通常把什么当作环境答案往往很模糊。有时候是指竞争对手的出价分布有时候是指流量质量的变化有时候是指某个时段整体的竞争烈度。问题是这些描述都是松散统计量没法直接塞进一个优化算法里做微分运算。我们在内部做了一次梳理发现出价策略失效的场景里超过七成不是因为策略本身收敛得不好而是策略适应的环境分布已经变了。LR 类模型尤其明显——线上重训一次要跑几个小时等模型上线环境又跑了一段距离。这就像你在高速上按后视镜里的车距踩油门但镜子里照的是五秒前的路况。所以我们需要一个东西能把环境的变化趋势、波动幅度、周期性都压缩成一个紧凑表示的模块。这个模块要快、要稳、要能跟上线上节奏。Bid2X 的出发点就是把这层“环境建模”从策略模型里剥离开单独做一个可训练、可更新的网络结构。1.2 “基础模型视角”到底指什么标题里加了“基础模型视角”这不是为了凑名词。我们的思考逻辑是过去做竞价环境建模通常是针对某个特定账户、特定 campaign 去拟合一个环境模型。问题在于每个 campaign 的样本量不同新账户冷启动的时候甚至没有样本这种情况下拟合出来的环境模型是非常脆的。基础模型的做法不一样。它希望先在海量竞价日志上学习环境的通用结构然后把这种结构作为先验去适配到具体场景上。打个比方你新入职一家公司不会从零开始研究所有人的性格而是先掌握“职场沟通的一般规律”再根据具体同事的特点微调。Bid2X 做的就是类似的事情——先在足够大的数据范围内把环境表示学出来再迁移到目标场景里快速适配。这里面还有个关键设计我们不是直接用基础模型去出一个最终出价那太慢了且不可控。基础模型只负责产出一个环境表征向量这个向量再去指导一个轻量级策略头去出价。等于把环境理解和决策控制拆成两层各有各的更新节奏。1.3 方案选型背后的技术取舍选型的时候我们其实比较过几条路线。第一是直接用强化学习里的 environment model 思路做一个基于观测的隐空间转移模型这个路线理论优美但工程上太重线上推一次要过一遍生成式前向延迟撑不住。第二是把环境建模做成纯统计模块用滑动窗口算特征再做聚类好处是便宜坏处是表达力不够没法刻画多维度的动态关联。Bid2X 最终走了第三条路用序列建模的方式做环境表征但不再用传统逐时刻滚动预测的思路而是用一个固定窗口的编码器把一段竞价历史压缩成一个环境向量。这个向量随后会被两个分支使用一个分支做环境变化趋势的预测另一个分支直接给策略网络当前的环境提示。具体实现上我们参考了时间序列领域里比较成熟的 patch 编码思路把竞价序列切成小段分别编码再用自注意力去融合段与段之间的关系。这个做法的优势在于环境变化的模式往往不是单个时刻能看出来的它需要跨段对比——比如“上午十点的竞争烈度缓升”这种模式必须看到多个时间段的相对关系才能捕捉。2. 核心实现细节与工程落地2.1 数据构造环境信号怎么定义环境建模的第一步是定义什么算环境的“观测”。我们的做法是从竞价日志里抽出一组原始信号包括但不限于请求量、出价分布分位数、成交率、平均点击价格、头部买量占比、时段相对强度等。这些信号不做太多手工组合直接进模型让网络自己学关联。这里有个容易被忽略的坑不同信号的量纲和更新频率差很多。请求量是按分钟更新的但成交率可能是按小时才稳定。直接拼在一起做序列建模高频信号会主导梯度低频信号基本被淹没。我们做了两层归一化处理第一层是做 z-score第二层是给不同信号配置不同的时间窗口聚合长度保证进入序列模型之前各信号的统计特性大致在同一尺度上。这个数据构造过程我们迭代了很多版本。最开始我们试图直接塞原始日志结果模型几乎只在学请求量的波动其他全部是噪声。后来改了分位数特征、加了频域过滤才慢慢让模型“注意到”环境里那些细小但关键的信号。注意环境信号的定义不要追求大而全关键是信号之间要有可学习的关联结构。宁可少而精不要多而杂杂了模型会把注意力放在噪声上。2.2 网络结构从 patch 编码到环境表征Bid2X 的骨干结构可以拆成三块输入端、编码器、输出分支。输入端先把原始信号序列切成长度为 L 的 patch每个 patch 内部先过一次 MLP 做局部压缩然后加位置编码进入 Transformer 层做全局交互。编码器部分我们用了 4 层 Transformerhidden size 控制在 128head 数 8。这个规模比我们预想的要小很多。一开始我们也试图上大模型hidden 拉到 768但效果没有明显变好线上推理延迟却翻了四倍。后来想明白了环境表征不需要模型记住太多细节它只需要抓住“状态之间的相对关系”小模型反而更容易泛化。实操心得凡是做线上实时推理的模型结构设计一定要抱着“够用就好”的心态。大模型不是不好而是你在短视频场景里塞一个需要几十毫秒推理的环境模型连特征工程都撑不住。编码器输出的向量会过一个 bottleneck 层压到 32 维形成的就是我们说的“环境向量”。这个向量就是整个 Bid2X 的核心资产——它承载了当前竞价环境的结构化描述可以被策略模型直接消费也可以被存储下来做时序分析。两个输出分支里分支 A 预测未来几轮的环境变化趋势分支 B 直接输出一个环境量表给策略模型用。分支 A 负责训练时的自监督损失分支 B 负责线上使用时的效果输出。训练时两个分支同时优化线上只保留 B避免不必要的计算。2.3 训练目标趋势预测与对比学习结合Bid2X 的损失函数不是单一目标而是由三部分构成。第一部分是趋势预测损失让模型根据过去一段环境序列预测未来若干步的环境走向。这里不是直接回归原始信号而是先经过一个标准化的目标变换让模型学习“变化的方向和幅度”。第二部分是重构损失过一层轻量级 decoder 把环境向量还原成输入序列的特征这一步类似于自编码器的思路目的是防止环境向量变成只顾预测、丢掉关键信息的捷径。我在工程上遇到过一个典型失败模型把所有信息都塞到预测分支里环境向量本身退化成常数表现就是 loss 在降但向量完全不可解释。加上重构约束之后这个问题立刻缓解。第三部分是对比损失我们在训练时故意把同一 campaign 在不同时段的序列切出来做正样本对把不同 campaign 的序列做负样本对。这个做法的效果很神奇——模型学出来的环境向量开始有了语义不同 campaign 之间的向量距离基本能反映它们在竞争烈度和流量结构上的真实差异。三部分损失的权重是 4:2:1趋势预测为主重构次之对比辅助。这个比例我们在多个数据集上试过稳定性和效果平衡最好。2.4 慢环境独立训练与快环境在线适配Bid2X 的训练分两层节奏。慢环境基础模型在离线集群上跑数据量是两周的竞价日志batch 大小 2048训练步数大概 20 万步每 4 小时重新产出一版基础权重。这个节奏不追求实时因为它学习的是环境里那些“慢变量”——周期性、趋势性、跨场景的共同结构。快环境适配则是在线完成的每 10 分钟用最近一小时的日志微调一个小的 adapter只更新环境向量编码器最后两层。这样做的成本很低却能保证环境向量里始终包含“最近一小时发生了什么”这一层信息。说实话这一层的设计我们是踩过坑的。最早的版本里基础模型更新节奏也是 10 分钟一版但线上环境高频波动加上梯度噪声反而让模型越来越不稳定。后来改成“慢基础快适配”的双层结构之后模型既保留了跨场景的泛化能力又能快速响应当前场景的变化。3. 实操过程与离线实验记录3.1 训练框架与资源配置整个 Bid2X 的训练基于 PyTorch 实现分布式用 DeepSpeed 的 ZeRO-2。离线训练集群给到 4 机 8 卡 A100样本总量大概是 30 亿条竞价记录每条记录包含上文提到的环境信号。数据管线方面我们先把原始日志按 campaign时段做聚合切成 5 分钟粒度的统计序列然后滑窗生成训练样本。每窗口长度为 48 个时间步也就是 4 小时预测未来 12 步的环境走向。这里涉及一个数据处理细节样本不能简单随机打散。因为环境序列的时序关系本身就是学习目标跨窗口乱序会导致模型学到“全局平均环境”而不是“状态迁移规则”。我们按 campaign 分组打散——组内保持时序组间可以乱序。这个改动看似微小但对最终效果的提升非常大。3.2 基线设定与评估指标离线评估阶段我们对比了三个基线第一个是纯特征工程加 XGBoost 预测环境走向的 baseline第二个是一个简单的 LSTM 序列模型第三个是带滚动预测机制的 Transformer 模型。Bid2X 作为第四个方案参与对比。评估指标主要有三个维度。第一个是环境向量下游任务的 AUC主要看环境表征能不能帮助出价策略做出更优的决策第二个是趋势预测的 RMSE看模型对“未来环境走向”的预测准确度第三个是工程效率包括模型推理耗时和模块单独更新的成本。结果是环境向量下游 AUC 方面Bid2X 比 XGBoost baseline 高了 5.2 个百分点比 LSTM 高了 3.1 个百分点比滚动 Transformer 高 1.8 个百分点。趋势预测 RMSE 的优势更明显比 LSTM 低了近 12%。3.3 训练中的关键调参与参数记录把调参过程中几个关键的数值记录一下供后来者参考。学习率方面主干 Transformer 用 3e-4adapter 部分用 3e-5两者相差十倍。为什么这么设因为主干学的是稳定结构步子大一点没关系adapter 要在线更新步子大了容易震荡。batch size 对效果的影响比预想大。2048 和 4096 之间模型效果差距大约是 3%但训练吞吐差了近一倍。后来我们取 2048 并用梯度累积模拟 4096 的效果两者落点基本一致但显存压力小很多。dropout 的设置要特别小心。环境信号本身噪声大dropout 设太大会让模型把关键信号也丢掉。我们最后把主干 dropout 定为 0.15adapter 部分定为 0.05。测试过 0.2 和 0.3在对比任务上效果明显下降。重要提示环境建模模型的 dropout 误区很常见——觉得噪声大就该 drop 多一点。实际上环境信号里大部分噪声是系统性的dropout 掉的往往是真实模式。噪声建模应该交给模型内部的注意力机制去处理而不是在输入端暴力屏蔽。3.4 消融实验哪个模块最不能砍消融实验的结果挺有意思。去掉重构损失以后环境向量的可解释性明显下降下游 AUC 掉了 2.1 个点。去掉对比损失跨场景泛化能力明显变弱冷启动场景的效果差不多回到了基线水平。去掉趋势预测损失整个模型的训练信号少了主干效果掉得最惨说明趋势预测还是整个模型的核心任务。还有一个被忽略的维度是 patch 长度。我们用过 4、6、12 三种长度4 的效果最差6 和 12 差不多但 12 的推理耗时明显增加。最后选了 6等于用一小时数据构造一个 patch既有局部细节又有跨段关系是性价比最高的点。这一轮消融做完基本上可以确认 Bid2X 里最不能砍的三件事是趋势预测主任务、重构约束、patch 化的编码方式。对比损失也很重要但加在主干后面做辅助砍掉只是让泛化弱一些不会让整个框架坍塌。4. 线上部署与实时适配细节4.1 延迟控制与模块拆分线上落地的时候最担心的就是延迟。Bid2X 的完整链路包括特征组装、patch 编码、Transformer 前向、adapter 微调、策略输出整个串起来如果超过预算那就得砍模块。实测跑下来bid2x 的环境向量计算耗时大约在 3 到 5 毫秒取决于流量大小。这个数字在我们的预算范围内但注意这是纯环境向量计算不包括策略模型自身的时间。为了达到这个延迟水平我们做了不少工程优化。首先是把 Transformer 层数从 4 层压到 3 层效果损失不到 0.3 个点推理速度快了 20%。其次是量化把权重从 FP32 压到 FP16精度影响可以忽略。最后是 batch 推理优化环境向量计算和策略模型的前向合并到同一个 batch 里省了不少开销。实操心得线上模型的性能优化优先考虑“能不能少算”其次才是“怎么能算得快”。Bid2X 里我们很多结构设计都围绕减少不必要的计算展开而不是盲目堆优化算子。4.2 线上适配的评估闭环上线之后不能只看单一指标。我们搭了一个轻量级的评估闭环实时流量按 1% 分桶做加扰实验对比有环境向量提示和无环境向量提示的出价策略差异。实验指标包括支出效率、转化成本、零转化率、异常出价占比。第一轮实验盯了两周效果在整体上没有出现负向。细分到冷启动 campaign 和流量波峰时段优势明显——转化成本平均低了 8%零转化率下降了 30%。但在流量稳定时段效果几乎持平。说明 Bid2X 的价值主要集中在“环境复杂度较高的场景”而不是所有场景。这个结论对我们后续方向很重要。既然环境影响大的场景才是重点那后续迭代就不应该在稳定时段上抠细节而应该把精力放到极端波动场景的建模上。4.3 冷启动场景的特殊处理冷启动是环境建模最尴尬的场景——没有历史没有序列模型怎么出环境向量Bid2X 的做法比较取巧不依赖目标 campaign 的历史而是用基础模型的先验向量作为起点然后随着真实数据的积累逐步调整权重。具体实现上我们在 adapter 更新时加入了一个置信度系数数据量越少adapter 更新的步长越小数据量越充足步长恢复。相当于先用通用规律顶着再逐步过渡到个性化规律。这个策略在冷启动上的效果提升是最明显的转化成本降低了约 12%。冷启动场景的建模思路应该是“通用优先个性渐进”而不是“先攒数据再建模”。数据攒够了环境早就变了好几轮这种模型天生就慢半拍。5. 常见问题与避坑指南5.1 环境向量退化成常数的排查这个问题训练中出现的频率很高。表现是 loss 一直在降但环境向量在 t-SNE 可视化下聚成一团没有结构。第一次遇到时我们一度怀疑对比损失写错了排查了半天才发现是重构损失缺失导致信息都跑到趋势预测分支里去了。排查的方法很简单把环境向量直接拿来训练一个 3 层 MLP 去预测 campaign IDACC 如果低于 60%说明向量信息量不足。正常训练下这个 ACC 应该在 75% 以上。5.2 在线波动后模型一蹶不振有一轮线上实验遇到一个诡异问题某天流量剧烈波动后模型效果不只是当天变差后面几天都恢复不了。复盘下来问题出在 adapter 的更新上——波峰时段的梯度噪声太大adapter 一次性吃进去太多不良更新直接偏离了正常状态。修复方案是给 adapter 加梯度裁剪和动量约束。梯度裁剪限制单次更新幅度动量约束让 adapter 更平滑地跟随环境变化而不是被极端样本带跑。这两个补救措施加上后恢复时间从几天缩短到几小时。经验之谈在线学习的模型鲁棒性和快速响应是一对天然矛盾。我们最终选择的折中方案是“快速跟进但限制单步幅度”宁可多走几步跟上也不要一步跨过头。5.3 多场景迁移时的坑Bid2X 跨场景迁移时出现过向量分布偏移的问题。具体是流量结构差异很大的两个场景之间环境向量虽然形式上一致但含义可能完全不一致。比如一个全是视频流量的场景和一个全是图文流量的场景环境向量的同一位可能代表完全不同的东西。解决方式是给环境向量加一个场景 ID 条件。Transformer 编码时把场景 ID 作为一个额外的 token 拼进去让模型在训练时就知道“现在处理的是哪类场景”。这样适配到具体场景时模型可以自动调整环境向量的解释方式。5.4 数据时间窗口选择时间窗口的选择对效果影响很大但容易被忽略。我们测试过 1 小时、4 小时、24 小时三种窗口。1 小时窗口的模型对新变化反应最快但噪声大24 小时窗口太平滑对日内变化不敏感4 小时窗口在平衡性和敏感度上表现最好。结合流量场景考虑4 小时窗口正好覆盖一个相对完整的流量波动周期又能保持足够的敏感性。如果场景流量有更强的周期性可以按周期长度动态调整窗口但默认 4 小时已经足够通用。6. 关于后续演进的一些个人想法这块内容做完之后我个人的体会是竞价环境建模这个方向真正的瓶颈不是模型结构而是“环境到底该由谁来定义”这个问题本身。Bid2X 做的是用数据驱动的方式定义环境但如果平台侧能提供更细粒度的流量特征比如竞争度分解、人群重合度等环境建模的精度还能再上一个台阶。如果把 Bid2X 继续往下做我有两个想法。第一个是把环境向量和出价策略之间的连接做得更紧——目前是向量进策略头做 concat后续可以考虑用环境向量动态调整策略模型的温度系数让策略在环境不确定时更保守在环境稳定时更激进。第二个是引入多目标环境表征不只建模竞争维度也建模用户价值维度看看能不能为预算分配提供更全局的视角。另一个未来方向是环境变化预警。目前 Bid2X 的模型输出的是当前环境的状态向量但其实它可以顺带预测环境的突变概率。这个信号如果用来做预警提前调整策略或者暂停出价能显著减少环境突变带来的消耗浪费。最后分享一个踩坑经验环境建模的评估周期一定要够长。我们最早评估模型时只看三天效果结论是正向的拉到两周再看发现稳定时段的负向下滑被平均值掩盖了。环境模型的价值本来就不是天天都体现的评估节奏不对很容易判断错误。我在实际跑这个项目的过程中最大的感受是竞价广告看起来是个“策略问题”但做深了会发现首先要解决的是“观测问题”。环境能被清晰地表达出来策略的优化空间才会真正打开。这也是 Bid2X 这个项目最吸引我的地方。