phoenix/
A two-stage Grok-style recsys.
Retrieval narrows millions of posts to thousands. Ranking scores those thousands with a transformer that is not allowed to let candidates look at each other.
Retrieval
Two-tower. Your history embedding against a semantic-ID index.
millions → thousands
Ranking
Transformer with candidate isolation. Score(A) does not depend on B.
thousands → ordered
Retrieval — two towers
No learned user-ID embedding. You are what you interacted with. Candidates are semantic IDs — 6 × 256 residual-quantized codes from multimodal embeddings — plus hashed author IDs.
History transformer
- Engagement sequence
- Profile token · no user ID
- Normalized embedding [B, D]
Semantic-ID index
- 6 × 256 residual codes
- Hashed author IDs
- Stored in the checkpoint
Ranking — candidate isolation
The ranker sees user tokens, history, and candidates. History can attend normally. Candidates may attend to the user and the history — never to other candidates. The score for post A does not depend on whether post B is in the batch, so scores are cacheable.
Rows attend to columns. Filled cells are allowed. Empty cells between candidates are the isolation rule.
| Query ↓ / Key → | User | History | A | B | C |
|---|---|---|---|---|---|
| User | allowed | allowed | blocked | blocked | blocked |
| History | allowed | allowed | blocked | blocked | blocked |
| Cand A | allowed | allowed | allowed | blocked | blocked |
| Cand B | allowed | allowed | blocked | allowed | blocked |
| Cand C | allowed | allowed | blocked | blocked | allowed |
What the ranker actually outputs
Logits for each action (like, reply, report, dwell, …) plus a dwell regression. Combining those into one number is Home Mixer's job, not the model's. Hash embeddings mean a brand-new post is representable immediately — there is no vocabulary to rebuild.