0x00 한

Research / Cross-service topics

Embedding-based candidate generation

Candidate generation (retrieval) is the stage that first picks, from all content, the candidates to pass to the ranking model. Embedding-based candidate generation is summarized in one sentence: "find the items closest to the user in a vector space." Behind that sentence, however, are three very different families. Each family builds its vectors differently, and X runs all 3 families at the same time.

Family How the vectors are built X implementation
Graph decomposition Splits the follow graph into communities and uses the degree of membership as the vector SimClusters
Graph embedding Uses the edges of a heterogeneous graph as the training objective TwHIN
Two-tower Trains a user tower and an item tower separately and scores with a dot product DeepRetrieval, Phoenix retrieval

DeepRetrieval, in the two-tower family, is the main candidate generator for posts from accounts the user does not follow in X 2025. Phoenix retrieval is the candidate source that does the same job in the 2026 public code (xai-org/x-algorithm).

Placing Meta alongside adds one more point of comparison. What X's sources let us confirm is "how the vectors are built" (the encoder family). What Meta's sources let us confirm is mostly "where and how those vectors are stored" (the index). The two are different halves of the same question, so reading only one body of literature leaves the other side invisible.

The difference between the two companies' sources is likely a difference in public channels rather than in the actual systems inferred (X vs Meta section). However, X moved to two-tower at the time Meta used two-tower and ANN (approximate nearest neighbor search) as baselines to beat, and this opposite movement is not explained by the sources inferred (same section).

Family 1 — Graph decomposition (SimClusters)

SimClusters vectors are sparse vectors. The number of dimensions equals the number of communities, and most values are 0. These vectors are built by community detection, not by training.

The pipeline has five stages verified — src/scala/com/twitter/simclusters_v2/.

  1. Build cosine similarity between producers (accounts that receive follows) from the follow graph. Producer eligibility is set by minActiveFollowers, and the top topK are kept verified — UpdateKnownFor20M145K2020.scala:65-66.
  2. Cap the number of neighbors with maxNeighbors, and keep the larger of the two directional scores (Max(score)) to make the graph symmetric. If squareWeightsEnable is on, the edge weight becomes cos² × 10. This 10 is hardcoded. A separate wtCoeff parameter sits next to it but is not used here verified — UpdateKnownForSBFRunner.scala:321-339,409-413, UpdateKnownFor20M145K2020.scala:84.
  3. Metropolis-Hastings community detection builds KnownFor (producer → community). This stage runs 1 time per week (batchIncrement = Days(7)) verified — UpdateKnownFor20M145K2020.scala:46.
  4. Multiplying the follow matrix by KnownFor produces InterestedIn, the per-user communities (InterestedIn = followMatrix × KnownFor). It uses socialProofThreshold = 2 and maxClustersPerUser = 50 verified — InterestedInFromKnownFor.scala:85-86,327-336.
  5. Tweet embeddings are accumulated in real time by adding the user's InterestedIn vector each time a like is pressed verified — simclusters_v2/README.md:65-67, summingbird/storm/TweetJob.scala:123-207.

Before quoting constants — code defaults differ from the actual production run

The hyperparameter values visible in the code of the KnownFor job (UpdateKnownFor20M145K2020) are fallbacks used when no CLI argument is given. This is because the job takes all of its hyperparameters in the form args.int("name", default). The same file also keeps the actual production run command as a comment, and two of its values disagree with the code defaults verified — UpdateKnownFor20M145K2020.scala:144,155.

Parameter Code default Production command in comment Match?
minActiveFollowers 400 :65 --minActiveFollowers 400 ✅
topK (number of producers) 20,000,000 :66 --topK 20000000 ✅
maxNeighbors 400 :69 --maxNeighbors 100 ❌ 4×
maxEpochs 3 :83 --maxEpochs 4 ❌
wtCoeff 10.0 :84 --wtCoeff 10.0 ✅ (but not used in step 2 above)

The docstring of the neighboring file also gives maxEpochs as 4: "run the clustering algorithm for x iterations (x = 4 in the prod setting)" verified — UpdateKnownForSBFRunner.scala:50. In other words, two independent documents point to 4, and only the code default is 3. The values to quote are 100 and 4 inferred.

Two more facts are important.

  • "145,000 communities" is not in the code. The community count k is not a hyperparameter set by configuration but a value that emerges from the run verified — repo-wide grep 145000, ModelVersions.scala:11-13, UpdateKnownForSBFRunner.scala:636,646.
    • The integer 145000 appears in the whole repo only in one line of prose at README.md:33 ("In production … k ~= 145000").
    • Otherwise there is only the model version string "20M_145K_2020".
    • In the code, k is carried over from last week's result (maxClusterIdInPreviousAssignment).
  • The algorithm itself is not in the repo. The repo only imports com.twitter.sbf.core.MHAlgorithm, and there is no com/twitter/sbf/ source tree. Only the configuration values are public verified — UpdateKnownForSBFRunner.scala:5-9.

The speed of time decay differs by layer verified.

  • The fav signal in InterestedIn has a half-life of 100 days verified — InterestedInFromKnownFor.scala:286-301.
  • Tweet embeddings have a half-life of 8 hours verified — summingbird/common/Configs.scala:38.

