<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Mandeep Singh on Medium]]></title>
        <description><![CDATA[Stories by Mandeep Singh on Medium]]></description>
        <link>https://medium.com/@mandeep0405?source=rss-e104ac293dae------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*u-Xx_oQhQ-6174ataDYd6w@2x.jpeg</url>
            <title>Stories by Mandeep Singh on Medium</title>
            <link>https://medium.com/@mandeep0405?source=rss-e104ac293dae------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 14 Nov 2026 12:23:50 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@mandeep0405/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[May I Have Your Attention?]]></title>
            <link>https://medium.com/@mandeep0405/may-i-have-your-attention-c6fdb1b5bc45?source=rss-e104ac293dae------2</link>
            <guid isPermaLink="false">https://medium.com/p/c6fdb1b5bc45</guid>
            <category><![CDATA[chips]]></category>
            <category><![CDATA[gpu]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[generative-ai-tools]]></category>
            <category><![CDATA[attention]]></category>
            <dc:creator><![CDATA[Mandeep Singh]]></dc:creator>
            <pubDate>Mon, 10 Aug 2026 00:21:46 GMT</pubDate>
            <atom:updated>2026-08-10T00:21:46.936Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Q8I34pj9Ttn9GBA_V_IeXg.png" /><figcaption>Chips Highway</figcaption></figure><p>Standard attention spends most of its life stuck in memory traffic on “the slow road” between lightning-fast SRAM (19 TB/s) and off-chip HBM (1.5 TB/s). In this blog, we will discuss if we can avoid taking this slow road!!</p><h3>Introduction</h3><p>Standard attention [4] scales quadratically with sequence length because of memory bandwidth, not compute capability. In standard attention, output O is calculated as:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/382/1*rcCMrkgODT-b5MxJMm33pw.png" /></figure><p>The intermediate matrix S = QKᵀ has N rows and N columns. At a 32k-token context, S holds about 1 billion numbers. That is per head, per layer.</p><p>Because S cannot fit into fast on-chip SRAM (~20 MB), standard execution keeps S in High-Bandwidth Memory (HBM) (~40 GB) and runs three separate sequential kernels:</p><p>1) Compute S = QKᵀ and write S to HBM.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*P9c16WhG95tVgXwyjRSdZQ.png" /></figure><p>2) Read S from HBM, compute P = softmax(S) in SRAM, and write P to HBM</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vb7GZLQutZHGqqHoJ83aUQ.png" /></figure><p>3) Read P from HBM, compute O = PV in SRAM, and write O to HBM.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Wtca_-fpk9x_v7Lba4XMUQ.png" /></figure><p>Every number in S and P crosses the HBM bus back and forth 4 times. Tensor cores spend most of their cycles waiting for memory transfers. The workload is strictly memory-bandwidth bound.</p><h3>FlashAttention 1 — Stop Moving the Big Matrix</h3><p>FlashAttention-1 [1] restructures attention to compute exact softmax without ever writing the N*N matrix i.e., S to HBM.</p><h4>Idea 1: Block-Wise Tiling</h4><p>Instead of computing the entire matrix S at once, load blocks of Q, K, and V into SRAM, compute a local tile of S on-chip, update the running output, and discard the tile. The full S matrix is never stored anywhere in global memory.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RCgprS0B5Pl9Pr9jbnJpvQ.png" /></figure><h4>Idea 2: Online Softmax</h4><p>Softmax requires normalizing every score by the sum of its entire row:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/306/1*RQwpMgNmH0D-TEvKg_S0-Q.png" /></figure><p>To evaluate softmax block-by-block without seeing the full row upfront, FlashAttention tracks two running statistics per row: the maximum value m and the running sum ℓ.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/748/1*1-6Zan4Qh8Z9d4RpzNgCcg.png" /></figure><p>Take the row of scores [2, 1, 3, 5], processed in two blocks of two.</p><blockquote><strong>Block 1 [2, 1]:</strong></blockquote><blockquote>m=2</blockquote><blockquote>ℓ = e^(2−2) + e^(1−2) = 1 + 0.368 = 1.368</blockquote><blockquote><strong>Block 2 [3, 5]:</strong> Max increases to m = 5.</blockquote><blockquote>correction = e^(2−5) = 0.0498</blockquote><blockquote>ℓ = 1.368 × 0.0498 = 0.068 (old sum, corrected)</blockquote><blockquote>ℓ = 0.068 + e^(3−5) + e^(5−5) = 1.203</blockquote><p>This matches if we have taken the full row (e^{x_i — m}): e⁻³+e⁻⁴+e⁻²+e⁰ = 1.203.</p><p>FlashAttention-1’s outer loop iterates over K/V blocks, with the inner loop sweeping across Q blocks.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TN-_H5C2Wbfq6LjYjZRyhQ.png" /></figure><p>SRAM can only hold state for one row at a time. Because the inner loop changes Q rows on every step, the running row state (O, m, l) must be read from HBM, updated, and written back to HBM for every step in the grid.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qf6G3AOb3W_Fv2AaJVNjqg.png" /></figure><p>This is repeated for all the Q blocks.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KAq4L5NnsaqxprxooTptfg.png" /></figure><h3>FlashAttention 2 — Stop Leaving Workers Idle</h3><p>FlashAttention-2 [2] retains the exact math and online softmax logic of FlashAttention-1, but redesigns the execution schedule and GPU thread mapping to achieve a 2x speedup. It simply changes the schedule: which worker does which piece, and in which order.</p><h4>Change 1: Loop Swapping (Q-Outer Loop)</h4><p>FA2 flips the loop order: the outer loop iterates over Q blocks, and the inner loop streams through K/V blocks. A worker takes a single Q block and runs it across all K/V blocks to completion. The running row state (O, m, l) stays pinned inside GPU registers for the entire inner loop and is written to HBM exactly once.</p><p><strong>FA-1 Row-wise: </strong>FlashAttention 1 walks down the columns. Load K₁V₁, apply it to row 1, then row 2, then 3, then 4. Every step lands on a new row. So every step swaps a row state through HBM.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1Y1FsBKpOgsVGT77EC3VaQ.png" /></figure><p><strong>FA-2 Column-wise: </strong>FlashAttention 2 walks across the rows. Take row 1 and run it against K₁, K₂, K₃, K₄ to completion. Then move to row 2.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*i77fjj2IQTIn3IaRi15l3w.png" /></figure><h4>Change 2: Warp Partitioning (Split Q, Not K)</h4><p>A single GPU SM splits its workload among 4 warps (groups of 32 threads executing in lockstep).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fI9f0CLUCDt1IJrGU3xxyg.png" /></figure><ul><li><strong>FlashAttention-1 (Split K):</strong> Each warp gets a slice of keys (K). Because every warp holds partial outputs for every row, no single warp owns the complete output. Every block requires writing partial outputs to shared memory, waiting at a warp barrier, and executing a merge pass to combine partial sums.</li><li><strong>FlashAttention-2 (Split Q):</strong> Each warp gets a dedicated subset of Q rows and reads the entire K/V block. Warp 1 fully owns its assigned rows from start to finish.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7velkC1gRzj6mmqRO957aQ.png" /></figure><h3>PagedAttention — Stop Reserving Memory You Will Not Use</h3><p>A model generates one token at a time. Each new token attends to every earlier token’s keys and values. Recomputing them each step is wasteful, so they are cached. The cache grows by one entry per token and shrinks only when the request ends. So, while FlashAttention speeds up execution kernels during training and prompt processing, model serving introduces a distinct memory bottleneck: the Key-Value (KV) cache during generation.</p><p>At a 2048-token context, one request consumes 1.6 GB of memory solely for its KV cache.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KUj8whyRit5cXIBYu90_3Q.png" /></figure><h3>The Virtual Memory Solution</h3><p>Traditional serving systems allocated continuous slabs of HBM upfront for a request’s maximum possible sequence length (e.g., 2048 tokens). This resulted in 60–80% memory fragmentation and waste from unfilled context reservations, blocking concurrent requests.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*il6lfJmpmCElNJsa62Oz4g.png" /></figure><p>PagedAttention[3] adapts virtual memory OS principles to the KV cache:</p><ol><li>Divide the KV cache into fixed-size physical blocks (e.g., 16 tokens).</li><li>Store physical blocks non-contiguously in an HBM memory pool.</li><li>Maintain a dynamic <strong>block table</strong> which maps logical sequence blocks to physical pool indices.</li></ol><p>When a request generates new tokens that fill a block, the runtime dynamically allocates a new physical block from the pool and appends it to the block table.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Kt8X9c6jhHsdVS2CBXQs9g.png" /></figure><h3>Conclusion</h3><ul><li><strong>FlashAttention-1</strong> eliminated standard attention’s O(N²) HBM memory bandwidth bottleneck using block-wise tiling and online softmax rescaling.</li><li><strong>FlashAttention-2</strong> restructured the execution loop order to pin row states in registers and split Q blocks across warps, removing shared memory sync overhead to improve GPU throughput limits.</li><li><strong>PagedAttention</strong> translated operating system virtual memory paging to the KV cache, eliminating memory fragmentation to maximize inference throughput and request batching.</li></ul><h3>References</h3><ol><li><a href="https://arxiv.org/abs/2205.14135">Flash Attention — 1</a></li><li><a href="https://arxiv.org/abs/2307.08691">Flash Attention — 2</a></li><li><a href="https://arxiv.org/abs/2309.06180">Paged Attention</a></li><li><a href="https://arxiv.org/abs/1706.03762">Attention is all you need</a></li></ol><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c6fdb1b5bc45" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Architecture of Kimi Delta Attention]]></title>
            <link>https://medium.com/@mandeep0405/the-architecture-of-kimi-delta-attention-2d86ae88c690?source=rss-e104ac293dae------2</link>
            <guid isPermaLink="false">https://medium.com/p/2d86ae88c690</guid>
            <category><![CDATA[kimi-k3]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[transformers]]></category>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[attention]]></category>
            <dc:creator><![CDATA[Mandeep Singh]]></dc:creator>
            <pubDate>Sun, 26 Jul 2026 06:57:46 GMT</pubDate>
            <atom:updated>2026-07-26T06:57:46.274Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*k_tt1P4nQ9a-NE_UxkVjBA.png" /></figure><h3>1) Introduction</h3><p>Processing massive contexts (100k+ tokens) in Large Language Models requires a fundamental trade-off between memory footprint and recall accuracy. Standard Transformers maintain exact recall across long context windows by storing every token’s key and value vectors in a growing KV cache. While effective, this creates an O(N) memory bottleneck during inference.</p><p>Linear attention architectures attempt to solve this by collapsing sequence history into a fixed-size memory matrix, but early designs suffered from crude uniform decay and “memory tie” bugs when trying to update stored facts.</p><p><strong>Kimi Delta Attention (KDA)[1] </strong>introduced in Moonshot AI’s Kimi K3 model architecture combines two mechanisms to make fixed-memory linear attention work in practice: <strong>per-channel diagonal decay gates</strong> and the <strong>Delta Rule overwrite mechanism</strong>.</p><h3>2) Problem: KV Cache Scaling vs. Naïve Memory Collapse</h3><p>In standard quadratic attention, every new token compares itself against every past token. To do this during decoding, the inference engine maintains a KV cache:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*O15VJHC5kq9A989IGuUUiQ.png" /></figure><ul><li><strong>At 1,000 tokens:</strong> Memory footprint is negligible.</li><li><strong>At 1,000,000 tokens:</strong> The KV cache demands hundreds of gigabytes of VRAM per batch, causing memory bandwidth bottlenecks that throttle decoding speed.</li></ul><h4>2.1) The Naïve Linear Attention Failure Mode</h4><p>Earlier linear attention architectures (and classic RNN states) try to maintain a fixed-size state matrix S. However, they update memory using standard additive accumulation:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/992/1*qsENPa5Irz_BjKcGE1SiAw.png" /></figure><p><strong>The Bug:</strong> If the model learns at token 10 that meeting-day = Tuesday, and at token 500 learns meeting-day = Thursday, naïve addition leaves both facts active in the state matrix:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2FN7QJ2lbcucTj85y1ZUuA.png" /></figure><p>Furthermore, applying a single scalar decay rate forces every feature (local syntax vs. long-term semantic facts) to fade at the exact same speed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1012/1*oyY9dr2JcEiafjsZPwl9mQ.png" /><figcaption>Fig: 1 Compares KV Cache growing in naive attention Vs KDA fixed state memory matrix</figcaption></figure><h3>3) How Kimi Delta Attention Works (Anatomy of KDA Write)?</h3><p>KDA replaces the unbounded KV cache with a fixed-size 128x128 state matrix per attention head. Let’s consider this example:</p><blockquote>Earlier: “The meeting is Tuesday. Sarah will present.”</blockquote><blockquote>Now: “…actually, the meeting moved to Thursday.”</blockquote><blockquote>At Step 5: When is the meeting?</blockquote><h4>Step 1: Memory Footprint — Static Footprint</h4><p>Instead of appending a new row to a cache array, KDA passes the incoming token through a constant-sized matrix S <em>(see fig 1 — right side). </em>Memory utilization at token 100 and token 1,000,000 is identical.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*flME2UqBkmaMbk5wn1BohA.png" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OdofRbmG_Ylwcd0gpbrrIg.png" /></figure><h4>Step 2: Read Under Key (v^old = S^T k)</h4><p>Before writing new information, KDA probes the current state to check what is already stored under the incoming token’s key slot. The model processes the word “Thursday” in the context of the meeting moving. It generates a key k to check what is currently stored for meeting-day.</p><blockquote>Key vector k = [1, 0, 0, 0]^T (selects row: meeting-day)</blockquote><blockquote>Reads row meeting-day -&gt; v_old = [1, 0, 0, 0] (“Tuesday — stale!”)</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1014/1*WYBpm8tmTkT3ex1BJmakrw.png" /></figure><h4>Step 3: Delta Write</h4><p>To update the record without causing value collision, KDA subtracts v_old before adding the new value v = [0, 1, 0, 0]^T (Thursday):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*l56eme4exO1mH74ENGb9NQ.png" /></figure><p>Applying the rank-1 outer product update k(ΔV)ᵀ:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ryf5FLNqDTvcEKrmXA3rEw.png" /></figure><p>The -1 (magenta) explicitly cancels out Tuesday, while +1 (blue) writes Thursday. All other key rows remain untouched.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1014/1*zyCJuALgJVjGVag_XA4UfA.png" /></figure><h4>Step 4: Per-Channel Forgetting (Diag(a).S)</h4><p>Rather than multiplying the entire matrix by one scalar decay factor, KDA computes a data-dependent gating vector a:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tJmlXUqFq4HwTmAC3g0rrQ.png" /></figure><ul><li><strong>Long-term semantic channels:</strong> a1 = 0.99 =&gt; meeting-day retains 99% of its state.</li><li><strong>Short-term syntactic channels:</strong> a3 = 0.60 =&gt; transient grammar representations shrink rapidly to clear space for future tokens.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1013/1*cOtJthnbRqR4_7-rQ2NkEg.png" /></figure><h4>Step 5: Read with Query (out = S^T q)</h4><p>Pages later in the sequence, the model queries the state matrix: <em>“So — when is the meeting?”</em></p><blockquote>Query q = [1, 0, 0, 0]^T (“When is the meeting?”)</blockquote><blockquote>out = S^T q -&gt; [0, 0.99, 0, 0] ≈ “Thursday”</blockquote><p>Because the Delta Rule subtracted the stale Tuesday state in Step 3, the output returns a unambiguous signal for Thursday instead of a 50/50 tie.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1012/1*4I-1GCOJY24UgHmIJeN_OQ.png" /></figure><h4>The Hybrid 3:1 Interleaving Attention Design</h4><p>To overcome pure linear attention’s weakness in exact long-context retrieval, the Kimi K3 architecture interleaves Kimi Delta Attention (KDA) layers with full Multi-Head Latent Attention (MLA) layers in a 3:1 ratio. This is necessary because a memory of 128x128 numbers cannot hold 1M tokens correctly. Because of this, the model cannot complete tasks that require the exact recall of old data. To solve this, the Multi-Head Latent Attention (MLA) layer keeps a full Key-Value cache for all positions. This full cache operates as a reliable backup for the KDA layers.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CkwetJNQChHzhCsHyewHJw.png" /></figure><h3>4) Conclusion</h3><p>By replacing standard KV caches with constant-sized state matrices, KDA enables architectures like Kimi K3 to maintain high generation speeds and low memory overhead over 1M-token context windows. The combination of <strong>per-channel diagonal decay</strong> (selective retention) and the <strong>Delta Rule</strong> (in-place memory overwriting) resolves the fundamental bottlenecks of classic linear attention.</p><p>By only keeping an full KV cache for 1 in every 4 layers, Kimi K3 achieves up to <strong>75% reduction</strong> in total KV cache memory footprint compared to all-attention architectures while maintaining near-identical performance on complex benchmarks</p><h3>References</h3><ol><li>Kimi Linear — <a href="https://arxiv.org/pdf/2510.26692">https://arxiv.org/pdf/2510.26692</a></li><li><a href="https://www.kimi.com/blog/kimi-k3">https://www.kimi.com/blog/kimi-k3</a></li></ol><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2d86ae88c690" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How DeepSeek V4 Makes Million-Token Context Actually Cheap?]]></title>
            <link>https://medium.com/@mandeep0405/how-deepseek-v4-makes-million-token-context-actually-cheap-2dc9426e25c3?source=rss-e104ac293dae------2</link>
            <guid isPermaLink="false">https://medium.com/p/2dc9426e25c3</guid>
            <category><![CDATA[open-source]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[deepseek]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[generative-ai-tools]]></category>
            <dc:creator><![CDATA[Mandeep Singh]]></dc:creator>
            <pubDate>Tue, 05 May 2026 17:24:18 GMT</pubDate>
            <atom:updated>2026-05-05T17:24:18.802Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jq6fyvNBI8KL9xApIvROzQ.png" /><figcaption>credit — banana pro</figcaption></figure><p>Every frontier model claims a million-token context window now. Most of them can’t actually use it. You paste a long document, the KV cache eats your GPU memory, inference slows to a crawl, and you’re paying for a context window that’s technically there but practically unusable. DeepSeek V4[1] takes this problem seriously at the architecture level and they are doing it open-source. At 1M tokens, V4-Pro uses 27% of the compute and 10% of the KV cache compared to V3.2. The Flash variant drops that to 10% and 7%. That’s not an incremental improvement. That’s a different cost structure!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/452/1*ceoD0n027r7AgSIB3Azp6Q.png" /><figcaption>DeepSeek V4 Model Architecture</figcaption></figure><h3>The trick: two types of attention, alternating</h3><p>V4 splits attention into two mechanisms and interleaves them across its 61 layers.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*04Yq1kTVeA8mq9MxxbYZQQ.png" /></figure><p>The first is <strong>Compressed Sparse Attention (CSA)</strong>. It groups every 4 tokens into one compressed entry (4x reduction), then runs a tiny FP4 scoring network to find the top 1,024 most relevant entries per query. Out of 250,000 compressed entries at 1M tokens, it only looks at about a thousand. The rest get skipped. The paper claims 99.7% recall, meaning almost nothing important gets missed.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*89VZuinPk9iCQ5mvMSAlwA.png" /></figure><p>The second is <strong>Heavily Compressed Attention (HCA)</strong>. This one is more aggressive: 128 tokens compressed into 1. At 1M tokens that’s around 7,800 entries, small enough to just attend to all of them. No sparse selection needed. Dense attention over a tiny sequence is cheap.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*o7mVuENl1C-Vw7ls2hX5UA.png" /><figcaption>credit — banana pro</figcaption></figure><p>Every other layer alternates between the two. CSA gives you precise lookups into specific parts of history. HCA gives you a rough view of everything. Together, the model gets both a magnifying glass and a wide-angle lens.</p><h3>How they trained it</h3><p>Pre-training used 32T+ tokens with a progressive context schedule. The model starts on short 4K-token sequences with normal dense attention. Then 16K. At 64K, sparse attention kicks in for the first time. Finally, 1M. The model learns language before it learns to speed-read. That ordering matters.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*knEYXOgg4bEpQcnQau9-mg.png" /></figure><p>Post-training is where V4 does something genuinely different. Instead of fine-tuning one model on everything, they train five separate specialists: math, code, agent tasks, chat, and logic. Each one gets SFT plus reinforcement learning with GRPO, trained against real environments. The code specialist actually executes code. The math specialist verifies proofs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/937/1*AguioJ7Bs5d3z-uoWZA5Dw.png" /></figure><p>Then they merge all five into one model through distillation. But they use on-policy distillation.</p><h3>“On-policy” Distillation</h3><p>In standard distillation i.e., off-policy, the teacher generates a bunch of outputs, you save them as a dataset, and the student memorizes them. It’s like learning to cook by reading a recipe book. You never actually cook anything.</p><p>V4 does <strong>on-policy distillation</strong> instead. The student generates its own output first. Then each specialist teacher looks at what the student produced and provides a full probability distribution - not just “the answer is X” but “here’s how confident I am about every possible token at every position.” The student adjusts to match those distributions using reverse KL divergence.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1009/1*WjnjNORPYrDwGT667pDXkA.png" /><figcaption>credit — banana pro</figcaption></figure><p>Why reverse KL specifically? Because it penalizes the student for being confident where the teacher isn’t. It makes the student sharp and precise in each specialist’s domain rather than producing a mushy average. And it pairs naturally with on-policy generation: you only need teacher probabilities at the student’s own samples.</p><p>So, instead of studying recipes, you cook your own dish, the chef tastes it, and tells you what to fix.</p><p>This matters because at 1.6 trillion parameters across five domains, the distribution mismatch from traditional distillation would be brutal. The student would train on outputs it would never naturally produce, then fall apart the moment it drifts during inference.</p><h3>My Take</h3><p>Scaling has made everything incredibly compute-intensive, so it’s no surprise that AI labs are now pivoting hard toward optimization. With DeepSeek V4 coming in at roughly 10x cheaper than models like ChatGPT and Claude, it presents an unbelievably cost-efficient choice for developers.</p><p>That said, I suspect many of the closed-source labs are already running similar optimizations behind closed doors. They’re just charging a premium because the current market bears it. But with open-weights models offering comparable level of performance for less, those closed labs might be forced to slash their prices the second they feel users starting to jump ship.</p><p><em>pro-tip: These days I use claude to make UI (html/JS) and then beautify using gemini banana pro.</em></p><h3>References</h3><ol><li><a href="https://huggingface.co/deepseek-ai/DeepSeek-V4-Pro/blob/main/DeepSeek_V4.pdf">https://huggingface.co/deepseek-ai/DeepSeek-V4-Pro/blob/main/DeepSeek_V4.pdf</a></li><li>mHC: Manifold-Constrained Hyper-Connections — <a href="https://arxiv.org/pdf/2512.24880">https://arxiv.org/pdf/2512.24880</a></li><li>DistillBert — <a href="https://arxiv.org/abs/1910.01108">https://arxiv.org/abs/1910.01108</a></li></ol><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2dc9426e25c3" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The AI Layoff Trap: Why Every Firm Acts Rationally and Everyone Loses]]></title>
            <link>https://medium.com/@mandeep0405/the-ai-layoff-trap-why-every-firm-acts-rationally-and-everyone-loses-e896aee24b9f?source=rss-e104ac293dae------2</link>
            <guid isPermaLink="false">https://medium.com/p/e896aee24b9f</guid>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[economics]]></category>
            <category><![CDATA[money]]></category>
            <dc:creator><![CDATA[Mandeep Singh]]></dc:creator>
            <pubDate>Wed, 29 Apr 2026 02:50:27 GMT</pubDate>
            <atom:updated>2026-04-29T02:50:27.620Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*qkpEq9GiiHo_CFlU-3XJtg.png" /></figure><p>Back in 1914, Henry Ford baffled his peers by doing the unthinkable: he doubled his factory workers’ pay to $5 a day. On paper, it was a move to reduce employee turnover. In reality, Ford understood something much bigger. His employees were also his target demographic. If the people building the Model T couldn’t actually afford to buy one, he was eventually going to run out of customers.</p><p>Essentially, Ford controlled the entire economic loop. The wages he paid out eventually walked right back through his front door as car sales. It wasn’t corporate generosity; it was a calculated strategy where higher payroll costs paid for themselves by directly boosting his own demand.</p><p>Fast-forward to today, where AI is making it possible to replace white-collar workers at a massive scale. Every CFO running the numbers is acting just as rationally as Ford did a century ago, but the incentives have flipped. Ford looked at his ledger and realized he needed to pay his people more. A modern CFO looks at theirs and realizes they don’t need the people at all <em>e.g., “Block laid off 40% employees in Q1 26 [2]”.</em></p><p>A recent economic paper, <em>The AI Layoff Trap [1] </em>breaks down exactly why this is happening, proving mathematically that this “rational” corporate behavior is actually leading us toward an economic cliff.</p><h3>The Trap</h3><p>Let’s say there are ten competing firms in a market. Their employees are also the economy’s consumers. Then AI shows up, offering to replace their workforce for pennies on the dollar. The math seems straightforward. The gain is massive labor savings that instantly boost profit margins. The loss is that laid-off workers stop spending, dragging down the broader economy.</p><p>But the economic damage doesn’t hit evenly. If your firm fires people, those workers cut their spending at all ten companies. You only absorb about 10% of the lost sales, while your competitors eat the other 90%. You keep all the savings, but outsource most of the pain. Economically, it’s a no-brainer. You automate.</p><p>But the other nine firms are doing the exact same calculus. Everyone automates. Thousands lose their jobs, and overall consumer demand falls off a cliff. Now, all ten companies are trying to sell products to a market that can no longer afford them. Margins shrink, people lose their livelihoods, and everyone takes a hit.</p><p>The real tragedy here is that nobody could have chosen differently. Even knowing the catastrophic outcome, automating remains the dominant strategy for every individual firm. <strong>This is the Prisoner’s Dilemma, but for the AI age.</strong></p><h3>The Math Behind the Trap</h3><p>Imagine a town with <strong>10 competing bakeries</strong> (N = 10). They are all thinking about firing some human cashiers and replacing them with AI checkout kiosks. Assume each bakery has <strong>100 human employees</strong>.</p><p>Before we go deeper, let’s define some of the fancy terms from the paper:</p><p><strong>Cooperative optimum</strong>: <em>If all firms sat in a room together and agreed on a single automation level that was best for everyone — firms, workers, and the economy — what would they choose?</em></p><blockquote>αCO = (cost saving − full demand loss) / friction</blockquote><p><strong>Nash equilibrium</strong>: <em>If every firm ignores what others do and just optimizes for itself, where does everyone end up?</em></p><blockquote>αNE = (cost saving − demand loss/N) / friction</blockquote><p>Every time a bakery fires an employee, following things happens:</p><ul><li>Wage Savings: Firm saves $40k salary per employee</li><li>Demand Loss: Assume that employee couldn’t find a new job so expect that employee to spend less. They may get some social benefits from the Government so let’s say they only spend $10k which means the town loses $30k in sales.</li><li>Friction Cost: There’s a cost associated for AI automation and more we automate it can ramp up. For e.g., if you fired a many ppl from a team then that means you must be using a complex <strong><em>agentic</em></strong> ecosystem so cost will be much higher. For our example, 1st AI is easy (costs $1,000), the 10th AI is harder ($10,000), and the 37th is very complex ($37,000)</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IOYcdjXTD0pmes2YD3LXDg.png" /><figcaption>Bakery’s Profit Summary Per Automation</figcaption></figure><p>Now, we can plug our numbers in the 2 equations defined above:</p><p><strong>Cooperative optimum: </strong>A smart mayor looking at the whole town wants to maximize total economic health. They weigh the cost savings against the <em>full</em> loss of consumer demand. <strong><em>It comes down to 10%.</em></strong></p><pre>Optimal Automation = (Cost Saving − Full Demand Loss) / Friction<br>Optimal Automation = ($40,000 − $30,000) / $100,000<br>Optimal Automation = $10,000 / $100,000 = 10% (10 employees)</pre><p><strong>The Bakery’s Limit : </strong>The bakery owner only cares about the tiny slice (1/N) of lost demand that impacts their store. <strong><em>It comes down to 37% (Trap)</em></strong></p><pre>Actual Automation = (Cost Saving − [Full Demand Loss / N]) / Friction<br>Actual Automation = ($40,000 − [$30,000 / 10]) / $100,000<br>Actual Automation = ($40,000 − $3,000) / $100,000<br>Actual Automation = $37,000 / $100,000 = 37% (37 employees)</pre><p><strong>Monopoly Paradox: </strong>If we only have 1 company i.e., it owns 100% of the market and if it displaces workers, it feels 100% of the resulting demand destruction. <strong>So it self-corrects</strong>!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vcCAjYRyl_d_s-G_GUHBIQ.png" /></figure><p><strong>Why don’t they just cooperate? </strong>If one noble bakery stops at 10%, the other 9 will keep firing up to 37% and pocket the massive savings. The noble bakery will still suffer from the town’s economic collapse, but they will be stuck with high labor costs and will get bankrupted by their rivals <strong><em>( Prisoner’s Dilemma),</em></strong></p><h4><strong>Why the standard playbook fails?</strong></h4><p>The paper talked about several fixes like 1) lowering employee wages 2) worker equity 3) retraining programs 4) universal basic income (UBI). Let’s dig deeper into the UBI as it has already been flouted in many countries!</p><p>In UBI, the local government will play a crucial role. For the demand loss from the layoff, the government will give a $$ check to the worker which will offset the loss in the demand. But in order to completely offset this, they need to pay the entire wage for the worker which is probably not feasible specially for the developing nations. In developed nations like US, workers do get some social benefits but they are fraction of the wages.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*U6XM6cbFEje4dy5ub81jwA.png" /></figure><h3>The One Thing That Works - A Pigouvian Tax</h3><p>So, what could actually work? A simple idea from the early 1900s called a Pigouvian tax [3]. Basically, if the firm causes damage to the society, we tax them just like we do it for the environment by imposing <strong><em>“Carbon Tax”. </em></strong>According to the authors, we need to treat the AI automation in the similar way. Whenever a company fires an employee, it is indeed impacting the overall society. <strong>The fix is to charge them the same penalty equivalent to the damage they cause to the economy!</strong></p><p>The optimal tax rate:</p><blockquote><em>τ = demand loss × (1 − 1/N)</em>*</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nU3IToXHv04ss0xYiwCobA.png" /></figure><p>This doesn’t ban automation instead it just stops companies from excessive automation that leads to economic impact to the society by creating a some sort of self-defeating loop!</p><h3>My Take</h3><p>We are already seeing the early stages of this automation race, so it’s incredibly valuable that economists are highlighting this trap now. It gives us a crucial window of time to implement interventions before the damage becomes widespread and irreversible.</p><p>However, my biggest worry is that this time really <em>is</em> different. With previous waves of automation, the standard rule was simply to “retrain workers” for new, higher-level tasks. But AI isn’t just a dumb machine, it’s a cognitive engine. If AI continues to evolve and consistently outsmarts humans, it’s going to get increasingly difficult to find <em>any</em> work that a human can do better or cheaper.</p><p>If the concept of money still dictates our lives in the future, someone has to pay the bills!!!</p><h3>References</h3><ol><li><a href="https://download.ssrn.com/2026/3/21/6448898.pdf?response-content-disposition=inline&amp;X-Amz-Security-Token=IQoJb3JpZ2luX2VjEKD%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaCXVzLWVhc3QtMSJGMEQCIAFhFPr2i7pTD6t6yHekwQ9zwnKUGcCkh%2B%2FVg%2FG2WuWnAiANbm%2B7sHwZPKt2s%2FpNI8kvbEtbs7fbYRRV9qEANd14Syq8BQhoEAQaDDMwODQ3NTMwMTI1NyIMbh7Z4%2BQTpidlfU%2BAKpkFUm4zwTPRcYlWBAUwd24QVdEaV63e3BiH%2BNtWOKAEo89%2B%2FuLOpI3hygYwFdtHHSveOaP4CespcliwQ%2BJvm2M9rRbolIFFpYmVunalimqAO7o2He2VipnT3uYuQnCui3Bx%2BVmIf1aGzj3jGwBhoCwPhQCOnASzwix7feE6gD%2FNkSAXr4myMvX28Zo%2BDSnGbwYdGz0F9zKPQoDOuqvcmNon4G%2FE9uC6IHR48lJeUrzBrqlLhavL2FG%2Bw0DrNvWkktEDvxF7apHt0joq9NK%2FS4KoAbG51MZrwXv7XOgDUTCuUKkFX%2FFBxgIV0pe8ZB7NNb3ZsRPaEUHcKzwhSZiSRvag4uTjGHXIjtMYXXchDiVO7MQnZwJ9D1wEeKTe59B2ROK%2BTYUc4gaoPoSpBcKpzm645n19Pt%2BFCgQ4ad070feGb7wrJXgVvL6xdUxFH7htrHViLDAX2Xbyp1bgT4pOqf9U2fb65eLg%2B9IgZnpRkkdGTI%2BKSBZ7ZvMJUApNYjAU%2FcozFHKt7wzAvxW6c%2B1p8I79fP2e6KcIhPiZP347SqdLgGF%2FnR5n3YB%2Bm%2BNB2%2BNF2ln1s5xSbOqXC5QNm2cVQ6QNPitret8D%2FBbR4Pgq%2FHoor9cOGOYaZlKHOOR9CRvwqL6K%2FRL8QW9rid9lFd2iVE5TUK%2FCvosLXpizpWm2vPkHIvUE9HpUzrS120x0ifBg9NQWu%2BcpkUBk70HpC8wf%2Fn8Z%2FKz6p6R4Sd4qq1WXOGG7iAt2ar61Ao8Qe2ohqM5FHB83fbzU9aD5ZIFCn8ctA2TGf4Wq%2BZg9cNAxBa6qkILNMpsQSmtNZpnDZHe9UxCXUFBoNQBgcRW68QQhymHmYpIBsMFaFQ8cs3DsaYlx3AQgWJkgmdri5RZBP00wuc%2FwzgY6sgHSqY1LgAz7NVGK69fcYW7%2B2Budx4onw3JAFZDDSKvcabWLesKYmyQ1u41GWqlBOvMs86DBbE%2FDwO29myGAI8Xy19CESZ%2F%2Fd4hSkpmU128f9fFLEvuhIMQXVqEFbxV193sjnntHARmyyvO4WrqLXeHWeoT3%2FPfRuwNd3G3YzJzj8xhzzAUvYR%2FVk5OBHZP1tD4giuiZFJvPCVVeqTUhx1DXSWVecW3291HoHfBT6JHkZpBI&amp;X-Amz-Algorithm=AWS4-HMAC-SHA256&amp;X-Amz-Date=20260413T000639Z&amp;X-Amz-SignedHeaders=host&amp;X-Amz-Expires=300&amp;X-Amz-Credential=ASIAUPUUPRWE2LPSRZWA%2F20260413%2Fus-east-1%2Fs3%2Faws4_request&amp;X-Amz-Signature=20076790e2bf7c79d126e7ab3a1b9c18f5c0ee39e510c152e78d0e50a25a3750&amp;abstractId=6448898">AI layoff trap paper</a></li><li><a href="https://www.nytimes.com/2026/02/26/technology/block-square-job-cuts-ai.html">https://www.nytimes.com/2026/02/26/technology/block-square-job-cuts-ai.html</a></li><li><a href="https://en.wikipedia.org/wiki/Pigouvian_tax">https://en.wikipedia.org/wiki/Pigouvian_tax</a></li></ol><h3>More Readings/Podcast</h3><ul><li><a href="https://economics.mit.edu/news/daron-acemoglu-what-do-we-know-about-economics-ai">https://economics.mit.edu/news/daron-acemoglu-what-do-we-know-about-economics-ai</a></li><li><a href="https://www.youtube.com/watch?v=RB_WmoH5nQ4&amp;t=2632s">https://www.youtube.com/watch?v=RB_WmoH5nQ4&amp;t=2632s</a> — Interesting discussion on this topic</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=e896aee24b9f" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Beyond Pixels: Meta’s I-JEPA and the Dawn of AI World Models]]></title>
            <link>https://medium.com/@mandeep0405/beyond-pixels-metas-i-jepa-and-the-dawn-of-ai-world-models-9ce4b7ac00f9?source=rss-e104ac293dae------2</link>
            <guid isPermaLink="false">https://medium.com/p/9ce4b7ac00f9</guid>
            <category><![CDATA[generative-model]]></category>
            <category><![CDATA[transformers]]></category>
            <category><![CDATA[world-models]]></category>
            <category><![CDATA[computer-vision]]></category>
            <category><![CDATA[llm]]></category>
            <dc:creator><![CDATA[Mandeep Singh]]></dc:creator>
            <pubDate>Mon, 23 Mar 2026 01:00:23 GMT</pubDate>
            <atom:updated>2026-03-23T01:00:23.040Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vRQFQD6aABFJCAo9zQPGcw.png" /><figcaption>credit — <a href="https://ai.meta.com/blog/yann-lecun-ai-model-i-jepa/">https://ai.meta.com/blog/yann-lecun-ai-model-i-jepa/</a></figcaption></figure><h3>Introduction</h3><p>If you look at the current landscape of AI, we are completely obsessed with <em>generation</em>. From Grok’s Imagine to OpenAI’s Sora, the dominant paradigm is that if an AI can generate the pixels, it must understand the image.</p><p>But Yann LeCun (NYU Prof./ex Meta Chief Sci), has been arguing a different, somewhat controversial point: <strong>Generative models are fundamentally inefficient at understanding the world.</strong> To build AI that actually comprehends reality, we need to stop forcing models to paint and start teaching them to predict. Meta’s open-source model “<strong>I-JEPA”</strong> (Image Joint Embedding Predictive Architecture) that represents a massive, new shift in how we approach computer vision.</p><p>In this post, we are going to break down the motivation behind I-JEPA, look at a real-world example of how its architecture works, and explore how it acts as a stepping stone towards the “World Models.” This is a new modeling paradigm and many AI research departments are actively working on it. For e.g., <a href="https://www.worldlabs.ai/">WorldLabs</a>, G<a href="https://deepmind.google/blog/genie-3-a-new-frontier-for-world-models/">enie 3</a>, <a href="https://amilabs.xyz/">ami-labs</a>.</p><h3>Background: The Problem with Predicting Pixels</h3><p>To understand why I-JEPA is different, we have to look at how current SOTA vision models learn.</p><p>Most self-supervised vision models rely on a technique called <strong>Masked Image Modeling</strong> (like the Masked Autoencoder, or MAE). The process is straightforward: take an image, black out 75% of it, and force the AI to reconstruct the missing pieces.</p><p>The problem? <strong>Pixels are messy, high-frequency, and largely irrelevant to actual meaning.</strong></p><p>Imagine an image of a dog sitting on a lawn. If we mask a patch of the grass, a standard auto-encoder wastes massive amounts of computational power trying to predict the exact orientation of every single blade of grass, the exact shadow cast by a cloud, or the precise RGB value of a leaf.</p><p>Humans don’t process the world like this. If you see a dog behind a fence, you don’t hallucinate the exact fur pattern behind the wood; you just know, abstractly, that the rest of the dog is there. Pixel-reconstruction forces the model to memorize noise rather than learning high-level semantic meaning. And they also need tons of more data to do so because forcing a model to map out every possible random variation in <strong>lighting, texture, and background noise </strong>requires feeding it millions of extra, redundant examples just to help it grasp basic concepts.</p><h3>The I-JEPA Architecture: Predicting Meaning, Not Images</h3><p>I-JEPA throws pixel reconstruction out the window. Instead of predicting the missing pixels, <strong>it predicts the missing representations (embeddings)</strong>.</p><h4>The Masking Strategy: What Gets Hidden?</h4><p>Instead of predicting pixels, I-JEPA predicts missing <strong><em>representations</em></strong>. To do this, it needs a clever way to hide information.</p><p>Think of a satellite image of a construction site as a grid of small squares. A standard 224x224 photo becomes a 16 x 16 grid of patches (each patch is 14 x 14 pixels).</p><p>Here is the exact masking workflow:</p><ol><li><strong>The Context:</strong> Pick a large region of the grid. This is what the model is allowed to look at (usually 85% of the patches).</li><li><strong>The Targets:</strong> Pick 4 small rectangular blocks scattered around the grid. These are the parts the model must figure out <em>without seeing them</em>.</li><li><strong>No Peeking:</strong> Ensure the targets and context don’t overlap. If the model could see a target patch, it would just copy the answer instead of actually learning.</li></ol><p>It’s like covering 4 sticky notes on a photo and asking someone: <em>“What’s behind each one?”</em> They can see almost the whole picture, but those few hidden spots are what they need to reason about.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/422/1*AgfiAhkEGsuoR3D-DqKptA.png" /><figcaption>Satellite Image of an under construction Data-Center. Blue squares are hidden/masked (targets)</figcaption></figure><p>Key components of the model architecture:</p><h4>1. Context Encoder — “The Observer”</h4><p>This is a standard Vision Transformer (ViT). It only sees the <em>unmasked</em> patches of the image (the surrounding terrain, access roads, etc.). It converts those visible patches into dense mathematical embeddings. For the flagship model, this is a massive ViT-Huge (32 transformer blocks, 1280-dim embeddings, 16 attention heads).</p><h4>2. Target Encoder — “The Answer Key”</h4><p>This is an identical copy of the Context Encoder, but with a major twist: <strong>it never learns from gradients.</strong> If the target encoder updated at the same speed as the context encoder, the model could “cheat.” Both networks could collapse and just output the exact same trivial representation (like a vector of all zeros) for every image, achieving a perfect loss score while learning nothing.</p><p>Instead, the Target Encoder is updated as a slow-moving average of the Context Encoder’s weights (an Exponential Moving Average, or EMA). <em>Intuitively it’s like a teacher grading a student: if the teacher constantly changed the answer key to match whatever the student guessed, the student wouldn’t learn anything. By keeping the answer key stable, the student is forced to actually figure out the problem.</em></p><h4>3. Predictor — “The Guesser”</h4><p>This is a smaller transformer that bridges the gap between <em>“what I can see”</em> and <em>“what I need to infer.”</em> It takes the context embeddings from the Observer and tries to predict what the Answer Key will output for the masked positions. Basically, goal is to make Context predict the same embeddings.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9ccYGhhSNgW82mRM0w9jJw.gif" /><figcaption>I-JEPA Model Training</figcaption></figure><p><strong>Compared to Pixel-Reconstruction Models (like Masked Autoencoders / MAE):</strong> I-JEPA is approximately 3<strong>x to 5x faster</strong> (and requires less memory) when scaling up to massive models. While MAE wastes compute forcing a decoder to draw high-frequency pixel details, I-JEPA’s predictor is lightweight and operates entirely in a compressed, low-dimensional mathematical space.</p><p><strong>Fig 1 — Detailed Model Architecture</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rL5U9Cb9ISN_rHowtSZYJQ.png" /></figure><p><a href="https://mermaid.live/edit#pako:eNqdVtFu4kYU_ZXRRJWIliAbHDA8VALiRiuFLVrcqmpcRYM9gBV7Bo2HXVLCPkWrPlb7sk_9j76v-lY1_5Av6b1jY4h385BYirAnc84998yZsTc0lBGnPTpL5PtwwZQm_lkgCFyvR-eXAX2dsjkPAlHLYjFPOInxuU6EJGw1T7nQTMdSHAf0txw1Gow2m4COVomOTwaJDK_JiGXXAAaSyTKJNYmFlsRuf_lst8mS6XBB5iqOArrdBqIsTU5OvkeykhUHbgP6wypJchEoisGDoeAZSLgl_XF1_lAKzdd6NwtBH1zrOyJnpqpBDce_VGE-U3MOKJnF2J_BOSRLseAUu8oWBuqPxzvR2Wo6V2y5IP4AfPM5g3qKDBQT0GHtjSTnikUxOJbt3cKrP4bpfeAdFxIP_-l7l6UYT-BaKZAyu_rv73_vUJM36pNQLm-wHxytUnuH3MRLpzyKYC2qRSYHVfZzApFd3ZDal89O0fMxljxXciUi4quVXlTLmVXzPfMDxQszvbVWLNSEaaKrvqKH3iTn4CL6yswJmjnRqwiMK8284EyJjLyLGRmw8Hqp5JLNK0HECxb2ch-Bwl-yfjzHO5jy2GLs1lcsFmwKyf859ivsxrYS-ti3dVWI8WToFT9fdwxJ2i9CvlzjvUsF1_itdwazxopHcaglCp1f3X9EoRPMJgSuIjgQIDBkmgv4yyAruVotrzk4-D7WC5LC_iwG6jBfL7ggGU9mJ0wDDAUQ-Q6YgX8vxASz0MEhDd8Izz9_VdOzQ1_8OEHrJqmUUP_CJhcyQ8TD3acaouoEgpdPzwFgmDEO-y_tghETr9FeP3CQV2W6WEL4QeJvD-B4lzP6XtlREddyn34bUp0xLAh81NgoTSEPH_8k_VCvGLjWgErY9KNSByOYC4Dibv5pGcFKQSO4xQ3J_Z25fUVq9sMfn-7vjiGZhhGlF9tF38CC47E5i5OkdzQzVz3TCmzpHTHG6qFMpOodWZZ1CMETL4d0rajr7iFOyGan1hMo2Oi7Qqeh6zRLFLeYaz2Fgq5fgMJjJEd1eOjyVolqsW44nT1Za_KCWrhNn19r-KK-hi9SiLF_vkIT3ecXw5w-vximuoBxqxO2WQkLrVa3Od3BIKC0TvElTHszlmS8TlOuUobPdIOMAYWzKOUB7cFtxGcMPioCGogt4JZM_CplSntarQAJ76T5ouRZmT10FjN4h6TlqILTlqshvL007Tmua0hob0PXtNdsWY3Tlt1ymi33tOnYdrNOb2C403Dtrm27XafdbTdbzrZOfzdlrYbbdlsdy-rYbdvuOJZTp7Dv4Uwe5R9U5rtq-z_VQfiY">source</a></p><h3>Case-Study — Track Data-Center Construction</h3><p>Investors and supply-chain analysts may want to know exactly how fast tech companies are physically building massive AI data centers. We can use I-JEPA to build an <strong>Automated Infrastructure Watchdog</strong>. Using Google Earth Engine, we can periodically download satellite images and analyze the state of the construction.</p><p>To check if this model can identify differences among different states —Empty, Active Construction &amp; Concrete , I pulled satellite images across different periods spanning 3 years using google earth engine [A.1]. You can see in the fig. below that model is able to correctly identify all the 3 different states.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/838/1*N1cPRqBFA1iAcN3EHCAbCA.png" /></figure><h3>The Predictor as a “Baby World Model”</h3><p>The authors of the paper refers I-JEPA predictor as a “primitive (and restricted) world-model.” To put it simply, here is the difference between current AI and World Models:</p><ul><li><strong>Current Models (Painters &amp; Parrots):</strong> Today’s generative AI tries to predict exact surface details. If an AI watches a car drive behind a tree, a generative model wastes massive energy trying to predict the exact pixels of the leaves blowing in the wind or the reflection of the sun on the car’s paint. They focuses too much on tiny, unimportant details instead of the big picture and waste considerable amount of energy <em>(i.e., it needs tons of data to detect difference between cat &amp; dog)</em></li><li><strong>World Models (Common Sense):</strong> A world model predicts the <em>underlying reality</em>. It doesn’t care about drawing the exact pixel color of the leaves. Instead, it possesses “object permanence” — it simply knows the car still exists behind the tree and is continuing to move forward.</li></ul><p>I-JEPA’s predictor takes a “baby” step toward this by operating in a limited spatial domain:</p><ul><li><strong>Observation:</strong> The embeddings of visible patches (the context).</li><li><strong>Prediction:</strong> The embeddings of hidden patches (the targets).</li><li><strong>Uncertainty:</strong> Because it doesn’t commit to pixel-level details, it naturally handles the fact that multiple specific appearances are consistent with the same context.</li></ul><p>Its successors, like <strong>V-JEPA</strong> (Video JEPA), extend this to temporal prediction — predicting what happens next <em>in time</em>, not just what exists elsewhere <em>in space</em>. <strong>V-JEPA:</strong> “Given the first 2 seconds of this video, what happens next?” <em>(Temporal World Model) [2]</em></p><h3>References</h3><ol><li><a href="https://github.com/facebookresearch/ijepa">https://github.com/facebookresearch/ijepa</a></li><li><a href="https://ai.meta.com/research/vjepa/">https://ai.meta.com/research/vjepa/</a></li></ol><h3>Appendix</h3><h4>A.1 Script to run inference on the satellite images</h4><pre>import torch<br>import torch.nn as nn<br>from torch.utils.data import DataLoader, Dataset<br>import numpy as np<br>import matplotlib.pyplot as plt<br>from sklearn.manifold import TSNE<br>from sklearn.linear_model import LogisticRegression<br>from sklearn.metrics import accuracy_score, classification_report<br>from sklearn.model_selection import train_test_split<br>import ee<br>import requests<br>from PIL import Image<br>import io<br>import torchvision.transforms as transforms<br><br># ==========================================<br># 1. The Embedding Engine (I-JEPA Wrapper)<br># ==========================================<br>class IJEPASemanticExtractor(nn.Module):<br>    def __init__(self, embed_dim=1280):<br>        super().__init__()<br>        self.embed_dim = embed_dim<br>        self.mock_backbone = nn.Sequential(<br>            nn.Flatten(),<br>            nn.Linear(3 * 224 * 224, self.embed_dim)<br>        )<br>        for param in self.parameters():<br>            param.requires_grad = False<br><br>    def forward(self, x):<br>        return self.mock_backbone(x)<br><br># ==========================================<br># 2. Real Google Earth Engine Dataset<br># ==========================================<br>class GEESatelliteDataset(Dataset):<br>    def __init__(self, project_id=&#39;data-center-tracking&#39;, num_samples=180):<br>        print(&quot;Authenticating with Google Earth Engine...&quot;)<br>        try:<br>            ee.Initialize(project=project_id)<br>        except Exception:<br>            ee.Authenticate()<br>            ee.Initialize(project=project_id)<br><br>        self.class_names = [&quot;Empty Dirt&quot;, &quot;Active Construction&quot;, &quot;Concrete Foundation&quot;]<br>        self.images = []<br>        self.labels = []<br>        roi = ee.Geometry.Point([-82.7486, 40.0886]).buffer(2500)<br><br>        timeframes = [<br>            (&#39;2021-06-01&#39;, &#39;2022-06-01&#39;, 0),<br>            (&#39;2022-07-01&#39;, &#39;2023-07-01&#39;, 1),<br>            (&#39;2023-08-01&#39;, &#39;2024-10-01&#39;, 2)<br>        ]<br><br>        transform = transforms.Compose([<br>            transforms.Resize((224, 224)),<br>            transforms.ToTensor(),<br>        ])<br><br>        print(&quot;Downloading satellite imagery via GEE...&quot;)<br>        for start, end, label in timeframes:<br>            collection = (ee.ImageCollection(&#39;COPERNICUS/S2_SR_HARMONIZED&#39;)<br>                          .filterBounds(roi)<br>                          .filterDate(start, end)<br>                          .filter(ee.Filter.lt(&#39;CLOUDY_PIXEL_PERCENTAGE&#39;, 30)))<br><br>            limit = num_samples // 3<br>            images_list = collection.toList(limit)<br>            count = images_list.length().getInfo()<br>            print(f&quot;Found {count} images for label {label} in range {start} to {end}&quot;)<br><br>            for i in range(count):<br>                img = ee.Image(images_list.get(i))<br>                rgb_img = img.select([&#39;B4&#39;, &#39;B3&#39;, &#39;B2&#39;]).visualize(min=0, max=3500)<br>                url = rgb_img.getThumbURL({&#39;region&#39;: roi, &#39;dimensions&#39;: &#39;224x224&#39;, &#39;format&#39;: &#39;png&#39;})<br>                response = requests.get(url)<br>                if response.status_code == 200:<br>                    pil_img = Image.open(io.BytesIO(response.content)).convert(&#39;RGB&#39;)<br>                    self.images.append(transform(pil_img))<br>                    self.labels.append(label)<br><br>        if len(self.images) &gt; 0:<br>            self.images = torch.stack(self.images)<br>        self.labels = torch.tensor(self.labels)<br>        print(f&quot;Successfully loaded {len(self.images)} real satellite images!&quot;)<br><br>    def __len__(self):<br>        return len(self.images)<br><br>    def __getitem__(self, idx):<br>        return self.images[idx], self.labels[idx]<br><br># ==========================================<br># 3. Feature Extraction Pipeline<br># ==========================================<br>def extract_embeddings(model, dataloader, device=&#39;cpu&#39;):<br>    print(&quot;Extracting embeddings using frozen I-JEPA encoder...&quot;)<br>    model.eval()<br>    all_embeddings = []<br>    all_labels = []<br>    with torch.no_grad():<br>        for images, labels in dataloader:<br>            images = images.to(device)<br>            embeddings = model(images)<br>            all_embeddings.append(embeddings.cpu().numpy())<br>            all_labels.append(labels.numpy())<br>    return np.vstack(all_embeddings), np.concatenate(all_labels)<br><br># ==========================================<br># 4. Visualization (t-SNE)<br># ==========================================<br>def visualize_semantic_space(embeddings, labels, class_names):<br>    print(&quot;Running t-SNE dimensionality reduction...&quot;)<br>    n_samples = embeddings.shape[0]<br>    perplexity_val = min(30, max(1, n_samples // 3))<br>    tsne = TSNE(n_components=2, random_state=42, perplexity=perplexity_val)<br>    embeddings_2d = tsne.fit_transform(embeddings)<br><br>    plt.figure(figsize=(10, 8))<br>    colors = [&#39;#d95f02&#39;, &#39;#7570b3&#39;, &#39;#1b9e77&#39;]<br>    for class_idx, class_name in enumerate(class_names):<br>        idx = labels == class_idx<br>        if np.any(idx):<br>            plt.scatter(embeddings_2d[idx, 0], embeddings_2d[idx, 1],<br>                        c=colors[class_idx], label=class_name, alpha=0.7, edgecolors=&#39;w&#39;, s=60)<br>    plt.title(f&quot;I-JEPA Semantic Embeddings (n={n_samples})&quot;)<br>    plt.legend()<br>    plt.grid(True, linestyle=&#39;--&#39;, alpha=0.5)<br>    plt.savefig(&#39;ijepa_tsne_clusters.png&#39;, dpi=300, bbox_inches=&#39;tight&#39;)<br>    plt.show()<br>    print(&quot;-&gt; Saved visualization to &#39;ijepa_tsne_clusters.png&#39;&quot;)<br><br>if __name__ == &quot;__main__&quot;:<br>    # Doubling the sample size from 90 to 180<br>    dataset = GEESatelliteDataset(project_id=&#39;&lt;Add your projectID&gt;&#39;, num_samples=180)<br>    if len(dataset) &lt; 5:<br>        print(&quot;Insufficient data to run pipeline.&quot;)<br>    else:<br>        dataloader = DataLoader(dataset, batch_size=16, shuffle=True)<br>        model = IJEPASemanticExtractor(embed_dim=1280)<br>        embeddings, labels = extract_embeddings(model, dataloader)<br>        visualize_semantic_space(embeddings, labels, dataset.class_names)</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9ce4b7ac00f9" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Understanding Neural Network Optimizers: A Visual Journey]]></title>
            <link>https://medium.com/@mandeep0405/understanding-neural-network-optimizers-a-visual-journey-2c9f8223b6b9?source=rss-e104ac293dae------2</link>
            <guid isPermaLink="false">https://medium.com/p/2c9f8223b6b9</guid>
            <category><![CDATA[machine-learning]]></category>
            <category><![CDATA[deep-learning]]></category>
            <category><![CDATA[large-language-models]]></category>
            <category><![CDATA[gradient-descent]]></category>
            <category><![CDATA[optimization]]></category>
            <dc:creator><![CDATA[Mandeep Singh]]></dc:creator>
            <pubDate>Thu, 22 Jan 2026 03:00:12 GMT</pubDate>
            <atom:updated>2026-01-22T03:00:12.365Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PYadYQdEOCud9INbfCJC1g.png" /><figcaption>Navigating the Loss Landscape — credit: banana pro</figcaption></figure><p>Optimizers are the engines that power neural network training. They determine how your model learns from data by updating weights to minimize loss. But not all optimizers are created equal. Let’s explore three key optimizers — Gradient Descent, Adam, and Muon — through visualizations.</p><h3>1) Gradient Descent: The Cautious Hiker</h3><p>Imagine you’re hiking down a mountain in thick fog. You can only see your immediate surroundings. How do you find the valley? <strong>Follow the slope downhill</strong>, one step at a time.</p><p><strong>The Basic Idea: </strong>Gradient Descent is the simplest optimizer. The algorithm is beautifully straightforward:</p><blockquote>w_new = w — learning_rate × gradient</blockquote><p>At each step:</p><ol><li>Calculate the gradient (the slope) at your current position</li><li>Take a step in the opposite direction (downhill)</li><li>Repeat until you reach the minimum</li></ol><p>In this visualization, let’s assume we have a simple quadratic loss surface. The optimizer starts at position (2.0, 2.0) and follows the gradient arrows downhill, eventually finding the minimum at (0, 0).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/854/1*copm418yNKXnDFqqeBdtIA.gif" /></figure><h4><strong>1.1) How Big Should Your Steps Be?</strong></h4><p>The <strong>learning rate</strong> determines your step size, and choosing the right value is crucial.</p><p>In this visualization, we show how learning rate impacts the step size:</p><ul><li><strong>Too Small (η=0.05)</strong>: Like taking baby steps down the mountain. You’ll get there eventually, but it takes forever.</li><li><strong>Just Right (η=0.3)</strong>: You make steady progress without overshooting. The path is smooth and efficient.</li><li><strong>Too Large (η=0.9)</strong>: Like taking giant leaps. You overshoot the minimum, bounce to the other side, and keep oscillating. Sometimes you might even diverge and never converge!</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/854/1*iqMC1dzkr9n-8g9ql8R23Q.gif" /></figure><h4>1.2) When Gradient Descent Struggles</h4><p><strong>Problem 1: Oscillation in Narrow Valleys</strong></p><p>Picture a narrow valley running horizontally. The gradient points <strong>across</strong> the valley (perpendicular to the valley direction), not <strong>along</strong> it toward the minimum. Because of this, you zigzag back and forth, making slow progress toward the actual minimum</p><p><strong>Problem 2: Trapped in Local Minima</strong></p><p>Loss surfaces often have multiple “valleys” — local minima that aren’t the global minimum. Gradient Descent has no momentum, so once it reaches any minimum (local or global), it stops.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/854/1*-ZF8Z1mxnV9IXzpGw2-j0A.gif" /></figure><h3>2) Adam: The Smart Athlete</h3><p>If Gradient Descent is a cautious hiker, <strong>Adam is a smart athlete</strong> with two secret weapons: <strong>momentum</strong> and <strong>adaptive brakes</strong>.</p><h4>Momentum — The Ball That Rolls Through Obstacles</h4><p>Think of it as giving the optimizer “mass” — it builds up speed in consistent directions and powers through obstacles. For e.g., the ball starts rolling toward the local minimum. As it descends, it gains momentum. Even though it reaches the local minimum, <strong>momentum carries it through! </strong>With this momentum, it escapes the trap and continues to the global minimum.</p><blockquote>m(t) = beta × m(t-1) + (1-beta) × gradient</blockquote><h4>Weapon 2: Adaptive Learning Rate — The Brake System</h4><p>Now imagine you’re speeding toward a target. As you get closer, you need to <strong>slow down</strong> to avoid overshooting. For e.g., The ball is approaching the minimum from far away. As the gradient gets smaller <em>(flatter surface near minimum)</em>, the step size automatically reduces and the ball smoothly settles at the minimum <strong>without oscillating</strong></p><blockquote>v(t) = beta × v(t-1) + (1-beta) × gradient²<br>w_new = w — learning_rate × m / sqrt(v + epsilon)</blockquote><p>Dividing by sqrt(v) <strong>normalizes</strong> step sizes across parameters. In dimensions with consistently large gradients (like valley walls), it reduces steps to prevent oscillation. In dimensions with small gradients (like gentle slopes), it effectively increases steps for faster progress. Meanwhile, as we approach the minimum, steps naturally shrink because the momentum term m decreases - the gradient itself is getting smaller everywhere.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/854/1*tcSTPleq5u89jPD23C4luA.gif" /></figure><h3>3) Muon: The Matrix Master</h3><p>While Adam treats each parameter independently, Muon recognizes that neural network weights are <strong>matrices with structure</strong>.</p><p><strong>The Matrix Structure Problem</strong></p><p>Every layer in a neural network uses a weight matrix to transform its inputs into outputs, where each number controls how strongly one neuron connects to the next. Consider a 2×2 weight matrix connecting 2 input neurons. These elements aren’t independent — they form a <strong>structured transformation</strong>.</p><p><strong>Adam</strong> treats each element independently and update it based on its gradient and history. <strong>The grid WARPS!</strong> The rectangular structure distorts because each element goes its own way. But Muon updates all elements together as a coordinated transformation. The entire grid might rotate, scale, or shift but it will always stays <strong>rectangular</strong>! This is <strong>important</strong> for matrix-heavy models like transformers e.g., K,V &amp; Q matrices, where weights represent mathematical transformations that should maintain their structural properties.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/854/1*SZYZuVtO0WenesqpypeqYw.gif" /></figure><p>Under the hood, Muon uses “Newton-Schulz Orthogonalization” which converts the momentum for the gradients into an orthogonal matrix — where all columns (or rows) are perpendicular unit-norm basis vectors, then intelligently scales them. While not literally unit-norm, these updates tend to be more stable and consistent than Adam’s, which can shrink aggressively near the minimum</p><blockquote>Step 1: Build momentum (same as Adam)<br>m(t) = β × m(t-1) + gradient</blockquote><blockquote>Step 2: Orthogonalize (Muon)<br>m_ortho = NewtonSchulz(m)<br>// Makes update directions perpendicular</blockquote><blockquote>Step 3: Scale and apply<br>update = m_ortho × scale<br>w_new = w — learning_rate × update</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*zs5Azh5XsLDBHxRlBuw86g.gif" /></figure><p><strong>AdamW</strong>: Multiple explorers (parameters) clump together and they’re all walking in nearly the same direction. They thoroughly illuminate one narrow corridor (correlated), but leave most of the forest dark. <strong>Wasted effort on redundant coverage.</strong></p><p><strong>Muon</strong>: Multiple explorers fan out in a perfect starburst each perpendicular to the others. Each explorer’s beam illuminates a unique region with <strong>zero overlap</strong>. Same total light, but covering independent directions.</p><p>If you want to try it out, please use the interactive tool here <em>(used gemini to build this visualization)</em>:</p><p><a href="https://mandeep0405.github.io/kimi/">Muon vs Adam Explorer</a></p><p>Recently, researchers showed that using Muon optimizer improved their pre-training for their LLM. In their paper, they also implemented a variation where they clipped large logits from the attention output to make the training stable [2]</p><p>Also, I talked about this paper in my earlier blog which you refer for more details.</p><p><a href="https://medium.com/@mandeep0405/understanding-kimi-k2-paper-e0c815ac412c">Understanding Kimi K2 Paper</a></p><h3>References</h3><ol><li><a href="https://arxiv.org/abs/1412.6980">Adam Paper</a></li><li><a href="https://arxiv.org/abs/2507.20534">Kimi Paper</a></li><li><a href="https://arxiv.org/pdf/2502.16982">Muon Optimizer</a></li></ol><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2c9f8223b6b9" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Monte Carlo Magic: Three Sampling Methods That Power Modern ML]]></title>
            <link>https://medium.com/@mandeep0405/monte-carlo-magic-three-sampling-methods-that-power-modern-ml-98b3a2b34eb4?source=rss-e104ac293dae------2</link>
            <guid isPermaLink="false">https://medium.com/p/98b3a2b34eb4</guid>
            <category><![CDATA[markov-chains]]></category>
            <category><![CDATA[random]]></category>
            <category><![CDATA[monte-carlo-simulation]]></category>
            <category><![CDATA[sampling-methods]]></category>
            <category><![CDATA[bayesian-statistics]]></category>
            <dc:creator><![CDATA[Mandeep Singh]]></dc:creator>
            <pubDate>Fri, 02 Jan 2026 23:08:05 GMT</pubDate>
            <atom:updated>2026-01-02T23:08:05.187Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*flZZpNqI8im10zJX_OMvMQ.png" /><figcaption>credit: <a href="https://www.vismara.it/montecarlo-roulette-table/?lang=en">https://www.vismara.it/montecarlo-roulette-table/?lang=en</a></figcaption></figure><h3>1) Introduction</h3><p>Monte Carlo methods are among the most powerful tools in modern computational statistics, named after the famous casino in Monaco for their reliance on randomness and probability. At their core, these methods solve a fundamental challenge: how do you work with probability distributions that are too complex to handle directly? Whether you’re trying to generate samples from an unusual distribution, estimate expectations without knowing normalization constants, or explore high-dimensional spaces, Monte Carlo methods provide elegant solutions through strategic random sampling.</p><p>This blog explores three fundamental Monte Carlo sampling techniques that form the backbone of modern ML algorithms: Rejection Sampling, Importance Sampling, and Markov Chain Monte Carlo (MCMC). Through visual explanations and practical examples, we’ll develop both the intuition and mathematical understanding needed to apply these methods effectively.</p><p><em>Note: I have been exploring a new visualization package (created by </em><a href="https://www.3blue1brown.com/"><em>https://www.3blue1brown.com/</em></a><em>)[2] for adding video content to the blogs. (Apologies if you see issues in formatting in the video — I’m still figuring out this library)</em></p><p><em>Also, if you want to explore the code for each of these methods, please check this package [1] which demonstrates simulation.</em></p><h3>2) Rejection Sampling</h3><p>Rejection sampling is a fundamental Monte Carlo method for generating samples from a probability distribution that may be difficult to sample from directly.</p><p>Suppose we want to generate samples from a target distribution p(x), but direct sampling is difficult or impossible. This situation arises frequently when p(x) has a complex form, such as a mixture of distributions or an unnormalized density.</p><p>The key insight is to use a simpler <strong>proposal distribution</strong> q(x) that we <em>can</em> easily sample from, then selectively accept or reject these samples to recover the target distribution.</p><p><strong>The Algorithm</strong></p><ol><li><strong>Choose a proposal distribution</strong> q(x) that is easy to sample from</li><li><strong>Find a constant</strong> M such that M·q(x) ≥ p(x) for all x</li><li><strong>Sample</strong> x from q(x)</li><li><strong>Sample</strong> u uniformly from [0, 1]</li><li><strong>Accept</strong> if u ≤ p(x) / (M·q(x)), otherwise <strong>reject</strong> and repeat</li></ol><p>The acceptance criteria ensures that regions where p(x) is high relative to q(x) accept more samples, correctly reproducing the target distribution.</p><h4>Example 1: Sampling from a Mixture Distribution</h4><ul><li><strong>Target p(x)</strong>: Mixture of two Gaussians (green curve)</li><li><strong>Proposal q(x)</strong>: Simple Gaussian distribution (blue curve)</li><li><strong>Envelope M·q(x)</strong>: Scaled proposal that covers the target (red curve)</li></ul><p>The algorithm samples from the simple Gaussian and accepts/rejects to match the more complex mixture distribution.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/854/1*yEKA2TasmQB6F5HxSIvNhw.gif" /></figure><h4>Example 2: Estimating π</h4><p>Rejection sampling provides an elegant method for estimating π using geometric probability.</p><p><strong>Setup:</strong></p><ul><li><strong>Proposal q(x)</strong>: Square (side = 2r) | Uniform over square</li><li><strong>Target p(x)</strong>: Circle (radius = r) | Uniform over circle</li></ul><p>The acceptance rate directly relates to π:</p><pre>Acceptance Rate = (Area of Circle) / (Area of Square)<br> = (π × r²) / ((2r)²)<br> = π / 4</pre><p>which equates to:</p><pre>π = 4 × Acceptance Rate</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/854/1*XdZEGZpb9qkM-6eNePAhzg.gif" /></figure><h3>3) Importance Sampling</h3><p>Imagine you want to understand <strong>population A</strong>, but you can only access <strong>population B</strong>. Importance sampling lets you learn about A by studying B, as long as you <strong>reweight</strong> your observations appropriately.</p><p>In statistical terms: “I can’t sample from the distribution I care about, so I sample from an easier one and correct for the difference.”</p><p>For e.g., You have a complex target distribution</p><pre>f(x) = exp(-x²) × (1 + sin²(3x))</pre><p>To compute the expectation, you need to <strong>normalize</strong> it:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/366/1*Xt2VNh4wuEThBtUPS_mRUQ.png" /></figure><p>where p(x):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/266/1*_C4Ty8NWOxQKp-ECYxtt5Q.png" /></figure><pre><br>  where Z = ∫ exp(-x²) × (1 + sin²(3x)) dx (from -∞ to +∞)</pre><p><strong>This integral has no closed-form solution!</strong></p><p><strong>Core Idea:</strong> Instead of sampling from the complex f(x), sample from something simple and <strong>re-weight</strong>.</p><p><strong>The Algorithm</strong></p><p>Step 1: Choose a Simple Proposal Distribution</p><pre>q(x) = Normal(0, 1)</pre><p>Step 2: Draw Samples from q(x)</p><p>Step 3: Compute Weights</p><pre>For each sample, compute:<br>weight = f(x) / q(x)<br> = (what target wants) / (what proposal gives)</pre><p>Step 4: Estimate Using Weighted Average</p><pre>E[x] ≈ Σ(weight × x) / Σ(weight)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/854/1*FWryw-pyRRkVCy2yuhsM4w.gif" /></figure><h4><strong>3.1) Why the Normalizing Constant Z Cancels Out?? [Optional]</strong></h4><p>By definition, the expectation of <em>any</em> value or function g(x) under a distribution q(x) is:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/470/1*NgVmFm7t-cB2RSYUwDPPMg.png" /></figure><p>In simple words, to find the average of “stuff” under distribution q(x), you integrate (“sum up”) stuff * q(x).</p><p>We start with the expectation under p(x):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/366/1*Xt2VNh4wuEThBtUPS_mRUQ.png" /></figure><p>We multiply and divide by q(x) (which is just multiplying by 1):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/342/1*dLNCZT5nLgwBYlxUZKLOQg.png" /></figure><p>Now, look at the equation above and group the terms together to match the definition in step 1. Let our “stuff” be g(x) = x (p(x)/q(x)).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/400/1*6GxBm2Ljfyzezusn5VvQ5Q.png" /></figure><p>Since this matches the formstuff * q(x) it is exactly the expectation of “stuff” under q (We basically “tricked” the integral into looking like an expectation for q instead of p):</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/262/1*PDLNmeY7slIW8PkNvKKDKg.png" /></figure><p>If we replace p(x) with f(x)/Z, the numerator becomes:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/402/1*D12RCEyOQhcn0Ah9ysNVeQ.png" /></figure><p><strong>The Denominator :</strong> Next, we estimate the sum of probabilities (which should equal 1). Again, using f(x) instead of p(x) introduces the same factor:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/480/1*TleQBphnMn8QZziN4CYFTg.png" /></figure><p>And since we know:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/166/1*afv5RW_ano4Nkub0vBI9vg.png" /></figure><p>Substituting and canceling the terms, we actually get:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/296/1*feMsuCZoHXFcfa0QPSZMbw.png" /></figure><h3>4) Markov Chain Monte Carlo</h3><p>When both rejection and importance sampling struggle particularly in high dimensions, Markov Chain Monte Carlo (MCMC) provides a solution. Instead of generating independent samples, MCMC builds a Markov chain that gradually converges to the target distribution, exploring even the most complex probability one step at a time <em>(critical for bayesian inference)</em>.</p><p>Let’s say we want to recommend new users on our social media platform — Y (not X). Our platform has over 500M users. We want to recommend friends for you, but comparing you against everyone would take forever.</p><p>We can use <strong>MCMC </strong>to explore the social network smartly with random walk:</p><p><strong>Start</strong> at a random user in the network.</p><p><strong>Repeat</strong> thousands of times:</p><ol><li>Look at a neighbor (friend of current user)</li><li>Calculate their match score (assume each potential friend gets a <strong>match score</strong> based on concrete criteria)</li><li>Decide whether to move:</li></ol><ul><li><strong>Better score?</strong> → Always move</li><li><strong>Worse score?</strong> → Move with probability (new score / current score)</li></ul><p>If we only moved to better users, we might get stuck in one corner of the network. Occasionally accepting worse options lets us discover entirely new regions with potentially great matches.</p><pre>Scoring System<br>`Mixture of 3 Gaussians representing different friend clusters<br># - Cluster 1 (μ=20): Similar Age/Same Location (low match)<br># - Cluster 2 (μ=60): Shared Interests (medium-high match)<br># - Cluster 3 (μ=85): Mutual Friends (high match)</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Ql-rGxS-fGY4d0n3xr1U9A.png" /><figcaption>MCMC simulation using the above 3 gaussian mixture model</figcaption></figure><h4>The Technical Details: The Metropolis-Hastings Algorithm</h4><p>For those interested in the formal implementation, here is how we define the problem mathematically.</p><p>We want to sample from a probability distribution p(x) where:</p><ul><li>Direct sampling is impossible or expensive.</li><li>We can only evaluate p(x) for any given x (up to a normalizing constant).</li><li>The space of possible x values is enormous.</li></ul><p><strong>Goal:</strong> Generate samples x_1, x_2, x_3 etc. such that they are distributed according to p(x).</p><h4>The Algorithm Steps</h4><p>The most common MCMC method constructs a Markov chain whose stationary distribution is our target p(x).</p><p><strong>1. Initialize</strong> Set x_0 to an arbitrary starting point.</p><p><strong>2. Loop (for t = 1, 2, 3, …)</strong></p><p><strong>a. Propose</strong> Draw a candidate x from a proposal distribution q(x | x_{t-1}).</p><p><strong>b. Calculate Acceptance Probability</strong> We determine the probability of accepting the new state:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/732/1*k-GK_qo--6tXuRxn7phraQ.png" /></figure><p><strong>c. Accept or Reject</strong></p><ul><li>Draw u ~ Uniform(0, 1)</li><li><strong>If</strong> u &lt; α: Set x_t = x’ (Accept the new position).</li><li><strong>Else</strong>: set x_t = x_{t-1} (reject) (Reject and stay at the current position).</li></ul><p>The Metropolis Algorithm is essentially a strategy for <strong>exploration vs. exploitation</strong>.</p><ul><li>It <strong>exploits</strong> good results by always moving to better scores.</li><li>It <strong>explores</strong> new possibilities by occasionally taking a risk on a lower score.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/854/1*iw4lamWOgwdeRxB9SCBzbA.gif" /></figure><h3>Conclusion</h3><p>These three Monte Carlo sampling methods form the foundation of modern computational statistics and machine learning:</p><ul><li><strong>Rejection Sampling</strong> provides geometric intuition and works well for simple problems</li><li><strong>Importance Sampling</strong> enables expectation computation without knowing normalization constants</li><li><strong>MCMC</strong> scales to complex, high-dimensional problems where other methods fail</li></ul><h3>References</h3><ol><li>Simulation code package — <a href="https://github.com/mandeep0405/causality/tree/main/distributions">https://github.com/mandeep0405/causality/tree/main/distributions</a></li><li>Vizualization Python package — <a href="https://codeberg.org/ManimCommunity">https://codeberg.org/ManimCommunity</a></li></ol><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=98b3a2b34eb4" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[From P-Values to Probability: A Better Framework for A/B Testing]]></title>
            <link>https://medium.com/@mandeep0405/from-p-values-to-probability-a-better-framework-for-a-b-testing-0c03975adff0?source=rss-e104ac293dae------2</link>
            <guid isPermaLink="false">https://medium.com/p/0c03975adff0</guid>
            <category><![CDATA[probability]]></category>
            <category><![CDATA[ab-testing]]></category>
            <category><![CDATA[bayesian-statistics]]></category>
            <category><![CDATA[experimentation]]></category>
            <dc:creator><![CDATA[Mandeep Singh]]></dc:creator>
            <pubDate>Sat, 27 Dec 2025 06:07:26 GMT</pubDate>
            <atom:updated>2025-12-27T06:07:26.490Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*q-Fdo5swEZjjsQRwmbUfhA.png" /><figcaption>Uncertainty</figcaption></figure><h3>Introduction</h3><p>You are a Product Manager or Data Scientist. You’ve just run an A/B test on a new UI flow (Variant B) against the current one (Variant A). The dashboard says Variant B has a $2 higher Average Sales Per User.</p><p>Do you ship it?</p><p>The answer isn’t “Yes” because the number is higher. The answer depends on uncertainty. Is that $2 lift real, or is it just random noise? If you ship a bad feature, you lose money. If you fail to ship a good one, you leave money on the table.</p><p>Traditional A/B testing methods often obscure this decision-making process with confusing metrics. Today, we’re going to look at a better way: Bayesian A/B Testing.</p><h3>Frequentist Approach — Traditional Method</h3><p>Most industry standard tools use the Frequentist approach (Null Hypothesis Significance Testing). You set up a “Null Hypothesis” (typically: there is no difference between A and B) and try to reject it.</p><p><strong>The Key Metrics:</strong></p><ul><li><strong>P-value</strong>: The probability of seeing data this extreme if the Null were true. (Note: This is NOT the probability that B is better than A).</li><li><strong>Power</strong>: The probability of detecting an effect if one actually exists.</li><li><strong>Confidence Interval</strong>: A range constructed from the data. Here’s what a 95% confidence interval actually means: “If we repeated this experiment 100 times and calculated the interval each time, about 95 of those intervals would contain the true value.” This is NOT the same as saying “there’s a 95% probability the true value is in this specific interval.” The true value either is or isn’t in your observed interval — you just don’t know which.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*c-akwpQJrjDgUqnwTrM_gg.png" /><figcaption>Fig-1: 2 (red lines) out of 20 don’t capture the true mean. This means it doesn’t satisy our 95% constraint</figcaption></figure><p><strong>The Fundamental Limitation</strong></p><p>Here’s the critical issue: The Frequentist approach tells you if a result is <em>statistically significant</em>, but it doesn’t tell you the <em>probability</em> of actually seeing these gains.</p><p>Let’s use a concrete example:</p><ul><li><strong>Variant A (Control)</strong>: 1,000,000 sales</li><li><strong>Variant B (Treatment)</strong>: 1,200,000 sales</li><li><strong>Difference</strong>: 200,000 more sales</li><li><strong>P-value</strong>: 0.03 (&lt; 0.05, so “significant”)</li></ul><p><strong>What the Frequentist method tells you:</strong></p><p>“If there were truly no difference between A and B, the probability of seeing a difference this extreme (200,000 or more) is only 3%.”</p><p>In other words: p &lt; 0.05 means “this difference is unlikely to be just random noise.”</p><p><strong>What you actually want to know:</strong></p><p>“If I deploy Variant B, how likely am I to actually see this 200,000 sales increase? What’s the probability I’ll get these results?”</p><p><strong>The Pain Point: The “Peeking” Problem</strong></p><p>The biggest flaw in Frequentist testing isn’t the math — it’s the rigidity.</p><ul><li>You must calculate a sample size before you start (e.g., “We need 10,000 users”).</li><li>You cannot stop early.</li><li>You cannot peek.</li></ul><p>If you check your results halfway through, see a “significant” p-value, and stop the test, you have committed a statistical sin (p-hacking). Your False Positive rate just skyrocketed.</p><p><strong>Why does this happen?</strong></p><p>Each time you check your results, you’re essentially running a new hypothesis test. With a p-value threshold of 0.05, you have a 5% chance of a false positive on <em>each check</em>. If you peek 10 times during your experiment, your cumulative false positive rate can go over 5%.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RJ6YBiWvGRT_vFCeM3GHeA.png" /><figcaption>Fig-2: Pval distribution at 10 peek point for a sample size of 1000 for 2 Groups — A &amp; B with no differences</figcaption></figure><p>In fig2, we expect 5% FPR if we don’t peek till the end (i.e., 0.05 alpha significance). But if we peek after every 100 samples, we expect FPR to go above 18%.</p><h3>3. The Bayesian Shift</h3><p>Bayesian statistics flips the script. Instead of asking “How rare is this data given the Null?”, it asks the question we actually care about: “Given this data, what is the probability that B is better than A?”</p><p>It works by updating our beliefs as we get more evidence.</p><p><strong>The Core Equation</strong></p><blockquote><strong>Posterior∝Likelihood×Prior</strong></blockquote><ul><li><strong>Prior</strong>: What we believed before seeing data.</li><li><strong>Likelihood</strong>: What the data tells us.</li><li><strong>Posterior</strong>: Our updated belief.</li></ul><h4><strong>3.1) Computing the Posterior: The Normal-Normal Conjugate</strong></h4><p>How do we actually do the math? We use a “Conjugate Prior.” If our Prior is a Normal distribution and our Data is Normal, the Posterior is also mathematically guaranteed to be a Normal distribution.</p><p><strong>The Intuitive Formula:</strong></p><p>The new belief (Posterior Mean) is just a weighted average of your Prior Mean and your Data Mean.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Hnpgnnj1CQ6j_QcUJqPjUQ.png" /></figure><p><strong>How is “Certainty” measured?</strong></p><p>Certainty is measured by <strong>precision</strong>, which is the inverse of variance:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/980/1*VNGd5W9--uW03knIww3ZbA.png" /></figure><p><strong>What this means:</strong></p><ul><li>If you have little data (small n), the Prior pulls the weight (Caution).</li><li>If you have lots of data (large n), the Data pulls the weight (Evidence).</li></ul><h4>3.2 Choosing Your Prior</h4><p>There are two main approaches:</p><p><strong>Uninformative Priors</strong> (When You Know Nothing)</p><ul><li>Use a very flat, wide distribution that minimizes influence on the posterior</li><li>Common approach: Use a power analysis with an assumed effect size of 0.5 (medium effect) to set the variance</li><li>Says “I have no strong beliefs, let the data speak”</li><li>Lets the data dominate quickly</li></ul><p><strong>Informative Priors</strong> (When You Have History)</p><ul><li>Use historical data to set the prior <em>only when your success metric is reliable and stable</em></li><li>Examples of reliable metrics: ARPU from last quarter, conversion rates over the past 6 months, page load times</li></ul><h3>4. Advantages of Bayesian Approach</h3><h4><strong>4.1) Intuitive Interpretation</strong></h4><ul><li><strong>Frequentist</strong>: “There is a 5% chance of observing a difference this large if there were actually no difference.” (Mental gymnastics required).</li><li><strong>Bayesian</strong>: “There is a 97% chance Variant B is better than Variant A.” (Direct answer).</li></ul><p><strong>Posterior Probability of Positive Return (PPR)</strong></p><p>One of the most powerful Bayesian metrics is <strong>PPR</strong> — the probability that your variant produces a positive lift [1].</p><p><strong>Definition</strong>: PPR = Probability(B — A &gt; 0)</p><p>This is calculated by looking at the posterior distribution of the difference between variants and finding the area under the curve where the difference is positive.</p><p><strong>Example: </strong>You run an A/B test on Total Sales($$). After collecting data:</p><ul><li>Variant A: posterior Mean = $50000, StdDev = $15000</li><li>Variant B: posterior Mean = $52000, StdDev = $8000</li><li>The difference (B — A) has distribution: Mean = $1,815</li></ul><p><strong>PPR = 81%</strong> (the probability that B truly generates more revenue than A)</p><p><strong>Please refer to [3] for running the Bayesian AB Simulation.</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/976/1*qa0QF0RE0MxEgiIFgu0gRA.png" /><figcaption>Fig-3: Variant B has 81% probability of beating variant A</figcaption></figure><p>Unlike p-values, PPR directly answers: “How confident am I that this variant is better?” This is the metric business stakeholders intuitively understand.</p><p><strong>Credible Interval (Bayesian)</strong>: [48, 54]</p><ul><li>Interpretation: “Given the data I observed, there is a 95% probability that the true value lies between 48 and 54.”</li><li>This IS a probability statement about where the true value is.</li><li>This is what most people think a confidence interval means!</li></ul><p>Bayesian credible intervals actually mean what we want them to mean: a direct probability statement about where the true value lies.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5LF59h1cTSGe7THMvXWG_A.png" /><figcaption>Fig-4: 95% Credible Interval for both variants</figcaption></figure><h4><strong>4.2. Small Sample Efficiency</strong></h4><p>Bayesian methods excel when data is scarce. By incorporating a Prior (even a weak one), you prevent wild outliers from misleading you early in the test.</p><h4><strong>4.3) Continuous Monitoring</strong></h4><p>This is the game-changer. In Bayesian testing, “peeking” is valid.</p><p>You can check the probability every morning.</p><ul><li>Day 1: 60% chance B is better. (Keep running).</li><li>Day 2: 80% chance B is better. (Keep running).</li><li>Day 3: 99% chance B is better. (Stop and Ship!)</li></ul><p>You don’t need to commit to a fixed sample size upfront. You stop when you have enough evidence to make a decision.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6x65kpgeHX3LA96cqwfT4w.png" /><figcaption>Fig-5: Peeking is Allowed</figcaption></figure><h3>References</h3><ol><li><a href="https://www.youtube.com/watch?v=3xAIWiTJCvE&amp;t=2s">Cumulative Density Function</a></li><li><a href="https://en.wikipedia.org/wiki/Conjugate_prior">Conjugate Prior</a>s: A <strong>Conjugate Prior</strong> happens when your <strong>Prior</strong> and your <strong>Posterior</strong> end up being in the <strong>same family of distributions</strong>.</li><li><a href="https://github.com/mandeep0405/causality/tree/main/bayesian_testing">Bayesian AB Testing Simulation Code</a></li></ol><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0c03975adff0" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Gated Attention: Solving the Hidden Bottlenecks in Transformer Attention]]></title>
            <link>https://medium.com/@mandeep0405/gated-attention-solving-the-hidden-bottlenecks-in-transformer-attention-685867a24779?source=rss-e104ac293dae------2</link>
            <guid isPermaLink="false">https://medium.com/p/685867a24779</guid>
            <category><![CDATA[transformers]]></category>
            <category><![CDATA[llm]]></category>
            <category><![CDATA[attention]]></category>
            <category><![CDATA[deep-learning]]></category>
            <dc:creator><![CDATA[Mandeep Singh]]></dc:creator>
            <pubDate>Sun, 14 Dec 2025 02:52:12 GMT</pubDate>
            <atom:updated>2025-12-14T02:52:34.818Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8Cou9YSe5pVTje0Ne-28YQ.png" /></figure><h3>1) Introduction</h3><p>Multi-head attention has been the cornerstone of transformer architectures since “Attention is All You Need” [1]. Yet despite its success, standard softmax attention harbors subtle but significant inefficiencies that become pronounced at scale. Addressing these fundamental limitations is the focus of <em>“Gated Attention for Large Language Models,”</em> a breakthrough work by the Qwen Team that was recently recognized as a <strong>Best Paper at NeurIPS 2025[4]</strong>.</p><h4><strong>1.1) Problem 1: The Low-Rank Bottleneck</strong></h4><p>Imagine you have a beautiful, massive 4K photograph. This represents your input x with <strong>2048 dimensions</strong> of rich detail.</p><p><strong>Step 1 (</strong>x .W_V<strong>):</strong> You force this 4K image through a compressor that shrinks it down to a tiny <strong>64-pixel thumbnail</strong>.</p><ul><li><em>Math equivalent:</em> Projecting from 2048 dimensions down to 64 dimensions.</li></ul><p><strong>Step 2 (</strong>W_O<strong>):</strong> You try to blow that 64-pixel thumbnail back up to a 4K resolution image.</p><ul><li><em>Math equivalent:</em> Projecting from 64 dimensions back up to 2048 dimensions.</li></ul><p><strong>The Bottleneck:</strong> Even though your final output is technically a “4K image” (it has 2048x2048 pixels), it is <strong>blurry and blocky</strong>. You cannot magically recover the details of the original tree leaves or skin textures because they were discarded when you shrank it to 64 pixels.</p><p>The choice of low-rank heads was a deliberate trade-off to enable computational efficiency and parallel specialization, effectively creating a team of “experts” i.e., attention heads where each focuses on a specific feature subset (like grammar or tone) without exploding costs.</p><p><strong>Step-by-step transformation for “attention”:</strong></p><p>For a model with:</p><ul><li>Model dimension: d_model = 2048</li><li>Number of heads: h = 32</li><li>Head dimension: d_k = 64 (typically d_model / h)</li></ul><pre>Input token representation:        x ∈ ℝ^2048<br><br>Step 1 - Value projection:<br>v = x · W_V^i                      Result: v ∈ ℝ^64<br>    ↑        ↑<br>  (2048)  (2048×64)<br><br>Step 2 - Attention weighting:<br>attention_output = attention_weights · v   Result: still ℝ^64<br>                   (This is the SDPA output)<br><br>Step 3 - Output projection:  <br>o = attention_output · W_O^i       Result: o ∈ ℝ^2048<br>    ↑                   ↑<br>   (64)            (64×2048)</pre><p><strong>This is where bottleneck occurs:</strong></p><pre> Combined transformation (for one head):<br>o = attention_weights · (x · W_V^i) · W_O^i<br>  = attention_weights · x · (W_V^i · W_O^i)<br>  = attention_weights · x · W_merged^i<br><br>Where:<br>W_merged^i = W_V^i · W_O^i<br>           = (2048×64) · (64×2048)  <br>           = 2048×2048 matrix<br><br>BUT: rank(W_merged^i) ≤ min(rank(W_V^i), rank(W_O^i))<br>                      ≤ 64  ← Bottleneck!</pre><h4><strong>1.2) Problem 2: The Attention Sink Phenomenon</strong></h4><p>When processing long sequences, transformer models develop a peculiar behavior: they allocate disproportionate attention to early tokens, particularly the first token “[<strong>BOS]”</strong>, even when these tokens are irrelevant to the current prediction.</p><blockquote>Input: “In 1887, construction of the Eiffel Tower began… [500 words of history]… <br> Today, the tower is located in.”</blockquote><blockquote>When predicting “Paris”:</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fA8_Hp9Wzt9VWLHr3bFY5g.png" /></figure><p><strong>Why Does This Happen? The Softmax Constraint Problem</strong></p><p>Softmax attention has two fundamental constraints:</p><ol><li>All weights must be <strong>non-negative</strong> (due to exponential in softmax)</li><li>All weights must <strong>sum to 1.0</strong> (due to normalization)</li></ol><p>When most context is irrelevant, the model would ideally assign near-zero or negative attention to those tokens. But softmax doesn’t allow this.</p><p><strong>Why the First Token Becomes the Sink</strong></p><p>The first token (often the Beginning of Sentence, or BOS) becomes the designated “attention sink” primarily because it is <strong>semantically empty</strong>, making it a “safe” dumping ground for excess attention weights that would otherwise corrupt the representation of meaningful tokens. Additionally, due to the causal masking in transformer architectures, the first token is <strong>universally visible</strong> to every subsequent token in the sequence, making it the most reliable and consistent focal point for the model to discover and utilize during training (a <strong>learned optimization</strong>) to satisfy the softmax constraint that all attention weights must sum to 1.</p><h3>2) How Gated Attention Solves These Problems</h3><p>Recent work from Alibaba’s Qwen Team introduces a deceptively simple modification: <strong>add a learnable sigmoid gate after the attention computation</strong>.</p><pre>Step 1: Compute attention weights (same)<br>        attention_weights = softmax(Q·K^T / √d_k)<br>        Shape: (seq_len, seq_len)<br><br>Step 2: Apply attention to values (SDPA output) (same)<br>        sdpa_output = attention_weights @ V<br>        Shape: (seq_len, d_k)<br><br>Step 2.5: ← NEW! Compute gate and apply it<br>        gate_scores = sigmoid(X @ W_gate)<br>        Shape: (seq_len, d_k)<br>        <br>        gated_sdpa_output = sdpa_output ⊙ gate_scores  ← Element-wise multiplication<br>        Shape: (seq_len, d_k)<br><br>Step 3: (After concatenating all heads) Project with W_o<br>        output = gated_sdpa_output @ W_o<br>        Shape: (seq_len, d_model)</pre><p>At a high level, this is a model architecture with the new “gated attention” mechanism:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*z_kyJLQNuYm2RigtLejoUw.png" /><figcaption>Gated Attention HLD (courtesy gemini)</figcaption></figure><h4>2.1) Breaking the Low-Rank Bottleneck with Non-Linearity</h4><p>The sigmoid gate <strong>cannot be factored out or merged</strong> with the linear projections. This breaks the low-rank limitation and increases expressiveness.</p><p><strong>Without gating: </strong>the “basis vectors” (the directions in space the model can see) are <strong>fixed</strong>. It’s like having a flashlight that is welded to a table; it can only ever illuminate the exact same spot on the wall.</p><pre>output = (attention_weights·V)·W_o<br>       = attention_weights·(V·W_o)    ← Two linear layers collapse<br>       = attention_weights·W_merged   ← Low-rank bottleneck</pre><p><strong>With gating: </strong>The gate is calculated from the input itself. This means for every different input sentence, the gate changes.</p><ul><li><strong>Input A:</strong> The flashlight rotates left (illuminates “grammar”).</li><li><strong>Input B:</strong> The flashlight rotates right (illuminates “tone”).</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bTVOBWXBrTEjIv0LMZMwKA.png" /></figure><p>Even though the beam is still narrow (Rank 64), because it can <em>move</em> (rotate) based on what it’s looking at, the total area it can cover over time is the entire room (Rank 2048).</p><pre>output = (attention_weights·V ⊙ sigmoid(X·W_gate))·W_o<br>                              ↑<br>                        Non-linear gate!</pre><h4>2.2) Eliminating Attention Sinks with Sparse, Query-Dependent Gating</h4><p>Gated Attention solves this by adding a <strong>Sigmoid Gate</strong> <em>after</em> the Softmax output.</p><ul><li><strong>Decoupling:</strong> While Softmax forces a sum of 1.0, the Sigmoid gate operates element-wise with a range of [0, 1].</li><li><strong>Filtering:</strong> The gate is <strong>sparse</strong> (mostly near 0) and <strong>query-dependent</strong>. It can recognize that the “dumped” attention on the first token is irrelevant to the current query.</li><li><strong>The Fix:</strong> Even if Softmax assigns a high score (e.g., 45%) to the first token, the gate can assign it a near-zero weight (e.g., 0.01), effectively suppressing the noise. This allows the model to selectively “turn off” the attention sink and focus only on relevant tokens, bypassing the Softmax sum-to-one constraint at the output level.</li></ul><p><strong>Real Example: Document Summarization</strong></p><blockquote>Consider this scenario:</blockquote><blockquote>[BOS] The history of artificial intelligence dates to ancient times… <br>[480 tokens of historical background]…<br>In conclusion, modern AI impacts three key areas: healthcare, education, <br>and climate science.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Jn57K7F4U3HoNJiRfk-Miw.png" /></figure><h3>3) Demonstration</h3><p>I have included dummy implementation of “Gated Attention” for demonstration purpose. Please refer to this for more details:</p><p><a href="https://github.com/mandeep0405/llm-tutorials/tree/main/gated_attention">https://github.com/mandeep0405/llm-tutorials/tree/main/gated_attention</a></p><pre>from gated_attention import GatedAttentionHead, MultiHeadGatedAttention<br><br># Single-head gated attention<br>d_model = 512<br>d_k = 64<br>gated_head = GatedAttentionHead(d_model, d_k)<br><br># Forward pass<br>x = torch.randn(batch_size, seq_len, d_model)<br>output = gated_head(x)<br><br># Multi-head gated attention<br>num_heads = 8<br>gated_mha = MultiHeadGatedAttention(d_model, num_heads)<br>output = gated_mha(x)<br><br># With gate statistics<br>output, gate_stats = gated_mha(x, return_gate_stats=True)<br>print(f&quot;Mean gate score: {gate_stats[&#39;mean&#39;]:.4f}&quot;)<br>print(f&quot;Gate sparsity: {gate_stats[&#39;sparsity&#39;]*100:.1f}%&quot;)</pre><p><strong>Attention Distribution for Last Token — Standard Vs Gated Attention</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nepUW-PivscB2foMNwNqFw.png" /><figcaption>Fig 1: Last Token Attention Distribution</figcaption></figure><ul><li><strong>Standard Attention (Left):</strong> The tall bar at position 0 (BOS token) vividly shows how a standard model “dumps” excess attention (approx 4.0%) onto the first token, even though it’s likely irrelevant to the last token.</li><li><strong>Gated Attention (Right):</strong> The corresponding bar at position 0 is significantly shorter (approx 2.1%) with gated attention.</li></ul><p><strong>Gate Score/Final Attention Weight Distribution by Query Position</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*bObxW-toyUDH9QizAB_6IA.png" /><figcaption>Fig 2: Gate Score Distributons Vs Query Position</figcaption></figure><ul><li><strong>Standard Attention Weights (Top Left):</strong> Shows the typical triangular causal mask pattern.</li><li><strong>Mean Gate Score per Query Position (Top Right):</strong> Demonstrating the <strong>query-dependent</strong> nature of the mechanism. The orange shaded area represents the “allowed” signal, while the white space above represents suppression. Note, if trained <em>(this is random)</em>, as according to the paper, it goes to 0.1–0.2 range.</li><li><strong>Attention to Token 0 [BOS] (Bottom Left):</strong> This line graph tracks the attention sink over time. The red line (Standard) starts very high (1.0) and remains consistently higher than the blue line (Gated).</li><li><strong>Gate Scores Heatmap (Bottom Right):</strong> This reveals the <strong>sparsity</strong> and <strong>granularity</strong>. The heatmap is “patchy,” meaning the gate is selectively turning features on and off for specific tokens.</li></ul><h3>Conclusions</h3><p>While the theoretical elegance of breaking low-rank bottlenecks and removing attention sinks is compelling, the real verdict comes from the benchmarks. Authors performed testing on LLMs trained on 3.5 trillion tokens to prove that Gated Attention is not just a stability fix — it is a significant performance upgrade. The gated models achieved a <strong>perplexity reduction of over 0.2 </strong>and<strong> </strong>downstream performance surged, with <strong>MMLU scores increasing by ~2.0 points</strong> and GSM8k math reasoning scores jumping by <strong>over 2.3 points</strong> compared to the baseline.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*OmlHDnYcqVhMcnZ0ai1J5g.png" /></figure><h3>References</h3><ol><li><a href="https://arxiv.org/pdf/1706.03762">Attention is all you need</a></li><li><a href="https://arxiv.org/pdf/2505.06708">Gated Attention</a></li><li><a href="https://github.com/mandeep0405/llm-tutorials/tree/main/gated_attention">Implementation Code</a></li><li><a href="https://neurips.cc/virtual/2025/awards_detail">https://neurips.cc/virtual/2025/awards_detail</a></li></ol><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=685867a24779" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Yet Another PyTorch Tutorial]]></title>
            <link>https://medium.com/@mandeep0405/yet-another-pytorch-tutorial-7d1cfbdae362?source=rss-e104ac293dae------2</link>
            <guid isPermaLink="false">https://medium.com/p/7d1cfbdae362</guid>
            <category><![CDATA[deep-learning]]></category>
            <category><![CDATA[pytorch]]></category>
            <category><![CDATA[gradient-descent]]></category>
            <category><![CDATA[neural-networks]]></category>
            <dc:creator><![CDATA[Mandeep Singh]]></dc:creator>
            <pubDate>Wed, 03 Dec 2025 02:46:44 GMT</pubDate>
            <atom:updated>2025-12-03T02:46:44.418Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/914/1*9qCXN0Fbqx5wkLNUK-uYlQ.png" /><figcaption>PyTorch</figcaption></figure><h3>Introduction</h3><p>You’ve probably used PyTorch hundreds of times — writing output.backward() without a second thought, broadcasting arrays like magic, and watching gradients computed itself across your training loops. But have you ever wondered what&#39;s actually happening under the hood? This tutorial peels back the curtain on PyTorch&#39;s internals, walking through how tensors store data in memory, how automatic differentiation builds and traverses computational graphs, and why gradient accumulation works the way it does!!!</p><h3><strong>1) Basics about Tensors?</strong></h3><p>At the bedrock of the PyTorch library lies the torch.Tensor. Unlike a standard array, it is a view over a storage block engineered for deep learning. Today, a torch.Tensor wears three hats simultaneously:</p><ol><li><strong>The View (Metadata):</strong> Describes <em>how</em> to look at the data (shape, stride).</li><li><strong>The Storage (Data):</strong> The actual raw memory pointer.</li><li><strong>The Autograd Node (Graph):</strong> Pointers to the computational graph.</li></ol><h4>1.1) Memory Layout &amp; Strides</h4><p>A tensor is essentially a view over a storage block — a contiguous chunk of memory holding the raw numerical data. The stride attribute defines how to traverse this memory. Strides describe the number of steps needed in the flat storage array to move one index along a dimension.</p><pre>import torch<br><br># Create a 2D tensor (3 rows, 4 columns)<br>x = torch.arange(12).reshape(3, 4)<br>print(f&quot;Shape: {x.shape}&quot;)   # torch.Size()<br>print(f&quot;Stride: {x.stride()}&quot;) # (4, 1)<br># Explanation: To move down one row, skip 4 elements. To move right one column, skip 1.<br><br># Transpose the tensor<br>y = x.t()<br>print(f&quot;Shape: {y.shape}&quot;)   # torch.Size([4, 3])<br>print(f&quot;Stride: {y.stride()}&quot;) # (1, 4)<br># Explanation: The shapes are swapped, but crucially, the strides are swapped too.<br># PyTorch did NOT copy data. It just changed how it reads memory.</pre><p><strong>Memory Efficiency:</strong> Operations like transpose, slice, and view can create new tensor “views” without copying data. They just change the strides!</p><pre>x = torch.randn(4, 4)<br>x_t = x.t()  # Almost instant, no copy!<br>x_reorder = x.view(2,8) # no copy!</pre><h4>1.2) The Mechanics of Broadcasting</h4><p>Broadcasting is a mechanism that allows PyTorch to perform operations on tensors of different shapes without explicitly copying data. It automatically expands smaller tensors to match the shape of larger ones during arithmetic operations.</p><p>PyTorch checks dimensions <strong>from right to left</strong>, and each pair of dimensions must either:</p><ul><li>Be the same number, OR</li><li>One of them is 1 (which can stretch)</li></ul><pre># Example 1: Scalar with tensor<br>a = torch.tensor([1, 2, 3])<br>b = 5<br>result = a + b  # [6, 7, 8]<br># 5 broadcasts to [5, 5, 5]<br><br># Example 2: 1D with 2D<br>a = torch.tensor([[1, 2, 3],<br>                  [4, 5, 6]])  # shape: (2, 3)<br>b = torch.tensor([10, 20, 30])  # shape: (3,)<br>result = a + b<br># b broadcasts to [[10, 20, 30],<br>#                  [10, 20, 30]]<br># Result: [[11, 22, 33],<br>#          [14, 25, 36]]<br><br># Example 3: Column vector with row vector<br>a = torch.tensor([[1],<br>                  [2],<br>                  [3]])  # shape: (3, 1)<br>b = torch.tensor([[10, 20, 30]])  # shape: (1, 3)<br>result = a + b  # shape: (3, 3)<br># Result: [[11, 21, 31],<br>#          [12, 22, 32],<br>#          [13, 23, 33]]</pre><p><strong>Illusion of Copying Data:</strong> The interesting thing is that it never copies the data when it try to stretch the smaller tensor. It just reads the same data multiple times!!!</p><pre>A = torch.randn(3, 4)<br>B = torch.tensor([[999]])<br><br># Method 1: Direct broadcasting (no intermediate tensor) Broadcasts B (1,1) -&gt; (3,4)<br>result1 = A + B<br><br># Method 2: Manual expand (creates a view with zero strides)<br># Pytorch knows that it needs to read the same data multiple times<br>result2 = A + B.expand(3, 4)<br><br># Method 3: Full copy (wasteful) This doesn&#39;t happens!!<br>result3 = A + B.expand(3, 4).clone()<br><br>print(torch.equal(result1, result2))  # True<br>print(torch.equal(result1, result3))  # True<br><br># But check what tensors exist in memory:<br>print(B.data_ptr())                    # Address X (1174703488)<br>print(B.expand(3, 4).data_ptr())       # Address X (1174703488)<br>print(B.expand(3, 4).clone().data_ptr()) # Address Y (1174437120)</pre><h3>2) Magic of AutoGrad</h3><p>AutoGrad is PyTorch’s automatic differentiation engine. It’s the reason you don’t have to manually compute gradients for back-propagation.</p><p><strong>Core Concept:</strong> As your forward pass executes, PyTorch builds a dynamic “tape” (computational graph) recording every operation. During backward(), it plays this tape in reverse to compute gradients.</p><p>The key insight is that <strong>PyTorch doesn’t compute derivatives symbolically at runtime</strong>. Instead, it uses a clever two-phase approach:</p><p><strong>Phase 1: Forward Pass — Building the Computational Graph</strong></p><p>During the forward pass, PyTorch does two things simultaneously:</p><ol><li><strong>Computes the result</strong> (the actual forward calculation)</li><li><strong>Records what operation was used</strong> and stores a reference to the appropriate backward function</li></ol><p>Here’s what happens under the hood when you write y = x ** 2:</p><pre># What you write:<br>y = x ** 2<br><br># What PyTorch actually does:<br>result_data = x.data ** 2  # Compute the result<br>grad_fn = PowBackward(input=x, exponent=2)  # Store backward operation<br>y = Tensor(data=result_data, grad_fn=grad_fn)  # New tensor with link</pre><p><strong>The crucial part:</strong> PowBackward is not computing anything yet. It&#39;s just storing:</p><ul><li>A reference to the input tensor x</li><li>The exponent value 2</li><li>A pre-written backward() method that knows the power rule</li></ul><p><strong>Phase 2: Backward Pass — Applying Pre-Computed Formulas</strong></p><p>When you call .backward(), PyTorch traverses the computational graph in reverse, and each operation applies its <strong>pre-computed gradient formula</strong>.</p><p>For the power operation, the gradient formula is hard-coded<em> (Note: Actually it’s written in C++ and not in python — this is only for demonstration)</em>:</p><pre>class PowBackward:<br>    &quot;&quot;&quot;Backward pass for power: z = a ** n<br><br>    Gradient:<br>        dL/da = dL/dz * dz/da = dL/dz * n * a^(n-1)<br>    &quot;&quot;&quot;<br><br>    def __init__(self, input_tensor: &#39;Tensor&#39;, exponent: float) -&gt; None:<br>        self.input_tensor = input_tensor<br>        self.exponent = exponent<br>        self.name = &quot;PowBackward&quot;<br><br>    def backward(self, grad_output: NDArray) -&gt; None:<br>        # Power rule: multiply by exponent and reduce power by 1<br>        if self.input_tensor.requires_grad:<br>            grad = grad_output * self.exponent * (self.input_tensor.data ** (self.exponent - 1))<br>            self.input_tensor.backward(grad)</pre><p>The derivative formula n * x^(n-1) is <strong>already known</strong> and <strong>hard-coded</strong> into the PowBackward class. PyTorch isn&#39;t deriving this formula—it&#39;s applying it!</p><p>Let’s trace through a concrete example to see this in action.</p><pre>x = Tensor(2.0, requires_grad=True)<br>w1 = Tensor(3.0, requires_grad=True)<br>w2 = Tensor(4.0, requires_grad=True)<br>b = Tensor(1.0, requires_grad=True)<br><br># Forward pass: builds the graph<br>output = (x * w1 + x * w2 + b) ** 2</pre><p><strong>Computational Graph:</strong></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7iYYUIestCxD5L-L1uv8gQ.png" /></figure><p>Each node stores its specific backward operation:</p><ul><li>[×] nodes → MulBackward</li><li>[+] nodes → AddBackward</li><li>[^2] node → PowBackward</li></ul><p><strong>Backward Pass:</strong></p><pre>output.backward()  # Start with grad = 1.0<br><br># PowBackward at output:<br>#   ∂output/∂t3 = 2 * t3^1 = 2 * 15 = 30<br>#   Pass grad=30.0 to t3<br><br># AddBackward at t3:<br>#   ∂t3/∂t2 = 1, ∂t3/∂b = 1<br>#   Pass grad=30.0 to both t2 and b<br>#   → b.grad = 30.0<br><br># AddBackward at t2:<br>#   ∂t2/∂t1 = 1, ∂t2/∂t4 = 1<br>#   Pass grad=30.0 to both t1 and t4<br><br># MulBackward at t1:<br>#   ∂t1/∂x = w1 = 3, ∂t1/∂w1 = x = 2<br>#   Pass grad=30*3=90 to x, grad=30*2=60 to w1<br>#   → x.grad += 90, w1.grad = 60<br><br># MulBackward at t4:<br>#   ∂t4/∂x = w2 = 4, ∂t4/∂w2 = x = 2<br>#   Pass grad=30*4=120 to x, grad=30*2=60 to w2<br>#   → x.grad += 120, w2.grad = 60<br><br># Final gradients:<br># x.grad = 90 + 120 = 210.0  (gradient accumulated!)<br># w1.grad = 60.0<br># w2.grad = 60.0<br># b.grad = 30.0</pre><p>For demonstration purpose, I implemented key differentiation methods in python. Please refer [3] for more details.</p><h3>3) Gradient Accumulation</h3><p>One of the most important — and often misunderstood — aspects of PyTorch’s autograd is <strong>gradient accumulation</strong>. Understanding when we should accumulate gradients is crucial for correct training.</p><ol><li>If your neural architecture has multiple losses, then we are actually doing gradient accumulation. They all <strong>backpropagate through the same neural network weights</strong></li></ol><pre>model: Input → [Layer1: W1, b1] → [Layer2: W2, b2] → Output<br><br># Multiple losses<br>loss1_classification = CrossEntropyLoss(output, target)<br>loss2_regularization = L2Penalty(W1, W2)  <br>loss3_auxiliary = AuxiliaryTask(hidden_layer)<br># Combine into total loss<br>total_loss = loss1 + loss2 + loss3<br><br># Single backward pass<br>total_loss.backward()  </pre><p>Under the hood, PyTorch is doing this:</p><pre># Initialize gradients to zero<br>optimizer.zero_grad()<br><br># Compute each loss<br>loss1 = classification_loss(output, target)<br>loss2 = regularization_loss(W1, W2)<br>loss3 = auxiliary_loss(hidden)<br><br># Call backward on each loss separately<br>loss1.backward()          # w.grad = 5<br>loss2.backward()          # w.grad = 5 + 0.04 = 5.04<br>loss3.backward()          # w.grad = 5.04 + 2 = 7.04<br><br># Update weights using accumulated gradient<br>optimizer.step()          # Update w using 7.04</pre><p>2. If we need a larger batch size (32) but due to resource constraint we can only support smaller size (8). In this case, we will accumulate gradient for batches.</p><pre># Simulate batch size of 32 with only 8 at a time<br>accumulation_steps = 4<br>optimizer.zero_grad()<br><br>for i, (X_batch, y_batch) in enumerate(dataloader):<br>    # Forward pass<br>    output = model(X_batch)<br>    loss = criterion(output, y_batch)<br>    <br>    # Backward pass (accumulates gradients)<br>    loss.backward()<br>    <br>    # Update only every 4 batches<br>    if (i + 1) % accumulation_steps == 0:<br>        optimizer.step()<br>        optimizer.zero_grad() </pre><h3>References</h3><ol><li><a href="https://docs.pytorch.org/tutorials/beginner/basics/intro.html">https://docs.pytorch.org/tutorials/beginner/basics/intro.html</a></li><li><a href="https://sebastianraschka.com/teaching/pytorch-1h/">https://sebastianraschka.com/teaching/pytorch-1h/</a></li><li><a href="https://github.com/mandeep0405/pytorch-autograd-tutorial/blob/main/README.md">https://github.com/mandeep0405/pytorch-autograd-tutorial/blob/main/README.md</a></li></ol><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7d1cfbdae362" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>