重新理解生成式召回:从负采样到物品编码
2026-9-23
| 2026-9-25
字数 5940阅读时长≈ 15 分钟
type
Post
status
Published
date
Sep 23, 2026
slug
rethink-gr-sampler-tokenizor
summary
生成式召回如果不是轻轻松松地拿到收益,大概率是做错了。 其实生成式比双塔简单 一方面,业界越来越多的人用生成式召回很快拿到了不小的业务结果;另一方面来与和传统召回对比,相比从零到一落地一套双塔召回,生成式召回在建模和 infra 上其实简单得多。这听起来有点反直觉:在一家推荐 Infra 足够成熟的公司里,上线一路双塔已经非常标准化,生成式召回反而容易被当成更新、更复杂的技术。但这只是因为业界在双塔上积累了接近十年的 Infra 和认知,很多今天看来理所当然的能力,当年都不是免费的。
tags
推荐
category
推荐系统
icon
password
priority
3
notion image
生成式召回如果不是轻轻松松地拿到收益,大概率是做错了。

其实生成式比双塔简单

一方面,业界越来越多的人用生成式召回很快拿到了不小的业务结果;另一方面来与和传统召回对比,相比从零到一落地一套双塔召回,生成式召回在建模和 infra 上其实简单得多。这听起来有点反直觉:在一家推荐 Infra 足够成熟的公司里,上线一路双塔已经非常标准化,生成式召回反而容易被当成更新、更复杂的技术。但这只是因为业界在双塔上积累了接近十年的 Infra 和认知,很多今天看来理所当然的能力,当年都不是免费的。
如果你从零做过双塔,第一个要解决的就是向量索引。在还没有成熟向量检索库的时候,我有幸手搓过基于 K-Means 的搜索树做近似检索。今天有了 Faiss、HNSW 和各种向量检索系统,这件事被隐藏了起来,但它本身并不简单。双塔还有一个常被忽略的问题:用户塔和物品塔的一致性。用户向量一般可以实时计算,物品向量却往往需要异步或流式刷新,如果模型已经更新,向量库里的物品向量还来自旧模型,用户和物品就不再处于完全一致的向量空间,于是又需要向量刷新、模型版本管理、增量建索引和各种兜底机制。生成式召回在索引这件事上直接得多:只需要把 SID 到物品 ID 的映射存进 KV 或倒排索引,模型生成 SID 以后直接查回物品。
双塔看起来简单,并非是第一天起,而是它的复杂性已经沉到了 Infra 下面。
公平地说,生成式召回也把一部分成本转移到了别处:自回归解码加 beam search 的推理开销、延迟和 GPU 成本,明显高于一次用户塔前向加一次 ANN 检索,这是生成式召回在 serving 侧实打实的代价。但是今天模型是以 scaling 为基础的,GPU 上的推理应当是所有scalable 模型理应付出的成本。
除了infra,双塔在建模上并不简单,当时能一把做正的人也不算多。

双塔真正的难点:negative sampling

