隆太威电子网欢迎您!
新闻资讯

X (Twitter) 推荐系统架构设计深度解析

作者:    发布时间:2026-03-11 16:00:11    浏览量:

X (Twitter) 推荐系统架构设计深度解析

本文基于 xai-org/x-algorithm 开源仓库的源码分析,系统性地解读了 X 平台 "For You" 信息流的推荐算法架构、核心机制与设计哲学。


目录

  1. 系统架构总览
  2. 召回阶段:双塔模型
  3. 精排阶段:Transformer 排序模型
  4. 特征工程:Embedding 与 Hash Trick
  5. 关键设计决策
  6. 工程实践要点
  7. 总结与思考

1. 系统架构总览

1.1 核心组件

X 的推荐系统由三个核心模块构成:

组件 技术栈 职责
Home Mixer Rust Pipeline 编排层,负责组装整个推荐流水线
Phoenix JAX/Python ML 模型层,提供召回 (Retrieval) 和精排 (Ranking) 模型
Thunder Rust 实时数据层,提供 In-Network(关注的人)推文流

1.2 推荐漏斗

┌─────────────────────────────────────────────────────────────────┐
│                        全量推文库                                │
│                       (~数亿条推文)                              │
└───────────────────────────┬─────────────────────────────────────┘
                            │
                            ▼ Thunder (关注) + Phoenix Retrieval (推荐)
┌─────────────────────────────────────────────────────────────────┐
│                        候选集                                    │
│                       (~1000 条)                                 │
└───────────────────────────┬─────────────────────────────────────┘
                            │
                            ▼ Pre-Scoring Filters (初筛)
┌─────────────────────────────────────────────────────────────────┐
│                        过滤集                                    │
│                       (~500 条)                                  │
└───────────────────────────┬─────────────────────────────────────┘
                            │
                            ▼ Phoenix Scorer (精排 Transformer)
┌─────────────────────────────────────────────────────────────────┐
│                        精排集                                    │
│                       (~32 条)                                   │
└───────────────────────────┬─────────────────────────────────────┘
                            │
                            ▼ Weighted Scorer + Selection
┌─────────────────────────────────────────────────────────────────┐
│                     最终 Feed                                    │
│                    (Top K 展示)                                  │
└─────────────────────────────────────────────────────────────────┘

2. 召回阶段:双塔模型

2.1 为什么需要双塔?

核心矛盾 :精排模型虽然精准,但计算复杂度 O(N),无法遍历数亿推文。

解决方案 :双塔模型通过解耦 (Decoupling) 实现极速召回。

2.2 双塔架构

┌─────────────────┐                    ┌─────────────────┐
│    User Tower   │                    │  Candidate Tower│
│  (Transformer)  │                    │     (MLP)       │
├─────────────────┤                    ├─────────────────┤
│ - 用户 ID       │                    │ - 推文 ID       │
│ - 历史行为序列  │                    │ - 作者 ID       │
│ - 用户特征      │                    │ - 推文特征      │
└────────┬────────┘                    └────────┬────────┘
         │                                      │
         ▼                                      ▼
    V_user [256]                          V_item [256]
         │                                      │
         └──────────────┬───────────────────────┘
                        │
                        ▼
              Similarity = V_user · V_item
                        │
                        ▼
                   Top-K 召回

2.3 核心优势

特性 说明
物品向量可预计算 推文发布时即可离线计算 Embedding,存入向量数据库
用户向量实时计算 请求时只需跑一次 User Tower
ANN 极速检索 利用近似最近邻算法,毫秒级从亿级候选中检索 Top-K

2.4 共享向量空间的学习

问题 :用户特征(年龄、性别)和物品特征(文本、图片)看似不在同一空间,为何能计算相似度?

答案 :通过监督学习强行对齐。

训练数据: (用户A, 推文B, 点赞=1)  -- 正样本
         (用户A, 推文C, 滑过=0)  -- 负样本

Loss Function: 让 V_A · V_B ↑, 让 V_A · V_C ↓

结果: 神经网络自动学会把"喜欢篮球的用户""NBA推文"映射到同一区域

3. 精排阶段:Transformer 排序模型

3.1 与召回的区别

维度 召回 (双塔) 精排 (Transformer)
处理规模 亿级 → 千级 百级 → 十级
交互方式 独立编码,仅点积 全交互 Attention
精度 粗糙 精准
计算复杂度 O(1)(配合索引) O(N times S)

