· liyu · algorithm · 13 min read

当双塔模型遇见 RAG:召回与检索,殊途同归

搜广推系统中的双塔模型和 RAG 中的向量检索,一个诞生于推荐系统,一个兴起于大模型时代。当我把两者的架构图放在一起时,突然发现它们竟如此相似——本质上都是在向量空间中寻找最近的"灵魂伴侣"。

搜广推系统中的双塔模型和 RAG 中的向量检索,一个诞生于推荐系统,一个兴起于大模型时代。当我把两者的架构图放在一起时,突然发现它们竟如此相似——本质上都是在向量空间中寻找最近的"灵魂伴侣"。

前段时间,我一边在搜广推的召回层调研双塔模型,一边又在大模型项目中搭建 RAG 流水线。某天晚上画架构图时,我把两张图摆在一起,愣了几秒——

这不是同一个东西吗?

一个叫”双塔召回”,一个叫”向量检索”,术语不同、出身不同,但骨子里干的是同一件事:把 Query 和候选集分别编码成向量,然后在向量空间中找最近邻。

这篇文章,我想把这个”顿悟时刻”完整地讲出来。

第一幕:搜广推中的双塔模型

召回层的使命

在搜广推系统中,面对百万甚至千万级别的候选池(商品、视频、广告),不可能对每一个候选都跑一遍精排模型。召回层的任务是:用尽可能低的计算成本,从海量候选中筛选出数百到数千个粗粒度相关的候选,交给下游精排。

传统的召回方式包括基于规则的热门召回、协同过滤召回、倒排索引召回等。但这些方法要么缺乏个性化,要么无法捕捉深层语义。双塔模型应运而生。

双塔架构

双塔模型(Two-Tower Model / Dual Encoder)的核心思想极其简洁:

┌──────────────┐          ┌──────────────┐
│   User Tower │          │  Item Tower  │
│              │          │              │
│  用户特征输入  │          │  物品特征输入  │
│  (ID, 行为,   │          │  (ID, 标题,   │
│   画像...)    │          │   类目...)    │
│      ↓       │          │      ↓       │
│  Embedding   │          │  Embedding   │
│   layers     │          │   layers     │
│      ↓       │          │      ↓       │
│  User Vector │          │  Item Vector │
│   (128-d)    │          │   (128-d)    │
└──────┬───────┘          └──────┬───────┘
       │                         │
       └────────┐   ┌────────────┘
                ↓   ↓
          cos(u, v) 或 dot(u, v)

            相关性得分

两座”塔”各自独立地将输入编码为一个稠密向量,然后通过内积或余弦相似度计算匹配分数。

训练过程

训练双塔模型本质上是在学习一个向量空间,使得:

  • 用户点击/购买/观看过的物品,两者向量距离近
  • 用户没有交互过的物品,两者向量距离远

常用的损失函数是对比学习风格的:

# 简化的双塔训练逻辑
def compute_loss(user_vec, pos_item_vec, neg_item_vecs):
    """
    user_vec:      用户塔输出 [batch, dim]
    pos_item_vec:  正样本物品向量 [batch, dim]
    neg_item_vecs: 负样本物品向量 [batch, num_neg, dim]
    """
    # 正样本相似度
    pos_score = torch.sum(user_vec * pos_item_vec, dim=-1)  # [batch]

    # 负样本相似度
    neg_scores = torch.bmm(
        neg_item_vecs, user_vec.unsqueeze(-1)
    ).squeeze(-1)  # [batch, num_neg]

    # Softmax cross-entropy
    logits = torch.cat([pos_score.unsqueeze(-1), neg_scores], dim=-1)
    labels = torch.zeros(logits.size(0), dtype=torch.long)
    loss = F.cross_entropy(logits, labels)
    return loss

服务阶段:离线 + 在线的优雅分工

双塔模型最大的工程优势在于两座塔完全解耦

  1. 离线:用 Item Tower 把所有候选物品的向量预计算好,存入向量数据库(如 Faiss、Milvus)
  2. 在线:用户请求到来时,只需过一遍 User Tower 得到用户向量,然后去向量数据库做 ANN(近似最近邻)检索