双理想情况下,我们希望建模一个 full softmax:
如果物品只有几千个,这没有任何问题。但工业推荐系统的候选集合可能有千万甚至上亿,不可能为每个样本计算完整分母,只能从某个采样分布 中采出少量物品,用一个很小的集合去近似整个物品空间。也就是说,从引入 sampling 的那一刻起,我们优化的就已经不是原始的 full softmax,而是它的近似。
早期 YouTubeDNN 用的是类似 Word2Vec 的 sampled softmax。词表只有几十万时,把采样表放进内存没什么问题;但候选集合变成上亿个持续变化的物品时,如何维护采样分布、如何高效采样,本身就是一个不轻的 Infra 问题。后来 In-batch Negative 极大地简化了这件事:直接把同一个 batch 里其他样本的正例当作当前样本的负例。问题也很明显,负例分布实际上对齐了正例分布,一个物品作为正例出现得越频繁,就越容易成为别人的负例,热门物品因此被额外打压。
《Sampling-Bias-Corrected Neural Modeling for Large Corpus Item Recommendations》提出了经典的 LogQ correction,直觉很简单:一个物品如果只是因为热门而更容易被采成负例,就不应该因为这种采样频率被再惩罚一次。但 LogQ 要求准确估计物品的采样概率,在流式系统里这远没有公式看上去那么简单,我在实际业务中就见过 LogQ 被反复实现错误。
而采样概率还只是第一层问题。一个好的 还必须覆盖真正重要的竞争物品:如果正例是一双运动鞋,负例大多是锅碗瓢盆,loss 很好优化,模型却几乎学不到有用的区分边界。于是又有了 hard negative、混合负样本、跨 batch 负样本,以及越来越复杂的 sampler。
更让人绝望的是,这并不是工程做到位就能彻底解决的问题。《Adaptive Sampled Softmax with Kernel Based Sampling》给出了一个很漂亮的结论:只有当采样分布恰好等于模型自己的 softmax 分布时,
sampled softmax 的梯度才是 full-softmax gradient 的无偏估计。现实中的均匀采样、热门度采样、In-batch Negative 都做不到这一点,LogQ 和更多负样本可以减小 bias,但有限采样本身始终是一种近似。所以召回领域有一句很形象的话:排序是特征的艺术,召回是样本的艺术。它背后的事实是,双塔的建模质量很大程度上取决于 negative sampling。
而生成式召回最重要的变化之一,就是直接绕开了这件事。

生成式召回:把一个大 softmax 拆成若干个小 softmax

生成式召回首先给每个物品定义一个编码,也就是 SID(semantic id):
然后优化:
原来无法在全量物品上计算的巨大 softmax,被改写成了若干个小词表上的条件 softmax。只要每层词表足够小,每个 token 都可以计算完整 softmax,也就不再需要 negative sampling。从这个角度看,生成式召回和 hierarchical softmax 有很深的关系:它不再直接回答"这一亿个物品中是哪一个",而是先给每个物品编一条路径,再沿着路径连续做几次小分类。只不过这棵树没有被显式展开,而是隐藏在一个 Decoder-only Transformer 里。粗略地说:
这里真正重要的不是用了 Transformer,而是把一个巨大的物品预测问题重新分解了。没有 negative sampling,也就没有它引入的梯度 bias,不用再研究该采多少负样本、LogQ 怎么修、hard negative 怎么挖。当然,这不意味着生成式召回从此"完全无偏":训练数据仍有曝光偏差,线上反馈仍有选择偏差,beam search 也会带来搜索误差。消失的只是一个非常具体的问题,即为了近似超大规模 full softmax 而人为引入的 sampling bias。
但这世上没有免费的午餐。negative sampling 的问题消失以后,真正需要设计的东西并没有消失,只是换了位置。
双塔的核心问题在 sampler,生成式召回的核心问题在 tokenizer。
这和开头"轻轻松松拿到收益"并不矛盾。tokenizer 难在上限,不难在下限:只要 SID 守住几条基本原则,哪怕是最朴素的建模方案,生成式召回也应该 work;如果它迟迟拿不到收益,通常不是 模型架构和训练方式不对,而是它的 tokenizor违背了某条原则。所以一套生成式召回打不过双塔,我第一个要检查的通常不是模型架构,而是 SID。
 

编码而非聚类,ID 先于 Semantic

