☰
生成式召回:突破向量检索瓶颈的交易搜索新范式
2026/10/2 15:31:03 网站建设 项目流程

最近一年,身边做搜索召回的朋友聊起来,话题绕来绕去还是那几个:向量检索、多路召回、ANN索引调参、双塔蒸馏。老实说,这个方向已经被卷到很细了——双塔结构大家都会,HNSW参数也都摸过一遍,多路召回拼来拼去无非是你涨零点几个点、我涨零点几个点。但在得物这类交易搜索场景里,我最近一直在折腾一个完全不同的做法:把召回从“检索匹配”换成“条件生成”,也就是业界讨论越来越多的生成式召回。这篇文章不聊PPT,只讲我踩过的路、想明白的道理、上过线的方案,以及翻过的车,给同样在纠结“召回还能往哪卷”的同学一个参考。

1. 向量检索卷到今天,真正的瓶颈在哪里

1.1 双塔召回的天花板:被内积约束和ANN钉死的结构

先聊一个基本盘。现在绝大多数交易搜索的召回主力还是向量检索,主流方案是双塔模型:用户侧一个塔,商品侧一个塔,各自把特征压成一个固定维度的向量,然后用内积或者余弦相似度衡量相关性,线上靠ANN(近似最近邻)索引做快速检索。

这套方案能成为行业标准,当然有它的道理:离线侧好训练,user embedding和item embedding解耦之后可以预计算、落库、建立索引,在线侧一次查询就是一次矩阵计算加近邻搜索,延迟能控制在个位数毫秒。但问题也恰恰出在这个“固定维度”和“解耦”上。

我给一个比较直观的类比。双塔模型就像是把人和商品各自拍成一张身份证照片,然后比照片像不像。照片是定死的,维度不能太高——内积检索要求embedding维度不能太大,否则在线计算量和存储成本都扛不住。于是模型被迫把所有信息压缩到128维甚至更低,标题信息、类目信息、价格带、风格、款式细节全部挤在一个小空间里。压缩越狠,表达能力损失越大,这是第一个结构性矛盾。

第二个结构性矛盾是交互时机。双塔结构里,user塔和item塔只在最后的点积处发生交互,之前是完全独立的。模型没法在做召回之前就充分理解“这个用户现在说这句话到底想要什么”。精排模型可以用交叉特征,因为量小可以上复杂结构;召回不行,召回面对的是全量商品,必须用解耦结构先圈定候选集。这套思路本身没错,但它天然决定了召回侧的表达上限。

第三个问题在ANN。即使是近似近邻搜索,线上能拿回来的也只是有限topK。你会发现向量召回更擅长找“和一个已知好东西相似的另一个好东西”,这个特性决定了它在做相关性召回、相似款召回时确实很强。但如果用户的真实需求不是“相似”,而是“组合”“搭配”“按口语化约束筛选”,向量召回就会显得笨拙,因为它本质上在问“离谁近”,而不是在问“用户要什么”。

1.2 用户意图一稀疏,匹配式召回就暴露短板

交易搜索和普通网页搜索最大的区别在于:搜索词里充满了强意图信息。用户不是来“了解”的,是来“买”的。而且得物这类平台有个特点,用户的搜索表达非常口语化、非常组合化。比如“夏天穿的黑色的AJ1低帮”“倒钩”“情侣款鸳鸯配色”“预算一千以内的实战篮球鞋”,这些query如果拆成关键词走向量召回,效果很容易拉胯。

为什么?因为向量检索本质是在做语义相似匹配,它学到的距离关系是基于语料里“文本共现”和“行为共现”的。一个组合型query,在历史数据里可能根本没有完整出现过,向量检索只能把它拆碎之后分别去找片段相似的item。这还不是最麻烦的,最麻烦的是:交易场景里用户要的东西,往往是一个组合——品牌、系列、配色、功能、价格带、预计成交区间,这些维度组合起来才是一个真正可成交的候选集合。