User taste is forgotten slowly, and content popularity is forgotten quickly. Within the same system, the two half-lives are set 300× apart (100 days × 24 hours ÷ 8 hours) verified — InterestedInFromKnownFor.scala:291-292, summingbird/common/Configs.scala:38 (c54bec0).

SANN — search is an inverted-index scan, not ANN

Despite the name "SimClusters ANN", SANN is not a graph index like HNSW. It brute-force scans the entire inverted index from communities to tweets verified — simclusters-ann/.../ApproximateCosineSimilarity.scala.

The scan range is set by two constants.

Scan at most 50 candidate clusters × at most 200 tweets per cluster → accumulate scores → top N

verified — maxScanClusters = 50, maxTopTweetsPerCluster = 200, SimClustersANNCandidateSource.scala:325-347

There are 4 scoring modes, chosen at runtime verified — :111-119.

  • LogCosine: uses score / sourceEmbedding.logNorm / log(1 + norm).
  • Cosine: uses score / l2norm / sqrt(...).
  • NoSourceNorm: skips source normalization.
  • DotProduct: applies no normalization.

The production default is PairEmbeddingCosineSimilarity, with minScore = 0.7 and a candidate age cap of 1.days verified — :325-347.

This approach works because the vectors are sparse. If only 50 dimensions are other than 0, scanning the inverted index costs less than a graph index. It cannot be used with dense vectors inferred.

↑ Contents

Family 2 — Graph embedding (TwHIN)

TwHIN trains (lhs, relation, rhs) triples of a heterogeneous graph (a graph with multiple node and edge types). It is in the TransE family, but uses a dot product rather than distance. TransE is a graph embedding approach that trains so that the first node vector plus the relation vector comes close to the second node vector. TwHIN adds the relation vector on the RHS side and uses the dot product of that result and the LHS as the score.

translated = x[:, 1, :] + trans_embs      # add the relation vector to the RHS
score      = (x[:, 0, :] * translated).sum(-1)

verified — the-algorithm-ml/projects/twhin/models/models.py:53-54,90

The public training code has several problems, so it should not be reproduced as is verified.

  • The config file docstring says "apply the transform to lhs", but the code applies it to rhs verified — config.py:39-41 vs models.py:54.
  • Relation embeddings are looked up with self.all_trans_embs.data[batch.rels]. Going through .data detaches them from autograd. The relation vectors are not trained along this path verified — models.py:36.
  • Negative samples are given label 1 and distinguished by lowering their weight with neg_weight verified — models.py:150-159.
    • The code is labels = cat([batch.labels, ones(num_negatives)]).
  • Negative scores are computed without the relation vector. Only positive samples receive + trans_embs verified — models.py:67-68,83-87.
  • global_negatives is only assigned and is not used in forward(). It is dead code verified — models.py:25.
  • Validation dataset creation is commented out, and the LR scheduler is off (scheduler = None) verified — run.py:49, optimizer.py:63-64.

The production dimension count is not public. The only public config is a single example (embedding_dim: 4), and the test constant is EMB_DIM = 128 verified — local.yaml, test_models.py:16-17.

