前两年跟同行交流被问得最多的问题是“你们向量检索用 HNSW 还是 IVF量化比特设多少双塔是不是得上 cross attention” 每次我都耐心回答但心里清楚这些都不是交易搜索召回层最该被攻克的关卡。得物交易搜索从去年开始做了一次召回层的底层重构核心变化不是把向量检索调得更狠而是把召回从“度量匹配”换成“条件生成”——也就是业内讨论越来越多的生成式召回。这篇文章把我们的动机、方案设计、落地细节和踩过的坑完整拆一遍给同样在琢磨召回范式跃迁的同学一个可参考的样本。1. 向量检索不是不重要是“只靠它”不够了1.1 向量检索解决的痛点与本质先回到基础。向量检索这套范式能流行是因为它把“语义匹配”这个过去极难工程化的问题变成了一个可计算的度量问题。做法大家很熟query 和商品分别过双塔编码器映射到同一个向量空间然后用内积或余弦距离衡量相关性。训练目标通常是对比学习那一套——让点击/成交的(query, item)向量距离更近让没交互的拉远。线上用 ANN近似最近邻索引把几十亿商品压成可快速搜索的图或倒排结构在毫秒级返回候选。这个范式的历史功绩不用怀疑它把“ query 和 doc 表面不匹配但语义相关”的场景接住了。比如用户搜“通勤小白鞋”过去靠词面匹配的倒排索引很难搞因为“通勤”和“小白鞋”都不一定出现在商品标题里但向量空间里它们确实和大量“白色休闲板鞋”离得近。得物搜索的上一代召回主力就是双塔ANN这个组合至今仍在服役我不会劝你拆掉它。但问题是交易搜索的目标不是“语义相关”而是“成交”。语义相似和交易命中之间隔着一条很多团队没意识到的鸿沟。这几年大家把向量检索卷到了极致——更大的 batch、更难的负样本、更精致的损失函数、更快的 ANN——但交易指标的增长越来越平。不是向量检索做得不好而是它这个范式本身有结构性上限。1.2 交易搜索场景里向量检索的三块天花板第一块天花板硬约束缺失。交易搜索的 query 里大量携带数值型和枚举型硬约束比如“500以内的篮球鞋”“42码白色空军一号”“适合夏天穿的透气跑鞋”。向量编码器能把“篮球鞋”“跑鞋”“白色”这类语义编码得不错但对“500以内”“42码”这种精确边界基本无感。你可以在训练时把价格区间塞进特征但 ANN 召回阶段做的是近邻搜索不是约束求解。结果就是向量召回一批差不多的商品硬约束留给后面的粗排精排去过滤而过滤后候选池经常只剩一两个甚至空。用户感知就是“明明有货但搜不到”。第二块天花板尾部 query 的样本稀疏。双塔模型的表达能力上限取决于训练数据里(query, item)对的覆盖度。头部 query 样本充足向量学得不错但交易搜索有大量长尾 query比如“下雨天穿什么鞋不容易滑”“送男朋友的生日礼物球鞋”。这些 query 本身出现次数少双塔很难学出稳定的表征。你可以做 query 归一化、扩展、聚类但本质问题没变模型没见过足够多样本就谈不上“理解”。第三块天花板多条件组合的“组合爆炸”。用户真实需求常常是“品牌品类场景价格带尺码风格”的多维组合。两个塔各自把 query 和 doc 压成一个稠密向量等于把多维约束强行压到一个低维空间里的一个点。这个过程信息损失非常大。你可以把向量维度拉到 768、1024但组合空间是乘积级的维度涨得永远没组合涨得快。所以双塔在简单语义匹配上很强在复杂约束组合上天生吃亏。1.3 一个反直觉的事实语义越相似交易越可能失败举个我们调模型时反复遇到的真实例子。用户搜“AJ1 芝加哥 42码”注意这个 query 的成交意图极其明确就是要 Air Jordan 1 Retro High OG Chicago 这个特定鞋款、特定配色、42 码。但双塔向量召回很容易召回来一堆“类似 Chicago 配色的 AJ1 衍生款”“红色 AJ 其他鞋型”“42.5 码的 AJ1”。为什么因为从语义向量空间看这些商品和 query 的距离都很近——它们确实是“AJ1”“红色系”“篮球鞋”——但都不是用户要的那一件。语义相似度拉满交易命中率为零。反直觉的地方就在这里在非交易场景比如内容推荐、资讯搜索语义越相似用户体验越好但在交易场景用户要的不是“相似的东西”而是“能满足全部约束的那一个 SKU”。你召回一堆相似但不满足约束的商品用户不会觉得“推荐得真懂我”只会觉得“搜不到我要的”。这个案例让我们开始认真审视召回阶段是不是不该只做“匹配”而是应该直接“算出”满足条件的目标。这就是生成式召回的起点。2. 生成式召回到底在“生成”什么2.1 召回问题的另一种表述条件生成而非度量匹配传统召回的问题表述是给定 query q 和候选商品集 D学习一个打分函数 f(q,d)取 topK。这本质上是一个“对全集打分”的问题——你想象一个巨大的数据库每个商品都算一遍相似度然后挑最高的 K 个。ANN 只是把“全算一遍”这个动作加速了但没有改变问题的本质相关性是在向量空间里“度量”出来的。生成式召回换了一个问法给定 query q训练一个条件生成模型 P(d|q)让模型直接“生成”最可能满足用户需求的商品。这里的 d 可以是商品 ID、SKU ID也可以是商品属性序列。模型不是在候选集里做近邻搜索而是把“用户需求到目标商品”的映射关系直接编码进网络参数里。你可以这么理解传统召回像一家猎头公司收到职位需求后去人才简历库里筛简历简历库可以很大但筛的时候只能靠标签匹配和相似度排序。而生成式召回更像一个对行业极其熟悉的资深顾问他不需要查库直接根据职位描述在脑子里“计算”出最合适的几个候选人是谁。生成式模型也是类似——它不检索索引它“算答案”。2.2 从“按图索骥”到“直接出图”的范式转变过去做召回物理上要维护一个巨大的索引倒排索引、向量索引都要把全量商品组织成可查询的结构。商品新增、下架、信息修改都要实时同步到索引。召回质量的上限很大程度取决于索引结构能承载多少信息——向量索引承载了语义信息但丢掉了硬约束信息倒排索引承载了词面信息但丢掉语义信息。生成式召回的优雅之处在于索引信息被内化到模型参数里。模型经过训练之后它的参数里已经编码了“什么 query 对应什么商品”“哪些属性之间经常共现”“价格带与品牌之间的约束关系”等知识。线上推理时不需要回查一个显式的索引结构而是通过自回归解码一步步“算”出目标商品序列。这在范式上是真正的跃迁从“按图索骥”变成了“直接出图”。当然这不是说生成式召回完全不需要任何外部结构。我们的落地实践里生成式模型输出候选之后仍然会有一个轻量级的约束校验层和去重层保证输出结果可交易。但核心的候选生产能力从“检索”迁移到了“生成”。2.3 生成式召回的两种主流落地形态据我观察业界探索生成式召回主要有两条技术路线。一条是序列生成路线把商品 ID 当作 token用类似 T5/BART 的 encoder-decoder 结构把 query 编码后作为 promptdecoder 自回归地生成一串商品 ID。训练样本是(query, 成交商品ID序列)。线上用 beam search 或采样方式解码出 topK 候选。这条路线落地相对直接工程链路上和 NLP 生成任务高度相似复用空间大。我们最后选择的就是这条。另一条是扩散模型路线把商品向量看成数据分布中的一个点用扩散模型学习从噪声到真实商品向量的生成过程。推理时从随机噪声出发逐步去噪得到商品表征再映射回具体商品。这条路线在候选多样性和全局一致性上潜力更大但训练成本高、推理延迟难控、商品 ID 到向量的映射稳定性也还有不少问题。更适合作为下一代探索方向短期落地风险偏高。不管哪条路线核心共性是一致的不再追求“找得准”而是追求“算得对”。模型直接拟合“用户需求 - 满足需求的商品”这条因果链而不是拟合“query 语义 - 商品语义”的相关性。这个转变对交易搜索来说是根本性的。3. 得物交易搜索的生成式召回落地实录3.1 样本构造把交易行为改写成生成语料第一步也是最容易被低估的一步样本构造。传统双塔训练样本是 (query, doc, label) 三元组label 是 0/1 或者点击/成交权重。生成式召回需要的是 (query, 商品ID序列) 这样的“提示-答案”对。这个改造听起来简单实际上有不少讲究。我们拿搜索的成交日志做主要样本来源一个用户搜了某 query最终成交了某个商品就构造一条“query - 成交SKU ID”的样本。这里有几个设计细节商品粒度我们的 item ID 分成两层SKU 级和商品级。SKU 级携带尺码、颜色等精确信息对交易场景更敏感但 ID 空间巨大、更新频繁商品级更稳定但会丢属性约束。初期我们主用商品级 ID 生成候选SKU 级约束交给后续排序和过滤层解决这样生成模型压力小、词典稳定上线节奏也更可控。负样本纯用成交日志会让模型只见过正例没有见过“不该生成什么”。我们会在同一 session 内、相似 query 下采一些曝光但未成交的商品作为负例样本让模型学到“这个 query 不应该生成那个商品”的边界。上下文增强单条 query 信息量有限我们尝试把用户近 N 次点击序列、加购序列也拼进输入让生成目标从“这个搜索词对应什么”变成“这个用户在当下场景里想要什么”。实验证明这个改动对长尾 query 的召回帮助很明显。3.2 模型选型与训练的关键细节模型结构我们最终定了 encoder-decoder 的 Transformer出发点很朴素既然要把 query 变成商品 ID这就是一个标准的序列到序列问题。Encoder 负责理解用户需求Decoder 负责在商品 ID 序列空间里搜索最可能成交的目标。词典设计是个容易被低估的工程点。直接拿几百万商品 ID 当 token词典爆炸不说还会带来严重的样本稀疏——每个 token 出现的次数太少模型根本学不会。我们的做法是给商品 ID 做分桶映射对近义词、同款不同配色等高频共现商品分配相邻 ID并在 embedding 层做参数共享。本质上相当于给商品 ID 空间做了一次语义化压缩。这一步做完词典规模降了一个量级训练速度和效果都有明显提升。训练过程有两点值得分享。第一解码目标里我们让模型预测的不只是一个商品 ID而是“商品 ID 成交权重”。换句话说损失函数是交叉熵但按成交金额或转化率加权。这样模型生成候选时不光学了“哪个商品对该 query 合适”还学了对“成交贡献更大的商品”给更高优先级。第二我们在训练最后阶段加入了多任务学习除了生成商品 ID还让模型同时预测 query 的品牌意图、品类意图、价格带意图。这样相当于强制模型先“理解需求结构”再去“生成答案”。别看这个改动小它对长尾 query 的泛化能力帮助很大因为模型学到了“先拆解需求再匹配商品”的中间表征。3.3 在线服务架构与延迟控制生成式召回上线前我们内部最大的疑虑是延迟。向量检索走 ANN 是毫秒级近邻查询生成式模型要做自回归解码每解码一个 token 就是一次前向计算。如果生成 10 个候选都要逐个解码延迟很容易失控。我们最后落地的方案做了三层优化。第一层Decoder 尺寸收缩。为一线上服务单独蒸馏了一个 4 层的轻量 decoderencoder 保持完整。线上实测表明decoder 深度从 6 层减到 4 层生成质量几乎不掉延迟降低约四成。第二层Beam size 控制与提前停止。固定 beam size4并且在解码到第 3~4 个 token 时对整棵 beam 做一次“可成交性”预判明显不行的分支直接剪掉。这个技巧对延迟的改善很直接因为大多数样本解码两三个 token 后就能锚定到具体商品类目不需要跑完整个序列。第三层融合部署而非替代。我们没有把生成式召回做成独立召回通道而是把它做成一个“辅助候选生成器”与向量召回并行双方各出 K 个候选混合后进入排序层。这样即使生成式模型偶发故障或延迟抖动向量召回通道仍然在兜底整个搜索服务不会挂。从线上看生成式通道的 p99 延迟控制在 40ms 以内业务指标收益显著但工程风险被压到了很低。3.4 新旧两套范式如何共存这里想重点强调一个思路生成式召回的落地不是“替换”向量检索而是“叠加一种新的候选供给方式”。我们在实践中反复验证过两者是互补关系不是替代关系。向量召回在语义宽泛、没有强约束的 query 上仍然很强比如“潮流运动鞋推荐”这种 query 召唤的是“泛兴趣候选池”向量召回的多样性和稳定性都更好。生成式召回则在“多约束精确满足”场景上碾压比如“复古跑鞋 千元内 适合秋冬”它能把品牌、价格、场景、季节完全揉进生成过程。所以我们线上做了一个意图分诊判定为明确交易意图的 query 加大生成式通道的候选占比泛意图 query 保持向量召回主导。这个“双通道分诊”架构比直接上线一套新系统要稳得多。既拿到了生成式的增量收益又不牺牲向量检索在通用场景上的成熟表现。如果你也要做这个方向的改造我强烈建议你按这个路径走不要一上来就玩“范式替代”。4. 离线指标涨了线上却翻车——我们踩过的四个坑4.1 热门商品霸榜长尾商品“被遗忘”第一个坑出现在小流量测试阶段。离线指标很漂亮但上线后发现生成式通道返回到排序层的候选大面积集中在高曝光热门商品上。看板显示长尾商品的渗透率反而比纯向量召回更低了。原因不难理解生成式模型学的是条件概率 P(item|query)而训练数据里热门商品的出现频次远高于长尾商品。模型天然偏向高频项这是生成模型的“通病”。尤其在用轻微采样或 beam search 时热门商品会占据 beam 里的多个位置长尾商品根本没有出头的机会。我们的解法是双管齐下。先做样本层面的纠偏对高频商品样本做降采样对低频商品做升采样类似 NLP 里的 logQ correction 思想。再做解码层面的约束在 beam search 时限制同一个二级类目下的候选数量强制增加候选的商品类目多样性。两个改动叠加后长尾商品在生成候选中的占比恢复到了和向量召回相当的水平同时原本的精度收益还保住了。4.2 Teacher forcing 带来的训练与推理不一致第二个坑藏得更深。我们做生成式模型是用标准的 teacher forcing 方式训练——decoder 的每一步输入都是真实样本里的前一个商品 ID。但线上推理时decoder 的输入是它自己上一步生成的输出。如果某一步生成错了后面的生成会沿着错误轨迹继续放大最后整个候选序列的质量崩掉。这就是经典的 exposure bias。它在召回场景的发现很有意思离线评估里模型表现很好因为评估时同样用了 teacher forcing但在线真实流量里模型前一步生成偏离后面的候选立刻出现连环错误端到端转化掉得比预期明显。就是因为离线测试“作弊”了。我们的修正方案是 scheduled sampling 加一致性正则。scheduled sampling 大家熟训练时按概率用模型自己的输出替代真实输出概率随时间从 0 逐步增大。我们另外加了一个一致性损失强制模型在同一 query 下即使前一步输入略有不同最终生成的商品序列也应尽量一致。这让生成过程对单步错误更鲁棒。改造之后线上长尾 query 的端到端成交率回升了约 8%。4.3 商品 ID 的“新老更替”引发语义漂移第三个坑在商品供给高速变化的电商场景不可避免。得物的商品池更新很快每天都有大量新鞋新衣上架、老款售罄下架。生成式模型把商品知识全部编码在参数里而新商品在训练时是“不存在的”模型根本没有见过它的 ID更不可能生成它。新品冷启在向量检索时代只需要把新商品 embedding 灌进索引就行在生成式范式下却成了个头疼的问题。我们试过几种解法。最直接的是定期增量训练每天用小批量新数据 finetune。但增量训练会干扰模型对老商品的知识可能出现“学一个新商品忘一个老商品”的灾难性遗忘。后来引入了一个轻量级的“新品映射网络”在不更新主模型参数的前提下学一个从商品属性文本到生成 logits 修正量的映射。简单说让一个浅层网络负责“补课”新品知识主模型负责老品知识两者叠加作为最终的生成概率。这套方案上线后新品在生成式通道的被召回率追平了向量检索通道。4.4 生成候选多样性不足排序层无米下锅第四个坑来自排序层的吐槽。生成式通道刚上线时排序层的同学发现一个现象精排模型打分靠前的商品在生成式候选里往往高度同质——同一款鞋的不同配色、同一个品牌下外观几乎一样的鞋型。问题是精排需要的是“候选之间有区分度”如果召回上来的全是近似款排序模型再强也排不出差异化结果用户体验依然是“翻来翻去就那几双”。beam search 本身就是多样性杀手它每一步都保留概率最高的几个分支最后得到的往往是同一概率簇里的近邻商品。我们的对策是在解码目标里直接加入多样性惩罚对 beam 内的候选计算两两相似度相似度过高的候选在累计得分里扣分同时引入 grouping beam search把候选按品牌/类目/价格带分组每组独立做 beam search最后合并。这样既保证了每一组内部的高质量又让组间有足够的差异。改动后排序层的 NDCG 和用户点击深度都有可见提升。5. 召回评估体系也必须跟着“跃迁”5.1 传统评估指标为什么不够用了做传统召回时大家习惯用 RecallK、Hit Rate、MRR 这类指标。它们的共同假设是存在一个“标准答案集合”通常是人工标注或历史成交日志然后看模型召回的候选里有没有命中标准答案命中的比例越高越好。这个评估框架对双塔模型有效因为双塔模型确实是在做近邻检索在“历史行为”这个标准答案维度上可以被度量。但这个框架对生成式召回有明显盲区。生成式模型的强项恰恰是“生成历史行为之外的合理商品”——用户可能从没买过某个商品但该商品在属性上完全满足用户 query 的约束生成式模型把它算出来这本该是功劳。然而用传统 RecallK 去评估这个有效召回因为不在“历史标准答案”里会被记成负分。结果就是我们辛辛苦苦优化出来的生成式能力在传统指标上看不到任何收益反过来误导团队以为模式不成立。5.2 我们线上用的升级指标组合踩了这个坑之后我们给生成式召回设计了一套分层评估体系分为离线与在线两组。离线看四个维度一是约束满足率人工标注一批带明确属性约束的 query检查生成候选里有多少比例完全满足约束二是长尾覆盖率统计低频 query 生成候选的成功率和多样性三是新品召回率针对上架 7 天内的商品统计召回占比四是候选同质度计算生成候选两两间的特征相似度均值控制多样性问题。在线看三个核心无结果率是底线指标生成式通道参与后不能比之前的无结果率更高成交转化提升是收益指标按实验组和对照组计算相同 query 下的成交转化差异深度交互指标兜底用点击深度、加购率等判断生成式候选是不是只是“看着相关、实际不想要”。这套组合跑了大半年基本能准确反映生成式召回的真实价值。评估体系这件事特别想多说一句很多团队做新范式模型换得很积极评估体系却还沿用旧范式的标尺最后得出“新范式没用”的错误结论。如果决定做生成式召回评估指标一定要跟着范式走否则你都不知道自己在优化什么。6. 给想试生成式召回团队的三点建议如果你看完这篇也动了改造召回层的念头我会建议你先冷静回答三个问题。第一你的业务瓶颈是不是真在召回如果首屏无结果率很低、用户主要是“有结果但排序不精准”那你的钱应该花在排序模型上生成式召回解决不了你的问题。第二你的 query 结构里约束型占比够不够高没有足够多的多条件精确匹配场景生成式召回的相对优势发挥不出来ROI 会打折扣。第三你的工程团队能不能接受训练和推理链路复杂度上一个台阶生成式召回不是调个包就能上的技术它需要训练样本重构、模型调优、serving 改造三个方向同步投入。我们选择这条路是因为得物交易搜索的 query 里天然有大量强交易意图表达——用户来就是“要买那件东西”的而不是“随便逛逛看看”。这种情况下生成式召回把“理解需求”和“匹配商品”两件事合二为一优势是实打实的。最后分享一个我在这个项目里感触最深的一点技术范式的跃迁最难的不是模型本身而是团队脑子的“范式切换”。我们内部刚开始推生成式召回时最大的阻力不是工程难度而是很多人默认“召回就该是检索生成怎么可能做召回”。直到我们用小流量实验证明了约束满足率大幅提升这个认知才被逐渐扭转。所以如果你想做这件事先花时间把团队对召回的理解对齐再动手写代码你会走得比我顺畅得多。
阅读完成 · 觉得有帮助?