AnyLearn
All lessons
AIintermediate

The two-stage funnel: retrieval then ranking

Every feed has the same impossible job: pick ten items out of millions, in under a tenth of a second. The answer is a funnel. Learn why recommenders split into a cheap retrieval stage and an expensive ranking stage, how two-tower models make retrieval possible, and why nearest-neighbour search is the trick that makes the whole thing fit in a budget.

Updated · AI-authored, review-gated · how lessons are made

Not signed in: your progress and quiz score won't be saved.
Progress1 / 9

An impossible budget

Open any feed and a system just did something absurd. It had a corpus of maybe a hundred million items. It had to choose roughly ten. It had about 100 milliseconds. And it had to do it for every user at once.

Score every item with a good model and the arithmetic kills you immediately. If one scoring pass takes 1 millisecond, then 100 million items is 100,000 seconds, which is over a day, for one refresh.

So no production recommender ever scores everything. That is the single fact that explains the architecture of every feed you use. The system must throw away almost the entire corpus before it thinks carefully, and the interesting engineering is entirely in how you throw things away cheaply without losing the good ones.

The standard answer is a funnel: a couple of stages, each smaller and smarter than the last.

Full lesson text

All 9 steps on one page, for reading, reference, and search.

Show

1. An impossible budget

Open any feed and a system just did something absurd. It had a corpus of maybe a hundred million items. It had to choose roughly ten. It had about 100 milliseconds. And it had to do it for every user at once.

Score every item with a good model and the arithmetic kills you immediately. If one scoring pass takes 1 millisecond, then 100 million items is 100,000 seconds, which is over a day, for one refresh.

So no production recommender ever scores everything. That is the single fact that explains the architecture of every feed you use. The system must throw away almost the entire corpus before it thinks carefully, and the interesting engineering is entirely in how you throw things away cheaply without losing the good ones.

The standard answer is a funnel: a couple of stages, each smaller and smarter than the last.

2. The funnel

The architecture that became standard, popularised by a 2016 paper describing YouTube's recommender, splits the job into two neural networks with very different jobs.

Candidate generation, also called retrieval, takes millions of items and returns a few hundred. It has to be extremely cheap per item, so it is allowed to be crude. Its only real duty is recall: do not lose the good stuff.

Ranking takes those few hundred and puts them in order. It runs a much heavier model with far more features, because it only has to score hundreds of things, not millions. Its duty is precision: get the top few exactly right.

candidate generationranking
input sizemillionshundreds
output sizehundredsdozens, ordered
cost per itemtinylarge
optimises forrecallprecision
featuresfew, coarsemany, fine

The stages are graded: cheap and approximate first, expensive and accurate last.

3. Retrieval as a geometry problem

The trick that makes retrieval cheap is to stop comparing and start measuring distance.

Represent every item as a point in a few hundred dimensions, an embedding. Represent the user as a point in the same space. Now define the whole problem as: find the points nearest to the user's point.

That reframing is the payoff. Similarity is no longer a model call per item, it is a vector operation. The learning problem becomes: place things so that the geometry is meaningful, so that items a user would enjoy end up physically close to that user's vector.

YouTube's paper framed candidate generation as extreme multiclass classification: given this user, predict which video out of millions they watch next. Training that directly needs a softmax over the entire corpus, which is far too expensive, so it is trained with negative sampling instead: compare the true item against a handful of random ones rather than against all of them.

4. The two-tower model

The structure that produces those embeddings is the two-tower model. Two separate networks that never touch until the very end.

# user tower: everything about the person
user_vec = user_tower([
    watch_history_embeddings,   # what they watched
    search_tokens,              # what they searched
    geo, device, age_of_account,
])                              # -> [0.21, -0.03, ...] 256 numbers

# item tower: everything about the thing
item_vec = item_tower([
    content_embedding,          # what it IS (audio, visual, text)
    creator_id, language, topic, age_of_item,
])                              # -> [0.19, -0.01, ...] same 256 dims

score = dot(user_vec, item_vec)   # closeness = predicted affinity

The towers are trained together so that a dot product between them predicts engagement. But they run apart, and that separation is the entire point of the design.

5. Why the towers must be separate

Here is the constraint that forces the architecture. A model that takes user and item together is more accurate, because it can learn interactions between them. But it must be run once per pair. Millions of items means millions of forward passes per request. Impossible.

Because the two towers never see each other, an item's vector does not depend on who is asking. So:

  1. Compute every item's vector offline, in advance, once.
  2. Load them into an index.
  3. At request time run the user tower once, then look up neighbours.

That is millions of forward passes traded for one. The cost of a request stops scaling with the size of the corpus.

The price is real: the towers cannot model user-item interactions, only their positions. That crudeness is acceptable precisely because retrieval is not the final word. Ranking, which does see both together, cleans it up afterwards. The funnel lets each stage be wrong in ways the next stage can fix.