The serving side is well exposed, separately from the training code verified.

  • It uses 6 Manhattan (X's internal KV store) datasets verified — TwhinEmbeddingsStore.scala:32-49.
    • They are twhin_user_positive_embeddings, twhin_user_negative_embeddings, twhin_tweet_embeddings, twhin_video_embeddings, and 2 rebuild datasets.
  • If engagement is below 16, the embedding is set to 0 verified — :32-49,57-65.
    • The threshold is MinEngagementCount = 16; at or above it, the embedding is normalized by dividing by the count.
  • It also enters as a home ranking feature verified — TwhinEmbeddingsAdapter.scala:33-46.
    • user.twhin.tw_hi_n.user_engagement_as_float_tensor is one example.

In other words, TwHIN is used for retrieval and is also a ranking feature. The same embedding is reused in two places.

↑ Contents

Family 3 — Two-tower (DeepRetrieval)

DeepRetrieval is the main OON (out-of-network, posts from accounts the user does not follow) candidate generator in X 2025. The user tower is called remotely in real time, and the item tower's output is precomputed and stored in a vector DB.

user embedding = Hydra EmbeddingGenerationService gRPC call
item      = VecDB collection (precomputed)
search        = annClient.search(collection, vector, SearchParams(limit, scoreThreshold), filter)

verified — DeepRetrievalUserEmbeddingQueryFeatureHydrator.scala:92-98, DeepRetrievalUserTweetANNCandidateSource.scala:77-84

u2i and i2i differ starting from how they are called verified. u2i finds items with a user vector, and i2i finds items similar to a seed item.

  • u2i: the client carries the user vector and calls search(vector).
  • i2i: does not send a vector and calls searchById(tweetId). The server looks up the seed tweet's vector itself (DeepRetrievalTweetTweetANNCandidateSource.scala:71-77).

The latency budget is very tight. User embedding generation gets 150ms per request and 250ms overall, and only 1% retries are allowed verified — HydraEmbeddingGenerationServiceClientModule.scala:19-40.

A payload filter is applied together with the vector search. The filter fields are category, isHighQuality, isHighNegEngRatio, and tier. In other words, it is not a pure nearest-neighbor search but a filtered ANN verified — DeepRetrievalUserTweetANNCandidateSource.scala:103-121.

There are at least 12 vector DB collections, serving multiple models at once verified — TweetMixerGlobalParams.scala:461-1149. Examples of collection names are as follows.

  • tweet-deep-retrieval, tweet-deep-retrieval-exp3, tweet-deep-retrieval-media
  • content-exploration-deep-retrieval, content-exploration-text-emb
  • creator-incentive-retrieval-v1, refreshed-twhin-tweet, etc.

The training code is not public. tweet-mixer/ has only inference calls; the model structure, loss function, and negative sampling are absent verified — repo-wide.

↑ Contents

Index layer — three kinds coexist

X's code has several vector indexes with different purposes side by side, and each embedding family uses a different index verified.

Index Purpose Parameters
In-house HNSW (Scala/Java) Legacy ANN service No production values in the repo. See below
FAISS SWIG binding IndexHNSWFlat/PQ/SQ —
Qdrant TwHIN only collectionName = "twhin-prod", topK = 100, timeout 20ms
VecDB (in-house thrift) DeepRetrieval family SearchParams(limit, scoreThreshold) and payload filter
SANN inverted index SimClusters See above

verified — TwHINANNCandidateSource.scala:76-78, ann/src/main/java/com/twitter/ann/faiss/swig/

The HNSW numbers should not be quoted. None of the three values that grep finds is a production setting verified.

  • efConstruction = 200 and maxM = 16 are values from example code for a recall benchmark verified — ann/.../experimental/Runner.scala:28-29.
    • The same main() also contains dimen = 300, trainDataSetSize = 2000, and testDataSetSize = 30.
  • ef = 800 is the value of a random query for JIT warmup, not a serving default verified — HnswQueryIndexServer.scala:95.
    • It is the value in queryWithDistance(randomQuery(), 100, HnswParams(ef = 800)) inside warmup().
  • The real values are all job arguments, received in the form args.int("ef_construction") verified — IndexBuilderApp.scala:59, IndexBuilderFromBQApp.scala:124, IndexingStrategy.scala:50. No config file that passes the arguments is in the repo.

We confirmed the same caveat on the 2026 Rust side. A hardcoded constant p::AUTHOR_DIVERSITY_DECAY remained in a scorer file that was disconnected from the module tree. "A grep finds the number" and "that number is actually running" are different matters.

All embedding search results are wrapped in memcache. The TTL is 600 seconds, with 20% jitter. The formula is randomizedTTL(ttl, earlyExpiration = 0.2) = ttl - ttl × 0.2 × rand() verified — Utils.scala:47-49. This appears intended to prevent a thundering herd caused by simultaneous expiry inferred.

There is one exception. Only the tweet-tweet embedding ANN has a TTL of 180 seconds verified — DeepRetrievalTweetTweetEmbeddingANNCandidateSource.scala:52.

The cache code has one caveat. In MemcachedCandidateSource, the path where the cache is "off" still writes. Only reads are skipped verified — MemcachedCandidateSource.scala:41-53.


↑ Contents

Meta — the interesting part is in the index, not the encoder

If Meta's public retrieval literature is laid out chronologically, two-tower appears only once in the middle, and everything before and after is about indexes. In 2020, ANN was added inside the query language of an existing inverted index. In 2024–2026, the index was either trained, as in HILL, or moved inside the model tensors, as in SilverTorch. The question this lineage asks is "what to treat the index as" rather than "which embedding to use."

↑ Contents

Layer 0 — EBR 2020: ANN was not split out as a separate service

Facebook Search's EBR (Embedding-based Retrieval) did not split ANN out as a separate service. Instead, it added one nearest-neighbor operator to the existing Boolean inverted index (Unicorn) verified — Embedding-based Retrieval in Facebook Search, KDD 2020 (arXiv 2020-06). It is a Facebook Search deployment case, but it is placed here because it is the starting point of the later Meta-wide retrieval literature.

The added operator is the single line below.

(nn <key> :radius <radius>)

Because this operator is part of the Boolean query language, hybrid retrieval expressions that freely mix embeddings and terms became possible verified.

"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."

For example, in an expression like (and (term "sports") (nn user_emb :radius 0.3)), term matching and embedding search are composed within a single query. It is not a structure in which a separate ANN service is attached and detached. The paper also states directly why it was built this way verified. Instead of using a separate system, NN was implemented with existing primitives, and it inherited all existing features such as real-time updates, efficient query planning and execution, and multi-hop query support.

"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."

The paper chose radius mode over top-K mode verified. It wrote that radius mode can give a better trade-off between system performance and result quality. As a possible reason, it noted that in radius mode the other conditions of the matching expression constrain the NN search, while top-K mode must scan the whole index.

"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."

That is, the filter is not applied after the ANN result but constrains the ANN search itself. X's DeepRetrieval is also a "filtered ANN" with payload filters (Family 3). However, X passes the filter to the vector DB, whereas in Meta 2020 the filter is the query language itself. These are answers to the same problem that are two steps apart inferred.

The paper also recorded results from quantization tuning and operational guidance.

  • Both PCA and OPQ were tried for data transformation, and OPQ was generally more effective verified same paper.

    "We experimented with both PCA and OPQ to transform the data, and observed that OPQ is generally more effective."

  • Beyond a certain size, the accuracy gain from the PQ (product quantization, a quantization that splits a vector into pieces to compress it) byte count was limited verified same paper: "we found that the accuracy improvement is limited after x > d/4."
  • It recommends comparing IMI and IVF as the coarse quantizer verified same paper: "It is useful to compare between IMI and IVF algorithms."
  • The operational guidance states explicitly to tune online, not against offline recall verified same paper: "Tune nprobe, num_clusters, and pq_bytes online to understand the real perf impact."

The paper also covers hard negative mining (a technique that picks negative samples the model easily confuses with positives for training) in concrete terms.

  • Online HNM builds a small pool from the other positive documents in the batch and picks the document with the highest similarity as the hardest negative verified same paper: "select the documents which received the highest similarity scores as the hardest negatives".
  • Offline HNM runs in the following order verified same paper.
    1. Generate the top K for each query.
    2. Select hard negatives.
    3. Retrain.
    4. Repeat this process.
  • The hardest ones should not be used verified same paper. The paper wrote the result of comparing sampling by rank position as follows.

    "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."

This last quote is the most valuable in practice inferred. Most papers only state "use hard negatives," but this paper is one of the few primary sources that says with numbers that the rank range you sample from determines the result inferred.

↑ Contents

Layer 1 — SilverTorch (2025–2026): moved the index into tensors inside the model

SilverTorch made the item index, eligibility filter, scoring layer, and user tower all into tensors or operators inside a single PyTorch model verified — SilverTorch blog post, 2026-05-26 / paper HTML, arXiv 2025-11 first version.

"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."

SilverTorch has three components verified.

Component What Replaces
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 inverted-index filter
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)" Part of later-stage ranking