SID 最容易犯的第一个错误,就是把它理解成聚类。我看过一种说法,把生成式召回理解成"先把物品聚类,再生成物品所属的 cluster",我觉得这个理解从根上就偏了。SID 当然可以利用聚类结构,相似物品共享前缀本来就是它的重要能力,但两者最大的区别在于:聚类允许信息损失,编码首先要求保留物品identity。
SID 可以具有前缀聚类结构,但不能退化成聚类结果,其中的信息论损失是后续模型 scaling 无法弥补的。
设物品是随机变量 ,SID 是 。如果 是一一映射,知道完整 SID 就能唯一恢复物品:
SID 改变了物品的表示方式,但没有丢掉物品identity,即身份唯一性。而如果 ,却有 ,那么:
这部分不确定性一旦在 tokenizer 里产生,后面的 Transformer 无论 scaling 到多大都无法恢复。模型即使把 SID 预测得百分之百正确,也不知道该返回 还是 。这就是为什么"把 SID 当聚类"是一个危险的直觉:聚类习惯于认为一个簇里有很多物品是理所当然的,但生成式召回最终要召回的是物品,而不是 cluster。
后者损失了信息量,不仅仅是检索时增大了熵,也使得SID 自回归学习时任务变得简单,loss 下降反而学习到的表征变差。
如果 只是 的可逆编码,那么:
一个无损 SID 并没有降低推荐问题本身的信息量,它做的只是重新分解这个问题。因为 ,由链式法则:
从这个意义上说,随机 hash 是一个非常重要的底线 baseline。只要 hash 空间足够大、每个物品都有唯一 SID,随机 hash 至少做好了第一件事:它是一个可靠的编码。物品身份没有丢,也不会因为过度追求语义而形成巨型 cluster 或 不稳定编码。所以随机 SID 往往不会错得离谱,它的问题不在正确性,而在 scaling 效率。但理论上只要有足够大的模型,足够多的数据,后续的 scaling 是没有问题,这也是据说 OneRec 第一个版本就是随机 hash 但依旧 work 的原因。
业界处理碰撞的常见做法,是在语义 SID 末尾追加一个去重 token,让发生碰撞的物品重新变成一一映射,TIGER 就是这么做的。这个做法本身很好,但很多人没有意识到它背后的含义:最后那一层承担的不是语义,而是identity。
 
既然SID 优先是编码,而非聚类,要有足够多的孤点簇,那么充足的编码空间就是必要条件。假设每层词表大小是 ,SID 长度是 ,理论地址空间是 ,生成式的魅力就在于和 是线性增加,而寻址空间指数增加。但由于增大 是消耗更大decode latency(串行),而增大 K 可以有效的利用 GPU 的并行计算,所以一般 被控制在 3 个以内,而就需要 足够的大。
也就是 SID 需要足够大的词表空间。否则哪怕是随机 hash,也容易造成物品碰撞,沦为聚类,损失后续 scaling 的效果天花板。
 
很多的失败来自于,过度地关注语义该怎么,比如花了巨大地精力处理一个视频切片的多模态信息的压缩编码与重建。在 identity 上没做好,在词表均衡度上没做好,导致变成了实质上的语义聚类。
实际上,Semantic ID 首先是 ID,其次才是 Semantic。
 

Semantic 的名字充满误导

我现在习惯叫它Tokenizor,就是你有一种 learnable 的编码方式,能把物品转化成 token。Semantic ID 会植入第一印象,就是你要开始做多模态表征了!
Semantic ID 起源于 Doc 的生成式检索,那自然是文本模态的压缩,Tiger 把它用于生成式推荐,也自然延续了文本 embedding,再量化编码的模式。
它的演进路线是这样的:
DSI / 生成式文档检索 → Semantic DocID → TIGER → 推荐系统里的 Semantic ID。
DSI 这类生成式检索工作最先提出一个核心问题:既然检索最终是在找某篇 document,那能不能不做 query embedding → ANN → document,而是直接让 Transformer 生成 document ID。DSI 试了 Atomic ID、普通字符串 ID,以及 Semantic String DocID。其中 Semantic String 的做法是先用 BERT 得到文档的文本 embedding,再做 hierarchical k-means,把聚类路径变成类似 (3, 7, 2, 5) 的 ID。于是相似文档会共享前缀。
TIGER 做的事情非常自然:把这个范式从 document retrieval 搬到了 item retrieval,同时把 Semantic ID 的构造方式从 hierarchical clustering 升级成 learned quantization。
 
