Pratham Patel
· 30 min read

MoE Load Balancing from Scratch

Building Mixture-of-Experts routing from the ground up — sigmoid scores, top-K selection, expert biases, aux-loss-free balancing, and SMEBU — all derived step by step with a 4-expert toy model.

The Arcee Trinity Large technical report introduces a 400-billion-parameter sparse Mixture-of-Experts language model that activates only 13 billion parameters per token. The model has 256 routed experts per layer, but only 4 fire for any given token. That means 252 experts sit idle on every forward pass. How does the model decide which 4 to activate? And how does it prevent all tokens from piling onto the same few “popular” experts while the rest gather dust?

We will derive the entire MoE routing and load balancing mechanism from scratch, culminating in the paper’s novel contribution: SMEBU (Soft-clamped Momentum Expert Bias Updates), a new method for keeping experts balanced during training. We will use a single tiny example — 4 experts, 8 tokens — and trace every computation end to end.


The Setup: Our Running Example

We will work with the simplest possible Mixture-of-Experts layer:

  • 4 routed experts: Nr=4N_r = 4 (labeled experts 1, 2, 3, 4)
  • 1 shared expert: Ns=1N_s = 1 (always active for every token)
  • Top-2 routing: Kr=2K_r = 2 (each token activates exactly 2 of the 4 routed experts)
  • Model dimension: d=3d = 3 (so every vector has 3 components)

Each expert is a small feedforward network (FFN). The shared expert processes every token. The routed experts compete for tokens — each token picks its top-2 favorites.

We have one token vector:

u=[10.51]\mathbf{u} = \begin{bmatrix} 1 \\ 0.5 \\ -1 \end{bmatrix}

and four router vectors, one per routed expert:

e1=[100],e2=[010],e3=[001],e4=[110]\mathbf{e}_1 = \begin{bmatrix} 1 \\ 0 \\ 0 \end{bmatrix}, \quad \mathbf{e}_2 = \begin{bmatrix} 0 \\ 1 \\ 0 \end{bmatrix}, \quad \mathbf{e}_3 = \begin{bmatrix} 0 \\ 0 \\ 1 \end{bmatrix}, \quad \mathbf{e}_4 = \begin{bmatrix} 1 \\ 1 \\ 0 \end{bmatrix}

That is the entire setup. Every derivation and numerical check in this post uses these exact vectors.


What is a Mixture-of-Experts Layer?

A Mixture-of-Experts (MoE) layer replaces the single feedforward network in a standard transformer block with a collection of smaller feedforward networks (the “experts”), plus a routing mechanism that decides which experts process each token. The idea is simple: the model can have enormous total capacity (many experts = many parameters) while keeping per-token computation cheap (only a few experts fire per token).

The output of the MoE layer for token tt is:

ht=ut+i=1NsFFNi(s)(ut)+i=1Nrgi,tFFNi(r)(ut)\mathbf{h}'_t = \mathbf{u}_t + \sum_{i=1}^{N_s} \text{FFN}_i^{(s)}(\mathbf{u}_t) + \sum_{i=1}^{N_r} g_{i,t} \, \text{FFN}_i^{(r)}(\mathbf{u}_t)

where ut\mathbf{u}_t is the input to the MoE layer, FFNi(s)\text{FFN}_i^{(s)} are the shared experts (always active), FFNi(r)\text{FFN}_i^{(r)} are the routed experts, and gi,tg_{i,t} is the gating score for routed expert ii on token tt. Most of the gi,tg_{i,t} are zero — only the top-KrK_r selected experts have nonzero gates.

Concrete example

In our setup (Ns=1N_s = 1, Nr=4N_r = 4, Kr=2K_r = 2), this becomes:

h=u+FFN(s)(u)+g1FFN1(r)(u)+g2FFN2(r)(u)+g3FFN3(r)(u)+g4FFN4(r)(u)\mathbf{h}' = \mathbf{u} + \text{FFN}^{(s)}(\mathbf{u}) + g_1 \, \text{FFN}_1^{(r)}(\mathbf{u}) + g_2 \, \text{FFN}_2^{(r)}(\mathbf{u}) + g_3 \, \text{FFN}_3^{(r)}(\mathbf{u}) + g_4 \, \text{FFN}_4^{(r)}(\mathbf{u})

Exactly 2 of the 4 gating scores g1,g2,g3,g4g_1, g_2, g_3, g_4 will be nonzero (the top-2 selected experts), and the other 2 will be zero. The shared expert always contributes.

The entire challenge is computing those gating scores gi,tg_{i,t}. That requires three steps: (1) compute routing scores, (2) select the top-KK experts, and (3) normalize the scores into gates. We derive each step now.


Step 1: Sigmoid Routing Scores

The routing score measures how much a given token “prefers” a given expert. We compute it by taking the dot product of the token vector ut\mathbf{u}_t with the expert’s router vector ei\mathbf{e}_i, then passing the result through the sigmoid function.

The sigmoid function maps any real number to the interval (0,1)(0, 1):

σ(x)=11+ex\sigma(x) = \frac{1}{1 + e^{-x}}

The routing score for routed expert ii on token tt is:

si,t=σ ⁣(utei)\boxed{s_{i,t} = \sigma\!\left(\mathbf{u}_t^\top \mathbf{e}_i\right)}

Why sigmoid instead of softmax?