The reported improvements are as follows.

  • The Bloom index is 291–523× faster than the CPU inverted index verified.
  • The fused Int8 ANN kernel is 2.2–14.7× faster than Faiss-GPU verified.
  • Overall throughput improves 23.7× in requests per second verified.
  • TCO improves 20.9× verified.

The key point is that the policy filter went inside the GPU kernel inferred. The place where the filter sits is different in all three cases.

  • X passes the filter to the vector DB as a payload condition.
  • Meta 2020 expressed the filter as a Boolean clause of the query language.
  • SilverTorch made the filter a bitwise operation inside the model forward pass.

In all three generations, "where the filter is placed" determines the architecture.

Index refresh cycles and streaming in-place updates are not repeated here. They are covered in model-freshness.md.

↑ Contents

Layer 2 — MoL and h-indexer: drop the dot product, but supplement it with an index

Meta's KDD 2023 paper uses MoL (Mixture of Logits) instead of the dot product as the similarity function, and supplements the ANN index that this made unusable with the h-indexer verified — Revisiting Neural Retrieval on Accelerators, KDD 2023 (Meta).

The paper's problem statement is clear verified. Dot-product-based models produce low-rank recommendations, and dot products cannot capture complex user-item interactions. The paper views these interactions as multifaceted and likely high rank.

"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) expresses similarity as an adaptive mixture of several more elementary logits verified same paper: "parameterize ϕθ(u,x) as an adaptive mixture of more elementary logits".
    • The formula in the follow-up paper is ϕ(q,x) = Σ_{p=1..P} π_p(q,x)·⟨f_p(q), g_p(x)⟩. That is, an adaptive gating weight is applied to the dot products of P pairs of low-rank embeddings verified — Retrieval with Learned Similarities, Bailu Ding (Microsoft) and Jiaqi Zhai (Meta), arXiv 2024-07.
  • The h-indexer is a 1-stage search that pulls a wide set of candidates in front of MoL verified. MoL is not a dot product, so an ANN index cannot be used, and search is therefore split into 2 stages verified.

    "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."

  • Serving latency is comparable to an optimized CPU MIPS baseline (MIPS is a search for the items with the largest dot product) verified.

    "The median serving GPU latency of our proposed architecture is 30ms, comparable with optimized CPU MIPS baseline (20ms)."

In short, it is not that "the dot product was dropped" but that "the dot product was demoted to stage 1" inferred. The price of higher expressiveness is paid by stacking one more index layer. Read together with the statement that the OverArch of SilverTorch implements MoL, MoL appears to have moved from the 2023 paper into the 2026 production serving stack inferred. However, the two documents do not explicitly link to each other.

⚠ The h-indexer is not in the MoL paper (2407.15462). The source is the KDD 2023 paper observed 2026-08-09. The inspected corpus is 2 HTML full texts, v3 and v4, of arXiv 2407.15462, and the string h-indexer appears 0 times in them.