我知道的一个 case,努力地处理多模态语义,做极致的压缩重建,最后打不过简单的Hash 的。这背后有 Semantic 这个名字充分地误导,语义重建优化的是 Semantic Similarity,而“语义”的真正价值是recommendation predictability。
语义相似的物品,因为潜在的价格/补贴可能有非常大的购买人群差异;用户经常一起买的东西,语义未必相似,比如著名的啤酒和尿布。要追求是“协同相似”而非“语义相似”。
《Rethinking Generative Recommender Tokenizer: Recsys-Native Encoding and Semantic Quantization Beyond LLMs》指出:SID 本质上应该是一种适合 recommendation + autoregressive generation 的编码。它需要同时保留推荐所需的信息,并让 token 序列容易被逐 token 预测。
做到编码优先的前提下,语义能带来而提升是把协同相似的物品共享 token 前缀,增大后续模型的 Scaling ROI。
假设两个物品在用户行为上高度相似,随机 hash 给出的 SID 却是 和 ,从第一位开始就没有任何共享结构,对 Transformer 来说它们几乎是两个毫不相关的标签。模型当然可以学出来,足够大的模型加足够多的数据,理论上能记住任意随机映射,但代价是要花更多的容量和数据,去重新发现原本就存在于物品空间里的结构。
随机 hash 没有破坏信息,但浪费了 scaling。
这一点可以直接从前面的熵公式推出来:对任何一一映射的 SID,随机 hash 和 Semantic ID 需要表达的信息量没有区别。在无限模型、无限数据的理想情况下,它们都能拟合同一个 ;但现实中容量和数据永远有限,真正不同的是哪一种分解更容易学。如果 SID 把预测规律相似的物品放在相同前缀下,这些物品的大量交互就能共同帮助模型学习 ,再在后面的 token 中逐步完成更细的区分;随机 hash 没有这种结构,新增的模型容量首先要拿去补编码缺失的结构,而不是学习用户兴趣和更细粒度的物品差异。
所以 SID 里的 "Semantic",不是为了构建一个信息丰富的多模态语义,而是为了减少模型必须自己学习的结构。同样的模型规模、同样的训练数据,一个好的 SID 应该比随机 hash 更快逼近正确的物品分布;反过来,达到同样效果,它应该需要更少的参数和数据。
所以是“ID”在影响scaling 的上限,“Semantic”在影响 scaling 的 ROI。
scaling law 不只属于模型,也属于 tokenizer,甚至可能在生成式召回这个场景下是决定 scaling Law 的关键问题。
这也意味着评价一个 SID 不能只看当前模型指标,还要看它的 scaling curve:模型从 100M 扩到 500M、1B 时,能不能持续把新增容量转化为收益?训练数据从十亿扩到百亿时,能不能持续从新增数据中获益?反过来,如果 SID 碰撞严重、前缀分布极度不均衡,模型很快就会撞到 tokenizer 的天花板,这时继续 scaling 也很难有持续收益,因为瓶颈已经不在 Transformer。
 

一个强baseline

