X 的产品推荐算法

本文内容撰写存在 AI 辅助

开源项目地址

第 1 章:全景概览

本章目标:快速建立 X 推荐系统的模块边界、职责分工与核心设计哲学。

1 四大子系统角色与关系

子系统 主语言 主要职责 在请求链路中的位置
home-mixer Rust 在线编排:接收请求、组织流水线、返回最终 Feed 请求入口与总调度
candidate-pipeline Rust 可复用流水线框架(Trait 抽象) 执行引擎
thunder Rust Kafka 事件消费 + 内存索引,提供站内候选 in-network 召回
phoenix Python (JAX/Haiku) 召回(双塔)与排序(Transformer)模型 out-of-network 召回 + 预测打分

[!tip] 术语首现

  • For You 编排层(For You Orchestration Layer):这里对应 home-mixer
  • 站内候选(In-Network):来自关注关系内容。
  • 站外候选(Out-of-Network, OON):由模型发现的非关注内容。

2 系统架构总览

3 核心设计哲学

  1. 执行与策略解耦(Execution-Policy Decoupling)

    • 执行顺序由 candidate-pipeline 提供,业务策略由 home-mixer 装配。
    • 好处:策略变更不改引擎;多业务线复用同一框架。
    • 若不解耦:业务逻辑会散落在执行代码里,演进成本高。
  2. Trait 可插拔(Pluggable Traits)

    • Source/Hydrator/Filter/Scorer/Selector/SideEffect 统一接口。
    • 好处:新增策略仅需实现 Trait + 注册组件。
    • 若不抽象:阶段逻辑硬编码,A/B 试验成本高。
  3. 全链路可降级(Graceful Degradation)

    • Query Hydrator / Source / Scorer 失败多为“记录错误并继续”;Filter 失败回退到备份候选。
    • 好处:高可用优先,避免局部依赖抖动导致全请求失败。
    • 若无降级:单点依赖故障会直接打穿 SLA。

本章结论:系统通过“编排层 + 可插拔流水线 + 降级策略”实现了高可演进、可用性优先的在线推荐架构。


第 2 章:一次请求的完整旅程

本章目标:按真实执行顺序串起一次 For You 请求,从输入到返回形成主线心智模型。

1 用户上下文补全(Query Hydration)

candidate-pipeline 首先执行 hydrate_query(),并行拉取用户上下文,再顺序合并。

1.1 补全内容

  • 用户行为序列(User Action Sequence, UAS)UserActionSeqQueryHydrator (典型动作包括:点赞、回复、转发/引用、点击、个人页点击、视频观看、分享、停留、关注作者、不感兴趣、拉黑、静音、举报等)
  • 用户特征(User Features)UserFeaturesQueryHydrator(关注/屏蔽/订阅/屏蔽词)

1.2 补全方式

  • 并行请求:join_all 扇出,同时触发多个下游调用
  • 顺序合并:各 hydrator 只回写自己负责字段 ,然后合并(update)
  • 失败降级:单模块失败不终止请求,推荐场景能够容忍个别字段的缺失

[!question] 为什么“先部分补全,再 update”?

  • 并发安全:避免多个任务同时写同一 query
  • 失败隔离:某个 hydrator 失败不污染其他结果。
  • 职责清晰:hydrate=取数update=合并

2 候选获取(Source)

Source 阶段并行拉候选,当前核心是 Thunder(站内)+ Phoenix(站外)。

  • Thunder 在内存中维护“可推荐帖子池”,并根据关注关系和发帖排序来进行站内召回候选
  • Phoenix 计算用户行为向量,在全网内容向量库(离线计算)中进行相似检索,召回站外候选
  • in_network_only=true:只跑 Thunder,禁用 Phoenix。
  • in_network_only=false:两路并行拉取并合并候选池。

[!tip] 设计动机 并行拉取将总耗时逼近 max(T_thunder, T_phoenix);若串行,延迟近似相加。

3 候选补全(Candidate Hydration)