Many earlier MoE models use softmax over all expert scores, which forces all scores to sum to 1. This means pushing one expert’s score up necessarily pushes others down — the scores are coupled. Trinity uses sigmoid routing, where each expert gets an independent score in (0,1)(0, 1). This decoupling leads to more stable router logits during training, which matters especially when using the Muon optimizer.

Numerical check

Let us compute the four routing scores for our token u=[1,0.5,1]\mathbf{u} = [1, 0.5, -1]^\top.

Dot products:

ue1=1×1+0.5×0+(1)×0=1\mathbf{u}^\top \mathbf{e}_1 = 1 \times 1 + 0.5 \times 0 + (-1) \times 0 = 1 ue2=1×0+0.5×1+(1)×0=0.5\mathbf{u}^\top \mathbf{e}_2 = 1 \times 0 + 0.5 \times 1 + (-1) \times 0 = 0.5 ue3=1×0+0.5×0+(1)×1=1\mathbf{u}^\top \mathbf{e}_3 = 1 \times 0 + 0.5 \times 0 + (-1) \times 1 = -1 ue4=1×1+0.5×1+(1)×0=1.5\mathbf{u}^\top \mathbf{e}_4 = 1 \times 1 + 0.5 \times 1 + (-1) \times 0 = 1.5

Sigmoid scores:

s1=σ(1)=11+e1=11+0.368=11.368=0.731s_1 = \sigma(1) = \frac{1}{1 + e^{-1}} = \frac{1}{1 + 0.368} = \frac{1}{1.368} = 0.731 s2=σ(0.5)=11+e0.5=11+0.607=11.607=0.622s_2 = \sigma(0.5) = \frac{1}{1 + e^{-0.5}} = \frac{1}{1 + 0.607} = \frac{1}{1.607} = 0.622 s3=σ(1)=11+e1=11+2.718=13.718=0.269s_3 = \sigma(-1) = \frac{1}{1 + e^{1}} = \frac{1}{1 + 2.718} = \frac{1}{3.718} = 0.269 s4=σ(1.5)=11+e1.5=11+0.223=11.223=0.818s_4 = \sigma(1.5) = \frac{1}{1 + e^{-1.5}} = \frac{1}{1 + 0.223} = \frac{1}{1.223} = 0.818

Ranking from highest to lowest: expert 4 (0.8180.818) > expert 1 (0.7310.731) > expert 2 (0.6220.622) > expert 3 (0.2690.269).

Notice that the scores are independent — they do not sum to 1 (0.731+0.622+0.269+0.818=2.4400.731 + 0.622 + 0.269 + 0.818 = 2.440). Each expert gets its own “affinity” for this token.

Quick sanity check using the identity σ(x)=1σ(x)\sigma(-x) = 1 - \sigma(x): we have s1=σ(1)=0.731s_1 = \sigma(1) = 0.731 and s3=σ(1)=0.269s_3 = \sigma(-1) = 0.269, and indeed 0.731+0.269=1.0000.731 + 0.269 = 1.000. ✓


Step 2: Top-K Selection with Expert Bias

Now we select which experts to activate. The selection uses the routing score si,ts_{i,t} plus an expert bias bib_i:

selection score for expert i=si,t+bi\text{selection score for expert } i = s_{i,t} + b_i

The expert bias is a scalar associated with each expert that gets updated during training (but outside the gradient computation — it is “decoupled” from backpropagation). Its purpose is load balancing: by increasing bib_i for underutilized experts and decreasing it for overutilized ones, we can steer tokens toward less popular experts.

We select the top-KrK_r experts by their selection scores:

