<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>GPU Systems and Performance Engineering on Synaptic Radio</title><link>https://synapticradio.com/categories/gpu-systems-and-performance-engineering/</link><description>Recent content in GPU Systems and Performance Engineering 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/categories/gpu-systems-and-performance-engineering/index.xml" rel="self" type="application/rss+xml"/><item><title>Megatron Tensor Parallelism: Rebuilding a SwiGLU Feed-Forward Block Across Two Ranks</title><link>https://synapticradio.com/post/megatron-tensor-parallel-mlp/</link><pubDate>Thu, 16 Jul 2026 00:00:00 +0000</pubDate><author>anshuman264@gmail.com (Anshuman Sahoo)</author><guid>https://synapticradio.com/post/megatron-tensor-parallel-mlp/</guid><description>&lt;img src="https://synapticradio.com/images/megatron-tensor-parallel-mlp/cover.svg" alt="Featured image of post Megatron Tensor Parallelism: Rebuilding a SwiGLU Feed-Forward Block Across Two Ranks" />&lt;p>Megatron training is often introduced through launcher flags: tensor parallelism, pipeline parallelism, sequence parallelism, distributed optimizer, recomputation, and process groups. That is useful when operating the framework, but it is a poor way to learn what the framework is doing.&lt;/p>
&lt;p>I wanted to understand one smaller question first:&lt;/p>
&lt;blockquote>
&lt;p>How can two ranks reproduce the forward pass, backward pass, and parameter update of one dense Transformer feed-forward sublayer without either rank storing the full intermediate width?&lt;/p>&lt;/blockquote>
&lt;p>So I rebuilt that path with ordinary PyTorch and two CPU processes. The block is not a complete Transformer. It contains the part under test: pre-RMSNorm, a fused gate/up projection, SwiGLU, a down projection, and a residual connection.&lt;/p>
&lt;p>The finished two-rank step matched the dense reference to within &lt;code>1.27e-07&lt;/code> in the forward pass and &lt;code>1.49e-08&lt;/code> after one SGD update. More importantly, two deliberately broken versions failed in different places. Removing the forward reduction changed the output immediately. Removing the backward reduction left the output and loss correct, but corrupted the gradient entering RMSNorm.&lt;/p>
&lt;p>That difference is the useful lesson. Tensor parallelism is not merely a way to divide weights. It is a way to divide one algebraic operation while restoring the missing sums at the exact boundaries where the dense computation requires them.&lt;/p>
&lt;h2 id="start-with-the-dense-block">Start with the dense block
&lt;/h2>&lt;p>Here is the dense computation the two ranks must reproduce:&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;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&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">class&lt;/span> &lt;span class="nc">DenseSwiGLUBlock&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">nn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Module&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="sa">r&lt;/span>&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"> ┌──────────────────────────────┐
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> │ │
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> │ residual path │
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> │ ▼
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> input x ────────────┼───────────────────────────── (+) ── output
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> │ ▲
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> ▼ │
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> RMSNorm │
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> │ │
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> ┌──────┴──────┐ │
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> ▼ ▼ │
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> gate projection up projection │
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> │ │ │
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> ▼ │ │
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> SiLU │ │
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> │ │ │
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> └────── × ────┘ │
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> │ │
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> ▼ │
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> down projection ──────────────────────┘
&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>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="fm">__init__&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hidden_size&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ffn_size&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="nb">int&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">super&lt;/span>&lt;span class="p">()&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="fm">__init__&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">norm_weight&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Parameter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ones&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hidden_size&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1"># Stack gate and up projections so the tensor-parallel version can shard the shared FFN dimension.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">w_gate_up&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Parameter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">empty&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">2&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ffn_size&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">hidden_size&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">w_down&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Parameter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">empty&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hidden_size&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">ffn_size&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">b_down&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">nn&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Parameter&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">zeros&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">hidden_size&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="k">def&lt;/span> &lt;span class="nf">forward&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="bp">self&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="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">x&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="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">norm_weight&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">gate&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">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">w_gate_up&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">up&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">x&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">w_gate_up&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">hidden&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">silu&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">gate&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">up&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="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">hidden&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">w_down&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="bp">self&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">b_down&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 executable fixture uses:&lt;/p>
&lt;ul>
&lt;li>sequence length &lt;code>S = 3&lt;/code>;&lt;/li>
&lt;li>batch size &lt;code>B = 2&lt;/code>;&lt;/li>
&lt;li>model width &lt;code>H = 8&lt;/code>;&lt;/li>
&lt;li>feed-forward width &lt;code>F = 12&lt;/code>;&lt;/li>
&lt;li>tensor-parallel size &lt;code>P = 2&lt;/code>.&lt;/li>
&lt;/ul>
&lt;p>The dense shape trace is:&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;/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">input [3, 2, 8]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">RMSNorm output [3, 2, 8]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">gate projection [3, 2, 12]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">up projection [3, 2, 12]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">SwiGLU activation [3, 2, 12]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">down projection [3, 2, 8]
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">residual output [3, 2, 8]
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The equations are:&lt;/p>
\[
N = \operatorname{RMSNorm}(X),
\]\[
G = N W_g^\top,
\qquad
U = N W_u^\top,
\]\[
A = \operatorname{SiLU}(G) \odot U,
\]\[
Y = X + A W_d^\top + b_d.
\]&lt;p>The gate and up matrices each have shape &lt;code>[F, H]&lt;/code>. The down matrix has shape &lt;code>[H, F]&lt;/code>. That shared feed-forward dimension is the axis we will partition.&lt;/p>
&lt;h2 id="split-the-expansion">Split the expansion
&lt;/h2>&lt;p>The first change is to divide the gate and up projections by &lt;strong>output feature&lt;/strong>. Each rank receives the full normalized token representation but computes only half of the feed-forward channels.&lt;/p>
&lt;p>For two ranks:&lt;/p>
\[
W_g =
\begin{bmatrix}
W_g^{(0)} \\
W_g^{(1)}
\end{bmatrix},
\qquad
W_u =
\begin{bmatrix}
W_u^{(0)} \\
W_u^{(1)}
\end{bmatrix}.
\]&lt;p>Rank &lt;code>r&lt;/code> computes:&lt;/p>
\[
G^{(r)} = N\left(W_g^{(r)}\right)^\top,
\qquad
U^{(r)} = N\left(W_u^{(r)}\right)^\top,
\]\[
A^{(r)} = \operatorname{SiLU}\left(G^{(r)}\right) \odot U^{(r)}.
\]&lt;p>The nonlinear operation remains local because each gate feature is paired with the corresponding up feature on the same rank.&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;/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">normed_parallel&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">CopyToTensorParallelRegion&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">apply&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">normed&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">gate_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">normed_parallel&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">w_gate_up_local&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">up_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">normed_parallel&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">w_gate_up_local&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">hidden_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">silu&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">gate_local&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">up_local&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Nothing needs to be summed in the forward pass yet. Rank 0 owns one set of feed-forward features; rank 1 owns the other. Concatenating those slices would recover the dense SwiGLU activation.&lt;/p>
&lt;p>&lt;img src="https://synapticradio.com/images/megatron-tensor-parallel-mlp/figure-01-sharding.svg"
loading="lazy"
alt="The gate, up, and down matrices are partitioned along the shared feed-forward dimension"
>&lt;/p>
&lt;p>The shape correspondence is easier to see in one table:&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">Shape on each rank&lt;/th>
&lt;th>Partition&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>[3, 2, 8]&lt;/code>&lt;/td>
&lt;td style="text-align: right">&lt;code>[3, 2, 8]&lt;/code>&lt;/td>
&lt;td>replicated&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>RMSNorm output &lt;code>N&lt;/code>&lt;/td>
&lt;td style="text-align: right">&lt;code>[3, 2, 8]&lt;/code>&lt;/td>
&lt;td style="text-align: right">&lt;code>[3, 2, 8]&lt;/code>&lt;/td>
&lt;td>replicated&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Gate weight&lt;/td>
&lt;td style="text-align: right">&lt;code>[12, 8]&lt;/code>&lt;/td>
&lt;td style="text-align: right">&lt;code>[6, 8]&lt;/code>&lt;/td>
&lt;td>output features&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Up weight&lt;/td>
&lt;td style="text-align: right">&lt;code>[12, 8]&lt;/code>&lt;/td>
&lt;td style="text-align: right">&lt;code>[6, 8]&lt;/code>&lt;/td>
&lt;td>output features&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Local SwiGLU output&lt;/td>
&lt;td style="text-align: right">&lt;code>[3, 2, 12]&lt;/code>&lt;/td>
&lt;td style="text-align: right">&lt;code>[3, 2, 6]&lt;/code>&lt;/td>
&lt;td>feed-forward features&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Down weight&lt;/td>
&lt;td style="text-align: right">&lt;code>[8, 12]&lt;/code>&lt;/td>
&lt;td style="text-align: right">&lt;code>[8, 6]&lt;/code>&lt;/td>
&lt;td>input features&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Partial down output&lt;/td>
&lt;td style="text-align: right">—&lt;/td>
&lt;td style="text-align: right">&lt;code>[3, 2, 8]&lt;/code>&lt;/td>
&lt;td>partial sum&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Reduced output&lt;/td>
&lt;td style="text-align: right">&lt;code>[3, 2, 8]&lt;/code>&lt;/td>
&lt;td style="text-align: right">&lt;code>[3, 2, 8]&lt;/code>&lt;/td>
&lt;td>replicated&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>In Megatron terminology, this expansion is the role played by a column-parallel linear layer: each rank owns different output columns of the logical projection.&lt;/p>
&lt;h2 id="split-the-contraction">Split the contraction
&lt;/h2>&lt;p>Now partition the down projection along its &lt;strong>input feature&lt;/strong> dimension. Each rank consumes the SwiGLU slice it already owns:&lt;/p>
\[
W_d =
\begin{bmatrix}
W_d^{(0)} &amp; W_d^{(1)}
\end{bmatrix}.
\]&lt;p>Each rank computes a full-width but incomplete output:&lt;/p>
\[
Z^{(r)} = A^{(r)}\left(W_d^{(r)}\right)^\top.
\]&lt;p>The dense result is the sum of those partial products:&lt;/p>
\[
Z = \sum_{r=0}^{P-1} Z^{(r)}.
\]&lt;p>That equation tells us exactly where the first collective belongs.&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;/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 class="n">hidden_local&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">w_down_local&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="kc">None&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">mlp_out&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">ReduceFromTensorParallelRegion&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">apply&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">partial&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="n">out&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">residual&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">mlp_out&lt;/span> &lt;span class="o">+&lt;/span> &lt;span class="n">b_down&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;code>ReduceFromTensorParallelRegion&lt;/code> performs an all-reduce in the forward pass:&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="k">class&lt;/span> &lt;span class="nc">ReduceFromTensorParallelRegion&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">autograd&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Function&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nd">@staticmethod&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">forward&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ctx&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="n">y&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">clone&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">dist&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">all_reduce&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">y&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">op&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">dist&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ReduceOp&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">SUM&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">y&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="nd">@staticmethod&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">backward&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">grad_output&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">grad_output&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The backward pass through this operation is an identity. Every rank receives the same gradient for the replicated output, and each local down-projection shard uses that gradient to compute its own weight and activation gradients.&lt;/p>
&lt;p>This is the row-parallel half of the pair: split the input features, compute partial outputs, and sum them.&lt;/p>
&lt;h2 id="put-the-other-collective-in-backward">Put the other collective in backward
&lt;/h2>&lt;p>The expansion layer has the opposite communication pattern.&lt;/p>
&lt;p>Its forward pass needed no reduction because the output features were intentionally partitioned. But during backpropagation, every rank computes only the contribution from its local gate and up features to the gradient of the replicated normalized input.&lt;/p>
&lt;p>For the dense input gradient:&lt;/p>
$$
\begin{aligned}
\frac{\partial L}{\partial N}
&amp;=
\sum_{r=0}^{P-1}
\left(\frac{\partial L}{\partial N}\right)_r .
\end{aligned}
$$&lt;p>That sum must happen before the gradient continues through RMSNorm and into the residual input. The custom operation around &lt;code>normed&lt;/code> therefore does nothing in forward and reduces gradients in backward:&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="k">class&lt;/span> &lt;span class="nc">CopyToTensorParallelRegion&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">torch&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">autograd&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">Function&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nd">@staticmethod&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">forward&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ctx&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="k">return&lt;/span> &lt;span class="n">x&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="nd">@staticmethod&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">def&lt;/span> &lt;span class="nf">backward&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">ctx&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">grad_output&lt;/span>&lt;span class="p">):&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">grad&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">grad_output&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">clone&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">dist&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">all_reduce&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">grad&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">op&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">dist&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">ReduceOp&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="n">SUM&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">grad&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The two communication rules are now paired:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Logical layer&lt;/th>
&lt;th>Forward&lt;/th>
&lt;th>Backward&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Column-parallel gate/up projection&lt;/td>
&lt;td>keep local feature slices&lt;/td>
&lt;td>sum replicated-input gradient&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Row-parallel down projection&lt;/td>
&lt;td>sum partial outputs&lt;/td>
&lt;td>keep local shard gradients&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;img src="https://synapticradio.com/images/megatron-tensor-parallel-mlp/figure-02-collectives.svg"
loading="lazy"
alt="The missing forward and backward collectives create different first failure signals"
>&lt;/p>
&lt;p>This maps to the core Megatron pattern:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Reconstruction&lt;/th>
&lt;th>Megatron-style concept&lt;/th>
&lt;th>Why it exists&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Fused local gate/up weights&lt;/td>
&lt;td>&lt;code>ColumnParallelLinear&lt;/code> role&lt;/td>
&lt;td>partition expansion outputs&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Local down-projection weights&lt;/td>
&lt;td>&lt;code>RowParallelLinear&lt;/code> role&lt;/td>
&lt;td>consume partitioned hidden features&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Identity forward, reduce backward&lt;/td>
&lt;td>copy-to-tensor-parallel region&lt;/td>
&lt;td>combine contributions to replicated input gradients&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Reduce forward, identity backward&lt;/td>
&lt;td>reduce-from-tensor-parallel region&lt;/td>
&lt;td>reconstruct the dense contraction output&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The fixture implements these semantics directly with &lt;code>torch.distributed&lt;/code>; it does not import Megatron-Core classes.&lt;/p>
&lt;h2 id="reproduce-one-dense-training-step">Reproduce one dense training step
&lt;/h2>&lt;p>First inspect the actual environment, block, and tensor shapes:&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">python -m torch.distributed.run --standalone --nproc_per_node&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> code/megatron_tp_mlp.py --mode inspect
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;!-- BEGIN AUTO-GENERATED TERMINAL OUTPUT: inspect -->
&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;/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;backend&amp;#34;: &amp;#34;gloo&amp;#34;,
&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;block_path&amp;#34;: &amp;#34;pre-RMSNorm -&amp;gt; fused gate/up -&amp;gt; SwiGLU -&amp;gt; down projection -&amp;gt; residual add&amp;#34;,
&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;: [3, 2, 8],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;norm_weight&amp;#34;: [8],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;w_gate_up&amp;#34;: [2, 12, 8],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;w_down&amp;#34;: [8, 12],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;output&amp;#34;: [3, 2, 8]
&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_shapes&amp;#34;: {
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;gate_up_weight&amp;#34;: [2, 6, 8],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;gate&amp;#34;: [3, 2, 6],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;up&amp;#34;: [3, 2, 6],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;swiglu&amp;#34;: [3, 2, 6],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;down_weight&amp;#34;: [8, 6],
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;partial_output&amp;#34;: [3, 2, 8]
&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;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;!-- END AUTO-GENERATED TERMINAL OUTPUT: inspect -->
&lt;p>The launcher’s Gloo connection lines are omitted from this embedded excerpt; the complete retained output is in &lt;a class="link" href="data/terminal-01-inspect.txt" >&lt;code>data/terminal-01-inspect.txt&lt;/code>&lt;/a>. The environment record is in &lt;a class="link" href="data/environment.json" >&lt;code>data/environment.json&lt;/code>&lt;/a>.&lt;/p>
&lt;p>Now compare one complete sharded training step with the dense reference initialized from the same tensors:&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">python -m torch.distributed.run --standalone --nproc_per_node&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> code/megatron_tp_mlp.py --mode equivalence
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;!-- BEGIN AUTO-GENERATED TERMINAL OUTPUT: equivalence -->
&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;/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;mode&amp;#34;: &amp;#34;equivalence&amp;#34;,
&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;forward_max_abs_diff&amp;#34;: 1.2665987014770508e-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;: 2.9802322387695312e-08,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;norm_grad_max_abs_diff&amp;#34;: 1.4901161193847656e-08,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;gate_up_grad_max_abs_diff&amp;#34;: 4.470348358154297e-08,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;down_grad_max_abs_diff&amp;#34;: 2.2351741790771484e-08,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;down_bias_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;!-- END AUTO-GENERATED TERMINAL OUTPUT: equivalence -->
&lt;p>The complete transcript is &lt;a class="link" href="data/terminal-02-equivalence.txt" >&lt;code>data/terminal-02-equivalence.txt&lt;/code>&lt;/a>.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Check&lt;/th>
&lt;th style="text-align: right">Maximum absolute difference&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.27e-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;/tr>
&lt;tr>
&lt;td>Input gradient&lt;/td>
&lt;td style="text-align: right">&lt;code>2.98e-08&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>RMSNorm-weight gradient&lt;/td>
&lt;td style="text-align: right">&lt;code>1.49e-08&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Fused gate/up gradient&lt;/td>
&lt;td style="text-align: right">&lt;code>4.47e-08&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Down-projection gradient&lt;/td>
&lt;td style="text-align: right">&lt;code>2.24e-08&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Down-projection bias gradient&lt;/td>
&lt;td style="text-align: right">&lt;code>2.24e-08&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Parameters after one SGD step&lt;/td>
&lt;td style="text-align: right">&lt;code>1.49e-08&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The differences are consistent with floating-point operation ordering. Within this CPU fixture, the two-rank path reproduces the dense forward pass, backward pass, and parameter update.&lt;/p>
&lt;h2 id="remove-each-collective">Remove each collective
&lt;/h2>&lt;p>An equivalence result is useful only if the test can reject plausible wrong implementations. The two broken versions below are designed to fail at different points.&lt;/p>
&lt;h3 id="remove-the-forward-reduction">Remove the forward reduction
&lt;/h3>&lt;p>Prediction: each rank will treat a partial down-projection result as the complete MLP output, so the first mismatch should appear in the forward 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">python -m torch.distributed.run --standalone --nproc_per_node&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> code/megatron_tp_mlp.py --mode missing_forward_reduce
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;!-- BEGIN AUTO-GENERATED TERMINAL OUTPUT: missing-forward -->
&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;/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;mode&amp;#34;: &amp;#34;missing_forward_reduce&amp;#34;,
&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;forward_max_abs_diff&amp;#34;: 1.1021764278411865,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;loss_abs_diff&amp;#34;: 0.19488954544067383,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;input_grad_max_abs_diff&amp;#34;: 0.03851410746574402,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;norm_grad_max_abs_diff&amp;#34;: 0.04135049879550934,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;gate_up_grad_max_abs_diff&amp;#34;: 0.05925934761762619,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;down_grad_max_abs_diff&amp;#34;: 0.026660695672035217,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;post_step_weight_max_abs_diff&amp;#34;: 0.0036689937114715576,
&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;!-- END AUTO-GENERATED TERMINAL OUTPUT: missing-forward -->
&lt;p>The retained output is &lt;a class="link" href="data/terminal-03-missing-forward.txt" >&lt;code>data/terminal-03-missing-forward.txt&lt;/code>&lt;/a>.&lt;/p>
&lt;p>The prediction holds. The first changed signal is the model output, with a maximum error of &lt;code>1.10&lt;/code>. Once the loss is computed from the wrong output, every downstream gradient is also wrong.&lt;/p>
&lt;h3 id="remove-the-backward-reduction">Remove the backward reduction
&lt;/h3>&lt;p>Prediction: the forward output and loss will still match because the row-parallel forward sum remains intact. The first mismatch should appear only when gradients from the two local expansion shards need to be combined before RMSNorm.&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">python -m torch.distributed.run --standalone --nproc_per_node&lt;span class="o">=&lt;/span>&lt;span class="m">2&lt;/span> &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="se">&lt;/span> code/megatron_tp_mlp.py --mode missing_backward_reduce
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;!-- BEGIN AUTO-GENERATED TERMINAL OUTPUT: missing-backward -->
&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;/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;mode&amp;#34;: &amp;#34;missing_backward_reduce&amp;#34;,
&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;forward_max_abs_diff&amp;#34;: 1.2665987014770508e-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.13864503800868988,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;norm_grad_max_abs_diff&amp;#34;: 0.20240136981010437,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;gate_up_grad_max_abs_diff&amp;#34;: 4.470348358154297e-08,
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &amp;#34;down_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;: 0.010120153427124023,
&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;!-- END AUTO-GENERATED TERMINAL OUTPUT: missing-backward -->
&lt;p>The retained output is &lt;a class="link" href="data/terminal-04-missing-backward.txt" >&lt;code>data/terminal-04-missing-backward.txt&lt;/code>&lt;/a>.&lt;/p>
&lt;p>This is the more revealing failure. The forward output still agrees to &lt;code>1.27e-07&lt;/code>, and the loss is identical. The local gate/up and down-projection gradients also match. Yet the input gradient differs by &lt;code>0.139&lt;/code>, the RMSNorm-weight gradient differs by &lt;code>0.202&lt;/code>, and one optimizer step moves the parameters apart by &lt;code>0.0101&lt;/code>.&lt;/p>
&lt;p>A forward-only test would approve this broken training graph.&lt;/p>
&lt;p>The distributed test suite checks all four cases:&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">pytest -q code/test_megatron_tp_mlp.py
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;!-- BEGIN AUTO-GENERATED TERMINAL OUTPUT: tests -->
&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">4 passed in 26.94s
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;!-- END AUTO-GENERATED TERMINAL OUTPUT: tests -->
&lt;p>The retained test output is &lt;a class="link" href="data/terminal-05-tests.txt" >&lt;code>data/terminal-05-tests.txt&lt;/code>&lt;/a>. The complete implementation is &lt;a class="link" href="code/megatron_tp_mlp.py" >&lt;code>code/megatron_tp_mlp.py&lt;/code>&lt;/a>, and the direct tests are &lt;a class="link" href="code/test_megatron_tp_mlp.py" >&lt;code>code/test_megatron_tp_mlp.py&lt;/code>&lt;/a>.&lt;/p>
&lt;h2 id="what-this-reconstruction-provesand-what-it-does-not">What this reconstruction proves—and what it does not
&lt;/h2>&lt;p>The small implementation establishes one precise result: for this pre-RMSNorm SwiGLU feed-forward block, partitioning the expansion by output features and the contraction by input features reproduces dense training when the partial contraction outputs are summed in forward and the partial replicated-input gradients are summed in backward.&lt;/p>
&lt;p>It also shows why the two reductions need separate tests. A missing forward collective is visible immediately. A missing backward collective can survive output and loss checks while silently corrupting upstream learning.&lt;/p>
&lt;p>The fixture does &lt;strong>not&lt;/strong> execute Megatron-Core. It does not test:&lt;/p>
&lt;ul>
&lt;li>self-attention or a complete Transformer block;&lt;/li>
&lt;li>sequence parallelism;&lt;/li>
&lt;li>CUDA or NCCL collective behavior;&lt;/li>
&lt;li>BF16 or FP8 arithmetic;&lt;/li>
&lt;li>fused RMSNorm, SwiGLU, or linear kernels;&lt;/li>
&lt;li>asynchronous communication overlap;&lt;/li>
&lt;li>gradient accumulation;&lt;/li>
&lt;li>pipeline parallelism;&lt;/li>
&lt;li>distributed optimizer state;&lt;/li>
&lt;li>more than two tensor-parallel ranks.&lt;/li>
&lt;/ul>
&lt;p>Those are not cosmetic differences. For example, sequence parallelism changes which activations are replicated, fused kernels change numerical ordering, and larger process groups change communication cost and failure behavior.&lt;/p>
&lt;p>Still, the reconstruction changes how I read Megatron code. I no longer start with “which tensors are split?” I start with the dense equation and ask two questions:&lt;/p>
&lt;ol>
&lt;li>Which sum has been replaced by independent local products?&lt;/li>
&lt;li>At what point must those products be combined so forward and backward remain equivalent?&lt;/li>
&lt;/ol>
&lt;p>For the feed-forward block, those questions lead directly to the matched column-parallel and row-parallel projections—and to one collective in each direction.&lt;/p>
&lt;h2 id="references">References
&lt;/h2>&lt;ul>
&lt;li>NVIDIA, &lt;a class="link" href="https://docs.nvidia.com/megatron-core/developer-guide/latest/index.html" target="_blank" rel="noopener"
>Megatron Core User Guide&lt;/a>.&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://docs.nvidia.com/megatron-core/developer-guide/latest/apidocs/core/core.tensor_parallel.layers.html" target="_blank" rel="noopener"
>Tensor-parallel layers API&lt;/a>.&lt;/li>
&lt;/ul></description></item><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>