在线请求:
  User 特征 → User Tower → user_vec

                    向量数据库 ANN 检索
                   (Faiss / Milvus / Qdrant)

                    返回 Top-K 候选物品

这个流程保证了毫秒级响应,因为最耗时的 Item Tower 推理在离线阶段已经完成。

第二幕:RAG 中的向量检索

RAG 的动机

大语言模型(LLM)虽然强大,但有两个硬伤:

  • 知识截止:训练数据有时间边界,无法感知最新信息
  • 幻觉问题:对于训练数据中覆盖不足的领域,可能生成看似合理但错误的内容

RAG(Retrieval-Augmented Generation,检索增强生成)的解决思路是:先检索,再生成。 在 LLM 生成回答之前,先从外部知识库中检索出相关文档片段,作为上下文注入 Prompt。

向量检索的流程

RAG 的检索环节,核心就是向量检索:

┌──────────────┐          ┌──────────────┐
│  Query Tower │          │  Doc Tower   │
│  (Encoder)   │          │  (Encoder)   │
│              │          │              │
│  用户问题输入  │          │  文档块输入    │
│      ↓       │          │      ↓       │
│  Embedding   │          │  Embedding   │
│   Model      │          │   Model      │
│      ↓       │          │      ↓       │
│  Query Vec   │          │  Doc Vec     │
│   (768-d)    │          │   (768-d)    │
└──────┬───────┘          └──────┬───────┘
       │                         │
       └────────┐   ┌────────────┘
                ↓   ↓
          cos(q, d) 或 dot(q, d)

            相关性得分

离线索引阶段

from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import FAISS

# 1. 文档分块
chunks = text_splitter.split_documents(documents)

# 2. 用 Embedding 模型将每个文档块编码为向量
embedding_model = OpenAIEmbeddings()

# 3. 存入向量数据库
vector_store = FAISS.from_documents(chunks, embedding_model)

在线查询阶段

# 1. 用户提问
query = "BM25 和 TF-IDF 有什么区别?"

# 2. 用同一个 Embedding 模型编码 Query
# 3. 在向量数据库中做 ANN 检索
relevant_docs = vector_store.similarity_search(query, k=5)

# 4. 将检索到的文档拼入 Prompt,交给 LLM 生成回答
prompt = f"基于以下参考资料回答问题:\n{relevant_docs}\n\n问题:{query}"
answer = llm(prompt)

Embedding 模型:共享的编码器

在 RAG 中,Query 和 Document 通常使用同一个 Embedding 模型(如 text-embedding-3-smallbge-large-zhGTE 等)。这个模型的训练目标和双塔模型如出一辙:

  • 语义相关的 Query-Document 对 → 向量距离近
  • 不相关的 Query-Document 对 → 向量距离远

只不过 RAG 场景中 Query Tower 和 Doc Tower 共享权重(Siamese 架构),而推荐系统中双塔通常不共享(因为用户和物品的特征空间完全不同)。

第三幕:把两张图放在一起

当我真正把两个架构图并排放在一起时,相似之处清晰得令人震撼:

维度双塔模型(搜广推召回)RAG 向量检索
Query 侧User Tower:编码用户特征 → 用户向量Query Encoder:编码用户问题 → 查询向量
Candidate 侧Item Tower:编码物品特征 → 物品向量Doc Encoder:编码文档块 → 文档向量
匹配方式cos / dot productcos / dot product
候选集存储向量数据库(Faiss / Milvus)向量数据库(Faiss / Milvus / Qdrant / Pinecone)
检索方式ANN 近似最近邻ANN 近似最近邻
离线阶段预计算所有 Item 向量入库预计算所有文档块向量入库
在线阶段实时计算 User 向量 → ANN 检索实时计算 Query 向量 → ANN 检索
下游消费精排模型 → 重排 → 展示LLM Prompt → 生成回答

从数据流的角度看,它们是完全同构的:

[在线输入] → [Encoder] → [Query Vector]

                     [向量数据库 ANN]  ← [离线预计算的 Candidate Vectors]

                        [Top-K 结果]

                       [下游模块消费]

唯一的区别在于:

  1. 编码器是否共享:RAG 通常共享,双塔通常不共享
  2. 输入特征的性质:推荐系统是结构化特征(ID + 行为 + 画像),RAG 是纯文本
  3. 下游消费方式:推荐给精排模型打分,RAG 给 LLM 当上下文

