<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Grouped-Query Attention on Synaptic Radio</title><link>https://synapticradio.com/tags/grouped-query-attention/</link><description>Recent content in Grouped-Query Attention on Synaptic Radio</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><managingEditor>anshuman264@gmail.com (Anshuman Sahoo)</managingEditor><webMaster>anshuman264@gmail.com (Anshuman Sahoo)</webMaster><copyright>Anshuman Sahoo</copyright><lastBuildDate>Sat, 18 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://synapticradio.com/tags/grouped-query-attention/index.xml" rel="self" type="application/rss+xml"/><item><title>Tensor-Parallel GQA Attention, One Head Group at a Time</title><link>https://synapticradio.com/post/tensor-parallel-gqa-attention/</link><pubDate>Thu, 16 Jul 2026 00:00:00 +0000</pubDate><author>anshuman264@gmail.com (Anshuman Sahoo)</author><guid>https://synapticradio.com/post/tensor-parallel-gqa-attention/</guid><description>&lt;img src="https://synapticradio.com/images/tensor-parallel-gqa-attention/cover.svg" alt="Featured image of post Tensor-Parallel GQA Attention, One Head Group at a Time" />&lt;p>Grouped-query attention creates a partitioning problem that ordinary multi-head attention can hide. There are more query heads than key/value heads, so several query heads reuse the same KV head. If a tensor-parallel split cuts projection matrices at an arbitrary width, it can separate a query head from the KV head it is supposed to share.&lt;/p>
&lt;p>I wanted to reconstruct the smallest attention block that still preserves that constraint. The reference is a pre-RMSNorm, causal GQA layer with rotary position embeddings, four query heads, two KV heads, and a residual connection. I then turn it into two rank-local attention paths in visible steps and compare the resulting training update against the dense block.&lt;/p>
&lt;p>The result is narrow but useful: &lt;strong>the natural partition is a complete query/KV group, not an arbitrary matrix slice&lt;/strong>. The forward pass also needs an output reduction, while the replicated input path needs a separate backward reduction. A forward-only test catches the first bug and misses the second.&lt;/p>
&lt;h2 id="start-with-the-dense-block">Start with the dense block
&lt;/h2>&lt;p>The dense reference is short enough to inspect:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">apply_rope&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> x[..., :half] ─┐
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> ├─ rotate by position/frequency phase ── RoPE(x)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> x[..., half:] ─┘
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># x: [S,B,H,D], D even&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">s&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">shape&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="mi">0&lt;/span>&lt;span class="p">]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">half&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">shape&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">//&lt;/span> &lt;span class="mi">2&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">pos&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">arange&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dtype&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dtype&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">device&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">device&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">view&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">s&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">freq&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">arange&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">half&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dtype&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">dtype&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">device&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">device&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">view&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">half&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">theta&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">pos&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mf">10000.0&lt;/span> &lt;span class="o">**&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">freq&lt;/span> &lt;span class="o">/&lt;/span> &lt;span class="nb">max&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">half&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">c&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">si&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">theta&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cos&lt;/span>&lt;span class="p">(),&lt;/span> &lt;span class="n">theta&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">sin&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">a&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">b&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="p">:&lt;/span>&lt;span class="n">half&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="o">...&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">half&lt;/span>&lt;span class="p">:]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">cat&lt;/span>&lt;span class="p">([&lt;/span>&lt;span class="n">a&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">c&lt;/span> &lt;span class="o">-&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">si&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">a&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">si&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">b&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">c&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">dim&lt;/span>&lt;span class="o">=-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">repeat_kv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">groups&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> KV heads [S,B,Hkv,D] ── repeat each head `groups` times ── [S,B,Hq,D]
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">x&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">repeat_interleave&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">groups&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">dim&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">def&lt;/span> &lt;span class="nf">dense_forward&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">cfg&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> x ── RMSNorm ── Q,K,V ── RoPE ── repeat KV ── attention ── output proj ─┐
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> └──────────────────────────────────────── residual ─────────────────── (+)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">residual&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">x&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">n&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">rms_norm&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;norm&amp;#39;&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="n">cfg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">eps&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">q&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">F&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">linear&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;wq&amp;#39;&lt;/span>&lt;span class="p">])&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">view&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cfg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">seq&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">cfg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">batch&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">cfg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">query_heads&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">cfg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">head_dim&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">k&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">F&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">linear&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;wk&amp;#39;&lt;/span>&lt;span class="p">])&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">view&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cfg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">seq&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">cfg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">batch&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">cfg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">kv_heads&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">cfg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">head_dim&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">v&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">F&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">linear&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;wv&amp;#39;&lt;/span>&lt;span class="p">])&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">view&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cfg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">seq&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">cfg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">batch&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">cfg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">kv_heads&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">cfg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">head_dim&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">q&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">k&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">apply_rope&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">q&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">apply_rope&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">k&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">groups&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">cfg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">query_heads&lt;/span> &lt;span class="o">//&lt;/span> &lt;span class="n">cfg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">kv_heads&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">ctx&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">attention&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">q&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">repeat_kv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">k&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">groups&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">repeat_kv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">v&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">groups&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">return&lt;/span> &lt;span class="n">residual&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">F&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">linear&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ctx&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">reshape&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">cfg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">seq&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="n">cfg&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">batch&lt;/span>&lt;span class="p">,&lt;/span>&lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">),&lt;/span> &lt;span class="n">p&lt;/span>&lt;span class="p">[&lt;/span>&lt;span class="s1">&amp;#39;wo&amp;#39;&lt;/span>&lt;span class="p">])&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The model dimensions are intentionally tiny, but the semantic axes are real:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Tensor&lt;/th>
&lt;th style="text-align: right">Shape&lt;/th>
&lt;th>Meaning&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Input &lt;code>x&lt;/code>&lt;/td>
&lt;td style="text-align: right">&lt;code>[5, 2, 16]&lt;/code>&lt;/td>
&lt;td>sequence, batch, hidden&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Query &lt;code>q&lt;/code>&lt;/td>
&lt;td style="text-align: right">&lt;code>[5, 2, 4, 4]&lt;/code>&lt;/td>
&lt;td>four query heads&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Key/value &lt;code>k&lt;/code>, &lt;code>v&lt;/code>&lt;/td>
&lt;td style="text-align: right">&lt;code>[5, 2, 2, 4]&lt;/code>&lt;/td>
&lt;td>two KV heads&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Repeated KV&lt;/td>
&lt;td style="text-align: right">&lt;code>[5, 2, 4, 4]&lt;/code>&lt;/td>
&lt;td>one KV head per two query heads&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Context&lt;/td>
&lt;td style="text-align: right">&lt;code>[5, 2, 4, 4]&lt;/code>&lt;/td>
&lt;td>one context vector per query head&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Output&lt;/td>
&lt;td style="text-align: right">&lt;code>[5, 2, 16]&lt;/code>&lt;/td>
&lt;td>residual-width result&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The first terminal command records the actual fixture and versions:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">PYTHONPATH&lt;/span>&lt;span class="o">=&lt;/span>code python code/tp_gqa_attention.py --mode inspect
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;python&amp;#34;: &amp;#34;3.13.5&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;pytorch&amp;#34;: &amp;#34;2.10.0+cpu&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;config&amp;#34;: {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;seq&amp;#34;: 5,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;batch&amp;#34;: 2,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;hidden&amp;#34;: 16,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;query_heads&amp;#34;: 4,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;kv_heads&amp;#34;: 2,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;head_dim&amp;#34;: 4,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;world_size&amp;#34;: 2,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;eps&amp;#34;: 1e-06
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;dense_shapes&amp;#34;: {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;x&amp;#34;: [5, 2, 16],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;q&amp;#34;: [5, 2, 4, 4],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;k_v&amp;#34;: [5, 2, 2, 4]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;rank_local&amp;#34;: {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;query_heads&amp;#34;: 2,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;kv_heads&amp;#34;: 1
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> },
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;rule&amp;#34;: &amp;#34;keep complete query/KV groups on each rank; sum output-projection partials&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The full transcript is retained in &lt;a class="link" href="data/terminal-01-inspect.txt" >&lt;code>data/terminal-01-inspect.txt&lt;/code>&lt;/a>, and the executable reference is in &lt;a class="link" href="code/tp_gqa_attention.py" >&lt;code>code/tp_gqa_attention.py&lt;/code>&lt;/a>.&lt;/p>
&lt;h2 id="first-change-split-complete-head-groups">First change: split complete head groups
&lt;/h2>&lt;p>Each KV head is shared by exactly two query heads. That gives two indivisible groups:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">group 0: query heads 0, 1 + KV head 0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">group 1: query heads 2, 3 + KV head 1
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;img src="https://synapticradio.com/images/tensor-parallel-gqa-attention/figure-01-head-groups.svg"
loading="lazy"
alt="Each rank owns two query heads and the KV head they reuse"
>&lt;/p>
&lt;p>Rank 0 receives group 0. Rank 1 receives group 1. In matrix terms, the Q projection is split by blocks of &lt;code>2 × head_dim = 8&lt;/code> output rows, while K and V are split by blocks of &lt;code>1 × head_dim = 4&lt;/code> output rows.&lt;/p>
&lt;p>The local code is the dense code with smaller head counts:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">q_local&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">F&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">linear&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">wq_local&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">view&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">S&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">B&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">k_local&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">F&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">linear&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">wk_local&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">view&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">S&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">B&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">v_local&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">F&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">linear&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">n&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">wv_local&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">view&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">S&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">B&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">q_local&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">apply_rope&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">q_local&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">k_local&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">apply_rope&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">k_local&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">k_local&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">repeat_kv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">k_local&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">groups&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">v_local&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">repeat_kv&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">v_local&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">groups&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">context_local&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">causal_attention&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">q_local&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">k_local&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">v_local&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>No communication is needed inside this local attention calculation. RoPE operates within each head, the causal mask is shared, and the rank already owns the KV head needed by both of its query heads. This matches the original Megatron observation that attention heads can be computed locally after Q, K, and V are column-partitioned by head ownership.&lt;/p>
&lt;p>The important restriction is divisibility. With two TP ranks, two KV heads divide cleanly. A configuration with two KV heads and four TP ranks cannot assign one complete KV head to every rank without replication or a different layout. The toy fixture does not solve that more general case.&lt;/p>
&lt;h2 id="second-change-split-the-output-projection">Second change: split the output projection
&lt;/h2>&lt;p>Local attention produces only half of the dense context width:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Tensor&lt;/th>
&lt;th style="text-align: right">Dense shape&lt;/th>
&lt;th style="text-align: right">Rank-local shape&lt;/th>
&lt;th>Ownership&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Normalized input&lt;/td>
&lt;td style="text-align: right">&lt;code>[5, 2, 16]&lt;/code>&lt;/td>
&lt;td style="text-align: right">&lt;code>[5, 2, 16]&lt;/code>&lt;/td>
&lt;td>replicated&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Query&lt;/td>
&lt;td style="text-align: right">&lt;code>[5, 2, 4, 4]&lt;/code>&lt;/td>
&lt;td style="text-align: right">&lt;code>[5, 2, 2, 4]&lt;/code>&lt;/td>
&lt;td>partitioned by query heads&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Key/value&lt;/td>
&lt;td style="text-align: right">&lt;code>[5, 2, 2, 4]&lt;/code>&lt;/td>
&lt;td style="text-align: right">&lt;code>[5, 2, 1, 4]&lt;/code>&lt;/td>
&lt;td>partitioned by KV heads&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Local context&lt;/td>
&lt;td style="text-align: right">—&lt;/td>
&lt;td style="text-align: right">&lt;code>[5, 2, 2, 4]&lt;/code>&lt;/td>
&lt;td>partitioned&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Output partial&lt;/td>
&lt;td style="text-align: right">—&lt;/td>
&lt;td style="text-align: right">&lt;code>[5, 2, 16]&lt;/code>&lt;/td>
&lt;td>partial sum&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Final output&lt;/td>
&lt;td style="text-align: right">&lt;code>[5, 2, 16]&lt;/code>&lt;/td>
&lt;td style="text-align: right">&lt;code>[5, 2, 16]&lt;/code>&lt;/td>
&lt;td>replicated after reduction&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The dense output projection has shape &lt;code>[hidden, query_heads × head_dim] = [16, 16]&lt;/code>. Each rank owns the columns corresponding to its local context features. If &lt;code>C^(r)&lt;/code> is rank &lt;code>r&lt;/code>&amp;rsquo;s local context and &lt;code>W_o^(r)&lt;/code> is the matching column slice, then&lt;/p>
\[
Y = X + \sum_{r=0}^{P-1} C^{(r)}\left(W_o^{(r)}\right)^\top.
\]&lt;p>Each rank can compute a full-width partial:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-python" data-lang="python">&lt;span class="line">&lt;span class="cl">&lt;span class="n">partial&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">F&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">linear&lt;/span>&lt;span class="p">(&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">context_local&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">reshape&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">S&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">B&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">),&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">wo_local&lt;/span>&lt;span class="p">,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>But no partial is the dense answer. The rank-local outputs must be summed before the residual addition.&lt;/p>
&lt;p>&lt;img src="https://synapticradio.com/images/tensor-parallel-gqa-attention/figure-02-equivalence.svg"
loading="lazy"
alt="The two output-projection partials sum to the dense output"
>&lt;/p>
&lt;p>This is the row-parallel half of the reconstruction. It also explains why gathering the local contexts first would be wasteful: the output projection can consume the partitioned context directly, and only its full-width partial outputs need to be reduced.&lt;/p>
&lt;h2 id="rebuild-the-complete-training-step">Rebuild the complete training step
&lt;/h2>&lt;p>The compact tensor-parallel path now has two transformations:&lt;/p>
&lt;ol>
&lt;li>partition Q, K, and V by complete GQA head groups;&lt;/li>
&lt;li>partition the output projection by the corresponding context features and sum the partial outputs.&lt;/li>
&lt;/ol>
&lt;p>The implementation uses one process to make the algebra easy to inspect, while treating the two branches as rank-local computations with shared parameters sliced exactly as two TP ranks would own them. It is therefore a numerical reconstruction of the TP layer, not a benchmark of distributed communication.&lt;/p>
&lt;p>Run the direct comparison:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">PYTHONPATH&lt;/span>&lt;span class="o">=&lt;/span>code python code/tp_gqa_attention.py --mode equivalence --seed &lt;span class="m">11&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;seed&amp;#34;: 11,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;mode&amp;#34;: &amp;#34;equivalence&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;forward_max_abs_diff&amp;#34;: 1.1920928955078125e-07,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;loss_abs_diff&amp;#34;: 0.0,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;input_grad_max_abs_diff&amp;#34;: 3.725290298461914e-09,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;parameter_grad_max_abs_diff&amp;#34;: 2.2351741790771484e-08,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;post_step_weight_max_abs_diff&amp;#34;: 1.4901161193847656e-08,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;passed&amp;#34;: true
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The comparison uses identical inputs, parameters, target tensor, MSE loss, and SGD learning rate. It checks more than the visible output:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Check&lt;/th>
&lt;th style="text-align: right">Seed 11 maximum absolute difference&lt;/th>
&lt;th style="text-align: right">Five-seed maximum&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Forward output&lt;/td>
&lt;td style="text-align: right">&lt;code>1.19e-07&lt;/code>&lt;/td>
&lt;td style="text-align: right">&lt;code>2.38e-07&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Loss&lt;/td>
&lt;td style="text-align: right">&lt;code>0.00&lt;/code>&lt;/td>
&lt;td style="text-align: right">&lt;code>0.00&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Input gradient&lt;/td>
&lt;td style="text-align: right">&lt;code>3.73e-09&lt;/code>&lt;/td>
&lt;td style="text-align: right">&lt;code>1.12e-08&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Parameter gradients&lt;/td>
&lt;td style="text-align: right">&lt;code>2.24e-08&lt;/code>&lt;/td>
&lt;td style="text-align: right">&lt;code>4.47e-08&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Parameters after SGD&lt;/td>
&lt;td style="text-align: right">&lt;code>1.49e-08&lt;/code>&lt;/td>
&lt;td style="text-align: right">&lt;code>2.98e-08&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Those residuals are consistent with different floating-point accumulation order, not a different computation. The retained five-seed results are in &lt;a class="link" href="data/results.csv" >&lt;code>data/results.csv&lt;/code>&lt;/a> and &lt;a class="link" href="data/results.json" >&lt;code>data/results.json&lt;/code>&lt;/a>.&lt;/p>
&lt;h2 id="break-forward-and-backward-separately">Break forward and backward separately
&lt;/h2>&lt;p>A reconstruction is more convincing when a plausible wrong version fails for the reason predicted by the algebra.&lt;/p>
&lt;h3 id="remove-the-output-reduction">Remove the output reduction
&lt;/h3>&lt;p>&lt;strong>Prediction:&lt;/strong> the first wrong signal should be the forward output because one rank-local partial is being mistaken for the complete output.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">PYTHONPATH&lt;/span>&lt;span class="o">=&lt;/span>code python code/tp_gqa_attention.py &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --mode missing_output_reduce --seed &lt;span class="m">11&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;seed&amp;#34;: 11,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;mode&amp;#34;: &amp;#34;missing_output_reduce&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;forward_max_abs_diff&amp;#34;: 0.700843870639801,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;loss_abs_diff&amp;#34;: 0.05957293510437012,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;input_grad_max_abs_diff&amp;#34;: 0.013707960024476051,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;parameter_grad_max_abs_diff&amp;#34;: 0.12953613698482513,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;post_step_weight_max_abs_diff&amp;#34;: 0.0064768195152282715,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;passed&amp;#34;: false
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The prediction holds. Across five seeds, the forward error ranges from &lt;code>0.324&lt;/code> to &lt;code>0.937&lt;/code>. Everything downstream is already operating on the wrong activation.&lt;/p>
&lt;h3 id="remove-the-replicated-input-gradient-reduction">Remove the replicated-input gradient reduction
&lt;/h3>&lt;p>The less obvious bug is in backward. Both column-partitioned projection branches read the same normalized input. In a real TP implementation, each rank computes only its local contribution to the gradient with respect to that replicated input. Those contributions must be summed.&lt;/p>
&lt;p>The fixture simulates a missing reduction by allowing both branches to contribute in forward while dropping rank 1&amp;rsquo;s contribution to the upstream input gradient.&lt;/p>
&lt;p>&lt;strong>Prediction:&lt;/strong> the forward output and loss should still match, but the first wrong signal should be &lt;code>dX&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">PYTHONPATH&lt;/span>&lt;span class="o">=&lt;/span>code python code/tp_gqa_attention.py &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> --mode missing_input_grad_reduce --seed &lt;span class="m">11&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">{
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;seed&amp;#34;: 11,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;mode&amp;#34;: &amp;#34;missing_input_grad_reduce&amp;#34;,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;forward_max_abs_diff&amp;#34;: 1.1920928955078125e-07,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;loss_abs_diff&amp;#34;: 0.0,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;input_grad_max_abs_diff&amp;#34;: 0.01103300042450428,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;parameter_grad_max_abs_diff&amp;#34;: 0.0307764932513237,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;post_step_weight_max_abs_diff&amp;#34;: 0.0015388727188110352,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;passed&amp;#34;: false
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The forward check passes within the same tolerance as the correct implementation. Training is still wrong. Across five seeds, the input-gradient error ranges from &lt;code>0.00998&lt;/code> to &lt;code>0.0182&lt;/code>.&lt;/p>
&lt;p>&lt;img src="https://synapticradio.com/images/tensor-parallel-gqa-attention/figure-03-failure-signatures.svg"
loading="lazy"
alt="Forward and backward communication failures expose different first signals"
>&lt;/p>
&lt;p>The test hierarchy now follows directly from the mechanism:&lt;/p>
&lt;ul>
&lt;li>compare forward outputs to catch a missing row-parallel output reduction;&lt;/li>
&lt;li>compare input and parameter gradients to catch missing backward communication;&lt;/li>
&lt;li>compare updated parameters to confirm that the entire state transition matches.&lt;/li>
&lt;/ul>
&lt;p>Run the suite:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">PYTHONPATH&lt;/span>&lt;span class="o">=&lt;/span>code pytest -q code/test_tp_gqa_attention.py
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">..... [100%]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">5 passed in 9.92s
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The full test output is retained in &lt;a class="link" href="data/terminal-05-tests.txt" >&lt;code>data/terminal-05-tests.txt&lt;/code>&lt;/a>.&lt;/p>
&lt;h2 id="how-this-maps-to-megatron-core">How this maps to Megatron Core
&lt;/h2>&lt;p>The reconstruction is deliberately written with ordinary PyTorch functions, but the objects map cleanly to Megatron&amp;rsquo;s tensor-parallel design:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Reconstruction object&lt;/th>
&lt;th>Megatron-style object&lt;/th>
&lt;th>Role&lt;/th>
&lt;th>Important difference&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Sliced Q/K/V weights&lt;/td>
&lt;td>column-parallel projections&lt;/td>
&lt;td>partition output features by head ownership&lt;/td>
&lt;td>production code uses TP process groups and optimized kernels&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Local RoPE and causal attention&lt;/td>
&lt;td>rank-local attention heads&lt;/td>
&lt;td>compute attention without immediate TP communication&lt;/td>
&lt;td>production may use FlashAttention and packed sequences&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Sliced output weight columns&lt;/td>
&lt;td>row-parallel output projection&lt;/td>
&lt;td>consume partitioned context&lt;/td>
&lt;td>production performs an actual collective&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Sum of output partials&lt;/td>
&lt;td>reduce-from-TP region&lt;/td>
&lt;td>reconstruct replicated hidden state&lt;/td>
&lt;td>this fixture sums Python tensors in one process&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Sum of input-gradient contributions&lt;/td>
&lt;td>copy-to-TP backward reduction&lt;/td>
&lt;td>recover gradient for replicated input&lt;/td>
&lt;td>this fixture simulates omission by detaching one branch&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The original Megatron-LM design partitions Q, K, and V so complete attention heads stay local, then row-partitions the output projection. NVIDIA&amp;rsquo;s current Megatron Core documentation still describes tensor parallelism as splitting individual layers, while the production implementation adds sequence parallelism, process-group management, communication overlap, Transformer Engine kernels, and hardware-specific collectives.&lt;/p>
&lt;p>This post does &lt;strong>not&lt;/strong> establish performance, communication cost, or correctness for Megatron Core itself. It does not run CUDA, NCCL, FlashAttention, sequence parallelism, context parallelism, mixed precision, fused QKV layouts, KV replication for awkward GQA ratios, dropout RNG tracking, or distributed optimizer state. Those are the next layers of the reconstruction.&lt;/p>
&lt;h2 id="what-the-reconstruction-made-visible">What the reconstruction made visible
&lt;/h2>&lt;p>I started with the intuition that tensor parallelism partitions projection matrices. That is mechanically true but not precise enough for GQA. The reusable rule is semantic: keep each query/KV reuse group intact, let attention remain local, and place reductions at the boundaries where independently computed contributions must become one tensor.&lt;/p>
&lt;p>The forward reduction and backward reduction are separate obligations. The second one is easy to miss because the model can produce the correct output while training with the wrong gradient. After this reconstruction, I would not approve a tensor-parallel attention change from forward parity alone.&lt;/p>
&lt;h2 id="references">References
&lt;/h2>&lt;ul>
&lt;li>Joshua Ainslie et al., &lt;a class="link" href="https://arxiv.org/abs/2305.13245" target="_blank" rel="noopener"
>“GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints”&lt;/a>, 2023.&lt;/li>
&lt;li>Mohammad Shoeybi et al., &lt;a class="link" href="https://arxiv.org/abs/1909.08053" target="_blank" rel="noopener"
>“Megatron-LM: Training Multi-Billion Parameter Language Models Using Model Parallelism”&lt;/a>, 2019.&lt;/li>
&lt;li>NVIDIA, &lt;a class="link" href="https://docs.nvidia.com/megatron-core/developer-guide/latest/user-guide/parallelism-guide.html" target="_blank" rel="noopener"
>Megatron Core parallelism strategies&lt;/a>.&lt;/li>
&lt;li>NVIDIA, &lt;a class="link" href="https://github.com/NVIDIA/Megatron-LM" target="_blank" rel="noopener"
>Megatron-LM source repository&lt;/a>.&lt;/li>
&lt;/ul></description></item></channel></rss>