GitHub 趋势 · 第 42 期
X 的推荐算法开源了:33,089 star,连改参数的理由都写进仓库
#推荐算法#开源审计#X
这份仓库的价值不是「你可以照着跑一个推荐系统」,而是「你可以核对 X 到底根据什么决定你看到什么」。它是可读优先、可跑其次:根目录 28 个条目里,真正配齐了构建清单、脚本和合成数据、能端到端跑通的只有 phoenix/ 那一块,而且要求 Linux + NVIDIA GPU + CUDA 12。
推荐算法被讨论得最多的一句话是「它是不是在压我」。但绝大多数讨论停在猜测,因为过去能拿到的东西只有论文、博客、二手截图和离职员工的回忆。这次 X 把请求路径的七个阶段、十七个前置过滤器、权重参数,以及一次真实线上实验的 diff,都放进了同一个仓库。
这到底是什么
仓库地址 github.com/xai-org/x-algorithm,一句话简介:Algorithm powering the For You feed on X。用 Rust 写的,Apache-2.0。
按 GitHub API 抓到的口径:33,089 star、5,384 fork、101 个未关闭 issue、296 个 watcher,创建于 2026-01-19,最近推送 2026-09-10,默认分支 main,仓库体积 4,020 KB——四兆,全是代码和文档,没有模型权重,也没有二进制。
一个必须先说的细节:它没有 Release。releases/latest 返回 404,仓库也关掉了 downloads。这意味着没有一个版本号可以锚定,你读到的默认值、行号和参数名随时可能跟 main 分支一起漂移。
home-mixer/ For You 信息流的组装层:流水线、权重、过滤
candidate-pipeline/ 流水线框架:source / hydrator / filter / scorer /
selector / side_effect 六种阶段,能并行就并行
thunder/ 站内候选:关注账号的近期帖子,常驻内存
phoenix/ 站外候选 + 排序模型:训练与推理,JAX + Rust
simclusters/ 站外候选:按「谁和谁互动」聚类
phoenix-rankall/ 检索索引的维护与事件层
vm-ranker/ 重排服务:用行列式点过程降低邻居相似度
visibility-filtering/ 能不能显示:ALLOW / INTERSTITIAL / DROP
grox/ clip/ media-model-proxy/ 内容理解:文本与图片视频分类
botmaker/ scarecrow/ 打标规则引擎与运行时
under-the-hood/ 账号可见性标签的报告聚合请求路径:两条流水线,七个阶段,一次请求内跑完
For You 不是「查一批帖子再排个序」,它是每次下拉都重新组装一次。README 里的架构图把整条路摊开了,我按原文顺序列一遍:
- ▪1. Query Hydration:取观众的近期互动序列(这是模型的主要输入)、关注列表、拉黑与静音、静音关键词、已看过的帖子、已服务过的帖子、已关注话题。
- ▪2. Candidate Sources:分站内与站外并行取候选。站内是 thunder(关注账号的近期帖子,常驻内存),站外是 phoenix 检索与 simclusters 聚类相似。
- ▪3. Candidate Hydration:补全帖文与媒体、作者详情与账号标签、引用帖、语言、互动计数、订阅状态。
- ▪4. Pre-Scoring Filters:打分之前先做十七道过滤,见下一节。
- ▪5. Scoring:PhoenixScorer 出概率,RankingScorer 加权求和,VMRanker 再调一次顺序。
- ▪6. Selection:按最终分排序,取前 K。
- ▪7. Post-Selection Filters:顺序定完才做可见性判定,DROP 的帖子连它的父帖、引用帖、被转帖一起撤掉。
另外一条 Blending Pipeline 包在外面,把模型排不了的东西插进来:广告、Who to Follow、提示语。广告位还会反过来重排自然内容来安排相邻关系。响应发出去之后还有 Side Effects:记录哪些帖子被服务过、刷新缓存、写广告与客户端事件日志。
打标与可见性:三个答案,独立于排序
这是整个仓库里我觉得最值得看的部分,也是它跟一般「推荐系统演示」最大的区别:排序和可见性是两件事。排序决定顺序,可见性决定「能不能出现」,两者是不同的服务、不同的输入、不同的规则。
打标路径不在请求路径上,是持续跑的:内容理解(文本与媒体分类器、图片视频模型、CLIP 嵌入、账号维度的 agatha / bdsm / user-cred-v2、成人内容分类器)产出分数和标签,打标规则(scarecrow 嵌 botmaker 当规则引擎,加 abuse-enforcement-service)落成标签存起来,请求时才被读回来。
| 可见性判定的三个答案 | 含义 |
|---|---|
| ALLOW | 正常显示 |
| INTERSTITIAL | 显示,但隔一层可点开的提示页,例如成人或血腥媒体 |
| DROP | 不显示 |
判定时会读上面那些标签,再加上观众是否拉黑、静音、关注作者,账号是否受保护、被停用、已注销,订阅专属状态,以及观众自己的设置和国家。有一条规则值得单独点出来:有些规则只在「这是来自你没关注账号的推荐」时才 DROP——比如高召回率的垃圾内容,同一篇帖子对关注者仍然放行。这也是为什么同一篇帖子在两个人的时间线上命运完全不同。
规则仓库里还有一份不常见的坦诚:botmaker-rules/ 明确写了,为降低被绕过和刷量的风险,部分规则当前不在仓库里。同时也有法律驱动的过滤器可以被逐行核对,例如巴西 2026 选举期间上线的 Brazil2026ElectionFilter,对被巴西选举法院报告的账号做过滤,除非观众自己关注了该账号,实现就在 home-mixer/filters/brazil_2026_election_filter.rs。
打分:多动作概率加权,以及那个被误读的「468 倍」
Phoenix 不给一个「相关度」分,它给每个动作一个概率,合并成总分是另一个显式步骤。动作清单按原文分五类:
Engagement favorite . reply . repost . quote . share . share via DM . share via copy link Clicks post . profile . link . photo expand . video open . quoted post Attention video quality view . dwell . dwell time . click dwell time . active seconds Author follow author Negative not interested . mute author . block author . report . not dwelled Final Score = Sigma (weight_i x P(action_i))
正向动作带正权重,负向带负权重,权重放在 home-mixer/params/param.rs,算式在 home-mixer/scorers/ranking_scorer.rs。求和之后还有三步调整:作者多样性衰减(同一作者的第二篇起逐篇打折,打到底线为止)、站外折扣(来自你没关注账号的帖子乘一个小于 1 的系数,关注账号的回复和转帖也打折)、新作者加成(曝光量低于阈值的作者被抬向目标位置)。
权重这件事有个流传很广的误读,官方在 README 里亲自下场纠正了。原文说,看到「举报的权重是点赞的 468 倍」就去推断「1 个举报抵 468 个赞」,是错的:权重乘的是你自己产生该动作的预测概率,不是原始互动计数。而这个概率很大程度上由你自己的行为决定。为了让读代码的人和 LLM 都不至于理解反,他们还专门在两个源文件里补了注释。
还有一个容易被忽略的细节在 VMRanker:它是排序之后的一次独立重排,用行列式点过程在帖子的嵌入上做取舍——牺牲一点分数,换取相邻帖子之间更低的相似度。也就是说,你刷到的内容变多样,是有明确代码来源的,不是玄学。
十七道前置过滤:读一遍就知道哪些帖子根本活不到打分
这些是打分之前执行的,顺序就是下表的顺序:
| 过滤器 | 过滤掉什么 |
|---|---|
| DropDuplicatesFilter | 多个来源返回的同一篇帖子 |
| CoreDataHydrationFilter | 正文与元数据加载失败的帖子 |
| AgeFilter | 超过 48 小时的帖子 |
| SelfTweetFilter | 观众自己的帖子 |
| OONRetweetReplyFilter | 未关注账号的转帖与回复,以及父帖缺失的回复 |
| OONNsfwSimclustersFilter | 作者被标记为成人内容且观众未关注其账号的 SimClusters 帖子 |
| RetweetDeduplicationFilter | 对同一帖子的重复转帖 |
| IneligibleSubscriptionFilter | 观众无权访问的订阅专属帖 |
| PreviouslySeenPostsFilter | 观众已经看过的帖子 |
| PreviouslySeenPostsBackupFilter | 同上,来自第二份曝光记录 |
| PreviouslyServedPostsFilter | 本次会话中已经服务过的帖子 |
| MutedKeywordFilter | 命中观众静音关键词的帖子 |
| AuthorSocialgraphFilter | 来自观众拉黑或静音账号的帖子 |
| VideoFilter | 请求明确排除视频时的视频帖 |
| TopicIdsFilter | 请求话题之外的帖子,以及在排除话题内的帖子 |
| NewUserMinEngagementFilter | 新账号的互动量低于阈值的站外帖子 |
| InventoryHoldoutFilter | 按配置比例确定性抽出的那部分帖子 |
打完分、选出前 K 之后还有三道后置过滤:VFFilter 撤掉可见性判定说 DROP 的帖子,AncillaryVFFilter 连带撤掉父帖、引用帖、被转帖已被撤掉的帖子,DedupConversationFilter 收拢同一对话的额外分支。
顺带一个设计上的取舍:已经看过的帖子处理了两遍。ThunderSource 直接拿到清单并跳过,其他来源不拿,所以它们的重复由上面那两个 PreviouslySeen 过滤器兜住。
连改参数的理由都留痕:一份持续交付的变更日志
README 顶部有一节 Notable Updates,逐日记录改动。2026-08-13 那次把可见性过滤、打标系统、以及「训练线上真正在用的 Phoenix 的代码 + 合成数据生成」一起放了进来,同时上了一个新的透明度工具 Under the Hood,让用户看到自己账号和帖子上的可见性标签的聚合统计。
更有意思的是 docs/BIDIRECTIONAL_BOOST_CHANGE.md 这份文档,它把一次真实线上实验的完整过程连 diff 一起公开了,原文的时间线是这样的:
- ▪2026-07-10 开始 A/B 测试,一小部分用户被随机分配到双向关注回复加成的 5 / 10 / 15 / 20 四个档位,当时大多数用户的值是 0,等于没有加成。
- ▪2026-07-13 看到初期结果不错,把 20 推给大量用户,同时继续用 0 / 5 / 10 / 15 做实验。
- ▪2026-07-24 拿到实验数据、也听到了反馈(原话大意是:世界杯正在进行,但有些人看不到足够多相关的讨论,因为那些帖子来自他们没关注的账号),于是把值从 20 调回 15。
param!(FavoriteWeight, f64, "rust_home_mixer_favorite_weight", 0.5);
param!(ReplyWeight, f64, "rust_home_mixer_reply_weight", 5.0);
param!(
BidirectionalFollowReplyWeightBoost,
f64,
"rust_home_mixer_bidirectional_follow_reply_weight_boost",
20.0 # 2026-07-13 全量上线
);
param!(RetweetWeight, f64, "rust_home_mixer_retweet_weight", 1.0);
# 2026-07-24:20.0 -> 15.0
- 20.0
+ 15.0这套东西的含金量不在代码多漂亮,而在于「这个数字为什么是 15 不是 20」有了可以查证的出处。同时 README 也说明了默认值是怎么进到代码里的:很多可调值读的是配置系统,仓库里有 cron 脚本定期把生产主值写回 param.rs 作为默认值——所以你看到的默认值,是某个时间点上的线上值,不是永久契约。
上手:先把要求读清楚,再决定要不要装
以下命令逐字取自 phoenix/QUICKSTART.md 原文。环境要求也是原文列的:
# Requirements # Linux with an NVIDIA GPU and CUDA 12 # uv and Python 3.11 or newer # A Rust toolchain and protoc 3.15 or newer uv sync --extra engine export PYTHONPATH=$PWD # Verify the install with random weights uv run python xrex/inference/oss_bench/bench.py --smoke --service_type ranking
这一步之后可以生成确定性合成数据并训一个 nano 排序模型。同一个 seed 产出同一份数据:
# 1. Generate deterministic synthetic data uv run python reference/world_snapshots.py --out ./synth_index --seed 20260721 export PHOENIX_INDEX_BASE=./synth_index uv run python reference/dump_gen.py --out ./synth_dump --seed 20260721 \ --num-rows 12288 --partitions 4 --rows-per-file 1024 \ --sid ./synth_index/sid_snapshot/post_sid_v5_256x6.parquet --self-check # 2. Train the ranking model uv run python reference/train_synth.py \ --data ./synth_dump --steps 6 --out "$PWD/checkpoints" --metrics
QUICKSTART 自己在这段旁边写了一句提醒,我原样转述:rehearsal 观察到 loss 在这份合成数据上下降,但不要把这六步当作模型质量的证据。官方口径是这套 walkthrough 只用来验证「发出去的那条链路能通」,生产数据、checkpoint、编排和规模都不在仓库里。
最后跑一次检索加排序的完整闭环:
# 5. Train retrieval and run retrieve -> rank uv run python reference/retrieve_then_rank.py \ --data ./synth_dump --sessions 3 --topk 16 \ --retrieval-port 9990 --ranking-port 9988 # retrieve_then_rank: 3 session(s) completed the full loop.
我的判断
适合谁:想看清工业级大规模推荐管线长什么样的工程师——尤其是「排序与可见性为什么要拆成两个服务」「多动作概率为什么不直接合成一个分数」这类架构取舍,这里给的是一手材料而不是二手总结。做内容风控或社区治理的人值得专门啃 visibility-filtering/ 与那三个答案(ALLOW / INTERSTITIAL / DROP),这套「先打标、再按观众关系逐条判定、且只对推荐流生效」的分工,比算法本身更容易迁移到自己业务。
不适合谁:想抄一套能跑的推荐系统的人会失望——没有生产数据、没有 checkpoint、没有编排,多数模块只适合读;只有 Mac 的人会卡在第一步,QUICKSTART 要求 Linux + NVIDIA GPU + CUDA 12;想直接拿来做产品的人也要想清楚 Apache-2.0 的边界:许可是宽松的,但代码里大量 import 的是内部基础设施(xai_service_runner、xai_kafka 这类),README 明确说这些部署相关文件普遍不在仓库里。
坑(五条)。① 没有 Release,只能跟 main,本文引用的参数名与默认值都可能已经变了;② 仓库自己列了一份「哪些没放进来」清单:Grox 用的 LLM 提示词(j2 文件)和部分 botmaker 规则,理由是降低被绕过刷量的风险——所以透明度是分层的,不是全量;③ 默认值来自生产主值的 cron 回写,读到的默认值不等于线上永远是那个值;④ 首次跑模型要等 JAX 编译,官方说可能要几分钟;⑤ phoenix/ 目录里另带 NOTICE 与 THIRD_PARTY_NOTICES.md,你如果要用那一块,第三方声明需要单独过一遍。
仓库:xai-org/x-algorithm · github.com/xai-org/x-algorithm
数据:33,089 star / 5,384 fork / 101 open issue / 296 subscriber / 创建 2026-01-19 / 最近推送 2026-09-10 / 默认分支 main / 体积 4,020 KB / 语言 Rust
许可:Apache-2.0(LICENSE 为标准文本,逐字核对无追加自定义条款)· 无 GitHub Release(releases/latest 为 404)
本文所有架构描述、命令与参数均取自仓库内 README.md、phoenix/QUICKSTART.md、docs/BIDIRECTIONAL_BOOST_CHANGE.md 原文,未做改写;star 与 issue 数取自 GitHub API 实时快照。
数据来源:GitHub Trending 官方页面(月榜第 1 位,本月 +6,248)· 抓取时间:2026-09-11 16:40 GMT+8
你会去翻这份代码吗?如果翻了,最先想看的是那十七个过滤器,还是权重表?留言说说你的判断。
下期写 akitaonrails/ai-memory(6,496 star,本月 +5,026,Rust):给 agent 编码 CLI 做长期记忆,并为不同厂商的 agent 之间做交接。备选是 apache/maka(5,214 star,本月 +3,941,Apache 孵化,agent 全程留痕)与 modular/modular(29,677 star,本月 +3,080,Mojo/MAX)。如果核实下来某一项撑不起一期,我就换月榜下一个高动量项目,绝不硬写。
#推荐算法#开源审计#X