수억 개의 후보를 트랜스포머로 랭킹하는 풀스택 추천 엔진을 그대로 공개한 레포.
수십억 게시물을 한 줄로 줄이는 과정.
X(구 트위터)를 열면 보이는 For You 피드는 마법이 아니다. 백엔드에서는 수십억 개의 후보 포스트를 2단계로 좁히고 랭킹하는 거대한 ML 파이프라인이 매 요청마다 돌아간다. xai-org/x-algorithm은 이 파이프라인을 — 후보 소싱, 콘텐츠 이해, 트랜스포머 랭킹, gRPC 서빙까지 — 거의 통째로 오픈소스로 풀었다.
2023년 트위터가 the-algorithm을 공개했을 때 화제였지만 "코드만 있고 못 돌리는" 한계가 있었다. 이번엔 사전학습된 미니 모델(파라미터 ~3 MB + 임베딩 테이블 1.4 GB × 2 = 전체 ~2.8 GB) + end-to-end inference 스크립트까지 같이 풀어, 누구나 노트북에서 추천 결과를 눈으로 확인할 수 있다.
FAISS, ScaNN, HNSW 등. 수백만~수십억 임베딩에서 ms 단위 검색을 가능하게 한다. 정확도를 1~5% 양보하고 속도를 100배 얻는 거래.한 문장으로 줄이면.
핵심은 세 가지 — (1) Rust 기반 실시간 서빙 레이어(Home Mixer, Thunder), (2) JAX/Haiku 기반 머신러닝(Phoenix Retrieval + Ranking), (3) 콘텐츠 이해(Grox)와 광고 블렌딩까지 포함한 운영 시스템.
"수많은 hand-engineered feature를 다 버리고, Grok 기반 트랜스포머 하나가 19종 액션을 동시에 예측한다"는 차세대 디자인을 가장 구체적으로 보여주는 첫 공개 사례다.
시대적 배경 + 경쟁 우위.
2023년 트위터 the-algorithm 공개는 거대한 화제였지만 한계가 분명했다 — 의존성이 너무 많아 사실상 돌릴 수 없었다. 2026년 5월 업데이트는 그 한계를 정확히 정조준했다. 사전 학습된 미니 모델(파라미터 ~3 MB + 임베딩 테이블 1.4 GB × 2 = 전체 ~2.8 GB)과 end-to-end inference 파이프라인을 함께 배포해, uv sync + run_pipeline.py 두 줄로 노트북에서 추천 결과가 나온다.
또 하나 — xAI가 자기들의 Grok 트랜스포머와 결합한 차세대 추천 알고리즘을 공개한 셈이라 ML 엔지니어 커뮤니티의 주목도가 폭발적이다. 광고 블렌딩(Ads), 콘텐츠 이해(Grox), 후보 소싱(Phoenix MoE) 같은 실제 프로덕션 모듈까지 포함돼, 추천 시스템을 학습용 장난감이 아닌 운영 시스템 관점에서 학습할 수 있다.
| 비교 대상 | x-algorithm의 차별점 |
|---|---|
| twitter/the-algorithm (2023) | hand-engineered feature와 휴리스틱을 제거, Grok 트랜스포머 하나가 multi-action 예측을 통째로 학습 |
| 전통적 MF / GBDT 추천 | 유저 임베딩+히스토리+후보를 시퀀스로 받아 19종 액션(좋아요·리포스트·리플라이·체류시간 등) 동시 예측 |
| 학술 논문용 toy code | 실제 프로덕션 그대로 Rust gRPC 서빙 + Kafka 인제스천 + ANN 검색까지 포함 |
| 일반 LLM 기반 추천 | Candidate Isolation 어텐션 마스크로 후보끼리 못 보게 — 배치 독립적 점수 + 캐시 가능 |
Rust 서빙 · Python/JAX 모델 · 인프라.
ScoredPostsService 엔드포인트, tokio 비동기 런타임Source / Hydrator / Filter / Scorer / Selector / SideEffect 6개 traitsports_corpus.npz)전체 구조도 → 패턴 하나하나.
┌──────────────────────────────────────────────────────────────────┐
│ 유저의 For You 피드 요청 (gRPC) │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ HOME MIXER (Rust, 오케스트레이터) │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ 1. Query Hydration : 유저 액션 시퀀스 + 팔로잉 리스트 │ │
│ │ 2. Candidate Sources: ┌──── Thunder ───┐ ┌── Phoenix ──┐ │ │
│ │ │ 팔로잉의 최근 │ │ ANN으로 코퍼│ │ │
│ │ │ 포스트(InNet) │ │ 스에서 OON │ │ │
│ │ └────────────────┘ └─────────────┘ │ │
│ │ 3. Hydration : 본문/저자/미디어/구독 상태 풀러옴 │ │
│ │ 4. Pre-Filters : 중복/오래된/뮤트/이전 노출 제거 │ │
│ │ 5. Scoring : Phoenix → Weighted → Diversity → OON │ │
│ │ 6. Selection : 최고점 Top-K 선택 │ │
│ │ 7. Post-Filters : VF 필터(스팸/폭력/삭제) + 대화 dedup │ │
│ └────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ ranked feed 응답 │
└──────────────────────────────────────────────────────────────────┘
전세계 수십억 개의 게시물을 한 번에 트랜스포머에 넣을 수는 없다. 그래서 "빠른 모델로 후보 좁히고 → 비싼 모델로 정밀 랭킹"하는 2단계 구조를 쓴다.
회사가 100만 명 이력서를 모두 면접관에게 넘길 수는 없다. 그래서 키워드 매칭 + 점수표(Retrieval)로 1차에서 100명을 추린 뒤, 면접관이 그 100명만 깊이 인터뷰(Ranking)한다. x-algorithm의 2단계도 정확히 같은 발상이다.
일반 트랜스포머는 모든 토큰이 서로 attention한다. 그런데 추천에서 후보들끼리 서로 보게 두면 "이 배치에 어떤 후보들이 같이 있느냐"에 점수가 영향을 받아 일관성과 캐싱이 깨진다. Phoenix는 어텐션 마스크를 다음처럼 설계한다:
Keys (어디를 볼 수 있나)
──────────────────────────────────────────────▶
│ User │ History │ Candidates │
┌────┼──────┼──────────────┼─────────────────────────┤
Q │ U │ ✓ │ ✓ ✓ ✓ ✓ │ ✗ ✗ ✗ ✗ ✗ ✗ ✗ │
u │ H │ ✓ │ ✓ ✓ ✓ ✓ │ ✗ ✗ ✗ ✗ ✗ ✗ ✗ │
e │ │ ✓ │ ✓ ✓ ✓ ✓ │ ✗ ✗ ✗ ✗ ✗ ✗ ✗ │
r │ C │ ✓ │ ✓ ✓ ✓ ✓ │ ✓ ✗ ✗ ✗ ✗ ✗ ✗ ← 자기만 │
y │ a │ ✓ │ ✓ ✓ ✓ ✓ │ ✗ ✓ ✗ ✗ ✗ ✗ ✗ attend│
▼ │ n │ ✓ │ ✓ ✓ ✓ ✓ │ ✗ ✗ ✓ ✗ ✗ ✗ ✗ │
└────┴──────┴──────────────┴─────────────────────────┘
유저와 히스토리는 자유롭게 서로 보지만, 후보는 자기 자신 외엔 다른 후보를 보지 못한다. 덕분에 각 후보 점수는 배치 독립적이 되어 결과가 안정적이고 부분 계산을 캐시할 수 있다.
전통적인 "relevance score 하나" 대신 19종 액션 확률을 동시에 예측한다:
Final Score = Σ (weight_i × P(action_i)) P(favorite) × w_1 P(reply) × w_2 P(repost) × w_3 P(click) × w_4 P(dwell) × w_5 ... P(block_author) × w_-1 ← 음수 가중치 P(report) × w_-2 ← 음수 가중치
긍정 액션엔 양수 가중치, 부정 액션엔 음수 가중치를 곱해 합산. 가중치만 조정하면 "참여도 위주 피드 ↔ 안전 위주 피드" 전환이 가능하다.
candidate-pipeline crate는 추천 흐름을 6개 trait로 추상화한다:
| Trait | 역할 |
|---|---|
Source | 데이터 소스에서 후보 가져오기 (Thunder, Phoenix, Ads) |
Hydrator | 후보에 메타데이터 붙이기 (저자, 미디어, 언어, 브랜드 안전) |
Filter | 부적격 후보 제거 (중복, 뮤트, 차단) |
Scorer | 점수 계산 (Phoenix ML, Weighted, Diversity, OON) |
Selector | 정렬 + Top-K 선택 |
SideEffect | 비동기 부수효과 (캐시, 로깅) |
독립적인 단계는 자동으로 병렬 실행되며, 단계별 에러 핸들링과 모니터링이 비즈니스 로직과 분리된다.
유저/포스트/저자 모두 vocab 1M인데 실제 ID는 수십억이다. 그래서 ID를 2개의 해시 함수로 매핑해 각각 임베딩 테이블에서 lookup 후 합치는 방식을 쓴다. Bloom filter와 비슷한 원리로, 메모리는 고정하면서 충돌 노이즈를 분산시킨다.
수십억 후보를 한 번에 트랜스포머에 넣으면 메모리·시간 둘 다 폭발한다. 1단계 retrieval 없이 ranking만으로는 실시간 서빙 불가능. 후보 isolation 마스크 없이 그냥 attention하면 배치 구성에 따라 같은 후보의 점수가 흔들린다.
2단계 깔때기로 수백만 → 수백으로 좁히고, 어텐션 마스크로 후보를 격리해 배치 독립적인 점수를 만든다. 캐싱 가능 + 일관성 보장 + 비용 통제, 세 가지를 동시에 해결.
어디에 뭐가 있는지.
xai-org/x-algorithm/ ├── home-mixer/ # Rust — 메인 오케스트레이터 (gRPC 서버) │ ├── ads/ # 광고 블렌딩 & 브랜드 안전 모듈 │ ├── candidate_hydrators/ # 후보 hydration 구현체 │ ├── candidate_pipeline/ # 파이프라인 단계 조합 │ ├── filters/ # Age/Selfpost/MutedKeyword/VF 등 │ ├── query_hydrators/ # Query hydration 구현체 │ ├── scorers/ # Weighted/Diversity/OON Scorer │ ├── selectors/ # Top-K 선택 로직 │ ├── side_effects/ # 캐시·로깅 부수효과 │ ├── sources/ # Thunder/Phoenix/Ads 등 후보 소스 │ ├── for_you_server.rs # For You 피드 gRPC 엔트리 │ ├── scored_posts_server.rs # ScoredPostsService gRPC 엔트리 │ └── server.rs # 서버 공통 │ ├── thunder/ # Rust — 실시간 인메모리 포스트 스토어 │ ├── kafka/ # Kafka 연동 (파티션/컨슈머 설정) │ ├── posts/ # 포스트 데이터 구조 │ ├── deserializer.rs # 메시지 역직렬화 │ ├── kafka_utils.rs # Kafka 유틸리티 │ ├── thunder_service.rs # in-network 후보 lookup 서비스 │ ├── main.rs # 엔트리포인트 │ └── lib.rs # 크레이트 루트 │ ├── phoenix/ # Python/JAX — ML 모델 (Retrieval + Ranking) │ ├── artifacts/ # Git LFS로 배포되는 사전학습 모델 + 임베딩 │ │ └── oss-phoenix-artifacts.zip (~2.7 GB) │ │ └── oss-phoenix-artifacts/ │ │ ├── sports_corpus.npz # 537K 스포츠 포스트 데모 코퍼스 │ │ └── example_sequence.json # 예시 유저 액션 시퀀스 (NFL/NBA/NHL) │ ├── recsys_model.py # 랭킹 트랜스포머 (Grok-1 포팅) │ ├── recsys_retrieval_model.py # Two-Tower retrieval 모델 │ ├── run_pipeline.py # ★ end-to-end inference 엔트리 │ ├── grok.py # Grok 트랜스포머 구현 │ ├── runners.py # 실행 헬퍼 │ ├── run_ranker.py # 랭커 단독 실행 │ ├── run_retrieval.py # retrieval 단독 실행 │ ├── test_recsys_model.py # 랭킹 모델 테스트 │ └── README.md │ ├── grox/ # Python — 콘텐츠 이해 (스팸/카테고리/PTOS) │ ├── classifiers/ │ ├── embedders/ │ └── task-engine/ │ ├── candidate-pipeline/ # Rust — 재사용 가능한 추천 파이프라인 crate │ └── src/ # Source/Hydrator/Filter/Scorer trait 정의 │ ├── CODE_OF_CONDUCT.md ├── LICENSE # Apache-2.0 └── README.md
모델만 빠르게 돌려보려면 phoenix/run_pipeline.py가 한 방에 retrieval → ranking을 실행해준다. 서빙 전체 흐름이 궁금하면 home-mixer/for_you_server.rs부터 읽으면 된다. Rust crate 구조가 익숙하지 않아도 candidate-pipeline/src/의 6개 trait 정의만 읽으면 전체 그림이 잡힌다.
기술 영역마다 무엇을 가져갈지.
candidate-pipeline처럼 도메인 흐름을 trait로 추상화해 새 Source/Filter를 한 줄로 추가실습: 작은 RSS 피드 수집기를 candidate-pipeline trait로 모듈화
jit/vmap으로 트랜스포머 forward 표현hk.transform이 파라미터를 어떻게 관리하는지실습: 같은 트랜스포머 구조를 MovieLens 영화 추천으로 옮겨 학습
실습: 자신의 북마크/즐겨찾기를 history로 보고 weight를 바꿔보며 결과 관찰
실습: docker-compose로 Kafka + Rust consumer + JAX inference를 묶은 로컬 데모
모드별 최소 사양.
| 구성 | 최소 (Phoenix 미니 모델) | 권장 (풀스택 빌드) |
|---|---|---|
| OS | Linux / macOS | Linux (Ubuntu 22.04+) |
| RAM | 16 GB (임베딩 1.4GB 로드) | 32 GB+ |
| 디스크 | 10 GB (아티팩트 ZIP ~2.7 GB + 압축 해제 ~5 GB) | 50 GB+ |
| Python | 3.11+(pyproject.toml 기준) | 3.11 + uv |
| Rust | — | 최신 stable + cargo |
| GPU | 불필요 (CPU inference 가능, 느림) | NVIDIA 8GB VRAM+ 또는 Apple Silicon Metal |
| 네트워크 | Git LFS 다운로드용 안정 회선 | Kafka cluster 구성 시 별도 |
Rust 부분은 일단 무시하고 phoenix/에서 uv sync → uv run run_pipeline.py --artifacts_dir ...만으로도 추천 결과를 볼 수 있다. M1/M2 맥북 16GB로도 충분.
JAX는 Windows 네이티브 지원이 제한적이다. WSL2(Ubuntu) 권장. Rust 부분은 Visual C++ Build Tools 필요. Git LFS는 별도 설치(git lfs install) 후 git lfs pull로 모델 다운로드 — 안 하면 3GB 짜리 포인터 파일만 받는다.
난이도별 실습 — Easy → Hard.
1) uv sync로 의존성 설치 2) git lfs pull로 artifacts 다운로드 3) uv run run_pipeline.py --artifacts_dir phoenix/artifacts 4) example sequence 기준 top 10 추천 결과 확인 5) P(favorite), P(repost) 등 액션 확률 비교
배우는 것: end-to-end inference 흐름, 액션별 확률 출력 형태
1) example_sequence.json 열기 2) 자기 취향 시뮬레이션 (NBA만 → MLB도 추가) 3) 같은 sports_corpus에서 상위 결과가 어떻게 달라지는지 관찰 4) 액션 시퀀스 길이를 바꿔보며 모델 sensitivity 확인
배우는 것: 유저 컨텍스트가 추천에 미치는 영향, 시퀀스 모델 동작
1) Weighted Scorer의 가중치 dict를 코드에서 추출 2) "참여도 우선"(favorite·repost 높음) vs "체류시간 우선"(dwell 높음) 두 프로파일 작성 3) 같은 유저에 두 프로파일 적용해 결과 비교 4) 차이를 표/차트로 정리해 README PR 제안
배우는 것: 다중 목표 학습의 가중치 의미, 피드 성격 디자인
1) HuggingFace의 작은 도메인 데이터셋(예: arXiv abstract) 다운로드 2) sentence-transformers로 임베딩 → my_corpus.npz 생성 3) Phoenix retrieval 코드를 거의 그대로 재활용 4) 도메인 검색 엔진처럼 동작 확인 5) ANN(hnswlib)로 속도 비교
배우는 것: Two-Tower 일반화, 임베딩 검색 파이프라인 직접 구성
1) candidate-pipeline의 trait 시스템 학습 2) Rust로 LanguageWhitelistFilter 작성 (한국어/영어만 통과) 3) Home Mixer의 filter 체인에 등록 4) 단위 테스트 작성 5) PR 형태로 통합 (실제 upstream에 보내지 않더라도 git workflow 연습)
배우는 것: Rust trait 시스템, 추천 파이프라인 확장, 실전 워크플로
주차별로 따라가면 전체 스택 마스터.
Collaborative Filtering vs Content-based vs Hybrid 정리 / Two-Tower 원리 (Google "Sampling-Bias-Corrected Neural Recommender" 논문) / MovieLens-100K로 user/item 임베딩 학습
Self-attention 수식 → JAX로 forward 직접 구현 / Haiku 튜토리얼 따라가며 hk.transform 익히기 / recsys_model.py 라인별 주석
tokio + tonic으로 작은 gRPC 서비스 / trait + 제네릭으로 plugin 시스템 흉내 / Home Mixer 코드 읽으며 trait 호출 다이어그램화
FAISS / ScaNN / hnswlib 비교, dot product vs cosine / Phoenix retrieval을 hnswlib와 연결해 실시간 검색기 구축
OpenTelemetry로 파이프라인 stage latency 트레이싱 / Kafka → Thunder 패턴을 자체 도메인(예: 블로그 인덱싱)으로 이식
나만의 도메인(영화/논문/뉴스)으로 Two-Tower retrieval + 트랜스포머 ranker를 처음부터 구현 / Rust gRPC 서버로 감싸 모바일·웹에서 호출
자주 마주칠 단어들.
FAISS, ScaNN, HNSW 등.ScoredPostsService 엔드포인트로 응답.git lfs install + git lfs pull 필요.공식 · 관련 프로젝트 · 학습 리소스.
uv sync → git lfs pull → uv run run_pipeline.py. example_sequence.json 기준 top 10 추천이 출력되는 것을 눈으로 확인.example_sequence.json에서 NFL을 NBA로 바꾸거나 액션 시퀀스를 늘려보며 추천이 어떻게 달라지는지 관찰. "시퀀스 모델은 컨텍스트 민감하다"를 손으로 체감.recsys_model.py에서 어텐션 마스크 만드는 부분을 찾아 왜 후보끼리 못 보게 했는지 주석으로 정리. 이게 이 레포의 핵심 혁신.my_corpus.npz로 만들고 Phoenix retrieval만 재활용해 도메인 검색기를 구축. Two-Tower의 일반화 능력 체감.