↑ Contents

Layer 3 — RankGraph: use a co-engagement graph as the retrieval index

The RankGraph family builds a graph linked by relations in which users engaged together (co-engagement) and uses it for retrieval verified.

  • RankGraph builds a heterogeneous graph (users, posts, ads, and so on) that spans several products verified — arXiv:2509.02942, 2025-09.
    • The graph goes through a GPU GNN and contrastive learning and is used for similarity retrieval and real-time clustering.
    • The paper positions RankGraph as a component of a recommendation foundation model.
  • RankGraph-2 uses two paths, U2U2I / U2I2I verified — arXiv HTML 2606.18379, 2026.
    • U2U2I traverses the graph in the order user→user→item, and U2I2I in the order user→item→item.
    • It builds 3 edge types (U-U, I-I, U-I) from engagement data alone.
    • For I-I edges it corrects popularity bias and precomputes neighbors based on PPR (Personalized PageRank).
    • Offline recall rose by up to 3.8×.
    • In a 14-day A/B, CTR rose by +0.96% and CVR by +2.75%. The change in U2U serving infrastructure cost is −83%.

The RankGraph family has a different goal from X's SimClusters inferred. SimClusters decomposes the follow graph into communities and updates 1 time per week. RankGraph-2 rebuilds the entire engagement graph every 3 hours (model-freshness.md). The difference between a social-graph basis and a behavior-graph basis shows up directly as a difference in update cycle. However, X also uses engagement edges in TwHIN, so simplifying this to "X = social / Meta = behavior" is wrong.

↑ Contents

Layer 4 — Andromeda + HILL/MoNN: train the index

Andromeda, Meta's ads retrieval engine, organizes ads into a multi-layer hierarchical index and trains this index together with the retrieval model verified — 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."

The last phrase of the second quote is the most important line in these notes. Meta explicitly named two-tower and ANN as baselines it has to beat. A primary source directly refutes the framing in which this repository has recorded that "two-tower is the standard."

  • It names the Grace Hopper Superchip and MTIA as hardware verified: "NVIDIA Grace Hopper Superchip and Meta Training and Inference Accelerator (MTIA) hardware"
  • It reports gains in retrieval recall and in ad quality on some segments verified: "+6% recall improvement to the retrieval system, delivering +8% ads quality improvement on selected segments"

The internal structure of the Andromeda-family index was published as a paper in 2026 verified — Efficient Retrieval Scaling with Hierarchical Indexing for Large Scale Recommendation, Meta, arXiv 2026-04-14.

  • MoNN (Modular Neural Network) strengthens the learning of user-item interactions beyond a single dot product while maintaining efficiency verified same paper.

    "enhances the learning of sophisticated user and item interactions beyond a single dot product while maintaining high efficiency."

    • MoNN is the foundation retrieval model of the Meta Ads Platform. The paper states: "currently in service for recommending advertisements to daily Facebook and Instagram users."
  • HILL is a method that organizes the memory of the foundation retrieval model hierarchically, so that search along the structure is fast while exactness is maintained verified same paper.

    "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."

  • Tree construction itself is training verified same paper.
    • The paper writes that it aims at a learning-based tree construction method: "we aim to design a learning-based tree construction method".
    • It places a cross-attention that uses item embeddings as the query.
    • Inspired by residual quantization, it tries to pass residue between input item embeddings: "inspired by residual quantization ... we aim to pass residue between input item embedding".
    • It is trained together with the foundation retrieval model: "enjoys the co-training with the foundation retrieval model."
  • The paper writes that it concretized the concept of test-time training in the recommendation system domain verified same paper.

    "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, and MoNN are all ads-side literature. We could not find a first-party statement that the same stack is used for organic feed retrieval inferred. The inspected corpus is the Meta blog and papers in the source list below, plus 15 system cards from the Transparency Center (viewed 2026-08-09). This is not evidence that nothing exists in undisclosed material. However, the direction matches the single line "a novel hierarchical deep neural retrieval architecture" from the 2023 organic-side blog quoted in facebook.md, so there is room to read it as a lineage connection inferred.

↑ Contents

Layer 5 — Instagram: a case where serving cost decided the architecture

Instagram is a case where a serving cost constraint decided the retrieval model structure verified. Detailed quotes and context are in instagram.md.

  • The reason for choosing two-tower is the cache, not accuracy verified — Scaling the Instagram Explore recommendations system, 2023-08-09.

    "user and item embeddings can be cached, making inference for the Two Tower model extremely efficient"

    • On ANN, the post writes only this: "e.g., FAISS, HNSW, etc".
    • The serving constraint that the item-tower output can be cached decided the model structure. It is a kind of reason that almost never appears in papers, and it is the sentence closest to the reason this repository exists.
    • In return, the item-tower retraining cycle is tied to the cost of rebuilding the index. Details are in model-freshness.md.
  • The label of the first-stage ranker is whether the item made the top-K chosen by the second-stage ranker verified. That is, retrieval and ESR (early-stage ranking) are built as distillation of the ranker.

    "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}"

  • IGQL is a retrieval-only DSL, and IG2Vec is an account embedding. The 2019 original text could not be accessed because of HTTP 500, so primary evidence for these names is missing observed 2026-08-09.
    • The original address is ai.meta.com/blog/powered-by-ai-instagrams-explore-recommender-system/.
    • We requested 3 URLs directly: ai.meta.com, ai.facebook.com, and a Medium mirror.
  • However, the way it works is still preserved in the 2020 post verified. The 2020 post refers to this query language without a name, and the query that these notes call IGQL is as follows.
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)

