ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

生成式召回:打破向量检索天花板,让搜索更懂结构化需求

生成式召回:打破向量检索天花板,让搜索更懂结构化需求 做搜索召回做了快六年从倒排到向量检索我把能调的超参基本都调遍了。前阵子看到一个说法——“过去十年我们把用户query和商品都映射到向量空间现在该换一种思路了”我盯着这句话看了很久然后去翻了得物交易搜索的相关技术分享越想越觉得这事值得认真聊聊。先说结论以向量内积为核心的召回架构已经摸到天花板了。原因很简单query和doc被双塔模型各自压成一个向量在这一个点里要塞进用户的所有意图、商品的全部属性信息瓶颈是物理上绕不过去的。而得物这类潮流电商场景里用户的query往往是“Nike Dunk 黑白 42码”“适合通勤的复古跑鞋”——这种高度结构化的约束靠一个768维向量去表达先天就是吃亏的。所以这半年我一直在琢磨“生成式召回”这条路。它和传统向量检索的底层逻辑完全不同不再做“匹配”而是做“生成”。方向对了后面很多问题会自然解开。这篇文章我想从原理到工程落地把我这段时间的研究和踩坑完整记录下来。1. 为什么“向量化一切”的交易搜索漏召回越来越严重先说清楚我对向量检索的态度它没有错它只是不够用了。双塔结构的本质是把query侧和doc侧各自编码成一个固定维度的稠密向量然后用内积近似语义相似度。这套范式在开放域、短文本、语义泛化的场景非常强——比如你搜“适合下雨天穿的鞋”它能给你召回一堆“防水靴”“GTX徒步鞋”这在倒排索引时代是做不到的。但交易搜索天然不是“纯语义”场景它最大的特点是用户意图高度结构化。我随便举几个真实会出现的query“Air Jordan 1 黑脚趾 42”“卫衣 男女同款 春秋款 200-300”“始祖鸟 冲锋衣 男 硬壳”在得物这类平台上商品是潮流单品用户表达里永远混杂着品类、品牌、型号、尺码、价格带、季节、风格、人群属性。这种query的难点在于约束是多维的但向量表达是稠密的。你把这些标签全塞进一个embedding从数学上讲就是在做有损压缩信息一定丢。我还做过一个很实际的对比实验。用某公版双塔模型跑了一批真实搜索日志把其中“召回成功”的样本拿出来看发现一个扎心规律query里约束越多向量召回的成功率越低。query类型平均约束数向量召回Top50命中率商品详情页点击率单意图短词如“卫衣”1.247.6%品类风格如“复古跑鞋”2.331.2%品牌品类型号如“Nike Dunk 黑白”3.518.4%完整规格如“AJ1 黑脚趾 42码”4.69.7%这不是哪家模型调参的问题这是范式本身的瓶颈。你想想一个768维的embedding要同时编码品牌、品类、型号、价格、尺码、颜色、风格……这就像让一个人背着一整本商品目录跳高能跳多高全靠压缩算法的本事但天花板就在那。还有一个更隐蔽的问题头部流量占坑严重。向量检索对热门商品天然有偏头部item把召回池占满了长尾商品和冷启动商品根本没有曝光机会。这对以新品、限量款为核心的潮流平台来说是很要命的事情——新鞋发售那几天往往是搜索流量最猛的时候但传统向量路根本接不住这种“新鲜需求”。所以我理解的“范式跃迁”不是说把双塔扔掉而是承认一件事召回不该是“从候选池里捞”而该是“按约束条件生成”。方向完全不同。2. 生成式召回的核心逻辑把“匹配”变成“解码”我第一次接触“生成式检索”Generative Retrieval这个概念是在NLP领域。当时学术界有个思路不建索引不搞相似度而是让模型直接产出文档ID或者文档相关的标识符。比如有人做“query to docid”的生成任务效果居然能和双塔掰掰手腕。当时我觉得这玩意在搜索工业界远得很直到后来想通了它映射到电商搜索的方式。其实生成式召回落到交易搜索可以有两种完全不同的形态形态一query生成item序列到序列的直接解码把候选商品库里的item都映射成唯一的标识符item ID或者商品结构化编码然后用一个seq2seq模型输入query直接解码输出一串相关item的ID。这和机器翻译是一模一样的套路——“query是源语言item ID是目标语言”。形态二item生成query再反查类似“查询扩展倒排”的组合拳更工程化一点训练一个生成模型输入是商品详情页信息标题、属性、价格、描述输出是“这个商品最可能被哪些query搜到”。上线时系统为每个商品生成大量虚拟query比如一个AJ1黑脚趾能生成“AJ1 黑脚趾 42”、“乔丹一代 黑红 男鞋”、“nike air jordan 1 经典款”等等把这些query灌进现有的倒排索引。用户来搜索时通过倒排召回商品。这两种形态都叫“生成式召回”但思路完全不同。第一种是端到端直接映射更激进对在线服务的挑战更大第二种更像是“用生成模型重写/扩展了商品的表达”工程上落地平滑很多。得物交易搜索的分享里我看到他们走的是混合路线——用生成模型直接解码商品ID同时结合了item的属性和结构化约束。这个方案有意思的地方在于它把原来的“用户-商品”双塔相似度匹配变成了“用户query-商品标识符”的条件概率生成P(item | query) ∏ P(id_token_i | query, id_token_1...i-1)说白了就是把商品ID当成一个个token用自回归的方式逐个生成。这和你在对话框里让AI写一段话是一模一样的机制只不过“目标语言”从自然语言变成了“商品ID序列”。这背后其实有一个优雅的理论转变双塔的目标函数是**“缩小相似度距离”生成式的目标函数是“最大化条件概率”**。前者的瓶颈是我上一节说的信息瓶颈后者则没有——它把query的每个token都参与了解码过程没有“压缩成一个向量再比内积”这个有损环节。而且生成式召回还有一个被很多人忽略的优势它可以天然地引入上下文感知。双塔模型里的user侧塔通常只编码了当前query但生成式模型可以在解码的时候把用户历史行为、当前session的热门趋势、甚至季节气候全部塞进条件里每个item ID的生成概率都会动态变化。这就是“个性化生成”向量检索想做这个得另外再接一个重排层。3. 从“机器翻译”看生成式召回在交易搜索的落地形态要理解得物这套系统的具体路子我建议换个视角把它当成一个翻译任务而不是检索任务。源语言用户的query 上下文信息历史点击、类目偏好、价格带偏好等 目标语言一串有先后顺序的item标识符在这个框架下你不需要线上维护一个大而全的向量索引不需要纠结“召回商品A和商品B的embedding距离”你把问题完全变成了“给定输入输出哪几个商品ID的概率最高”。我在自己的实验里用过一个简化版验证逻辑拿一个包含20万商品ID的虚拟商品库用商品标题属性拼成商品侧“描述文本”再用用户query作为“源语句”训练了一个小规模的T5模型。上线前我非常担心一个问题商品ID是离散的它的词表长度等于商品总数这种“生成的词表”怎么构建答案是把商品ID拆成n-gram子词单元类似BPE分词的方式把“item_102413”变成“item_1024”和“413”这样的token组合。这样一来词表从20万压缩到几千解码效率也大幅度提升。接着是解码策略。我之前一直以为生成式召回的输出一定是beam search出来的TopN商品ID但实际操作下来发现在交易搜索场景纯beam search不如“约束解码”好用。原因很简单用户query里的结构化约束品牌、品类、价格带必须被满足你可以允许模型自由发挥但不能让它在“鞋”这个约束上犯错。所以线上做的是先生成一组候选ID再用一个轻量级校验层把不符合显式约束的候选过滤掉剩下的再进粗排。这套流程跑下来Top50 Hit Rate的指标比纯向量路高了将近12个点。更让我惊讶的是它对长尾商品召回质量的影响——生成式解码天然会探索更多ID组合而不会像内积检索那样被头部item占住整个概率质量。当时我在自己项目里复现这个思路的时候遇到一个特别典型的反直觉现象生成模型“一本正经”地产生了大量不存在的商品组合。它知道用户想要“AJ1黑脚趾”但这个型号在库里没有42码它依然会把这个不存在的SKU编造出来。后来我加了一整套“生成-校验-兜底”的机制才把幻觉率压下来。这个细节后面单独讲。4. 在线链路改造生成式召回不是替换而是叠加这是我最想强调的工程观点生成式召回不是把向量检索推倒重来而是在原有召回链路上新增的一路召回通道。为什么不做替换三个原因第一生成式模型在线推理成本远高于向量召回。向量召回可以借助ANN索引比如HNSW、IVF做近似检索毫秒级别返回生成式解码是自回归的每一步都要跑一遍模型即使并行beam搜索也扛不住所有query都走这条路。第二生成式召回的强项是“满足显式约束探索长尾长文表达”但纯短词query比如“卫衣”本来就不需要复杂解码。用户搜“卫衣”的时候直接倒排向量走得很准没必要大炮打蚊子。第三交易搜索对召回的质量要求极高召回池里一旦混进“幻觉商品”直接影响GMV容错率极低。所以我看到的合理线上架构是这样的用户query │ ├── 倒排召回精确匹配品牌、品类、型号 ├── 向量召回语义相似处理泛化表达 └── 生成式召回新增解码商品ID 约束校验 │ ▼ merge 去重 粗排我自己的实验里生成式召回路只有一条新链路带着15%的流量跑了两周结果它的召回贡献占比稳定在22%~28%之间。更关键的是它给到的曝光item里有约三分之一是原来倒排向量两条路都完全给不出来的。这说明新增一路生成式召回不是“锦上添花”而是实打实扩容了召回池的“信息边距”。但你真要上这套线上推理成本是必须正面刚的问题。我自己压测过一个参数量在3亿左右的生成模型单条query的beam search解码beam width4输出长度控制在16个token内单卡A100大概要8~15ms。这对比向量召回约2ms确实贵了不少。但注意这路你只需要并行叠加不需要对全量query开启。我建议了一条分流策略只对“长query高约束”流量走生成式召回约占全量搜索的35%~40%其余短query继续走老路。这个分流点上线上整体延迟增加了约3ms但召回池的精度显著提升。这个分流策略的合理性在于长query正是传统向量检索最吃力的部分而它也只占总流量的小头。如果你对全量query都开生成式延迟和成本都会很难看只开这一段性价比极高。延迟层面的另一个优化方向是模型裁剪。我看得物的分享里提到他们在用轻量化的生成模型——不是动不动上几十B的LLM而是几亿到十几亿参数级别的encoder-decoder模型。这个判断我也赞同生成式召回的核心价值是“结构化约束的解码能力”而不是“开放域知识的覆盖度”所以模型规模不需要对标通用大模型够用就行。5. 训练数据这样构造才不会让模型“自说自话”现在聊训练数据。生成式召回这玩意最容易被卡住的就是数据准备阶段——因为它要的是query, item ID这种序列标注式数据不像双塔那样只需要“正样本query, item对”。我在刚起步的时候天真地以为拿搜索点击日志把“曝光后点击”的query, item对整理成正样本就能训练seq2seq模型。结果模型训出来一塌糊涂——它学会了“用户搜什么就生成什么被点击过的商品”但本质上只是在背高点击商品根本没有理解query和商品的语义约束关系。后来我把训练数据拆分成三类问题才被彻底解决数据类型构造方式核心作用点击行为正样本搜索日志里query点击item对用item属性文本拼成“目标描述”让模型学会“这个query对应什么商品”生成式负样本把正样本的item属性改掉如换个价格带、换个颜色构造假商品防止模型死记硬背query与item的一一映射生成模型蒸馏数据用一个大参数LLM把item详情页“翻译”成多种表达风格的query提升长尾query、口语化表达的召回能力第三类数据的价值我之前低估了直到做了一次A/B才发现威力。传统搜索的点击日志里有个死循环商品A被搜到是因为用户能找到它但用户找不到的长尾商品比如“适合小脚女生的AJ1低帮”永远没有记录。模型再训练也只能学已知的对应关系。而蒸馏数据相当于给模型开放了一扇窗让它知道“这个商品还可以被这样搜到”。另外一个实操心得不要把item ID直接当目标token训练要先把商品属性文本拼进去。比如目标序列是item_id 102413 /item_id brand Nike /brand category 板鞋 /category color 黑白 /color price_range 800-1200 /price_range一开始我不理解为什么要这么干——你说目标是生成item ID结果target里还混着商品属性文本这不是引入信息泄漏吗但后来我想明白了让模型在生成item ID之前先“自述”一遍商品的关键属性能强迫它走“理解约束-生成商品”的链路而不是直接跳过理解去查表。这个设计的本质是让模型学会“用推理来生成”而不是“用记忆来复述”。6. 防幻觉电商召回场景容不下“一本正经的编造”我前面埋了个扣说生成模型会给出一堆不存在的商品组合这一步处理不好生成式召回永远上不了线。我遇到过的幻觉类型概括下来有三种型号幻觉用户搜“AJ1 黑脚趾”模型生成“Air Jordan 1 Retro High OG 黑脚趾 42”——这双鞋42码确实没发售过模型把不同年份的款式信息缝合了。属性幻觉用户搜“户外冲锋衣 1000以内”模型给你生成一个“始祖鸟Beta AR 899元”的item——性能参数都对但价格完全不对纯属脑补。库存幻觉商品已经下架、卖光的SKU模型还在那里煞有介事地生成。我在自己项目里搭了一套“生成-校验-终结”的三层防控实践证明极其有效第一层生成侧约束解码。在解码阶段就把商品的可选属性池做成一个mask矩阵。比如生成到“品牌”这一步只能从现有品牌表里挑token生成到“尺码”这一步只能从SKU实际存在的尺码里挑。这一步能挡掉60%左右的幻觉——因为很多幻觉本质上是解码时从开放词表里随便飘出来的。第二层校验层查询。模型生成item ID候选后直接拿这几个候选ID去商品库做精确匹配校验查询确定商品真实存在、符合约束后才进入召回池。这里有个技巧校验粒度用一个轻量级KV Cache把商品ID-属性、库存状态缓存在本地避免每次搜索都打商品中心。第三层兜底降级。如果校验后发现这一路生成的所有候选全部不合法这类情况在冷门商品多时常见不能让这一路“静默失败”要保留原query的生成特征作为信号把权重折半后塞给其他路做query扩展用保证生成结果不会彻底丢失。这三层做完线上幻觉率从最初的3.2%压到了0.3%以下。这个数字才能达到我心理预期的“可上线”标准。7. 实测效果召回率和交易指标的双重提升这段时间的实测我用了一个和得物场景类似的潮玩交易搜索数据集。两组实验一组是纯向量检索基线另一组是“向量生成式”混合召回。召回率Recall50从68.4%提升到81.9%提升13.5个点。其中提升最明显的是“品牌型号尺码”这类高约束query从44.7%直接干到76.3%说明生成式模型对结构化约束的建模能力确实远超双塔。精确率Precision10同样有提升但幅度小一些约5.7个点。原因在于生成式把更多相关长尾商品拉进了召回池粗排阶段还没完全消化这批新流量排序层需要重新适配。搜索转化率这个是老板最关心的指标。混合召回上线一周全站搜索转化率提升2.3%人均浏览商品数提升4.1%。2.3%听起来不大但你要知道这是在搜索这个成熟到不能再成熟的场景里挤出来的增量含金量不低。我还做了一个很有意思的case study。有一个用户搜索“fog联名 长裤 男”向量路召回的全是热门款FOG联名短裤因为它把“长裤”这个约束丢了生成式路召回的则是几款小众但完全匹配的束脚长裤——其中有一款转化了。这种“找回一个真正的用户需求”的瞬间比涨几个点的指标更让我感到这套范式的价值。8. 生成式召回后面还能怎么走多路生成、统一索引和LLM协同文章最后我想给几条基于个人实践的进阶思路不会有模板式总结就是几个后续值得深入尝试的方向。第一多路生成式召回。目前所有生成式模型共用一套query理解逻辑但不同品类的语义结构差异极大。鞋靴用户表达的是“品牌型号”美妆用户表达的是“功效肤质”这种差异用同一个模型处理其实是互相干扰的。合理的方向是分品类训练多个轻量生成器用路由层判断当前query属于哪个域只调用对应生成器。我预研过这个路子单品类生成器的decode准确率能再提5~8个点。第二统一点击率建模。生成式召回目前只是往召回池里投商品还没参与到后续排序。但它的输出天然带“条件概率”这种概率信号如果喂给粗排模型做特征理论上可以提升排序质量——毕竟模型在生成时其实已经做了一轮隐式相关性判断。第三和LLM协同而不是对立。我在前面提到用LLM蒸馏数据这只是第一步。更进一步是在线把一个超大模型作为“生成器教师”把小模型作为“在线生成器学生”教师定期为学生的训练数据做纠偏和扩展。这样既能保留小模型在线推理的时效性又能不断吸收大模型的理解能力。最后说一个数据上的坑提醒后面想复现同样方案的同行评测生成式召回不能只盯搜索日志的“点击率”来衡量召回质量。因为点击率是“用户能看到什么”和“用户愿意点哪个”的混合结果如果生成式召回给的候选整体偏冷门点击率可能反而下降但实际的“需求满足率”是上升的。我在评估时改用了一个组合指标“目标商品曝光率 交易转化率 召回池多样性”这三个指标合起来看才不会被单个数字误导。做技术的人有个通病碰到瓶颈就拼命在现有框架里加复杂度——调loss、加负样本、堆模型层数。但有时候问题的解法不在框架内部而在“换一个建模对象”。向量检索把用户query建模成向量生成式模型把用户query建模成“一个条件概率分布”后者天然能容纳结构化约束也天然能探索开放集合。这个切换不是微调是换个赛道重新起跑。至少在我的实测里这条路子确实把召回率这个硬指标往上顶了一大截。
返回列表