---
title: "Phoenix 排序模型"
description: "生产版 Phoenix 排序：[用户前缀 | 历史 | 候选] 一条序列，64 个离散动作概率加连续值头，停留时长用 CREAD 损失"
---

> 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.

# Phoenix 排序模型

## 这一页回答什么

Phoenix 排序模型将用户特征、交互历史与一批候选帖子拼接为单条序列输入。模型经过 transformer 编码后，为每条候选帖子预测多项离散行为概率以及停留时长等连续值。编排服务 home-mixer 的 `PhoenixScorer` 组件提取这些预测值计算最终排序得分。

> 2026-08-13 起，官方仓库删除了 `phoenix/` 目录下 Grok-1 的演示代码（`recsys_model.py`、`grok.py`、`run_pipeline.py`），替换为生产训练与在线服务代码，核心实现位于 `phoenix/xrex/`（`phoenix/README.md:9-21`）。本页所有结论均基于新版代码。旧版 `PhoenixModel` 类已废弃，当前生产排序模型实现类为 `RecsysAggregatedModel`。

## 核心结论

1. **单序列串联三段结构**：模型将输入拼接为 `[用户前缀 token | 历史 S | 候选 C]`。旗舰配置 `home_direct_packed` 包含 2 个用户前缀 token（用户 ID token 与用户画像 token）、1022 个历史 token 与 64 个候选 token，前缀与历史合计 1024 个 token（`phoenix/xrex/configs/xrecsys.py:316-319`、`:56-68`）。
2. **多路特征投影求和生成 token**：系统为帖子与作者哈希、语义 ID、产品面、帖龄、时区、本地小时、关注关系以及互动计数分别配置嵌入表或投影矩阵，通过求和将各路表征合并为单张 D 维 token（`phoenix/xrex/models/recsys_feature_prep.py:1233-1330`）。
3. **候选注意力严格隔离**：单条候选 token 仅对用户前缀、历史序列以及自身施加注意力计算。旗舰配置包含 8 层 transformer，末尾 2 层仅更新候选表征，历史序列状态在第 6 层计算完成后冻结（详见 [候选隔离注意力掩码](https://x-algorithm.vergego.ai/concepts/candidate-isolation-masking/)）。
4. **输出结构划分双类预测头**：模型输出 64 个离散动作 logit（槽位编号严格对应 `ActionName` 枚举值），以及 8 个连续值槽位（包含停留时长、点击后停留时长、视频续看秒数与主页访问秒数等）（`phoenix/xrex/configs/xrecsys.py:45-48`、`phoenix/python/common/xai-proto/proto/recsys.proto:699-708`）。
5. **停留时长自 2026-10-08 改用 CREAD 损失**：算法在 30 个对数间隔阈值上计算分类损失，并叠加 Huber 还原误差。旗舰配置将旧有的 Tweedie 与 MAE 停留损失权重置为 0（`phoenix/xrex/models/loss_recsys.py:110-187`、`phoenix/xrex/configs/xrecsys.py:334-338`）。
6. **公开互动计数直接输入候选特征**：候选 token 包含点赞、回复、转发、引用与浏览 5 项计数。若帖子发布时间超过约 14 天，系统将上述计数置零并添加陈旧标记（`phoenix/xrex/models/recsys_feature_prep.py:733-768`、`phoenix/crates/common/xai-recsys/src/util.rs:765-790`）。
7. **编排层消费 29 项模型预测字段**：home-mixer 消费的预测字段覆盖上游 README 划分的五大类别（互动、点击、注意力、作者、负反馈）。源码另包含 README 未列出的 4 项预测字段（`xai-value-model/phoenix_scores.rs:1-32`）。

## 输入：三段拼成一条序列

```mermaid
flowchart LR
    U["用户前缀 ×2<br/>用户与 IP 哈希 / 画像特征"] --> SEQ
    H["历史 ×1022<br/>帖+作者+SID+动作+停留+上下文"] --> SEQ
    C["候选 ×64<br/>帖+作者+SID+互动计数+帖龄+上下文"] --> SEQ
    SEQ["拼接成 B × 1088 × 2560"] --> T["Transformer 8 层"]
```

`build_feature_prep_streams` 依次构造三段特征，`_assemble_unpacked` 执行拼接并返回候选段起始偏移量 `candidate_start_offset = 用户前缀长度 + 历史长度`（`phoenix/xrex/models/recsys_feature_prep.py:1233-1342`）。

| 段 | 构造函数 | 加进 token 的成分（旗舰 `home_direct_packed` 打开的项） |
|----|---------|------|
| 用户 ID token | `_build_user_token`（`recsys_feature_prep.py:502-531`） | 用户 2 个哈希嵌入、IP 2 个哈希嵌入，各自投影后相加 |
| 用户画像 token | `_build_user_features_token`（`:534`） | 国家、语言、经纬度（正弦编码）、性别、年龄段；`enable_user_installed_apps=False`（`xrecsys.py:353-359`） |
| 历史 token | `_build_seq_tokens`、`_add_context_features`、`_add_sid_features`、`_add_history_features`（`:1134`、`:626`、`:868`、`:925`） | 帖/作者哈希、语义 ID、产品面、帖龄、时区、本地小时、关注关系、互动计数、**动作多热向量**、**停留时长分桶**、点击后停留、首页续看/主页访问秒数、距本次请求的时间间隔 |
| 候选 token | 同上，最后一步换成 `_add_candidate_features`（`:1075`） | 帖/作者哈希、语义 ID、产品面、帖龄、时区、本地小时、关注关系、互动计数、陈旧标记、距最近一次历史曝光的时间间隔 |

时间间隔与首页续看/主页访问历史只在 `home_direct_packed` 打开（`xrecsys.py:554-559`）。

### 历史里的动作与停留时长

- **动作表征**：系统为历史中的每条帖子生成 64 维多热向量（用户触发的动作置为 1），乘以 `[64, D]` 维度权重表后累加至 token 向量（`recsys_feature_prep.py:933-940`）。代码已移除将 0/1 映射为 ±1 的旧式逻辑；全零动作向量通过 `action_mask` 清零。
- **停留时长分桶表**（10-08 新增，`enable_dwell_bucket_table=True`）：历史帖子的停留秒数映射至“0 与 30 个对数阈值”构成的网格节点。算法通过线性插值融合相邻两行嵌入；针对 2.0 秒哨兵值，系统依据“该帖子是否包含负反馈”映射至两行专用嵌入之一（`recsys_feature_prep.py:942-974`，`DWELL_TIME_DEFAULT_SENTINEL_SEC = 2.0` 见 `:428`）。开源默认参数为：`dwell_bucket_cap=240.0`、`dwell_bucket_log_min_threshold=2.0`、`dwell_bucket_num_thresholds=30`（`xrecsys.py:364-367`）。

### 帖龄、互动计数与陈旧帖

- **帖龄编码**：曝光时间减去发帖时间，按 60 分钟划分区间，上限设为 `POST_AGE_MAX_MINUTES = 4800`（`recsys_feature_prep.py:51-70`、`:644-653`）。
- **互动计数特征**：算法对点赞、回复、转发、引用与浏览 5 项计数分别计算 `log2(x+1)/32`。数值经过隐藏层宽度为 64 的 GELU MLP 投影后累加至 token 向量（`recsys_feature_prep.py:32-39`、`:733-761`）。帖子既有公开热度由此直接构成排序输入。
- **陈旧帖子标记**：服务端组装候选序列时，若帖子发布时长超出 `STALE_POST_14D_TTL_SEC = 1_213_200` 秒（约 14.04 天），系统将 5 项互动计数重置为 0，并设置 `is_stale_post` 状态位（`phoenix/xrex/data/recsys/feature_config.py:87-88`、`phoenix/crates/common/xai-recsys/src/util.rs:765-790`）。系统另提供 30 天配置 `STALE_POST_30D_TTL_SEC = 2_595_600`，通过 `enable_stale_post_30d` 控制，开源默认处于关闭状态。

### 语义 ID（SID）

系统为每条帖子输入 6 层、每层 256 码的残差量化编码（由帖子多模态嵌入计算生成），并追加一层基于帖子哈希派生的“哈希层”。各层查表求和后投影至 D 维空间（`phoenix/xrex/models/recsys_model.py:1714-1835`）。排序模型旗舰配置设为 `sid_cross_attn=False`，召回模型配置设为 `True`（`xrecsys.py:343-348`）。语义 ID 的生成来源详见 [Phoenix 训练](https://x-algorithm.vergego.ai/concepts/phoenix-training/)。

## transformer 与输出头

`RecsysAggregatedModel.__call__`（`phoenix/xrex/models/recsys_model.py:4312-4463`）：

```python
model_out = self.model(input_embeddings, attn_mask, ..., candidate_start=candidate_start)
out_embeddings = rms_norm_fn(model_out.output, name="final_layer_norm")
out_embeddings = out_embeddings[:, candidate_start_offset:, :]       # 非打包时只取候选段
logits = self.decode(out_embeddings)                                  # [B, C, 64]
continuous_predictions, head_logits_by_index = self.decode_continuous(out_embeddings, product_surface, ...)
```

- 模型骨架采用 `xrex/models/transformer.py` 中的 `Transformer` 实现。旗舰配置包含 8 层，设置 `emb_size=2560`、20 个查询头、4 个 KV 头与 `key_size=128`（`xrecsys.py:316-324`、`:1024-1062`）。架构细节详见 [Transformer 骨架](https://x-algorithm.vergego.ai/concepts/grok-transformer/)。
- 训练阶段启用序列打包时，模型依据 `seqpack_layout.candidate_positions` 提取候选位置，避免切片错位（`recsys_model.py:4394-4406`）。
- **离散动作预测头**：输出层包含 64 个槽位，由 `ACTION_TYPE_MAP_LEN = 60` 向上取整至 `OUTPUT_VOCAB_K = 64`（`xrecsys.py:45-48`、`:154-156`）。训练数据依据 `ActionName` 枚举值填充多热标签（`phoenix/xrex/data/parquet_recsys.py:1925`）。第 1 槽位对应点赞（`SERVER_TWEET_FAV = 1`），第 11 槽位对应“停留达标”（`CLIENT_TWEET_RECAP_DWELLED = 11`），其余槽位依次类推（`recsys.proto:456-570`）。端到端测试客户端直接通过 `logits[ActionName.SERVER_TWEET_FAV]` 读取点赞预测概率（`phoenix/reference/retrieve_then_rank.py:152-160`）。
- **连续值预测头**：输出层包含 8 个槽位，编号源自 `ContinuousActionName`：0 为主页访问秒数、1 为停留时长、2 为点击后停留时长、3 为首页视频续看秒数、4 为 bridge 概率，5 至 7 供广告业务使用（`recsys.proto:699-708`）。CREAD 槽位额外配置一个 `[D, 30]` 维度的阈值分类头。

## 训练目标

### 离散动作：多标签 BCE

模型为 64 个槽位分别计算 sigmoid 二元交叉熵，结合掩码求和后完成归一化（`phoenix/xrex/models/loss_recsys.py:21-54`，调用位置见 `recsys_model.py:5033-5041`）。损失权重包含三项调节策略：

- 浏览类损失权重减半：旗舰配置设 `view_loss_weight=0.5`，降低浏览动作的损失比重（`xrecsys.py:551`、`recsys_model.py:5011-5016`）。
- log-Q 样本校正：针对批次内采样的负样本，启用 `log_q_correction=True`，以候选帖子在批次内的出现频次倒数作为样本权重（`recsys_model.py:1523-1538`、`:4485-4495`、`:4558-4559`）。
- 负样本负反馈屏蔽：负样本上的负反馈类预测头不参与损失计算（`mask_neg_feedback_on_negatives=True`，`recsys_model.py:1312`、`:4990-4994`）。

### 连续值：停留时长的 CREAD 损失（2026-10-08）

旗舰配置同时登记 5 条停留相关损失，通过权重配置决定实际生效规则（`xrecsys.py:862-924`）：

| `metric_name` | 损失类型 | 适用产品面 | `norm_scale`（秒） | 旗舰权重（开源默认值） |
|---|---|---|---|---|
| `dwell-time-tweedie` | Tweedie p=1.5 | 仅图集页 | 300 | 0.0（`xrecsys.py:335`） |
| `dwell-time-mae` | MAE | 图集页以外 | 30 | 0.0（`xrecsys.py:336`） |
| `click-dwell-binary` | 是否超过 10 秒 | 全部 | 60 | 1.0（`xrecsys.py:334`） |
| `dwell-time-cread` | CREAD | 图集页以外 | 120 | 0.1（`xrecsys.py:337`） |
| `dwell-time-cread-immersive` | CREAD | 仅图集页 | 240 | 0.1（`xrecsys.py:338`） |

`validate_continuous_action_losses` 限制同一槽位与同一产品面不得同时存在两条权重大于 0 的损失配置（`recsys_model.py:418-431`）。上游提交 `35650fb`（2026-10-08）将 Tweedie 与 MAE 的固定权重 0.1 改为可配置项并在旗舰配置中置 0，同时引入两条 CREAD 损失。

CREAD 损失计算包含四个步骤（`loss_recsys.py:110-187`、`recsys_model.py:3452-3477`）：

1. **生成对数阈值**：从 2 秒至 `norm_scale`，算法按等比数列提取 30 个阈值。当 `norm_scale=120` 秒时，阈值序列约为 2.0、2.3、2.65 直至 90.48、104.2、120.0 秒（`cread_log_thresholds`，`loss_recsys.py:110-125`）。
2. **多阈值二分类**：模型针对每个阈值输出一个 logit，预测“真实停留时长 > 阈值”的概率，计算 30 个二元交叉熵损失的平均值（`:156-158`）。
3. **期望积分还原**：算法将 30 个 sigmoid 概率乘以对应区间宽度后累加求和，估算连续预测秒数（`recsys_model.py:3472-3476`）。预测值与截断后的真实值计算 Huber 损失（`delta = 5 秒 / norm_scale`），权重系数配置为 `cread_restoration_weight=0.5`（`loss_recsys.py:159-185`、`recsys_model.py:5080-5083`）。
4. **过滤哨兵样本**：真实停留为 0 或 2.0 秒哨兵值的样本不计入损失（`recsys_model.py:5053-5058`）。

在线推理时，连续头直接输出步骤 3 计算的还原秒数；home-mixer 获取的 `dwell_time` 得分由此生成。

旗舰配置另为首页视频续看秒数（权重 0.013）与主页访问秒数（权重 0.0035）分别配置 CREAD 损失：设置 `norm_scale=300`，仅在首页两个产品面生效，且仅在前置行为（打开视频或访问主页）触发时计入损失（`xrecsys.py:271-313`、`:552-553`）。

## 预测的行为全集：与上游 README 对照

home-mixer 获取 `PhoenixScores` 数据结构（`xai-value-model/phoenix_scores.rs:1-32`）。从 `ActionName` 槽位到具体字段的映射逻辑封装于 `xai_candidate_pipeline` 的 `phoenix_prediction_client`，该文件源码未包含，下表依据字段名称建立对应关系。

| README 类别 | README 列出的行为 | `PhoenixScores` 字段 | 核对结果 |
|---|---|---|---|
| Engagement | favorite · reply · repost · quote · share · share via DM · share via copy link | `favorite_score` `reply_score` `retweet_score` `quote_score` `share_score` `share_via_dm_score` `share_via_copy_link_score` | 7 项都在 |
| Clicks | post · profile · link · photo expand · video open · quoted post | `click_score` `profile_click_score` `open_link_score` `photo_expand_score` `video_open_score` `quoted_click_score` | 6 项都在 |
| Attention | video quality view · dwell · dwell time · click dwell time · video continuation seconds · profile visit seconds | `vqv_score` `dwell_score` `dwell_time` `click_dwell_time` `home_video_continuation_secs` `home_profile_visit_secs` | 6 项都在；后两项只在 `EnableHomeExcursionScores` 打开时从连续头读出，开源默认 `true`（`home-mixer/scorers/phoenix_scorer.rs:131-140`、`:171-183`，`home-mixer/params/param.rs:309-313`） |
| Author | follow author | `follow_author_score` | 在 |
| Negative | not interested · mute author · block author · report · not dwelled | `not_interested_score` `mute_author_score` `block_author_score` `report_score` `not_dwelled_score` | 5 项都在 |
| README 未列 | — | `quoted_vqv_score` `post_unexplored_score` `post_video_score` `grok_analyze_score` | 源码有、README 没写。前两个在打分里有条件权重（`xai-value-model/scoring.rs:90-100`）；后两个按名称对应 `SERVER_TWEET_POST_VIDEO = 44`、`CLIENT_TWEET_CLICK_GROK_ANALYZE = 46` 槽位（推测） |

各行为预测概率的加权打分机制详见 [打分与排序](https://x-algorithm.vergego.ai/concepts/scoring-and-ranking/)。

## home-mixer 怎样调用排序模型

`PhoenixScorer`（`home-mixer/scorers/phoenix_scorer.rs`）负责向排序服务发起请求并解析响应：

- **调用条件**：存在缓存候选时不触发排序；开关 `disable_home_mixer_phoenix_ranker` 开启时整体停用排序调用（`phoenix_scorer.rs:25`、`:73-82`）。
- **产品面划分**：关注流请求指定 `HomeTimelineRankedFollowing`，其余请求指定 `HomeTimelineRanking`（`:90-94`）。
- **集群路由策略**：默认路由至 `Experiment1Fou`；当用户历史动作数量低于 `PhoenixRankerNewUserHistoryThreshold` 时分流至新用户集群。开源默认阈值为 0，即默认不执行分流（`:32-68`，`param.rs:56-60`、`:294-298`）。
- **整页解码重排**：开启 `EnablePageDecodeRerank` 时，请求携带 `page_decode` 参数，模型额外输出整页精选序列（`:105-127`），开源默认配置为 `false`（`param.rs:327-331`）。对应训练实现在 `xrex/models/page_decoder.py` 与 `page_rl.py`，仅在 `immersive_page_rl*` 配置中启用（`xrecsys.py:446-487`）。
- **额外结果获取**：除 `phoenix_scores` 外，系统同时读取 `backbone_scores` 与 `served_slate_context`（`:136-150`）。

## 配置速查（开源默认值）

| 项 | `home_direct_packed`（旗舰） | `home_direct_packed_nano`（单卡） |
|---|---|---|
| `emb_size` / 层数 | 2560 / 8 | 512 / 4 |
| 查询头 / KV 头 | 20 / 4 | 4 / 2 |
| 嵌入表宽 | 1024 | 128 |
| 历史 / 候选长度 | 1022 / 64 | 1022 / 64 |
| 只更新候选的末尾层 | 2 | 0 |
| 用户 / 帖 / 作者 / IP 词表 | 1 亿 / 1 亿 / 3000 万 / 1000 万 | 10 万 / 10 万 / 3 万 / 0 |
| 每实体哈希数 | 2 | 2 |
| 稠密优化器 | Muon | Muon |

出处：`xrecsys.py:316-387`、`:532-577`、`:599-622`。

## 设计决策

| 决策 | 理由 |
|------|------|
| 三段拼一条序列，候选互相隔离 | 候选直接注意用户与历史；单条候选的分数不受同批其它候选影响，可缓存 |
| 特征"各自投影再相加"成一个 token | 加新特征只需加一张表或一个投影，序列长度不变 |
| 停留时长改用 CREAD | 停留分布长尾，先分类再还原比直接回归秒数更稳。源码未写选型理由，此为推测 |
| 陈旧帖清零互动计数 | 防止老帖靠累计热度占优。源码未写理由，此为推测 |
| 末尾层只更新候选 | 历史在前 6 层后冻结，后 2 层只算 64 个候选 token，省算力 |

## 源码锚点

- `phoenix/xrex/models/recsys_model.py:2482` —— `RecsysAggregatedModel`
- `phoenix/xrex/models/recsys_model.py:4312-4463` —— `__call__` 前向与输出头
- `phoenix/xrex/models/recsys_model.py:3452-3477` —— `_decode_cread`
- `phoenix/xrex/models/recsys_model.py:4950-5090` —— 离散与连续损失
- `phoenix/xrex/models/loss_recsys.py:21-246` —— 多热 BCE、CREAD、阈值二分类、Tweedie
- `phoenix/xrex/models/recsys_feature_prep.py:502-1342` —— 三段 token 的构造
- `phoenix/xrex/configs/xrecsys.py:316-622`、`:862-924` —— 旗舰与 nano 配置、连续损失清单
- `xai-value-model/phoenix_scores.rs:1-32` —— home-mixer 收到的预测字段
- `home-mixer/scorers/phoenix_scorer.rs:32-183` —— 调用方

## 相关页面

- [RecsysAggregatedModel（排序模型）](https://x-algorithm.vergego.ai/entities/recsys-model/) —— 排序模型的代码构件
- [候选隔离注意力掩码](https://x-algorithm.vergego.ai/concepts/candidate-isolation-masking/) —— 掩码与只更新候选的末尾层
- [Transformer 骨架](https://x-algorithm.vergego.ai/concepts/grok-transformer/) —— 注意力、RoPE、FFN
- [哈希嵌入](https://x-algorithm.vergego.ai/concepts/hash-based-embeddings/) —— ID 哈希与统一嵌入表
- [Phoenix 训练](https://x-algorithm.vergego.ai/concepts/phoenix-training/) —— 数据、优化器、合成数据与服务加载
- [Phoenix 召回：双塔检索架构](https://x-algorithm.vergego.ai/concepts/phoenix-retrieval/) —— 上游召回
- [打分与排序](https://x-algorithm.vergego.ai/concepts/scoring-and-ranking/) —— 预测如何加权成最终分

Source: https://x-algorithm.vergego.ai/concepts/phoenix-ranking/index.mdx