gi,t={si,t,if si,t+biTop-Kr ⁣({sj,t+bj}j=1Nr,  Kr),0,otherwise.g'_{i,t} = \begin{cases} s_{i,t}, & \text{if } s_{i,t} + b_i \in \text{Top-}K_r\!\left(\{s_{j,t} + b_j\}_{j=1}^{N_r},\; K_r\right), \\[4pt] 0, & \text{otherwise.} \end{cases}

This is the part that confuses almost everyone

Read the equation above carefully. The top-KK selection uses si,t+bis_{i,t} + b_i to decide WHICH experts get selected. But the gating value gi,tg'_{i,t} for the selected experts is si,ts_{i,t} — the routing score WITHOUT the bias. The bias influences the selection, but not the weight.

Why? Because the expert bias is updated by a heuristic rule (not gradient descent). If the bias affected the gating weights, those heuristic updates would corrupt the gradient signal. By keeping the bias out of the gating computation, we ensure the gradient flows cleanly through the routing scores while still allowing the bias to redirect token-to-expert assignments.

A natural follow-up question: if the bias does not affect the gating weights, how can it change the model’s behavior at all? The answer: it changes the SET of active experts. Selecting expert 2 instead of expert 4 means running a completely different FFN — even if the gating weights stay the same. The bias is a selection mechanism, not a weighting mechanism.

Numerical check with zero bias

Starting with b1=b2=b3=b4=0b_1 = b_2 = b_3 = b_4 = 0 (no load balancing intervention yet):

Expertsis_ibib_isi+bis_i + b_iSelected?
10.73100.731✓ (2nd)
20.62200.622
30.26900.269
40.81800.818✓ (1st)

Top-2 by selection score: experts 4 and 1.

So g4=s4=0.818g'_4 = s_4 = 0.818, g1=s1=0.731g'_1 = s_1 = 0.731, g2=0g'_2 = 0, g3=0g'_3 = 0.

Numerical check with nonzero bias

Now suppose training has been running for a while and the load balancer has set b1=0b_1 = 0, b2=0.2b_2 = 0.2, b3=0b_3 = 0, b4=0.2b_4 = -0.2. Expert 4 was overloaded, so its bias was decreased. Expert 2 was underloaded, so its bias was increased.

Expertsis_ibib_isi+bis_i + b_iSelected?
10.73100.731✓ (2nd)
20.6220.20.822✓ (1st)
30.26900.269
40.818-0.20.618

Top-2 by selection score: experts 2 and 1.

The bias flipped the selection. Expert 4, which had the highest routing score (0.8180.818), got demoted because its negative bias (0.2-0.2) dragged its selection score below expert 2’s boosted score (0.622+0.2=0.8220.622 + 0.2 = 0.822).

The gating values: g2=s2=0.622g'_2 = s_2 = 0.622, g1=s1=0.731g'_1 = s_1 = 0.731, g3=0g'_3 = 0, g4=0g'_4 = 0.

Notice that g2=0.622g'_2 = 0.622, not 0.8220.822. The bias affected selection but not the gate value. This is the decoupled design in action.


Step 3: The Gating Mechanism

The gating values gi,tg'_{i,t} need to be normalized so they sum to 1. We divide each nonzero gate by the sum of all nonzero gates:

gi,t=gi,tj=1Nrgj,t\boxed{g_{i,t} = \frac{g'_{i,t}}{\sum_{j=1}^{N_r} g'_{j,t}}}

Numerical check (zero bias case)

The nonzero gates are g4=0.818g'_4 = 0.818 and g1=0.731g'_1 = 0.731. Their sum is:

0.818+0.731=1.5490.818 + 0.731 = 1.549

Normalized:

g4=0.8181.549=0.528,g1=0.7311.549=0.472g_4 = \frac{0.818}{1.549} = 0.528, \qquad g_1 = \frac{0.731}{1.549} = 0.472

Check: 0.528+0.472=1.0000.528 + 0.472 = 1.000. ✓

Numerical check (nonzero bias case)

The nonzero gates are g2=0.622g'_2 = 0.622 and g1=0.731g'_1 = 0.731. Their sum is:

0.622+0.731=1.3530.622 + 0.731 = 1.353

Normalized:

g1=0.7311.353=0.540,g2=0.6221.353=0.460g_1 = \frac{0.731}{1.353} = 0.540, \qquad g_2 = \frac{0.622}{1.353} = 0.460

Check: 0.540+0.460=1.0000.540 + 0.460 = 1.000. ✓


The MoE Output

We now have everything we need. The MoE layer output for our token is (zero-bias case):

h=u+FFN(s)(u)+0.472FFN1(r)(u)+0.528FFN4(r)(u)\mathbf{h}' = \mathbf{u} + \text{FFN}^{(s)}(\mathbf{u}) + 0.472 \cdot \text{FFN}_1^{(r)}(\mathbf{u}) + 0.528 \cdot \text{FFN}_4^{(r)}(\mathbf{u})

The shared expert always contributes. Of the 4 routed experts, only experts 1 and 4 fire. Expert 4 gets slightly more weight (0.5280.528) than expert 1 (0.4720.472) because its routing score was higher (0.8180.818 vs 0.7310.731).

Experts 2 and 3 do nothing for this token. Their parameters are not accessed, their computation is skipped entirely. This is the source of MoE’s efficiency: 400B total parameters, but only 13B worth of computation per token.

Interpretation

Let us now step back and look at the full pipeline:

token udot product with each router vector eᵢ→ raw logitssigmoid→ routing scores sᵢ ∈ (0,1)add expert bias bᵢ→ selection scores (sᵢ + bᵢ)top-K selection→ which experts fire (sᵢ + bᵢ)normalize gates→ gᵢ summing to 1weighted sum of expert outputs→ MoE output h′

The expert bias bib_i enters at exactly one point: the top-KK selection. It affects who gets chosen, not how much weight they carry. Everything else flows through the learned routing scores si,ts_{i,t}.

The question that remains: how do we set the expert biases? That is the load balancing problem.


The Load Balancing Problem

Imagine training our 4-expert model on many tokens. If the router learns to always prefer experts 1 and 4 (because early in training they happen to give slightly better representations), then experts 2 and 3 never get selected. Experts that never get selected never receive gradient updates. Experts that never update never improve. Experts that never improve never get selected. This is a death spiral.

The result is called expert collapse: a few experts handle all the work while the rest are wasted. In Trinity Large, with 256 routed experts per layer, a collapse would mean the model effectively has far fewer experts than designed — a massive waste of parameters and compute.

We need a mechanism that gently steers tokens toward underutilized experts and away from overutilized ones. This is load balancing.

Our batch example

For the rest of this post, we track what happens across a batch of T=8T = 8 tokens, all processed in a single training step. Suppose the router (with current biases) makes the following top-2 selections:

TokenExpert selected 1Expert selected 2
141
242
341
443
541
642
742
841

Every single token chose expert 4 as its first pick. Expert 4 is the “popular” expert. The load counts (number of times each expert was selected) are:

n1=4,n2=3,n3=1,n4=8n_1 = 4, \quad n_2 = 3, \quad n_3 = 1, \quad n_4 = 8

Total selections: 4+3+1+8=16=Kr×T=2×84 + 3 + 1 + 8 = 16 = K_r \times T = 2 \times 8. ✓

The mean load is:

nˉ=1Nri=1Nrni=4+3+1+84=164=4\bar{n} = \frac{1}{N_r} \sum_{i=1}^{N_r} n_i = \frac{4 + 3 + 1 + 8}{4} = \frac{16}{4} = 4

In a perfectly balanced world, every expert would handle exactly 4 tokens. Instead, expert 4 handles 8 (twice the mean) and expert 3 handles just 1 (a quarter of the mean). Expert 4 is severely overloaded. Expert 3 is starving.


Aux-Loss-Free Load Balancing (The Sign-Based Method)

The standard aux-loss-free approach maintains a bias vector b=[b1,,bNr]\mathbf{b} = [b_1, \ldots, b_{N_r}] that is updated after each training step using a simple rule: increase the bias for underloaded experts, decrease it for overloaded ones.

Step 1. Compute the mean load:

nˉ=1Nri=1Nrni\bar{n} = \frac{1}{N_r} \sum_{i=1}^{N_r} n_i

Step 2. Update each bias using the sign of the deviation from the mean:

Δbi=γsign(nˉni)\Delta b_i = \gamma \cdot \text{sign}(\bar{n} - n_i)

where γ\gamma is a small step size (the “bias update speed”), and the sign function returns +1+1 if the argument is positive, 1-1 if negative, and 00 if zero.

Step 3. Apply the update:

bibi+Δbib_i \leftarrow b_i + \Delta b_i

Step 4. Center the biases (subtract the mean so they sum to zero):

bibi1Nrj=1Nrbjb_i \leftarrow b_i - \frac{1}{N_r} \sum_{j=1}^{N_r} b_j

The centering step prevents the biases from drifting collectively upward or downward, which would shift the overall selection threshold without improving balance.

Numerical check

Using our batch loads n1=4,n2=3,n3=1,n4=8n_1 = 4, n_2 = 3, n_3 = 1, n_4 = 8 and nˉ=4\bar{n} = 4, starting from bi=0b_i = 0, with γ=0.1\gamma = 0.1:

Deviations nˉni\bar{n} - n_i:

nˉn1=44=0,nˉn2=43=1,nˉn3=41=3,nˉn4=48=4\bar{n} - n_1 = 4 - 4 = 0, \quad \bar{n} - n_2 = 4 - 3 = 1, \quad \bar{n} - n_3 = 4 - 1 = 3, \quad \bar{n} - n_4 = 4 - 8 = -4

Sign of deviations:

sign(0)=0,sign(1)=1,sign(3)=1,sign(4)=1\text{sign}(0) = 0, \quad \text{sign}(1) = 1, \quad \text{sign}(3) = 1, \quad \text{sign}(-4) = -1

Updates:

Δb1=0.1×0=0,Δb2=0.1×1=0.1,Δb3=0.1×1=0.1,Δb4=0.1×(1)=0.1\Delta b_1 = 0.1 \times 0 = 0, \quad \Delta b_2 = 0.1 \times 1 = 0.1, \quad \Delta b_3 = 0.1 \times 1 = 0.1, \quad \Delta b_4 = 0.1 \times (-1) = -0.1

After applying updates (Step 3):

b1=0,b2=0.1,b3=0.1,b4=0.1b_1 = 0, \quad b_2 = 0.1, \quad b_3 = 0.1, \quad b_4 = -0.1

Centering (Step 4):

mean(b)=0+0.1+0.1+(0.1)4=0.14=0.025\text{mean}(\mathbf{b}) = \frac{0 + 0.1 + 0.1 + (-0.1)}{4} = \frac{0.1}{4} = 0.025 b1=00.025=0.025b_1 = 0 - 0.025 = -0.025 b2=0.10.025=0.075b_2 = 0.1 - 0.025 = 0.075 b3=0.10.025=0.075b_3 = 0.1 - 0.025 = 0.075 b4=0.10.025=0.125b_4 = -0.1 - 0.025 = -0.125

Check that the centered biases sum to zero: (0.025)+0.075+0.075+(0.125)=0(-0.025) + 0.075 + 0.075 + (-0.125) = 0. ✓

Interpretation

Expert 3 was the most underloaded (1 token vs mean 4) and expert 4 was the most overloaded (8 tokens vs mean 4). After the update, expert 3 has the second-highest bias (0.0750.075) and expert 4 has the lowest (0.125-0.125). On the next training step, the biases will push tokens toward experts 2 and 3 and away from expert 4. This is exactly the rebalancing behavior we want.

But there is a problem hiding in the sign function.


Why Sign-Based Updates Oscillate

Look again at the updates: expert 2 had a deviation of nˉn2=1\bar{n} - n_2 = 1 (slightly underloaded) and expert 3 had a deviation of nˉn3=3\bar{n} - n_3 = 3 (severely underloaded). Both received the exact same update Δb=+0.1\Delta b = +0.1, because sign(1)=sign(3)=1\text{sign}(1) = \text{sign}(3) = 1.

The sign function is blind to magnitude. It treats a tiny imbalance and a massive imbalance identically.

This becomes a serious problem near convergence. Suppose after many training steps the loads become nearly balanced: n1=4,n2=4,n3=3,n4=5n_1 = 4, n_2 = 4, n_3 = 3, n_4 = 5. The mean is still nˉ=4\bar{n} = 4.

Sign-based updates for the nearly balanced case:

Δb3=0.1×sign(43)=0.1×1=0.1\Delta b_3 = 0.1 \times \text{sign}(4 - 3) = 0.1 \times 1 = 0.1 Δb4=0.1×sign(45)=0.1×(1)=0.1\Delta b_4 = 0.1 \times \text{sign}(4 - 5) = 0.1 \times (-1) = -0.1

Expert 3 is only 1 token below average, yet it gets the full +0.1+0.1 boost — the same magnitude as when it was 3 tokens below average. Expert 4 is only 1 token above average, yet it gets the full 0.1-0.1 penalty.

These large updates overshoot. On the next step, expert 3 might become slightly overloaded, triggering a 0.1-0.1 swing in the other direction. Then it undershoots again. The biases oscillate around the equilibrium, never settling.

The paper puts it precisely: “Under the assumption that the ideal expert bias value is a fixed value, we note that the standard aux-loss-free load balancing cannot precisely converge on that value, as each local update under the sign()\text{sign}(\cdot) operator is always ±γ\pm\gamma.”

As the total number of experts increases (Trinity Large has 256), the per-layer bias norm grows, making the oscillations larger and contributing to training instability.

We need an update rule that is aggressive when the imbalance is large and gentle when the imbalance is small. We need SMEBU.


SMEBU: Soft-Clamped Momentum Expert Bias Updates

SMEBU replaces the sign-based update with three modifications: (1) a normalized, magnitude-aware update via tanh\tanh, (2) centering, and (3) momentum smoothing. We derive each step.

Step 1: Normalize the Violation

First, we compute how far each expert’s load deviates from the mean, as a fraction of the mean:

vi=nˉninˉ\boxed{v_i = \frac{\bar{n} - n_i}{\bar{n}}}

We call viv_i the normalized violation for expert ii. A positive viv_i means the expert is underloaded (fewer tokens than average). A negative viv_i means overloaded.

Dividing by nˉ\bar{n} makes the violation scale-independent. Whether the batch has 8 tokens or 8 million, viv_i lives on the same scale. An expert handling twice the mean load always has vi=1v_i = -1, regardless of the absolute numbers.

Numerical check (heavily imbalanced)

Using our loads n1=4,n2=3,n3=1,n4=8n_1 = 4, n_2 = 3, n_3 = 1, n_4 = 8 with nˉ=4\bar{n} = 4:

v1=444=0,v2=434=0.25,v3=414=0.75,v4=484=1v_1 = \frac{4 - 4}{4} = 0, \quad v_2 = \frac{4 - 3}{4} = 0.25, \quad v_3 = \frac{4 - 1}{4} = 0.75, \quad v_4 = \frac{4 - 8}{4} = -1

Expert 3 has v3=0.75v_3 = 0.75: it handled only 25% of its fair share. Expert 4 has v4=1v_4 = -1: it handled twice its fair share.

Numerical check (nearly balanced)

Using loads n1=4,n2=4,n3=3,n4=5n_1 = 4, n_2 = 4, n_3 = 3, n_4 = 5 with nˉ=4\bar{n} = 4:

v1=0,v2=0,v3=434=0.25,v4=454=0.25v_1 = 0, \quad v_2 = 0, \quad v_3 = \frac{4-3}{4} = 0.25, \quad v_4 = \frac{4-5}{4} = -0.25

The violations are much smaller now. Under the sign-based method, these would all produce the same ±γ\pm\gamma updates. Under SMEBU, they produce proportionally smaller updates, as we will see next.

Step 2: Soft-Clamp with tanh

We apply the hyperbolic tangent function, scaled by a parameter κ\kappa:

v~i=tanh(κvi)\boxed{\tilde{v}_i = \tanh(\kappa \, v_i)}

The hyperbolic tangent function is:

tanh(x)=exexex+ex\tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}