3.2 输入序列构造

精排模型将所有信息拼接成一个长序列:

输入序列 = [用户特征 | 历史记录1 | ... | 历史记录S | 候选推文1 | ... | 候选推文C]

形状: [Batch, 1 + 128 + 32, 256] = [Batch, 161, 256]

3.3 Candidate Isolation (候选隔离)

问题 :如果候选推文之间可以互相 Attention,评分会受批次内其他推文影响,不稳定。

解决方案 :特殊的 Attention Mask。

Keys (被看的位置)
          ─────────────────────────────────────────────▶
          │ 用户 │       历史       │      候选        │
    ┌─────┼──────┼──────────────────┼──────────────────┤
Q   │ 用户│  ✓   │  ✓ ✓ ✓ ✓ ✓ ✓ ✓   │  ✗ ✗ ✗ ✗ ✗ ✗    │
u   ├─────┼──────┼──────────────────┼──────────────────┤
e   │ 历史│  ✓   │  ✓ ✓ ✓ ✓ ✓ ✓ ✓   │  ✗ ✗ ✗ ✗ ✗ ✗    │
r   ├─────┼──────┼──────────────────┼──────────────────┤
i   │     │      │                  │  只能看自己       │
e   │ 候选│  ✓   │  ✓ ✓ ✓ ✓ ✓ ✓ ✓   │  ✓ ✗ ✗ ✗ ✗ ✗    │
s   │     │      │                  │  ✗ ✓ ✗ ✗ ✗ ✗    │
    │     │      │                  │  ✗ ✗ ✓ ✗ ✗ ✗    │
    └─────┴──────┴──────────────────┴──────────────────┘

规则:
- 候选 → 用户/历史: ✓ (可以看)
- 候选 → 其他候选: ✗ (不能看)
- 候选 → 自己: ✓ (可以 Self-Attention)

3.4 多目标预测

模型不输出单一分数,而是预测 所有交互行为的概率

输出: [B, Num_Candidates, Num_Actions]
     = [32, 32, 15]

Actions:
├── P(favorite)          # 点赞
├── P(reply)             # 评论
├── P(repost)            # 转发
├── P(click)             # 点击
├── P(video_view)        # 视频观看
├── P(share)             # 分享
├── P(follow_author)     # 关注作者
├── P(not_interested)    # 不感兴趣 (负面)
├── P(block_author)      # 屏蔽 (负面)
├── P(mute_author)       # 静音 (负面)
└── P(report)            # 举报 (负面)

3.5 加权打分

WeightedScorer 将多目标概率组合为最终分数:

text{Score} = sum_{i} w_i times P(text{action}_i)

  • 正面行为(点赞、转发):正权重
  • 负面行为(屏蔽、举报):负权重

优势 :业务层可通过调整权重快速改变推荐策略,无需重新训练模型。


4. 特征工程:Embedding 与 Hash Trick

4.1 三张 Embedding Table 架构

X 的 Phoenix 模型使用了 三张独立的 Embedding Table

┌─────────────────────────────────────────────────────────────────┐
│                    Embedding Table 架构                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌─────────────────────┐                                        │
│  │   User Table        │  用户 IDEmbedding                   │
│  │   [N_user, D]       │  例如: [10,000,000, 256]               │
│  └─────────────────────┘                                        │
│                                                                 │
│  ┌─────────────────────┐                                        │
│  │   Post Table        │  推文 IDEmbedding                   │
│  │   [N_post, D]       │  例如: [100,000,000, 256]              │
│  │                     │  (历史推文和候选推文共用此表)            │
│  └─────────────────────┘                                        │
│                                                                 │
│  ┌─────────────────────┐                                        │
│  │   Author Table      │  作者 IDEmbedding                   │
│  │   [N_author, D]     │  例如: [50,000,000, 256]               │
│  │                     │  (历史作者和候选作者共用此表)            │
│  └─────────────────────┘                                        │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

代码依据 (recsys_model.py):

class RecsysEmbeddings(NamedTuple):
    user_embeddings: ...              # 来自 User Table
    history_post_embeddings: ...      # 来自 Post Table
    candidate_post_embeddings: ...    # 来自 Post Table (同一张表)
    history_author_embeddings: ...    # 来自 Author Table
    candidate_author_embeddings: ...  # 来自 Author Table (同一张表)

注意 :用户和作者虽然本质上都是 UserID,但使用的是 不同的表 。User Table 代表"读者的兴趣画像",Author Table 代表"作者的内容风格/人设"。

