---
title: "RecsysTwoTowerModel（召回模型）"
description: "生产版双塔召回构件：RecsysTwoTowerModelConfig、RecsysCandidateTower、MolScorer、用户塔与服务前向；旧 PhoenixRetrievalModel 已删除"
---

> Documentation Index
> Fetch the complete documentation index at: https://x-algorithm.vergego.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# RecsysTwoTowerModel（召回模型）

## 是什么

`RecsysTwoTowerModel` 是 Phoenix 双塔召回模型的代码构件。它是一个 Haiku `hk.Module`，定义在 `phoenix/xrex/models/recsys_two_tower_model.py:1610`。配置类是同一文件中的 `RecsysTwoTowerModelConfig`（`:2854`）。本页说明类结构与方法；双塔召回的工作原理见 [Phoenix 召回：双塔检索架构](https://x-algorithm.vergego.ai/concepts/phoenix-retrieval/)。

> **旧构件的去向。** 旧页面描述的 `PhoenixRetrievalModel`、`CandidateTower`、`RetrievalOutput`（`phoenix/recsys_retrieval_model.py`）与测试文件 `test_recsys_retrieval_model.py` 已在 2026-08-13 删除。现在的候选塔是 `RecsysCandidateTower`。召回不再返回 `RetrievalOutput`，而由 `forward` 直接返回各语料区间的 top-K。

用户塔不是独立的类。它是一个 `RecsysAggregatedModel` 实例，与排序模型用同一个类、同一个 transformer，只是不带候选段（`recsys_two_tower_model.py:3138-3152`）。见 [RecsysAggregatedModel（排序模型）](https://x-algorithm.vergego.ai/entities/recsys-model/)。

## 配置：RecsysTwoTowerModelConfig

该类用 `@configclass` 声明，位于 `recsys_two_tower_model.py:2854-3150`：

| 字段 | 含义 | 类默认值 |
|------|------|------|
| `user_tower_config` | 用户塔，一个 `RecsysAggregatedModelConfig` | 必填 |
| `candidate_tower_config` | 候选塔，`RecsysCandidateModelConfig` | 必填 |
| `use_in_batch_negatives` | 把批内其他用户的候选用作负样本 | `True` |
| `num_global_negatives_per_example` | 每个样本的全局负样本数 | 0（各预设设为 64） |
| `logq_correction_scale` | log-Q 校正系数 | 2.0 |
| `fixed_temperature` | 固定温度；0 表示可学习（初值 0.1） | 0.0 |
| `own_negative_logit_offset` | 自身负反馈候选计入分母时的偏移 | 0.0 |
| `positive_count_user_weight_power` | 按正样本数给用户加权时的幂 | 0.0 |
| `mask_in_batch_false_negatives` | 剔除批内假负样本 | `False` |
| `user_query_heads` | 用户查询头数；0 表示平均池化 | 0 |
| `mol_item_components` 等 `mol_*` | MoL 打分的组件数、适配器秩、门控隐藏层 | 0（关） |
| `positive_actions` / `hard_negative_actions` / `soft_negative_actions` | `home` 头的正样本、硬负样本、软负样本动作 | 点赞 / 7 种负反馈 / 未停留 |
| `immersive_*`、`ld_*`、`tweet_click_positive_actions` | 其他头的动作清单 | 见 `:2918-2965` |
| `head_names` / `head_dataset_mapping` | 候选头名称；语料到头的映射 | `["home"]` / `None` |
| `w_fav` / `w_ld` / `w_tweet_click` | 服务时各打分槽位的权重 | 1.0 / 0.0 / 0.0 |
| `safety_filter_mode` | 安全标签过滤 | `"off"` |

计算属性如下（`:2977-3012`）：

- `blend_heads`：`head_names` 中属于 `("ld", "tweet_click")` 的头。这类头称为混合头：它们不负责语料，只增加打分槽位（`:75-80`）。
- `num_table_slots = 1 + len(blend_heads)`：索引每行分成的段数。
- `query_head_weights = (w_fav, w_ld?, w_tweet_click?)`：服务时每段的权重。

`make()`（`:3112-3150`）先做两项校验：`head_names` 数量与候选头数一致；混合头不能是第 0 个头，也不能负责语料。然后它构造用户塔、候选塔和模型。

预设位于 `phoenix/xrex/configs/xrecsys_two_tower.py:439-580`：`xrecsys_two_tower`、`xrecsys_two_tower_immersive_only(_gb300)`、`xrecsys_two_tower_combined(_gb300)`、`xrecsys_two_tower_home_only`、`xrecsys_two_tower_nano`。另有 `xrecsys_reply_two_tower_v2.py`（回复召回，本站未展开）。

## RecsysCandidateTower（候选塔）

该类是 `hk.Module`，位于 `recsys_two_tower_model.py:180-331`。它的配置 `RecsysCandidateModelConfig`（`:334-394`）包含 `emb_table_width`、`enable_linear_proj`、`max_posts`（默认 10,240,000）和 `num_candidate_heads`。

| 方法 | 位置 | 行为 |
|------|------|------|
| `_concat_then_mlp` | `:203-246` | 展平 → `Linear(2W)` → SiLU → `Linear(W)` → L2 归一化；头 `h > 0` 使用独立的参数名 |
| `_project_then_sum` | `:248-320` | 帖子、作者、SID 的每个 token 各乘一个 `[W, W]` 投影后相加 → L2 归一化 |
| `_mean_pool` | `:329-331` | 取均值 → L2 归一化，无参数 |
| `__call__` | `:322-327` | 按 `use_project_then_sum` → `enable_linear_proj` → 均值的优先顺序选择一种方式 |

`RecsysCandidateModelConfig.make_post_embeddings(num_table_slots)`（`:365-394`）创建形状为 `[max_posts, emb_table_width × num_table_slots]` 的 bfloat16 索引参数，即 checkpoint 里的 `post_embeddings`。

## MolScorer（MoL 打分）

该类是 `NamedTuple`，位于 `recsys_two_tower_model.py:710-884`。`mol_item_components > 0` 时启用（`combined` 预设为 1）：

- `item_components`：每条帖子经低秩适配器生成若干个单位向量（`:720-727`）。
- `mix`：计算“每个用户查询头 × 每个帖子组件”的余弦相似度，送进隐藏层为 32 的门控 MLP，经 softmax 后加权求和（`:729-737`）。
- `serving_side_tables`：为服务预计算每条帖子的范数与门控偏置。这些值存进 checkpoint，或在启动时计算（`:825-862`）。
- 配套 CUDA 核 `xrex/cuda/mol_epilogue/` 执行融合打分与 int8 量化（`:924-1003`）。

## RecsysTwoTowerModel 方法

| 方法 | 位置 | 作用 |
|------|------|------|
| `build_user_inputs` | `:1621-1815` | 构造用户塔输入；`feature_prep_enabled` 时使用特征准备路径（不含候选），否则用 `block_history_reduce` |
| `candidate_token_stack` | `:1894-1907` | 构造候选 token 栈：可选帖子哈希、作者哈希、SID token |
| `build_candidate_embeddings` | `:1909-1916` | 把 token 栈送入指定头的候选塔 |
| `build_candidate_embeddings_from_lookups` | `:1918-1949` | 构建索引时使用：从已查出的嵌入直接计算候选向量 |
| `__call__` | `:1951-1969` | 返回用户表示、各头的候选表示和统计量 |
| `candidate_representations` | `:1971-1988` | 为每个候选头各计算一次 |
| `user_representation` | `:1990-2128` | transformer → 平均池化或查询头池化 → L2 归一化 |
| `_query_heads_pool` / `_packed_query_heads` | `:2157-2215` | 带时间衰减的多查询头注意力池化 |
| `_mol_scorer` | `:2217-2252` | 构造 `MolScorer` 参数 |
| `forward` | `:2279-2600` | 服务前向：逐槽位打分并加权 → 掩码 → `top_k_by_key` |
| `loss` | `:2719-2815` | 每个头调用一次 `retrieval_loss` 后相加，再加激活 L2 正则 |
| `metrics` | `:2817-2851` | 召回率 @K 等指标 |

模块级函数：`retrieval_loss`（`:1035-1380`，采样 softmax 对比损失）、`positive_and_negative_masks`（`:675-702`）、`score_over_slots`（`:150-177`，按槽位加权求和）。

## 与排序模型的异同

| 维度 | 召回 `RecsysTwoTowerModel` | 排序 `RecsysAggregatedModel` |
|------|------|------|
| 序列 | 前缀 + 历史，无候选 | 前缀 + 历史 + 候选 |
| 用户 ID 嵌入 | 生产预设关闭 | 开 |
| 帖子 ID 嵌入 | 生产预设关闭，改用 SID + 作者 | 开，另加 SID |
| 输出 | 单位向量（或 4 个查询向量） | 64 个动作 logit + 8 个连续值 |
| 损失 | 对比损失 | 多标签 BCE + 连续损失 |
| 稠密优化器 | AdamW（`phoenix/TRAINING.md:21-25`） | 旗舰与 nano 用 Muon |

## 设计决策

| 决策 | 理由 |
|------|------|
| 用户塔直接复用排序模型类 | 两个模型共用主干与输入机制，只在输出头上不同（`phoenix/README.md:250-257`） |
| 混合头占用独立槽位 | 新目标（如 10-13 的 `ld`）不改动原有头的向量；服务时用权重调整比例 |
| MoL 打分可选 | 点积计算成本低；MoL 让一个用户能匹配多种兴趣。源码未写理由，此为推测 |

## 源码锚点

- `phoenix/xrex/models/recsys_two_tower_model.py:1610-2851` —— `RecsysTwoTowerModel`
- `phoenix/xrex/models/recsys_two_tower_model.py:2854-3150` —— `RecsysTwoTowerModelConfig`
- `phoenix/xrex/models/recsys_two_tower_model.py:180-394` —— 候选塔及其配置
- `phoenix/xrex/models/recsys_two_tower_model.py:710-1003` —— `MolScorer` 与服务端打分
- `phoenix/xrex/models/recsys_two_tower_model.py:1035-1380` —— `retrieval_loss`
- `phoenix/xrex/configs/xrecsys_two_tower.py:251-580` —— 预设

## 相关页面

- [Phoenix 召回：双塔检索架构](https://x-algorithm.vergego.ai/concepts/phoenix-retrieval/) —— 双塔召回原理
- [RecsysAggregatedModel（排序模型）](https://x-algorithm.vergego.ai/entities/recsys-model/) —— 用户塔复用的类
- [Phoenix 召回索引：rankall 与事件层](https://x-algorithm.vergego.ai/concepts/phoenix-rankall/) —— 索引语料的来源
- [Phoenix 训练](https://x-algorithm.vergego.ai/concepts/phoenix-training/) —— 训练与服务
- [Transformer 骨架](https://x-algorithm.vergego.ai/concepts/grok-transformer/) —— 用户塔的 transformer

Source: https://x-algorithm.vergego.ai/entities/recsys-retrieval-model/index.mdx
