<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Deep-Learning Mechanisms on Synaptic Radio</title><link>https://synapticradio.com/categories/deep-learning-mechanisms/</link><description>Recent content in Deep-Learning Mechanisms 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>Fri, 17 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://synapticradio.com/categories/deep-learning-mechanisms/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></channel></rss>