4.2 Hash Trick 完整流程

4.2.1 配置参数

@dataclass
class HashConfig:
    num_user_hashes: int = 2    # 用户 ID 用 2 个 Hash 函数
    num_item_hashes: int = 2    # 推文 ID 用 2 个 Hash 函数
    num_author_hashes: int = 2  # 作者 ID 用 2 个 Hash 函数

4.2.2 从原始 ID 到 Embedding 的完整流程

推文 ID 为例:

┌─────────────────────────────────────────────────────────────────┐
                 推文 ID  Embedding 完整流程                     
├─────────────────────────────────────────────────────────────────┤
                                                                 
  原始推文 ID: 1757483920193847296 (Snowflake 格式)               
                                                                
                                                                
  ┌─────────────────────────────────────────────────────────┐    
                 Hash 函数 (2 个不同的 seed)                    
    hash1 = xxhash64(tweet_id, seed=1) % TABLE_SIZE            
    hash2 = xxhash64(tweet_id, seed=2) % TABLE_SIZE            
                                                               
    假设 TABLE_SIZE = 10,000,000                               
    hash1 = 888                                                
    hash2 = 1024                                               
  └─────────────────────────────────────────────────────────┘    
                                                                
                                                                
  post_hashes = [888, 1024]   # 形状: [2]                        │
                                                                
                                                                
  ┌─────────────────────────────────────────────────────────┐    
                 Embedding Table 查表                           
                                                               
    Post_Table[888]   V1 = [0.12, -0.34, ..., 0.56]  [256]    
    Post_Table[1024]  V2 = [0.78, 0.23, ..., -0.11]  [256]    
  └─────────────────────────────────────────────────────────┘    
                                                                
                                                                
  post_embeddings = [V1, V2]   # 形状: [2, 256]                  │
                                                                 
└─────────────────────────────────────────────────────────────────┘

4.3 Reshape 操作:多向量合并

查表得到多个向量后,需要通过 Reshape (展平) 合并为单个向量:

# 查表后的原始形状
history_post_embeddings.shape = [B, S, 2, 256]
#                                ^  ^  ^   ^
#                                |  |  |   └── Embedding 维度 D
#                                |  |  └── num_item_hashes = 2
#                                |  └── 历史序列长度 S (如 128)
#                                └── Batch 大小 B

# Reshape: 把最后两维 [2, 256] 展平成 [512]
history_post_embeddings_reshaped = embeddings.reshape((B, S, 2 * 256))
# 结果形状: [B, S, 512]

本质 :把两个 256 维向量首尾相接成一个 512 维向量。

V1 = [0.12, -0.34, ..., 0.89]  # 256 维
V2 = [0.78, 0.23, ..., 0.45]   # 256 维

V_combined = [V1 | V2] = [0.12, -0.34, ..., 0.89, 0.78, 0.23, ..., 0.45]
# 512 维

所有实体的 Reshape

实体 Reshape 前 Reshape 后
用户 [B, 2, 256] [B, 512]
历史推文 [B, S, 2, 256] [B, S, 512]
历史作者 [B, S, 2, 256] [B, S, 512]
候选推文 [B, C, 2, 256] [B, C, 512]
候选作者 [B, C, 2, 256] [B, C, 512]

4.4 单条推文的特征构建

Reshape 后,还需将多个维度的特征 拼接并投影

┌─────────────────────────────────────────────────────────────────┐
│                      单条推文特征向量                            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌─────────────┐   ┌─────────────┐   ┌─────────────┐   ┌──────┐ │
│  │  推文 ID    │   │  作者 ID    │   │   用户行为   │   │ 场景 │ │
│  │ (Hash x2)  │   │ (Hash x2)  │   │ (Multi-Hot) │   │(枚举)│ │
│  └──────┬──────┘   └──────┬──────┘   └──────┬──────┘   └──┬───┘ │
│         │                 │                 │              │     │
│         ▼                 ▼                 ▼              ▼     │
│      [512]             [512]             [256]          [256]   │
│         │                 │                 │              │     │
│         └────────┬────────┴────────┬────────┴──────┬───────┘     │
│                  │                                              │
│                  ▼ Concatenate                                  │
│              [1536]                                             │
│                  │                                              │
│                  ▼ Linear Projection                            │
│              [256]  ────▶ 进入 Transformer                       │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

4.5 OOV (Out-of-Vocabulary) 问题的解决

