上周一个在快手做搜索的前同事发消息给我,说他们前段时间搞了一个收益非常大的上线,发了两篇论文,能不能帮忙宣传一下。
这个朋友去年还跟我吐槽,感觉自己马上要失业了,做搜索已经不知道该优化啥了。
ChatGPT要替代搜索引擎,这个论调炒了两年,作为一个做了五年搜索算法的老兵,过去两年我一直在思考一个问题:搜索系统还能做点什么。
BERT双塔、向量检索、多路召回、精排蒸馏...该加的路都加了,该卷的点都卷完了。行业共识是搜索已经是一个成熟技术,增量优化空间越来越小。
我问他改进有多大?他说这套系统已经在快手搜索全量上线,创造了近两年最大的单次实验收益。
“你们咋做的?”他就把论文发过来了。
Rethinking倒排索引、Rethinking搜索架构。
倒排索引是搜索系统的基石,从1998年到现在27年了,全世界搞搜索的都在这个框架里优化。
这两个Rethinking让我有点警惕,抱着怀疑的心态去看了下这两篇论文。
读完后的整体感受是:
快手这两篇论文(UniDex和UniSearch)在当下生成式大模型热度这么高的时候,这种工业级系统改进的工作,真的非常稀缺,有种返璞归真的感觉。
很多人以为生成式搜索就是Perplexity那种AI问答,其实快手做的是完全不同的另一件事。
Perplexity是改变了你「怎么看结果」,从10条蓝链接变成AI生成的答案;快手的内容还是快手里真实的短视频、直播间,改变的是系统「怎么找到结果」 , 后者是技术创新。具体来说:
-
UniDex把倒排索引从「词」换成了「语义ID」;
-
UniSearch把召回-粗排-精排三段式换成了端到端生成。
今天这篇文章,我会带入到之前做搜索算法的角色,讲一下面对数百亿视频资源、数十万并发直播间、每天数亿次搜索请求的业务场景里,他们在技术上怎么做到的。
UniDex重构倒排索引
先来聊聊UniDex。
倒排索引(Inverted Index)是所有搜索引擎的基石技术,基于词匹配(Term-based)进行检索。
词→包含该词的文档列表
"红烧肉"→[Doc1,Doc5,Doc29,...]
"教程"→[Doc1,Doc8,Doc15,...]
比如用户搜"红烧肉教程",系统找到“红烧肉”、“教程”两个索引的交集,然后用BM25打分。从1998年Google诞生到现在,这个范式一直在用。
但它有个致命问题:只能匹配"字词",无法理解"语义"。比如query用"教程",视频用"指南"、"tutorial"就没办法召回。
这就是 词汇鸿沟 (Vocabulary Mismatch)。为了缓解这个问题,工程师们发明了无数补丁:同义词词典、停用词处理、query改写、多路召回...系统也逐渐复杂,每次改动都要祈祷别把其他地方搞挂。
快手技术团队在这篇论文里给出了一种节省资源、搜索体验更好的解决方案—— UniDex。
从基于词项(Term-based)转向基于模型的语义(Model-based)索引。不用词,改用模型学出来的语义ID(Semantic IDs)。
UniDex主要包含两个核心模块:
-
UniTouch(触达模块):将query和doc映射为离散的语义ID。
-
UniRank(排序模块):通过语义匹配对检索结果进行精细排序。
UniTouch负责将视频和query映射为 语义ID序列 :
视频→Doc Encoder→语义嵌入→FSQ量化→8个语义ID序列,例如:
猫咪→[S1:57099,S2:315147,S3:315371,...]
狗狗→[S1:57099,S2:218453,S3:425681,...]
查询时也生成语义ID。
Query "可爱的猫"→Query Encoder→语义嵌入→FSQ量化→3个语义ID[S1:57099,S2:315147,S3:315371]。
为什么query用3个ID,document用8个?
因为query通常短且语义聚焦,3个ID足够;而document内容丰富,可能包含多个语义侧面,需要更多ID来表达。
这样即使query和视频元数据没有字面重合,只要语义相近就能检索到。
为什么要量化成离散ID?
因为连续的embedding无法直接建倒排索引。必须把连续空间离散化,才能像传统Term一样查表。
如何判断query和document是否相关?
传统BM25看有多少词重叠,UniDex的方法是看有多少语义ID重叠。
并且使用了"Max-Max"匹配策略,只要query的3个ID和doc的8个ID有1个匹配就召回,因为用户query可能有多重意图,匹配一个就该返回。
UniRank是精排模块,采用类似ColBERT的token级交互,但更轻量,因为query和doc只使用少量token,大大提升语义匹配精度。
UniRank与UniTouch用的是同一套语义编码框架,这样的好处是召回和排序两个阶段共用统一的语义表征方式,避免表征差异导致的匹配偏差。

所以它是用了一个统一的语义建模框架,这就是论文标题为什么叫"Unified Semantic Modeling"的原因。
UniDex的实际效果
看Table 1,这个对比很说明问题。
传统BM25的Recall@300是49.56%,SPLADE这种神经稀疏检索做到56.56%,快手自己的baseline是55.33%, UniDex直接干到70.74%,逼近了Dense Retrieval的效果上限。

再看Table 5的线上数据,在快手短视频搜索里(10亿视频库),CTR、VPD、LPC这些用户满意度指标全涨了。

更关键的是资源消耗:CPU核数-20550(节省了2万多核),内存节省37TB,响应延迟降低25%。
为什么能省资源? 传统的同义词扩展、query改写、多路召回、复杂term匹配规则...都省了,UniDex只需要一次模型推理,得到query语义ID,查UniDex的语义倒排索引就可以,简洁高效。

