Extending Raschka's GPT-2: an MoE trained from scratch on an RTX 3090 A developer extended Sebastian Raschka's GPT-2-style code from the book "Build a Large Language Model (from Scratch)" to add mixture-of-experts support and trained a 446M-parameter model with 220M active parameters from scratch on an RTX 3090, using 6 experts with 2 active per token. The training run took just under eight days, roughly four times longer than the developer's prior runs, and the resulting model beat the original OpenAI GPT-2 small (124M parameters) on the test set but fell short of GPT-2 medium (345M parameters), despite having more total parameters and fewer active ones. The write-up details the code added to GPT-2, including an auxiliary loss needed to ensure all experts are used during training. Mixture-of-experts models are really nifty. You get inference speed close to a small model's, with a lot of the smarts and knowledge of a large one. While they use as much memory as an equivalently-sized dense non-MoE model, they're much faster. The frontier labs don't publish their architectures, but Claude and ChatGPT are widely rumoured to be MoEs these days -- and certainly many large open-weights models like DeepSeek https://huggingface.co/deepseek-ai/DeepSeek-V4-Pro-0813 and Kimi K3 https://huggingface.co/moonshotai/Kimi-K3 are. In this post, I'll show you how I added MoE support to the GPT-2-style code from Sebastian Raschka https://sebastianraschka.com/ 's book " Build a Large Language Model from Scratch https://www.manning.com/books/build-a-large-language-model-from-scratch ", then used that to train a 446M-parameter model with 220M active parameters from scratch on my RTX 3090 -- essentially, GPT-2 small with 6 experts, 2 active per token. I wanted to understand how MoEs work, and as always, felt that the best way to do that is to build one and then to write it up like this . Hopefully because it's all fresh in my mind as I write this, I should be able to explain things clearly for others who've finished Raschka's book. The training run took just less than eight days, and the resulting model got a better loss on my test set than any of the other models I've trained so far -- which was a good thing, given that it took four times longer /2025/12/llm-from-scratch-28-training-a-base-model-from-scratch to train It was also better than the original OpenAI GPT-2 small 124M parameters , but not as good as GPT-2 medium 345M parameters , although it was close. That second result was interesting, as my model had more total parameters than GPT-2 medium, but fewer active ones. On an instruction fine-tuning test, it did better than any of my other models, but worse than both OpenAI models why OpenAI's models are so good on that particular test is a mystery /gpt-2-mysteries I'm digging into separately . I'll go into those numbers in more depth later on. Firstly, though, I'll run through the code that I needed to add to GPT-2 to make this work -- not just the code for the experts themselves, but the additional code for training it. With MoEs you can't just train to minimise loss on your training set -- you need to add on an "auxiliary" loss to make sure they actually use all of their experts, and that was where it became most interesting. Before we get into the weeds, though, let's start off with the basics; how do MoEs work in Transformers-based LLMs? The phrase "mixture of experts" evokes an idea of a model which has separate parts that are knowledgeable in different domains. Maybe one part would know about coding, another about history, another about philosophy, and so on. You can imagine something that had separate LLMs for different topics, and routed incoming inputs appropriately. That's actually not a bad design for a system -- Sakana.ai got a lot of interest for their Fugu https://sakana.ai/fugu/ system back in June of this year, and it works rather like that. But the "experts" in an MoE are at a much lower level, and as with so many things /2025/05/llm-from-scratch-13-taking-stock-part-1-attention-heads-are-dumb in LLMs their expertise is in some weird and alien thing that they determined was helpful for modelling language during their training. Let's look at how they fit in mechanically. The GPT-2-style LLM that we will start from -- the one Raschka describes in his book -- looks like this: Diagram 1 : a GPT-2-style LLM at the top level The MoE magic happens inside those Transformers layers, so let's zoom in on one of them: Diagram 2 : a GPT-2-style Transformers block Specifically, what we do to make our LLM an MoE is to replace that feed-forward network FFN with multiple separate FFNs -- our experts. Different context vectors get routed to different experts based on their contents. The FFNs themselves -- in both the dense and MoE versions -- are surprisingly simple /2025/08/llm-from-scratch-17-the-feed-forward-network . In GPT-2, they take in the incoming context vectors, run them through a normal linear layer to expand the number of dimensions by four, run the result through a GELU activation function, then project it back into the original incoming dimensionality with another linear layer. It's a really simple two-layer neural network, and at first glance seems somewhat arbitrary. Tutorials about LLMs tend to spend pages and pages explaining attention mechanisms, and pretty much gloss over the FFNs. But the FFNs take up twice as many parameters /2026/07/llm-parameter-counts as the attention mechanism in GPT-2. They're clearly highly important, and my very loose metaphor for why that is, is that attention is how the LLM works out what to think about, and the FFNs are where it does its thinking. It's not a perfect match for what's going on, but I think it's a decent working model for intuition. An MoE leverages this. Instead of having just one FFN per Transformers block, we have multiple, and we use a subset of them for each context vector. We have what is called a router, or a gating network. It takes the incoming context vectors, and for each one, decides which of these FFNs -- which experts -- to use. Then we feed the input into the experts that were selected for it, combine their results, and that's our final output -- like this: Diagram 3 : an outline of an MoE block Doing this gains us more "space" in the LLM for it to remember facts and ways of thinking about things -- we have multiple experts for that knowledge to be spread over. We could, of course, do that by dedicating more space to the FFNs -- for example, by having one bigger one. But with an MoE, because we only activate a subset of the experts for each context vector, we save on the amount of computing we do for each context vector. We need to keep all of the experts in RAM -- remember that we're routing to them per-context vector, so in a batch of sequences we're likely to be using most if not all of them. But we don't have to feed everything through all of them. So, that's the basics -- nothing conceptually difficult. What becomes more tricky is the implementation. How, concretely, does the router choose which experts to use for a given context vector, how do we implement that choice -- and how can we do all of that efficiently? And how do we train the router to make its choices? When I started on this project, my first step was a Google search for useful references. I came across this excellent summary https://www.ibm.com/think/topics/mixture-of-experts from IBM. In that post, they mention a number of papers; four seemed relevant