It maps any real number to the interval (1,1)(-1, 1). Three properties make it perfect for this job:

Property 1: Near zero, tanh is approximately the identity. For small x|x|:

tanh(x)x\tanh(x) \approx x

We can see why. When x|x| is small, ex1+xe^x \approx 1 + x and ex1xe^{-x} \approx 1 - x. Substituting:

tanh(x)(1+x)(1x)(1+x)+(1x)=2x2=x\tanh(x) \approx \frac{(1+x) - (1-x)}{(1+x) + (1-x)} = \frac{2x}{2} = x

So near balance (small viv_i), the update is proportional to the violation itself. A tiny imbalance produces a tiny update.

Property 2: Far from zero, tanh saturates at ±1\pm 1. As x|x| \to \infty, tanh(x)±1\tanh(x) \to \pm 1. So for large imbalances, the update is bounded — we never apply an update larger than λ\lambda (the learning rate from Step 3).

Property 3: tanh is a smooth approximation of sign. In fact:

sign(x)=limatanh(ax)\text{sign}(x) = \lim_{a \to \infty} \tanh(a \, x)

The parameter κ\kappa controls how quickly tanh transitions from the linear regime to the saturated regime. Large κ\kappa makes it behave more like sign. Small κ\kappa makes it more linear. Trinity Large uses κ=2\kappa = 2.