第一件事要求区分,第二件事要求共享,两者天然存在张力。沿着这个张力,可以得到一个我认为非常强的 SID baseline:第一层只负责一个较粗的语义或协同划分,后面的 token 不再继续做越来越细的聚类,而是用稳定的 hash 把 bucket 内的物品唯一地区分开:
它其实就是前面去重 token 思路的推广:既然最后一层本来就在承担身份,不如更早地把"共享"和"区分"交给不同的层。这个方案看起来没有纯 Semantic ID 漂亮,却同时守住了两件最重要的事情:第一层提供共享,行为或语义接近的物品至少会进入同一个大分支,模型可以共享一部分统计信号;后面的 hash 保证物品身份,不会因为一路聚类到底,最后把很多物品挤进同一个完整 SID。
Semantic 的作用是提供更好的先验结构,而不是取代 ID 本身。
我甚至觉得,一个复杂的 SID tokenizer 如果连"一个语义 token + 若干随机 hash token"这样的 baseline 都打不过,应该优先怀疑 tokenizer 本身,而不是继续往模型上加复杂度。更好的 tokenizer 无非是在此基础上做得更精细,看第二层是否还有值得共享的结构、第三层能否继续分解,同时不让碰撞、负载不均衡和搜索误差迅速上升。
过度共享还有一个更致命的代价,就是 beam search。前面所有关于 的讨论都有一个隐含前提:模型能把整棵树都看一遍。但线上 beam 的宽度是有限的,如果一个高层前缀下挂着几十万个物品,这个前缀一旦被剪掉,下面几十万个物品会一起失去召回机会。
编码无损,不代表检索无损。
所以 SID 的设计问题并不是"怎样让每一层都更有语义",而是:
在哪一层继续共享是收益,在哪一层应该停止共享、开始保留物品身份。
 

泛化到新物品

在 tokenizer 完全不更新的情况下,一个新物品能不能被旧编码器合理地映射进现有 SID 空间?
Atomic ID 在这个问题上几乎没有泛化能力,新物品就是一个模型从未见过的新 ID。随机 hash 可以轻松给它分配一个唯一地址,但这个地址和历史物品没有任何结构关系,模型无法从编码本身知道它该和哪些物品共享规律。而一个好的 Semantic SID encoder 可以把从未出现过的新物品直接放进已有的前缀结构,比如 。虽然这个物品还没有任何交互,前缀已经告诉模型它和历史上的一批物品共享某种结构,过去积累在这些前缀上的统计规律就能迁移过来。前面那个"语义前缀 + hash 后缀"的 baseline 在这里同样成立:新物品通过语义前缀获得泛化,通过 hash 后缀获得身份。
所以评价一个 tokenizer 时,我还会问一个很直接的问题:用旧 tokenizer 编码新物品,还能不能把它放到一个合理的位置?如果一个 tokenizer 只能很好地编码训练时见过的物品,却无法对新物品做合理外推,那它更像是一次离线聚类,而不是一个真正具备泛化能力的物品编码器。
Semantic 是完成后两件事的手段,而不是目标本身。
 

从 sampler 到 tokenizer

再回头看双塔和生成式召回的区别,就非常清楚了。两者面对的是同一个问题: 的 full softmax 无法直接计算。双塔的选择是引入 sampler,于是大量技术复杂性落在 上,也就是负样本该怎么采;生成式召回的选择是先把物品重新编码为 ,再把巨大的 flat classification 分解成若干个可以直接计算的小 softmax,于是复杂性落在 上,也就是物品该怎么编码。两代召回系统的核心变量发生了一次迁移:
过去十年,我们花了大量时间理解 sampler:random negative 为什么不够,In-batch Negative 有什么 bias,LogQ 怎么修正,hard negative 怎么挖,不同负样本怎么组合。到了生成式召回,我们需要重新积累一套关于 tokenizer 的认知:词表多大,SID 多深,怎样控制碰撞,怎样保证各层分布合理,共享在哪一层停下,语义信号和协同信号如何结合,以及如何让旧 tokenizer 对新物品保持足够的泛化能力。
回到开头那个判断:生成式召回如果不是轻轻松松地拿到收益,大概率是做错了,而错的地方通常在 tokenizer。就像是十年前,如果双塔不能轻轻松松拿到收益,大概率是负采样做错了。
Semantic ID 这个名字很容易让人忘记一件最简单的事情:
它首先应该是一个 ID,然后才是 Semantic。
双塔时代,召回是样本的艺术;生成式召回时代,它正在变成 tokenizer 的艺术。
  • 推荐
  • 互联网持续晋升指南 推荐系统技术深度与技术品味
    Loading...