Source 只给“轻候选”,Candidate Hydrator 再补推文/作者/视频等细节:

  • InNetworkCandidateHydrator:标记是否 in-network
  • CoreDataCandidateHydrator:推文核心数据(作者、文本、转推/回复关系)
  • VideoDurationCandidateHydrator:视频时长
  • SubscriptionHydrator:订阅作者信息
  • GizmoduckCandidateHydrator:作者资料(粉丝数、用户名)

[!question] 为什么不在 Query 阶段一次补齐? Query 阶段还没有候选 tweet_id 列表;候选级补全必须等 Source 产出候选后才能做。

4 过滤(Filter)

Filter 串行执行,每个过滤器输出 kept/removed;失败则回退到该过滤器前的备份候选。

常见规则:

  • 去重:DropDuplicatesFilterRetweetDeduplicationFilter
  • 内容有效性:CoreDataHydrationFilterAgeFilter
  • 用户侧约束:SelfTweetFilterAuthorSocialgraphFilterMutedKeywordFilter
  • 会话状态约束:PreviouslySeenPostsFilterPreviouslyServedPostsFilter
  • 订阅权限约束:IneligibleSubscriptionFilter

5 打分(Scorer)

按顺序执行:

  1. PhoenixScorer:多行为概率预测
  2. WeightedScorer:行为加权聚合
  3. AuthorDiversityScorer:同作者重复衰减
  4. OONScorer:站外分数系数调整

加权核心公式: $$ \text{weighted\_score}=\sum_i w_i \cdot P(\text{action}_i) $$

作者衰减公式: $$ \text{multiplier}=(1-\text{floor})\cdot \text{decay}^{\text{pos}}+\text{floor} $$

[!note] OON 调整语义 OONScorer分数倾斜(乘系数),不是硬配额;是否入选仍取决于整体排序结果。

[!warning] 参数裁剪 行为权重与 OON 系数来自 home-mixer/params,该模块未公开。 OON 系数默认保持站内外的帖子占比为 1:1

<!-- TODO: 具体权重常量 -->

6 选择与后过滤

先由 TopKScoreSelector 按分数排序截断,再执行后置 Hydrator/Filter:

  • Post-selection Hydrator:VFCandidateHydrator,可见性审查结果(安全/违规/删除等)
  • Post-selection Filter:VFFilterDedupConversationFilter,同一会话链路里只保留更优那条,避免刷屏式重复;同一会话链路,可以理解为围绕同一个对话线程(conversation)展开的一串推文关系(回复,转推等)

[!question] 为什么部分过滤放在 Selector 之后? VF 等检查成本高,放后置只处理入围 Top-K,显著降低全量候选开销。

本章结论:一次请求的主路径是“并行取数 + 串行决策”,并通过阶段化降级在稳定性与效果之间取得平衡。


第 3 章:流水线框架(candidate-pipeline)

本章目标:解释通用流水线如何用 Trait 抽象执行机制,并让业务模块可插拔。

1 Trait 体系与 enable() 条件启用

框架定义了六类核心 Trait:

  • QueryHydrator<Q>
  • Source<Q, C>
  • Hydrator<Q, C>
  • Filter<Q, C>
  • Scorer<Q, C>
  • Selector<Q, C>

每个 Trait 默认有 enable(),支持基于 query 条件启停(如 in_network_only)。

每个 Trait 的含义,可参考第二章的每小节内容

2 标准执行顺序

candidate_pipeline.rs 的固定顺序:

  1. QueryHydrator 查询补齐
  2. Source 粗召回(站内/站外)
  3. Hydrator 候选补齐
  4. Filter 过滤
  5. Scorer 评分
  6. Selector 排名
  7. PostSelectionHydrator 后补齐
  8. PostSelectionFilter 后过滤
  9. SideEffect(异步,不阻塞主返回)

这使“执行机制稳定、业务策略可替换”成为可能。

3 业务组装示例

home-mixer/candidate_pipeline/phoenix_candidate_pipeline.rs 展示了如何把策略装入框架:

  • Query Hydrators:用户行为序列 + 用户特征
  • Sources:Thunder + Phoenix
  • Filters/Scorers/Selector:策略链

本章结论:candidate-pipeline 用固定执行骨架承载可插拔策略,降低了功能迭代与实验成本。