Here is a comparison table with κ=2\kappa = 2:

viv_i (violation)κvi\kappa v_itanh(κvi)\tanh(\kappa v_i)sign(vi)\text{sign}(v_i)Ratio
1.00-1.002.00-2.000.964-0.9641-196.4%96.4\%
0.75-0.751.50-1.500.905-0.9051-190.5%90.5\%
0.50-0.501.00-1.000.762-0.7621-176.2%76.2\%
0.25-0.250.50-0.500.462-0.4621-146.2%46.2\%
0.10-0.100.20-0.200.197-0.1971-119.7%19.7\%
0.00\phantom{-}0.000.00\phantom{-}0.000.000\phantom{-}0.0000\phantom{-}0
+0.10+0.10+0.20+0.20+0.197+0.197+1+119.7%19.7\%
+0.25+0.25+0.50+0.50+0.462+0.462+1+146.2%46.2\%
+0.50+0.50+1.00+1.00+0.762+0.762+1+176.2%76.2\%
+0.75+0.75+1.50+1.50+0.905+0.905+1+190.5%90.5\%
+1.00+1.00+2.00+2.00+0.964+0.964+1+196.4%96.4\%

The “Ratio” column shows how much of the full sign-step SMEBU applies. At large violations (v=1|v| = 1), SMEBU applies 96.4% of the sign step — nearly identical. At moderate violations (v=0.25|v| = 0.25), it applies 46.2%. At tiny violations (v=0.1|v| = 0.1), it applies only 19.7%.