这个场景下,传统的向量检索会把重点放在文本语义迁移上,忽略用户在购买路径里真正起作用的行为信号。比如一个用户反复看了三双球鞋,又搜了“实战”“耐磨”,他真正想要的可能是那一类“半场实战鞋”,而不是单纯语义上最接近query的商品。向量检索对这类“行为+语言混合驱动”的表达,处理起来很蹩脚。

我观察到很多团队卷向量检索,卷到最后其实是在卷瓶颈之外的东西。换损失函数、换负样本策略、换数据增强、换模型结构,指标确实在涨,但涨的是“相似匹配能力”本身的天花板,而不是“召回能力”和“交易转化能力”的天花板。

1.3 交易搜索的特殊性:得物这类平台为什么更别扭

得物这个池子,和传统电商相比有几个明显差异,这些差异会让纯粹卷向量检索的战略越来越不值:

  • 非标品占比极高。球鞋、潮玩、配饰,很多商品不是标准化SKU,而是“系列+配色+版本+尺码+二级市场行情”的组合。同一个鞋型,不同配色的价格逻辑和市场热度差很多,向量模型很难区分这些细微但决定性的属性差异。
  • 长尾新词来得快。新款发布、联名、网络梗、突然爆火的配色名,这些词在语料库里几乎是全新出现。向量检索对于没有历史embedding的短语表达,只能靠词向量组合猜测,往往是滞后的。
  • 强属性约束比强语义相似更重要。用户在交易搜索里经常带尺码、价格带、版本(比如“中签款”和“普通款”)、新旧程度等硬性约束,这些约束性信息不是“语义相似度”能解决的,它更像一个带条件的生成过程。

这就是为什么我在这个场景里越来越觉得:召回问题的关键,不是把“相似”做得更好,而是把“用户意图到商品集合”的映射做得更直接。传统范式的路,已经走到一个“指标在涨、交易价值不涨”的尴尬位置了。

2. 生成式召回:不是LLM问答,是一次“召回定义”的替换

2.1 从“比对”到“生成”的范式切换

先明确一件事:生成式召回不是让GPT去给你编一个商品出来,也不是把用户query丢给大模型让它返回一段带商品链接的对话。这里说的生成式召回,是把“召回”这个任务重新定义为“给定用户状态和query,直接解码生成一系列商品标识(item ID)”,模型用生成的方式逐个产出候选商品ID,而不是在索引里做近邻搜索。

本质上,这是在把召回从一个“判别式+检索”问题,变成一个“条件生成”问题。传统检索,我们说“把用户和商品映射到同一个向量空间,找最近的”;生成式召回,我们说“把用户的历史行为序列、当前query作为条件,让模型自己学会输出一个商品ID序列作为预测结果”。

这个想法最早在Google的TIGER论文里给了我很大的启发。他们用seq2seq模型做推荐,输入是用户交互过的商品ID序列,输出是下一个要交互的商品ID。在这种设定下,模型不再依赖显式的向量距离,而是学会了“用户序列→下一个商品”这个分布的直接建模。用户的整个行为历史通过自注意力的方式被编码,解码器直接在商品ID词汇表上做生成。

我当时看到这个设计的第一反应是:这不就是给召回装了一个“意图压缩器”吗?传统向量检索必须在有限维度里把意图压成一个点,而生成模型的表达能力要强得多。它不需要把意图压缩到低维去比对,而是可以在解码过程中逐步细化——每一步生成都在做一次条件筛选,相当于模型自己内部做了一个“从粗到细”的迭代检索。这个能力,对于交易搜索里那种多维约束组合的query,天然匹配。

2.2 给商品造一门语言:商品ID的tokenization怎么设计

要做生成式召回,第一个绕不开的问题就是:商品ID用什么表示?你不能直接把几百万个商品的原始ID丢给解码器去softmax,那样词表有几百万,计算量直接爆炸,而且纯随机ID之间没有任何结构关系,模型学起来非常痛苦。