The account_nn(embedding_config=default) in this query is the IG2Vec-family account embedding plus nearest neighbors inferred. The basis is that the method name itself says account nearest-neighbor.

And this query matches the hybrid idea of Layer 0 inferred. Embedding search (account_nn), policy filter (filter), ranking, and diversity are all inside one retrieval expression. The (nn ...) operator of Facebook Search 2020 and the query language of the Instagram 2020 post independently arrived at the same form inferred.

↑ Contents

Layer -1 — Facebook connected content has no ANN at all

Facebook connected content needs no embedding search, because the corpus is already limited to the user's connections inferred — facebook.md. The funnel scale is described only for the unconnected side verified — ai.meta.com, 2023: "billions → thousands → a few hundred".

This structure is the root cause of the gap in the amount of literature between the two companies inferred. X has few connections in its follow graph and a large OON share from the start, so retrieval was the product. Facebook was dominated by connected content until 2021, so the retrieval problem itself was small. The share of unconnected content changed from 11.7% in 2021 to 41.0% in 2025 Q4 (facebook.md), and this period overlaps with the point when Meta's retrieval literature started to grow substantially.

↑ Contents

Case: Netflix — a second sample of the "no retrieval" extreme

As of TMIS 2015, Netflix has no ANN either. Facebook connected content has no ANN because "the graph is the candidate set"; Netflix has no ANN because "the catalog is the candidate set." PVR (the ranker of the classic generation that ordered titles within genre rows) sorts "the whole catalog (or a genre-filtered subset) per member" verified — TMIS 2015. With a corpus of thousands to tens of thousands, everything can be scored by brute force, so the problem of "finding near items quickly" does not exist. Netflix is a control showing that ANN is a product of the condition of a corpus in the hundreds of millions, not a technique in itself.

Netflix also uses embeddings. However, the purpose is different.

  • Embedding store: the Foundation Model (FM) builds member embeddings and title embeddings in daily batches and puts them in the embedding store, and downstream models reuse them both as features and as candidate sources verified — unified blog 2025.
    • It is the same dual-use pattern as X's TwHIN serving both search and ranking features. Embedding reuse holds regardless of corpus size.
    • The unified blog says only that these embeddings can also be used for candidate generation, and does not disclose whether ANN is used observed 2026-10-19 (1 blog body).
  • New title embeddings are initialized from metadata, not from ID verified — FM blog 2025.
    • For a new title, the embedding is built as a metadata-based weighted combination, and the mixing ratio with the ID embedding is adjusted by entity-age-based attention verified.
    • This is the opposite of X's TwHIN setting the embedding to 0 when engagement is below 16 (Family 2). X discards when there is no signal; Netflix fills in with content information.
    • This is likely possible because it is a curated catalog in which every title has rich metadata inferred.
  • The classic generation's sims (title similarity) combined a non-personalized similarity list with personalized row selection verified TMIS 2015. The simplest form of i2i stayed alive until recently.

The overall Netflix structure is in netflix.md.

↑ Contents

X vs Meta — structural contrast

The contrast between the two companies' materials is likely a difference in release channels rather than a real difference in systems inferred. X released code, and Meta writes papers and blogs. Code reveals constants, and papers reveal multipliers.

Axis X Meta
What is published Names and constants of candidate sources (11 home-mixer sources in Gen3 0bfc279; maxScanClusters = 50 and a TTL of 600 seconds in 2025 c54bec0) Index structure and multipliers (Bloom index 291–523×, Int8 kernel 2.2–14.7×)
Unit of candidate generation Runs named sources in parallel, then concatenates Descends once through a hierarchical index
Where filters apply Vector DB payload conditions and pipeline filters (17 in Gen3 0bfc279, 21 since the 2026-08-14 commit) Query-language Boolean clauses (2020) → GPU kernel bitwise (2026)
Encoder SimClusters / TwHIN / two-tower, 3 families run at once two-tower is an option on one surface (IG Explore), and the ads side explicitly tries to beat it
Where the interesting engineering is Encoder and constants Index
What cannot be seen Training code, loss, negative sampling (DeepRetrieval undisclosed) Per-source candidate count breakdown for the organic feed (no primary source found)

Concluding "X is encoder-centric, Meta is index-centric" is wrong; the safe statement is only that "the half each side published is different" inferred.

However, one thing is not explained by the shape of the sources inferred. Meta wrote directly that two-tower and ANN are baselines to beat, while X moved toward two-tower (DeepRetrieval, Phoenix retrieval) in 2025–2026. The two companies moved in opposite directions at the same time.


↑ Contents

What generalizes from this case