This is exactly the behavior we wanted: aggressive for large imbalances, gentle near equilibrium.

Numerical check (heavily imbalanced)

Violations: v1=0,v2=0.25,v3=0.75,v4=1v_1 = 0, v_2 = 0.25, v_3 = 0.75, v_4 = -1. With κ=2\kappa = 2:

v~1=tanh(2×0)=tanh(0)=0\tilde{v}_1 = \tanh(2 \times 0) = \tanh(0) = 0 v~2=tanh(2×0.25)=tanh(0.5)=0.462\tilde{v}_2 = \tanh(2 \times 0.25) = \tanh(0.5) = 0.462 v~3=tanh(2×0.75)=tanh(1.5)=0.905\tilde{v}_3 = \tanh(2 \times 0.75) = \tanh(1.5) = 0.905 v~4=tanh(2×(1))=tanh(2)=0.964\tilde{v}_4 = \tanh(2 \times (-1)) = \tanh(-2) = -0.964

Let us verify tanh(0.5)\tanh(0.5) explicitly:

tanh(0.5)=e0.5e0.5e0.5+e0.5=1.6490.6071.649+0.607=1.0422.256=0.462\tanh(0.5) = \frac{e^{0.5} - e^{-0.5}}{e^{0.5} + e^{-0.5}} = \frac{1.649 - 0.607}{1.649 + 0.607} = \frac{1.042}{2.256} = 0.462 \quad \checkmark

And tanh(2)\tanh(-2):

tanh(2)=tanh(2)=e2e2e2+e2=7.3890.1357.389+0.135=7.2547.524=0.964\tanh(-2) = -\tanh(2) = -\frac{e^{2} - e^{-2}}{e^{2} + e^{-2}} = -\frac{7.389 - 0.135}{7.389 + 0.135} = -\frac{7.254}{7.524} = -0.964 \quad \checkmark

Numerical check (nearly balanced)

Violations: v1=0,v2=0,v3=0.25,v4=0.25v_1 = 0, v_2 = 0, v_3 = 0.25, v_4 = -0.25. With κ=2\kappa = 2:

v~1=0,v~2=0,v~3=tanh(0.5)=0.462,v~4=tanh(0.5)=0.462\tilde{v}_1 = 0, \quad \tilde{v}_2 = 0, \quad \tilde{v}_3 = \tanh(0.5) = 0.462, \quad \tilde{v}_4 = \tanh(-0.5) = -0.462

Compare with sign: sign(v3)=1\text{sign}(v_3) = 1, sign(v4)=1\text{sign}(v_4) = -1.

SMEBU gives 0.4620.462 where sign gives 1.01.0. The update is less than half the sign-based step, because the imbalance is moderate. Near perfect balance, the ratio would shrink further — for v=0.05v = 0.05, SMEBU gives only tanh(0.1)=0.100\tanh(0.1) = 0.100, which is 10% of the sign step.

Step 3: Scale, Center, and Apply Momentum

The remaining three operations turn the soft-clamped violations into actual bias updates.

Scale by the load-balance learning rate λ\lambda:

Δbi=λv~i\Delta b_i = \lambda \, \tilde{v}_i

Center the updates so they sum to zero:

ΔbiΔbi1Nrj=1NrΔbj\Delta b_i \leftarrow \Delta b_i - \frac{1}{N_r} \sum_{j=1}^{N_r} \Delta b_j

Apply momentum, maintaining a momentum buffer mim_i (initialized to 0):

miβmi+(1β)Δbim_i \leftarrow \beta \, m_i + (1 - \beta) \, \Delta b_i

Update the bias:

bibi+mib_i \leftarrow b_i + m_i

The momentum here works exactly like momentum in SGD. Instead of applying the raw update Δbi\Delta b_i directly, we maintain a running average mim_i that blends the current update with past updates. When the updates are noisy (pointing in different directions on different steps), the momentum averages out the noise. When the updates are consistent (pointing in the same direction), the momentum accumulates and accelerates convergence.

The parameter β\beta controls the memory: β=0\beta = 0 means no momentum (use the raw update), β=1\beta = 1 means infinite memory (ignore new updates). Trinity Large uses β=0.5\beta = 0.5, giving equal weight to the current update and the accumulated history.

Why does momentum help here? Near convergence, expert loads fluctuate randomly around the mean — sometimes expert ii gets one extra token, sometimes one fewer. These fluctuations produce small, noisy, rapidly-alternating bias updates. Without momentum, the biases jitter. With momentum, consecutive opposing updates (+ϵ,ϵ,+ϵ,+\epsilon, -\epsilon, +\epsilon, \ldots) cancel in the running average, and the bias stays steady.