但这些都是”皮肤”层面的差异,骨架是一样的

更深一层的相似

如果继续深入,还能发现更多共鸣:

1. 负样本策略一样重要

双塔模型中,负样本的选择直接决定模型质量:随机负采样、In-batch Negatives、Hard Negative Mining,各有利弊。

RAG 的 Embedding 模型训练也面临同样问题。bge 系列模型就大量使用了 Hard Negative 来提升检索质量。如果只用随机负样本,模型只学会了区分”完全不相关”,面对”相关但不是最佳答案”的文档就会犯错。

2. 向量维度的权衡一样纠结

双塔模型中,向量维度通常选 64 ~ 256。维度太低,表达能力不足;维度太高,存储和检索成本爆炸。

RAG 的 Embedding 模型同样面临这个选择。OpenAI 的 text-embedding-3-small 输出 1536 维,bge-small 输出 512 维。在大规模场景下,降维(如 Matryoshka Representation Learning)是必修课。

3. ANN 检索引擎完全通用

双塔模型离线建好的 Faiss 索引,和 RAG 建好的 Faiss 索引,底层用的是同一套 ANN 算法:

  • IVF(Inverted File Index):先聚类,再在类内搜索
  • HNSW(Hierarchical Navigable Small World):基于图的近似搜索
  • PQ(Product Quantization):向量压缩,用空间换速度

我在推荐系统中调过的 Faiss 参数(nprobenlistMefSearch),拿到 RAG 项目中几乎可以直接复用。

4. 都在走向混合检索

推荐系统早已不是纯向量召回,而是多路召回融合:向量召回 + 协同过滤 + 热门召回 + 规则召回。

RAG 也在走同样的路:向量检索 + BM25 关键词检索 的混合检索(Hybrid Search)已经成为最佳实践。Elastic、Weaviate 等向量数据库都原生支持混合检索。

殊途同归。

悟到

回头看,这种相似其实不是巧合,而是必然。

双塔模型解决的问题是:如何从海量候选中,快速找到与当前用户最匹配的物品。

RAG 向量检索解决的问题是:如何从海量文档中,快速找到与当前问题最相关的片段。

把”用户”换成”问题”,把”物品”换成”文档”,把”匹配”换成”相关”——底层的数学问题完全一致:在高维向量空间中做最近邻搜索。

这让我意识到一件事:技术领域之间的壁垒,远比我们想象的要薄。搜广推工程师觉得 RAG 是”AI 新概念”,NLP 工程师觉得推荐系统是”另一个世界”。但当你剥开术语的外壳,看到的是同一套数学、同一套工程范式。

我在医疗 AI 和军工 Agent 的跨领域经历也反复印证了这一点——不同领域的技术往往共享相同的底层范式,跨界学习的成本远低于从零开始。

所以,并不能用几个标签代表我。但”在向量空间中寻找最近邻”这件事,倒是贯穿了我最近做的几乎所有项目。

也许这就是技术最迷人的地方——万法归一。

Share:
Back to Blog

Related Posts

View All Posts »
搜索算法基石:从 TF-IDF 到 BM25 的演进之路

搜索算法基石:从 TF-IDF 到 BM25 的演进之路

在搜广推系统中,搜索是最核心的能力之一。本文从信息检索的经典算法 TF-IDF 出发,深入剖析其原理与局限,再引出工业界广泛使用的 BM25 算法,探讨它如何优雅地解决 TF-IDF 的不足。

AI 大模型 Ragent 项目:长会话 Token 爆炸?会话摘要压缩策略与水位线机制深度解析
阶段 1 进阶:摘要压缩算法

AI 大模型 Ragent 项目:长会话 Token 爆炸?会话摘要压缩策略与水位线机制深度解析

当多轮对话聊到 30 甚至 50 轮,滑动窗口滑走了开头的关键约束、全量保留又会挤爆 Token 上下文,RAG 系统该如何破局?深入剖析 Ragent 记忆系统中的增量摘要压缩算法、lastMessageId 水位线推进机制、攒批压缩优化(summaryBatchSize)、Prompt 绝对禁止记录答案的设计哲学以及 Redisson 分布式锁防重实践。