UniSearch:统一搜索三段式
再来聊一下UniSearch。
UniDex是重构了搜索的"索引层",那么UniSearch则是动了搜索的架构。
传统搜索(包括用了UniDex的系统)本质还是检索范式,召回-粗排-精排,UniSearch是生成范式,Query直接生成Top-K结果的语义ID序列,统一了三段式。
“统一”的三层含义
看到一个模型替代三段式,很多人觉得就是把召回、粗排、精排三个模型合并成一个大模型。
太浅了。
传统级联架构的问题不是"模型太多",而是召回、粗排、精排的优化目标都不一致。
而且号称"生成式推荐"的相关模型们,也没有逃出这个陷阱,也是两阶段,先训练Video Encoder,再训Generator。
UniSearch是一个“真端到端”的训练架构。
把Search Generator和Video Encoder放到一个训练框架里,联合优化。
看架构图。Search Generator负责理解query、生成语义ID序列;Video Encoder负责把视频编码成embedding、通过VQ-VAE量化成语义ID。
两个模型虽然独立,但关键在于它们通过统一的损失函数绑在一起。论文里的联合损失函数写得很清楚:
L=λ1·L_contrast+λ2·L_codebook+λ3·L_NTP
-
L_contrast:query和video的语义对齐。
-
L_codebook:VQ-VAE的codebook质量。关键是这个codebook是可学习的。
-
L_NTP:Generator的生成准确性。
从语义对齐、离散化质量、生成准确性,全程一个目标函数联合优化。
论文里还有个很巧妙的设计。
每个视频用k个语义ID表示,但这k个ID不是独立学的,而是用 残差对比学习 (Residual Contrastive Learning)逐步递进:
-
第1步生成:ID_1(粗粒度语义:动画类)
-
第2步生成:ID_2(细粒度语义:儿童向)
-
第3步生成:ID_3(最细粒度:熊出没IP)
分别对应召回的粗粒度、粗排的中粒度、精排的细粒度,自然形成了从粗到细的层次结构,模拟了传统三阶段的能力递进。
UniSearch要在直播搜索全量上线,面临的第一个问题就是: 直播内容的极端动态性。
电商搜索,商品相对稳定,小时级更新就够了。短视频搜索,内容缓慢增长,分钟级更新也能接受。
但直播不一样。
11:00:00,某主播开播"熊出没玩具开箱";11:30:00,转播"王者荣耀";12:00:00,下播。内容每分钟都在变,直播间每秒都在开播/下播。快手有 数十万并发直播间 ,每秒数千次状态变化,所以UniSearch的实时链路必须做到秒级响应。
所以线上实时链路里是怎么跑的呢?
看架构图,整个链路分三块:以用户输入Query "熊出没"为例进行说明:
实时生产模块
这是保证内容时效性的关键。
Embedding生产 :直播间一开播,Video Encoder立刻提取标题、封面、主播信息等特征,编码成语义向量。
码本生产 :语义向量通过VQ-VAE量化成离散的语义ID。论文设计是k=3层ID,每层512个codebook,总共512³≈1.3亿种组合。
实时消费:新增/更新/逐出。
-
新增:直播间开播,生成语义ID,写入Trie树。
-
更新:主播改变内容(从聊天变打游戏),1分钟内embedding刷新,ID重新映射。
-
逐出:直播间下播,从Trie树删除对应路径。
论文说的是"1分钟时间窗口"——直播间的表征每分钟更新一次,保证内容和ID的一致性。
动态Trie树
这是整个实时链路的核心创新。
传统生成式模型的致命问题:可能生成不存在的ID组合。Trie树能实时监听"码本生产"的数据流,维护所有在线直播间的有效路径。
图上画的很清楚,t-1时刻的Trie和t时刻的Trie,结构在动态变化——有新节点加入,有旧节点移除。
UniSearch输出的不是直接的ID序列,而是码本概率分布。然后在Trie树上做beam search。
第1步生成:ID=57099(动画类)
问Trie:"接下来能选哪些?"Trie返回:[315147,315148,315149](当前在线的有效子节点)
第2步生成:从[315147,315148,315149]里选,假设选了315147(儿童向)再问Trie:"接下来能选哪些?"Trie返回:[315371,315372,...]
第3步生成:选315371(熊出没IP)
每一步生成时,Trie只允许选择"有效路径",保证生成的路径一定指向真实的直播间。
论文数据:有效生成率从51.3%提升到99.8%。
Reward System+Online Training
右侧的Reward System收集两种信号:
-
R_system:精排模型的系统评分(相关性、质量)
-
R_interaction:用户真实行为(点击、观看、关注)
合成最终奖励:R=γ1×R_system+γ2×R_interaction
每天晚上,左侧的Online Training用 SPO算法 (Search Preference Optimization)更新模型。
比如发现用户更喜欢"玩具开箱"类直播?→增加相关ID的生成概率。第二天早上新参数同步到线上,系统在持续进化。
这就是工业级生成式搜索的完整闭环。
UniSearch在快手直播搜索全量上线后,TPC+3.31%是快手直播搜索近两年单次实验的最大收益。

更有意思的是深入分析: 65%的增长来自长尾query ,因为UniSearch本质是语义理解提升了。

还有,58%的增长来自新用户。
新用户没有历史行为,传统推荐很难个性化。UniSearch靠语义理解能给出更准确的搜索结果。
最后
当所有人都在讨论"大模型能做什么"的时候,大部分答案都指向:生成文本、写代码、做助手。仿佛生成式AI的全部价值就是"替代人类生产内容"。
但快手这个案例让我看到了完全不同的可能性:
生成式AI不一定要生成内容,它可以用来重构系统架构。
UniDex和UniSearch的核心都是“统一”——统一的语义空间、统一的优化目标、统一的训练框架。
端到端优化,消除系统内耗,这应该是工业级系统的护城河。
从思考到创造