Full Numerical Walkthrough: SMEBU vs Sign-Based

Let us trace both methods through our heavily imbalanced batch (n1=4,n2=3,n3=1,n4=8n_1 = 4, n_2 = 3, n_3 = 1, n_4 = 8, nˉ=4\bar{n} = 4), starting from bi=0b_i = 0 and mi=0m_i = 0.

Hyperparameters: γ=0.1\gamma = 0.1 for sign-based, λ=0.1\lambda = 0.1, κ=2\kappa = 2, β=0.5\beta = 0.5 for SMEBU.

Sign-based method

StepFormulaExpert 1Expert 2Expert 3Expert 4
Loads nin_i4318
nˉni\bar{n} - n_i0134-4
sign(nˉni)\text{sign}(\bar{n} - n_i)0111-1
Δbi\Delta b_iγsign\gamma \cdot \text{sign}00.10.10.1-0.1
After addbi+Δbib_i + \Delta b_i00.10.10.1-0.1
Mean of b\mathbf{b}0.0250.025
After centerbimeanb_i - \text{mean}0.025-0.0250.0750.0750.0750.0750.125-0.125

Experts 2 and 3 got the same update (0.10.1), despite expert 3 being far more underloaded. The sign function erased the magnitude information.

SMEBU method

StepFormulaExpert 1Expert 2Expert 3Expert 4
Loads nin_i4318
viv_inˉninˉ\frac{\bar{n}-n_i}{\bar{n}}00.250.751.00-1.00
κvi\kappa v_i2vi2 v_i00.501.502.00-2.00
v~i\tilde{v}_itanh(κvi)\tanh(\kappa v_i)00.4620.9050.964-0.964
Δbi\Delta b_iλv~i\lambda \tilde{v}_i00.04620.09050.0964-0.0964
Mean of Δb\Delta\mathbf{b}0.01010.0101
Centered Δbi\Delta b_iΔbimean\Delta b_i - \text{mean}0.0101-0.01010.03610.03610.08040.08040.1065-0.1065
mim_i0.5×0+0.5×Δbi0.5 \times 0 + 0.5 \times \Delta b_i0.0051-0.00510.01810.01810.04020.04020.0532-0.0532
bib_i0+mi0 + m_i0.0051-0.00510.01810.01810.04020.04020.0532-0.0532

Let us verify the mean of Δb\Delta\mathbf{b} before centering:

0+0.0462+0.0905+(0.0964)4=0.04034=0.0101\frac{0 + 0.0462 + 0.0905 + (-0.0964)}{4} = \frac{0.0403}{4} = 0.0101 \quad \checkmark

And verify the centered updates sum to zero:

(0.0101)+0.0361+0.0804+(0.1065)=0.00010(-0.0101) + 0.0361 + 0.0804 + (-0.1065) = -0.0001 \approx 0 \quad \checkmark

(The tiny residual is from rounding to 4 decimal places.)

Comparing the results

ExpertSign-based bib_iSMEBU bib_i
1 (balanced)0.025-0.0250.005-0.005
2 (slightly under)+0.075+0.075+0.018+0.018
3 (severely under)+0.075+0.075+0.040+0.040
4 (severely over)0.125-0.1250.053-0.053

Two critical differences:

1. SMEBU differentiates by severity. Under sign-based, experts 2 and 3 both got bi=0.075b_i = 0.075 — the same bias despite very different loads (3 vs 1). Under SMEBU, expert 3 got 0.0400.040 and expert 2 got 0.0180.018. SMEBU gave more help to the expert that needed it more.

2. SMEBU gives smaller updates overall. The momentum halves the first-step update (since β=0.5\beta = 0.5 and mim_i starts at 0). On subsequent steps, the momentum accumulates consistent signals and dampens noise. The sign-based method applies the full γ\gamma every step regardless.


Near-Balance Behavior: Where SMEBU Truly Shines

The comparison above shows SMEBU’s advantages for a heavily imbalanced batch. But the difference becomes even more dramatic near convergence.

Suppose after many training steps the loads are nearly balanced: n1=4,n2=4,n3=3,n4=5n_1 = 4, n_2 = 4, n_3 = 3, n_4 = 5, nˉ=4\bar{n} = 4. This is only a tiny deviation from perfect balance.

Sign-based

Δb3=0.1×sign(43)=0.1×1=0.1\Delta b_3 = 0.1 \times \text{sign}(4-3) = 0.1 \times 1 = 0.1 Δb4=0.1×sign(45)=0.1×(1)=0.1\Delta b_4 = 0.1 \times \text{sign}(4-5) = 0.1 \times (-1) = -0.1

The full ±0.1\pm 0.1 step. The same magnitude as when expert 4 was carrying double the load. The update does not know that balance is almost achieved.

SMEBU

v3=0.25,v4=0.25v_3 = 0.25, \quad v_4 = -0.25 v~3=tanh(0.5)=0.462,v~4=tanh(0.5)=0.462\tilde{v}_3 = \tanh(0.5) = 0.462, \quad \tilde{v}_4 = \tanh(-0.5) = -0.462 Δb3=0.1×0.462=0.0462,Δb4=0.1×(0.462)=0.0462\Delta b_3 = 0.1 \times 0.462 = 0.0462, \quad \Delta b_4 = 0.1 \times (-0.462) = -0.0462

