X For You Feed Algorithm
目录
| 章节 | 内容 |
|---|---|
| 一、项目概述 | 系统简介、核心特性、技术栈 |
| 二、系统架构 | 整体架构图、服务交互时序 |
| 三、核心组件 | Home Mixer、Thunder(含三层分类、保留期、并发控制)、Phoenix |
| 四、数据流程 | 请求处理流程、过滤器详解、用户行为序列(UAS) |
| 五、ML模型架构 | 排序模型、Hash嵌入机制 |
| 六、候选管道框架 | Trait定义、执行流程 |
| 七、评分算法 | 20个评分信号、视频评分机制、印象去重、双阶段安全审核 |
| 八、目录结构 | 项目文件组织 |
| 九、创作者指南 | 算法应用、内容策略 |
一、项目概述
X For You Feed Algorithm 是 X 平台 “For You” 推荐信息流的开源推荐系统。
该系统结合了网内内容(来自已关注账户的帖子)和网外内容(通过机器学习检索发现的内容),并使用基于 Grok 的 Transformer 模型进行排序。
代码仓库位置:https://github.com/xai-org/x-algorithm
1.1 核心特性
mindmap
root((X推荐算法))
网内内容
Thunder服务
实时帖子存储
关注列表检索
网外内容
Phoenix检索
两塔模型
相似度搜索
ML排序
Grok Transformer
多动作预测
候选隔离
管道框架
可复用组件
并行执行
灵活扩展
1.2 技术栈
| 组件 | 技术 | 用途 |
|---|---|---|
| Home Mixer | Rust + gRPC | 主编排服务 |
| Thunder | Rust + Kafka | 实时帖子存储 |
| Phoenix | Python + JAX | ML模型 |
| Candidate Pipeline | Rust | 可复用管道框架 |
二、系统架构
2.1 整体架构图
flowchart TB
subgraph Client["客户端"]
APP[X App/Web]
end
subgraph HomeMixer["Home Mixer 编排服务"]
direction TB
GRPC[gRPC Server]
QH[Query Hydrators]
SOURCES[Candidate Sources]
HYDRATORS[Candidate Hydrators]
FILTERS[Filters]
SCORERS[Scorers]
SELECTORS[Selectors]
SE[Side Effects]
end
subgraph Thunder["Thunder 实时存储"]
direction TB
TS[Thunder Service]
PS[Post Store]
KL[Kafka Listener]
end
subgraph Phoenix["Phoenix ML服务"]
direction TB
PR[Phoenix Retrieval]
PP[Phoenix Prediction]
GM[Grok Model]
end
subgraph ExternalServices["外部服务"]
direction TB
GD[Gizmoduck
用户元数据]
SG[SocialGraph
社交关系]
ST[Strato
用户特征]
TES[TweetEntityService
推文实体]
UAS[UserActionSequence
用户行为]
VF[VisibilityFiltering
可见性过滤]
end
subgraph DataSources["数据源"]
KAFKA[Kafka
推文事件流]
DB[(用户数据库)]
end
APP -->|请求推荐| GRPC
GRPC --> QH
QH --> SOURCES
SOURCES --> HYDRATORS
HYDRATORS --> FILTERS
FILTERS --> SCORERS
SCORERS --> SELECTORS
SELECTORS --> SE
SE -->|返回结果| APP
SOURCES -->|网内候选| TS
SOURCES -->|网外候选| PR
SCORERS -->|预测请求| PP
PP --> GM
TS --> PS
KL --> PS
KAFKA --> KL
QH --> ST
QH --> UAS
HYDRATORS --> GD
HYDRATORS --> TES
FILTERS --> SG
FILTERS --> VF
ST --> DB
GD --> DB
SG --> DB
用餐厅比喻理解系统架构
把整个推荐系统想象成一家智能餐厅:
| 系统组件 | 餐厅角色 | 职责 |
|---|---|---|
| 客户端 App | 顾客 | 说"我饿了,给我推荐菜品" |
| Home Mixer | 大堂经理 | 协调所有流程,最终把菜单给顾客 |
| Thunder | 常客档案柜 | 存着顾客朋友们最近做的菜(关注列表内容) |
| Phoenix | 美食侦探 | 去全城搜罗可能合顾客口味的菜(网外内容) |
| 外部服务 | 各种助手 | 查顾客口味偏好、过敏信息、消费记录等 |
flowchart LR
subgraph Example["一次推荐请求的旅程"]
direction TB
C["用户小明打开App"] --> Q["大堂经理接待"]
Q --> |"同时派出两路人马"| P1["去常客档案柜
找小明朋友的最新帖子"]
Q --> P2["派美食侦探
搜索全平台好内容"]
P1 --> MERGE["汇总所有候选"]
P2 --> MERGE
MERGE --> FILTER["筛掉不合适的
(已看过的、被屏蔽的)"]
FILTER --> SCORE["给每道菜打分
预测小明喜欢的程度"]
SCORE --> SELECT["选出最好的50道"]
SELECT --> RETURN["呈现给小明"]
end
实际案例:小明打开 X,系统同时从他关注的100个账号中找最新帖子(Thunder),又从全平台数十亿帖子中检索可能感兴趣的内容(Phoenix),合并后经过层层筛选和打分,最终在200毫秒内返回50条精选内容。
2.2 服务交互时序图
sequenceDiagram
autonumber
participant Client as 客户端
participant HM as Home Mixer
participant Thunder as Thunder
participant Phoenix as Phoenix
participant External as 外部服务
Client->>+HM: GetScoredPosts(user_id, preferences)
par 查询增强
HM->>+External: 获取用户行为序列
External-->>-HM: 用户历史动作
and
HM->>+External: 获取用户特征
External-->>-HM: 用户元数据
end
par 候选获取
HM->>+Thunder: 获取网内帖子
Thunder-->>-HM: 关注者帖子列表
and
HM->>+Phoenix: ML检索候选
Phoenix-->>-HM: 网外候选列表
end
par 候选增强
HM->>+External: 获取帖子核心数据
External-->>-HM: 帖子详情
and
HM->>+External: 获取作者信息
External-->>-HM: 作者元数据
and
HM->>+External: 获取视频时长
External-->>-HM: 视频信息
end
HM->>HM: 执行过滤器链
HM->>+Phoenix: 预测用户参与度
Phoenix-->>-HM: 多动作概率预测
HM->>HM: 计算加权分数
HM->>HM: 作者多样性调整
HM->>HM: 网外分数调整
HM->>HM: Top-K选择
HM->>External: 异步缓存请求信息
HM-->>-Client: 返回排序后的帖子列表
三、核心组件
3.1 Home Mixer(主编排服务)
Home Mixer 是整个推荐系统的核心编排层,负责协调各个组件完成推荐流程。
classDiagram
class ScoredPostsService {
+scored_posts(request) Response
}
class PhoenixCandidatePipeline {
-query_hydrators: Vec~QueryHydrator~
-sources: Vec~Source~
-candidate_hydrators: Vec~Hydrator~
-filters: Vec~Filter~
-scorers: Vec~Scorer~
-selector: Selector
-side_effects: Vec~SideEffect~
+execute(query) Result
}
class ScoredPostsQuery {
+user_id: i64
+language: String
+device_context: DeviceContext
+seen_post_ids: Vec~i64~
+served_post_ids: Vec~i64~
+user_action_features: UserActionFeatures
+user_features: UserFeatures
}
class PostCandidate {
+post_id: i64
+author_id: i64
+created_at: i64
+is_retweet: bool
+is_reply: bool
+source: CandidateSource
+phoenix_scores: PhoenixScores
+weighted_score: f64
+final_score: f64
}
class PhoenixScores {
+p_favorite: f64
+p_reply: f64
+p_repost: f64
+p_quote: f64
+p_click: f64
+p_profile_click: f64
+p_video_view: f64
+p_photo_expand: f64
+p_share: f64
+p_follow_author: f64
+p_not_interested: f64
+p_block_author: f64
+p_mute_author: f64
+p_report: f64
}
ScoredPostsService --> PhoenixCandidatePipeline
PhoenixCandidatePipeline --> ScoredPostsQuery
PhoenixCandidatePipeline --> PostCandidate
PostCandidate --> PhoenixScores
用快递分拣中心比喻 Home Mixer
把 Home Mixer 想象成一个智能快递分拣中心,每个包裹就是一篇候选帖子:
flowchart TB
subgraph HomeMixerAnalogy["Home Mixer = 快递分拣中心"]
direction TB
subgraph Step1["1. 收件登记 (Query Hydrators)"]
Q1["了解收件人信息"]
Q2["查询收件人偏好"]
Q3["获取历史收件记录"]
end
subgraph Step2["2. 包裹来源 (Sources)"]
S1["本地仓库
Thunder: 朋友寄的包裹"]
S2["全国调货
Phoenix: 可能喜欢的商品"]
end
subgraph Step3["3. 包裹检查 (Hydrators)"]
H1["核实发件人身份"]
H2["检查包裹内容"]
H3["标记包裹属性"]
end
subgraph Step4["4. 安检筛选 (Filters)"]
F1["违禁品? 退回"]
F2["已收过? 退回"]
F3["发件人被拉黑? 退回"]
end
subgraph Step5["5. 优先级排序 (Scorers)"]
SC1["预测收件人满意度"]
SC2["计算综合优先级"]
end
subgraph Step6["6. 装车派送 (Selector)"]
SEL["选出最重要的50个包裹派送"]
end
Step1 --> Step2 --> Step3 --> Step4 --> Step5 --> Step6
end
一个请求的完整生命周期
假设用户小红打开 App 请求推荐:
| 阶段 | 发生了什么 | 具体例子 |
|---|---|---|
| Query Hydration | 了解小红是谁 | 查到小红喜欢美妆、关注了50个账号、最近点赞了护肤内容 |
| Sources | 收集候选内容 | Thunder返回300条关注者帖子,Phoenix返回500条网外帖子 |
| Hydrators | 补充帖子信息 | 给每篇帖子补上作者粉丝数、是否有视频、发布时间等 |
| Filters | 过滤不合适的 | 去掉小红已看过的、作者被小红屏蔽的、超过48小时的 |
| Scorers | 预测打分 | 预测小红对每篇帖子点赞/转发/停留的概率 |
| Selector | 选出最终结果 | 从剩余400条中选出分数最高的50条 |
关键洞察:整个过程约200毫秒完成,其中很多步骤是并行执行的(比如同时从Thunder和Phoenix获取候选)。
3.2 Thunder(实时帖子存储)
Thunder 是一个高性能的内存帖子存储服务,用于快速检索网内内容。
flowchart TB
subgraph Thunder["Thunder 服务架构"]
direction TB
subgraph Input["数据输入"]
KAFKA[Kafka 事件流]
TEL1[TweetEventsListener V1]
TEL2[TweetEventsListener V2]
end
subgraph Storage["存储层"]
PS[PostStore]
subgraph UserStores["用户帖子存储"]
OP[原创帖子
Original Posts]
SP[次级帖子
Replies/Retweets]
VP[视频帖子
Video Posts]
end
end
subgraph Service["服务层"]
TS[Thunder Service]
GRPC[gRPC Server]
end
subgraph Maintenance["维护任务"]
TRIM[自动清理任务
Auto-Trim Task]
end
end
KAFKA --> TEL1
KAFKA --> TEL2
TEL1 --> PS
TEL2 --> PS
PS --> OP
PS --> SP
PS --> VP
TS --> PS
GRPC --> TS
TRIM --> PS
Client[Home Mixer] -->|GetInNetworkPosts| GRPC
3.2.1 数据结构
erDiagram
PostStore ||--o{ UserPostStore : contains
UserPostStore ||--o{ TinyPost : has_original
UserPostStore ||--o{ TinyPost : has_secondary
UserPostStore ||--o{ TinyPost : has_video
TinyPost ||--|| LightPost : references
PostStore {
DashMap users
DashMap all_posts
Duration retention_period
}
UserPostStore {
i64 user_id
VecDeque original_posts
VecDeque secondary_posts
VecDeque video_posts
}
TinyPost {
i64 post_id
i64 created_at
}
LightPost {
i64 post_id
i64 author_id
i64 created_at
bool is_retweet
bool is_reply
bool has_video
String text
}
3.2.2 三层帖子分类
Thunder 按内容类型将帖子分为三类,分别存储和检索:
flowchart TB
subgraph PostClassification["帖子三层分类"]
direction TB
POST["新帖子入库"] --> CHECK1{"是回复或转发?"}
CHECK1 -->|否| ORIGINAL["原创帖 original_posts"]
CHECK1 -->|是| SECONDARY["次级帖 secondary_posts"]
ORIGINAL --> CHECK2{"包含视频?"}
SECONDARY --> CHECK3{"是转发且源帖有视频?"}
CHECK2 -->|是| VIDEO1["同时存入 video_posts"]
CHECK3 -->|是| VIDEO2["同时存入 video_posts"]
end
| 分类 | 包含内容 | 检索上限 | 用途 |
|---|---|---|---|
| original_posts | 非回复、非转发的原创内容 | MAX_ORIGINAL_POSTS_PER_AUTHOR | 核心时间线内容 |
| secondary_posts | 回复和转发 | MAX_REPLY_POSTS_PER_AUTHOR | 补充互动内容 |
| video_posts | 带视频的原创或转发 | MAX_VIDEO_POSTS_PER_AUTHOR | 视频专属推荐 |
3.2.3 保留期与自动清理
flowchart LR
subgraph Retention["内容保留机制"]
NEW["新帖子"] --> STORE["存入 PostStore"]
STORE --> TIMER["retention_period
默认 48 小时"]
TIMER --> TRIM["Auto-Trim Task"]
TRIM --> |"定期清理"| DELETE["移除过期帖子"]
end
关键参数:
retention_period: 默认 172,800 秒(48 小时)- 清理任务定期运行,移除超过保留期的帖子
- 统计日志每 5 秒输出一次存储状态
创作者启示:内容在 Thunder 中最多保留约 48 小时,超过此时间将不再出现在关注者的网内推荐中。这解释了为什么"热度"有明确的时间窗口。
3.2.4 并发控制与性能保护
flowchart TB
subgraph Concurrency["并发保护机制"]
REQ["请求到达"] --> SEM{"信号量检查"}
SEM -->|"有空位"| PROCESS["处理请求"]
SEM -->|"已满"| REJECT["RESOURCE_EXHAUSTED"]
PROCESS --> SPAWN["spawn_blocking"]
SPAWN --> |"避免阻塞异步运行时"| QUERY["执行查询"]
QUERY --> TIMEOUT{"超时检查"}
TIMEOUT -->|"超时"| PARTIAL["返回部分结果"]
TIMEOUT -->|"正常"| FULL["返回完整结果"]
end
| 保护机制 | 作用 | 场景示例 |
|---|---|---|
| 请求信号量 | 限制并发请求数 | 防止突发流量压垮服务 |
| spawn_blocking | 隔离阻塞操作 | 保护异步运行时不被阻塞 |
| 请求超时 | 防止长时间等待 | 关注列表过大时快速降级 |
实际案例:如果用户关注了 10 万个账号,系统会设置超时,可能只返回部分内容而非完全失败。
3.2.5 次级帖子过滤规则
回复和转发有特殊的过滤逻辑:
flowchart TB
subgraph SecondaryFiltering["次级帖子过滤"]
SEC["次级帖子"] --> CHECK1{"是自己转发自己?"}
CHECK1 -->|是| DROP1["排除"]
CHECK1 -->|否| CHECK2{"是嵌套回复?"}
CHECK2 -->|是| CHECK3{"回复对象是原帖
且作者在关注列表?"}
CHECK3 -->|是| KEEP["保留"]
CHECK3 -->|否| DROP2["排除"]
CHECK2 -->|否| KEEP
end
过滤原因:
- 自己转发自己:避免刷屏
- 嵌套回复:只保留与关注者直接相关的对话
3.3 Phoenix(ML模型服务)
Phoenix 包含两个核心模型:检索模型和排序模型。
flowchart TB
subgraph Phoenix["Phoenix ML服务"]
direction TB
subgraph Retrieval["检索阶段 - Two-Tower Model"]
direction LR
UT[用户塔
User Tower]
CT[候选塔
Candidate Tower]
SIM[相似度计算
Dot Product]
UT -->|用户嵌入 B,D| SIM
CT -->|帖子嵌入 N,D| SIM
SIM -->|Top-K候选| OUT1[候选列表]
end
subgraph Ranking["排序阶段 - Grok Transformer"]
direction TB
subgraph Input["输入处理"]
UI[用户输入]
HI[历史输入]
CI[候选输入]
end
subgraph Model["模型架构"]
EMB[Hash嵌入层]
TRANS[Transformer层]
HEAD[预测头]
end
subgraph Output["输出"]
PRED[多动作预测
14+动作概率]
end
UI --> EMB
HI --> EMB
CI --> EMB
EMB --> TRANS
TRANS --> HEAD
HEAD --> PRED
end
end
用相亲网站比喻 Phoenix
这部分的逻辑跟抖音的推荐系统逻辑类似,感兴趣也可以看看:https://95152.douyin.com/article/15358
Phoenix 的工作就像一个智能相亲网站,分两步帮你找到合适的对象:
flowchart TB
subgraph PhoenixAnalogy["Phoenix = 智能相亲网站"]
direction TB
subgraph Step1["第一步: 海选 (Two-Tower 检索)"]
YOU["你的画像
喜欢技术、爱运动、25岁"]
POOL["全平台候选池
数十亿用户"]
MATCH["向量相似度匹配"]
RESULT1["初筛500人
基础画像匹配"]
YOU --> MATCH
POOL --> MATCH
MATCH --> RESULT1
end
subgraph Step2["第二步: 精选 (Grok 排序)"]
DETAIL["深入分析
性格、价值观、生活方式"]
PREDICT["预测匹配度
会聊天? 会见面? 会交往?"]
RESULT2["精选50人
最可能成功的"]
RESULT1 --> DETAIL
DETAIL --> PREDICT
PREDICT --> RESULT2
end
end
两阶段模型对比
| 阶段 | 模型 | 比喻 | 处理规模 | 精度 |
|---|---|---|---|---|
| 检索 | Two-Tower | 海选:看基础条件 | 数十亿→数百 | 较粗 |
| 排序 | Grok Transformer | 精选:深入了解 | 数百→数十 | 精细 |
具体例子:为技术博主小张找读者
第一步:Two-Tower 检索
flowchart LR
subgraph UserTower["用户塔: 编码小张"]
U1["关注了React、Vue"]
U2["最近发了前端教程"]
U3["粉丝多为程序员"]
UV["→ 用户向量 [0.8, 0.2, ...]"]
end
subgraph CandidateTower["候选塔: 编码所有帖子"]
C1["帖子A: React性能优化"]
C2["帖子B: 美食探店"]
C3["帖子C: Python入门"]
CV["→ 帖子向量 [...], [...], [...]"]
end
UV --> |"计算相似度"| SIM["相似度排序"]
CV --> SIM
SIM --> TOP["Top-500: 主要是技术帖子"]
第二步:Grok Transformer 排序
对这500篇帖子,模型深入分析并预测:
| 帖子 | 预测点赞概率 | 预测回复概率 | 预测转发概率 | 最终排名 |
|---|---|---|---|---|
| React性能优化 | 45% | 12% | 8% | #1 |
| Vue3新特性 | 38% | 15% | 10% | #2 |
| TypeScript技巧 | 35% | 8% | 5% | #3 |
| Python入门 | 5% | 1% | 0.5% | #450 |
关键洞察:第一步只看"大方向是否匹配",第二步才精细预测具体互动行为。这就是为什么算法既能快速处理海量内容,又能精准预测用户偏好。
3.3.1 Grok Transformer 架构细节
十万张显卡,真的太明智了,现在看布局非常厉害了。
flowchart TB
subgraph GrokModel["Grok Transformer 架构"]
direction TB
subgraph Embeddings["嵌入层"]
direction LR
UE[用户嵌入
UserEmbedding]
PE[帖子嵌入
PostEmbedding]
AE[作者嵌入
AuthorEmbedding]
ACT[动作嵌入
ActionEmbedding]
end
subgraph Attention["注意力机制"]
direction TB
subgraph Mask["注意力掩码"]
M1[用户+历史: 全双向注意力]
M2[候选→用户/历史: 允许]
M3[候选→候选: 仅自注意力]
end
MHA[多头注意力
Multi-Head Attention]
end
subgraph FFN["前馈网络"]
GEGLU[GeGLU激活]
DENSE[Dense层]
end
subgraph Prediction["预测层"]
LOGITS[Logits]
SIGMOID[Sigmoid]
PROBS[概率输出]
end
end
Embeddings --> MHA
Mask --> MHA
MHA --> FFN
FFN --> LOGITS
LOGITS --> SIGMOID
SIGMOID --> PROBS
3.3.2 候选隔离机制
用通俗的话解释
想象你是一位老师,要给100篇作文打分。有两种打分方式:
方式A(传统方式):把100篇作文放在一起比较,互相参考着打分
- 问题:如果换一批作文一起评,同一篇作文的分数可能会变
方式B(候选隔离):每篇作文独立打分,只参考学生的历史表现,不看其他作文
- 优点:无论和哪些作文一起评,同一篇作文的分数始终一致
X 的算法采用方式B,这就是"候选隔离"。
flowchart TB
subgraph Traditional["传统方式: 候选互相影响"]
direction LR
T1["帖子A"] <--> T2["帖子B"]
T2 <--> T3["帖子C"]
T1 <--> T3
T_NOTE["A的分数受B、C影响"]
end
subgraph Isolated["候选隔离: 独立评分"]
direction LR
I1["帖子A"] --> USER["用户偏好"]
I2["帖子B"] --> USER
I3["帖子C"] --> USER
I_NOTE["每个帖子只看用户,不看其他帖子"]
end
具体例子
假设系统要为用户小明推荐内容,候选池有3篇帖子:
| 帖子 | 内容 | 作者 |
|---|---|---|
| 帖子A | Python教程 | 技术博主张三 |
| 帖子B | 美食探店 | 美食博主李四 |
| 帖子C | 健身打卡 | 健身博主王五 |
小明的历史行为:最近点赞了5篇技术文章
flowchart TB
subgraph Scoring["评分过程"]
direction TB
subgraph Context["共享上下文: 小明的信息"]
U["小明的画像"]
H["历史: 点赞5篇技术文章"]
end
subgraph Independent["独立评分: 每个帖子单独计算"]
direction LR
subgraph ScoreA["帖子A评分"]
A["Python教程"]
A --> |"参考"| U
A --> |"参考"| H
A --> |"不参考"| B
A --> |"不参考"| C
SA["预测: 80%会点赞"]
end
subgraph ScoreB["帖子B评分"]
B["美食探店"]
B --> |"参考"| U
B --> |"参考"| H
SB["预测: 15%会点赞"]
end
subgraph ScoreC["帖子C评分"]
C["健身打卡"]
C --> |"参考"| U
C --> |"参考"| H
SC["预测: 20%会点赞"]
end
end
end
关键点:
- 帖子A的80%预测分,是基于"小明喜欢技术内容"得出的
- 这个分数不会因为帖子B、C的存在而改变
- 即使把帖子B、C换成其他内容,帖子A的分数仍然是80%
为什么这很重要?
flowchart LR
subgraph Benefits["候选隔离的好处"]
direction TB
B1["公平性"]
B1_DESC["你的帖子分数
不受同批次其他帖子影响"]
B2["可缓存"]
B2_DESC["同一用户+同一帖子
分数可以复用"]
B3["可解释"]
B3_DESC["分数只取决于
用户偏好×内容匹配度"]
end
创作者启示
| 传统方式的问题 | 候选隔离的保障 |
|---|---|
| 如果同时有很多热门帖子,你的帖子可能被"比下去" | 你的帖子分数独立计算,不受其他帖子影响 |
| 不同时间发布,面对不同竞争对手,分数不稳定 | 同一用户看你的帖子,预测分数始终一致 |
| 难以判断内容质量,因为分数受环境影响 | 分数直接反映"用户偏好×内容匹配",更有参考价值 |
实际案例:你发了一篇技术教程,在技术爱好者小明的信息流中预测分是85%。无论小明的候选池里还有什么其他内容(可能是热门娱乐新闻、大V的爆款帖子),你这篇帖子对小明的预测分始终是85%。最终排名取决于所有帖子各自的独立分数排序。
技术细节(可选阅读)
候选隔离通过特殊的注意力掩码实现:
1模型输入: [用户信息, 历史行为1, 历史行为2, ..., 候选1, 候选2, 候选3]
2
3注意力规则:
4- 用户和历史: 可以互相"看到"(双向注意力)
5- 候选帖子: 只能"看到"用户和历史,看不到其他候选
6- 结果: 每个候选的分数只由用户+历史决定
这种设计让系统可以:
- 一次性处理多个候选(高效)
- 保证每个候选分数独立(公平)
- 缓存已计算的分数(节省资源)
四、数据流程
4.1 完整请求处理流程
flowchart TB
START([开始: 用户请求推荐]) --> QH
subgraph QH["1.查询增强 (并行)"]
direction LR
QH1[UserActionSeqHydrator
获取用户行为历史]
QH2[UserFeaturesHydrator
获取用户元数据]
end
QH --> SOURCES
subgraph SOURCES["2.候选获取 (并行)"]
direction LR
S1[ThunderSource
网内帖子]
S2[PhoenixSource
网外帖子]
end
SOURCES --> HYDRATORS
subgraph HYDRATORS["3.候选增强 (并行)"]
direction TB
H1[CoreDataHydrator
帖子核心数据]
H2[GizmoduckHydrator
作者信息]
H3[InNetworkHydrator
网络关系]
H4[SubscriptionHydrator
订阅状态]
H5[VideoDurationHydrator
视频时长]
H6[VFHydrator
可见性信息]
end
HYDRATORS --> FILTERS
subgraph FILTERS["4.预评分过滤 (顺序)"]
direction TB
F1[DropDuplicatesFilter
去重]
F2[CoreDataHydrationFilter
核心数据校验]
F3[AgeFilter
时效性过滤]
F4[SelfTweetFilter
过滤自己的帖子]
F5[AuthorSocialgraphFilter
屏蔽/静音用户]
F6[MutedKeywordFilter
静音关键词]
F7[PreviouslySeenPostsFilter
已浏览帖子]
F8[PreviouslyServedPostsFilter
已推送帖子]
F9[IneligibleSubscriptionFilter
付费墙检查]
F10[RetweetDeduplicationFilter
转发去重]
F1 --> F2 --> F3 --> F4 --> F5 --> F6 --> F7 --> F8 --> F9 --> F10
end
FILTERS --> SCORERS
subgraph SCORERS["5.评分 (顺序)"]
direction TB
SC1[PhoenixScorer
ML预测]
SC2[WeightedScorer
加权计算]
SC3[AuthorDiversityScorer
作者多样性]
SC4[OONScorer
网外调整]
SC1 --> SC2 --> SC3 --> SC4
end
SCORERS --> SELECTOR
subgraph SELECTOR["6.选择"]
SEL[TopKScoreSelector
选择Top-K]
end
SELECTOR --> POST
subgraph POST["7.后处理"]
direction TB
PH[Post-Selection Hydration
后选增强]
PF1[VFFilter
可见性检查]
PF2[DedupConversationFilter
对话去重]
PH --> PF1 --> PF2
end
POST --> SIDE
subgraph SIDE["8.副作用 (异步)"]
SE[CacheRequestInfoSideEffect
缓存请求信息]
end
SIDE --> END([结束: 返回排序帖子列表])
用餐厅点餐比喻请求处理流程
把推荐请求想象成你走进一家智能自助餐厅:
flowchart TB
subgraph RequestAnalogy["一次推荐请求 = 一次智能点餐"]
direction TB
subgraph Phase1["1. 了解顾客 (Query Hydration)"]
P1A["服务员查看你的会员卡"]
P1B["调出你的口味档案"]
P1C["查询你最近吃过什么"]
end
subgraph Phase2["2. 备选菜品 (Sources)"]
P2A["厨房A: 你常点的菜
(Thunder 网内)"]
P2B["厨房B: 今日主厨推荐
(Phoenix 网外)"]
end
subgraph Phase3["3. 菜品质检 (Hydrators)"]
P3A["核实原材料新鲜度"]
P3B["查看厨师评分"]
P3C["确认是否含过敏原"]
end
subgraph Phase4["4. 剔除不合适的 (Filters)"]
P4A["你过敏的? 移除"]
P4B["你吃过的? 移除"]
P4C["你拉黑的厨师? 移除"]
end
subgraph Phase5["5. 口味预测 (Scorers)"]
P5A["预测你对每道菜的满意度"]
P5B["综合评分排序"]
end
subgraph Phase6["6. 端上餐桌 (Selector)"]
P6A["选出评分最高的50道菜"]
end
Phase1 --> Phase2 --> Phase3 --> Phase4 --> Phase5 --> Phase6
end
具体例子:小明的一次刷推
背景:用户小明打开 App,系统需要在 200 毫秒内返回推荐。
flowchart TB
subgraph ConcreteExample["小明的这次刷新"]
START["小明下拉刷新"] --> Q
subgraph Q["1. 了解小明 (15ms)"]
Q1["查到小明ID: 12345"]
Q2["发现小明喜欢: 科技、游戏"]
Q3["最近点赞了: 3篇AI文章"]
Q4["屏蔽了: 2个营销号"]
end
Q --> S
subgraph S["2. 收集候选 (50ms)"]
S1["Thunder返回:
关注者的 350 条帖子"]
S2["Phoenix返回:
可能喜欢的 650 条帖子"]
S3["合计: 1000 条"]
end
S --> H
subgraph H["3. 补充信息 (30ms)"]
H1["补上每条帖子的:
作者粉丝数、发布时间
是否有视频、视频时长"]
end
H --> F
subgraph F["4. 过滤 (20ms)"]
F1["去掉重复: -50"]
F2["去掉超过7天: -80"]
F3["去掉自己发的: -5"]
F4["去掉屏蔽作者: -30"]
F5["去掉已看过: -200"]
F6["剩余: 635 条"]
end
F --> SC
subgraph SC["5. 打分 (60ms)"]
SC1["Phoenix预测小明对每条的:
点赞概率、转发概率、停留时间..."]
SC2["加权计算综合分数"]
SC3["调整作者多样性"]
end
SC --> SEL
subgraph SEL["6. 选择 (5ms)"]
SEL1["选出分数最高的 50 条"]
end
SEL --> END["返回给小明
总耗时: ~180ms"]
end
各阶段耗时分布
| 阶段 | 耗时 | 并行方式 | 做了什么 |
|---|---|---|---|
| Query Hydration | ~15ms | 并行查询用户信息 | 获取小明的偏好、历史、社交关系 |
| Sources | ~50ms | Thunder和Phoenix并行 | 收集1000条候选帖子 |
| Hydrators | ~30ms | 6个Hydrator并行 | 补充帖子详细信息 |
| Filters | ~20ms | 顺序执行10个过滤器 | 从1000条筛到635条 |
| Scorers | ~60ms | Phoenix模型预测 | 给635条帖子打分 |
| Selector | ~5ms | Top-K选择 | 选出最终50条 |
关键洞察:整个过程大量使用并行化,但过滤器必须顺序执行(因为后面的过滤器依赖前面的结果)。模型预测是最耗时的环节。
4.2 过滤器详细流程
flowchart TB
subgraph FilterChain["过滤器链"]
direction TB
INPUT[输入候选
~1000条] --> F1
F1[DropDuplicatesFilter
移除重复ID] -->|去重后| F2
F2[CoreDataHydrationFilter
确保核心数据完整] -->|有效数据| F3
F3[AgeFilter
移除过期帖子
默认7天] -->|时效内| F4
F4[SelfTweetFilter
移除用户自己的帖子] -->|非自己| F5
F5[AuthorSocialgraphFilter
移除已屏蔽/静音作者] -->|未屏蔽| F6
F6[MutedKeywordFilter
移除含静音关键词的帖子] -->|无静音词| F7
F7[PreviouslySeenPostsFilter
移除最近浏览过的帖子] -->|未见过| F8
F8[PreviouslyServedPostsFilter
移除最近推送过的帖子] -->|未推送| F9
F9[IneligibleSubscriptionFilter
移除无权访问的付费内容] -->|可访问| F10
F10[RetweetDeduplicationFilter
同一原帖的转发只保留一条] -->|去重转发| OUTPUT
OUTPUT[输出候选
~200-500条]
end
style INPUT fill:#e1f5fe
style OUTPUT fill:#c8e6c9
用安检流程比喻过滤器链
把过滤器链想象成机场安检流程,每道关卡检查特定问题:
flowchart TB
subgraph FilterAnalogy["过滤器链 = 机场安检"]
direction TB
PASSENGER["1000名乘客
(候选帖子)"] --> C1
subgraph C1["关卡1: 身份核验"]
CHECK1["有人冒用别人身份证?
(重复ID)"]
end
C1 --> C2
subgraph C2["关卡2: 证件完整性"]
CHECK2["证件信息不全?
(核心数据缺失)"]
end
C2 --> C3
subgraph C3["关卡3: 签证有效期"]
CHECK3["签证过期?
(帖子超过7天)"]
end
C3 --> C4
subgraph C4["关卡4: 禁飞名单"]
CHECK4["在黑名单上?
(被用户屏蔽的作者)"]
end
C4 --> C5
subgraph C5["关卡5: 违禁物品"]
CHECK5["携带违禁词?
(含静音关键词)"]
end
C5 --> C6
subgraph C6["关卡6: 重复登机"]
CHECK6["已经登过机?
(已看过/已推送)"]
end
C6 --> BOARD["约400名乘客通过
(进入评分阶段)"]
end
具体例子:1000条帖子的过滤之旅
假设用户小红请求推荐,系统收集了 1000 条候选帖子,看它们如何通过过滤器链:
flowchart TB
subgraph FilterExample["小红的1000条帖子过滤过程"]
START["输入: 1000 条帖子"] --> F1
F1["1. DropDuplicatesFilter
发现50条重复ID
剩余: 950"] --> F2
F2["2. CoreDataHydrationFilter
30条数据不完整
剩余: 920"] --> F3
F3["3. AgeFilter
80条超过7天
剩余: 840"] --> F4
F4["4. SelfTweetFilter
5条是小红自己发的
剩余: 835"] --> F5
F5["5. AuthorSocialgraphFilter
小红屏蔽了3个作者,共45条
剩余: 790"] --> F6
F6["6. MutedKeywordFilter
小红静音了'广告'关键词,命中20条
剩余: 770"] --> F7
F7["7. PreviouslySeenPostsFilter
小红最近看过180条
剩余: 590"] --> F8
F8["8. PreviouslyServedPostsFilter
已推送过120条
剩余: 470"] --> F9
F9["9. IneligibleSubscriptionFilter
15条付费内容小红无权看
剩余: 455"] --> F10
F10["10. RetweetDeduplicationFilter
同一原帖被转发多次,去重35条
剩余: 420"] --> END
END["输出: 420 条进入评分"]
end
style START fill:#e1f5fe
style END fill:#c8e6c9
过滤效果统计
| 过滤器 | 过滤掉 | 过滤原因 | 创作者启示 |
|---|---|---|---|
| DropDuplicates | 50 | 重复帖子ID | 正常技术去重,无需关注 |
| CoreDataHydration | 30 | 数据异常 | 确保帖子发布成功 |
| AgeFilter | 80 | 超过7天 | 48小时内是黄金曝光期 |
| SelfTweet | 5 | 用户自己发的 | 正常逻辑,自己发的不推给自己 |
| AuthorSocialgraph | 45 | 被用户屏蔽 | 避免引战,减少被拉黑 |
| MutedKeyword | 20 | 含静音关键词 | 避免使用常见广告词 |
| PreviouslySeen | 180 | 用户看过 | 无法二次触达同一用户 |
| PreviouslyServed | 120 | 已经推送过 | 同上 |
| IneligibleSubscription | 15 | 付费墙 | 考虑免费内容引流 |
| RetweetDedup | 35 | 转发去重 | 被多人转发说明内容有传播性 |
| 总计 | 580 | - | 58%的候选被过滤 |
关键洞察:超过一半的候选在评分前就被过滤掉。“已看过"和"已推送"是最大的过滤器,这解释了为什么持续产出新内容很重要——旧内容无法重复触达同一用户。
创作者实战建议
基于过滤器机制,创作者可以:
- 保持发布频率:旧内容会被"已看过"过滤器拦截,新内容才有机会
- 避免引战:被用户屏蔽后,你的所有内容都会被该用户过滤
- 慎用敏感词:用户可能静音了某些关键词,避免不必要的词汇
- 内容要完整:确保帖子正常发布,数据完整才能通过检验
4.3 用户行为序列(UAS)处理
用户行为序列是 Phoenix 模型的关键输入,记录用户的历史互动行为。
flowchart TB
subgraph UASPipeline["UAS 处理流程"]
direction TB
FETCH["从 UAS Fetcher 获取
Thrift 格式行为序列"] --> PRE["预聚合过滤"]
PRE --> |"KeepOriginalUserActionFilter"| AGG["时间窗口聚合"]
AGG --> |"UAS_WINDOW_TIME_MS"| POST["后聚合过滤"]
POST --> |"DenseAggregatedActionFilter"| TRUNC["序列截断"]
TRUNC --> |"保留最后 N 条"| OUTPUT["UserActionSequence
输出到 Phoenix"]
end
处理步骤详解
| 步骤 | 操作 | 目的 |
|---|---|---|
| 获取 | 从 UAS 服务拉取原始行为流 | 获取完整历史 |
| 预过滤 | 保留原始动作,过滤衍生动作 | 减少噪音 |
| 时间聚合 | 在 UAS_WINDOW_TIME_MS 内聚合 | 合并短时间内重复行为 |
| 后过滤 | 密集聚合动作过滤 | 进一步精简 |
| 截断 | 保留最后 UAS_MAX_SEQUENCE_LENGTH 条 | 控制模型输入长度 |
行为类型
mindmap
root((用户行为类型))
正向互动
Favorite 点赞
Reply 回复
Retweet 转发
Quote 引用
内容消费
Click 点击
VideoView 视频观看
PhotoExpand 图片展开
社交行为
ProfileClick 主页点击
Follow 关注
负向反馈
NotInterested 不感兴趣
Block 屏蔽
Mute 静音
Report 举报
创作者启示
| 洞察 | 说明 |
|---|---|
| 历史影响预测 | 算法会参考用户过去与类似内容的互动来预测对你内容的反应 |
| 行为有窗口期 | 只有时间窗口内的行为被聚合,非常久远的行为权重降低 |
| 序列有长度限制 | 只保留最近的 N 条行为,更早的行为被丢弃 |
| 负向行为被记录 | 屏蔽、静音等负向行为同样影响后续推荐 |
实际案例:如果用户最近频繁点赞技术类内容,模型会预测该用户更可能点赞你的技术帖子。反之,如果用户近期对某类内容频繁标记"不感兴趣”,你的同类内容预测分会降低。
五、ML模型架构
5.1 Two-Tower 检索模型
flowchart TB
subgraph TwoTower["Two-Tower 检索模型"]
direction TB
subgraph UserTower["用户塔 (User Tower)"]
direction TB
UF[用户特征]
UH[用户行为历史]
UE1[用户嵌入层]
UE2[用户编码器]
UV["用户向量 B x D"]
UF --> UE1
UH --> UE1
UE1 --> UE2
UE2 --> UV
end
subgraph CandidateTower["候选塔 (Candidate Tower)"]
direction TB
CF[帖子特征]
AF[作者特征]
CE1[候选嵌入层]
CE2[候选编码器]
CV["候选向量 N x D"]
CF --> CE1
AF --> CE1
CE1 --> CE2
CE2 --> CV
end
subgraph Matching["匹配层"]
DOT[点积相似度
UV · CV^T]
TOPK[Top-K选择]
UV --> DOT
CV --> DOT
DOT --> TOPK
end
end
TOPK --> OUT[检索候选列表]
用相亲配对比喻 Two-Tower 模型
把 Two-Tower 模型想象成一个智能相亲系统:
flowchart TB
subgraph TwoTowerAnalogy["Two-Tower = 相亲配对系统"]
direction TB
subgraph LeftTower["左塔: 了解你是谁"]
L1["你的基本信息
(年龄、职业、城市)"]
L2["你的兴趣爱好
(最近喜欢什么)"]
L3["你的择偶标准
(历史互动偏好)"]
L4["生成你的'画像向量'"]
end
subgraph RightTower["右塔: 了解候选对象"]
R1["候选人基本信息"]
R2["候选人特点"]
R3["候选人背景"]
R4["生成候选人'画像向量'"]
end
subgraph Matching["配对环节"]
M1["计算你和每个候选人的
'契合度分数'"]
M2["选出契合度最高的 1000 人
进入下一轮面试"]
end
LeftTower --> Matching
RightTower --> Matching
end
具体例子:为小明找兴趣帖子
场景:小明刷推荐,系统需要从数百万条帖子中快速筛选出可能感兴趣的 1000 条。
flowchart TB
subgraph TwoTowerExample["Two-Tower 为小明检索帖子"]
direction TB
subgraph UserTowerEx["用户塔处理小明"]
U1["输入小明的信息:
- 用户ID: 12345
- 最近点赞: AI、游戏、数码
- 关注的人: 100个科技博主"]
U2["编码成128维向量:
[0.23, -0.15, 0.87, ...]"]
end
subgraph CandTowerEx["候选塔处理帖子"]
C1["预计算所有帖子的向量
(离线处理,非实时)"]
C2["帖子A: [0.21, -0.12, 0.85, ...]
帖子B: [-0.5, 0.3, 0.1, ...]
帖子C: [0.19, -0.18, 0.82, ...]
..."]
end
subgraph MatchEx["匹配计算"]
M1["小明向量 · 帖子A向量 = 0.95 (很高!)"]
M2["小明向量 · 帖子B向量 = 0.12 (很低)"]
M3["小明向量 · 帖子C向量 = 0.91 (很高!)"]
M4["选出分数最高的1000条"]
end
UserTowerEx --> MatchEx
CandTowerEx --> MatchEx
end
Two-Tower 的优势
| 特性 | 说明 | 实际意义 |
|---|---|---|
| 离线预计算 | 候选塔可以提前计算所有帖子的向量 | 不用每次请求都重新计算帖子特征 |
| 高效检索 | 向量点积可以用近似最近邻(ANN)加速 | 从百万帖子中毫秒级找到Top-K |
| 独立更新 | 用户塔和候选塔可以分别训练更新 | 新帖子发布后快速入库 |
创作者启示:Two-Tower 决定了你的帖子能否进入"海选"。如果你的内容向量与大量用户的兴趣向量相似度低,就很难被检索出来。这就是为什么垂直领域持续输出很重要——让你的内容向量稳定在某个兴趣空间。
5.2 排序模型输入输出
flowchart LR
subgraph Input["模型输入"]
direction TB
subgraph UserInput["用户输入"]
UI1[用户ID哈希]
UI2[用户特征嵌入]
end
subgraph HistoryInput["历史输入"]
HI1[帖子嵌入序列]
HI2[作者嵌入序列]
HI3[动作类型序列]
end
subgraph CandidateInput["候选输入"]
CI1[候选帖子嵌入]
CI2[候选作者嵌入]
end
end
subgraph Model["Grok Transformer"]
TRANS[Transformer
with Candidate Isolation]
end
subgraph Output["模型输出"]
direction TB
subgraph PositiveActions["正向动作概率"]
PA1["P_like"]
PA2["P_reply"]
PA3["P_repost"]
PA4["P_quote"]
PA5["P_click"]
PA6["P_video_view"]
PA7["P_share"]
PA8["P_follow"]
end
subgraph NegativeActions["负向动作概率"]
NA1["P_not_interested"]
NA2["P_block"]
NA3["P_mute"]
NA4["P_report"]
end
end
Input --> Model
Model --> Output
用面试官打分比喻排序模型
把排序模型想象成一个经验丰富的面试官,需要给每个候选人打出多维度评分:
flowchart TB
subgraph RankingAnalogy["排序模型 = 面试官评估"]
direction TB
subgraph InputInfo["面试官收到的信息"]
I1["候选人简历
(帖子内容、作者)"]
I2["你的偏好档案
(用户历史行为)"]
I3["你最近面试过的人
(行为序列)"]
end
subgraph Evaluation["评估过程"]
E1["Transformer 神经网络
综合分析所有信息"]
end
subgraph Scores["打分结果"]
S1["你会喜欢这人吗? 85%"]
S2["你会和他聊天吗? 45%"]
S3["你会推荐给朋友吗? 30%"]
S4["你会反感这人吗? 5%"]
end
InputInfo --> Evaluation --> Scores
end
具体例子:小明看到一条 AI 帖子
假设候选池中有一条关于 ChatGPT 的帖子,看模型如何预测小明的反应:
flowchart TB
subgraph RankingExample["模型预测小明对 AI 帖子的反应"]
direction TB
subgraph ModelInput["模型输入"]
IN1["小明最近行为:
- 点赞了3篇AI文章
- 转发了1篇GPT教程
- 关注了2个AI博主"]
IN2["这条帖子信息:
- 内容: ChatGPT新功能介绍
- 作者: 科技博主(10万粉)
- 有3张配图"]
end
subgraph Prediction["模型预测"]
P1["分析小明对AI内容的历史反应"]
P2["分析这个作者的内容质量"]
P3["综合判断匹配度"]
end
subgraph Output["输出概率"]
O1["P_like = 0.72
(72%可能点赞)"]
O2["P_reply = 0.15
(15%可能评论)"]
O3["P_retweet = 0.25
(25%可能转发)"]
O4["P_click = 0.85
(85%可能点击看详情)"]
O5["P_not_interested = 0.03
(3%可能标记不感兴趣)"]
end
ModelInput --> Prediction --> Output
end
排序模型的关键特点
| 特点 | 说明 | 对创作者的意义 |
|---|---|---|
| 多任务预测 | 同时预测12+种用户行为 | 综合表现好的内容排名高 |
| 正负信号并存 | 既预测点赞也预测屏蔽 | 避免引发负向反馈很重要 |
| 依赖历史行为 | 参考用户最近与类似内容的互动 | 持续吸引特定人群能形成正循环 |
| 候选隔离 | 每条帖子独立评估 | 你的内容不会被其他帖子"带节奏" |
关键洞察:排序模型不只看"用户会不会喜欢",还会预测"用户会不会反感"。一条帖子即使点赞概率高,如果举报概率也高,最终得分可能不理想。
5.3 Hash嵌入机制
flowchart TB
subgraph HashEmbedding["Hash嵌入机制"]
direction TB
ID[实体ID
例如: user_id=12345] --> HASH
subgraph HASH["多哈希函数"]
H1[Hash函数1]
H2[Hash函数2]
H3[Hash函数N]
end
subgraph Buckets["嵌入桶"]
B1[Bucket 1
size=vocab_size/n]
B2[Bucket 2
size=vocab_size/n]
B3[Bucket N
size=vocab_size/n]
end
subgraph Embeddings["嵌入向量"]
E1[Embedding 1]
E2[Embedding 2]
E3[Embedding N]
end
H1 -->|"hash_id mod size"| B1
H2 -->|"hash_id mod size"| B2
H3 -->|"hash_id mod size"| B3
B1 --> E1
B2 --> E2
B3 --> E3
E1 --> SUM[Sum/Concat]
E2 --> SUM
E3 --> SUM
SUM --> FINAL[最终嵌入向量]
end
subgraph Benefits["优势"]
BEN1[减少嵌入表大小]
BEN2[处理OOV问题]
BEN3[提高泛化能力]
end
用图书馆索引比喻 Hash 嵌入
传统方法需要为每个用户/帖子存储一个独立的"书架"(嵌入向量),但用户有数亿、帖子有数十亿,内存根本装不下。
Hash 嵌入的思路是:用多个共享书架,通过巧妙的索引方式让每个实体有独特的组合。
flowchart TB
subgraph HashAnalogy["Hash嵌入 = 共享书架系统"]
direction TB
subgraph Traditional["传统方式: 每人一个书架"]
T1["用户1 → 书架1"]
T2["用户2 → 书架2"]
T3["..."]
T4["用户10亿 → 书架10亿"]
T5["问题: 需要10亿个书架!"]
end
subgraph HashWay["Hash方式: 共享书架"]
H1["只有1000个共享书架"]
H2["用户1 → 书架23 + 书架456 + 书架789"]
H3["用户2 → 书架45 + 书架123 + 书架678"]
H4["通过不同组合,每人都有独特的'混合书架'"]
end
end
具体例子:用户 ID 如何变成向量
假设要把用户 ID = 12345 转换成模型能理解的向量:
flowchart TB
subgraph HashExample["把用户12345转成向量"]
direction TB
INPUT["用户ID: 12345"] --> HASH
subgraph HASH["三个不同的哈希函数"]
H1["哈希1: 12345 mod 1000 = 345"]
H2["哈希2: (12345×7) mod 1000 = 415"]
H3["哈希3: (12345×13) mod 1000 = 485"]
end
HASH --> LOOKUP
subgraph LOOKUP["查找对应的嵌入桶"]
L1["桶345的向量: [0.1, 0.2, ...]"]
L2["桶415的向量: [0.3, 0.1, ...]"]
L3["桶485的向量: [0.2, 0.4, ...]"]
end
LOOKUP --> COMBINE
subgraph COMBINE["合并成最终向量"]
C1["方式1: 相加求平均"]
C2["方式2: 拼接在一起"]
C3["结果: 用户12345的独特向量"]
end
end
Hash 嵌入的优势
| 问题 | 传统方案 | Hash嵌入方案 |
|---|---|---|
| 内存占用 | 10亿用户 × 128维 = 512GB | 100万桶 × 128维 = 512MB |
| 新用户 | 没见过的用户没有向量 | 哈希总能得到一个组合 |
| 冷启动 | 新用户体验差 | 相似ID有相似向量,可泛化 |
| 更新成本 | 每个用户单独更新 | 批量更新共享桶 |
技术背景:这就是为什么 X 能在有限资源下处理数亿用户和数十亿帖子的推荐。Hash 嵌入是工程上的关键突破。
六、候选管道框架
6.1 核心Trait定义
flowchart TB
subgraph Traits["核心 Trait"]
direction TB
SOURCE["Source
获取候选"]
HYDRATOR["Hydrator
增强候选信息"]
FILTER["Filter
过滤候选"]
QH["QueryHydrator
增强查询信息"]
SCORER["Scorer
评分"]
SELECTOR["Selector
选择Top-K"]
SIDEEFFECT["SideEffect
副作用处理"]
end
subgraph Pipeline["CandidatePipeline"]
direction TB
CP["候选管道
组合所有Trait"]
end
CP --> SOURCE
CP --> HYDRATOR
CP --> FILTER
CP --> QH
CP --> SCORER
CP --> SELECTOR
CP --> SIDEEFFECT
Trait 方法定义:
| Trait | 核心方法 | 作用 |
|---|---|---|
Source<Q,C> |
get_candidates(query) → Vec<C> |
从数据源获取候选 |
Hydrator<Q,C> |
hydrate(query, candidates) → Vec<C> |
补充候选的详细信息 |
Filter<Q,C> |
filter(query, candidates) → Vec<C> |
过滤不符合条件的候选 |
QueryHydrator<Q> |
hydrate(query) → Q |
补充查询的上下文信息 |
Scorer<Q,C> |
score(query, candidates) → Vec<C> |
给候选打分 |
Selector<Q,C> |
select(query, candidates, limit) → Vec<C> |
选出最终结果 |
SideEffect<Q,C> |
execute(query, candidates) |
执行副作用(如缓存) |
用工厂流水线比喻管道框架
把候选管道想象成一条汽车组装流水线,每个 Trait 就是一个工位:
flowchart TB
subgraph PipelineAnalogy["候选管道 = 汽车组装流水线"]
direction TB
subgraph Station1["工位1: Source"]
S1["从仓库取零件
(从各处获取候选帖子)"]
end
subgraph Station2["工位2: Hydrator"]
S2["给零件贴标签、测尺寸
(补充帖子详细信息)"]
end
subgraph Station3["工位3: Filter"]
S3["质检,不合格的扔掉
(过滤不适合的帖子)"]
end
subgraph Station4["工位4: Scorer"]
S4["给每辆车打质量分
(给每条帖子评分)"]
end
subgraph Station5["工位5: Selector"]
S5["选出最好的50辆出厂
(选出Top-K帖子)"]
end
subgraph Station6["工位6: SideEffect"]
S6["记录生产日志
(缓存请求信息)"]
end
Station1 --> Station2 --> Station3 --> Station4 --> Station5 --> Station6
end
每个 Trait 的职责
| Trait | 类比 | 职责 | 实际例子 |
|---|---|---|---|
| Source | 仓库管理员 | 从各处获取候选 | ThunderSource 从内存取关注者帖子 |
| Hydrator | 质检员 | 补充候选信息 | VideoDurationHydrator 补充视频时长 |
| Filter | 安检员 | 剔除不合格候选 | AgeFilter 剔除超过7天的帖子 |
| QueryHydrator | 档案员 | 了解"客户"信息 | UserFeaturesHydrator 获取用户偏好 |
| Scorer | 评估师 | 给候选打分 | WeightedScorer 计算综合分数 |
| Selector | 采购经理 | 选出最终结果 | TopKScoreSelector 选前50名 |
| SideEffect | 文书 | 记录处理过程 | CacheRequestInfoSideEffect 缓存请求 |
为什么这样设计?
flowchart TB
subgraph DesignBenefits["管道设计的好处"]
direction TB
B1["模块化
每个组件职责单一,易于测试"]
B2["可扩展
新增过滤器只需实现 Filter trait"]
B3["可复用
同一个 Hydrator 可用于多个管道"]
B4["可配置
通过组合不同组件构建不同管道"]
end
创作者启示:理解管道设计有助于理解你的内容是如何被"处理"的。每一步都是独立的,不会因为某一步出问题而影响其他步骤。这也意味着你可以通过优化某一步(如提高内容质量通过更多 Filter)来提升整体表现。
6.2 管道执行流程
stateDiagram-v2
[*] --> QueryHydration: 接收查询
QueryHydration --> CandidateFetching: 查询增强完成
note right of QueryHydration: 并行执行所有QueryHydrator
CandidateFetching --> CandidateHydration: 候选获取完成
note right of CandidateFetching: 并行执行所有Source
CandidateHydration --> PreScoringFiltering: 候选增强完成
note right of CandidateHydration: 并行执行所有Hydrator
PreScoringFiltering --> Scoring: 过滤完成
note right of PreScoringFiltering: 顺序执行Filter链
Scoring --> Selection: 评分完成
note right of Scoring: 顺序执行Scorer链
Selection --> PostSelectionHydration: 选择完成
note right of Selection: Top-K选择
PostSelectionHydration --> PostSelectionFiltering: 后选增强完成
PostSelectionFiltering --> SideEffects: 后选过滤完成
SideEffects --> [*]: 返回结果
note right of SideEffects: 异步执行副作用
具体例子:一条帖子如何走完管道
假设有一条科技博主发的 AI 帖子,看它如何在管道中被处理:
flowchart TB
subgraph PipelineExample["这条AI帖子的管道之旅"]
direction TB
START["AI帖子诞生
作者: 科技博主, 内容: ChatGPT教程"] --> S1
S1["1. Source阶段
Phoenix检索时发现这条帖子
加入候选池"] --> S2
S2["2. Hydrator阶段
补充信息: 作者10万粉
有3张配图, 无视频"] --> S3
S3["3. Filter阶段
✓ 未过期(发布2小时)
✓ 用户未屏蔽作者
✓ 不含静音词
通过!"] --> S4
S4["4. Scorer阶段
Phoenix预测: P_like=0.72
加权计算: score=8.5"] --> S5
S5["5. Selector阶段
在1000条中排第15名
进入Top-50!"] --> S6
S6["6. SideEffect阶段
记录'已推送给用户小明'"] --> END
END["成功! 出现在小明的时间线第15位"]
end
style START fill:#e1f5fe
style END fill:#c8e6c9
管道各阶段的并行与顺序
gantt
title 管道执行时序 (单位: 毫秒)
dateFormat SSS
axisFormat %L ms
section 查询阶段
QueryHydration (并行) :q1, 000, 015
section 获取阶段
Thunder Source :s1, 015, 055
Phoenix Source :s2, 015, 065
section 增强阶段
CoreDataHydrator :h1, 065, 085
AuthorHydrator :h2, 065, 085
VideoHydrator :h3, 065, 080
section 过滤阶段
Filters (顺序) :f1, 085, 105
section 评分阶段
PhoenixScorer :crit, sc1, 105, 155
WeightedScorer :sc2, 155, 165
section 选择阶段
TopKSelector :sel, 165, 170
性能洞察:Source 阶段和 Hydrator 阶段大量并行,Filter 和 Scorer 顺序执行。总耗时约 150-200ms,其中 Phoenix 模型预测占了最大头。
七、评分算法
7.1 完整评分信号体系
算法实际使用 19个离散信号 + 1个连续信号,分为五大类:
mindmap
root((评分信号体系))
互动类 正向
favorite_score 点赞
reply_score 回复
retweet_score 转发
quote_score 引用
内容消费类 正向
click_score 点击
photo_expand_score 图片展开
profile_click_score 主页点击
quoted_click_score 引用点击
视频类 正向
vqv_score 视频观看
条件:时长大于阈值
分享类 正向
share_score 通用分享
share_via_dm_score 私信分享
share_via_copy_link_score 复制链接
停留类 正向
dwell_score 离散停留
dwell_time 连续停留时长
转化类 正向
follow_author_score 关注作者
负向信号 惩罚
not_interested_score 不感兴趣
block_author_score 屏蔽作者
mute_author_score 静音作者
report_score 举报
信号完整列表
| 类别 | 信号名 | 权重方向 | 触发场景 | 创作者启示 |
|---|---|---|---|---|
| 互动 | favorite_score | 正 | 用户点赞 | 创作引发共鸣的内容 |
| reply_score | 正 | 用户回复 | 设计讨论话题,提出问题 | |
| retweet_score | 正 | 用户转发 | 创造分享价值 | |
| quote_score | 正 | 用户引用并评论 | 发布可延伸的观点 | |
| 内容消费 | click_score | 正 | 用户点击查看详情 | 标题/首句吸引点击 |
| photo_expand_score | 正 | 用户展开查看大图 | 发布有细节的图片 | |
| profile_click_score | 正 | 用户点击作者主页 | 展现独特人格特质 | |
| quoted_click_score | 正 | 用户点击被引用的内容 | 引用高质量来源 | |
| 视频 | vqv_score | 条件正 | 视频观看(需超过时长阈值) | 制作超过最低时长的视频 |
| 分享 | share_score | 正 | 通用分享操作 | 创作值得分享的内容 |
| share_via_dm_score | 正 | 通过私信分享给朋友 | 垂直精准的实用内容 | |
| share_via_copy_link_score | 正 | 复制链接分享 | 适合跨平台传播的内容 | |
| 停留 | dwell_score | 正 | 用户在帖子上停留(离散) | 提供有深度的内容 |
| dwell_time | 正 | 停留时长(连续值,秒) | 长内容/Thread 保持信息密度 | |
| 转化 | follow_author_score | 正 | 用户关注作者 | 建立系列感,预告后续 |
| 负向 | not_interested_score | 负 | 用户标记不感兴趣 | 避免与受众无关的内容 |
| block_author_score | 负(强) | 用户屏蔽作者 | 避免引战和人身攻击 | |
| mute_author_score | 负 | 用户静音作者 | 控制发布频率 | |
| report_score | 负(最强) | 用户举报 | 遵守社区规范 |
加权计算流程
flowchart LR
subgraph Input["Phoenix 模型输出"]
A["19个离散概率 P_i"]
B["1个连续值 dwell_time"]
end
subgraph Process["加权计算"]
C["Σ(w_i × P_i)"]
D{"combined < 0?"}
E["正分支: + offset"]
F["负分支: 特殊归一化"]
end
A --> C
B --> C
C --> D
D -->|是| F
D -->|否| E
E --> G["weighted_score"]
F --> G
负分数处理:当加权总分为负时,采用特殊公式防止极端惩罚:
1negative_score = (combined + NEGATIVE_WEIGHTS_SUM) / WEIGHTS_SUM × NEGATIVE_SCORES_OFFSET
实际示例
假设某帖子的 Phoenix 预测结果:
| 场景 | 信号值 | 权重(假设) | 贡献分 |
|---|---|---|---|
| 预测 10% 用户会点赞 | 0.10 | +1.0 | +0.10 |
| 预测 5% 用户会回复 | 0.05 | +1.5 | +0.075 |
| 预测 2% 用户会转发 | 0.02 | +2.0 | +0.04 |
| 预测 1% 用户会屏蔽 | 0.01 | -10.0 | -0.10 |
| 总分 | +0.115 |
注意:一次屏蔽(-0.10)几乎抵消了点赞(+0.10)的全部收益,说明负向信号惩罚力度很大。
7.2 作者多样性评分
flowchart TB
subgraph AuthorDiversity["作者多样性评分"]
direction TB
INPUT[输入: 按分数排序的候选列表]
subgraph Algorithm["算法"]
direction TB
TRACK[跟踪每个作者出现次数]
CALC["multiplier = (1 - floor) × decay^position + floor
position = 该作者之前出现次数
decay = 衰减因子 (例如: 0.7)
floor = 最低保留比例 (例如: 0.1)"]
APPLY["new_score = score × multiplier"]
end
subgraph Example["示例"]
E1["作者A首次出现: multiplier = 1.0"]
E2["作者A第2次出现: multiplier = 0.73"]
E3["作者A第3次出现: multiplier = 0.51"]
E4["作者A第4次出现: multiplier = 0.36"]
E5["作者A第N次出现: multiplier → floor"]
end
INPUT --> TRACK
TRACK --> CALC
CALC --> APPLY
APPLY --> OUTPUT[输出: 多样性调整后的候选列表]
end
7.3 评分器链执行顺序
flowchart TB
subgraph ScorerChain["评分器链"]
direction TB
C1[候选列表
无分数] --> S1
S1[PhoenixScorer
调用ML模型获取预测] -->|添加phoenix_scores| S2
S2[WeightedScorer
计算加权分数] -->|添加weighted_score| S3
S3[AuthorDiversityScorer
多样性调整] -->|更新final_score| S4
S4[OONScorer
网外内容调整] -->|更新final_score| C2
C2[候选列表
带最终分数]
end
style C1 fill:#ffecb3
style C2 fill:#c8e6c9
7.4 视频评分机制
视频内容有特殊的评分规则,vqv_score(Video Quality View)只在满足时长条件时才计入。
flowchart TB
subgraph VideoScoring["视频评分判定"]
V1[视频帖子] --> CHECK{"视频时长 > MIN_VIDEO_DURATION_MS?"}
CHECK -->|是| APPLY["应用 VQV_WEIGHT 权重"]
CHECK -->|否| SKIP["VQV 权重 = 0"]
APPLY --> SCORE["计入 vqv_score × VQV_WEIGHT"]
SKIP --> ZERO["vqv_score 贡献 = 0"]
end
视频资格传播规则
flowchart LR
subgraph VideoEligibility["视频资格判定"]
direction TB
O["原创视频帖"] -->|"has_video = true"| YES1["计入视频评分"]
RT["转发帖"] --> CHECK2{"源帖有视频?"}
CHECK2 -->|是| YES2["继承视频资格"]
CHECK2 -->|否| NO2["无视频资格"]
RP["回复帖"] --> NO3["始终无视频资格"]
end
创作者启示
| 场景 | 是否计入 VQV | 建议 |
|---|---|---|
| 原创视频 > 阈值时长 | 计入 | 确保视频超过最低时长 |
| 原创视频 < 阈值时长 | 不计入 | 考虑延长到阈值以上 |
| 转发他人视频 | 继承源帖 | 转发优质长视频仍有收益 |
| 回复中附带视频 | 不计入 | 视频内容应发为独立帖 |
| GIF 动图 | 通常不计入 | GIF 一般不满足时长要求 |
实际案例:发布一个 8 秒的短视频可能不会获得视频观看加分,但延长到 15 秒以上则可以。具体阈值为平台配置参数。
7.5 印象去重机制
算法使用多层去重防止用户重复看到相同内容。
flowchart TB
subgraph Deduplication["三层去重机制"]
direction TB
subgraph Layer1["第一层: Bloom Filter"]
BF["客户端 Bloom Filter"]
BF --> |"概率性去重"| SEEN1["快速过滤已浏览"]
end
subgraph Layer2["第二层: 精确ID"]
IDS["显式已见ID列表"]
IDS --> |"精确匹配"| SEEN2["确定性过滤"]
end
subgraph Layer3["第三层: 对话去重"]
CONV["对话ID追踪"]
CONV --> |"同对话只留最高分"| SEEN3["Thread去重"]
end
INPUT["候选内容"] --> Layer1
Layer1 --> Layer2
Layer2 --> Layer3
Layer3 --> OUTPUT["去重后候选"]
end
Bloom Filter 工作原理
flowchart LR
subgraph BloomFilter["Bloom Filter 原理"]
POST["帖子ID"] --> HASH["多个哈希函数"]
HASH --> BIT["位数组检查"]
BIT --> |"所有位都为1"| MAYBE["可能已见"]
BIT --> |"任意位为0"| NEW["一定未见"]
MAYBE --> |"存在误判"| FP["假阳性:未见被判为已见"]
NEW --> PASS["通过过滤"]
end
优势:
- 空间效率:用少量内存追踪大量浏览历史
- 无假阴性:已见内容一定会被过滤
- 允许假阳性:少量未见内容可能被误过滤(可接受的代价)
对话级去重
flowchart TB
subgraph ConversationDedup["对话去重示例"]
direction TB
subgraph Thread["用户A的Thread"]
T1["帖子1: 主题介绍
得分: 0.8"]
T2["帖子2: 详细分析
得分: 0.95"]
T3["帖子3: 总结
得分: 0.7"]
end
T1 --> SAME["同一 conversation_id"]
T2 --> SAME
T3 --> SAME
SAME --> SELECT["只保留得分最高的"]
SELECT --> RESULT["帖子2 进入最终结果"]
end
创作者启示:
- Thread 中最高质量的一条会代表整个 Thread
- 不用担心 Thread 多条同时出现刷屏
- 首条应该能独立吸引人(可能被单独展示)
7.6 双阶段安全审核
网内和网外内容适用不同的安全级别:
flowchart TB
subgraph SafetyLevels["安全审核级别"]
direction TB
subgraph InNetwork["网内内容 In-Network"]
IN_LEVEL["TimelineHome 级别"]
IN_DESC["较宽松: 用户主动关注"]
end
subgraph OutOfNetwork["网外内容 Out-of-Network"]
OON_LEVEL["TimelineHomeRecommendations 级别"]
OON_DESC["较严格: 算法主动推荐"]
end
CONTENT["候选内容"] --> CHECK{"来源?"}
CHECK -->|"关注列表"| InNetwork
CHECK -->|"Phoenix检索"| OutOfNetwork
end
两阶段审核流程
flowchart LR
subgraph TwoStage["两阶段安全审核"]
PRE["预选阶段"] --> |"VFHydrator"| DATA["获取安全数据"]
DATA --> SCORE["评分选择"]
SCORE --> POST["后选阶段"]
POST --> |"VFFilter"| FINAL["最终过滤"]
end
| 阶段 | 执行时机 | 作用 |
|---|---|---|
| 预选增强 | 评分前 | 获取安全元数据,为后续过滤做准备 |
| 后选过滤 | Top-K 选择后 | 对入选内容做最终安全检查 |
为什么要两阶段?
- 预选阶段:快速获取大量候选的安全数据(批量处理效率高)
- 后选阶段:仅对最终入选的少量内容做精细检查(减少计算量)
创作者启示
| 内容类型 | 审核严格度 | 建议 |
|---|---|---|
| 粉丝可见内容 | 较宽松 | 仍需遵守基本规范 |
| 推荐给新用户 | 较严格 | 避免擦边内容,影响网外分发 |
实际案例:一条轻微争议的帖子可能在粉丝信息流正常显示,但不会被推荐给陌生用户。
7.7 网外内容调整
网外内容(来自 Phoenix 检索)会经过额外的分数调整:
flowchart LR
subgraph OONScoring["网外分数调整"]
IN["网内内容"] --> |"保持原分"| FINAL1["final_score"]
OON["网外内容"] --> |"× OON_WEIGHT_FACTOR"| FINAL2["final_score × 系数"]
end
设计意图:
- 优先展示用户主动关注的内容
- 网外内容需要更高的预测分数才能与网内竞争
- 平衡"信息茧房"与"用户偏好"
八、目录结构
flowchart TB
subgraph ProjectStructure["项目目录结构"]
direction TB
ROOT[x-algorithm/]
ROOT --> README[README.md
项目文档]
ROOT --> LICENSE[LICENSE
Apache 2.0]
ROOT --> COC[CODE_OF_CONDUCT.md]
ROOT --> CP[candidate-pipeline/
可复用管道框架]
ROOT --> HM[home-mixer/
主编排服务]
ROOT --> TH[thunder/
实时帖子存储]
ROOT --> PH[phoenix/
ML模型]
subgraph CPFiles["candidate-pipeline/"]
CP1[lib.rs]
CP2[candidate_pipeline.rs
核心编排逻辑]
CP3[source.rs
候选源Trait]
CP4[hydrator.rs
增强器Trait]
CP5[filter.rs
过滤器Trait]
CP6[scorer.rs
评分器Trait]
CP7[selector.rs
选择器Trait]
CP8[query_hydrator.rs
查询增强Trait]
CP9[side_effect.rs
副作用Trait]
end
subgraph HMFiles["home-mixer/"]
HM1[main.rs
gRPC服务入口]
HM2[server.rs
服务实现]
HM3[candidate_pipeline/
管道实现]
HM4[query_hydrators/
查询增强实现]
HM5[sources/
候选源实现]
HM6[candidate_hydrators/
候选增强实现]
HM7[filters/
过滤器实现]
HM8[scorers/
评分器实现]
HM9[selectors/
选择器实现]
HM10[side_effects/
副作用实现]
HM11[clients/
外部服务客户端]
end
subgraph THFiles["thunder/"]
TH1[main.rs
服务入口]
TH2[thunder_service.rs
gRPC服务]
TH3[posts/post_store.rs
帖子存储]
TH4[kafka/
事件监听器]
TH5[strato_client.rs
关注列表查询]
end
subgraph PHFiles["phoenix/"]
PH1[grok.py
Transformer架构]
PH2[recsys_model.py
排序模型]
PH3[recsys_retrieval_model.py
检索模型]
PH4[run_ranker.py
排序示例]
PH5[run_retrieval.py
检索示例]
end
CP --> CPFiles
HM --> HMFiles
TH --> THFiles
PH --> PHFiles
end
8.1 设计决策总结
mindmap
root((设计决策))
无手工特征
全部由Grok Transformer学习
减少维护负担
消除特征工程
候选隔离
候选间无注意力
分数可缓存
批处理灵活
多动作预测
14+动作类型
权重可调
无需重训练
Hash嵌入
多哈希函数
减少内存占用
提高泛化
可组合架构
通用Pipeline框架
易于扩展
并行执行
关注点分离
Thunder: 实时存储
Phoenix: ML模型
HomeMixer: 编排
8.2 性能指标
| 组件 | 目标延迟 | 说明 |
|---|---|---|
| Thunder 查询 | < 1ms | 内存查找,亚毫秒级 |
| Phoenix 检索 | < 50ms | 向量相似度搜索 |
| Phoenix 排序 | < 100ms | Transformer推理 |
| Home Mixer 端到端 | < 200ms | 完整推荐流程 |
8.3 监控与可观测性
flowchart TB
subgraph Observability["可观测性架构"]
direction TB
subgraph Metrics["指标收集"]
M1[请求计数]
M2[响应延迟]
M3[候选数量]
M4[过滤率]
M5[缓存命中率]
end
subgraph Thunder["Thunder指标"]
T1[POST_STORE_REQUESTS]
T2[POST_STORE_POSTS_RETURNED]
T3[POST_STORE_POSTS_RETURNED_RATIO]
T4[POST_STORE_TOTAL_POSTS]
T5[POST_STORE_REQUEST_TIMEOUTS]
end
subgraph Logging["日志"]
L1[INFO: 阶段完成]
L2[WARN: 降级处理]
L3[ERROR: 失败异常]
end
end
8.4 扩展指南
8.4.1 添加新的过滤器
sequenceDiagram
participant Dev as 开发者
participant Code as 代码库
participant Pipeline as CandidatePipeline
Dev->>Code: 1. 创建新Filter结构体
Dev->>Code: 2. 实现Filter trait
Dev->>Code: 3. 实现filter()方法
Dev->>Pipeline: 4. 在pipeline配置中添加
Pipeline->>Pipeline: 5. 自动集成到过滤链
8.4.2 添加新的评分器
sequenceDiagram
participant Dev as 开发者
participant Code as 代码库
participant Pipeline as CandidatePipeline
Dev->>Code: 1. 创建新Scorer结构体
Dev->>Code: 2. 实现Scorer trait
Dev->>Code: 3. 实现score()方法
Dev->>Code: 4. 实现update()方法
Dev->>Pipeline: 5. 在pipeline配置中添加
Pipeline->>Pipeline: 6. 自动集成到评分链
九、创作者指南
基于对 X 推荐算法源代码的深入分析,本章节从技术原理出发,提炼出可落地的创作策略。
9.1 理解推荐系统的工作方式
X 的推荐系统由三个核心模块协同工作:
flowchart TB
subgraph System["推荐系统架构"]
direction TB
subgraph Sources["内容来源"]
S1["Thunder
关注列表内容"]
S2["Phoenix Retrieval
全平台检索"]
end
subgraph Scoring["评分系统"]
SC["Phoenix Model
Grok大模型打分"]
end
S1 --> POOL["候选内容池"]
S2 --> POOL
POOL --> SC
SC --> RESULT["最终排序结果"]
end
| 模块 | 技术实现 | 核心作用 | 创作者启示 |
|---|---|---|---|
| 关注流 | Thunder | 从用户关注列表中筛选近期内容 | 粉丝基数影响初始曝光量 |
| 发现流 | Phoenix检索 | 基于向量相似度匹配全平台内容 | 内容质量决定能否触达新用户 |
| 排序模型 | Phoenix Model | 预测用户互动概率并排序 | 互动预期越高,排名越靠前 |
9.2 算法评分机制
算法同时预测14种正向行为和4种负向行为的发生概率,加权求和得出最终分数:
1最终分数 = Σ(权重_i × P_i) + 偏移量
2
3正向行为(权重 > 0):点赞、回复、转发、引用、点击、视频观看、分享、关注等
4负向行为(权重 < 0):不感兴趣、屏蔽、静音、举报
关键洞察:负向信号的惩罚力度通常远大于正向信号的奖励。一次被屏蔽可能需要多次点赞才能弥补。
9.3 内容必须通过的过滤关卡
在进入评分环节之前,内容需要通过一系列过滤器:
| 过滤器 | 过滤条件 | 应对策略 |
|---|---|---|
| 时效性过滤 | 发布超过48小时的内容不再推荐 | 把握发布时机,热点内容要快 |
| 重复检测 | 相同或高度相似的内容会被去重 | 确保每条内容有独特价值 |
| 社交关系过滤 | 被用户屏蔽或静音后完全不可见 | 避免引发用户反感的行为 |
| 关键词过滤 | 包含用户屏蔽词的内容不展示 | 注意敏感词汇的使用 |
| 安全审核 | 违规内容降权或移除 | 遵守平台社区规范 |
9.4 作者多样性机制
算法通过衰减系数防止单一作者霸占信息流:
1衰减公式:multiplier = (1 - floor) × decay^position + floor
2
3实际效果(假设 decay=0.7, floor=0.1):
4- 第1条内容: 得分保持100%
5- 第2条内容: 得分降至约73%
6- 第3条内容: 得分降至约51%
7- 第4条内容: 得分降至约36%
这意味着短时间内密集发帖会导致后续内容的竞争力大幅下降。
9.5 双渠道分发策略
| 渠道 | 流量特点 | 优化方向 |
|---|---|---|
| 网内分发 Thunder | 稳定但有上限,取决于粉丝数量 | 维护粉丝关系,避免被取关 |
| 网外发现 Phoenix | 不稳定但无上限,取决于内容质量 | 创作有广泛吸引力的内容 |
9.6 不同阶段的增长策略
| 阶段 | 粉丝规模 | 主要依赖 | 策略重点 |
|---|---|---|---|
| 冷启动期 | < 1,000 | 发现流 | 创作能引起广泛共鸣的内容,借助热门话题获得初始曝光 |
| 成长期 | 1,000-10,000 | 双渠道并重 | 稳定服务现有粉丝的同时,持续尝试突破圈层 |
| 成熟期 | > 10,000 | 关注流为主 | 深耕垂直领域,维护核心粉丝群体 |
新账号冷启动建议
新账号面临"鸡生蛋蛋生鸡"问题:没粉丝→没曝光→没粉丝。突破方法:
- 参与互助社区 - 但需要筛选高质量社区
- 积累第一批粉丝 - 先成为 KOC(关键意见消费者)
- 与大账号互动 - 一个大账号的回复/转发顶得上1000个散户
- 在热门帖子下留高质量评论 - 借势获得初始曝光
9.7 内容过滤机制详解
每条发布的内容在进入评分阶段之前,必须通过一系列过滤器的检查。这些过滤器基于代码中的 CandidateFilter 实现,主要分为以下几类:
flowchart LR
POST["发布内容"] --> G1["时效性检查"]
G1 --> G2["内容去重"]
G2 --> G3["用户自身过滤"]
G3 --> G4["转发归并"]
G4 --> G5["付费内容权限"]
G5 --> G6["已读标记"]
G6 --> G7["屏蔽词匹配"]
G7 --> G8["社交关系检查"]
G8 --> G9["元数据完整性"]
G9 --> G10["安全合规审核"]
G10 --> SCORE["进入评分"]
| 过滤阶段 | 技术实现 | 对创作者的影响 |
|---|---|---|
| 时效性检查 | 基于发布时间戳,约48小时后从推荐池移除 | 内容有明确的生命周期,需把握发布节奏 |
| 内容去重 | 通过内容哈希识别重复发布 | 避免复制粘贴相同内容 |
| 用户自身过滤 | 不向用户推荐其自己的内容 | 系统正常行为 |
| 转发归并 | 同一原帖的多次转发只保留一条 | 原创内容在算法中更有优势 |
| 付费内容权限 | 订阅类内容需验证用户权限 | 付费墙内容仅对订阅者可见 |
| 已读标记 | 用户浏览过的内容不再重复推荐 | 需要持续产出新内容 |
| 屏蔽词匹配 | 检查用户设置的屏蔽关键词 | 避免使用可能被大量用户屏蔽的词汇 |
| 社交关系检查 | 被拉黑或静音的账号内容不展示 | 维护健康的社区互动关系 |
| 元数据完整性 | 验证帖子的必要字段是否完整 | 正常发布不会触发此问题 |
| 安全合规审核 | 检测违规或有害内容 | 遵守平台社区规范 |
9.8 创作者实践指南
基于算法评分机制,以下是提升内容表现的策略体系。
一、核心策略总览
mindmap
root((创作者策略))
互动类信号
reply_score
设计讨论话题
提出开放问题
经营评论区
quote_score
发布可延伸观点
引用时添加见解
retweet_score
创造分享价值
实用/认同/共鸣
停留类信号
dwell_time
开头设悬念
中间有密度
结尾有价值
photo_expand
图片留悬念
信息图优势
video_view
前3秒抓人
添加字幕
15-60秒最佳
转化类信号
profile_click
展现人格特质
优化Bio和固定帖
follow_author
建立系列感
预告后续内容
share_via_dm
垂直专业内容
精准实用信息
规避负面
not_interested
避免刷屏
控制发布间隔
block/mute
避免引战
处理负面评论
report
遵守社区规范
注意敏感边界
二、信号优化详解
2.1 互动类信号
目标:提升
reply_score、quote_score、retweet_score
| 信号 | 低效写法 | 高效写法 |
|---|---|---|
| reply | “今天读完了一本好书,收获很大” | “程序员转管理岗,技术能力会退化吗?我观察身边的案例,发现…” |
| quote | “这篇文章写得很好” | “这个观点我部分同意,但在实际项目中还需要考虑…” |
| retweet | “分享一个工具” | “找了三个月终于找到的免费设计资源,设计师朋友们收藏” |
2.2 停留类信号
目标:提升
dwell_time、photo_expand、video_view
| 信号 | 技巧 | 示例 |
|---|---|---|
| dwell_time | 悬念开头 | “我用了一个反常识的方法,把转化率提升了3倍。一开始团队都反对…” |
| photo_expand | 信息密集图 | 流程图、对比图、数据可视化(需要放大才能看清细节) |
| video_view | 前3秒钩子 | 开头直接展示结果或冲突,而非缓慢铺垫 |
2.3 转化类信号
目标:提升
profile_click、follow_author、share_via_dm
| 信号 | 触发场景 | 示例 |
|---|---|---|
| profile_click | 展现独特视角 | 持续输出某垂直领域的深度见解,让人好奇"这人是谁" |
| follow_author | 系列预告 | “这是我总结的产品方法论第3篇,下周更新用户调研实战” |
| share_via_dm | 精准实用 | “给正在找工作的朋友:这5个简历细节90%的人都忽略了” |
2.4 负面信号规避
目标:降低
not_interested、block、mute、report
| 信号 | 触发行为 | 规避方法 |
|---|---|---|
| not_interested | 内容与用户无关 | 明确目标受众,不追求所有人喜欢 |
| block/mute | 频繁刷屏或引战 | 控制发帖间隔,理性讨论不人身攻击 |
| report | 违规或争议内容 | 了解平台规则,敏感话题谨慎措辞 |
三、内容形式选择
flowchart LR
subgraph 文字内容
T1[长文/Thread] --> D1[dwell_time]
T2[观点/问题] --> D2[reply_score]
T3[干货总结] --> D3[retweet_score]
end
subgraph 图片内容
P1[信息图] --> E1[photo_expand]
P2[多图帖] --> E2[滑动查看]
P3[高清细节图] --> E3[放大查看]
end
subgraph 视频内容
V1[短视频15-60s] --> F1[vqv_score]
V2[前3秒钩子] --> F2[完播率]
V3[添加字幕] --> F3[静音友好]
end
各形式适用场景
| 形式 | 适用场景 | 具体示例 |
|---|---|---|
| Thread | 复杂知识拆解 | “我如何从0到1搭建数据中台,分7步讲清楚:1/7 首先要解决的是…” |
| 观点帖 | 引发讨论 | “为什么我不建议应届生去创业公司?说三个真实踩坑经历” |
| 干货图 | 信息整合 | 一张图总结某框架的核心概念,需要放大查看细节 |
| 对比图 | 突出差异 | Before/After 对比,优化前后的代码/设计/数据 |
| 短视频 | 操作演示 | 30秒演示一个实用技巧,开头直接展示效果 |
| 教程视频 | 完整流程 | 带字幕的步骤演示,关键节点有文字标注 |
四、分发渠道策略
flowchart TB
subgraph 两大分发渠道
IN[In-Network
关注流]
OUT[Out-of-Network
发现流]
end
subgraph 新账号策略
N1[参与热门话题] --> OUT
N2[在大V评论区互动] --> OUT
N3[加入垂直社区] --> IN
end
subgraph 成熟账号策略
M1[维护粉丝互动] --> IN
M2[偶尔破圈内容] --> OUT
M3[建立内容系列] --> IN
end
4.1 新账号冷启动
| 策略 | 具体做法 | 示例 |
|---|---|---|
| 热门话题参与 | 在趋势话题下发表有见地的观点 | 某技术发布新版本时,第一时间分享试用体验和踩坑记录 |
| 大V评论互动 | 在行业KOL帖子下留高质量评论 | 不是"说得好",而是补充一个具体案例或提出延伸问题 |
| 垂直社区 | 加入细分领域的互助群组 | 持续在某个技术话题下输出,成为该话题的活跃贡献者 |
4.2 成熟账号增长
| 策略 | 具体做法 | 示例 |
|---|---|---|
| 粉丝维护 | 定期回复评论、发起互动 | 每周固定一个"问答时间",回复粉丝提问 |
| 破圈内容 | 偶尔发布泛受众内容 | 平时发技术内容,偶尔发一篇职场成长感悟触达更广人群 |
| 内容系列 | 建立固定栏目 | “每周工具推荐”、“踩坑日记"等让粉丝形成期待 |
五、内容规划
5.1 时效性矩阵
flowchart TB
subgraph 高成本
A1[教程系列
常青+高投入]
A2[趋势解读
时效+深度]
end
subgraph 低成本
B1[工具推荐
常青+轻量]
B2[个人感悟
日常互动]
B3[话题讨论
中等时效]
B4[热点评论
高时效+快速]
end
subgraph 时效性
direction LR
LOW[常青内容] -.-> MID[趋势内容] -.-> HIGH[热点内容]
end
| 类型 | 时效窗口 | 内容示例 |
|---|---|---|
| 常青教程 | 长期有效 | “Git 工作流完全指南”、“系统设计面试必备知识” |
| 工具推荐 | 较长有效 | “提升开发效率的10个VS Code插件” |
| 趋势解读 | 24-48小时 | 某大厂发布新技术后的深度分析和实践建议 |
| 话题讨论 | 12-24小时 | 行业热议话题的个人观点和经验分享 |
| 热点评论 | 2-4小时 | 突发新闻/事件的第一时间简短点评 |
| 日常互动 | 即时 | 工作日常分享、提问互动、粉丝答疑 |
5.2 发布节奏
flowchart LR
subgraph 发布间隔
I1[两帖间隔 2-4 小时]
I2[避免 30 分钟内连发]
I3[触发多样性衰减]
I1 --> I2 --> I3
end
subgraph 账号规模建议
S1[新账号: 1-2条/天
质量优先]
S2[成长期: 2-3条/天
平衡曝光]
S3[大账号: 可更频繁
衰减影响小]
end
5.3 内容配比
pie title 内容类型配比
"常青内容" : 40
"趋势响应" : 30
"互动内容" : 20
"实验内容" : 10
示例:技术博主周计划
| 类型 | 比例 | 本周内容计划 |
|---|---|---|
| 常青内容 | 40% | 周二:Docker入门教程 Thread;周四:代码审查最佳实践 |
| 趋势响应 | 30% | 周一:回应最新发布的框架更新;周五:行业报告解读 |
| 互动内容 | 20% | 周三:发起技术选型投票;周末:回复本周精选评论 |
| 实验内容 | 10% | 周六:尝试一种新的内容形式(如短视频演示) |
六、写作技巧
6.1 外链处理
flowchart TD
LINK[需要放外链] --> Q1{内容能否独立?}
Q1 -->|能| A1[主帖完整内容
链接放回复]
Q1 -->|不能| Q2{是否长内容?}
Q2 -->|是| A2[用Thread展开
链接放末条]
Q2 -->|否| A3[截图关键信息
配合链接]
| 场景 | 处理方式 | 具体示例 |
|---|---|---|
| 推荐文章 | 主帖总结要点+链接放回复 | 主帖总结3个关键点,回复放原文链接 |
| 产品发布 | 截图关键信息+链接 | 截图产品核心功能界面,配文说明 |
| 长篇教程 | Thread展开+末条放链接 | 用5条推文讲清核心步骤,最后一条放完整链接 |
| 自己的博客 | 提炼精华到推文 | 把博客最有价值的段落直接发出,链接作为"完整版” |
6.2 Thread 写作结构
flowchart TB
T1["首条: 独立成立 + 吸引继续"] --> T2["中间: 逻辑递进 + 信息密度"]
T2 --> T3["末条: 总结 + 引导回首条"]
NOTE["建议长度: 5-10条"]
示例模板:
11/6 [悬念开头 + 明确预期]
2 三年前我从大厂裸辞去创业,烧光了50万积蓄。
3 今天想聊聊这段经历教会我的6件事。
4
52/6 [核心观点1 + 具体案例]
6 第一件事:现金流比梦想重要
7 当时我们...
8
93/6 [核心观点2 + 具体案例]
10 第二件事:...
11
124/6 [核心观点3 + 具体案例]
13 第三件事:...
14
155/6 [核心观点4-5]
16 第四件事:...
17 第五件事:...
18
196/6 [总结 + 行动引导]
20 最后一件事:失败不可怕,可怕的是没有复盘
21 这6条是我用50万换来的教训,希望对你有帮助。
22 如果觉得有用,欢迎转发给需要的朋友。
七、速查表
| 目标 | 核心信号 | 策略要点 |
|---|---|---|
| 提升互动 | reply_score |
提问、讨论、争议性话题 |
| 促进转发 | retweet_score |
实用、认同、社交货币 |
| 增加引用 | quote_score |
可延伸观点、不完整信息 |
| 延长停留 | dwell_time |
悬念开头、Thread、长内容 |
| 图片优化 | photo_expand |
信息图、细节图、多图 |
| 视频优化 | vqv_score |
短时长、前3秒、加字幕 |
| 主页转化 | profile_click |
人格展现、系列内容 |
| 促进关注 | follow_author |
预告更新、持续价值 |
| 私信分享 | share_via_dm |
垂直实用、精准人群 |
| 规避惩罚 | 负面信号 | 控制频率、避免引战 |
内容检测清单
发布前使用此清单自检:
flowchart TB
START["准备发布内容"] --> Q1{"是否有明确价值?
教育/娱乐/信息"}
Q1 -->|是| Q2{"是否易于互动?
问题/讨论点"}
Q1 -->|否| REVISE1["重新思考内容价值"]
Q2 -->|是| Q3{"是否适合分享?
实用/有趣/独特"}
Q2 -->|否| ADD["添加互动元素"]
Q3 -->|是| Q4{"时机是否合适?
高峰时段/热点相关"}
Q3 -->|否| ENHANCE["增强可分享性"]
Q4 -->|是| Q5{"最近是否发过类似内容?"}
Q4 -->|否| WAIT["等待更好时机"]
Q5 -->|否| PUBLISH["发布"]
Q5 -->|是| SPACE["间隔一段时间再发"]
数据驱动优化
关键指标追踪
| 指标 | 对应算法信号 | 优化方向 |
|---|---|---|
| 互动率 | like + reply + repost | 内容共鸣度 |
| 引用率 | quote | 话题延展性 |
| 个人主页点击 | profile_click | 个人品牌吸引力 |
| 粉丝增长 | follow_author | 长期价值 |
| 视频完播率 | video_view | 视频内容质量 |
| 平均停留时长 | dwell_time | 内容深度 |
A/B 测试建议
flowchart LR
subgraph Testing["内容测试矩阵"]
direction TB
T1["测试不同开头钩子"]
T2["测试发布时间"]
T3["测试内容长度"]
T4["测试媒体类型"]
T5["测试话题标签"]
end
Testing --> ANALYZE["分析互动数据"]
ANALYZE --> ITERATE["迭代优化"]
ITERATE --> Testing
长期增长策略
flowchart TB
subgraph GrowthFlywheel["增长飞轮"]
direction TB
CONTENT["优质内容"] --> ENGAGE["用户互动"]
ENGAGE --> SIGNAL["正向信号"]
SIGNAL --> RANK["算法推荐"]
RANK --> REACH["更大曝光"]
REACH --> FOLLOW["新粉丝"]
FOLLOW --> NETWORK["网内流量增加"]
NETWORK --> CONTENT
end
核心原则:
- 一致性 - 保持稳定的发布节奏和内容风格
- 互动性 - 积极回复评论,建立社区感
- 价值性 - 每条内容都要有明确的用户价值
- 真实性 - 避免刻意迎合算法而失去个人特色
八、算法认知纠偏
通过分析源代码,可以澄清一些广为流传但缺乏依据的说法:
关于发布频率
有观点认为"发得越多曝光越多"。但从代码来看,AuthorDiversityScorer 会对同一作者的连续内容施加递减系数。这意味着在短时间内密集发布,后续内容的得分会被压低。算法的设计意图是让用户信息流保持多样性,而非被单一创作者霸屏。
建议:控制发布频率,两条内容之间保持适当间隔,确保每条内容都有足够的曝光窗口。
关于粉丝数量
有说法称"粉丝越多推荐越多"。但在评分逻辑中,算法预测的是当前用户对当前内容的互动概率,这个计算过程中并没有作者粉丝数的加权因子。购买假粉丝不仅无法提升推荐,还可能因为这些账号不产生真实互动而稀释整体互动率。
建议:专注于内容质量和真实粉丝的培养,有机增长才是可持续的策略。
关于蓝标认证
有传言称"蓝标账号有流量加权"。通过代码分析,在 For You 推荐的评分逻辑中没有发现蓝标加权的实现。蓝标可能在其他产品功能中有影响(如回复排序),但在内容推荐算法中没有直接权重。
建议:是否订阅蓝标应基于其实际提供的功能来评估,而非假设的推荐加权。
关于发布时间
有观点宣称存在"普适的最佳发帖时间"。但算法是个性化的,每个用户看到内容的时间取决于他们自己的活跃模式。不存在对所有用户都有效的统一最佳时间。
建议:通过分析工具了解自己粉丝的活跃时间分布,测试不同时间段的效果,找到适合自己受众的发布节奏。
关于"限流"
当内容数据表现不佳时,常见的归因是"被限流了"。但从过滤器代码来看,只有明确违规的内容才会被过滤,不存在基于内容表现的"隐性限流"机制。数据下滑更可能的原因包括:
- 内容与受众期待不匹配
- 竞争环境变化
- 发布时机问题
- 受众兴趣迁移
建议:将注意力放在可控因素上——内容质量、发布策略、互动经营——而非外部归因。
关于历史数据
有说法认为"算法只看最近的表现"。但代码中存在 UserActionSequenceHydrator,用于获取用户的历史行为序列。这说明算法会参考较长时间范围内的用户行为来进行预测。
建议:保持长期稳定的内容质量,避免大起大落。历史表现会影响算法对你内容的预测。
关于评论区价值
有观点认为"在别人帖子下评论不如发自己的内容"。但评论本身也是内容展示的渠道,高质量的评论可以获得点赞、回复,从而被更多人看到。对于新账号来说,在热门帖子下留下有见地的评论是获得初始曝光的有效方式。
建议:将评论区视为内容输出的补充渠道,认真对待每一条评论。
认知纠偏速查:
| 流传说法 | 代码实际表现 | 调整建议 |
|---|---|---|
| 发得多曝光多 | 多样性机制会压制连续内容 | 控制频率,保证每条内容的曝光窗口 |
| 买粉提升推荐 | 评分基于互动预测,非粉丝数 | 专注有机增长 |
| 蓝标有流量加权 | 推荐算法中无此逻辑 | 基于实际功能评估价值 |
| 存在最佳发帖时间 | 算法个性化,因人而异 | 分析自己粉丝的活跃时间 |
| 数据差是被限流 | 过滤器只针对违规内容 | 反思内容和策略本身 |
| 只看近期表现 | 会参考历史行为序列 | 保持长期稳定质量 |
| 评论区没价值 | 评论是曝光的有效渠道 | 认真参与有质量的讨论 |
文档生成时间: 2026-01-21 基于 X For You Feed Algorithm 开源代码库分析