做搜索这两年,大家都有个很直观的感受:向量检索从一件加分项慢慢变成了基础设施,谁家还没上向量召回都觉得落伍了。但如果你真的做过交易搜索,又会发现一件事——向量检索上线之后,搜索效果确实涨了一波,可越往后越难挤出水来,尤其面对那些“多条件 + 强约束 + 高时效”的交易类query,向量方案总有种使不上劲的感觉。
拿得物这类偏潮流交易平台来举例。用户搜“黑白熊猫 42码”,背后是一个真实可买的球鞋SKU,需求里同时带着款式、配色、尺码三个硬条件,任何一个不满足,用户都不会下单。传统倒排可以精确匹配文本,但语义泛化弱;向量检索能拉回语义相近的商品,可面对这种多条件组合查询,经常是“泛化过头”——颜色对了尺码不对,款式对了价格不对。这也是为什么“别再只卷向量检索了”这个说法在交易搜索圈子里越来越有共鸣:召回范式可能需要一次真正的跃迁,而关键词是“生成式”。
这篇内容,我会围绕得物这类交易搜索场景,拆解为什么向量检索不是终点,生成式召回到底解决什么问题、整体怎么设计、关键环节怎么落地、会踩哪些坑。适合搜索/推荐/NLP方向的工程师、算法负责人,以及所有对召回架构演进感兴趣的读者。
1. 为什么说“别再只卷向量检索了”
1.1 交易搜索和通用搜索的召回诉求根本不一样
通用搜索比如网页搜索,本质上是在海量文档里找“最相关的一段信息”,文档之间没有库存、价格、尺寸、新品时效这些交易约束。用户搜“什么是量子计算”,返回十篇相关文章,点哪篇都行。
交易搜索完全不同。用户搜“AJ1 芝加哥 42”,背后是一个“可购买商品”,而不是一段信息。召回结果里如果混入“46码”,或混入“配色相似但不在售的老款”,哪怕语义上再接近,点击都会很勉强,成交率更低。这类query在交易场景中非常密集,尤其是球鞋、潮牌、服饰这类强属性商品,尺码、配色、货号、版本都是用户显式或隐式的硬需求。
所以交易搜索的召回,本质上是一个“多条件约束下的候选生成问题”:给定query表达出的意图,结合用户的实时行为和平台侧的库存/价格/在售状态,找到一组“可以买且用户会买”的商品。这个问题的复杂度,远超单纯做“query和doc的语义相似度匹配”。这也是为什么老牌向量检索方案做到后面,会出现明显的天花板。
1.2 向量检索在交易搜索里的三个天花板
第一个天花板是多条件组合失焦。向量检索的核心逻辑是把query和doc分别编码成稠密向量,再计算相似度。但交易场景里用户表达往往是组合式的,“黑色 42码 通勤跑鞋”这个query,理想结果应该同时满足“黑色”“42码”“通勤跑鞋”三个条件。实际做向量召回时,要么把整句话直接编码,让模型自己隐式消化;要么分词后多向量融合。实践下来,无论是哪种方式,模型都很容易“顾此失彼”,由于训练数据里高频商品占比大,模型对“通勤跑鞋”这类语义核心记得牢,对“黑色”“42码”这种属性条件经常一带而过。
第二个天花板是马太效应和长尾抑制。ANN检索返回的TopK天然偏向与query相似度最高的向量,而相似度又容易集中在热门商品上——因为热门商品有更多的曝光、点击、成交样本,Embedding训练得更加充分。结果就是:向量召回给出来的候选越来越像“全网爆款”,而不是“这个用户此刻想买的东西”。长尾商品、新品因为训练样本稀疏,向量表征质量不稳定,很难进入TopK,形成典型的头部聚集、长尾失声。
第三个天花板是相关性校准困难。向量距离近,不代表“可成交”。用户搜“白色T恤”,向量召回返回了相近风格的白色卫衣,语义上没毛病,但用户就是不会买。要校准这种交易相关性,往往需要依赖精排模型去兜底。但如果召回环节就没有把真正满足条件的长尾商品带回来,精排再强也没用——天花板在召回那里就封死了。
除此之外,还有索引维护成本的问题。商品向量索引需要随上新、下架、价格变动反复更新,全量构建和增量更新的工程成本都不小。而交易平台的商品生命周期又短,新款频繁上线,陈旧商品需要快速下线,这对向量索引的实时性要求很高。不是不能做,但综合下来,召回侧的投入产出比越来越低。
1.3 生成式召回:把“检索”变成“生成”的范式跃迁
所谓生成式召回,简单说就是:不再是“在大规模的候选池里搜索匹配项”,而是训练一个生成模型,直接根据query和用户行为序列,一步一步地“生成”出一组候选商品的ID序列。
这个思路对应的其实是业界已经出现的生成式推荐和生成式检索方向,代表工作包括TIGER、VQ-Rec这类用Transformer直接生成Item ID候选序列的模型。在推荐场景,模型根据用户历史行为序列生成下一批要推荐的Item ID;在交易搜索场景,输入侧多加一个query,模型基于“用户想买什么”和“用户平时偏好什么”这两个信号,生成一组满足条件、且排序合理的候选商品ID。
为什么说这是范式跃迁?因为问题建模方式变了。检索是在一个大集合里做“大海捞针”,生成是模型自己理解“用户需要什么样的针”,然后“把针造出来”。生成式召回可以天然地把价格、类目、尺码、库存这些约束条件注入解码过程,让候选集合从第一步就是“可交易、可购买、符合综合条件”的。
当然要强调一点:生成式召回不是取代向量检索,而是在现有召回体系上新增一路候选,甚至反向弥补向量检索的短板。得物这类平台的实操路径,更多是“生成式 + 向量 + 倒排”多种召回手段并行,进粗排后统一融合。真正跃迁的是“召回能力边界”,不是“替换掉既有系统”。
2. 交易搜索生成式召回的整体设计思路拆解
2.1 三个核心模块:语义商品ID、条件生成模型、约束解码
生成式召回要落地,绕不开三个模块:怎么把商品表示成模型可以“生成”的符号;用什么模型结构去生成候选;生成过程中怎么施加交易约束。这三个模块逐一拆开讲。
第一个是商品ID化。模型要生成“候选商品”,但商品ID本身是离散的、稀疏的、没有任何语义关系的序号,直接让模型生成“商品原始ID”是学不动的。就像让你写作文时每个字用随机乱码代替,模型根本找不到规律。所以必须先把商品映射成一种有语义结构的离散ID。业界主流做法是用残差量化向量(RQ-VAE)把商品的表征压缩成多级离散编码,每个商品最终对应一个由几个整数组成的语义ID序列。这一步非常关键,它决定了生成模型能不能学到“类目相近、风格相近、属性相近的商品ID也相近”这一规律。
第二个是条件生成模型。输入侧是query的token序列,加上用户近期的行为商品ID序列;输出侧是候选商品ID序列。可以理解为训练一个Decoder-only的Transformer,用标准自回归方式预测下一个“商品语义ID”。训练时用搜索日志里的点击、成交样本作为目标序列,推理时用Beam Search生成TopK候选。
第三个是约束解码。这是交易搜索区别于推荐场景的重点。生成模型在训练时不在乎商品是否还有库存、价格是否符合用户预期、尺码是否匹配,所以推理时必须把这类约束加进去。约束解码可以做到前缀裁剪、白名单过滤、条件重排等多级控制,让生成结果从第一刻起就落在“可交易”的集合里。
2.2 为什么商品必须“语义ID化”?RQ-VAE怎么用
先回答一个问题:为什么不直接生成原始商品ID,比如一次性输出“123456”就完事了?原因很简单,原始商品ID是随机分配的编号,相邻编号的商品毫无关系。模型在生成ID时没有任何可归纳的规律,等于是硬背复制粘贴,泛化能力为零,新商品冷启动更是无从谈起。
那商品ID怎么做到“有语义”?这里说的语义不是文本概念,而是“商品表征空间里的语义”。拿得物这种平台举例,一件商品可以用标题、类目、品牌、图片特征、行为特征共同表达。我们把商品多模态特征编码成一个向量,然后用残差量化把它离散成几个code。
RQ-VAE的思路是:第一层量化器去拟合整个商品向量的分布,得到一个code,然后算残差;第二层量化器去拟合第一层剩下的残差,再得到一个code;以此类推,每一层都在弥补上一层的损失。最后每个商品表示为“第1层code + 第2层code + 第3层code + 第4层code”这样的整数序列。这里有个重要的性质:前缀相同的商品,在高维表征空间里会更接近。也就是说,两件鞋类商品可能有着同样的第1、2层code,只是在更低层细分上去区分具体款和配色。这种性质让生成模型有机会学到“由粗到细”的生成规则,先预测大类,再预测具体属性,完全符合人类购物时的决策逻辑。
实操中也有两种量化路线。一种是用现成的预训练商品表征直接做量化,训练快速但表征不是端到端的;另一种是把量化器和商品编码器联合训练,效果更好但工程复杂度更高。我建议起步阶段先用前者,验证生成式召回能带来线上收益后,再考虑升级到联合训练。
2.3 生成模型的结构选择:为什么Decoder-only更顺手
生成式召回的主流结构其实经历过一轮选择。最初的生成式推荐模型往往采用Encoder-Decoder结构,Encoder编码用户历史,Decoder生成Item ID。但在交易搜索场景里,输入既有query又有用户行为序列,而且两者的信息不是简单的“编码一遍”就结束的,Encoder-Decoder往往信息交互不够充分。
我更推荐Decoder-only结构。把query的token序列、用户行为序列中的商品语义ID,全部拼接成一个长序列,让模型在这条序列上做自回归训练。目标不是预测下一个token本身,而是预测下一个“商品语义ID”。这样做的优势有几个:
第一,query和用户行为之间是“平等”的序列关系,模型可以自由建模两者之间的交叉注意力,不受Encoder-Decoder中间瓶颈限制。第二,线上推理时复用同一套KV Cache,解码效率高。第三,训练和推理目标一致,都是next-item prediction,避免“训练-推理不一致”的经典问题。
举个输入输出的例子。用户query是“黑白熊猫 42码”,行为序列里有三个商品的语义ID,那么训练时输入可以组织为:
# 伪代码,仅展示样本组织逻辑 query_tokens = tokenizer.tokenize("黑白熊猫 42码") behavior_ids = [ item_encoder.item_to_semantic_id(clk_item_1), # e.g., [231, 511, 9, 342] item_encoder.item_to_semantic_id(clk_item_2), # e.g., [231, 511, 11, 88] item_encoder.item_to_semantic_id(clk_item_3), # e.g., [230, 499, 7, 12] ] target_ids = item_encoder.item_to_semantic_id(order_item) # e.g., [231, 510, 12, 77]模型读入整段序列后,去预测target_ids这4个code。训练时每个code都用交叉熵损失,真实做法是对所有code逐位预测。推理时,模型输出候选的语义ID序列,再映射回商品ID。
3. 关键环节落地:从数据到推理的完整链路
3.1 样本构造与特征组织
数据是生成式召回的地基。实操层面,一份训练样本由三部分构成:搜索上下文、用户行为序列、目标商品。
搜索上下文主要是query,也可以带上应用端场景(比如来自首页搜索还是频道页搜索)、用户画像粗粒度特征。用户行为序列取最近N次点击或成交的商品,把每个商品转成语义ID后拼接。目标商品是用户在搜索后点击或下单的商品,同样转成语义ID序列。
正样本怎么取?曝光点击、曝光成交都是典型正样本,按用户反馈强度可以给不同权重,成交样本的权重应当更高。负样本怎么构造?常见做法是曝光未点击、随机负采样、以及batch内随机负例的组合。这里有个非常关键的细节:生成式召回模型是学习“用户接下来会选什么”,负样本不能只是随便采样,否则模型学到的不是交易意图而是全局商品流行度。
我踩过的坑是刚开始用纯随机负采样,模型生成的结果全是热门商品,和向量召回高度重合,新通路完全没有增量。后来改成“随机负采样 + 曝光未点击 + 同query下其他用户成交但当前用户未点击的商品”,三路负样本混合,总算把模型从“复读机”里拉了出来。
3.2 语义ID训练的实操细节
训练语义ID这步,数据准备上要把商品的全部文本、图片、类目信息都汇总起来。文本特征可以来自标题、品牌、类目路径,图片特征用预训练视觉模型抽取embedding。然后一起送进RQ-VAE。
量化器配置方面给一个可参考的起点:codebook数量取4096,层数取4。也就是说每个商品最后生成4个code,每个code的取值范围是0到4095。这个配置的好处是候选空间够大,又不会大到模型学不动。如果商品总数特别大,可以适当加层数或加大codebook,但层数过多会带来解码时延问题,不建议一上来就搞6层8层。
损失函数主要有三块:重建损失,让解码后的向量还原原始商品表征;commitment loss,避免编码器输出飘离量化中心;perplexity控制,防止codebook中大量code没有被使用而过早坍缩。实际训练时我会把重建损失放第一位权重,commitment loss用一个小权重,perplexity通过正则项控制。如果发现codebook利用率很低,很多code从不被激活,这是量化器坍缩的信号,需要调小学习率或重置死掉的code。
训练完成后要做一项检查:随机抽一些商品,看它们的语义ID是否具备“同类商品前缀接近、异类商品前缀差远”的性质。这一步不过关,后面生成模型怎么训练都是白搭。
3.3 生成模型的训练细节与参数参考
生成模型本身的配置相比常规语言模型轻很多。参数量一般不需要上亿级别,几千万参数在工业界已经能跑出不错效果。下面给一组可供参考的起始配置:
- 模型结构:Decoder-only Transformer
- 层数:6到12层
- 隐层维度:512到768
- 注意力头:8到12
- 最大序列长度:建议把query和用户行为序列的总长度控制在512以内,decode目标商品ID长度设为4,也就是只生成4个code
训练时采用teacher forcing,每个code位置都在前文条件下计算交叉熵。为了让模型在生成阶段更“理直气壮”,我习惯在损失里再加入轻微的对比学习分量:让模型对同一query下正样本商品ID的预测概率显著高于负样本商品ID。
训练完成后,用验证集先测一下生成质量。一个简单有效的检查方式是:给定一条query,看看模型生成出的候选商品ID集合里,会不会出现“类目跑偏”的情况。比如搜“运动鞋”,模型生成一堆“服饰”,说明模型压根没学会query和商品ID之间的对齐,大概率是数据或训练配置出了问题,不用急着上线。
3.4 推理阶段的约束注入与线上融合
推理阶段是生成式召回最考验工程的地方。流程大概是:
- 输入query和用户行为序列后,用Beam Search生成候选语义ID序列。
- 解码过程中做前缀限制:比如商品第一层code已经能区分大类,可以直接限制模型只能生成“允许的品类前缀”,否则剪枝。
- 生成完成后,把语义ID映射回真实商品ID。
- 对候选集合做硬过滤:下架商品过滤、库存为0的商品过滤、尺码/价格不符合query显式约束的过滤。
Beam Size怎么定?我建议线上起始用4到8,每个query生成100到200个候选就够了。生成式召回的核心价值不是把召回池从十万涨到百万,而是提供一路高质量、强约束的候选,和向量召回、倒排召回形成差异化互补。如果Beam开太大,时延涨得飞快,而召回升幅并不明显,性价比极低。
线上融合也不复杂。倒排召回、向量召回、生成式召回三路结果做集合并集,粗排模型统一打分后再进精排。为了让融合效果好,我会给生成式召回这路单独记录候选标记,方便后面分析生成式召回的独特贡献,也方便AB实验时按路拆解收益来源。
上线节奏建议走三步:先离线用历史日志回放,看生成式候选是否覆盖真实成交样本;再上影子链路,在不影响线上主链路的前提下观察候选质量;最后小流量AB,逐步放量。千万不能跳过影子阶段直接全量,生成式模型对训练数据的拟合偏好一旦带入线上,可能持续输出跟用户历史行为高度同质化的商品,这个风险需要人为控量。
4. 评估体系与常见问题排查实录
4.1 离线评估:别只盯着Recall@K
很多人评估召回模型只有一个指标:Recall@K。但生成式召回如果只看Recall@K,很容易得出“涨了两个点”的乐观结论,上线后却没有任何交易收益。原因在于Recall@K只衡量“正确结果在不在候选集合里”,不衡量“候选集合整体质量”。
生成式召回理想的离线评估应该至少看四类指标:
- 命中类:Recall@K、HitRate@K,看候选集合对真实成交样本的覆盖。
- 质量类:候选集合与query的文本相关性、类目一致性、属性一致性,需要抽样人工评测。
- 多样性类:候选集合的类目熵、品牌熵、价格带覆盖度、与向量召回的Jaccard重合度。
- 长尾类:非热门商品在生成候选中的占比、新品进入候选的比率。
这里我要特别提醒的是重合度问题。生成式召回如果生成的候选80%都和向量召回重合,说明模型学到的是“全局畅销”,而不是“个性化+强约束”,这路召回加了等于没加。理想情况下,生成式召回和向量召回的Top候选重合度应该控制在30%以下,才有真正的路径增量。
4.2 在线AB实验:交易场景更看重什么
在线实验的指标选取也要贴合交易场景。除了搜索场景通用的点击率、转化率,还要关注:
- 搜索成交额(GMV):这是交易搜索的北极星之一。
- 搜索无结果率(NIR):生成式召回如果设计得好,会把“之前无结果但有潜在成交可能”的长尾query救回来,这个指标是典型改善项。
- 人均浏览商品数:衡量召回是否给了用户更多可选空间。
- 首屏相关性:通过人工标注或者用户反馈代理,防止召回“泛化”过头。
AB实验分层上,建议先拿一类最典型的流量做验证,比如“复杂条件query流量”,也就是同时包含品牌、品类、尺码、颜色等多个属性的搜索请求。这种流量最能体现生成式召回的差异化优势。如果在这部分流量上没有显著提升,那大概率是生成式召回没做好,而不是这条路本身不行。
4.3 常见问题速查表
| 问题 | 可能原因 | 排查与修复 |
|---|---|---|
| codebook利用率低,大量code失效 | 量化器坍缩,码本坍塌 | 降低学习率,重置不活跃code,增加重建损失权重 |
| 生成候选全是热门商品 | 负样本构造不当,过度拟合流行度 | 增加曝光未点击和个性化负样本,降低全局热门权重 |
| Beam Search结果重复率高 | 解码多样性不足 | 加入长度惩罚和重复惩罚,或改用分组采样 |
| 生成候选与向量召回高度重合 | 语义ID区分度不够,模型学到全局共性 | 增加样本权重差异,调整特征或量化层数 |
| 推理时延超标 | Beam过宽、序列过长 | 剪枝策略前移,限制行为序列长度,KV Cache复用 |
| 新商品无法生成 | 语义ID未覆盖新item | 对新商品实时计算语义ID并加入索引,冷启动增加随机扰动 |
| query含尺码/价格约束但生成候选不满足 | 约束未注入解码过程 | 增加前缀白名单,解码后补一次硬过滤 |
这个表是实操里最容易复现的问题清单。每个问题我基本都在线上遇到过一轮,其中最折腾人的不是模型训练,而是“候选集合与向量召回重合”这个问题——它不报错、不显眼,但会让整条新路线的收益归零。排查方法很简单:线上抽样比对两路召回结果的交集大小,如果超过一半,就要回头检查语义ID和负样本设计。
4.4 交易平台的特殊性:得物场景下的几个注意事项
如果场景是得物这种潮流交易平台,还有三个点值得单独说。
第一是强视觉属性。球鞋、潮服这类商品,用户决策高度依赖“看起来对不对”。文本ID很难表达“黑白配色”“低帮”“刺绣logo”这类视觉信号。实操时我会在商品表征阶段直接拼入图片特征向量,让RQ-VAE量化的“语义”包含视觉信息,而不是只用文本和类目。这一点对转化率提升非常关键。
第二是尺码这个细节。用户搜“42码”,有些搜索日志里query没有带尺码,但用户历史行为里买的都是42码,生成模型能不能从行为序列里学到这个偏好?这要求训练数据里把“成交尺码”也作为一个信号参与建模。我在样本构造时会把用户下单尺码作为一个额外特征拼到序列里,而不是只依赖query和点击记录。
第三是新品尖货的强时效性。潮流平台的新品上架节奏快,热门款可能几天内售罄,生成式召回需要很强的实时性。如果语义ID和候选索引还是一天一更新,会漏掉大量当季尖货。工程上至少要做到小时级更新,关键类目最好做到近实时。
5. 一些个人体会和后续可以做的方向
生成式召回这条路,我最深的一个体会是:它真正的价值不在“多召回几个商品”,而在“让召回环节第一次有了把复杂约束和个性化偏好摞在一起建模的能力”。向量检索的思维惯性是“在库里找一个最像的”,生成式检索的思维惯性是“根据用户当下的需求描述,构造出符合条件的一组答案”。两者看着都在解决召回问题,实际上是在两个不同的抽象层级上思考。
如果让我给想尝试这个方向的团队一条最实际的建议,我会说:先别急着上模型,先花两周时间把“语义ID”做扎实。把商品表征空间的质量提上去,把量化器的codebook调稳,把“同类商品前缀相近”这个性质验证清楚,再开始训练生成模型。语义ID这步如果偷懒了,后面所有的生成、解码、融合都会在同一个地基上反复出问题,而且很难定位,因为bug不在模型本身,而在表征层。
后续可以扩展的方向也有不少。一个是把生成式召回和精排更紧密地联动,比如让生成模型直接输出带预排序分数的候选,给精排提供更好的起点。另一个是多模态生成,当前做的是“生成商品ID”,下一步可以直接“生成商品图对应的多模态表征”,让视觉信号更早介入。还有就是可控性解码,在生成过程中用更细粒度的控制条件约束类目、价格带、品牌,让候选集合能跟着平台的运营策略实时变化。
放在更大的视野里,交易搜索的召回范式确实在经历一次切换:从“构建索引去匹配”,走向“理解需求去生成”。这项技术刚过了落地验证的阶段,后面能长出什么,很大程度上取决于第一批吃螃蟹的人把地基打得有多扎实。