After centering and momentum, the effective step is even smaller. SMEBU recognizes that the imbalance is mild and responds gently.

If the imbalance were even tinier — say n3=4,n4=4n_3 = 4, n_4 = 4 with one token fluctuation — the violation would be v0.06v \approx 0.06, giving tanh(0.12)0.119\tanh(0.12) \approx 0.119, which is only 11.9% of the sign step. The biases would barely budge, because there is barely anything to fix. This is convergence.

The fundamental problem with the sign function, stated precisely: it maps the continuous violation signal to the discrete set {1,0,+1}\{-1, 0, +1\}, destroying all magnitude information. The tanh function preserves magnitude while still bounding the updates to (1,+1)(-1, +1), preventing any single step from being catastrophically large. It is a “continuous relaxation of the discrete update,” exactly as the paper describes.


Connecting It All: The Unified View

Let us step back and see the sign-based and SMEBU methods as special cases of a single framework. Both methods compute a bias update of the form:

Δbi=λf ⁣(nˉninˉ)\Delta b_i = \lambda \cdot f\!\left(\frac{\bar{n} - n_i}{\bar{n}}\right)

where f()f(\cdot) is a function that maps the normalized violation to an update magnitude.

For the sign-based method:

f(v)=sign(v)f(v) = \text{sign}(v)

For SMEBU:

f(v)=tanh(κv)f(v) = \tanh(\kappa \, v)

Both functions are odd (f(v)=f(v)f(-v) = -f(v)), both are bounded (f(v)1|f(v)| \leq 1), and both have f(0)=0f(0) = 0. The difference is entirely in how they treat intermediate values:

  • sign\text{sign} is a step function: it jumps from 0 to ±1\pm 1 at v=0v = 0, with no values in between.
  • tanh\tanh is a smooth S-curve: it transitions gradually, with f(v)κvf(v) \approx \kappa v near zero and f(v)±1f(v) \to \pm 1 far from zero.

As κ\kappa \to \infty, the tanh curve becomes steeper and approaches the sign function. As κ0\kappa \to 0, the tanh curve becomes shallower and approaches a pure linear update f(v)=κvf(v) = \kappa v. The parameter κ\kappa controls where on this spectrum we sit.

Trinity Large uses κ=2\kappa = 2, which is in the moderate range: the update is noticeably different from sign for violations smaller than about 0.5, but behaves almost identically to sign for violations larger than 1.

Adding momentum is the second key difference. The momentum buffer mim_i acts as a low-pass filter on the update sequence. High-frequency noise (random fluctuations in expert loads) gets attenuated, while low-frequency signals (persistent imbalances) pass through and accumulate. This is the same principle behind why momentum SGD converges faster than vanilla SGD in noisy settings.


The Full SMEBU Algorithm

For reference, here is the complete SMEBU update, combining all the pieces we derived:

Given: Expert loads n1,,nNrn_1, \ldots, n_{N_r} from the current training step. Maintained state: bias vector b\mathbf{b}, momentum buffer m\mathbf{m} (both initialized to zero). Hyperparameters: λ\lambda (learning rate), κ\kappa (tanh scale), β\beta (momentum).

nˉ=1Nri=1Nrni\bar{n} = \frac{1}{N_r}\sum_{i=1}^{N_r} n_i vi=nˉninˉ(normalized violation)v_i = \frac{\bar{n} - n_i}{\bar{n}} \qquad \text{(normalized violation)} v~i=tanh(κvi)(soft clamp)\tilde{v}_i = \tanh(\kappa \, v_i) \qquad \text{(soft clamp)} Δbi=λv~i(scale)\Delta b_i = \lambda \, \tilde{v}_i \qquad \text{(scale)} ΔbiΔbi1Nrj=1NrΔbj(center)\Delta b_i \leftarrow \Delta b_i - \frac{1}{N_r}\sum_{j=1}^{N_r} \Delta b_j \qquad \text{(center)} miβmi+(1β)Δbi(momentum)m_i \leftarrow \beta \, m_i + (1 - \beta)\,\Delta b_i \qquad \text{(momentum)} bibi+mi(update)\boxed{b_i \leftarrow b_i + m_i} \qquad \text{(update)}

Trinity Large uses λ=5×104\lambda = 5 \times 10^{-4}, κ=2\kappa = 2, β=0.5\beta = 0.5.


Summary

We built the Mixture-of-Experts routing mechanism from the ground up: a token vector hits each expert’s router vector, the dot products pass through sigmoid to produce independent routing scores in (0,1)(0,1), the expert bias shifts the selection threshold without affecting the gating weights (the decoupled design), the top-KK experts fire, and their outputs are weighted by normalized routing scores. The expert bias is the lever for load balancing — increasing it for underused experts, decreasing it for overused ones — and SMEBU is the mechanism that adjusts that lever intelligently. By replacing the sign function with tanh\tanh, SMEBU produces updates proportional to the severity of the imbalance: aggressive corrections for large deviations, gentle nudges near equilibrium, and convergence to zero updates at perfect balance. Momentum smooths out the noise from stochastic load fluctuations, preventing the oscillation that plagues sign-based methods. Together, these changes enabled Trinity Large to train stably with 256 experts per layer across 17 trillion tokens with zero loss spikes.


Previous: Foundation Prior: How LLM Outputs Reshape Bayesian Beliefs
Next: Mathematical Prerequisites for Mixture of Experts

Enjoyed this post?

Subscribe to get notified when I publish new posts. No spam, unsubscribe anytime.