MoE Router
gpt-oss
Medium
Top-k expert routing with softmax taken after the top-k selection, not before.
Independent study note. Written from the public paper and official code. This is not TensorTonic Plus and does not reproduce their exercises, starter code, or tests. For the official version, subscribe on TensorTonic.
Overview
Each gpt-oss block replaces the dense MLP with a token-choice Mixture-of-Experts. Section 2.2: a linear router maps the residual (after RMSNorm) to one score per expert; both released sizes pick the top (4) experts; the mixture weights are “the softmax of the router projection over only the selected experts.” That last clause is the whole point of this note. Softmax-then-top-k and top-k-then-softmax are different functions and yield different weights on the same logits.
gpt-oss-120b has 128 experts; gpt-oss-20b has 32. (k=4) in both, so a token activates (4/128) or (4/32) of the expert parameters. The card does not mention a load-balancing auxiliary loss at inference; the reference MLPBlock is inference-only and has none.
How it works
Let (\tilde{x}=\mathrm{RMSNorm}(x)\in\mathbb{R}^{T\times d}) with (d=2880). The router is an nn.Linear gate: (d\to E), so
g_t = W_r\tilde{x}_t + b_r \in \mathbb{R}^{E}.
(E=128) or (32). For each token, take the top (k=4) logits, keeping them sorted:
\mathcal{T}_t,\; z_t = \mathrm{TopK}(g_t,\,k=4).
(\mathcal{T}_t) is the index tuple, (z_t\in\mathbb{R}^{4}) the corresponding logits. Mixture weights are
w_t = \mathrm{softmax}(z_t),\qquad w_{t,i}=\frac{e^{z_{t,i}}}{\sum_{i'=1}^{k}e^{z_{t,i'}}}.
The (E-k) unselected logits never enter the softmax, so they contribute neither normalization nor a residual “not selected” class. The four weights on a token always sum to 1. If two selected logits are equal they share mass equally; if one selected logit dominates, its weight approaches 1 and the other three approach 0.
Contrast the rejected order: (\pi=\mathrm{softmax}(g_t)) over all (E), then keep (\pi_{\mathcal{T}_t}). Those four numbers generally sum to less than 1 (the discarded experts still hold probability). Using them un-normalized under-scales the MoE output; re-normalizing them is not the same as softmax-on-selected, because softmax-on-all has already mixed the discarded logits into the denominator.
The official call is torch.topk(g, k=experts_per_token, dim=-1, sorted=True) then softmax(experts.values, dim=1). For a rank-2 activation [T, E], dim=1 is the expert-of-the-k axis. Ties in topk follow PyTorch’s stable-by-value-then-index convention; the paper is silent on ties.
The router sees the normalized residual, not the raw skip stream. It does not see token id, position, or layer index. The same four-expert rule applies on every layer; there is no shared expert on the side (unlike DeepSeek-style MoE).
Official code
gpt_oss/torch/model.py: MLPBlock holds self.gate and, in forward, torch.topk then F.softmax on experts.values. ModelConfig.experts_per_token = 4, num_experts = 128 by default (120b). The educational path upcasts expert weights from MXFP4 to BF16 after load; the router itself stays BF16.
Watch-outs
- Softmax after top-k, over the (k) logits only. Softmax over (E) then masking is the common bug and will not match the checkpoint.
sorted=Trueis what the reference passes. An unsorted top-k plus a softmax is numerically the same weights, but gathering expert matrices by those indices must stay aligned with the weight vector.- Router input is RMSNorm((x)), same tensor that is later multiplied by the expert weights. Routing on raw (x) selects different experts.
- (k=4) is fixed across both model sizes. Changing (k) without retraining changes both the active-parameter count the card quotes (5.1B / 3.6B) and the trained mixture.
Sources
- Paper: gpt-oss-120b & gpt-oss-20b Model Card, §2.2
- Code: openai/gpt-oss
gpt_oss/torch/model.py(MLPBlock.gate,topk/softmax)