第 4 章:站内召回(Thunder)

本章目标:拆解 Thunder 的“离线准备 + 在线检索”双链路,以及三层过滤细节。

1 数据准备链路:Kafka → 反序列化 → 内存索引

核心流程:

  1. 消费推文创建/删除事件(tweet_events_listener*.rs
  2. 将 kafak 消息按约定协议反序列化为可读的事件(deserializer.rs
  3. 映射为 LightPost(发帖事件) / TweetDeleteEvent(删帖行为)
  4. 更新 PostStore 三类索引,每个作者可配置不同的“索引上限”,来控制延迟和开销:
    • original_posts_by_user:每个作者的“原创推文时间线”
    • secondary_posts_by_user:每个作者的“次级内容时间线”(回复/转推)
    • video_posts_by_user:每个作者的“视频时间线”(包含转推,但不包含回复)

2 在线召回链路:限流 → 查索引 → 过滤 → 排序截断

thunder_service.rs::get_in_network_posts

  1. Semaphore 并发限流
  2. 参数约束(列表截断、max_results 默认)
  3. 读取 PostStore
  4. created_at 倒序 + Top-N 返回

3 三层过滤逐层展开

3.1 第一层:请求入口层(Service)

  • 限制 following_user_idsexclude_tweet_ids 输入规模
  • 可选从 Strato (在线特征与状态数据的读写后端)拉关注列表
  • 早期限流,避免过载

3.2 第二层:PostStore 查询层(核心)

  • 仅扫描关注作者
  • 每作者扫描上限 MAX_TINY_POSTS_PER_USER_SCAN
  • 过滤 exclude_tweet_ids、已删除推文
  • 过滤“转推自己内容”
  • secondary 帖子附加会话约束(回复链合法性)
  • 每作者返回上限(original/reply/video 分别配置)

3.3 第三层:排序截断层

  • 全量候选按时间倒序
  • 截断到 max_results

[!tip] 设计动机 把高频过滤前置到内存索引层,可在“低延迟 + 高并发”下维持可控资源消耗。

[!warning] 缺失模块 thunder/config.rsthunder/metrics.rsthunder/strato_client.rs 在当前快照缺失。

<!-- TODO: Thunder 配置与指标完整定义 -->

本章结论:Thunder 通过事件驱动内存索引和分层过滤,实现了高吞吐、低延迟的站内候选供给。


第 5 章:站外召回与双塔模型(Phoenix Retrieval)

本章目标:说明 Phoenix 站外召回触发条件、双塔机制与 Top-K 质量保障。

1 PhoenixSource 触发流程

触发条件(home-mixer/sources/phoenix_source.rs):

  • in_network_only == false
  • user_action_sequence 可用(否则报错并由上游降级)

2 双塔原理(Two-Tower)

双塔模型是目前最主流的向量召回方法:

设用户向量为 $\mathbf{u}$,候选向量为 $\mathbf{v}$,相似度可写为: $$ \text{score} = \mathbf{u}\cdot\mathbf{v} $$

本项目代码中:

  • 用户塔:PhoenixRetrievalModel.build_user_representation()(Transformer 编码 user+history)
  • 候选塔:CandidateTower(MLP 投影 + L2 归一化)
  • 检索:_retrieve_top_k() 计算相似度并取 Top-K

测试保证(test_recsys_retrieval_model.py):

  • 索引合法性0 <= index < corpus_size
  • 分数有序性:Top-K 分数单调不升

3 工程保障(离线/增量/ANN/分片)

[!note] 代码与生产差异 当前开源代码在检索示例中使用全量矩阵乘 + top_k,属于可读性优先实现。

生产级常见保障通常包括:

  • 候选向量离线全量重算 + 近实时增量
  • ANN 索引(HNSW/IVF/PQ 等)
  • 分片并行检索与多路 merge

本章结论:Phoenix 站外召回在理论上采用双塔 + Top-K,开源代码展示了核心算法形态,生产工程细节留待补全。


第 6 章:排序模型(Phoenix Ranker)

本章目标:完整拆解排序模型输入、结构、掩码机制、输出语义与测试方法。

1 模型架构总览

2 输入详解:RecsysBatch + RecsysEmbeddings

2.1 A. RecsysBatch(hash/离散特征)

字段 形状 含义
user_hashes [B, Hu] 用户多哈希 ID(0 常作 padding)
history_post_hashes [B,S,Hi] 历史内容哈希
history_author_hashes [B,S,Ha] 历史作者哈希
history_actions [B,S,A] 历史行为多热向量
history_product_surface [B,S] 历史场景离散特征
candidate_post_hashes [B,C,Hi] 候选内容哈希
candidate_author_hashes [B,C,Ha] 候选作者哈希
candidate_product_surface [B,C] 候选场景离散特征

2.2 B. RecsysEmbeddings(查表向量)

字段 形状 含义
user_embeddings [B,Hu,D] 用户 hash 查表向量
history_post_embeddings [B,S,Hi,D] 历史内容向量
candidate_post_embeddings [B,C,Hi,D] 候选内容向量
history_author_embeddings [B,S,Ha,D] 历史作者向量
candidate_author_embeddings [B,C,Ha,D] 候选作者向量
# phoenix/recsys_model.py
embeddings = jnp.concatenate([user_embeddings, history_embeddings, candidate_embeddings], axis=1)
padding_mask = jnp.concatenate([user_padding_mask, history_padding_mask, candidate_padding_mask], axis=1)
candidate_start_offset = user_padding_mask.shape[1] + history_padding_mask.shape[1]

[!question] 为什么 hash 和 embedding 要同时输入?

  • embedding 负责语义表示;
  • hash 负责身份/有效位控制(如 padding mask)、追踪与调试;
  • 二者分离可兼容“离线查表/在线查表”不同工程路径。

3 Transformer 骨架(grok.py

主干组件:RMSNorm 、MHA(多头注意力)、FFN(前馈网络)、RoPE

结构路径:DecoderLayer 里先 Attention 再 FFN,均带残差与归一化

4 推荐专用注意力掩码(Candidate Isolation Mask)

候选隔离掩码(Candidate Isolation Mask)

  • 也称推荐场景专用注意力掩码,确保候选之间不能相互“泄漏信息”,让模型关注与目标用户与候选之间的信息交换与评估;该掩码能显著提高推荐模型的线上稳定性与可解释性
  • 实现在 phoenix/grok.py,但其生效依赖流水线输入组织:框架先构建 [user, history, candidates] 序列,再将 candidate_start_offset 传入 Transformer。

规则:

  1. User+History 区域:保留因果注意力;
  2. Candidate 区域:每个候选可看 User+History;
  3. Candidate 之间互不可见,仅允许看自己(对角线)。
# phoenix/grok.py
causal_mask = jnp.tril(jnp.ones((1, 1, seq_len, seq_len), dtype=dtype))
attn_mask = causal_mask.at[:, :, candidate_start_offset:, candidate_start_offset:].set(0)
candidate_indices = jnp.arange(candidate_start_offset, seq_len)
attn_mask = attn_mask.at[:, :, candidate_indices, candidate_indices].set(1)

这样设计的收益:

  • 候选分数对“同批其他候选”不敏感,在线表现更稳定;
  • 批内增删候选不会引入强耦合偏差。

若不这么做,候选之间会相互影响,导致分数不可比与线上波动。

5 输出定义

模型输出:

  • logits 形状:[B, C, A]
  • 推理时通常经 sigmoid 转为多行为概率
# phoenix/runners.py
output = hk_forward(batch, recsys_embeddings)
probs = jax.nn.sigmoid(output.logits)

6 行为类型列表(正向 + 负向)

可见来源:home-mixer/scorers/phoenix_scorer.rsActionNameContinuousActionName 的映射。

序号 行为字段 代码映射 属性 中文解释
1 favorite_score ServerTweetFav 正向 点赞
2 reply_score ServerTweetReply 正向 回复
3 retweet_score ServerTweetRetweet 正向 转推
4 photo_expand_score ClientTweetPhotoExpand 正向 展开查看图片
5 click_score ClientTweetClick 正向 点击推文详情
6 profile_click_score ClientTweetClickProfile 正向 点击作者头像/主页
7 vqv_score ClientTweetVideoQualityView 正向 视频有效播放
8 share_score ClientTweetShare 正向 分享(泛指)
9 share_via_dm_score ClientTweetClickSendViaDM 正向 通过私信分享
10 share_via_copy_link_score ClientTweetShareViaCopyLink 正向 复制链接分享
11 dwell_score ClientTweetRecapDwelled 正向 停留阅读(是否超过阈值)
12 quote_score ServerTweetQuote 正向 引用推文
13 quoted_click_score ClientQuotedTweetClick 正向 点击被引用的推文
14 follow_author_score ClientTweetFollowAuthor 正向 看完推文后关注作者
15 not_interested_score ClientTweetNotInterestedIn 负向 点击"不感兴趣"
16 block_author_score ClientTweetBlockAuthor 负向 拉黑作者
17 mute_author_score ClientTweetMuteAuthor 负向 静音作者
18 report_score ClientTweetReport 负向 举报推文
19 dwell_time DwellTime 连续值 停留时长(毫秒级连续值)

[!warning] 完整性声明 UAS 原始事件全集定义在外部协议与聚合模块;当前仓库可直接确认的是“排序打分中被消费的行为类型”。

<!-- TODO: UAS 完整枚举需补充自 xai_recsys_proto / 外部 schema -->

7 测试方法

  1. 掩码行为测试test_recsys_model.py

    • 验证候选可看 user/history + 自身
    • 验证候选间不可见
    • 验证边界 case(单候选、几乎全候选)
  2. Top-K 合法性测试test_recsys_retrieval_model.py

    • 索引不越界
    • 分数有序(降序)

本章结论:Phoenix Ranker 通过“结构化输入 + 候选隔离注意力 + 多行为输出”实现了可扩展且稳定的候选打分能力。


第 7 章:重排与策略

本章目标:解释模型分数如何被策略层转化为最终可上屏排序。

1 多行为加权聚合:正负行为对冲

WeightedScorer 将多行为概率线性组合: $$ \text{combined\_score}=\sum_i w_i P_i $$

其中负反馈行为(如 not_interested/block/mute/report)通常对应负权重,对冲“高点击但高风险”的内容。

[!note] 为什么需要对冲? 仅优化单一正向指标会导致“短期刺激、长期满意度下降”。多行为联合能更稳健地近似真实用户价值。

2 作者多样性衰减:控制信息源集中

AuthorDiversityScorer 先按 weighted_score 排序,再按作者出现次序施加衰减: $$ \text{multiplier}=(1-\text{floor})\cdot \text{decay}^{\text{position}}+\text{floor} $$

  • floor 的物理意义:衰减下限,保证“强相关内容”不被彻底压没。
  • 不做此策略会导致头部作者占位过多,降低 feed 多样性。

3 OON 调整:发现性与社交相关性的平衡

OONScorerin_network=false 候选乘系数(OON_WEIGHT_FACTOR),体现“社交强相关”与“内容发现”之间的平衡。

[!warning] 参数不可见 OON 系数具体值在 home-mixer/params

<!-- TODO: OON_WEIGHT_FACTOR -->

4 后置安全过滤与会话去重

  • VFFilter:基于可见性结果移除不可展示内容(安全/违规/删除等)
  • DedupConversationFilter:同一会话链路只保留更优候选,减少重复占位

本章结论:重排策略层把“模型相关性”转成“可消费、可控风险、可持续体验”的最终排序结果。


第 8 章:工程实现

本章目标:从并发、跨语言通信、降级机制与工程权衡四个维度审视实现质量。

1 Rust 异步并发模型

  • tokio 作为运行时
  • join_all 用于 QueryHydrator / Source / Hydrator 并发
  • Semaphore 用于 Thunder 服务入口限流
  • spawn_blocking 用于 CPU/阻塞操作(如 PostStore 重处理)

2 Rust ↔ Python 交互(gRPC/RPC)

可见事实:

  • home-mixer 通过客户端 Trait 调用 phoenix_retrieval_clientphoenix_prediction_client
  • tonic 广泛用于 Rust gRPC 服务/客户端链路。

不可见事实:

  • 客户端具体协议实现、连接池、重试、超时策略在 home-mixer/clients 被裁剪。

3 全链路降级设计

阶段 失败处理策略 风险与收益
QueryHydrator 记录错误,继续 保障可用性,但个性化特征可能缺失
Source 单 Source 失败不终止 保证有结果,但召回覆盖下降
Hydrator 长度不一致时跳过更新 防止数据错位污染
Filter 出错回退到备份候选 降低错误放大,但可能放宽规则
Scorer 单 scorer 失败继续 保证链路不中断,但分数质量下降
SideEffect 异步执行,不阻塞响应 主链低延迟,副作用最终一致

4 已知局限与 Trade-off

  1. 配置/参数不可见:难以做精确复现实验。
  2. 客户端实现裁剪:网络层可靠性细节不可审计。
  3. 检索工程细节缺失:ANN、索引发布、增量更新流程未公开。
  4. 部分模块声明存在但源码缺失:如 candidate-pipeline/utilthunder/config

5 当前代码的创新点

  1. 候选隔离掩码落地到排序主干:兼顾效果与线上稳定性。
  2. 召回/排序与策略重排解耦:模型更新与策略更新互不强耦合。
  3. 全链路可降级:面向生产稳定性的工程优先级明确。
  4. 统一 Trait 可插拔框架:降低多场景复用成本。

6 关键代码模块索引表

模块 关键文件 作用
Pipeline 引擎 candidate-pipeline/candidate_pipeline.rs 执行顺序、并发、降级
Query 补全 home-mixer/query_hydrators/*.rs UAS 与用户特征补全
候选来源 home-mixer/sources/*.rs Thunder/Phoenix 召回
候选补全 home-mixer/candidate_hydrators/*.rs 推文/作者/视频/VF 信息
过滤策略 home-mixer/filters/*.rs 去重、时效、社交图、安全
打分策略 home-mixer/scorers/*.rs 预测、加权、多样性、OON
选择器 home-mixer/selectors/top_k_score_selector.rs 排序截断
站内召回服务 thunder/thunder_service.rs gRPC 查询入口与限流
站内索引 thunder/posts/post_store.rs 内存索引与分层过滤
排序模型 phoenix/recsys_model.py, phoenix/grok.py Transformer 打分
召回模型 phoenix/recsys_retrieval_model.py 双塔检索

本章结论:工程实现体现了“在线稳定性优先 + 模块可演进”的取舍,但开源裁剪使部分生产细节仍不可见。


第 9 章:总结与展望

本章目标:收敛全文结论并给出后续可推进方向。

1 总结

X 推荐系统在当前代码中呈现出清晰分层:

  • home-mixer 负责编排
  • candidate-pipeline 负责执行骨架
  • thunder 负责站内高速召回
  • phoenix 负责站外召回与排序建模

其关键价值不只在“模型能力”,更在“模型 + 策略 + 工程降级”的协同设计。

2 展望

建议优先补齐以下内容以提升可复现性:

  1. 开源 clients/params/config 的最小可运行替代实现;
  2. 提供 ANN 检索与索引发布的参考流水线;
  3. 增加端到端压测与故障注入样例;
  4. 给出策略权重的可配置实验模板。

本章结论:该项目已具备完整推荐链路骨架,下一步重点应放在“可复现生产细节”的补全。


附录 A:术语表

术语 全称 含义
For You For You Feed 个性化推荐信息流
Query Hydration 查询补全 在召回前补齐用户上下文
UAS User Action Sequence 用户行为序列
OON Out-of-Network 站外(非关注关系)候选
VF Visibility Filtering 可见性/安全过滤
ANN Approximate Nearest Neighbor 近似最近邻检索
Candidate Isolation Mask 候选隔离掩码 候选间互不可见的注意力掩码
Strato Strato Service 内部特征读写/存储服务(本仓库仅见调用)
TES Tweet Entity Service 推文实体信息服务(本仓库仅见调用)
Gizmoduck Gizmoduck Service 用户资料服务(本仓库仅见调用)

往年同期文章