<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Instagram on Vivek's Field Notes</title><link>https://heyyviv.github.io/tags/instagram/</link><description>Recent content in Instagram on Vivek's Field Notes</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Thu, 22 Oct 2026 20:00:00 +0530</lastBuildDate><atom:link href="https://heyyviv.github.io/tags/instagram/index.xml" rel="self" type="application/rss+xml"/><item><title>What Reel You Will Get Next</title><link>https://heyyviv.github.io/blog/what-reel-you-will-get-next/</link><pubDate>Thu, 22 Oct 2026 20:00:00 +0530</pubDate><guid>https://heyyviv.github.io/blog/what-reel-you-will-get-next/</guid><description>&lt;style>
.sd { --ink:#2D3748; --muted:#718096; --line:#CBD5E0; --card:#FFFFFF; --s1:#2a78d6; --s2:#eb6834; margin:1.6em 0; }
body.dark-mode .sd { --ink:#E2E8F0; --muted:#A0AEC0; --line:#4A5568; --card:#2D3748; --s1:#3987e5; --s2:#d95926; }
.sd svg { width:100%; height:auto; display:block; font-family:var(--font-primary, sans-serif); }
.sd .cap { font-size:.85em; color:var(--muted); text-align:center; margin-top:.4em; }
.sd .box { fill:var(--card); stroke:var(--line); stroke-width:1.5; }
.sd .lbl { fill:var(--ink); font-size:13px; font-weight:600; }
.sd .sub { fill:var(--muted); font-size:11px; }
.sd .wire { stroke:var(--line); stroke-width:2; fill:none; }
.sd .dot1 { fill:var(--s1); } .sd .dot2 { fill:var(--s2); }
/* funnel */
.funnel .row { display:flex; align-items:center; gap:.8em; margin:.35em 0; }
.funnel .bar { height:34px; border-radius:6px; background:var(--s1); transform-origin:left; animation:grow 1.2s ease-out both; }
.funnel .row:nth-child(2) .bar { animation-delay:.4s; opacity:.8; }
.funnel .row:nth-child(3) .bar { animation-delay:.8s; opacity:.65; }
.funnel .row:nth-child(4) .bar { animation-delay:1.2s; background:var(--s2); }
.funnel .txt { font-size:.9em; color:var(--ink); white-space:nowrap; }
.funnel .txt small { color:var(--muted); }
@keyframes grow { from { transform:scaleX(0); } to { transform:scaleX(1); } }
/* panda chart */
.sd rect.bar { transform-box:fill-box; transform-origin:bottom; animation:rise .8s ease-out both; }
.sd .bar.s1 { fill:var(--s1); } .sd .bar.s2 { fill:var(--s2); }
.sd rect.bar:hover { opacity:.75; }
.sd .grid { stroke:var(--line); stroke-width:1; }
.sd .tick { fill:var(--muted); font-size:11px; }
.sd .event { stroke:var(--ink); stroke-width:1.5; stroke-dasharray:4 4; }
@keyframes rise { from { transform:scaleY(0); } to { transform:scaleY(1); } }
.legend { display:flex; gap:1.2em; justify-content:center; flex-wrap:wrap; font-size:.85em; color:var(--ink); }
.legend i { display:inline-block; width:12px; height:12px; border-radius:3px; margin-right:.4em; vertical-align:-1px; }
/* wukong stack */
.wk { display:flex; flex-direction:column; gap:8px; max-width:480px; margin:1.6em auto .4em; }
.wk div { border:1.5px solid var(--line); border-radius:8px; padding:8px 12px; background:var(--card); color:var(--ink); font-size:.9em; display:flex; justify-content:space-between; gap:1em; animation:pulse 4s infinite; }
.wk div span { color:var(--muted); text-align:right; }
.wk div:nth-child(2) { animation-delay:.6s; } .wk div:nth-child(3) { animation-delay:1.2s; }
.wk div:nth-child(4) { animation-delay:1.8s; } .wk div:nth-child(5) { animation-delay:2.4s; }
@keyframes pulse { 0%,30%,100% { border-color:var(--line); } 10%,20% { border-color:var(--s1); box-shadow:0 0 0 3px color-mix(in srgb, var(--s1) 25%, transparent); } }
@media (prefers-reduced-motion: reduce) {
 .sd *, .funnel * { animation:none !important; }
 .sd .dot1, .sd .dot2 { display:none; }
}
&lt;/style>
&lt;p>We watched what &lt;strong>instagram.com&lt;/strong> sends while you scroll Reels. Then we liked one panda video. The next batch was full of animals, and pandas kept landing in the top slot.&lt;/p>
&lt;p>How does a system learn that in about 80 seconds? Let&amp;rsquo;s design it from scratch, using three things:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>What we observed&lt;/strong> in the browser (our experiments, desktop web, one account)&lt;/li>
&lt;li>&lt;strong>Monolith&lt;/strong>: ByteDance&amp;rsquo;s paper on real-time training&lt;/li>
&lt;li>&lt;strong>Wukong&lt;/strong>: Meta&amp;rsquo;s paper on scaling ranking models&lt;/li>
&lt;/ol>
&lt;blockquote>
&lt;p>&lt;strong>Ground rule.&lt;/strong> Instagram&amp;rsquo;s ranking runs on Meta&amp;rsquo;s servers, so we can&amp;rsquo;t see it. Every claim here is labelled &lt;strong>observed&lt;/strong> (we saw it in network traffic), &lt;strong>Meta says&lt;/strong> (public docs) or &lt;strong>paper&lt;/strong> (Monolith or Wukong). Monolith is ByteDance&amp;rsquo;s system, not Instagram&amp;rsquo;s, and Meta has never said Reels runs Wukong. We use both as reference designs.&lt;/p>
&lt;/blockquote>
&lt;h2 id="1-what-the-client-actually-does-observed">1. What the client actually does (observed)&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>What&lt;/th>
&lt;th>What we saw&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Feed delivery&lt;/td>
&lt;td>One GraphQL call returns &lt;strong>~14 reels&lt;/strong>. No request per swipe&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Preloading&lt;/td>
&lt;td>Videos for upcoming reels start downloading before you reach them&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Event log&lt;/td>
&lt;td>&lt;code>logging_client_events&lt;/code>, batched &lt;strong>every ~10 s&lt;/strong>, ~240 events in 4 min&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Per-reel signal&lt;/td>
&lt;td>&lt;code>video_exited&lt;/code> carries the exact second you left, the reel length and the loop count&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>The join key&lt;/td>
&lt;td>Each reel arrives with a &lt;code>tracking_token&lt;/code>. Every event about that reel sends it back (&lt;strong>73/73 matched&lt;/strong>)&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Next-page request&lt;/td>
&lt;td>Sends only the IDs of &lt;code>seen_reels&lt;/code>. Watch time travels separately in the event log&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Third parties&lt;/td>
&lt;td>None. Everything went to Instagram/Meta domains&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>So the whole system is a loop:&lt;/p>
&lt;div class="sd">
&lt;svg viewBox="0 0 700 230" role="img" aria-label="Feedback loop: you scroll, events are logged, joined, trained on and ranked, and the next page comes back to you">
&lt;path id="fwd" class="wire" d="M130,67 L570,67"/>
&lt;path id="back" class="wire" d="M630,94 C630,215 70,215 70,94"/>
&lt;circle class="dot1" r="6">&lt;animateMotion dur="3s" repeatCount="indefinite">&lt;mpath href="#fwd"/>&lt;/animateMotion>&lt;/circle>
&lt;circle class="dot1" r="6">&lt;animateMotion dur="3s" begin="1s" repeatCount="indefinite">&lt;mpath href="#fwd"/>&lt;/animateMotion>&lt;/circle>
&lt;circle class="dot1" r="6">&lt;animateMotion dur="3s" begin="2s" repeatCount="indefinite">&lt;mpath href="#fwd"/>&lt;/animateMotion>&lt;/circle>
&lt;circle class="dot2" r="7">&lt;animateMotion dur="4s" repeatCount="indefinite">&lt;mpath href="#back"/>&lt;/animateMotion>&lt;/circle>
&lt;rect class="box" x="10" y="40" width="120" height="54" rx="10"/>&lt;text class="lbl" x="70" y="64" text-anchor="middle">You scroll&lt;/text>&lt;text class="sub" x="70" y="80" text-anchor="middle">watch, skip, loop&lt;/text>
&lt;rect class="box" x="150" y="40" width="120" height="54" rx="10"/>&lt;text class="lbl" x="210" y="64" text-anchor="middle">Event log&lt;/text>&lt;text class="sub" x="210" y="80" text-anchor="middle">every ~10 s&lt;/text>
&lt;rect class="box" x="290" y="40" width="120" height="54" rx="10"/>&lt;text class="lbl" x="350" y="64" text-anchor="middle">Joiner&lt;/text>&lt;text class="sub" x="350" y="80" text-anchor="middle">by tracking_token&lt;/text>
&lt;rect class="box" x="430" y="40" width="120" height="54" rx="10"/>&lt;text class="lbl" x="490" y="64" text-anchor="middle">Trainer&lt;/text>&lt;text class="sub" x="490" y="80" text-anchor="middle">online updates&lt;/text>
&lt;rect class="box" x="570" y="40" width="120" height="54" rx="10"/>&lt;text class="lbl" x="630" y="64" text-anchor="middle">Ranker&lt;/text>&lt;text class="sub" x="630" y="80" text-anchor="middle">scores candidates&lt;/text>
&lt;text class="sub" x="350" y="200" text-anchor="middle">next page: ~14 reels, each stamped with a tracking_token&lt;/text>
&lt;text class="sub" x="350" y="20" text-anchor="middle">blue = your signals going up · orange = the next batch coming down&lt;/text>
&lt;/svg>
&lt;div class="cap">The loop. "You scroll", "Event log" and the returning page are observed. Joiner, trainer and ranker live on Meta's servers: that part is our design, not something we saw.&lt;/div>
&lt;/div>
&lt;h2 id="2-requirements">2. Requirements&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Functional:&lt;/strong> given a user, return the next page of ~14 reels, ordered by how likely they are to engage.&lt;/li>
&lt;li>&lt;strong>Fresh:&lt;/strong> a new interest should show up within a page or two, not tomorrow.&lt;/li>
&lt;li>&lt;strong>Scale:&lt;/strong> billions of reels and users. New IDs appear and old ones die every minute.&lt;/li>
&lt;li>&lt;strong>Latency:&lt;/strong> a page must be ready before you hit the end of the current one. Preloading hides the rest.&lt;/li>
&lt;/ul>
&lt;h2 id="3-high-level-design-the-funnel-meta-says">3. High-level design: the funnel (Meta says)&lt;/h2>
&lt;p>Meta&amp;rsquo;s system card for &lt;strong>Reels Chaining&lt;/strong> lists four stages: gather inventory, use signals to pick about 100, predict, then rank by score. Every reel we captured carried &lt;code>inventory_source: &amp;quot;recommended_clips_chaining_model&amp;quot;&lt;/code>, which matches that name.&lt;/p>
&lt;div class="sd funnel">
&lt;div class="row">&lt;div class="bar" style="width:62%">&lt;/div>&lt;div class="txt">Billions of reels &lt;small>(Meta AI blog)&lt;/small>&lt;/div>&lt;/div>
&lt;div class="row">&lt;div class="bar" style="width:40%">&lt;/div>&lt;div class="txt">Thousands of candidates &lt;small>(retrieval)&lt;/small>&lt;/div>&lt;/div>
&lt;div class="row">&lt;div class="bar" style="width:20%">&lt;/div>&lt;div class="txt">~100 &lt;small>(light model + integrity checks)&lt;/small>&lt;/div>&lt;/div>
&lt;div class="row">&lt;div class="bar" style="width:8%">&lt;/div>&lt;div class="txt">~14 per page &lt;small>(observed)&lt;/small>&lt;/div>&lt;/div>
&lt;div class="cap">Cheap models shrink the list, so the expensive model only scores ~100 reels.&lt;/div>
&lt;/div>
&lt;p>Why a funnel? Running the big model on billions of reels for every request would be far too slow. Each stage is cheaper than the next and hands over a shorter list.&lt;/p>
&lt;p>The hard parts aren&amp;rsquo;t the boxes. They&amp;rsquo;re two properties of the data, both named in the Monolith paper.&lt;/p>
&lt;h2 id="4-problem-1-the-features-are-sparse-ids">4. Problem 1: the features are sparse IDs&lt;/h2>
&lt;p>Features are what the model looks at for one (you, reel) pair: your user ID, the reel ID, the creator ID, the audio ID, your city, the hour. Almost all of them are &lt;strong>IDs&lt;/strong>. Each ID becomes a learned vector (an &lt;em>embedding&lt;/em>) stored in a giant table.&lt;/p>
&lt;p>The usual shortcut is to hash IDs into a fixed-size table. Then two reels can land in the same slot and share one vector, and popular IDs make those collisions hurt more.&lt;/p>
&lt;p>&lt;strong>Monolith&amp;rsquo;s fix (paper):&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>A &lt;strong>collisionless hash table&lt;/strong> (cuckoo hashing). Every ID gets its own vector, and lookups are O(1).&lt;/li>
&lt;li>&lt;strong>Frequency filter:&lt;/strong> an ID only gets a slot after it appears a few times. Rare IDs would be undertrained anyway.&lt;/li>
&lt;li>&lt;strong>Expiry:&lt;/strong> IDs unused for a set period are evicted, so dead reels and inactive users stop eating memory.&lt;/li>
&lt;/ul>
&lt;h2 id="5-problem-2-interests-drift-by-the-minute-concept-drift">5. Problem 2: interests drift by the minute (concept drift)&lt;/h2>
&lt;p>The pattern in the data keeps changing. Cricket during the IPL. Diwali reels in October. Pandas, right after you like one. A model trained yesterday is already stale.&lt;/p>
&lt;p>&lt;strong>Monolith&amp;rsquo;s answer (paper): train online, sync often.&lt;/strong>&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-mermaid" data-lang="mermaid">flowchart LR
 U[User actions&amp;lt;br/&amp;gt;watch, like, share] --&amp;gt; LK[(Log Kafka)]
 MS[Model server] --&amp;gt;|features used&amp;lt;br/&amp;gt;at serve time| FK[(Feature Kafka)]
 LK --&amp;gt; J[Online joiner&amp;lt;br/&amp;gt;by request key]
 FK --&amp;gt; J
 J --&amp;gt; TK[(Training examples)]
 TK --&amp;gt; TW[Training workers]
 TW --&amp;gt; TPS[Training PS]
 TPS --&amp;gt;|sparse: every minute&amp;lt;br/&amp;gt;dense: daily| SPS[Serving PS]
 SPS --&amp;gt; MS
 TK --&amp;gt; H[(HDFS: batch training)]
&lt;/code>&lt;/pre>&lt;ul>
&lt;li>&lt;strong>Joiner.&lt;/strong> Features are logged when a reel is served. Actions arrive later, sometimes days later. Both carry a request key, so they can be paired. Recent ones wait in memory, older ones on disk. &lt;em>Our &lt;code>tracking_token&lt;/code> (73/73 matched) is exactly the kind of key such a joiner needs. That&amp;rsquo;s an inference: we can&amp;rsquo;t see Meta&amp;rsquo;s joiner.&lt;/em>&lt;/li>
&lt;li>&lt;strong>Negative sampling.&lt;/strong> Skips vastly outnumber likes, so negatives are sampled down. A log-odds correction at serving time undoes the bias.&lt;/li>
&lt;li>&lt;strong>Cheap sync.&lt;/strong> In a short window only a few IDs get touched, so only those embedding rows are pushed, every minute. The paper&amp;rsquo;s example: 100,000 IDs × 1024 dims ≈ 400 MB per minute. Dense weights move slowly, so they sync daily.&lt;/li>
&lt;li>&lt;strong>Reliability trade.&lt;/strong> Snapshots happen once a day. Losing up to a day of updates hurt surprisingly little.&lt;/li>
&lt;li>&lt;strong>Payoff (paper).&lt;/strong> A 30-minute sync beat 1-hour and 5-hour syncs. In a live A/B test on an ads model, online training beat batch training on AUC every day for a week.&lt;/li>
&lt;/ul>
&lt;h3 id="does-instagram-behave-like-this-our-panda-run-observed">Does Instagram behave like this? Our panda run (observed)&lt;/h3>
&lt;div class="sd">
&lt;svg viewBox="0 0 640 250" role="img" aria-label="Bar chart: panda and animal reels per feed page. Zero before page 4, a burst of 9 animal reels on page 5, pandas on most pages after.">
&lt;line class="grid" x1="50" x2="620" y1="210" y2="210"/>&lt;line class="grid" x1="50" x2="620" y1="110" y2="110" stroke-dasharray="2 4"/>&lt;line class="grid" x1="50" x2="620" y1="10" y2="10" stroke-dasharray="2 4"/>
&lt;text class="tick" x="42" y="214" text-anchor="end">0&lt;/text>&lt;text class="tick" x="42" y="114" text-anchor="end">5&lt;/text>&lt;text class="tick" x="42" y="14" text-anchor="end">10&lt;/text>
&lt;line class="event" x1="310" x2="310" y1="10" y2="210"/>&lt;text class="sub" x="304" y="24" text-anchor="end">first like + share →&lt;/text>
&lt;text class="tick" x="76" y="228" text-anchor="middle">0&lt;/text>
&lt;text class="tick" x="128" y="228" text-anchor="middle">1&lt;/text>
&lt;text class="tick" x="180" y="228" text-anchor="middle">2&lt;/text>
&lt;text class="tick" x="232" y="228" text-anchor="middle">3&lt;/text>
&lt;rect class="bar s1" style="animation-delay:0.32s" x="267" y="190" width="16" height="20" rx="3">&lt;title>Page 4: 1 giant panda of 14 reels&lt;/title>&lt;/rect>
&lt;rect class="bar s2" style="animation-delay:0.32s" x="285" y="170" width="16" height="40" rx="3">&lt;title>Page 4: 2 animal reels of 14&lt;/title>&lt;/rect>
&lt;text class="tick" x="284" y="228" text-anchor="middle">4&lt;/text>
&lt;rect class="bar s2" style="animation-delay:0.40s" x="337" y="30" width="16" height="180" rx="3">&lt;title>Page 5: 9 animal reels of 15 (no giant panda)&lt;/title>&lt;/rect>
&lt;text class="tick" x="336" y="228" text-anchor="middle">5&lt;/text>
&lt;rect class="bar s1" style="animation-delay:0.48s" x="371" y="190" width="16" height="20" rx="3">&lt;title>Page 6: 1 giant panda of 14 reels, slot #0&lt;/title>&lt;/rect>
&lt;rect class="bar s2" style="animation-delay:0.48s" x="389" y="170" width="16" height="40" rx="3">&lt;title>Page 6: 2 animal reels of 14&lt;/title>&lt;/rect>
&lt;text class="tick" x="388" y="228" text-anchor="middle">6&lt;/text>
&lt;rect class="bar s1" style="animation-delay:0.56s" x="423" y="170" width="16" height="40" rx="3">&lt;title>Page 7: 2 giant pandas of 13 reels, slots #0 and #7&lt;/title>&lt;/rect>
&lt;rect class="bar s2" style="animation-delay:0.56s" x="441" y="110" width="16" height="100" rx="3">&lt;title>Page 7: 5 animal reels of 13&lt;/title>&lt;/rect>
&lt;text class="tick" x="440" y="228" text-anchor="middle">7&lt;/text>
&lt;rect class="bar s1" style="animation-delay:0.64s" x="475" y="190" width="16" height="20" rx="3">&lt;title>Page 8: 1 giant panda of 14 reels, slot #0&lt;/title>&lt;/rect>
&lt;rect class="bar s2" style="animation-delay:0.64s" x="493" y="110" width="16" height="100" rx="3">&lt;title>Page 8: 5 animal reels of 14&lt;/title>&lt;/rect>
&lt;text class="tick" x="492" y="228" text-anchor="middle">8&lt;/text>
&lt;rect class="bar s1" style="animation-delay:0.72s" x="527" y="190" width="16" height="20" rx="3">&lt;title>Page 9: 1 giant panda of 14 reels&lt;/title>&lt;/rect>
&lt;rect class="bar s2" style="animation-delay:0.72s" x="545" y="130" width="16" height="80" rx="3">&lt;title>Page 9: 4 animal reels of 14&lt;/title>&lt;/rect>
&lt;text class="tick" x="544" y="228" text-anchor="middle">9&lt;/text>
&lt;rect class="bar s1" style="animation-delay:0.80s" x="579" y="190" width="16" height="20" rx="3">&lt;title>Page 10: 1 giant panda of 13 reels&lt;/title>&lt;/rect>
&lt;rect class="bar s2" style="animation-delay:0.80s" x="597" y="170" width="16" height="40" rx="3">&lt;title>Page 10: 2 animal reels of 13&lt;/title>&lt;/rect>
&lt;text class="tick" x="596" y="228" text-anchor="middle">10&lt;/text>
&lt;text class="sub" x="335" y="246" text-anchor="middle">feed page (~14 reels each)&lt;/text>
&lt;/svg>
&lt;div class="legend">&lt;span>&lt;i style="background:var(--s1)">&lt;/i>Giant panda reels&lt;/span>&lt;span>&lt;i style="background:var(--s2)">&lt;/i>All animal reels (incl. pandas)&lt;/span>&lt;/div>
&lt;div class="cap">One account, desktop web, 13 minutes. Topics judged from captions, so counts are a lower bound. Hover a bar for details.&lt;/div>
&lt;/div>
&lt;details>&lt;summary>Table view&lt;/summary>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Page&lt;/th>
&lt;th>0&lt;/th>
&lt;th>1&lt;/th>
&lt;th>2&lt;/th>
&lt;th>3&lt;/th>
&lt;th>4&lt;/th>
&lt;th>5&lt;/th>
&lt;th>6&lt;/th>
&lt;th>7&lt;/th>
&lt;th>8&lt;/th>
&lt;th>9&lt;/th>
&lt;th>10&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Giant pandas&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>0&lt;/td>
&lt;td>1&lt;/td>
&lt;td>2&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;td>1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>All animals&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>0&lt;/td>
&lt;td>2&lt;/td>
&lt;td>9&lt;/td>
&lt;td>2&lt;/td>
&lt;td>5&lt;/td>
&lt;td>5&lt;/td>
&lt;td>4&lt;/td>
&lt;td>2&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Reels in page&lt;/td>
&lt;td>14&lt;/td>
&lt;td>13&lt;/td>
&lt;td>14&lt;/td>
&lt;td>14&lt;/td>
&lt;td>14&lt;/td>
&lt;td>15&lt;/td>
&lt;td>14&lt;/td>
&lt;td>13&lt;/td>
&lt;td>14&lt;/td>
&lt;td>14&lt;/td>
&lt;td>13&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/details>
&lt;ul>
&lt;li>&lt;strong>0 pandas in 55 reels&lt;/strong> before. &lt;strong>6 in 83&lt;/strong> after, three times in &lt;strong>slot #0&lt;/strong>.&lt;/li>
&lt;li>The very next page (~80 s after the first like) had &lt;strong>9/15 animal reels&lt;/strong>: a broad &amp;ldquo;cute animals&amp;rdquo; burst first, then pandas specifically.&lt;/li>
&lt;li>Pandas came back with Korean and Chinese captions and no shared keyword. That fits embedding similarity better than keyword matching.&lt;/li>
&lt;li>~14 hours later, with no more panda engagement, pandas had nearly vanished (1/41, last slot).&lt;/li>
&lt;/ul>
&lt;p>This is what a real-time system looks like from the outside. We can&amp;rsquo;t tell &lt;em>which&lt;/em> mechanism did it: online training, a real-time retrieval source or short-term user features. Meta&amp;rsquo;s Explore post mentions real-time sources and hourly retraining. It&amp;rsquo;s one account with no control run, so treat it as a strong hint, not proof.&lt;/p>
&lt;h2 id="6-the-ranker-wukong-paper-meta">6. The ranker: Wukong (paper, Meta)&lt;/h2>
&lt;p>Now the expensive stage: scoring ~100 reels. The model&amp;rsquo;s real job is &lt;strong>feature interactions&lt;/strong>. &amp;ldquo;Likes pandas&amp;rdquo; alone is weak. &amp;ldquo;Likes pandas × liked something 2 minutes ago × reel is from a new creator&amp;rdquo; is strong.&lt;/p>
&lt;p>Older recommenders grew mostly by making &lt;strong>embedding tables&lt;/strong> bigger. That adds memory but little compute, and quality plateaus. Wukong grows the &lt;strong>dense interaction part&lt;/strong> instead, the way LLMs scale.&lt;/p>
&lt;div class="sd wk">
&lt;div>&lt;b>Embeddings&lt;/b>&lt;span>user, reel, creator, audio… (720 features)&lt;/span>&lt;/div>
&lt;div>&lt;b>Wukong layer 1&lt;/b>&lt;span>FM block ∥ linear block → order 1–2&lt;/span>&lt;/div>
&lt;div>&lt;b>Wukong layer 2&lt;/b>&lt;span>order 1–4&lt;/span>&lt;/div>
&lt;div>&lt;b>Wukong layer 3&lt;/b>&lt;span>order 1–8&lt;/span>&lt;/div>
&lt;div>&lt;b>MLP head&lt;/b>&lt;span>P(click), P(like / follow)&lt;/span>&lt;/div>
&lt;/div>
&lt;div class="sd">&lt;div class="cap">Each layer doubles the interaction order. Stack L layers and you capture up to 2&lt;sup>L&lt;/sup>-way interactions.&lt;/div>&lt;/div>
&lt;ul>
&lt;li>&lt;strong>FM block:&lt;/strong> a factorization machine takes dot products between embeddings, then an MLP. Interacting two order-k inputs gives order 2k.&lt;/li>
&lt;li>&lt;strong>Linear compress block:&lt;/strong> recombines embeddings without raising the order, so lower orders survive.&lt;/li>
&lt;li>Both outputs are concatenated, with a residual connection and LayerNorm: &lt;code>X(i+1) = LN(concat(FMB(Xi), LCB(Xi)) + Xi)&lt;/code>.&lt;/li>
&lt;li>&lt;strong>The result (paper):&lt;/strong> on Meta&amp;rsquo;s internal dataset (&lt;strong>146B examples, 720 features&lt;/strong>, embedding tables fixed at 627B parameters), quality kept improving across &lt;strong>two orders of magnitude of compute&lt;/strong>, past 100 GFLOP per example. That&amp;rsquo;s roughly &lt;strong>0.1% better LogLoss per 4× compute&lt;/strong>. The best baseline needed &lt;strong>40×&lt;/strong> more compute to match it.&lt;/li>
&lt;/ul>
&lt;p>How this maps to Reels: the system card says Reels predicts things like reshares, watching to the end, likes and audio-page visits, then combines them into one score. A multi-task ranker like Wukong is a natural fit for that stage. That&amp;rsquo;s our design choice, not a confirmed fact.&lt;/p>
&lt;h2 id="7-the-full-design">7. The full design&lt;/h2>
&lt;pre tabindex="0">&lt;code class="language-mermaid" data-lang="mermaid">flowchart TB
 C[Client&amp;lt;br/&amp;gt;~14 reels/page + preload] --&amp;gt;|seen_reels IDs| API[Feed API]
 C --&amp;gt;|events every ~10 s&amp;lt;br/&amp;gt;+ tracking_token| LOG[(Event stream)]
 API --&amp;gt; R[Retrieval&amp;lt;br/&amp;gt;embeddings: billions to thousands]
 R --&amp;gt; P[Light ranker + integrity&amp;lt;br/&amp;gt;to ~100]
 P --&amp;gt; W[Heavy ranker&amp;lt;br/&amp;gt;Wukong-style multi-task]
 W --&amp;gt; RR[Re-rank&amp;lt;br/&amp;gt;diversity, policy, exploration]
 RR --&amp;gt;|page + tracking_token per reel| C
 LOG --&amp;gt; J[Online joiner]
 API -.-&amp;gt;|serve-time features| J
 J --&amp;gt; T[Online trainer&amp;lt;br/&amp;gt;Monolith-style]
 T --&amp;gt;|sparse rows every minute| W
 T --&amp;gt;|sparse rows every minute| R
&lt;/code>&lt;/pre>&lt;p>&lt;strong>Takeaways&lt;/strong>&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Batch the feed, stream the signals.&lt;/strong> The client pulls ~14 reels at a time but reports behaviour every ~10 s. The next page is the earliest point ranking can react.&lt;/li>
&lt;li>&lt;strong>A join key makes learning possible.&lt;/strong> Every served item carries a token, and every event echoes it back.&lt;/li>
&lt;li>&lt;strong>Freshness is a sync problem.&lt;/strong> Push only the embedding rows that changed, every minute. Ship the dense weights daily.&lt;/li>
&lt;li>&lt;strong>Scale the interactions, not just the tables.&lt;/strong> Stacked FM layers give the ranker a scaling law.&lt;/li>
&lt;/ol>
&lt;h2 id="sources">Sources&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>Observed:&lt;/strong> our captures of instagram.com Reels, Oct 20–21 2026 (desktop web, one account, secret values masked).&lt;/li>
&lt;li>Meta Transparency Center, &lt;a href="https://transparency.meta.com/features/explaining-ranking/ig-reels-chaining/">Instagram Reels Chaining AI system&lt;/a>.&lt;/li>
&lt;li>Meta AI, &lt;a href="https://ai.meta.com/blog/ai-unconnected-content-recommendations-facebook-instagram/">The AI behind unconnected content recommendations on Facebook and Instagram&lt;/a> (2023).&lt;/li>
&lt;li>Meta Engineering, &lt;a href="https://engineering.fb.com/2023/08/09/ml-applications/scaling-instagram-explore-recommendations-system/">Scaling the Instagram Explore recommendations system&lt;/a> (2023). This one is about Explore, not Reels.&lt;/li>
&lt;li>Liu et al., &lt;a href="https://arxiv.org/abs/2209.07663">Monolith: Real Time Recommendation System With Collisionless Embedding Table&lt;/a>, ByteDance, 2022.&lt;/li>
&lt;li>Zhang et al., &lt;a href="https://arxiv.org/abs/2403.02545">Wukong: Towards a Scaling Law for Large-Scale Recommendation&lt;/a>, Meta, ICML 2024.&lt;/li>
&lt;/ul></description></item></channel></rss>