4.5.1 Hash Trick 本身就是 OOV 解决方案

核心洞察 :使用 Hash Trick 后, 不存在真正的 OOV

def get_embedding(any_id: int, table_size: int) - > int:
    # 无论 ID 是什么,总能映射到表中的某一行
    return hash(any_id) % table_size

任何新 ID 都会被 Hash 函数映射到 [0, TABLE_SIZE) 范围内,不会出现"找不到"的情况。

4.5.2 冲突带来的"隐式 OOV"

虽然技术上没有 OOV,但新 ID 会 继承旧 ID 的 Embedding (Hash 冲突):

新推文 X (刚发布,从未训练过)
    
     Hash
槽位 888 (之前被老推文 A 训练过)
    
     查表
Embedding = 老推文 A 的语义向量 (可能完全不相关)

4.5.3 X 的三层防御机制

层 1:Multi-Hash 组合唯一性

# 新推文 X
hash1(X) = 888   # 撞了老推文 A
hash2(X) = 5001  # 撞了老推文 B

# 组合 (888, 5001) 是全新的,提供了区分度

层 2:Author Embedding 兜底

# 新推文 X 的特征
post_embedding = 噪声 (推文 ID 是新的)
author_embedding = 准确 (作者是老用户)

# Transformer 会自动 attend 到更可靠的信号

层 3:在线学习快速修正

t=0:    Embedding 是噪声
t=5min: 收集到 1000 次反馈
t=10min: 梯度下降更新 Table[888] 和 Table[5001]
t=15min: Embedding 已包含推文 X 的真实语义

4.6 Hash 冲突的容忍性

关键洞察 :推荐是 概率排序任务 ,不需要精确语义匹配。

对比 LLM 词向量 推荐系统 ID Embedding
映射方式 一对一 多对一 (Hash 冲突)
是否允许冲突 ❌ 不允许 ✅ 可容忍
任务类型 精确生成 概率排序
错误代价 句子完全错乱 Top-3 变 Top-5,可接受

冲突为何不致命

  1. Multi-Hash 组合冲突率极低 ($10^{-12}$)
  2. 冲突是随机噪声,统计上被正确信号淹没
  3. 有正则化效果,防止过拟合单条推文

4.7 开源代码中的特征缺失

重要发现 :X 开源的代码中 没有显式的内容特征 (文本 Embedding、图片特征)。

模型主要依赖:

  • 协同过滤信号 :用户行为(点赞、转发等)
  • 隐式语义学习 :相似内容被相似用户喜欢 → Embedding 自动靠近

冷启动兜底 :依赖 Author Embedding 。新推文虽然 ID 是噪声,但作者是老用户,可提供初始语义。

4.8 完整的 Embedding 流程图

┌─────────────────────────────────────────────────────────────────────────────┐
│                    X Phoenix Embedding 完整架构                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  原始输入                                                                    │
│  ┌──────────┐ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐│
│  │ UserID   │ │ History TweetIDs │ │ History AuthorIDs│ │ Candidate IDs   ││
│  └────┬─────┘ └────────┬─────────┘ └────────┬─────────┘ └────────┬─────────┘│
│       │                │                    │                    │          │
│       ▼                ▼                    ▼                    ▼          │
│  ┌─────────────────────────────────────────────────────────────────────────┐│
│  │                        Hash 函数 (xxhash64, 2个seed)                    ││
│  └─────────────────────────────────────────────────────────────────────────┘│
│       │                │                    │                    │          │
│       ▼                ▼                    ▼                    ▼          │
│  user_hashes      post_hashes          author_hashes        post_hashes    │
│   [B, 2]          [B, S, 2]             [B, S, 2]           [B, C, 2]      │
│       │                │                    │                    │          │
│       ▼                ▼                    ▼                    ▼          │
│  ┌────────────┐  ┌────────────┐      ┌────────────┐      ┌────────────┐    │
│  │ User Table │  │ Post Table │      │Author Table│      │ Post Table │    │
│  │ [N_u, 256] │  │ [N_p, 256] │      │ [N_a, 256] │      │ (同一张表)  │    │
│  └─────┬──────┘  └─────┬──────┘      └─────┬──────┘      └─────┬──────┘    │
│        │               │                   │                   │           │
│        ▼               ▼                   ▼                   ▼           │
│   [B, 2, 256]    [B, S, 2, 256]      [B, S, 2, 256]      [B, C, 2, 256]   │
│        │               │                   │                   │           │
│        ▼               ▼                   ▼                   ▼           │
│  ┌─────────────────────────────────────────────────────────────────────────┐│
│  │                     Reshape → 展平最后两维                              ││
│  └─────────────────────────────────────────────────────────────────────────┘│
│        │               │                   │                   │           │
│        ▼               ▼                   ▼                   ▼           │
│    [B, 512]       [B, S, 512]         [B, S, 512]         [B, C, 512]     │
│        │               │                   │                   │           │
│        ▼               ▼                   ▼                   ▼           │
│  ┌─────────────────────────────────────────────────────────────────────────┐│
│  │         Concat (拼接多维度特征) + Linear Projection → [256]             ││
│  └─────────────────────────────────────────────────────────────────────────┘│
│        │                                                                    │
│        ▼                                                                    │
│  ┌─────────────────────────────────────────────────────────────────────────┐│
│  │                        Transformer                                      ││
│  │             [User | History_1 | ... | Candidate_1 | ...]                ││
│  │             形状: [B, 1 + S + C, 256] = [B, 161, 256]                   ││
│  └─────────────────────────────────────────────────────────────────────────┘│
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