Looking at X alone yields generalizations about embedding families, vector search, embedding reuse, public code and constants, and time decay.

  • A service does not use only one embedding family. X runs SimClusters' sparse graph decomposition, TwHIN's graph embedding, and DeepRetrieval's two-tower at the same time, and each family has a different index technology. The question "which embedding do they use" is wrong because the question itself is singular.

  • Vector search is not pure nearest neighbor. In production, payload filters (quality, tier, category) are always applied together. This is the first reason that the paper's recall@k differs from production behavior.

  • Embeddings are reused for search and for ranking features. TwHIN is the representative case. The cost of building them is split across two places.

  • For sparse vectors, an inverted-index scan beats a graph index. This is why SimClusters does not use HNSW.

  • Published training code is not production code. Reading X's public TwHIN code as "this is how X trains" is wrong. This code is in the following state:

    • The validation loader is commented out.
    • The scheduler is off.
    • The relation embeddings are detached with .data.
  • There are three kinds of constants, and all three may not be production.

    1. The default in args.int(name, default) is a fallback, not the executed value.
    2. Literals in benchmark and warmup code have nothing to do with executed values.
    3. Constants in files cut off from the module tree are not executed.

    For X's SimClusters, the actual execution command remains in a comment in the same file, so we could confirm the first case. Without such a clue, numbers found by grep must not be quoted.

  • Time-decay constants differ by layer. At X, the half-life of taste is 100 days and the half-life of content is 8 hours. They are not unified under a single half-life.

Putting the Meta side alongside adds five more.

  • "Where to put vector search" is the core of the architecture decision.
    • Instead of splitting ANN out as a separate service, Meta put it in as an operator ((nn <key> :radius <radius>)) inside the existing Boolean inverted index verified (EBR, arXiv 2006.11632, KDD 2020).
    • 5 years later, SilverTorch turned the index itself into a model tensor and put it on the GPU verified (SilverTorch, arXiv 2511.14881, 2025-11).
    • Both decisions changed the location of the index, not the encoder.
  • Where filters are placed separates the generations. 2020 EBR put the filter inside the index query, the 2020 Instagram post put it inside the query language, and 2025 SilverTorch put it inside the GPU kernel verified (the three sources above). In effect, the same problem as X attaching payload filters to ANN calls was solved three different ways.
  • A serving cost constraint sometimes dictates the model structure. The reason Instagram explicitly gave for choosing two-tower in 2023 Explore is not accuracy but that the item tower output can be cached verified (Instagram Explore, 2023). Detailed quotes are in instagram.md.
  • In Meta's literature, two-tower is not the goal but a baseline to beat. Andromeda describes its gains against two-tower/ANN, and MoL against dot-product models and a CPU MIPS baseline verified (Layer 2, Layer 4). Conversely, X moved toward two-tower in 2025–2026 verified (x.md). The same technique is a destination on one side and a starting point on the other.
    • The baselines of the RankGraph family are Filament2, GAT-DGI, PBG, and HSTU, so they are not statements against two-tower/ANN verified — arXiv 2509.02942, arXiv 2606.18379.
  • The release channel determines what can be known. Source code reveals constants and hides ratios. Papers and blogs reveal ratios (+6% recall, 23.7× RPS) and hide constants. It is not that "X has constants and Meta does not" but that the half each side published is different. This is also why the X section and the Meta section of these notes differ in sentence density inferred.

Putting Netflix alongside adds two more.

  • The existence of a retrieval layer is a function of corpus size.
    • With hundreds of millions of items (X OON, Meta unconnected), ANN is essential.
    • When limited by a graph (Facebook connected), the graph is the candidate set.
    • With thousands to tens of thousands (Netflix), everything can be scored by brute force.
    • "Which ANN do you use" is a question that does not hold in the third case.
  • Cold-start embeddings have two extremes: discard and fill. X's TwHIN uses a 0 vector when engagement is below 16, while the Netflix FM initializes from metadata and then blends it in with entity-age attention. UGC (user-uploaded content) has thin metadata and an unbounded set of items. A curated catalog has rich metadata and a finite set of items. This difference shows up directly in the design.

↑ Contents

Who uses this