我最后采用的是层次化语义ID方案。把每个商品ID拆成长度固定的token序列,每个token代表一个语义维度。以得物这类潮流交易平台为例,一个商品ID可以被拆成:

  • 一级token:大类目,比如球鞋/服饰/数码/潮玩,词表大概几十到一两百
  • 二级token:子类目或品牌聚合,比如运动鞋、篮球鞋、板鞋,或者是“Nike”“Adidas”这样的品牌维度
  • 三级token:风格/系列/功能标签,比如“实战”“休闲”“联名”“复刻”“透气”
  • 四级token:颜色、材质、价格带等细粒度约束
  • 末位token:具体商品ID在细粒度分组内的编号

这样每个商品都对应一个长度固定的token序列,比如[球鞋, 篮球鞋, 实战, 黑色系, 300-599元档, item#12345]。解码器从头开始逐步生成这个序列,本质上就是一步一步做条件约束,从类目约束到品牌约束到价格带约束,最后定位到具体商品。

这个设计背后是有逻辑的。词表大小取决于每一级的取值数量,每一级都限制在可管理水平,最后一级虽然大,但它是在前几级已经约束过的局部空间里做选择,可以用打分或小范围检索兜底,不需要在几百万候选里做全量softmax。这种“层级约束+局部生成”的结构,既控制了计算成本,又让模型学到的生成路径带有语义解释性。

2.3 模型架构和训练目标的取舍

我们用的主体结构还是编解码Transformer,但不追求大模型,重点在可控、可上线的条件下把“生成”这件事跑稳。Encoder端输入两类信息:一是用户近期的行为序列,按时间排序的点击/加购/购买商品ID token序列;二是当前搜索query的token序列,以及一些基础的上下文特征。两者拼在一起过Encoder做自注意力。

Decoder端负责生成候选商品ID序列。训练目标就是标准的自回归负对数似然,给定真实目标商品ID序列,最大化每一步的生成概率。这个目标非常直接,不需要额外设计复杂的对比学习Loss,模型自然就学会了条件生成。

但在实操中我们还额外加了一个非常轻量的对齐Loss:把Decoder最终输出的候选商品表示,和一个现成的向量召回通道的item embedding做一次点积约束。这个约束不是为了替代生成,而是为了让生成式召回产出的候选集合和已有的向量召回体系保持一个“可对话”的关系,方便后续粗排、精排统一切换特征,也方便回退兜底。

架构上不需要想象得太宏大。我们的实际配置是Encoder 6层、Decoder 3层、hidden size 128左右,参数量不到1亿,单卡能训。这个体量在交易场景已经够用了,你要真上个几十B的模型,在线推理那关根本过不了。

训练数据我用的是“搜索session行为日志”,关键是构造“用户行为序列+query→目标商品”的三元组。目标商品取用户在搜索后实际点击、加购、购买的商品,过滤掉纯曝光无效样本。负样本不用挖太狠,普通的随机采样加batch内负例就够了,因为这个任务本质是生成任务,模型犯错的主要来源是生成路径错了,而不是判别边界模糊。

3. 交易搜索场景落地:从训练到在线serving的完整链路

3.1 样本与特征:用户行为序列怎么变成模型输入

落地阶段第一个要钉死的细节是特征入口的规范化。生成式召回对行为序列的依赖,比向量召回要重得多,因为它没有把item embedding提前算好的机会——它必须在解码的当下同时消化行为序列里的商品信息。所以样本构造的质量直接决定模型性能上限。

一条训练样本长这样:[行为序列token] + [query token] → [目标商品ID token序列]。行为序列里的每个商品先通过同样的ID tokenizer变成token序列,然后按时间顺序排成一个长序列。太长的序列会拖慢训练速度,我们截断在最近30个交互行为,但对最近几天的强行为做了加权重复采样,确保近期意图在序列里被充分表达。

特征里必须包含的行为类型有:曝光点击、加购、收藏、下单、搜索后点击。不同类型行为不能一视同仁地拼进去,否则模型会把“看过”和“买过”混为一谈。我们的做法是给行为类型追加一个特殊的token前缀,比如[CLK]、[CART]、[BUY],让模型在学习过程中自己区分行为强度。

Query侧的处理比较常规,分词后拼接查词表即可。但有一个场景细节值得一提:交易场景里query中的数字很重要,尺码、价格、年份(“19年款”)都直接影响候选范围。分词时要把这些数字片段单独保留成token,不要和文本混在一起,否则模型学不到数字约束的语义。

3.2 在线解码:延迟、beam和候选规模的三角平衡

模型训好之后,最大的挑战不在离线和训练,而在在线serving。向量检索的延迟大概在几毫秒,生成式召回如果直接照搬beam search,延迟会翻几十倍,线上显然不能接受。

我们花了不少时间调这个三角平衡:候选数量、beam宽度、解码长度。

  • beam宽度不能太大。beam=4是一个比较稳的取值,再往上延迟增长太快,收益却不大。beam=8实测下来比beam=4只多提升不到2%的召回覆盖率,但延迟涨了将近一倍。
  • 解码长度严格限制。商品ID token序列长度是固定的,比如6个token,那Decoder最多也就解码6步。但这里有个小技巧:提前终止条件不能只看<eos>,因为很多ID序列并没有显式结束符。我们额外加了一条规则——当解码分数的累计增益连续两步低于阈值时提前截断,把已生成的token直接映射回对应层级的候选集合。
  • 候选规模和下游粗排的容量要匹配。生成式召回一次解码产出100~300个候选是比较合理的范围,太多了粗排扛不住,太少了生成式覆盖长尾的潜力发挥不出来。

在线延迟的量化目标我们定的是P99不超过20ms,解码部分跑在GPU上,用Triton Inference Server做批处理。真实线上压测下来,单请求解码耗时一般在6~12ms,算上特征拼接和ID映射,总延迟仍在可接受范围内。作为对比,这个延迟比单路向量检索贵了大概5倍,但比多路向量召回里再加一个重排模型要便宜得多——关键在于省掉了“向量召回全量库”的构建和维护成本,这些钱省下来买GPU,账是算得平的。

3.3 与现有检索链路怎么衔接

这里我建议所有想做的团队都明确一个原则:生成式召回不要作为替换项,而是作为新增通道,和现有向量召回并行。为什么?因为生成式召回的优势在长尾和组合约束,而向量检索在头部相似召回方面依然有不可替代的稳定性和效率,两者互补而不是互斥。

我们的线上架构是:用户query进来后,同时走向量召回、类目召回、热榜兜底和生成式召回。生成式通道产出的候选集合,和其他通道做加权融合,然后统一进入粗排层。融合权重的初始预估值不要拍脑袋,可以先给生成式通道一个较低权重,比如0.3,然后按A/B实验逐步提升,同时监控粗排后各通道的留存率。

一个非常关键的工程细节是:生成式召回产出的候选ID,必须确认在商品状态表里是“在售”的。因为Generator训练数据来自历史行为日志,里面天然混入了已下架、已售罄的坑品。生成出来的商品ID如果不过滤直接进粗排,会产生很高比例的无效曝光。我们在生成通道后面加了一层实时商品状态哈希表过滤,这一步让候选集合的有效率从80%出头的水平直接拉到了97%以上,这个坑我后面会细说。

4. 上线实测:指标涨跌背后的真相

4.1 评测体系先搭起来,否则后面全是糊涂账

生成式召回上线之前,评测体系如果还按老一套向量检索的指标来,会被数据骗得很惨。向量召回看Recall@N,生成式召回看这个指标当然没问题,但只靠这个会掩盖两个关键问题:它生成的结果是不是真正覆盖了长尾,它带来的交易增量是不是真实增量。

我重新搭了一套离线评测,核心指标分三层:

  • 召回率层:Recall@50、Recall@100。这里Recall的定义是“测试集里用户下一时刻真实交互的商品,是否出现在生成式召回和向量召回融合后的候选集合里”。这一层保证的是基本盘不能丢。
  • 覆盖层:长尾覆盖率、候选集合的信息熵。信息熵指数如果下降,说明模型输出高度集中在少数爆款上,这是生成式召回最典型的翻车方式。
  • 交易价值层:无法离线准确估,所以必须靠在线实验回传,观察GMV、UV价值、成交转化率这类交易指标。

离线阶段我们把这三层指标分开看,任何一层不达标都不放量。很多团队只盯着Recall@N看,结果上线一看长尾覆盖率掉了10个点,PV_CTR和GMV反而降了,就是这个原因。

4.2 长尾覆盖和交易转化各自的表现

能放量之前,先把一组相对客观的离线/在线数据给大家一个参考维度。我们不追求跨场景复现,但趋势是明确的:生成式召回在长尾覆盖上显著优于向量检索,在头部召回上则明显偏弱。

拿“新款和冷门色彩”这个维度举例,向量检索通道产出的候选集中,新品或非热门配色占比通常很低,因为训练数据中这些商品行为稀疏,embedding语义不充分;而生成式召回因为解码路径是“类目→品牌→系列→配色→价格带”逐层约束的,即使某个商品本身行为稀疏,只要它的属性组合是清晰的,模型依然能够生成到它。我们离线测试中,长尾覆盖率相对提升了20%以上,这部分增量非常扎实。

在线A/B测出来的情况是:纯看点击率,生成式召回并没有显著优势,甚至略低一点。但看加购率、下单转化和搜索成交金额,生成通道的流量有明显正向拉升,尤其针对那些“搜索词里带硬属性约束”“行为序列里有连续比较轨迹”的session,成交提升幅度可达10%~20%。我的理解是,生成式召回产出的结果更贴近用户“当前的交易意图”,而不是“视觉上相似的东西”,这部分意图到交易的转化管道,是向量检索一直没打通的地方。

4.3 我踩过的三个典型坑

绕不开的还是要讲坑,这次上线前后我踩的坑不少,挑三个最有代表性的说。

第一个坑就是上面提到的在售状态过滤。刚开始我信心满满地把生成通道的候选直接丢进粗排,结果线上很快就观察到大量无效曝光——用户搜“实战篮球鞋”,生成出来的候选里混了好几个两周前已经售罄的联名款,点击率自然稀烂。原因不复杂:训练数据里的行为样本很多发生在商品下架前,模型学到的是“这个商品在这个序列里被需要”,但商品当前的售卖状态完全不在模型感知范围内。解决方案就是在生成通道后加实时状态过滤,这是所有想做生成式召回的人第一件要做的事。

第二个坑是分布倾斜,也就是popularity bias。生成模型天然倾向于生成高频商品ID,因为在训练数据里高频商品出现的次数多,模型只需要偷懒地记住“这个ID经常被生成”,就能把loss降到很低。你会发现离线指标Recall@100很好看,但实际一查生成的候选,里面一半都是一模一样的十几二十个爆款。解法方面,我们在解码时加了温度采样和重复惩罚,同时给训练loss加了一个KL正则,拉近生成分布和目标商品分布的距离,这个组合基本能把倾斜控制住。

第三个坑和基础设施有关,说出来有点丢人但很常见——商品ID mapping表的一致性。离线训练时用的商品ID字典,和线上服务时遵循的商品状态哈希表,版本必须保持一致。我们有几次更新了商品库,结果线上生成出的ID映射到了完全错误的商品,或者直接映射不到。这个问题的排查极其痛苦,因为错误是间歇性的,只在特定商品出现。最后靠给映射表加版本号、上线时自动校验ID字典哈希才彻底解决。我建议所有工程团队,在搞生成式召回的第一天就把ID字典版本管理这套机制建起来,别等出问题了再补。

5. 范式跃迁的真实边界:什么场景适合,什么场景别硬上

5.1 生成式召回能打的条件

从我这段时间的折腾经验来看,生成式召回能真正发挥价值的场景,通常同时具备以下特征:

  • 用户行为序列密度够高。生成式召回吃序列建模,用户如果没有足够多的历史交互信号,Encoder端几乎是空转的。新用户、低频用户占比较高的场景,生成式召回的优势会明显被稀释。
  • 商品属性维度复杂、结构性强。像球鞋、数码、美妆这种有明确系列、质地、功效、价格带强约束的商品,层次化商品ID能发挥出非常强的约束生成能力。反过来,商品属性维度极弱的纯标品场景,生成式召回能带来的增量会小很多。
  • 用户query口语化、组合化、长尾词多。这是和向量检索拉开差距最关键的条件。如果你们平台的query普遍短、结构单一、意图明确,向量检索其实已经够用,没有“跃迁”的必要。

我们这次在得物交易搜索里能跑出正向结果,核心助推力就在于这三点天然满足:用户搜索词长尾且带有强组合意图,商品属性结构化程度高,用户通常有连续浏览和比较的行为路径。

5.2 哪些情况别跟风

反过来,有几个情况我建议别硬上生成式召回。

首先是用户行为太稀疏的场景。一个新用户进来只做了一次浏览,没有任何历史序列,Encoder里几乎是空的,生成式召回和盲猜没什么区别。这种情况宁可保留规则召回或者向量召回,至少后者能靠query侧语义先把球捞回来。

其次是商品ID体系本身就很混乱的场景。如果你们平台的商品ID没有稳定的属性层级,或者类目体系经常调整,那构建层次化ID tokenizer的成本会非常高,而且模型每次类目调整都要重训。这种场景里,向量检索那种“不用管ID长什么样、只要embedding稳定”的优势就体现出来了。

最后是团队没有GPU serving能力的场景。生成式召回在线解码对推理性能有硬要求,如果你们基础设施只有CPU、GPU资源匮乏,延迟目标根本达不到,那这个范式在你们现阶段就是“理论正确、现实骨感”。我理解范式跃迁听着很性感,但工程现实永远是第一约束。

5.3 如果要上,我的建议节奏

最后分享一下我实际执行时采用的节奏,供参考。

第一阶段,不要动主链路。用离线日志把生成式召回模型训出来,先离线评测三层指标,重点是长尾覆盖率。如果长尾没有明显优势,生成式召回就不用做下去了,直接止损。

第二阶段,作为补充通道接入。线上权重压低,和向量召回、类目召回并行融合。在这个阶段把在线延迟、ID映射一致性、在售状态过滤这些工程细节全部磨平。

第三阶段,A/B实验逐步放量。不要一次性灰度全量,而是先切5%流量观察交易指标,确认成交转化有正向或至少不跌,再逐步放大。同时建立一个监控面板,专门盯生成通道候选集合的信息熵和长尾覆盖率,防止模型悄悄退化成爆款生成器。

整个过程中我最深的体会是:范式跃迁这个词听着高大上,但真正落地全靠一层一层的工程细节和扎实的评测体系托底。生成式召回确实换掉了“比对检索”的旧底座,但它不会魔法般地解决所有问题。它更适合作为一个“突破长尾和组合约束瓶颈的新能力”接入现有系统,而不是一个一言不合就替换全链路的银弹。如果你正在纠结自己的召回是不是也该换路线,我建议你先问自己一个问题:你的用户意图是“找相似的”还是“按条件买对的”?如果是后者,生成式召回是一个值得认真试试的方向。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询