5. 关键设计决策

5.1 纯模型驱动,无手工特征

X 的 README 明确声明:

We have eliminated every single hand-engineered feature and most heuristics from the system.

  • 优势 :简化数据 Pipeline,减少特征工程负担
  • 代价 :完全依赖用户行为,冷启动问题严重

5.2 技术栈分工

技术 原因
Pipeline 编排 Rust 高并发、低延时、内存安全
ML 模型 JAX 高性能自动微分、TPU 原生支持
实时数据流 Rust + Kafka 处理百万级 TPS

5.3 召回与精排的边界

  • 召回 (Retrieval) :极致效率,用双塔"粗筛"
  • 精排 (Ranking) :极致精度,用 Transformer"细选"
  • 关键约束 :精排只处理 ~32 个候选,严格控制计算量

6. 工程实践要点

6.1 Embedding Table 管理

策略 说明
表大小 百万到亿级,平衡冲突率与内存
热门物品专属行 高频访问的推文/作者可分配独立索引
在线学习 新推文几分钟内根据反馈修正 Embedding

6.2 分布式 ID 生成 (Snowflake)

64-bit ID 结构:
┌───────┬───────────────────┬────────────┬──────────────┐
│ 1 bit41 bits        │  10 bits   │   12 bits    │
│ sign  │ timestamp (ms)    │ machine ID │  sequence    │
└───────┴───────────────────┴────────────┴──────────────┘

特性: 全局唯一、时间有序、高性能、去中心化

6.3 开源代码的局限

以下模块被排除,未开源:

pub mod clients; // 后端服务通信
pub mod params;  // 模型权重参数
pub mod util;    // 工具函数 (含 Hash 实现)

7. 总结与思考

7.1 架构设计要点

  1. 分层漏斗 :召回→粗排→精排→混排,逐层收窄,逐层精细
  2. 效率与精度的权衡 :双塔换速度,Transformer 换精度
  3. 协同过滤为主 :依赖用户行为信号,而非内容理解
  4. 工程约束驱动设计 :候选数量、历史长度严格受限

7.2 开放问题

问题 思考
内容特征缺失 生产系统是否有额外的 Content Embedding 通道?
冷启动 新作者 + 新推文如何处理?是否有探索机制?
实时性 在线学习的更新频率?Embedding 漂移如何控制?

7.3 对实践的启示

  1. 不必追求完美匹配 :推荐是概率任务,容错性高
  2. Hash Trick 是性价比之选 :用可控的精度损失换取巨大的空间节省
  3. 用户行为是最强信号 :协同过滤在工业界依然有效
  4. Transformer 适合精排 :在候选集收窄后才能发挥威力

附录:核心代码文件索引

文件 路径 职责
精排模型 phoenix/recsys_model.py PhoenixModel 定义
召回模型 phoenix/recsys_retrieval_model.py 双塔模型定义
Transformer phoenix/grok.py 注意力机制与 Mask
Pipeline home-mixer/candidate_pipeline/phoenix_candidate_pipeline.rs 流水线编排
打分器 home-mixer/scorers/weighted_scorer.rs 多目标加权

本文档基于 xai-org/x-algorithm 开源代码分析,结合推荐系统工程实践整理。

审核编辑 黄宇