X, Instagram, and Meta ads use the embedding retrieval techniques covered in these notes verified. Facebook is where EBR was first published (search), but ANN does not appear in the connected content path inferred. As of TMIS 2015, Netflix scores the whole catalog, and whether it uses ANN in the FM generation is not public verified (TMIS) / observed 2026-10-19 (FM unified blog). For Threads, the candidate generation approach itself is not public observed 2026-08-09.

  • X uses SimClusters, TwHIN, DeepRetrieval, and Phoenix retrieval verified — x.md.
    • SimClusters (2020–) updates 1 time per week.
    • DeepRetrieval was the mainstay in 2025, and Phoenix retrieval is in the 2026 public code.
    • The encoder families are public, and the index has 3 layers: Qdrant/VecDB/FAISS.
  • Facebook is where EBR was first published (search), but ANN does not appear in the connected content path inferred — facebook.md.
    • On the feed side, the share of unconnected content rose from 11.7% (2021 Q3) to 41.0% (2025 Q4), and this period overlaps with the period when retrieval literature grew substantially.
    • In the connected content path, the follow graph is the candidate set.
  • Instagram is the only case in this repository where a primary source says "serving cost decided the model" verified — instagram.md.
    • The 2020 post released a retrieval query language that uses account-embedding nearest neighbors (account_nn) verified. The names IG2Vec and IGQL have no primary evidence because the 2019 original text could not be opened observed 2026-08-09.
    • Explore uses two-tower (2023; cacheability was the reason for the choice).
  • Meta's Layers 1–4 cover different surfaces. Only Layer 4 (Andromeda and HILL/MoNN) is explicitly stated to be ads-only verified.
    • For SilverTorch (Layer 1), the blog says it is the main retrieval system for UGC feeds and video content verified — SilverTorch blog, 2026-05-26.
    • The MoL and h-indexer (Layer 2) paper says only that it experimented in one of Meta's large recommendation deployments and does not name the surface verified — arXiv 2306.04039.
    • RankGraph (Layer 3) is a cross-domain graph holding users, posts, and ads together, and RankGraph-2 is described as deployed on several Meta surfaces verified — arXiv 2509.02942, arXiv 2606.18379.
    • There is no first-party statement that Andromeda and HILL/MoNN are also used for the organic feed inferred.
  • Threads has no statement at all about embedding retrieval and ANN. The candidate generation approach itself is not public observed 2026-08-09 — threads.md.
    • The inspected sources are the body of the ig-threads-feed card and 2 Threads engineering blog posts.
  • Netflix scores the whole catalog as of TMIS 2015. It builds the FM embedding store daily, uses it as dual-use for features and candidates, and initializes new titles from metadata verified — netflix.md.

Index rebuild and retraining cycles are summarized in model-freshness.md. SilverTorch, the 3-hour cycle of RankGraph-2, and Instagram's daily item tower count here. They are not repeated here.

↑ Contents

Not yet verified

We could not yet confirm four things on the X side and five on the Meta side.

  • X
    • We do not know the production embedding dimension and the actual relation set of TwHIN. Only the 4 relations in the example configuration are public.
    • We do not know the model structure, loss, and negative sampling of DeepRetrieval. Only the inference call is public.
    • We do not know the full list of what the legacy HNSW ANN service indexes.
    • We do not know the scoring formula of GraphJet TopSecondDegreeByCountForTweet. It is an external project, so VLDB 2016 p1281 must be consulted.
  • Meta
    • We do not know the per-source candidate counts of the organic feed. X has a constant for each source, but on the Meta side only the total funnel volumes of Facebook and Instagram are public. We do not know how many of them come from embedding sources.
    • We do not know whether Andromeda and HILL/MoNN also serve the organic feed. All of it is ads literature. We could not find a statement that the same infrastructure is used.
    • We do not know the current status of IG2Vec and IGQL. They have not been mentioned since the 2020 blog, and the original URL returns 500 (measured 2026-08-09). We cannot tell whether they were retired or only renamed.
    • We do not know the index update latency of SilverTorch. Throughput and TCO figures exist, but "how many seconds until a new item becomes searchable" does not.
    • We do not know whether radius mode is still used. It was the key choice of 2020 EBR, but the word radius does not appear again in later literature.

↑ Contents

Sources

X

  • twitter/the-algorithm — commit c54bec0 of 2025-09-03, public source code. We referred to simclusters_v2/, simclusters-ann/, ann/, and tweet-mixer/.
  • twitter/the-algorithm-ml — 2023, public source code. We referred to projects/twhin/.
  • xai-org/x-algorithm — 2026, public source code (Apache 2.0).
  • SimClusters — KDD 2020 paper. Quoted from simclusters_v2/README.md:9. Figures not reproduced in the repo appear only in the paper.

Meta — index layers

  • Embedding-based Retrieval in Facebook Search — 2020, paper (KDD 2020). Primary source for the (nn <key> :radius <radius>) operator, the radius vs. top-K comparison, OPQ/IMI tuning, and hard negative mining.
  • Revisiting Neural Retrieval on Accelerators — 2023, paper (KDD 2023). Definition of MoL and the primary source of the h-indexer.
  • Retrieval with Learned Similarities — 2024, paper. Follow-up to MoL. ⚠ The term h-indexer is not in this paper (full check of v3 and v4 HTML, 2026-08-09).
  • SilverTorch: Index as Model — 2026-05-26, blog post / arXiv 2511.14881 — 2025-11 first version, paper. The OverArch and MoL mentions are only in the paper and not in the blog post.
  • RankGraph — 2025, paper. Uses a co-engagement graph for retrieval.
  • RankGraph-2 — 2026, paper. Covers the U2U2I / U2I2I paths and the 3-hour rebuild.
  • Meta Andromeda — 2024-12-02, blog post. Describes the trained hierarchical index and explicitly names two-tower and ANN as baselines.
  • HILL / MoNN — 2026-04-14, paper. Covers the structure of the Andromeda-family index in detail.

Netflix

Meta — service-side evidence (the original quotes are in the service notes)

↑ Contents