임베딩 기반 후보 생성
최종 갱신: 2026-08-09
"유저와 가까운 아이템을 벡터 공간에서 찾는다"는 한 문장 뒤에 서로 아주 다른 세 계열이 있다. X는 셋을 동시에 운영한다.
| 계열 | 벡터를 만드는 방법 | X의 구현 |
|---|---|---|
| 그래프 분해 | 팔로우 그래프를 커뮤니티로 쪼개고 소속도를 벡터로 | SimClusters |
| 그래프 임베딩 | 이종 그래프의 엣지를 학습 목표로 | TwHIN |
| Two-tower | 유저탑·아이템탑을 따로 학습, 내적으로 채점 | DeepRetrieval, Phoenix retrieval |
Meta를 같이 놓으면 축이 하나 더 생긴다. X 자료에서 캘 수 있는 건 "벡터를 어떻게 만드는가"(인코더 계열)이고, Meta 자료에서 캘 수 있는 건 대부분 **"그 벡터를 어디에 어떻게 담는가"(인덱스)**다. 같은 질문의 서로 다른 절반이라 한쪽 문헌만 읽으면 다른 쪽이 통째로 안 보인다. 아래 X 3계열 → Meta 인덱스 스택 → 대조 순서로 읽으면 된다.
계열 1 — 그래프 분해 (SimClusters)
희소 벡터다. 차원이 커뮤니티 수이고 대부분 0이다. 학습이 아니라 커뮤니티 탐지로 만든다.
파이프라인 [확인] src/scala/com/twitter/simclusters_v2/:
- 팔로우 그래프에서 producer-producer 코사인 유사도를 만든다. producer 자격
minActiveFollowers, 상위topK명.[확인]UpdateKnownFor20M145K2020.scala:65-66 - 이웃을
maxNeighbors로 자르고, 양방향 중Max(score)로 대칭화.squareWeightsEnable이 켜져 있으면 엣지 가중치를cos² × 10으로 바꾼다 — 이10은 하드코딩이다(옆에wtCoeff파라미터가 따로 있는데 여기 안 쓰인다).[확인]UpdateKnownForSBFRunner.scala:321-339,409-413,UpdateKnownFor20M145K2020.scala:84 - Metropolis-Hastings 커뮤니티 탐지로 KnownFor(producer → 커뮤니티)를 만든다. 주 1회 실행(
batchIncrement = Days(7)).[확인]UpdateKnownFor20M145K2020.scala:46 InterestedIn = 팔로우행렬 × KnownFor→ 유저 → 커뮤니티.socialProofThreshold = 2,maxClustersPerUser = 50.[확인]InterestedInFromKnownFor.scala:85-86,327-336- 트윗 임베딩은 좋아요가 눌릴 때마다 그 유저의 InterestedIn 벡터를 더해서 실시간으로 누적한다.
[확인]simclusters_v2/README.md:65-67,summingbird/storm/TweetJob.scala:123-207
상수를 인용하기 전에 — 코드 기본값과 실제 프로덕션 실행이 다르다
이 잡의 하이퍼파라미터는 전부 args.int("name", default) 꼴이라 코드에 보이는 값은 CLI 인자가 없을 때의 폴백이다. 그리고 같은 파일이 주석으로 실제 프로덕션 실행 명령을 적어놨는데, 두 개가 어긋난다. [확인] — UpdateKnownFor20M145K2020.scala:144,155
| 파라미터 | 코드 기본값 | 주석의 프로덕션 명령 | 일치? |
|---|---|---|---|
minActiveFollowers |
400 :65 |
--minActiveFollowers 400 |
✅ |
topK (producer 수) |
20,000,000 :66 |
--topK 20000000 |
✅ |
maxNeighbors |
400 :69 |
--maxNeighbors 100 |
❌ 4배 |
maxEpochs |
3 :83 |
--maxEpochs 4 |
❌ |
wtCoeff |
10.0 :84 |
--wtCoeff 10.0 |
✅ (단 위 2번에서 안 쓰임) |
maxEpochs는 옆 파일 docstring도 "run the clustering algorithm for x iterations (x = 4 in the prod setting)"이라고 쓴다 [확인] UpdateKnownForSBFRunner.scala:50. 즉 두 개의 독립된 문서가 4를 가리키는데 코드 기본값만 3이다. 인용해야 할 값은 100과 4다 [추정].
중요한 사실 두 가지 더:
- "커뮤니티 145,000개"는 코드에 없다. 정수
145000은 레포 전체에서README.md:33산문 한 줄에만 나오고("In production … k ~= 145000"), 나머지는 모델 버전 문자열"20M_145K_2020"뿐이다. 코드상 k는 지난주 결과에서 이어받는다(maxClusterIdInPreviousAssignment). 즉 설정하는 하이퍼파라미터가 아니라 창발값이다.[확인]— repo-widegrep 145000,ModelVersions.scala:11-13,UpdateKnownForSBFRunner.scala:636,646 - 알고리즘 본체가 레포에 없다.
com.twitter.sbf.core.MHAlgorithm을 import만 하고com/twitter/sbf/소스 트리는 없다. 공개된 건 설정값뿐이다.[확인]UpdateKnownForSBFRunner.scala:5-9
시간 감쇠가 계층마다 다르다 [확인]:
- InterestedIn의 fav 신호: 반감기 100일 (
InterestedInFromKnownFor.scala:286-301) - 트윗 임베딩: 반감기 8시간 (
summingbird/common/Configs.scala:38)
유저 취향은 느리게, 콘텐츠 인기는 빠르게 잊는다. 같은 시스템 안에서 시간 상수를 12,500배 다르게 잡았다.
SANN — 검색은 ANN이 아니라 역인덱스 스캔이다
이름은 "SimClusters ANN"인데 HNSW 같은 그래프 인덱스가 아니다. 커뮤니티 → 트윗 역인덱스를 브루트포스로 훑는다. [확인] — simclusters-ann/.../ApproximateCosineSimilarity.scala
후보 클러스터 최대 50개 스캔 × 클러스터당 트윗 최대 200개 → 점수 누적 → 상위 N
[확인] — maxScanClusters = 50, maxTopTweetsPerCluster = 200, SimClustersANNCandidateSource.scala:325-347
채점 모드가 4가지고 런타임에 고른다 [확인] :111-119:
LogCosine—score / sourceEmbedding.logNorm / log(1 + norm)Cosine—score / l2norm / sqrt(...)NoSourceNorm— 소스 정규화 생략DotProduct— 정규화 없음
프로덕션 default는 PairEmbeddingCosineSimilarity, minScore = 0.7, 후보 나이 상한 1.days. [확인] :325-347
희소 벡터라서 이게 성립한다. 0이 아닌 차원이 50개뿐이면 역인덱스 스캔이 그래프 인덱스보다 싸다. 조밀 벡터에는 못 쓰는 접근이다 [추정].
계열 2 — 그래프 임베딩 (TwHIN)
이종 그래프의 (lhs, relation, rhs) 트리플을 학습한다. TransE 계열인데 거리가 아니라 내적을 쓴다.
translated = x[:, 1, :] + trans_embs # RHS에 관계 벡터를 더한다
score = (x[:, 0, :] * translated).sum(-1)
[확인] — the-algorithm-ml/projects/twhin/models/models.py:53-54,90
공개 학습 코드에 문제가 여럿 있다 — 그대로 재현하면 안 된다 [확인]:
- 설정 파일 docstring은 "lhs에 변환을 적용"이라고 쓰는데 코드는 rhs에 적용한다.
config.py:39-41vsmodels.py:54 - 관계 임베딩 조회가
self.all_trans_embs.data[batch.rels]—.data라서 autograd에서 분리된다. 이 경로로는 관계 벡터가 학습되지 않는다.models.py:36 - 음성 샘플에 레이블 1을 붙이고
neg_weight로 눌러서 구분한다.labels = cat([batch.labels, ones(num_negatives)]).models.py:150-159 - 음성 점수는 관계 벡터 없이 계산한다. 양성만
+ trans_embs를 받는다.models.py:67-68,83-87 global_negatives는 대입만 되고forward()에서 안 쓰인다. 죽은 코드.models.py:25- 검증 데이터셋 생성이 주석 처리, LR 스케줄러 비활성(
scheduler = None).run.py:49,optimizer.py:63-64
프로덕션 차원 수는 공개 안 됐다. 공개된 유일한 설정은 장난감(embedding_dim: 4)이고 테스트 상수는 EMB_DIM = 128이다. [확인] local.yaml, test_models.py:16-17
서빙 쪽은 별개로 잘 드러난다 [확인]:
- Manhattan 데이터셋 6종:
twhin_user_positive_embeddings,twhin_user_negative_embeddings,twhin_tweet_embeddings,twhin_video_embeddings, rebuild 2종.TwhinEmbeddingsStore.scala:32-49 - 참여 16회 미만이면 임베딩을 0으로 만든다 (
MinEngagementCount = 16, 이상이면 카운트로 나눠 정규화).:32-49,57-65 - 홈 랭킹 피처로도 들어간다:
user.twhin.tw_hi_n.user_engagement_as_float_tensor등.TwhinEmbeddingsAdapter.scala:33-46
즉 TwHIN은 검색용이면서 동시에 랭킹 피처다. 임베딩을 두 군데서 재사용한다.
계열 3 — Two-tower (DeepRetrieval)
X 2025의 주력 OON 후보 생성기. 유저탑은 실시간 원격 호출, 아이템탑은 미리 계산해 벡터DB에.
유저 임베딩 = Hydra EmbeddingGenerationService gRPC 호출
아이템 = VecDB 컬렉션(사전 계산)
검색 = annClient.search(collection, vector, SearchParams(limit, scoreThreshold), filter)
[확인] — DeepRetrievalUserEmbeddingQueryFeatureHydrator.scala:92-98, DeepRetrievalUserTweetANNCandidateSource.scala:77-84
u2i와 i2i가 호출 방식부터 다르다 [확인]:
- u2i: 클라이언트가 유저 벡터를 들고 가서
search(vector) - i2i: 벡터를 안 보내고
searchById(tweetId)— 씨앗 트윗의 벡터를 서버가 알아서 찾는다.DeepRetrievalTweetTweetANNCandidateSource.scala:71-77
지연 예산이 아주 빡빡하다. 유저 임베딩 생성은 요청당 150ms / 총 250ms, 재시도는 1%만. [확인] HydraEmbeddingGenerationServiceClientModule.scala:19-40
페이로드 필터가 벡터 검색에 붙는다 — category, isHighQuality, isHighNegEngRatio, tier. 즉 순수 최근접이 아니라 필터링된 ANN이다. [확인] DeepRetrievalUserTweetANNCandidateSource.scala:103-121
컬렉션이 최소 12개다 — tweet-deep-retrieval, tweet-deep-retrieval-exp3, content-exploration-deep-retrieval, content-exploration-text-emb, tweet-deep-retrieval-media, creator-incentive-retrieval-v1, refreshed-twhin-tweet 등. 여러 모델을 동시에 서빙한다. [확인] TweetMixerGlobalParams.scala:461-1149
학습 코드는 공개되지 않았다. tweet-mixer/에는 추론 호출만 있고 아키텍처·손실·음성 샘플링이 없다. [확인] — repo-wide
인덱스 계층 — 세 종류가 공존한다
| 인덱스 | 용도 | 파라미터 |
|---|---|---|
| 자체 HNSW (Scala/Java) | 레거시 ANN 서비스 | 레포에 프로덕션 값 없음 — 아래 참조 |
| FAISS SWIG 바인딩 | IndexHNSWFlat/PQ/SQ |
— |
| Qdrant | TwHIN 전용 | collectionName = "twhin-prod", topK = 100, timeout 20ms |
| VecDB (자체 thrift) | DeepRetrieval 계열 | SearchParams(limit, scoreThreshold) + payload filter |
| SANN 역인덱스 | SimClusters | 위 참조 |
[확인] — TwHINANNCandidateSource.scala:76-78, ann/src/main/java/com/twitter/ann/faiss/swig/
HNSW 숫자를 인용하면 안 되는 이유. grep으로 잡히는 세 값이 전부 프로덕션 설정이 아니다 [확인]:
efConstruction = 200,maxM = 16→ann/.../experimental/Runner.scala:28-29. 같은main()안에dimen = 300,trainDataSetSize = 2000,testDataSetSize = 30이 함께 있는 리콜 벤치마크 장난감이다.ef = 800→HnswQueryIndexServer.scala:95.warmup()안의queryWithDistance(randomQuery(), 100, HnswParams(ef = 800))— JIT 예열용 무작위 쿼리지 서빙 기본값이 아니다.- 실제 값은 전부 잡 인자다:
args.int("ef_construction")[확인]IndexBuilderApp.scala:59,IndexBuilderFromBQApp.scala:124,IndexingStrategy.scala:50. 인자를 넘기는 설정 파일은 레포에 없다.
같은 함정을 2026 Rust 쪽에서도 봤다 — 모듈 트리에서 끊긴 스코어러 파일에 p::AUTHOR_DIVERSITY_DECAY 하드코딩 상수가 남아 있는 것. "grep으로 숫자가 잡힌다"와 "그 숫자가 돌고 있다"는 다른 얘기다.
모든 임베딩 검색 결과는 memcache로 감싼다. TTL 600초에 **지터 20%**를 준다 — randomizedTTL(ttl, earlyExpiration = 0.2) = ttl - ttl × 0.2 × rand(). 동시 만료로 인한 thundering herd 방지다 [추정]. [확인] Utils.scala:47-49
예외: 트윗-트윗 임베딩 ANN만 TTL 180초다. [확인] DeepRetrievalTweetTweetEmbeddingANNCandidateSource.scala:52
캐시 코드에 함정이 하나 있다. MemcachedCandidateSource에서 캐시가 "꺼진" 경로도 쓰기는 한다 — 읽기만 건너뛴다. [확인] MemcachedCandidateSource.scala:41-53
Meta — 재미있는 부분이 인코더가 아니라 인덱스에 있다
Meta의 retrieval 공개 문헌을 시간순으로 늘어놓으면 two-tower가 등장하는 건 중간 한 번뿐이고, 앞뒤로는 전부 인덱스 얘기다. 2020년에는 ANN을 기존 역인덱스 쿼리 언어에 밀어 넣었고, 2024~2026년에는 인덱스를 학습시키거나(HILL) 모델 텐서 안으로 접어 넣었다(SilverTorch). "무슨 임베딩을 쓰나"보다 **"인덱스를 무엇으로 볼 것인가"**가 이 계보의 질문이다.
계층 0 — EBR 2020: ANN을 별도 서비스로 빼지 않았다
[확인] Embedding-based Retrieval in Facebook Search, KDD 2020 (arXiv 2020-06). Facebook Search 배포 사례이지만 이후 Meta 전체 retrieval 문헌의 출발점이라 여기에 둔다.
기존 Boolean 역인덱스(Unicorn)에 최근접 이웃 연산자를 하나 추가했다:
(nn <key> :radius <radius>)
"By having the (nn) operator as part of our Boolean query language we can now support hybrid retrieval expressions, with arbitrary combinations of embeddings and terms."
즉 (and (term "sports") (nn user_emb :radius 0.3)) 같은 식으로 term 매칭과 임베딩 검색이 한 쿼리 안에서 합성된다. 별도 ANN 서비스에 붙였다 떼는 구조가 아니다. 이유도 직접 밝힌다 [확인]:
"By implementing NN support in terms of pre-existing primitives, instead of writing a separate system, we inherited all the features of the existing system, such as realtime updates, efficient query planning and execution, and support for multi-hop queries."
radius 모드 vs top-K 모드 — 논문이 radius 쪽을 골랐다 [확인]:
"we found that radius mode can give better trade-off of system performance and result quality. One possible reason is that radius mode enables a constrained NN search (constrained by other parts of the matching expression) but top K mode provides a more relaxed operation which needs to scan the whole index to get top K results."
→ 필터가 ANN 결과에 사후 적용되는 게 아니라 ANN 탐색 자체를 제약한다. X의 DeepRetrieval도 payload 필터를 거는 "필터링된 ANN"이지만(위 참조), 그건 벡터DB에 필터를 넘기는 형태고 Meta 2020은 필터가 곧 쿼리 언어다. 같은 문제의식에 대한 두 단계 다른 답이다 [추정].
양자화 튜닝 (전부 [확인], 같은 논문):
- "We experimented with both PCA and OPQ to transform the data, and observed that OPQ is generally more effective."
- PQ 바이트 수: "we found that the accuracy improvement is limited after x > d/4."
- coarse quantizer: "It is useful to compare between IMI and IVF algorithms."
- 운영 지침: "Tune nprobe, num_clusters, and pq_bytes online to understand the real perf impact." — 오프라인 recall이 아니라 온라인에서 튜닝하라고 명시한다.
hard negative mining (전부 [확인]):
- online HNM: 배치 안의 다른 positive 문서들로 작은 풀을 만들고 "select the documents which received the highest similarity scores as the hardest negatives"
- offline HNM: (1) 쿼리별 top K 생성 → (2) hard negative 선택 → (3) 재학습 → (4) 반복
- 가장 어려운 것을 쓰면 안 된다: "using the hardest examples is not the best strategy. We compared sampling from different rank positions and found sampling between rank 101-500 achieved the best model recall."
[추정] 이 마지막 문장이 실무적으로 제일 값나간다. "hard negative를 쓴다"는 서술만 있는 논문이 대부분인데 어느 랭크 구간에서 뽑는지가 결과를 가른다고 수치로 말한 몇 안 되는 1차 출처다.
계층 1 — SilverTorch (2025~2026): 인덱스를 모델 안 텐서로 접었다
[확인] SilverTorch 블로그, 2026-05-26 / 논문 HTML, arXiv 2025-11 초판
"Index as Model: Every retrieval component — the item index, eligibility filter, scoring layer and user tower — becomes a tensor or operator inside a single PyTorch model."
부품이 셋이다 (전부 [확인]):
| 부품 | 무엇 | 대체 대상 |
|---|---|---|
| Bloom Index | "a bloom-filter-based GPU indexing structure. It addresses the warp divergence of forward index, ensures contiguous memory access through bitwise operations" | CPU 역인덱스 필터 |
| fused Int8 ANN kernel | "representing embeddings with 8-bit integers and leveraging GPU's dp4a instruction (computing four multiply-adds in one instruction)", "streams item embeddings directly from the embedding table ... avoiding intermediate tensor construction" | Faiss-GPU |
| OverArch | "a SilverTorch model includes additional scoring layers (referred to as the OverArch layer)", *"The OverArch implements Mixture of Logits (MoL)"* | 후단 랭킹 일부 |
수치 (전부 [확인]): Bloom index 291–523× faster than CPU inverted index / fused Int8 ANN kernel 2.2–14.7× faster than Faiss-GPU / 전체 23.7× more requests per second, TCO 20.9×.
정책 필터가 GPU 커널 안으로 들어갔다는 게 핵심이다 [추정]. X는 필터를 벡터DB의 payload 조건으로 넘기고, Meta 2020은 쿼리 언어의 Boolean 절로 표현했고, SilverTorch는 모델 forward 안의 bitwise 연산으로 만들었다. 세 세대 모두 "필터를 어디에 두느냐"가 아키텍처를 결정한다.
인덱스 갱신 주기·스트리밍 in-place 갱신은 여기서 반복하지 않는다 → model-freshness.md.
계층 2 — MoL과 h-indexer: 내적을 버리되 인덱스로 벌충한다
[확인] Revisiting Neural Retrieval on Accelerators, KDD 2023 (Meta)
문제 제기가 명확하다: "dot product-based models produce low-rank recommendations", "dot products cannot capture complex user-item interactions, which are multifaceted and likely high rank." [확인]
- MoL (Mixture of Logits) — "parameterize ϕθ(u,x) as an adaptive mixture of more elementary logits". 후속 논문의 수식:
ϕ(q,x) = Σ_{p=1..P} π_p(q,x)·⟨f_p(q), g_p(x)⟩— P쌍의 저랭크 임베딩 내적에 adaptive gating weight를 건다.[확인]Retrieval with Learned Similarities, Bailu Ding(Microsoft)·Jiaqi Zhai(Meta), arXiv 2024-07 - h-indexer — MoL은 내적이 아니라 ANN 인덱스를 못 쓴다. 그래서 2단으로 나눈다
[확인]: "an accelerator friendly algorithm, h-indexer, is used to find a large number of candidates (k′=10⁵) out of a 100M corpus, and then a complex similarity function (e.g., mixture-of-logits) is used to find the final top k (e.g., 100) candidates." - 지연: "The median serving GPU latency of our proposed architecture is 30ms, comparable with optimized CPU MIPS baseline (20ms)."
[확인]
→ "내적을 버렸다"가 아니라 "내적을 1단으로 강등했다"이다 [추정]. 표현력을 올리는 대가를 인덱스 계층 하나를 더 쌓아서 치른다. SilverTorch의 OverArch가 MoL을 구현한다는 문장과 이어 읽으면, MoL은 2023 논문에서 2026 프로덕션 서빙 스택으로 실제로 넘어간 것으로 보인다 [추정] — 다만 두 문서가 서로를 명시적으로 잇지는 않는다.
⚠ h-indexer는 MoL 논문(2407.15462)에 없다. 출처는 KDD 2023 쪽이다. [관찰] 2026-08-09 — 검사한 코퍼스는 arXiv 2407.15462의 v3·v4 HTML 전문 2건이고 h-indexer 문자열이 0회다.
계층 3 — RankGraph: co-engagement 그래프를 retrieval 인덱스로 쓴다
- RankGraph
[확인]arXiv:2509.02942, 2025-09. 여러 프로덕트를 가로지르는 이종 그래프(users, posts, ads 등)를 만들고 GPU GNN + contrastive learning으로 similarity retrieval과 real-time clustering에 쓴다. recommendation foundation model의 구성요소로 위치시킨다. - RankGraph-2
[확인]arXiv HTML 2606.18379, 2026. U2U2I / U2I2I 두 경로. 엣지 3종(U-U, I-I, U-I)을 참여 데이터만으로 만들고, I-I 엣지에 popularity bias 보정, PPR 기반 이웃 사전계산. 오프라인 recall 최대 3.8×, 14일 A/B에서 CTR +0.96% / CVR +2.75%, U2U 서빙 인프라 비용 −83%.
X의 SimClusters와 겨냥이 다르다 [추정]: SimClusters는 팔로우 그래프를 커뮤니티로 분해하고 주 1회 갱신하는데, RankGraph-2는 참여 그래프를 3시간마다 통째로 다시 만든다(→ model-freshness.md). 소셜 그래프 기반과 행동 그래프 기반의 차이가 그대로 갱신 주기 차이로 나온다. 다만 X도 TwHIN에서 참여 엣지를 쓰므로 "X=소셜 / Meta=행동"으로 단순화하면 틀린다.
계층 4 — Andromeda + HILL/MoNN: 인덱스를 학습시킨다
[확인] Meta Andromeda, 2024-12-02
"Andromeda organizes ads into a hierarchical index with multiple layers, reducing the number of inference steps by focusing only on most relevant nodes." "The hierarchical index and retrieval models are jointly trained, which aligns the index representations with neural networks; this improves both precision and recall compared to commonly used two-tower neural networks or approximate nearest neighbor search."
이 마지막 절이 이 노트에서 가장 중요한 한 줄이다. Meta가 two-tower + ANN을 자기가 이겨야 할 베이스라인으로 명시했다. 이 저장소가 "two-tower가 표준"이라고 적어온 프레임을 1차 출처가 직접 반박한다.
- 하드웨어: "NVIDIA Grace Hopper Superchip and Meta Training and Inference Accelerator (MTIA) hardware"
[확인] - 성과: "+6% recall improvement to the retrieval system, delivering +8% ads quality improvement on selected segments"
[확인]
2026년에 논문으로 내부가 열렸다 [확인] Efficient Retrieval Scaling with Hierarchical Indexing for Large Scale Recommendation, Meta, arXiv 2026-04-14:
- MoNN = Modular Neural Network — "enhances the learning of sophisticated user and item interactions beyond a single dot product while maintaining high efficiency." Meta Ads Platform의 foundation retrieval model이고 "currently in service for recommending advertisements to daily Facebook and Instagram users."
- HILL — "aims to hierarchically organize the memory in the foundation retrieval model, such that the searching and retrieval along the structure can be speed up and maintain the exactness."
- 트리 구축이 학습이다: "we aim to design a learning-based tree construction method", 아이템 임베딩을 query로 쓰는 cross-attention, "inspired by residual quantization ... we aim to pass residue between input item embedding", 그리고 "enjoys the co-training with the foundation retrieval model."
- test-time training: "fine-tuning the model on this set further improves inference performance, and concretize the concept of 'test-time training' within the recommendation system domain."
⚠ Andromeda·HILL·MoNN은 전부 광고 쪽 문헌이다. 유기적 피드 retrieval에 같은 스택이 쓰인다는 1차 진술을 찾지 못했다. [추정] — 검사한 코퍼스는 아래 출처 목록의 Meta 블로그·논문과 Transparency Center 시스템 카드 15종(2026-08-09 열람)이다. 미공개 자료까지 없다는 근거는 아니다. — 다만 facebook.md가 인용한 2023년 유기 쪽 블로그의 "a novel hierarchical deep neural retrieval architecture" 한 줄과 방향이 같아 계보상 연결로 읽을 여지는 있다 [추정].
계층 5 — Instagram: 서빙 비용이 아키텍처를 정한 사례
상세는 instagram.md에 있고 여기서는 왜 이 노트에 값나가는지만 적는다.
- two-tower를 고른 이유가 정확도가 아니라 캐시다
[확인]: "user and item embeddings can be cached, making inference for the Two Tower model extremely efficient" — Scaling the Instagram Explore recommendations system, 2023-08-09. ANN은 *"e.g., FAISS, HNSW, etc"*라고만 쓴다.- → 아이템탑 출력이 캐시 가능하다는 서빙 제약이 모델 구조를 결정했다. 논문에서는 거의 안 나오는 종류의 이유이고, 이 저장소가 존재하는 이유에 가장 가까운 문장이다. 그 대가(아이템탑 재학습 주기가 인덱스 재구축 비용에 묶임)는 → model-freshness.md.
- first-stage ranker의 라벨이 second-stage의 top-K 소속 여부다
[확인]: "We train the first stage ranker to predict the output of the second stage with the label: PSelect = { media in top K results ranked by the second stage}" — retrieval/ESR을 랭커의 증류로 만든다. - IGQL / IG2Vec — retrieval 전용 DSL과 계정 임베딩. 2019년 원문(
ai.meta.com/blog/powered-by-ai-instagrams-explore-recommender-system/)이 HTTP 500으로 접근 불가라 이름의 1차 근거는 비어 있다[관찰]2026-08-09 (직접 요청해 본 URL 3건 —ai.meta.com·ai.facebook.com·Medium 미러). 다만 기계는 2020년 글에 살아 있다[확인]:
user.let(seed_id=user_id)
.liked(max_num_to_retrieve=30)
.account_nn(embedding_config=default)
.posted_media(max_media_per_account=10)
.filter(non_recommendable_model_threshold=0.2)
.rank(ranking_model=default)
.diversify_by(seed_id, method=round_robin)
[추정] account_nn(embedding_config=default)가 IG2Vec 계열 계정 임베딩 + 최근접 이웃이다 — 메서드 이름 자체가 account nearest-neighbor다. 그리고 이 쿼리는 계층 0의 하이브리드 사상과 같다: 임베딩 검색(account_nn)·정책 필터(filter)·랭킹·다양성이 하나의 retrieval 표현식 안에 들어 있다. Facebook Search 2020의 (nn ...) 연산자와 Instagram 2020의 IGQL이 독립적으로 같은 형태에 도달했다.
계층 -1 — Facebook 연결 콘텐츠에는 ANN이 아예 없다
[추정] 코퍼스가 이미 유저의 연결로 제한돼 있어 임베딩 검색이 필요 없다 → facebook.md. 비연결 쪽만 *"billions → thousands → a few hundred"*로 서술된다 [확인] ai.meta.com, 2023.
이게 두 회사 문헌량 격차의 근본 원인이다 [추정]: X는 팔로우 그래프가 얇고 OON 비중이 처음부터 커서 retrieval이 곧 제품이었고, Facebook은 2021년까지 연결 콘텐츠가 주력이라 retrieval 문제 자체가 작았다. 그 비중이 2021 11.7% → 2025 Q4 41.0%로 바뀌었고(facebook.md), Meta의 retrieval 문헌이 쏟아지기 시작한 시점과 겹친다.
케이스: Netflix — "retrieval 없음" 극단의 두 번째 표본
Facebook 연결 콘텐츠가 "그래프가 곧 후보 집합"이라 ANN이 없다면, Netflix는 "카탈로그가 곧 후보 집합"이라 ANN이 없다. PVR이 *"카탈로그 전체(또는 장르 필터된 부분집합)를 회원별로 정렬"*한다 [확인] TMIS 2015 — 코퍼스 수천~수만이면 브루트포스 전 채점이 가능해서 "가까운 것을 빨리 찾는" 문제 자체가 없다. ANN은 기법이 아니라 코퍼스가 수억이라는 조건의 산물임을 보여주는 대조군이다.
그래도 임베딩은 쓴다 — 용도가 다르다:
- FM 임베딩 스토어
[확인]통합 블로그 2025: Foundation Model이 회원·타이틀 임베딩을 일 단위 배치로 만들어 임베딩 스토어에 넣고, 다운스트림 모델들이 피처와 후보 소스 양쪽으로 재사용한다. X의 TwHIN이 검색용+랭킹 피처 겸용인 것과 같은 dual-use 패턴 — 임베딩 재사용은 코퍼스 크기와 무관하게 성립한다. - 신규 타이틀 임베딩 초기화가 ID가 아니라 메타데이터다
[확인]FM 블로그 2025: 신규 타이틀은 메타데이터 기반 가중 결합으로 임베딩을 만들고, entity age 기반 어텐션으로 ID 임베딩과 혼합 비율을 조절한다. X TwHIN이 참여 16회 미만이면 임베딩을 0으로 만드는 것(위 계열 2)과 정반대 — X는 신호 없으면 버리고, Netflix는 콘텐츠 정보로 채운다. 편성 카탈로그(모든 타이틀에 풍부한 메타데이터)라 가능한 선택[추정]. - 고전 세대의 sims(타이틀 유사도)는 비개인화 유사도 리스트 + 개인화 행 선택 조합
[확인]TMIS 2015 — i2i의 가장 단순한 형태가 최근까지 살아 있었다.
X vs Meta — 구조적 대조
| 축 | X | Meta |
|---|---|---|
| 공개된 것 | 후보 소스의 이름과 상수 (소스 11개, maxScanClusters = 50, TTL 600초) |
인덱스의 구조와 배율 (Bloom index 291–523×, Int8 kernel 2.2–14.7×) |
| 후보 생성의 단위 | 이름 붙은 소스를 병렬 실행 후 concat | 계층 인덱스를 한 번 타고 내려감 |
| 필터가 붙는 곳 | 벡터DB payload 조건 + 파이프라인 필터 17개 | 쿼리 언어 Boolean 절(2020) → GPU 커널 bitwise(2026) |
| 인코더 | SimClusters / TwHIN / two-tower 3계열 동시 운영 | two-tower는 한 지면(IG Explore)의 선택지, ads 쪽은 명시적으로 그걸 이기려 함 |
| 재미있는 엔지니어링의 위치 | 인코더와 상수 | 인덱스 |
| 못 보는 것 | 학습 코드·손실·음성 샘플링 (DeepRetrieval 미공개) | 유기적 피드의 소스별 후보 수 분해 (no primary source found) |
[추정] 이 대조는 실제 시스템 차이라기보다 공개 채널 차이일 가능성이 크다. X는 코드를 던졌고 Meta는 논문과 블로그를 쓴다. 코드는 상수를 노출하고 논문은 배율을 노출한다. "X는 인코더 중심, Meta는 인덱스 중심"으로 결론내면 안 되고, "각자 공개한 절반이 다르다"까지가 안전하다.
다만 하나는 자료 형태로 설명되지 않는다 [추정]: Meta는 two-tower + ANN을 이겨야 할 베이스라인이라고 직접 썼고 X는 2025~2026에 two-tower(DeepRetrieval, Phoenix retrieval)로 옮겨갔다. 같은 시점에 반대 방향이다.
이 케이스에서 일반화할 수 있는 것
- 한 서비스가 임베딩 계열을 하나만 쓰지 않는다. X는 희소 그래프 분해(SimClusters), 그래프 임베딩(TwHIN), two-tower(DeepRetrieval)를 동시에 운영하고, 각각 인덱스 기술도 다르다. "무슨 임베딩 쓰나"는 질문 자체가 단수형이라 틀렸다.
- 벡터 검색은 순수 최근접이 아니다. 프로덕션에서는 payload 필터(품질·티어·카테고리)가 항상 붙는다. 논문의 recall@k와 프로덕션 동작이 다른 첫 번째 이유다.
- 임베딩은 검색용과 랭킹 피처용으로 재사용된다. TwHIN이 대표적이다. 만드는 비용을 두 곳에서 나눠 갚는다.
- 희소 벡터면 역인덱스 스캔이 그래프 인덱스보다 낫다. SimClusters가 HNSW를 안 쓰는 이유다.
- 공개된 학습 코드는 프로덕션 코드가 아니다. X의 TwHIN 공개 코드는 검증 로더가 주석 처리되고 스케줄러가 꺼진 상태이며 관계 임베딩이
.data로 분리돼 있다. 이걸 "X가 이렇게 학습한다"로 읽으면 안 된다. - 상수는 세 종류이고 셋 다 프로덕션이 아닐 수 있다. ①
args.int(name, default)의 default는 폴백이지 실행값이 아니다 ② 벤치마크·warmup 코드의 리터럴은 아예 무관하다 ③ 모듈 트리에서 끊긴 파일의 상수는 안 돈다. X SimClusters는 같은 파일 주석에 실제 실행 명령이 남아 있어 ①을 잡아낼 수 있었지만, 그런 단서가 없으면 grep으로 나온 숫자는 인용하면 안 된다. - 시간 감쇠 상수가 계층마다 다르다. X는 취향 100일 / 콘텐츠 8시간. 하나의 반감기로 통일하지 않는다.
Meta 쪽을 붙이면 여기에 다섯 개가 더 붙는다.
- "벡터 검색을 어디에 둘 것인가"가 아키텍처 결정의 본체다. Meta는 ANN을 별도 서비스로 빼는 대신 기존 Boolean 역인덱스 안의 연산자(
(nn <key> :radius <radius>))로 넣었고[확인](EBR, arXiv 2006.11632, KDD 2020), 5년 뒤 SilverTorch에서는 인덱스 자체를 모델 텐서로 접어 GPU에 올렸다[확인](SilverTorch, arXiv 2511.14881, 2025-11). 두 결정 모두 인코더를 바꾼 게 아니라 인덱스의 위치를 바꾼 것이다. - 필터를 어디에 두느냐가 세대를 가른다. 2020 EBR은 필터를 인덱스 질의문 안에, 2020 IGQL은 질의 언어 안에, 2025 SilverTorch는 GPU 커널 안에 넣었다
[확인](위 세 출처). X가 payload 필터를 ANN 호출에 붙이는 것과 같은 문제를 세 번 다르게 푼 셈이다. - 서빙 비용 제약이 모델 구조를 지정하는 경우가 있다. Instagram이 2023 Explore에서 two-tower를 고른 이유로 명시한 건 정확도가 아니라 아이템 타워 출력이 캐시 가능하다는 점이다
[확인](Instagram Explore, 2023). 자세한 인용은 instagram.md. - Meta 문헌에서 two-tower는 목표가 아니라 이겨야 할 baseline이다. Andromeda·MoL·RankGraph 모두 자기 성과를 two-tower/ANN 대비로 서술한다
[확인](아래 출처 3건). 반대로 X는 2025~2026에 two-tower 쪽으로 이동했다[확인](x.md). 같은 기법이 한쪽에선 도착점, 한쪽에선 출발점이다. - 공개 채널이 무엇을 알 수 있는지 정한다. 소스코드는 상수를 노출하고 비율을 숨기며, 논문·블로그는 비율(+6% recall, 23.7× RPS)을 노출하고 상수를 숨긴다. "X는 상수가 있고 Meta는 없다"가 아니라 각자 공개한 절반이 다르다. 이 노트의 X 절과 Meta 절의 문장 밀도가 다른 이유이기도 하다.
[추정] - retrieval 레이어의 존재는 코퍼스 크기의 함수다. 수억 아이템(X OON, Meta 비연결)이면 ANN이 필수고, 그래프로 제한되면(Facebook 연결) 그래프가 후보 집합이고, 수천~수만(Netflix)이면 브루트포스 전 채점이 된다. "무슨 ANN 쓰세요"는 세 번째 경우엔 성립하지 않는 질문이다.
- 콜드스타트 임베딩의 두 극단 — 버리기 vs 채우기. X TwHIN은 참여 16회 미만이면 0벡터, Netflix FM은 메타데이터로 초기화 + entity age 어텐션. UGC(메타데이터 빈약, 아이템 무한)와 편성 카탈로그(메타데이터 풍부, 아이템 유한)의 차이가 그대로 설계에 나온다.
누가 이걸 쓰는가
- X — SimClusters(2020~, 주 1회 갱신), TwHIN, DeepRetrieval(2025 주력), Phoenix retrieval(2026). 인코더 계열이 공개돼 있고 인덱스는 Qdrant/VecDB/FAISS 3계층.
[확인]→ x.md - Facebook — EBR의 원 발표처(검색)이고, 피드 쪽은 unconnected 콘텐츠 비중이 11.7%(2021 Q3) → 41.0%(2025 Q4)로 오르면서 retrieval 문헌이 쏟아진 시기와 겹친다. 다만 연결 콘텐츠 경로에는 ANN이 등장하지 않는다 — 팔로우 그래프가 곧 후보 집합이다.
[확인]→ facebook.md - Instagram — IG2Vec 계정 임베딩 + IGQL 질의 언어(2020), Explore two-tower(2023, 캐시 가능성이 선정 이유). 이 저장소에서 "서빙 비용이 모델을 정했다"고 1차 출처로 말할 수 있는 유일한 사례.
[확인]→ instagram.md - Meta ads — SilverTorch, MoL + h-indexer, RankGraph/RankGraph-2, Andromeda + HILL/MoNN. 위 계층 1~4가 전부 여기 문헌이다. organic feed가 같은 스택을 쓴다는 1차 진술은 없다.
[확인](ads 한정) /[추정](organic 적용 여부) - Threads — 임베딩 retrieval·ANN에 대한 진술이 하나도 없다. 후보 생성 방식 자체가 미공개.
[관찰]2026-08-09 (ig-threads-feed 카드 본문 + Threads 엔지니어링 블로그 2편) → threads.md - Netflix — ANN 없음(전 카탈로그 채점). FM 임베딩 스토어(일 단위, 피처+후보 dual-use), 신규 타이틀은 메타데이터 초기화. → netflix.md
[확인]
인덱스 재구축·재학습 주기(SilverTorch, RankGraph-2 3시간, Instagram 일 단위 아이템 타워)는 model-freshness.md에 정리돼 있다. 여기서 반복하지 않는다.
아직 확인 못한 것
X 쪽:
- TwHIN 프로덕션 임베딩 차원과 실제 관계 집합 (공개된 건 장난감 설정 4개 관계뿐)
- DeepRetrieval의 모델 구조·손실·음성 샘플링 — 추론 호출만 공개
- 레거시 HNSW ANN 서비스가 무엇을 인덱싱하는지 전체 목록
- GraphJet
TopSecondDegreeByCountForTweet의 점수 계산식 — 외부 프로젝트, VLDB 2016 p1281 참조
Meta 쪽:
- organic feed의 소스별 후보 수. X는 소스마다 상수가 있는데 Meta는 Facebook/Instagram 퍼널 총량만 공개돼 있고 임베딩 소스가 그중 얼마인지 모른다.
- Andromeda·HILL/MoNN이 organic feed도 서빙하는가. 전부 ads 문헌이다. 같은 인프라를 쓴다는 진술을 못 찾았다.
- IG2Vec·IGQL의 현재 상태. 2020년 블로그 이후 언급이 없고, 원문 URL은 500을 반환한다(2026-08-09 실측). 폐기됐는지 이름만 바뀌었는지 불명.
- SilverTorch의 인덱스 갱신 지연. 처리량·TCO 수치는 있는데 "새 아이템이 몇 초 만에 검색 가능해지는가"가 없다.
- radius mode를 아직도 쓰는가. 2020 EBR의 핵심 선택인데 이후 문헌에 radius라는 단어가 다시 안 나온다.
출처
X
- twitter/the-algorithm — 2025-09-03 커밋
c54bec0, 공개 소스코드.simclusters_v2/,simclusters-ann/,ann/,tweet-mixer/. - twitter/the-algorithm-ml — 2023, 공개 소스코드.
projects/twhin/. - xai-org/x-algorithm — 2026, 공개 소스코드 (Apache 2.0).
- SimClusters — KDD 2020 논문.
simclusters_v2/README.md:9에서 인용. 레포에 재현되지 않은 수치는 논문에만 있다.
Meta — 인덱스 계층
- Embedding-based Retrieval in Facebook Search — 2020, 논문 (KDD 2020).
(nn <key> :radius <radius>)연산자, radius vs top-K, OPQ/IMI 튜닝, hard negative mining의 1차 출처. - Revisiting Neural Retrieval on Accelerators — 2023, 논문 (KDD 2023). MoL 정의와 h-indexer의 1차 출처.
- Retrieval with Learned Similarities — 2024, 논문. MoL 후속. ⚠ h-indexer라는 용어는 이 논문에 없다 (v3·v4 HTML 전수 확인, 2026-08-09).
- SilverTorch: Index as Model — 2026-05-26, 블로그 / arXiv 2511.14881 — 2025-11 초판, 논문. OverArch·MoL 언급은 논문에만 있고 블로그에는 없다.
- RankGraph — 2025, 논문. co-engagement 그래프 retrieval.
- RankGraph-2 — 2026, 논문. U2U2I / U2I2I, 3시간 재구축.
- Meta Andromeda — 2024-12-02, 블로그. 학습되는 계층적 인덱스, two-tower/ANN을 baseline으로 명시.
- HILL / MoNN — 2026-04-14, 논문. Andromeda 계열 인덱스의 구조 상세.
Netflix
- The Netflix Recommender System (TMIS) — 2015, 논문. PVR의 전 카탈로그 정렬.
- Foundation Model for Personalized Recommendation — 2025, 블로그. 메타데이터 콜드스타트 초기화, entity age 어텐션.
- Integrating Netflix's Foundation Model — 2025, 블로그. 임베딩 스토어 dual-use.
Meta — 서비스 쪽 근거 (인용 원문은 서비스 노트에 있다)
- Scaling the Instagram Explore recommendations system — 2023-08-09, 블로그. two-tower를 아이템 타워 캐시 가능성 때문에 골랐다는 진술.
- Designing a Constrained Exploration System — 2020-12-10, 블로그. IGQL 쿼리 예시. ⚠ engineering.fb.com에 다른 제목으로 중복 게재.
- The AI behind unconnected content recommendations — 2023, 블로그. billions → thousands → hundreds 퍼널.
- ⚠ IG2Vec·IGQL 명칭의 1차 원문(2019 "Powered by AI")은 2026-08-09 기준 HTTP 500. 이 노트는 그 글을 근거로 쓰지 않았다.
[관찰]2026-08-09 (요청해 본 URL 3건)