这一页回答什么
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。
核心结论
- 单序列串联三段结构:模型将输入拼接为
[用户前缀 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)。 - 多路特征投影求和生成 token:系统为帖子与作者哈希、语义 ID、产品面、帖龄、时区、本地小时、关注关系以及互动计数分别配置嵌入表或投影矩阵,通过求和将各路表征合并为单张 D 维 token(
phoenix/xrex/models/recsys_feature_prep.py:1233-1330)。 - 候选注意力严格隔离:单条候选 token 仅对用户前缀、历史序列以及自身施加注意力计算。旗舰配置包含 8 层 transformer,末尾 2 层仅更新候选表征,历史序列状态在第 6 层计算完成后冻结(详见 候选隔离注意力掩码)。
- 输出结构划分双类预测头:模型输出 64 个离散动作 logit(槽位编号严格对应
ActionName枚举值),以及 8 个连续值槽位(包含停留时长、点击后停留时长、视频续看秒数与主页访问秒数等)(phoenix/xrex/configs/xrecsys.py:45-48、phoenix/python/common/xai-proto/proto/recsys.proto:699-708)。 - 停留时长自 2026-10-08 改用 CREAD 损失:算法在 30 个对数间隔阈值上计算分类损失,并叠加 Huber 还原误差。旗舰配置将旧有的 Tweedie 与 MAE 停留损失权重置为 0(
phoenix/xrex/models/loss_recsys.py:110-187、phoenix/xrex/configs/xrecsys.py:334-338)。 - 公开互动计数直接输入候选特征:候选 token 包含点赞、回复、转发、引用与浏览 5 项计数。若帖子发布时间超过约 14 天,系统将上述计数置零并添加陈旧标记(
phoenix/xrex/models/recsys_feature_prep.py:733-768、phoenix/crates/common/xai-recsys/src/util.rs:765-790)。 - 编排层消费 29 项模型预测字段:home-mixer 消费的预测字段覆盖上游 README 划分的五大类别(互动、点击、注意力、作者、负反馈)。源码另包含 README 未列出的 4 项预测字段(
xai-value-model/phoenix_scores.rs:1-32)。
输入:三段拼成一条序列
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 训练。
transformer 与输出头
RecsysAggregatedModel.__call__(phoenix/xrex/models/recsys_model.py:4312-4463):
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 骨架。 - 训练阶段启用序列打包时,模型依据
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):
- 生成对数阈值:从 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)。 - 多阈值二分类:模型针对每个阈值输出一个 logit,预测“真实停留时长 > 阈值”的概率,计算 30 个二元交叉熵损失的平均值(
:156-158)。 - 期望积分还原:算法将 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)。 - 过滤哨兵样本:真实停留为 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 槽位(推测) |
各行为预测概率的加权打分机制详见 打分与排序。
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——RecsysAggregatedModelphoenix/xrex/models/recsys_model.py:4312-4463——__call__前向与输出头phoenix/xrex/models/recsys_model.py:3452-3477——_decode_creadphoenix/xrex/models/recsys_model.py:4950-5090—— 离散与连续损失phoenix/xrex/models/loss_recsys.py:21-246—— 多热 BCE、CREAD、阈值二分类、Tweediephoenix/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(排序模型) —— 排序模型的代码构件
- 候选隔离注意力掩码 —— 掩码与只更新候选的末尾层
- Transformer 骨架 —— 注意力、RoPE、FFN
- 哈希嵌入 —— ID 哈希与统一嵌入表
- Phoenix 训练 —— 数据、优化器、合成数据与服务加载
- Phoenix 召回:双塔检索架构 —— 上游召回
- 打分与排序 —— 预测如何加权成最终分