上周我们在做得物搜索召回层的例行案例复盘一条用户真实query“科比那双AJ”让我彻底坐不住了。双塔向量召回的Top10里有两双湖人配色、两双篮球场分类下的其他球鞋还有一双完全不相干的板鞋——单看相似度指标这事不算翻车但站在用户视角这结果几乎没法用。也就是从那天起我们开始认真讨论一个问题向量检索这口饭是不是真的吃到头了。这篇文章不打算做概念科普也不做纸上谈兵的方案对比。我想完整记录我们团队在过去大半年里如何从“继续卷双塔、卷ANN、卷难例挖掘”的思路中跳出来把召回任务从“向量相似度比对”改造成“序列生成任务”也就是现在大家说的生成式召回Generative Retrieval。整个过程踩过不少坑也推翻过两版方案。如果你正在做电商搜索、交易搜索或任何带强约束的垂直搜索这篇内容应该能给你一个相对完整的参考。先说结论生成式召回不是来替换向量召回的它是在向量检索旁边多开了一条完全不同的路。这条路解决了向量检索长期解决不了的三类问题但也带来了延迟、增量、可控性这些新麻烦。怎么用、怎么训、怎么上都在下面了。1. 向量检索卷不动的三个死结从“科比那双AJ”说起1.1 双塔模型的交互缺陷两个塔各写各的答案做搜索召回的人对双塔结构再熟悉不过query塔和item塔分别编码线上算内积或余弦相似度再配合Faiss这类ANN索引做近邻检索。这套路线的优点是工程上极爽——item向量可以离线全量算好在线只用算一条query向量然后去向量库里做近似KNN。但它最根本的问题也出在这query和item的语义交互被彻底砍掉了。“科比那双AJ”这种query双塔模型处理起来非常吃力。query塔把这句话压成一个768维向量item塔把每双鞋的标题、类目、属性压成另一个向量两者只在最终点积那一瞬间“见了面”。可“那双”是一个指代需要结合“科比”这个语义实体去推理出“科比穿过的AJ”、而不是“湖人配色AJ”或“所有AJ球鞋”。这种细粒度的关联在双塔的点积空间里几乎不可能精确表达。有人会说那用ColBERT这种晚交互模型不就行了确实晚交互能在最后阶段做token级注意力效果比双塔好一截。但晚交互的在线代价是逐pair计算召回阶段要面对百万级item候选这个成本在电商搜索里基本吃不消。我们当时的线上跑双塔少量规则路召回已经很吃力再加晚交互等于把延迟再拉高一倍。所以不是大家不知道双塔笨而是双塔的“笨”换来的是在线可行性。1.2 多条件约束的组合陷阱向量空间撑不起布尔逻辑得物交易搜索比通用搜索更麻烦的一点是用户query里充满了结构化约束。比如“黑色800蓬男款羽绒服”“适合送人的大牌手链”这些query看起来是自然语言骨子里是属性过滤颜色黑色填充800蓬性别男品类羽绒服。双塔模型在处理这类query时会把这些约束“揉碎”平均到一个向量里——好一点的情况是召回一些颜色相近但品类不对的商品差一点的就是完全跑偏。我们做过一个统计在得物搜索里带两个以上显式属性约束的query大概占了全部搜索量的三成左右。对这部分流量双塔召回的真实命中率明显低于简单品类词query。这不是调参能解决的因为向量空间本质上是连续分布但属性约束是离散的布尔逻辑。你可以在向量空间里学出“黑色”这个方向也可以学出“羽绒服”这个方向但“黑色羽绒服800蓬男款”这个联合约束在低维空间里会被压缩成一个折中的点——这个点既不像黑色也不完全像羽绒服更别说满足全部四个约束。后来我们也试过加属性编码、分层embedding确实有提升但始终是“修修补补”。真正让我们意识到必须换引擎的是另一个更隐蔽的问题。1.3 马太效应与长尾流失数据飞轮越转越偏向量召回高度依赖训练数据而训练数据天然偏向高频query和高曝光的item。这带来的结果就是头部的热门鞋款被反复召回长尾新品、小众款几乎失去曝光机会。我们去年的离线数据里有一条很扎眼的曲线——召回列表中item的曝光集中度逐年上升Top1%的商品吃掉了超过40%的召回流量。新品上架后如果本身没有初始流量双塔模型几乎学不到它的有效向量形成“没曝光→没数据→更没曝光”的死亡循环。这个问题的核心在于双塔模型学到的是“query到已有item集合的映射分布”它没有能力“发明”一个它没见过几次的item。但电商搜索和通用搜索最大的不同恰恰在于商品的时效性和新鲜度极其重要。一双新发售的球鞋发售当天的搜索需求是爆炸性的而这正好是向量检索最不擅长的时间窗口。这些死结叠加在一起逼着我们去思考如果召回不再靠比对而是靠“生成”会怎样2. 生成式召回到底在生成什么一次范式的跃迁2.1 从“大海捞针”到“默写答案”生成式检索的本质生成式召回Generative Retrieval的核心思想非常朴素既然选item这么难那我们干脆让模型直接“写出”匹配的item ID。形式上它把召回问题建模成一个条件概率问题给定query序列X和用户上下文去生成目标商品标识序列Y最大化 P(Y|X) 而不是最大化 query向量和item向量的相似度。这个思路最早在文档检索领域被提出——典型工作是Meta的DSIDifferentiable Search Index和清华的SEQUIR。DSI把doc的标识docID当作生成目标让T5模型在给定query时直接生成对应docIDSEQUIR进一步研究了docID的构造对生成效果的影响发现语义化的docID能显著提升生成质量。到了GenRet等工作学界已经开始讨论如何把语料库中所有doc的token构建成可学习的索引空间。用“默写答案”来理解这个范式就非常直观向量检索是给你一张百万人的通讯录让你在里面逐一比对找出谁姓张生成式召回是让模型直接报出那个名字。前者考的是检索效率后者考的是模型的“记忆推理”能力。你可能会觉得这不就是死了记吗模型真能背下几百万个商品吗这就是接下来要解决的核心问题——商品ID怎么设计直接决定了模型能不能背得动、背得准。2.2 商品不是单词语义化ID的设计是成败关键我们在项目初期犯过一个典型错误——直接用数据库里的数字主键ID作为生成目标。模型需要学习“Nike Air Jordan 1芝加哥”映射到“10034562”这样一个无规律的13位数字训练loss怎么都压不下来。这就像让一个人背一本随机电话号码簿纯靠死记硬背是背不动百万级词表的。后来我们参考SEQUIR的思路把商品ID做了语义化编码。具体做法是拆解出商品的属性树品牌2位代码 一级品类2位代码 系列3位代码 年份2位 款号序列3位 单品扩展位3位整个ID变成一个15位、每2到3位成组的序列。这样模型生成ID时不是在背一个随机数字而是在“逐步决策”先想清楚品牌再想品类再想系列最后具体到款。这个改动带来的提升是断崖式的——同样的模型结构换用语义化ID后训练loss下降速度肉眼可见地变快离线Recall100提升了十几个点。背后的道理也很简单生成模型本质上是在做逐token的条件概率建模如果每个token都有语义含义模型就能把“科比那双AJ”解码成“品牌Nike系列AJ年份近年款号高帮篮球鞋”。生成过程变成了一次“推理”而不是一次“记忆”。2.3 不只是换个模型生成式召回的隐性收益换引擎的价值并不只在“效果更好”。生成式召回给我感受最深的是它带来了三件向量检索做不到的事情第一结构化约束可以被当作生成条件。你想让模型明白“黑色800蓬男款”不需要在向量空间里找方向只需要在生成ID的每一步给它约束在解码时对品类位、属性位做mask限制。模型不再需要用连续向量去拟合离散约束。第二外部知识可以注入生成过程。这里的知识可以是品牌知识、潮流社区的口语表达甚至可以来自更大参数量模型的世界知识蒸馏。双塔模型没法轻易地把“科比和AJ”的关系编码进去但生成式模型在预训练阶段见过大量类似语料。第三多目标可以合并。用户画像、会话上下文、实时价格带都可以拼进输入序列里一起参与条件生成而我们不需要为此重建一个向量塔。这三个隐性收益对我们的吸引力比单点指标提升大得多。因为它们意味着召回层从“一个相似度计算器”升级成了“一个能理解需求并给出方案的小型推理器”。3. 得物交易搜索的特殊性为什么我们敢赌这条路3.1 潮流交易场景哑谜式query与精确货号并存得物的搜索数据有个很有趣的分裂感。一边是用户搜“科比那双AJ”“冬天黑棉袄”这种高度口语化、带指代和隐含知识的“哑谜式query”另一边是硬核潮流玩家直接输“AJ1 High OG Chicago 2015”这种精确货号式query。这两种表达使用双塔模型的效果都不理想哑谜式query考验语义推理精确货号式query考验的是对属性序列的完全匹配。特别是对精确货号式query传统字面匹配其实做得不错但它的问题在于无法泛化——用户少打个词、换个大小写、夹带一个年号字面匹配就失效向量模型又对这种细粒度约束不敏感。而这两种表达恰好都能被生成式模型优雅处理理解完语义后模型本身就学会了“精确货号”这种格式的生成方式。3.2 可履约约束召回结果不能“看起来像”就完事交易搜索和网页搜索最大的区别是召回结果必须可履约。用户搜“AJ1芝加哥”你召回一双已经售罄的绝版鞋这比召回一双不相干的鞋更让人沮丧。库存状态、上下架状态、尺码覆盖、价格带、发货时效这些约束在向量召回里很难作为硬条件在线生效通常只能在粗排或重排阶段用规则过滤——但到那时候已经晚了前面的召回可能根本没把真正可履约的商品找出来。生成式召回对这个问题的解法是天然的校验器Validator可以接在生成器后面把“生成结果是否在当前索引中”和“生成结果是否可履约”变成两道硬过滤器。生成器负责给出候选校验器负责保证候选能买、能履约。这种“生成校验”的结构比向量召回更接近交易搜索的本质——先保证正确再追求相关。3.3 成本与风险判断为什么最终立项说实话决定上生成式召回这件事内部争议不小。核心矛盾就是成本向量召回一层倒排索引加一个双塔模型就能跑生成式召回要维护一个不小的生成模型、要做语义ID体系、还要应对解码延迟。我们当时的判断依据有三点一是摩尔的成本曲线生成式模型推理成本每年都在明显下降二是搜索场景的价值足够大召回层的几个点提升对应的GMV增量级远大于工程投入三是团队里已经有人研究过SEQUIR和DSI大家评估后认为可行性比想象中高。最终我们决定做一个“并行通道”而非“替换方案”——生成式召回先作为新增召回路和向量召回并存不成功最多就是浪费一些算力成功了再逐步扩大流量。4. 引擎设计生成器、校验器、融合层怎么协作4.1 整体链路生成召回和向量召回先做加法再做减法我们最终的落地架构用一句话概括就是双路并行校验兜底融合打分。生成式召回不是替代向量召回而是在召回池里新增一路候选。线上整体链路如下用户query进入搜索服务后同时触发向量召回和生成式召回两条通道生成式通道依序执行语义ID解码 → 合法ID映射 → 可履约过滤 → 轻量粗排向量通道照旧走双塔ANN但保留原来的结果不变两条通道的结果集做并集去重送入同一个粗排/精排模型继续走原有排序链路。这样设计的核心考量是风险隔离。生成式召回如果出了问题最坏情况只是丢了这一路的候选还有向量检索兜底而向量召回完全不受影响线上随时可以回滚。在初始阶段我们在生成式通道召回量并不大只补充向量通道之外的新结果这样不会抢占原有曝光灰度验证时目标指标也看得更清晰。4.2 生成器架构选型T5系和LLM系到底选哪个生成器我们选了encoder-decoder架构而不是纯decoder的LLM。在模型选型上我们内部讨论过很多次最终结论是纯decoder的LLM比如Qwen、LLaMA系在长文本生成、开放域对话上很强但在搜索召回这种场景有个致命问题——输出空间是自然语言词汇而不是我们可控的语义ID。你很难保证它每次都严格输出15位编码就算约束解码token效率也太低。encoder-decoder模型T5系天然适合“输入query输出目标序列”的任务。encoder可以完整编码query和上下文decoder每一步生成一个ID片段配合beam search输出可控性高得多。我们线上用的模型以T5架构为基础参数规模在3亿左右词表只保留语义ID相关token和少量文本token。这样模型能完整塞进单个GPU做在线推理延迟可控。如果你有更强的算力也可以用LLM做底座但要在解码约束上额外投入不少工程精力——我们团队评估下来现阶段用中小规模的encoder-decoder模型性价比最高。4.3 校验器先过滤“错ID”再过滤“不可卖”生成模型是概率模型它一定会“编造”出一些不存在的商品ID组合。比如模型生成了一个品牌代码和品类代码组合但数据库里根本没有这个款号。这就是校验器存在的意义。我们的校验器分两层第一层是合法性校验。把所有候选ID映射回商品主键查这个ID是否存在、是否拼写合法。这里用布隆过滤器做index能快速过滤掉绝大多数非法ID。第二层是可履约校验。对合法ID进一步检查库存0、在架状态、尺码覆盖、售卖区域等硬约束。这一层以前在排序后才做现在提前到召回阶段直接砍掉了大量“看着相关但买不了”的无效候选。校验器的实现不复杂但它彻底改变了链路的气质——召回结果不再是“可能相关”而是“确定能买”。这对交易搜索的体验提升是结构性的。4.4 融合层怎么并进主链路不打架融合层我们一开始做得比较粗暴直接对生成召回候选和向量召回候选做并集去重然后统一送进精排。上线后发现一个问题生成式召回的分布和向量召回差异很大精排模型对这种“新来”的候选非常不友好经常给出较低的预估分。这不是因为候选差而是因为精排模型没见过这种召回源的分布。后来我们的解法是在精排特征里加入“召回源”特征并且给生成式召回源一个短的探索期——让精排模型逐渐学到这路候选的真实转化价值。同时在早期我们也限制生成式召回的占比比如不超过总召回量的5%确保不会因为精排不适应而影响主链路指标。等精排充分吸收后再逐步放开。5. 训练配方数据、损失、蒸馏一个都不能省5.1 训练语料的构造把用户行为序列“翻译”成监督信号生成式召回再怎么换范式终究逃不过数据。我们训练语料的主要来源是搜索点击、下单、加购行为构造方式是把一次会话内的query和用户行为串成序列样本输入序列 [用户画像token] [会话query token] [历史点击item语义ID序列] 输出目标 [本次点击/下单item的语义ID序列]长度最多15位输入序列的构造有一个容易被忽视的细节历史点击item的语义ID序列要和当前query的格式对齐。早期我们直接用商品标题做历史行为效果很差因为标题文本和输出目标语义ID不在同一个语义空间。后来把所有历史行为都先转成对应的语义ID序列模型的生成难度立刻下降了一个档次。5.2 三阶段训练领域预训练、召回精调、打分对齐我们的训练流程分成三个阶段每一阶段解决不同层面的问题阶段任务训练目标作用领域预训练商品标题、潮流社区内容、类目文档对商品语义ID做掩码恢复让模型学习潮流领域知识和商品ID的语法结构召回精调搜索行为日志query→目标商品最大化点击/成交ID序列的条件概率让模型学会“给query生成正确商品”打分对齐同query下正负样本对最小化正样本与负样本生成概率差距让模型具备基础的排序判别能力而不是只会生成领域预训练这步很关键。我们直接拿全量商品标题、社区穿搭帖、潮流百科文档来做自监督的ID掩码恢复。比如把“AJ1 High OG Chicago 2015”做token化随机mask掉“Chicago”对应的ID段让模型恢复。这一步让模型对“商品ID的语法”建立起了先验认知后续精调也收敛得更快。第三阶段的打分对齐本质上是借鉴了对比学习在同一个query下正样本被点击/下单的item的生成概率应该高于负样本曝光但未点击的item。我们构造了一个Pairwise Loss把负样本的生成概率当做一个margin去推开。这个阶段让生成模型不仅仅是“能生成对的”还具备了对候选排序的能力。5.3 容易翻车的两个细节beam宽度与生成长度约束训练和推理阶段有两个细节必须提前定好否则后期返工成本很高。第一个是beam search的宽度。beam太窄模型第一遍解码就锁死在次优路径上beam太宽在线延迟线性上升。我们最终用的beam4配合合法性校验器做候选过滤后效果和beam8基本持平但延迟降了将近一半。第二个是生成长度。商品语义ID虽然是15位但我们可以根据query类型动态约束解码长度。比如用户只搜“Nike”生成器只需解码到品类位就停止不一定非要补齐15位。这样既能缩短延迟又能避免模型强行组装出一个“过度具体”的伪ID。6. 上线前必须趟过的坑延迟、增量与非法输出6.1 延迟预算从“离线随便跑”到“每毫秒都很贵”生成式召回最大的非议就是延迟。双塔召回在线通常个位数毫秒而生成模型解码一次少说也要几十毫秒起步碰上beam search直接奔着100毫秒去了。我们的优化思路是把延迟压在三个层级第一层是模型规格。3亿参数的T5模型配合FP16量化单次decode控制在20毫秒以内第二层是解码策略。除了beam4之外我们还做了前缀缓存——同一session内重复query的encoder输出直接复用省掉重复编码第三层是服务化架构。生成服务独立部署和主搜索服务之间用异步请求超时熔断生成通道超过40毫秒就直接降级返回空结果绝不让生成延迟拖垮主链路。第三层这招很重要。搜索服务不能因为新功能变慢所以我们在架构设计上就是“可降级”的。后期优化好之后即便生成召回延迟稳定在50毫秒以内我们依然保留这个熔断开关。6.2 新商品增量token空间不是静态的商品是不断上新的而模型的词表和ID映射却是训练时定死的这就产生了OOV问题。新上架一款球鞋如果它的语义ID从没在训练中出现过生成模型根本不可能输出它那生成式召回对新品就是瞎子。我们的解法是为“增量ID”设计了一套动态映射机制。新商品的语义ID按照编码规则生成后不会立刻成为模型可生成的token而是先进一个“增量索引池”。当用户query经过生成器输出候选ID前缀比如品牌品类系列已经对上某一类商品我们会在增量索引池里做前缀匹配把符合条件的新品作为补充候选直接加入。这样生成模型没“见过”的新品也能通过前缀逻辑被覆盖到。代价是这部分新品缺少模型级的精排但我们会在校验后补一个简单的相关性规则等数据积累后再把增量ID纳入下一轮模型训练。6.3 非法ID处理模型会“编造”不存在的款你训练得再好生成模型依然是概率模型它一定会输出一些语法完全合法但在商品库中不存在的ID。我们上线初期统计过这类“幻觉ID”占生成结果的5%-10%。如果不处理它们会直接污染召回池。我们的处理不是简单丢弃而是加了一个“beam候选重试”机制。模型解码时保留beam上的前N个候选序列如果Top1候选被校验器判定为非法就自动尝试下一个合法候选而不是直接返回空。这个机制上线后生成式召回的可用率从92%提升到97%以上。你可以把校验器理解成一个严格的监考老师而beam search提供了“备选答案”两者配合能大幅减少模型的幻觉影响。6.4 灰度验证别让新引擎变成线上事故生成式召回上线时我们遵循了非常保守的灰度策略第一阶段跑影子流量把生成式召回的结果记录到日志但不进入线上排序第二阶段开通1%小流量只观察搜索点击率、无结果率、成交转化率等指标第三阶段逐步放量到5%、20%、50%每一步都有独立的数据看板和自动回滚开关。这里有个经验值得分享不要只看点击率这类泛化指标一定要盯“无结果率”和“翻页深度”。生成式召回在早期因为校验过于严格一直存在候选过少的问题点击率虽然好看但用户搜索后能看到的结果明显变少。这个坑如果不看无结果率指标很容易被整体点击率上涨掩盖掉。7. 效果复盘涨了多少以及涨在了哪里7.1 离线评估召回覆盖率与长尾指标的双重改善先看离线结果。我们以双塔模型的最强版本作为baselineRecall100约0.83生成式召回通道加入后整体召回池的Recall100提升到0.87左右但更有意义的是“增量命中”部分——生成式通道单独命中、而向量通道完全没有召回的有效成交商品占到了总成交商品数的5.6%。这说明生成式召回真正在做的是把过去向量检索完全够不到的那部分长尾需求拉了回来。在长尾query子集低频、包含属性组合、口语化表达上增量命中的比例会翻倍。我们抽样了2000条长尾query发现其中大概12%的成交商品是由生成式召回独立带来的。这个数字已经远超我们的立项预期。7.2 在线指标点击率、转化率与GMV的相对提升在线灰度放量到全流量后核心指标的变化如下指标相对提升幅度搜索点击率CTR1.8%成交转化率CVR4.2%搜索带动的GMV2.7%无结果率-0.5个百分点这些数据单拎出来说幅度都不算爆炸但你要知道这是在召回层做的一次“加法”获得的增量而不是通过降价促销或者换排序模型。在搜索这个环节提升永远是最难啃的骨头。CVR和GMV的提升幅度远高于CTR这说明生成式召回的增量结果更贴近成交意图——这大概率与“可履约校验前置”的设计有关召回来的东西更靠谱用户愿意买单。7.3 提升来自哪里三类被“救活”的query为了弄清楚提升来源我们做了详细的归因分析。最终发现提升集中在三类query上多属性组合约束型query比如“黑色羽绒服男款800蓬”。过去向量召回想满足所有约束都很难现在生成器通过语义ID逐步解码天然逼着模型按属性树走位这类query的成交转化提升了接近8%。口语化与同义改写型query比如“科比那双AJ”“送男朋友的球鞋”。生成模型对这类表达的语义理解能力明显优于双塔带来了大量增量成交。新奇特与时效性query比如新发售鞋款的“货号式搜索”。增量ID池的前缀匹配机制保证了新品在发售当周就能获得召回机会这对潮流交易平台的价值是不言而喻的。问题类型双塔向量召回的表现生成式召回的表现多属性约束组合向量空间平均化约束被稀释语义ID逐位解码约束直接生效口语化指代表达语义泛化弱点积空间难以对齐生成模型善于处理隐含推理新品冷启动曝光不足导致向量未收敛循环窘境增量ID池前缀匹配可在线覆盖可履约性过滤需在排序后处理无效结果多校验器提前介入候选可买可卖在线延迟毫秒级极快数十毫秒级需工程优化和降级开关8. 这条路的边界在哪我们还留了哪些坑没填最后说几句大实话。生成式召回不是银弹它现在还有几个明显的边界没突破。第一是千亿级商品规模的扩展性。如果商品量上去一个数量级语义ID的token空间会急剧膨胀模型的记忆压力也会成倍增加。我们现在的方案是在“亿级以内”这个规模上成立的更大的规模可能需要把生成式召回做成“多级缩小范围”的pinpoint架构而不是一步到位生成完整ID。第二是可解释性。生成模型不会告诉你“我为什么生成这个ID”这给问题排查带来了额外难度。我们目前是靠日志里记录的beam路径和校验器命中情况去做事后归因但距离真正的推理过程透明还有一段路。第三是混合场景迁移的泛化能力。召回层、粗排层、精排层的边界在这篇文章里显得很清晰但在实际系统里生成式模型能做的事远远不止召回。团队里已经在探索用生成式思路做“个性化购物车预测”和“穿搭组合推荐”如果后续跑通了我会再写一篇专门的文章分享。我个人在这段经历里最大的体会是当大家都在卷同一个技术指标时真正值得关注的反而是“这个技术指标到底在为什么服务”。向量检索的内卷本质上是把“相似度”当成了搜索的全部但搜索的本质不是算相似度而是理解需求并给出可执行的答案。想明白这一点很多所谓的范式跃迁其实只是回到了正确的问题上而已。