一、系统架构概览
X 的"For You"推荐系统采用两阶段推荐架构 ,结合了关注内容 和发现内容,使用基于Grok的Transformer模型进行排序。
核心组件
- Home Mixer - 编排层,负责整个推荐流程的协调
- Thunder - 内网内容源,实时内存存储
- Phoenix - ML组件,包含检索和排序两个模型
- Candidate Pipeline - 可复用的推荐管道框架
二、完整推荐流程
阶段1:请求接收与查询增强
入口: home-mixer/server.rs - get_scored_posts()
rust
// 1. 接收gRPC请求
async fn get_scored_posts(
&self,
request: Request<pb::ScoredPostsQuery>,
) -> Result<Response<ScoredPostsResponse>, Status>
查询增强器(Query Hydrators):
1.1 UserActionSeqQueryHydrator
位置: home-mixer/query_hydrators/user_action_seq_query_hydrator.rs
功能: 获取用户的交互历史序列
- 从 User Action Sequence 服务获取用户最近的交互行为
- 包括:点赞、转发、回复、点击、分享等行为
- 对行为进行聚合和过滤
- 截断到最大序列长度(
UAS_MAX_SEQUENCE_LENGTH)
关键代码:
rust
// 获取用户交互序列
let uas_thrift = self.uas_fetcher.get_by_user_id(query.user_id).await;
// 聚合用户行为
let aggregated_actions = self.aggregator.run(&filtered_actions,
p::UAS_WINDOW_TIME_MS, 0);
// 截断序列
if aggregated_actions.len() > p::UAS_MAX_SEQUENCE_LENGTH {
aggregated_actions.drain(0..drain_count);
}
1.2 UserFeaturesQueryHydrator
功能: 获取用户特征
- 关注列表
- 用户偏好设置
- 其他用户元数据
输出: 增强后的查询对象包含:
user_action_sequence: 用户交互历史序列user_features: 用户特征(关注列表等)
阶段2:候选内容获取
位置: candidate-pipeline/candidate_pipeline.rs - fetch_candidates()
两个候选源并行执行:
2.1 Thunder Source(关注内容)
位置: home-mixer/sources/thunder_source.rs
原理:
-
Thunder服务架构:
- 实时数据流: 从Kafka消费推文创建/删除事件
- 内存存储: 使用
PostStore在内存中维护最近的推文 - 数据结构:
posts: 所有推文的完整数据(按post_id索引)original_posts_by_user: 每个用户的原创推文secondary_posts_by_user: 每个用户的回复和转发video_posts_by_user: 每个用户的视频推文
-
Kafka消费流程:
rust// thunder/kafka/tweet_events_listener_v2.rs async fn process_tweet_events_v2() { loop { let messages = consumer.poll(batch_size).await; let (light_posts, delete_posts) = deserialize_batch(messages); post_store.insert_posts(light_posts); post_store.mark_as_deleted(delete_posts); } } -
查询流程:
rust// home-mixer/sources/thunder_source.rs let request = GetInNetworkPostsRequest { user_id: query.user_id, following_user_ids: following_list, // 关注列表 max_results: THUNDER_MAX_RESULTS, }; // Thunder服务根据关注列表从内存中查找推文 let response = client.get_in_network_posts(request).await?; -
Thunder内部查询逻辑:
rust// thunder/thunder_service.rs // 从PostStore中获取关注用户的推文 let all_posts = post_store.get_all_posts_by_users( &following_user_ids, &exclude_tweet_ids, start_time, request_user_id, ); // 按时间排序(新推文优先) let scored_posts = score_recent(all_posts, max_results);
特点:
- 亚毫秒级查询延迟
- 自动清理过期推文
- 支持实时更新
2.2 Phoenix Retrieval Source(发现内容)
位置: home-mixer/sources/phoenix_source.rs
原理:
-
两塔模型架构:
- User Tower: 编码用户特征和交互历史为embedding
- Candidate Tower: 编码所有推文为embedding
- 相似度搜索: 使用点积相似度检索Top-K推文
-
检索流程:
rust// 使用用户交互序列进行检索 let response = phoenix_retrieval_client.retrieve( user_id, sequence.clone(), // 用户交互序列 PHOENIX_MAX_RESULTS ).await?; -
Phoenix检索模型实现:
python# phoenix/recsys_retrieval_model.py class PhoenixRetrievalModel: def __call__(self, batch): # User Tower: 使用Transformer编码用户 user_emb = self.model(user_features, history) user_emb = normalize(user_emb) # L2归一化 # Candidate Tower: 编码候选推文 candidate_emb = self.candidate_tower(post_emb, author_emb) candidate_emb = normalize(candidate_emb) # 相似度计算(点积) scores = dot_product(user_emb, candidate_emb) # 返回Top-K return top_k_indices, top_k_scores
特点:
- 从全局语料库中发现相关内容
- 基于用户交互历史的个性化检索
- 使用近似最近邻(ANN)搜索实现高效检索
阶段3:候选内容增强
位置: candidate-pipeline/candidate_pipeline.rs - hydrate()
多个增强器并行执行,为候选推文添加元数据:
3.1 InNetworkCandidateHydrator
标记推文是否来自关注用户
3.2 CoreDataCandidateHydrator
获取推文核心数据:
- 推文文本
- 媒体内容
- 创建时间等
3.3 VideoDurationCandidateHydrator
获取视频推文的时长信息
3.4 SubscriptionHydrator
获取订阅状态信息
3.5 GizmoduckCandidateHydrator
获取作者信息:
- 用户名
- 认证状态
- 作者元数据
阶段4:预评分过滤
位置: candidate-pipeline/candidate_pipeline.rs - filter()
过滤器顺序执行,每个过滤器移除不符合条件的候选:
4.1 DropDuplicatesFilter
移除重复的推文ID
4.2 CoreDataHydrationFilter
移除无法获取核心数据的推文
4.3 AgeFilter
移除过旧的推文(超过MAX_POST_AGE)
4.4 SelfTweetFilter
移除用户自己发布的推文
4.5 RetweetDeduplicationFilter
去重转发内容(同一原始推文的多次转发只保留一个)
4.6 IneligibleSubscriptionFilter
移除用户无法访问的付费内容
4.7 PreviouslySeenPostsFilter
移除用户已经看过的推文
4.8 PreviouslyServedPostsFilter
移除本次会话中已经展示过的推文
4.9 MutedKeywordFilter
移除包含用户静音关键词的推文
4.10 AuthorSocialgraphFilter
移除来自被屏蔽/静音作者的推文
阶段5:评分
位置: candidate-pipeline/candidate_pipeline.rs - score()
评分器顺序执行,每个评分器更新候选的分数:
5.1 Phoenix Scorer(ML预测)
位置: home-mixer/scorers/phoenix_scorer.rs
功能: 使用Phoenix Transformer模型预测用户对各种行为的概率
模型架构:
python
# phoenix/recsys_model.py
class RecsysModel:
def __call__(self, batch, embeddings):
# 输入:
# - User embedding: [B, 1, D]
# - History embeddings: [B, S, D] (S=历史序列长度)
# - Candidate embeddings: [B, C, D] (C=候选数量)
# Transformer处理(带候选隔离)
# 关键:候选之间不能相互关注,只能关注用户和历史
output = self.transformer(
user_emb, history_emb, candidate_emb,
attention_mask=create_candidate_isolation_mask()
)
# 输出:每个候选的多个行为概率
logits = [B, C, num_actions]
return logits
预测的行为类型:
- 正面行为:
favorite,reply,repost,quote,click,profile_click,video_view,photo_expand,share,dwell,follow_author - 负面行为:
not_interested,block_author,mute_author,report - 连续值:
dwell_time
关键代码:
rust
// home-mixer/scorers/phoenix_scorer.rs
let result = phoenix_client.predict(
user_id,
sequence.clone(), // 用户交互序列
tweet_infos // 候选推文信息
).await?;
// 提取预测概率
let phoenix_scores = PhoenixScores {
favorite_score: p.get(ActionName::ServerTweetFav),
reply_score: p.get(ActionName::ServerTweetReply),
retweet_score: p.get(ActionName::ServerTweetRetweet),
// ... 其他行为
};
5.2 Weighted Scorer
位置: home-mixer/scorers/weighted_scorer.rs
功能: 将多个行为预测组合成最终相关性分数
计算公式:
rust
weighted_score =
favorite_score × FAVORITE_WEIGHT +
reply_score × REPLY_WEIGHT +
retweet_score × RETWEET_WEIGHT +
click_score × CLICK_WEIGHT +
// ... 其他正面行为
- not_interested_score × NOT_INTERESTED_WEIGHT +
- block_author_score × BLOCK_AUTHOR_WEIGHT +
// ... 其他负面行为(负权重)
关键代码:
rust
fn compute_weighted_score(candidate: &PostCandidate) -> f64 {
let s = &candidate.phoenix_scores;
let combined_score =
apply(s.favorite_score, FAVORITE_WEIGHT) +
apply(s.reply_score, REPLY_WEIGHT) +
apply(s.retweet_score, RETWEET_WEIGHT) +
// ... 所有行为
- apply(s.not_interested_score, NOT_INTERESTED_WEIGHT) +
- apply(s.block_author_score, BLOCK_AUTHOR_WEIGHT);
// 分数偏移和归一化
offset_score(combined_score)
}
5.3 Author Diversity Scorer
位置: home-mixer/scorers/author_diversity_scorer.rs
功能: 降低同一作者多次出现的推文分数,确保信息流多样性
算法:
rust
// 按分数排序
ordered.sort_by(|a, b| b.score.cmp(&a.score));
// 对每个作者,第一次出现保持原分数,后续出现按位置衰减
for (candidate, position) in ordered {
let multiplier = (1.0 - floor) × decay_factor^position + floor;
adjusted_score = original_score × multiplier;
author_counts[author_id] += 1;
}
衰减公式:
multiplier(position) = (1 - floor) × decay_factor^position + floor
5.4 OON Scorer(发现内容调整)
位置: home-mixer/scorers/oon_scorer.rs
功能: 对发现内容(Out-of-Network)的分数进行调整
阶段6:选择
位置: candidate-pipeline/candidate_pipeline.rs - select()
选择器: TopKScoreSelector
位置: home-mixer/selectors/top_k_score_selector.rs
功能: 按最终分数排序,选择Top-K候选
rust
// 按分数降序排序
candidates.sort_by(|a, b| b.score.cmp(&a.score));
// 选择Top-K
candidates.truncate(TOP_K_CANDIDATES_TO_SELECT);
阶段7:选择后处理(
7.1 选择后增强
VFCandidateHydrator: 获取可见性过滤数据
7.2 选择后过滤
VFFilter: 移除被删除、垃圾、暴力、血腥等内容的推文
DedupConversationFilter: 去重同一对话线程的多个分支
三、关键技术设计
1. 无手工特征工程
- 系统完全依赖Grok-based Transformer从用户交互序列中学习相关性
- 无需手动设计内容相关性特征
- 显著降低了数据管道和服务基础设施的复杂度
2. 候选隔离
- Transformer推理时,候选之间不能相互关注
- 确保推文分数不依赖于批次中的其他推文
- 使分数一致且可缓存
3. 基于哈希的嵌入
- 使用多个哈希函数进行嵌入查找
- 减少嵌入表大小,提高效率
4. 多行为预测
- 同时预测多种用户行为(点赞、转发、回复、举报等)
- 正面和负面行为都有预测,提供更全面的用户偏好信号
5. 可组合的管道架构
candidate-pipeline框架提供灵活的推荐管道构建能力- 管道执行和监控与业务逻辑分离
- 支持并行执行和优雅的错误处理
- 易于添加新的源、增强器、过滤器和评分器
四、数据流图
用户请求
↓
[Query Hydration]
├─ UserActionSeqQueryHydrator → 用户交互序列
└─ UserFeaturesQueryHydrator → 用户特征(关注列表等)
↓
[Candidate Sourcing] (并行)
├─ Thunder Source → 关注推文(关注用户的推文)
└─ Phoenix Retrieval Source → 全局推文(ML发现的推文)
↓
[Candidate Hydration] (并行)
├─ CoreDataCandidateHydrator → 推文核心数据
├─ GizmoduckCandidateHydrator → 作者信息
├─ VideoDurationCandidateHydrator → 视频时长
└─ SubscriptionHydrator → 订阅状态
↓
[Pre-Scoring Filters] (顺序)
├─ DropDuplicatesFilter
├─ AgeFilter
├─ SelfTweetFilter
├─ MutedKeywordFilter
└─ ... (10个过滤器)
↓
[Scoring] (顺序)
├─ Phoenix Scorer → ML预测(多个行为概率)
├─ Weighted Scorer → 加权组合分数
├─ Author Diversity Scorer → 多样性调整
└─ OON Scorer → 外网内容调整
↓
[Selection]
└─ TopKScoreSelector → 按分数排序,选择Top-K
↓
[Post-Selection Processing]
├─ VFCandidateHydrator → 可见性数据
├─ VFFilter → 可见性过滤
└─ DedupConversationFilter → 对话去重
↓
[Side Effects]
└─ CacheRequestInfoSideEffect → 缓存请求信息
↓
返回排序后的推文列表
五、性能优化
1. Thunder的内存存储
- 所有内网推文存储在内存中
- 亚毫秒级查询延迟
- 自动清理过期推文
2. 并行执行
- 候选源并行获取
- 增强器并行执行
- 副作用异步执行
3. 候选隔离
- 使分数可缓存
- 减少重复计算
4. 批处理
- Phoenix模型批处理预测
- Kafka消息批处理