6. Approximate nearest neighbour search

Even with precomputed vectors, comparing the user against a hundred million item vectors on every request is still too slow. Exact nearest neighbour search is linear in corpus size, and linear is exactly what we are trying to escape.

So the system does not find the nearest neighbours. It finds approximately the nearest neighbours, in roughly logarithmic time, using an ANN index.

The idea is to precompute structure over the vectors: partition the space into regions, or build a navigable graph across the points, so a query can walk to a good neighbourhood while touching only a tiny fraction of the corpus.

The tradeoff is explicit and tunable: search fewer regions and you get answers in microseconds while occasionally missing a genuinely close item. Search more and you get better recall for more compute.

That missed item is usually invisible. When you are picking 500 candidates out of 100 million to hand to a ranker, whether the true 400th-best made the cut changes nothing a user would notice.

7. The ranking stage

Now the budget is different. A few hundred candidates instead of a hundred million means roughly six orders of magnitude more compute available per item. The ranker spends it.

It takes user and item together, plus features retrieval could never afford: how many times this exact user saw this creator this week, time since they last watched something similar, current session length, item freshness, device, and dozens more. It can model interactions, because it sees both sides at once.

One detail from the YouTube paper matters more than the architecture. They did not train the ranker to predict clicks. They modified logistic regression to predict expected watch time.

The reason is a lesson about optimisation targets generally: optimise for click probability and the system learns that clickbait works, because a misleading thumbnail genuinely does maximise clicks. The model was not broken. It was succeeding at the objective it was given, and the objective was wrong.

8. The funnel is not the last word

The two-stage funnel has been standard for a decade and still runs most of what you use. It is now being challenged, and the challenge is already in production.

The reframing: stop treating recommendation as retrieve then score and treat it as sequence prediction. Given everything a user has done, generate what they do next, the way a language model generates a next token. Items are assigned semantic IDs, discrete codes derived from their content, so a transformer can emit the ID of the next item directly instead of scoring a candidate list.

Meta published the strongest evidence. Their HSTU architecture (Zhai et al., ICML 2024) recasts recommendation as sequential transduction, scales to 1.5 trillion parameters, and reported a 12.4 percent improvement in online A/B tests, deployed across multiple surfaces of a platform with billions of users. The code is open source.

Others go further: Kuaishou's OneRec (2025) collapses retrieval and ranking into a single generative model, attacking the two-stage split itself.

So learn the funnel, because it is what almost everything runs. Just do not mistake it for the end of the story.

9. The funnel, end to end

Item vectors are built offline and indexed. At request time the user tower runs once, nearest-neighbour search cuts millions to hundreds, and only then does the expensive ranker look at user and item together.

flowchart TD
  A["item tower: runs offline for every item"] --> B["ANN index of item vectors"]
  C["request arrives"] --> D["user tower: runs once"]
  D --> E["user vector"]
  E --> F["nearest neighbour lookup in the index"]
  B --> F
  F --> G["few hundred candidates"]
  G --> H["ranker: sees user and item together, many features"]
  H --> I["predicted watch time per candidate"]
  I --> J["ordered feed, about ten items"]

Check your understanding

The lesson ends with a 5-question quiz. Take it in the player above to see your score.

  1. Why do production recommenders use a funnel instead of scoring every item with one good model?
    • Because a single model cannot represent users and items at once
    • Because scoring millions of items per request would take far longer than the latency budget allows
    • Because neural networks cannot output more than ten results
    • Because item embeddings expire every few seconds
  2. What does keeping the user tower and item tower separate actually buy you?
    • Item vectors can be computed offline in advance, so a request costs one forward pass instead of millions
    • It lets the model learn user-item interactions more accurately
    • It removes the need for an ANN index
    • It guarantees exact nearest neighbours
  3. The retrieval stage optimises for recall rather than precision. Why is that acceptable?
    • Because recall and precision are the same at large scale
    • Because users never look past the first item
    • Because the ranking stage sees user and item together and reorders the survivors properly
    • Because ANN search is exact, so nothing is lost
  4. Approximate nearest neighbour search sometimes misses a genuinely close item. Why is that tolerable?
    • The miss is logged and retried on the next request
    • ANN only misses items the user has already seen
    • Ranking re-scans the full corpus anyway
    • When picking ~500 candidates from 100 million, missing the true 400th-best changes nothing a user notices
  5. YouTube's ranker predicted expected watch time rather than click probability. What problem was that solving?
    • Watch time is cheaper to compute than clicks
    • Optimising for clicks rewards clickbait, because misleading thumbnails genuinely do maximise clicks
    • Click data was unavailable at their scale
    • Watch time removes the need for a candidate generation stage

Related lessons