{
  "@context": "https://schema.org",
  "@id": "https://aggr.aymericbeaumet.com/items/huggingface-co/2026-02-26-mixture-of-experts-moes-in-transformers/#webpage",
  "@type": [
    "WebPage",
    "ArchiveComponent"
  ],
  "articleBody": "## Introduction\n\nOver the past few years, scaling dense language models has driven most progress in LLMs. From early models like the original [ULMFiT](https://nlp.fast.ai/classification/2018/05/15/introducing-ulmfit.html) (~30M parameters) or GPT-2 (1.5B parameters, which at the time was considered \"too dangerous to release\" 🧌), and eventually to today’s hundred-billion–parameter systems, the recipe was simple:\n\n> More data + more parameters gives better performance.\n\n[Scaling laws](https://huggingface.co/papers/2001.08361) reinforced this trend, but dense scaling has practical limits:\n\n- Training becomes increasingly expensive.\n- Inference latency grows.\n- Deployment requires significant memory and hardware.\n\nThis is where Mixture of Experts (MoEs) enter the picture.\n\n> If you're already familiar with MoEs and want to jump straight into the engineering work done in transformers, you can head directly to [Transformers and MoEs](https://huggingface.co/blog/moe-transformers#transformers-and-moes).\n\n## From Dense to Sparse: What Are MoEs?\n\nA Mixture of Experts model keeps the Transformer backbone, but replaces certain dense feed-forward layers with a set of **experts**. An “expert” is not a topic-specialized module (e.g., \"math expert\", \"code expert\"). It is simply a learnable sub-network. For each token, a **router** selects a small subset of experts to process it.\n\n| [![MoE routing diagram](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/moe_routing.png)](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/moe_routing.png) |\n| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Figure 1: Expert 1 among 4 experts is activated (Source: [Maarten Grootendorst](https://newsletter.maartengrootendorst.com/p/a-visual-guide-to-mixture-of-experts))                                                                                                |\n\nDifferent tokens activate different experts, based on their hidden representations.\n\n> Model capacity depends on total parameters, but inference speed depends on active parameters.\n\nThis is the key idea.\n\nFor example, take [`gpt-oss-20b`](https://huggingface.co/openai/gpt-oss-20b). It has 21B total parameters, but uses 4 active experts per token, out of a total of 32 experts. Considering the shared components plus the active experts, this model uses ~3.6B active parameters per token. Running this model on an M3 Ultra Mac, which has a memory bandwidth of about 800 GB, we could estimate generation speed as ~ `800 / (3.6 * 2)` in `bfloat16`, where each parameter takes 2 bytes. This yields about **111 tokens per second**. The actual performance number we get is ~115 tok/s, which is very close to the back-of-the-envelope calculation.\n\nYour browser does not support the video tag.\n\nThis super fast speed confirms the model works approximately as a 3.6B parameter one, but it has the same capacity (or quality) as a 21B parameter model.\n\n*(Note: speed would be even faster if we used kernels for the native mxfp4 quantization the model uses).*\n\nMoEs are attractive for these reasons:\n\n1. Better Compute Efficiency\n\n   Given a fixed training FLOP budget, MoEs often outperform dense counterparts.\n\n   | [![MoE vs Dense training graphs](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/faster_training.png)](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/faster_training.png) |\n   | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n   | Figure 2: Dense vs. MoE training curves (Source: [OLMoE: Open Mixture-of-Experts Language Models](https://huggingface.co/papers/2409.02060))                                                                                                                                        |\n\n   This means faster iteration and better scaling efficiency.\n\n2. A Natural Parallelization Axis\n\n   Experts provide a structural boundary in the computation graph. Since different tokens engage different experts, we can parallelize across experts (we discuss this later in [Expert Parallelism](https://huggingface.co/blog/moe-transformers#expert-parallelism)).\n\n3. Industry Adoption\n\n   Recent major MoE releases of open models that happened in the past few weeks include [Qwen 3.5](https://huggingface.co/collections/Qwen/qwen35), [MiniMax M2](https://huggingface.co/collections/MiniMaxAI/minimax-m2), [GLM-5](https://huggingface.co/collections/zai-org/glm-5), or [Kimi K2.5](https://huggingface.co/collections/moonshotai/kimi-k25).\n\n   The trend accelerated after the success of [DeepSeek R1](https://huggingface.co/deepseek-ai/DeepSeek-R1) in January 2025, building on earlier systems like [DeepSeek V2](https://huggingface.co/deepseek-ai/DeepSeek-V2). Another early MoE was [Mixtral-8x7B](https://huggingface.co/mistralai/Mixtral-8x7B-v0.1), released in December 2023.\n\n   | [![2-year timeline of MoE model addition in the transformers package](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/moe_2y_timeline.png)](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/moe_2y_timeline.png) |\n   | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n   | Figure 3: 2-year timeline of MoE model addition to the `transformers` library. DeepSeek R1 marks a clear inflection point.                                                                                                                                                                                                |\n\n   Closed labs use MoEs too. ChatGPT has long been [*rumored*](https://x.com/soumithchintala/status/1671267150101721090) to use a sparse architecture, and the open [gpt-oss models](https://huggingface.co/collections/openai/gpt-oss) certainly do.\n\n> If you want to learn more about MoEs in general, we strongly suggest reading [this blog](https://huggingface.co/blog/moe) and watching our recent [YouTube video on routing](https://youtu.be/CDnkFbW-uEQ).\n\n## Transformers and MoEs\n\nMost tooling in the ecosystem, including model loading, device placement, quantization, and backend execution was originally designed for **dense** models. MoEs challenge these assumptions.\n\nMaking MoEs **first-class citizens** in `transformers` means redesigning parts of the loading pipeline, execution model, and distributed abstractions, not just adding new model classes. We’ll focus on how the `transformers` library has evolved to support sparse architectures across:\n\n- [Weight Loading Refactor](https://huggingface.co/blog/moe-transformers#weight-loading-refactor)\n- [Expert Backend](https://huggingface.co/blog/moe-transformers#expert-backend)\n- [Expert Parallelism](https://huggingface.co/blog/moe-transformers#expert-parallelism)\n- [Training MoEs with transformers](https://huggingface.co/blog/moe-transformers#training-moes-with-transformers)\n\n## Weight Loading Refactor\n\n[`AutoModelForCausalLM.from_pretrained(\"model_id\")`](https://huggingface.co/docs/transformers/main/en/model_doc/auto#transformers.AutoModelForCausalLM.from_pretrained) downloads and loads model weights into a PyTorch model. For dense models, loading is relatively straightforward where each tensor in the checkpoint maps one-to-one to a parameter in the runtime module.\n\nFor MoEs, it’s more complicated. In most MoE checkpoints, each expert is serialized independently. If you peek inside the [DeepSeek-V3 checkpoint index](https://huggingface.co/deepseek-ai/DeepSeek-V3/raw/main/model.safetensors.index.json), you’ll see keys like:\n\n```bash\nmodel.layers.3.mlp.experts.0.gate_proj.weight\n...\nmodel.layers.3.mlp.experts.255.gate_proj.weight\n```\n\nEach expert has its own set of weight matrices, essentially 256 (0 to 255 total, taking DeepSeek-V3 as an example) small feed-forward networks saved side by side. At runtime, however, GPUs execute optimized kernels. Modern MoE kernels such as [grouped GEMMs and fused MoE implementations](https://huggingface.co/kernels-community/megablocks) are designed to process *all experts in a single operation*, not by looping over them one at a time.\n\nTo do that efficiently, they require expert weights to be packed into a single **contiguous tensor**.\n\nSo we have a mismatch:\n\n- **Checkpoint:** 256 separate tensors\n- **Runtime:** 1 packed tensor\n\nBridging this gap systematically is what the [weight loading refactor](https://github.com/huggingface/transformers/pull/41580) enables.\n\nWith the introduction of a [generic WeightConverter](https://huggingface.co/docs/transformers/main/en/weightconverter), the mental model shifted from:\n\n> A checkpoint already matches my runtime layout; loading is mostly a key-by-key copy.\n\nto:\n\n> A checkpoint is just a serialized source of tensors. Loading is a **conversion pipeline** that transforms them into the runtime layout we want.\n\n### Dynamic Weight Loading with `WeightConverter`\n\nThe central abstraction introduced by this refactor is **dynamic weight loading** via a [`WeightConverter`](https://huggingface.co/docs/transformers/main/en/weightconverter).\n\n`WeightConverter` lets us define:\n\n```\nsource key patterns → target key(s) + operations\n```\n\nPrimitive operations (chunk, concatenate, etc.) are composable. Two that are particularly useful for MoEs:\n\n- [`MergeModulelist`](https://github.com/huggingface/transformers/blob/main/src/transformers/core_model_loading.py) merges a list of tensors into a single tensor. For example, you can compose `MergeModulelist` with `Concatenate` to stack the experts in a MoE and pack them into one tensor.\n\n  ```python\n  WeightConverter(\n      [\"block_sparse_moe.experts.*.w1.weight\", \"block_sparse_moe.experts.*.w3.weight\",],\n      \"mlp.experts.gate_up_proj\",\n      operations=[\n          MergeModulelist(dim=0),\n          Concatenate(dim=1),\n      ],\n  )\n  ```\n\n- [`SplitModulelist`](https://github.com/huggingface/transformers/blob/b71de73468429eb02da18caa50e9b5200400a4ed/src/transformers/core_model_loading.py#L208) splits a tensor back into a list of tensors. For example, you can split a stack of experts back into individual experts.\n\n  ```python\n  WeightConverter(\n      \"mlp.experts.down_proj\",\n      \"block_sparse_moe.experts.*.w2.weight\",\n      operations=[SplitModulelist(dim=0)],\n  )\n  ```\n\n### Lazy Materialization of Tensors\n\nThe refactor improves not just *what* conversions exist, but *how* they’re scheduled.\n\nThe loader scans checkpoint keys once, matches them against converter patterns, and groups tensors per converter. Once a key is identified as needed, it’s registered as a *future* and materialized via a thread pool. Conversion operations run only once their dependencies are ready. For example, `MergeModulelist` waits until all experts for a layer are loaded.\n\nThis avoids repeated scans and reduces memory peaks.\n\n### Benchmark: Weight-Loading Pipeline Improvements\n\nTo evaluate the improvements introduced by the new weight-loading pipeline, we benchmarked the v4 vs v5 versions of `transformers`. The focus is on loading speed of large MoE models, which is often a bottleneck in training and inference.\n\nWe benchmarked v4 vs v5 using:\n\n- v4 branch: [https://github.com/ariG23498/transformers/tree/bench-v4](https://github.com/ariG23498/transformers/tree/bench-v4)\n- v5 branch: [https://github.com/ariG23498/transformers/tree/bench-v5](https://github.com/ariG23498/transformers/tree/bench-v5)\n\nExample:\n\n```python\nfrom transformers import AutoModelForCausalLM\n\nmodel_id = \"Qwen/Qwen1.5-110B-Chat\"\nmodel = AutoModelForCausalLM.from_pretrained(model_id)\n```\n\nTwo relevant environment variables:\n\n- `HF_ENABLE_PARALLEL_LOADING`: Enables parallel shard loading via threads.\n\n- `HF_DEACTIVATE_ASYNC_LOAD`:Disables the new async pipeline (v5 escape hatch).\n\n### Results\n\n**Model:** `Qwen/Qwen1.5-110B-Chat` **GPU:** 1× A100 (80GB)\n\n| Version | Strategy            | Loading Mode    | Time   |\n| ------- | ------------------- | --------------- | ------ |\n| v4.57.6 | `device_map=\"auto\"` | Threadpool      | 66.24s |\n| v4.57.6 | `device_map=\"auto\"` | Sequential      | 67.29s |\n| v4.57.6 | TP                  | —               | OOM    |\n| v5      | `device_map=\"auto\"` | Async (default) | 20.71s |\n| v5      | `device_map=\"auto\"` | Sync            | 45.3s  |\n| v5      | TP                  | Async           | 10.1s  |\n| v5      | TP                  | Sync            | 19.28s |\n\n| [![Loading benchmarks](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/loading_benchmark.png)](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/loading_benchmark.png) |\n| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| Figure 4: Loading benchmarks (v4 vs v5)                                                                                                                                                                                                                                        |\n\nThe speedup is not just “more threads.”\n\nIt’s the combination of **Single-pass routing**, **Async materialization**, and **Conversion-aware scheduling** which together avoid unnecessary materialization and memory peaks while enabling expert packing and projection fusion at load time.\n\n### Where Quantization Fits In\n\nWith this refactor we can now create the runtime module structure first and then convert the weights into the structure. We can now optionally attach quantization within the conversion pipeline, making quantization part of the weight loading pipeline itself. This is crucial because quantizing “per expert” only makes sense once experts exist in a predictable packed layout.\n\nThis end to end pipeline was not possible earlier and now it comes to the users as an exposed API.\n\n## Expert Backend\n\nOnce experts are packed into a single runtime tensor, another question arises:\n\n> How do you actually route through them efficiently?\n\nIn a Mixture of Experts model, each token is routed to different experts. This means the runtime must dispatch tokens to their selected expert weights, execute the projections efficiently, apply the routing weights and then collect and reorder the results.\n\nThis is what the [Experts Backend system](https://huggingface.co/docs/transformers/experts_interface) (introduced in [PR #42697](https://github.com/huggingface/transformers/pull/42697)) addresses. The Experts Backend introduces a **pluggable execution architecture** that decouples expert computation from the model implementation. Instead of hardcoding one dispatch strategy inside each MoE model, the system allows expert layers to dynamically select a backend at runtime.\n\nThis is implemented via a decorator pattern:\n\n```python\n@use_experts_implementation\n```\n\nThe decorator wraps expert classes and dispatches computation to the selected backend automatically.\n\nThree backends are currently provided:\n\n1. `eager` which loops over the selected experts and applies projections per expert. This is used for correctness reference and debugging.\n\n2. `batched_mm` uses the [`torch.bmm`](https://docs.pytorch.org/docs/stable/generated/torch.bmm.html) API. This duplicate selected expert weights per token and performs a single batched GEMM. This backend is very well suited for small batch, GPU-heavy workloads where memory is available.\n\n3. `grouped_mm` uses [`torch._grouped_mm`](https://docs.pytorch.org/docs/stable/generated/torch.nn.functional.grouped_mm.html) API. Here we sort tokens by expert ID, group them, and then perform a single grouped GEMM. This backend shines with large batches or memory-constrained setups.\n\n| [![](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/expert_backend.png)](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/expert_backend.png) |\n| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| Figure: Expert backend illustration                                                                                                                                                                                                                    |\n\n## Expert Parallelism\n\nMixture of Experts (MoE) models can have hundreds of billions of parameters (far more than what fits on a single GPU). Expert parallelism (EP) addresses this by distributing experts across multiple devices. Each device loads only its assigned subset of experts, computes for those experts and then participates in result aggregation. This approach scales models to far larger parameter counts without increasing computation cost because each token activates only a few experts.\n\nExpert parallelism is enabled via `enable_expert_parallel`:\n\n```python\nimport torch\nfrom transformers import AutoModelForCausalLM, AutoTokenizer\nfrom transformers.distributed.configuration_utils import DistributedConfig\n\ndistributed_config = DistributedConfig(enable_expert_parallel=True)\n\nmodel = AutoModelForCausalLM.from_pretrained(\n    \"openai/gpt-oss-120b\",\n    dtype=\"auto\",\n    distributed_config=distributed_config,\n)\n```\n\nLaunch with:\n\n```bash\ntorchrun --nproc-per-node N script.py\n```\n\nWhere `N` evenly divides the total number of experts, and possibly matches the number of GPUs in your node.\n\nWhen `enable_expert_parallel=True`, the model switches from the standard tensor-parallel (TP) plan to an expert-parallel (EP) plan with specialized sharding strategies.\n\nCore components of EP lie in:\n\n1. [`GroupedGemmParallel`](https://github.com/huggingface/transformers/blob/b71de73468429eb02da18caa50e9b5200400a4ed/src/transformers/integrations/tensor_parallel.py#L934): This splits the expert weights along the expert dimension (`dim=0`). Here each device loads only `num_experts / num_devices`.\n\n2. [`RouterParallel`](https://github.com/huggingface/transformers/blob/b71de73468429eb02da18caa50e9b5200400a4ed/src/transformers/integrations/tensor_parallel.py#L977): This remaps global expert indices to local indices, masks out experts not assigned to the current rank, ensures each device computes only with its local experts and uses an all-reduce to combine partial outputs across devices.\n\n## Training MoEs with Transformers\n\nMoEs are excellent for scaling inference, but training them is significantly more complex.\n\nMoEs have a Massive parameter count, the distributed expert communication is complicated, there are routing in-stabilities that need to be handled. To address this, we collaborated with **Unsloth** to enable significantly faster Mixture-of-Experts training:\n\n- \\~12× faster MoE training\n- \\>35% VRAM reduction\n- \\~6× longer context\n- 12–30× overall speedup compared to v4\n\nWe leverage the Expert Backend abstraction, standardize around PyTorch’s `torch._grouped_mm` API and use custom Triton grouped-GEMM + LoRA kernels. Unsloth builds on top of the Transformers (and TRL) optimizations to push performance further.\n\n> For full details, we recommend reading: [Unsloth’s official guide](https://unsloth.ai/docs/new/faster-moe)\n\n## Conclusion\n\nAs sparse architectures continue to evolve, we want the transformers library to evolve with them. If you’re building with MoEs or experimenting with new sparse ideas, we’d love to hear from you. Let us know what abstractions, kernels, or workflows you’d like to see next in `transformers`.\n",
  "authors": [],
  "captured_at": "2026-10-04T12:50:30.804479567+00:00",
  "category": "ai",
  "content_html": "<h2>Introduction</h2>\n<p>Over the past few years, scaling dense language models has driven most progress in LLMs. From early models like the original <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://nlp.fast.ai/classification/2018/05/15/introducing-ulmfit.html\">ULMFiT</a> (~30M parameters) or GPT-2 (1.5B parameters, which at the time was considered &quot;too dangerous to release&quot; 🧌), and eventually to today’s hundred-billion–parameter systems, the recipe was simple:</p>\n<blockquote>\n<p>More data + more parameters gives better performance.</p>\n</blockquote>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/papers/2001.08361\">Scaling laws</a> reinforced this trend, but dense scaling has practical limits:</p>\n<ul>\n<li>Training becomes increasingly expensive.</li>\n<li>Inference latency grows.</li>\n<li>Deployment requires significant memory and hardware.</li>\n</ul>\n<p>This is where Mixture of Experts (MoEs) enter the picture.</p>\n<blockquote>\n<p>If you're already familiar with MoEs and want to jump straight into the engineering work done in transformers, you can head directly to <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/blog/moe-transformers#transformers-and-moes\">Transformers and MoEs</a>.</p>\n</blockquote>\n<h2>From Dense to Sparse: What Are MoEs?</h2>\n<p>A Mixture of Experts model keeps the Transformer backbone, but replaces certain dense feed-forward layers with a set of <strong>experts</strong>. An “expert” is not a topic-specialized module (e.g., &quot;math expert&quot;, &quot;code expert&quot;). It is simply a learnable sub-network. For each token, a <strong>router</strong> selects a small subset of experts to process it.</p>\n<table>\n<thead>\n<tr>\n<th><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/moe_routing.png\"><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/moe_routing.png\" alt=\"MoE routing diagram\" loading=\"eager\" decoding=\"async\" fetchpriority=\"high\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></a></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Figure 1: Expert 1 among 4 experts is activated (Source: <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://newsletter.maartengrootendorst.com/p/a-visual-guide-to-mixture-of-experts\">Maarten Grootendorst</a>)</td>\n</tr>\n</tbody>\n</table>\n<p>Different tokens activate different experts, based on their hidden representations.</p>\n<blockquote>\n<p>Model capacity depends on total parameters, but inference speed depends on active parameters.</p>\n</blockquote>\n<p>This is the key idea.</p>\n<p>For example, take <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/openai/gpt-oss-20b\"><code>gpt-oss-20b</code></a>. It has 21B total parameters, but uses 4 active experts per token, out of a total of 32 experts. Considering the shared components plus the active experts, this model uses ~3.6B active parameters per token. Running this model on an M3 Ultra Mac, which has a memory bandwidth of about 800 GB, we could estimate generation speed as ~ <code>800 / (3.6 * 2)</code> in <code>bfloat16</code>, where each parameter takes 2 bytes. This yields about <strong>111 tokens per second</strong>. The actual performance number we get is ~115 tok/s, which is very close to the back-of-the-envelope calculation.</p>\n<p>Your browser does not support the video tag.</p>\n<p>This super fast speed confirms the model works approximately as a 3.6B parameter one, but it has the same capacity (or quality) as a 21B parameter model.</p>\n<p><em>(Note: speed would be even faster if we used kernels for the native mxfp4 quantization the model uses).</em></p>\n<p>MoEs are attractive for these reasons:</p>\n<ol>\n<li>\n<p>Better Compute Efficiency</p>\n<p>Given a fixed training FLOP budget, MoEs often outperform dense counterparts.</p>\n<table>\n<thead>\n<tr>\n<th><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/faster_training.png\"><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/faster_training.png\" alt=\"MoE vs Dense training graphs\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></a></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Figure 2: Dense vs. MoE training curves (Source: <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/papers/2409.02060\">OLMoE: Open Mixture-of-Experts Language Models</a>)</td>\n</tr>\n</tbody>\n</table>\n<p>This means faster iteration and better scaling efficiency.</p>\n</li>\n<li>\n<p>A Natural Parallelization Axis</p>\n<p>Experts provide a structural boundary in the computation graph. Since different tokens engage different experts, we can parallelize across experts (we discuss this later in <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/blog/moe-transformers#expert-parallelism\">Expert Parallelism</a>).</p>\n</li>\n<li>\n<p>Industry Adoption</p>\n<p>Recent major MoE releases of open models that happened in the past few weeks include <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/collections/Qwen/qwen35\">Qwen 3.5</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/collections/MiniMaxAI/minimax-m2\">MiniMax M2</a>, <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/collections/zai-org/glm-5\">GLM-5</a>, or <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/collections/moonshotai/kimi-k25\">Kimi K2.5</a>.</p>\n<p>The trend accelerated after the success of <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/deepseek-ai/DeepSeek-R1\">DeepSeek R1</a> in January 2025, building on earlier systems like <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/deepseek-ai/DeepSeek-V2\">DeepSeek V2</a>. Another early MoE was <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/mistralai/Mixtral-8x7B-v0.1\">Mixtral-8x7B</a>, released in December 2023.</p>\n<table>\n<thead>\n<tr>\n<th><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/moe_2y_timeline.png\"><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/moe_2y_timeline.png\" alt=\"2-year timeline of MoE model addition in the transformers package\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></a></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Figure 3: 2-year timeline of MoE model addition to the <code>transformers</code> library. DeepSeek R1 marks a clear inflection point.</td>\n</tr>\n</tbody>\n</table>\n<p>Closed labs use MoEs too. ChatGPT has long been <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://x.com/soumithchintala/status/1671267150101721090\"><em>rumored</em></a> to use a sparse architecture, and the open <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/collections/openai/gpt-oss\">gpt-oss models</a> certainly do.</p>\n</li>\n</ol>\n<blockquote>\n<p>If you want to learn more about MoEs in general, we strongly suggest reading <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/blog/moe\">this blog</a> and watching our recent <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://youtu.be/CDnkFbW-uEQ\">YouTube video on routing</a>.</p>\n</blockquote>\n<h2>Transformers and MoEs</h2>\n<p>Most tooling in the ecosystem, including model loading, device placement, quantization, and backend execution was originally designed for <strong>dense</strong> models. MoEs challenge these assumptions.</p>\n<p>Making MoEs <strong>first-class citizens</strong> in <code>transformers</code> means redesigning parts of the loading pipeline, execution model, and distributed abstractions, not just adding new model classes. We’ll focus on how the <code>transformers</code> library has evolved to support sparse architectures across:</p>\n<ul>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/blog/moe-transformers#weight-loading-refactor\">Weight Loading Refactor</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/blog/moe-transformers#expert-backend\">Expert Backend</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/blog/moe-transformers#expert-parallelism\">Expert Parallelism</a></li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/blog/moe-transformers#training-moes-with-transformers\">Training MoEs with transformers</a></li>\n</ul>\n<h2>Weight Loading Refactor</h2>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/docs/transformers/main/en/model_doc/auto#transformers.AutoModelForCausalLM.from_pretrained\"><code>AutoModelForCausalLM.from_pretrained(&quot;model_id&quot;)</code></a> downloads and loads model weights into a PyTorch model. For dense models, loading is relatively straightforward where each tensor in the checkpoint maps one-to-one to a parameter in the runtime module.</p>\n<p>For MoEs, it’s more complicated. In most MoE checkpoints, each expert is serialized independently. If you peek inside the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/deepseek-ai/DeepSeek-V3/raw/main/model.safetensors.index.json\">DeepSeek-V3 checkpoint index</a>, you’ll see keys like:</p>\n<pre><code class=\"language-bash\"><span class=\"code-snippet\" data-language=\"Shell\"><span class=\"syntax-source syntax-shell syntax-bash\"><span class=\"syntax-meta syntax-function-call syntax-shell\"><span class=\"syntax-variable syntax-function syntax-shell\">model.layers.3.mlp.experts.0.gate_proj.weight</span></span>\n<span class=\"syntax-meta syntax-function-call syntax-shell\"><span class=\"syntax-variable syntax-function syntax-shell\">...</span></span>\n<span class=\"syntax-meta syntax-function-call syntax-shell\"><span class=\"syntax-variable syntax-function syntax-shell\">model.layers.3.mlp.experts.255.gate_proj.weight</span></span>\n</span></span></code></pre>\n<p>Each expert has its own set of weight matrices, essentially 256 (0 to 255 total, taking DeepSeek-V3 as an example) small feed-forward networks saved side by side. At runtime, however, GPUs execute optimized kernels. Modern MoE kernels such as <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/kernels-community/megablocks\">grouped GEMMs and fused MoE implementations</a> are designed to process <em>all experts in a single operation</em>, not by looping over them one at a time.</p>\n<p>To do that efficiently, they require expert weights to be packed into a single <strong>contiguous tensor</strong>.</p>\n<p>So we have a mismatch:</p>\n<ul>\n<li><strong>Checkpoint:</strong> 256 separate tensors</li>\n<li><strong>Runtime:</strong> 1 packed tensor</li>\n</ul>\n<p>Bridging this gap systematically is what the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/huggingface/transformers/pull/41580\">weight loading refactor</a> enables.</p>\n<p>With the introduction of a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/docs/transformers/main/en/weightconverter\">generic WeightConverter</a>, the mental model shifted from:</p>\n<blockquote>\n<p>A checkpoint already matches my runtime layout; loading is mostly a key-by-key copy.</p>\n</blockquote>\n<p>to:</p>\n<blockquote>\n<p>A checkpoint is just a serialized source of tensors. Loading is a <strong>conversion pipeline</strong> that transforms them into the runtime layout we want.</p>\n</blockquote>\n<h3>Dynamic Weight Loading with <code>WeightConverter</code></h3>\n<p>The central abstraction introduced by this refactor is <strong>dynamic weight loading</strong> via a <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/docs/transformers/main/en/weightconverter\"><code>WeightConverter</code></a>.</p>\n<p><code>WeightConverter</code> lets us define:</p>\n<pre><code><span class=\"code-snippet\">source key patterns → target key(s) + operations\n</span></code></pre>\n<p>Primitive operations (chunk, concatenate, etc.) are composable. Two that are particularly useful for MoEs:</p>\n<ul>\n<li>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/huggingface/transformers/blob/main/src/transformers/core_model_loading.py\"><code>MergeModulelist</code></a> merges a list of tensors into a single tensor. For example, you can compose <code>MergeModulelist</code> with <code>Concatenate</code> to stack the experts in a MoE and pack them into one tensor.</p>\n<pre><code class=\"language-python\"><span class=\"code-snippet\" data-language=\"Python\"><span class=\"syntax-source syntax-python\"><span class=\"syntax-meta syntax-function-call syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-function syntax-python\">WeightConverter</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-begin syntax-python\">(</span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-python\">\n    <span class=\"syntax-meta syntax-structure syntax-list syntax-python\"><span class=\"syntax-punctuation syntax-section syntax-list syntax-begin syntax-python\">[</span><span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-python\">&quot;</span></span></span><span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\">block_sparse_moe.experts.*.w1.weight<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-python\">&quot;</span></span></span><span class=\"syntax-punctuation syntax-separator syntax-list syntax-python\">,</span> <span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-python\">&quot;</span></span></span><span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\">block_sparse_moe.experts.*.w3.weight<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-python\">&quot;</span></span></span><span class=\"syntax-punctuation syntax-separator syntax-list syntax-python\">,</span><span class=\"syntax-punctuation syntax-section syntax-list syntax-end syntax-python\">]</span></span><span class=\"syntax-punctuation syntax-separator syntax-arguments syntax-python\">,</span>\n    <span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-python\">&quot;</span></span></span><span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\">mlp.experts.gate_up_proj<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-python\">&quot;</span></span></span><span class=\"syntax-punctuation syntax-separator syntax-arguments syntax-python\">,</span>\n    <span class=\"syntax-variable syntax-parameter syntax-python\">operations</span><span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span><span class=\"syntax-meta syntax-structure syntax-list syntax-python\"><span class=\"syntax-punctuation syntax-section syntax-list syntax-begin syntax-python\">[</span>\n        <span class=\"syntax-meta syntax-function-call syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-function syntax-python\">MergeModulelist</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-begin syntax-python\">(</span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-python\"><span class=\"syntax-variable syntax-parameter syntax-python\">dim</span><span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span><span class=\"syntax-constant syntax-numeric syntax-integer syntax-decimal syntax-python\">0</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-end syntax-python\">)</span></span><span class=\"syntax-punctuation syntax-separator syntax-list syntax-python\">,</span>\n        <span class=\"syntax-meta syntax-function-call syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-function syntax-python\">Concatenate</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-begin syntax-python\">(</span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-python\"><span class=\"syntax-variable syntax-parameter syntax-python\">dim</span><span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span><span class=\"syntax-constant syntax-numeric syntax-integer syntax-decimal syntax-python\">1</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-end syntax-python\">)</span></span><span class=\"syntax-punctuation syntax-separator syntax-list syntax-python\">,</span>\n    <span class=\"syntax-punctuation syntax-section syntax-list syntax-end syntax-python\">]</span></span><span class=\"syntax-punctuation syntax-separator syntax-arguments syntax-python\">,</span>\n</span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-end syntax-python\">)</span></span>\n</span></span></code></pre>\n</li>\n<li>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/huggingface/transformers/blob/b71de73468429eb02da18caa50e9b5200400a4ed/src/transformers/core_model_loading.py#L208\"><code>SplitModulelist</code></a> splits a tensor back into a list of tensors. For example, you can split a stack of experts back into individual experts.</p>\n<pre><code class=\"language-python\"><span class=\"code-snippet\" data-language=\"Python\"><span class=\"syntax-source syntax-python\"><span class=\"syntax-meta syntax-function-call syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-function syntax-python\">WeightConverter</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-begin syntax-python\">(</span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-python\">\n    <span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-python\">&quot;</span></span></span><span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\">mlp.experts.down_proj<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-python\">&quot;</span></span></span><span class=\"syntax-punctuation syntax-separator syntax-arguments syntax-python\">,</span>\n    <span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-python\">&quot;</span></span></span><span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\">block_sparse_moe.experts.*.w2.weight<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-python\">&quot;</span></span></span><span class=\"syntax-punctuation syntax-separator syntax-arguments syntax-python\">,</span>\n    <span class=\"syntax-variable syntax-parameter syntax-python\">operations</span><span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span><span class=\"syntax-meta syntax-structure syntax-list syntax-python\"><span class=\"syntax-punctuation syntax-section syntax-list syntax-begin syntax-python\">[</span><span class=\"syntax-meta syntax-function-call syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-function syntax-python\">SplitModulelist</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-begin syntax-python\">(</span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-python\"><span class=\"syntax-variable syntax-parameter syntax-python\">dim</span><span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span><span class=\"syntax-constant syntax-numeric syntax-integer syntax-decimal syntax-python\">0</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-end syntax-python\">)</span></span><span class=\"syntax-punctuation syntax-section syntax-list syntax-end syntax-python\">]</span></span><span class=\"syntax-punctuation syntax-separator syntax-arguments syntax-python\">,</span>\n</span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-end syntax-python\">)</span></span>\n</span></span></code></pre>\n</li>\n</ul>\n<h3>Lazy Materialization of Tensors</h3>\n<p>The refactor improves not just <em>what</em> conversions exist, but <em>how</em> they’re scheduled.</p>\n<p>The loader scans checkpoint keys once, matches them against converter patterns, and groups tensors per converter. Once a key is identified as needed, it’s registered as a <em>future</em> and materialized via a thread pool. Conversion operations run only once their dependencies are ready. For example, <code>MergeModulelist</code> waits until all experts for a layer are loaded.</p>\n<p>This avoids repeated scans and reduces memory peaks.</p>\n<h3>Benchmark: Weight-Loading Pipeline Improvements</h3>\n<p>To evaluate the improvements introduced by the new weight-loading pipeline, we benchmarked the v4 vs v5 versions of <code>transformers</code>. The focus is on loading speed of large MoE models, which is often a bottleneck in training and inference.</p>\n<p>We benchmarked v4 vs v5 using:</p>\n<ul>\n<li>v4 branch: <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/ariG23498/transformers/tree/bench-v4\">https://github.com/ariG23498/transformers/tree/bench-v4</a></li>\n<li>v5 branch: <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/ariG23498/transformers/tree/bench-v5\">https://github.com/ariG23498/transformers/tree/bench-v5</a></li>\n</ul>\n<p>Example:</p>\n<pre><code class=\"language-python\"><span class=\"code-snippet\" data-language=\"Python\"><span class=\"syntax-source syntax-python\"><span class=\"syntax-meta syntax-statement syntax-import syntax-python\"><span class=\"syntax-keyword syntax-control syntax-import syntax-from syntax-python\">from</span></span><span class=\"syntax-meta syntax-statement syntax-import syntax-python\"><span class=\"syntax-meta syntax-import-source syntax-python\"> <span class=\"syntax-meta syntax-import-path syntax-python\"><span class=\"syntax-meta syntax-import-name syntax-python\">transformers</span></span> <span class=\"syntax-meta syntax-statement syntax-import syntax-python\"><span class=\"syntax-keyword syntax-control syntax-import syntax-python\">import</span></span></span></span><span class=\"syntax-meta syntax-statement syntax-import syntax-python\"></span><span class=\"syntax-meta syntax-statement syntax-import syntax-python\"> <span class=\"syntax-meta syntax-generic-name syntax-python\">AutoModelForCausalLM</span></span>\n\n<span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">model_id</span></span> <span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span> <span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-python\">&quot;</span></span></span><span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\">Qwen/Qwen1.5-110B-Chat<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-python\">&quot;</span></span></span>\n<span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">model</span></span> <span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span> <span class=\"syntax-meta syntax-function-call syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">AutoModelForCausalLM</span><span class=\"syntax-punctuation syntax-accessor syntax-dot syntax-python\">.</span></span><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-function syntax-python\">from_pretrained</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-begin syntax-python\">(</span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">model_id</span></span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-end syntax-python\">)</span></span>\n</span></span></code></pre>\n<p>Two relevant environment variables:</p>\n<ul>\n<li>\n<p><code>HF_ENABLE_PARALLEL_LOADING</code>: Enables parallel shard loading via threads.</p>\n</li>\n<li>\n<p><code>HF_DEACTIVATE_ASYNC_LOAD</code>:Disables the new async pipeline (v5 escape hatch).</p>\n</li>\n</ul>\n<h3>Results</h3>\n<p><strong>Model:</strong> <code>Qwen/Qwen1.5-110B-Chat</code> <strong>GPU:</strong> 1× A100 (80GB)</p>\n<table>\n<thead>\n<tr>\n<th>Version</th>\n<th>Strategy</th>\n<th>Loading Mode</th>\n<th>Time</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>v4.57.6</td>\n<td><code>device_map=&quot;auto&quot;</code></td>\n<td>Threadpool</td>\n<td>66.24s</td>\n</tr>\n<tr>\n<td>v4.57.6</td>\n<td><code>device_map=&quot;auto&quot;</code></td>\n<td>Sequential</td>\n<td>67.29s</td>\n</tr>\n<tr>\n<td>v4.57.6</td>\n<td>TP</td>\n<td>—</td>\n<td>OOM</td>\n</tr>\n<tr>\n<td>v5</td>\n<td><code>device_map=&quot;auto&quot;</code></td>\n<td>Async (default)</td>\n<td>20.71s</td>\n</tr>\n<tr>\n<td>v5</td>\n<td><code>device_map=&quot;auto&quot;</code></td>\n<td>Sync</td>\n<td>45.3s</td>\n</tr>\n<tr>\n<td>v5</td>\n<td>TP</td>\n<td>Async</td>\n<td>10.1s</td>\n</tr>\n<tr>\n<td>v5</td>\n<td>TP</td>\n<td>Sync</td>\n<td>19.28s</td>\n</tr>\n</tbody>\n</table>\n<table>\n<thead>\n<tr>\n<th><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/loading_benchmark.png\"><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/loading_benchmark.png\" alt=\"Loading benchmarks\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></a></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Figure 4: Loading benchmarks (v4 vs v5)</td>\n</tr>\n</tbody>\n</table>\n<p>The speedup is not just “more threads.”</p>\n<p>It’s the combination of <strong>Single-pass routing</strong>, <strong>Async materialization</strong>, and <strong>Conversion-aware scheduling</strong> which together avoid unnecessary materialization and memory peaks while enabling expert packing and projection fusion at load time.</p>\n<h3>Where Quantization Fits In</h3>\n<p>With this refactor we can now create the runtime module structure first and then convert the weights into the structure. We can now optionally attach quantization within the conversion pipeline, making quantization part of the weight loading pipeline itself. This is crucial because quantizing “per expert” only makes sense once experts exist in a predictable packed layout.</p>\n<p>This end to end pipeline was not possible earlier and now it comes to the users as an exposed API.</p>\n<h2>Expert Backend</h2>\n<p>Once experts are packed into a single runtime tensor, another question arises:</p>\n<blockquote>\n<p>How do you actually route through them efficiently?</p>\n</blockquote>\n<p>In a Mixture of Experts model, each token is routed to different experts. This means the runtime must dispatch tokens to their selected expert weights, execute the projections efficiently, apply the routing weights and then collect and reorder the results.</p>\n<p>This is what the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/docs/transformers/experts_interface\">Experts Backend system</a> (introduced in <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/huggingface/transformers/pull/42697\">PR #42697</a>) addresses. The Experts Backend introduces a <strong>pluggable execution architecture</strong> that decouples expert computation from the model implementation. Instead of hardcoding one dispatch strategy inside each MoE model, the system allows expert layers to dynamically select a backend at runtime.</p>\n<p>This is implemented via a decorator pattern:</p>\n<pre><code class=\"language-python\"><span class=\"code-snippet\" data-language=\"Python\"><span class=\"syntax-source syntax-python\"><span class=\"syntax-meta syntax-annotation syntax-python\"><span class=\"syntax-punctuation syntax-definition syntax-annotation syntax-python\">@</span><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-annotation syntax-python\">use_experts_implementation</span></span></span>\n</span></span></code></pre>\n<p>The decorator wraps expert classes and dispatches computation to the selected backend automatically.</p>\n<p>Three backends are currently provided:</p>\n<ol>\n<li>\n<p><code>eager</code> which loops over the selected experts and applies projections per expert. This is used for correctness reference and debugging.</p>\n</li>\n<li>\n<p><code>batched_mm</code> uses the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://docs.pytorch.org/docs/stable/generated/torch.bmm.html\"><code>torch.bmm</code></a> API. This duplicate selected expert weights per token and performs a single batched GEMM. This backend is very well suited for small batch, GPU-heavy workloads where memory is available.</p>\n</li>\n<li>\n<p><code>grouped_mm</code> uses <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://docs.pytorch.org/docs/stable/generated/torch.nn.functional.grouped_mm.html\"><code>torch._grouped_mm</code></a> API. Here we sort tokens by expert ID, group them, and then perform a single grouped GEMM. This backend shines with large batches or memory-constrained setups.</p>\n</li>\n</ol>\n<table>\n<thead>\n<tr>\n<th><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/expert_backend.png\"><picture class=\"article-picture article-picture-fallback\" style=\"--image-ratio:16 / 9\"><img src=\"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/expert_backend.png\" alt=\"\" loading=\"lazy\" decoding=\"async\" fetchpriority=\"low\" referrerpolicy=\"no-referrer\" class=\"progressive-image\"></picture></a></th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Figure: Expert backend illustration</td>\n</tr>\n</tbody>\n</table>\n<h2>Expert Parallelism</h2>\n<p>Mixture of Experts (MoE) models can have hundreds of billions of parameters (far more than what fits on a single GPU). Expert parallelism (EP) addresses this by distributing experts across multiple devices. Each device loads only its assigned subset of experts, computes for those experts and then participates in result aggregation. This approach scales models to far larger parameter counts without increasing computation cost because each token activates only a few experts.</p>\n<p>Expert parallelism is enabled via <code>enable_expert_parallel</code>:</p>\n<pre><code class=\"language-python\"><span class=\"code-snippet\" data-language=\"Python\"><span class=\"syntax-source syntax-python\"><span class=\"syntax-meta syntax-statement syntax-import syntax-python\"><span class=\"syntax-keyword syntax-control syntax-import syntax-python\">import</span> <span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">torch</span></span></span>\n<span class=\"syntax-meta syntax-statement syntax-import syntax-python\"><span class=\"syntax-keyword syntax-control syntax-import syntax-from syntax-python\">from</span></span><span class=\"syntax-meta syntax-statement syntax-import syntax-python\"><span class=\"syntax-meta syntax-import-source syntax-python\"> <span class=\"syntax-meta syntax-import-path syntax-python\"><span class=\"syntax-meta syntax-import-name syntax-python\">transformers</span></span> <span class=\"syntax-meta syntax-statement syntax-import syntax-python\"><span class=\"syntax-keyword syntax-control syntax-import syntax-python\">import</span></span></span></span><span class=\"syntax-meta syntax-statement syntax-import syntax-python\"></span><span class=\"syntax-meta syntax-statement syntax-import syntax-python\"> <span class=\"syntax-meta syntax-generic-name syntax-python\">AutoModelForCausalLM</span><span class=\"syntax-punctuation syntax-separator syntax-import-list syntax-python\">,</span> <span class=\"syntax-meta syntax-generic-name syntax-python\">AutoTokenizer</span></span>\n<span class=\"syntax-meta syntax-statement syntax-import syntax-python\"><span class=\"syntax-keyword syntax-control syntax-import syntax-from syntax-python\">from</span></span><span class=\"syntax-meta syntax-statement syntax-import syntax-python\"><span class=\"syntax-meta syntax-import-source syntax-python\"> <span class=\"syntax-meta syntax-import-path syntax-python\"><span class=\"syntax-meta syntax-import-name syntax-python\">transformers</span><span class=\"syntax-punctuation syntax-accessor syntax-dot syntax-python\">.</span><span class=\"syntax-meta syntax-import-name syntax-python\">distributed</span><span class=\"syntax-punctuation syntax-accessor syntax-dot syntax-python\">.</span><span class=\"syntax-meta syntax-import-name syntax-python\">configuration_utils</span></span> <span class=\"syntax-meta syntax-statement syntax-import syntax-python\"><span class=\"syntax-keyword syntax-control syntax-import syntax-python\">import</span></span></span></span><span class=\"syntax-meta syntax-statement syntax-import syntax-python\"></span><span class=\"syntax-meta syntax-statement syntax-import syntax-python\"> <span class=\"syntax-meta syntax-generic-name syntax-python\">DistributedConfig</span></span>\n\n<span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">distributed_config</span></span> <span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span> <span class=\"syntax-meta syntax-function-call syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-function syntax-python\">DistributedConfig</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-begin syntax-python\">(</span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-python\"><span class=\"syntax-variable syntax-parameter syntax-python\">enable_expert_parallel</span><span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span><span class=\"syntax-constant syntax-language syntax-python\">True</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-end syntax-python\">)</span></span>\n\n<span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">model</span></span> <span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span> <span class=\"syntax-meta syntax-function-call syntax-python\"><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">AutoModelForCausalLM</span><span class=\"syntax-punctuation syntax-accessor syntax-dot syntax-python\">.</span></span><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-variable syntax-function syntax-python\">from_pretrained</span></span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-begin syntax-python\">(</span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-python\">\n    <span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-python\">&quot;</span></span></span><span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\">openai/gpt-oss-120b<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-python\">&quot;</span></span></span><span class=\"syntax-punctuation syntax-separator syntax-arguments syntax-python\">,</span>\n    <span class=\"syntax-variable syntax-parameter syntax-python\">dtype</span><span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span><span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-python\">&quot;</span></span></span><span class=\"syntax-meta syntax-string syntax-python\"><span class=\"syntax-string syntax-quoted syntax-double syntax-python\">auto<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-python\">&quot;</span></span></span><span class=\"syntax-punctuation syntax-separator syntax-arguments syntax-python\">,</span>\n    <span class=\"syntax-variable syntax-parameter syntax-python\">distributed_config</span><span class=\"syntax-keyword syntax-operator syntax-assignment syntax-python\">=</span><span class=\"syntax-meta syntax-qualified-name syntax-python\"><span class=\"syntax-meta syntax-generic-name syntax-python\">distributed_config</span></span><span class=\"syntax-punctuation syntax-separator syntax-arguments syntax-python\">,</span>\n</span><span class=\"syntax-punctuation syntax-section syntax-arguments syntax-end syntax-python\">)</span></span>\n</span></span></code></pre>\n<p>Launch with:</p>\n<pre><code class=\"language-bash\"><span class=\"code-snippet\" data-language=\"Shell\"><span class=\"syntax-source syntax-shell syntax-bash\"><span class=\"syntax-meta syntax-function-call syntax-shell\"><span class=\"syntax-variable syntax-function syntax-shell\">torchrun</span></span><span class=\"syntax-meta syntax-function-call syntax-arguments syntax-shell\"><span class=\"syntax-variable syntax-parameter syntax-option syntax-shell\"><span class=\"syntax-punctuation syntax-definition syntax-parameter syntax-shell\"> --</span>nproc-per-node</span> N script.py</span>\n</span></span></code></pre>\n<p>Where <code>N</code> evenly divides the total number of experts, and possibly matches the number of GPUs in your node.</p>\n<p>When <code>enable_expert_parallel=True</code>, the model switches from the standard tensor-parallel (TP) plan to an expert-parallel (EP) plan with specialized sharding strategies.</p>\n<p>Core components of EP lie in:</p>\n<ol>\n<li>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/huggingface/transformers/blob/b71de73468429eb02da18caa50e9b5200400a4ed/src/transformers/integrations/tensor_parallel.py#L934\"><code>GroupedGemmParallel</code></a>: This splits the expert weights along the expert dimension (<code>dim=0</code>). Here each device loads only <code>num_experts / num_devices</code>.</p>\n</li>\n<li>\n<p><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/huggingface/transformers/blob/b71de73468429eb02da18caa50e9b5200400a4ed/src/transformers/integrations/tensor_parallel.py#L977\"><code>RouterParallel</code></a>: This remaps global expert indices to local indices, masks out experts not assigned to the current rank, ensures each device computes only with its local experts and uses an all-reduce to combine partial outputs across devices.</p>\n</li>\n</ol>\n<h2>Training MoEs with Transformers</h2>\n<p>MoEs are excellent for scaling inference, but training them is significantly more complex.</p>\n<p>MoEs have a Massive parameter count, the distributed expert communication is complicated, there are routing in-stabilities that need to be handled. To address this, we collaborated with <strong>Unsloth</strong> to enable significantly faster Mixture-of-Experts training:</p>\n<ul>\n<li>~12× faster MoE training</li>\n<li>&gt;35% VRAM reduction</li>\n<li>~6× longer context</li>\n<li>12–30× overall speedup compared to v4</li>\n</ul>\n<p>We leverage the Expert Backend abstraction, standardize around PyTorch’s <code>torch._grouped_mm</code> API and use custom Triton grouped-GEMM + LoRA kernels. Unsloth builds on top of the Transformers (and TRL) optimizations to push performance further.</p>\n<blockquote>\n<p>For full details, we recommend reading: <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://unsloth.ai/docs/new/faster-moe\">Unsloth’s official guide</a></p>\n</blockquote>\n<h2>Conclusion</h2>\n<p>As sparse architectures continue to evolve, we want the transformers library to evolve with them. If you’re building with MoEs or experimenting with new sparse ideas, we’d love to hear from you. Let us know what abstractions, kernels, or workflows you’d like to see next in <code>transformers</code>.</p>\n",
  "content_markdown": "## Introduction\n\nOver the past few years, scaling dense language models has driven most progress in LLMs. From early models like the original [ULMFiT](https://nlp.fast.ai/classification/2018/05/15/introducing-ulmfit.html) (~30M parameters) or GPT-2 (1.5B parameters, which at the time was considered \"too dangerous to release\" 🧌), and eventually to today’s hundred-billion–parameter systems, the recipe was simple:\n\n> More data + more parameters gives better performance.\n\n[Scaling laws](https://huggingface.co/papers/2001.08361) reinforced this trend, but dense scaling has practical limits:\n\n- Training becomes increasingly expensive.\n- Inference latency grows.\n- Deployment requires significant memory and hardware.\n\nThis is where Mixture of Experts (MoEs) enter the picture.\n\n> If you're already familiar with MoEs and want to jump straight into the engineering work done in transformers, you can head directly to [Transformers and MoEs](https://huggingface.co/blog/moe-transformers#transformers-and-moes).\n\n## From Dense to Sparse: What Are MoEs?\n\nA Mixture of Experts model keeps the Transformer backbone, but replaces certain dense feed-forward layers with a set of **experts**. An “expert” is not a topic-specialized module (e.g., \"math expert\", \"code expert\"). It is simply a learnable sub-network. For each token, a **router** selects a small subset of experts to process it.\n\n| [![MoE routing diagram](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/moe_routing.png)](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/moe_routing.png) |\n| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Figure 1: Expert 1 among 4 experts is activated (Source: [Maarten Grootendorst](https://newsletter.maartengrootendorst.com/p/a-visual-guide-to-mixture-of-experts))                                                                                                |\n\nDifferent tokens activate different experts, based on their hidden representations.\n\n> Model capacity depends on total parameters, but inference speed depends on active parameters.\n\nThis is the key idea.\n\nFor example, take [`gpt-oss-20b`](https://huggingface.co/openai/gpt-oss-20b). It has 21B total parameters, but uses 4 active experts per token, out of a total of 32 experts. Considering the shared components plus the active experts, this model uses ~3.6B active parameters per token. Running this model on an M3 Ultra Mac, which has a memory bandwidth of about 800 GB, we could estimate generation speed as ~ `800 / (3.6 * 2)` in `bfloat16`, where each parameter takes 2 bytes. This yields about **111 tokens per second**. The actual performance number we get is ~115 tok/s, which is very close to the back-of-the-envelope calculation.\n\nYour browser does not support the video tag.\n\nThis super fast speed confirms the model works approximately as a 3.6B parameter one, but it has the same capacity (or quality) as a 21B parameter model.\n\n*(Note: speed would be even faster if we used kernels for the native mxfp4 quantization the model uses).*\n\nMoEs are attractive for these reasons:\n\n1. Better Compute Efficiency\n\n   Given a fixed training FLOP budget, MoEs often outperform dense counterparts.\n\n   | [![MoE vs Dense training graphs](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/faster_training.png)](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/faster_training.png) |\n   | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n   | Figure 2: Dense vs. MoE training curves (Source: [OLMoE: Open Mixture-of-Experts Language Models](https://huggingface.co/papers/2409.02060))                                                                                                                                        |\n\n   This means faster iteration and better scaling efficiency.\n\n2. A Natural Parallelization Axis\n\n   Experts provide a structural boundary in the computation graph. Since different tokens engage different experts, we can parallelize across experts (we discuss this later in [Expert Parallelism](https://huggingface.co/blog/moe-transformers#expert-parallelism)).\n\n3. Industry Adoption\n\n   Recent major MoE releases of open models that happened in the past few weeks include [Qwen 3.5](https://huggingface.co/collections/Qwen/qwen35), [MiniMax M2](https://huggingface.co/collections/MiniMaxAI/minimax-m2), [GLM-5](https://huggingface.co/collections/zai-org/glm-5), or [Kimi K2.5](https://huggingface.co/collections/moonshotai/kimi-k25).\n\n   The trend accelerated after the success of [DeepSeek R1](https://huggingface.co/deepseek-ai/DeepSeek-R1) in January 2025, building on earlier systems like [DeepSeek V2](https://huggingface.co/deepseek-ai/DeepSeek-V2). Another early MoE was [Mixtral-8x7B](https://huggingface.co/mistralai/Mixtral-8x7B-v0.1), released in December 2023.\n\n   | [![2-year timeline of MoE model addition in the transformers package](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/moe_2y_timeline.png)](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/moe_2y_timeline.png) |\n   | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n   | Figure 3: 2-year timeline of MoE model addition to the `transformers` library. DeepSeek R1 marks a clear inflection point.                                                                                                                                                                                                |\n\n   Closed labs use MoEs too. ChatGPT has long been [*rumored*](https://x.com/soumithchintala/status/1671267150101721090) to use a sparse architecture, and the open [gpt-oss models](https://huggingface.co/collections/openai/gpt-oss) certainly do.\n\n> If you want to learn more about MoEs in general, we strongly suggest reading [this blog](https://huggingface.co/blog/moe) and watching our recent [YouTube video on routing](https://youtu.be/CDnkFbW-uEQ).\n\n## Transformers and MoEs\n\nMost tooling in the ecosystem, including model loading, device placement, quantization, and backend execution was originally designed for **dense** models. MoEs challenge these assumptions.\n\nMaking MoEs **first-class citizens** in `transformers` means redesigning parts of the loading pipeline, execution model, and distributed abstractions, not just adding new model classes. We’ll focus on how the `transformers` library has evolved to support sparse architectures across:\n\n- [Weight Loading Refactor](https://huggingface.co/blog/moe-transformers#weight-loading-refactor)\n- [Expert Backend](https://huggingface.co/blog/moe-transformers#expert-backend)\n- [Expert Parallelism](https://huggingface.co/blog/moe-transformers#expert-parallelism)\n- [Training MoEs with transformers](https://huggingface.co/blog/moe-transformers#training-moes-with-transformers)\n\n## Weight Loading Refactor\n\n[`AutoModelForCausalLM.from_pretrained(\"model_id\")`](https://huggingface.co/docs/transformers/main/en/model_doc/auto#transformers.AutoModelForCausalLM.from_pretrained) downloads and loads model weights into a PyTorch model. For dense models, loading is relatively straightforward where each tensor in the checkpoint maps one-to-one to a parameter in the runtime module.\n\nFor MoEs, it’s more complicated. In most MoE checkpoints, each expert is serialized independently. If you peek inside the [DeepSeek-V3 checkpoint index](https://huggingface.co/deepseek-ai/DeepSeek-V3/raw/main/model.safetensors.index.json), you’ll see keys like:\n\n```bash\nmodel.layers.3.mlp.experts.0.gate_proj.weight\n...\nmodel.layers.3.mlp.experts.255.gate_proj.weight\n```\n\nEach expert has its own set of weight matrices, essentially 256 (0 to 255 total, taking DeepSeek-V3 as an example) small feed-forward networks saved side by side. At runtime, however, GPUs execute optimized kernels. Modern MoE kernels such as [grouped GEMMs and fused MoE implementations](https://huggingface.co/kernels-community/megablocks) are designed to process *all experts in a single operation*, not by looping over them one at a time.\n\nTo do that efficiently, they require expert weights to be packed into a single **contiguous tensor**.\n\nSo we have a mismatch:\n\n- **Checkpoint:** 256 separate tensors\n- **Runtime:** 1 packed tensor\n\nBridging this gap systematically is what the [weight loading refactor](https://github.com/huggingface/transformers/pull/41580) enables.\n\nWith the introduction of a [generic WeightConverter](https://huggingface.co/docs/transformers/main/en/weightconverter), the mental model shifted from:\n\n> A checkpoint already matches my runtime layout; loading is mostly a key-by-key copy.\n\nto:\n\n> A checkpoint is just a serialized source of tensors. Loading is a **conversion pipeline** that transforms them into the runtime layout we want.\n\n### Dynamic Weight Loading with `WeightConverter`\n\nThe central abstraction introduced by this refactor is **dynamic weight loading** via a [`WeightConverter`](https://huggingface.co/docs/transformers/main/en/weightconverter).\n\n`WeightConverter` lets us define:\n\n```\nsource key patterns → target key(s) + operations\n```\n\nPrimitive operations (chunk, concatenate, etc.) are composable. Two that are particularly useful for MoEs:\n\n- [`MergeModulelist`](https://github.com/huggingface/transformers/blob/main/src/transformers/core_model_loading.py) merges a list of tensors into a single tensor. For example, you can compose `MergeModulelist` with `Concatenate` to stack the experts in a MoE and pack them into one tensor.\n\n  ```python\n  WeightConverter(\n      [\"block_sparse_moe.experts.*.w1.weight\", \"block_sparse_moe.experts.*.w3.weight\",],\n      \"mlp.experts.gate_up_proj\",\n      operations=[\n          MergeModulelist(dim=0),\n          Concatenate(dim=1),\n      ],\n  )\n  ```\n\n- [`SplitModulelist`](https://github.com/huggingface/transformers/blob/b71de73468429eb02da18caa50e9b5200400a4ed/src/transformers/core_model_loading.py#L208) splits a tensor back into a list of tensors. For example, you can split a stack of experts back into individual experts.\n\n  ```python\n  WeightConverter(\n      \"mlp.experts.down_proj\",\n      \"block_sparse_moe.experts.*.w2.weight\",\n      operations=[SplitModulelist(dim=0)],\n  )\n  ```\n\n### Lazy Materialization of Tensors\n\nThe refactor improves not just *what* conversions exist, but *how* they’re scheduled.\n\nThe loader scans checkpoint keys once, matches them against converter patterns, and groups tensors per converter. Once a key is identified as needed, it’s registered as a *future* and materialized via a thread pool. Conversion operations run only once their dependencies are ready. For example, `MergeModulelist` waits until all experts for a layer are loaded.\n\nThis avoids repeated scans and reduces memory peaks.\n\n### Benchmark: Weight-Loading Pipeline Improvements\n\nTo evaluate the improvements introduced by the new weight-loading pipeline, we benchmarked the v4 vs v5 versions of `transformers`. The focus is on loading speed of large MoE models, which is often a bottleneck in training and inference.\n\nWe benchmarked v4 vs v5 using:\n\n- v4 branch: [https://github.com/ariG23498/transformers/tree/bench-v4](https://github.com/ariG23498/transformers/tree/bench-v4)\n- v5 branch: [https://github.com/ariG23498/transformers/tree/bench-v5](https://github.com/ariG23498/transformers/tree/bench-v5)\n\nExample:\n\n```python\nfrom transformers import AutoModelForCausalLM\n\nmodel_id = \"Qwen/Qwen1.5-110B-Chat\"\nmodel = AutoModelForCausalLM.from_pretrained(model_id)\n```\n\nTwo relevant environment variables:\n\n- `HF_ENABLE_PARALLEL_LOADING`: Enables parallel shard loading via threads.\n\n- `HF_DEACTIVATE_ASYNC_LOAD`:Disables the new async pipeline (v5 escape hatch).\n\n### Results\n\n**Model:** `Qwen/Qwen1.5-110B-Chat` **GPU:** 1× A100 (80GB)\n\n| Version | Strategy            | Loading Mode    | Time   |\n| ------- | ------------------- | --------------- | ------ |\n| v4.57.6 | `device_map=\"auto\"` | Threadpool      | 66.24s |\n| v4.57.6 | `device_map=\"auto\"` | Sequential      | 67.29s |\n| v4.57.6 | TP                  | —               | OOM    |\n| v5      | `device_map=\"auto\"` | Async (default) | 20.71s |\n| v5      | `device_map=\"auto\"` | Sync            | 45.3s  |\n| v5      | TP                  | Async           | 10.1s  |\n| v5      | TP                  | Sync            | 19.28s |\n\n| [![Loading benchmarks](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/loading_benchmark.png)](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/loading_benchmark.png) |\n| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| Figure 4: Loading benchmarks (v4 vs v5)                                                                                                                                                                                                                                        |\n\nThe speedup is not just “more threads.”\n\nIt’s the combination of **Single-pass routing**, **Async materialization**, and **Conversion-aware scheduling** which together avoid unnecessary materialization and memory peaks while enabling expert packing and projection fusion at load time.\n\n### Where Quantization Fits In\n\nWith this refactor we can now create the runtime module structure first and then convert the weights into the structure. We can now optionally attach quantization within the conversion pipeline, making quantization part of the weight loading pipeline itself. This is crucial because quantizing “per expert” only makes sense once experts exist in a predictable packed layout.\n\nThis end to end pipeline was not possible earlier and now it comes to the users as an exposed API.\n\n## Expert Backend\n\nOnce experts are packed into a single runtime tensor, another question arises:\n\n> How do you actually route through them efficiently?\n\nIn a Mixture of Experts model, each token is routed to different experts. This means the runtime must dispatch tokens to their selected expert weights, execute the projections efficiently, apply the routing weights and then collect and reorder the results.\n\nThis is what the [Experts Backend system](https://huggingface.co/docs/transformers/experts_interface) (introduced in [PR #42697](https://github.com/huggingface/transformers/pull/42697)) addresses. The Experts Backend introduces a **pluggable execution architecture** that decouples expert computation from the model implementation. Instead of hardcoding one dispatch strategy inside each MoE model, the system allows expert layers to dynamically select a backend at runtime.\n\nThis is implemented via a decorator pattern:\n\n```python\n@use_experts_implementation\n```\n\nThe decorator wraps expert classes and dispatches computation to the selected backend automatically.\n\nThree backends are currently provided:\n\n1. `eager` which loops over the selected experts and applies projections per expert. This is used for correctness reference and debugging.\n\n2. `batched_mm` uses the [`torch.bmm`](https://docs.pytorch.org/docs/stable/generated/torch.bmm.html) API. This duplicate selected expert weights per token and performs a single batched GEMM. This backend is very well suited for small batch, GPU-heavy workloads where memory is available.\n\n3. `grouped_mm` uses [`torch._grouped_mm`](https://docs.pytorch.org/docs/stable/generated/torch.nn.functional.grouped_mm.html) API. Here we sort tokens by expert ID, group them, and then perform a single grouped GEMM. This backend shines with large batches or memory-constrained setups.\n\n| [![](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/expert_backend.png)](https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/moe-transformers/expert_backend.png) |\n| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| Figure: Expert backend illustration                                                                                                                                                                                                                    |\n\n## Expert Parallelism\n\nMixture of Experts (MoE) models can have hundreds of billions of parameters (far more than what fits on a single GPU). Expert parallelism (EP) addresses this by distributing experts across multiple devices. Each device loads only its assigned subset of experts, computes for those experts and then participates in result aggregation. This approach scales models to far larger parameter counts without increasing computation cost because each token activates only a few experts.\n\nExpert parallelism is enabled via `enable_expert_parallel`:\n\n```python\nimport torch\nfrom transformers import AutoModelForCausalLM, AutoTokenizer\nfrom transformers.distributed.configuration_utils import DistributedConfig\n\ndistributed_config = DistributedConfig(enable_expert_parallel=True)\n\nmodel = AutoModelForCausalLM.from_pretrained(\n    \"openai/gpt-oss-120b\",\n    dtype=\"auto\",\n    distributed_config=distributed_config,\n)\n```\n\nLaunch with:\n\n```bash\ntorchrun --nproc-per-node N script.py\n```\n\nWhere `N` evenly divides the total number of experts, and possibly matches the number of GPUs in your node.\n\nWhen `enable_expert_parallel=True`, the model switches from the standard tensor-parallel (TP) plan to an expert-parallel (EP) plan with specialized sharding strategies.\n\nCore components of EP lie in:\n\n1. [`GroupedGemmParallel`](https://github.com/huggingface/transformers/blob/b71de73468429eb02da18caa50e9b5200400a4ed/src/transformers/integrations/tensor_parallel.py#L934): This splits the expert weights along the expert dimension (`dim=0`). Here each device loads only `num_experts / num_devices`.\n\n2. [`RouterParallel`](https://github.com/huggingface/transformers/blob/b71de73468429eb02da18caa50e9b5200400a4ed/src/transformers/integrations/tensor_parallel.py#L977): This remaps global expert indices to local indices, masks out experts not assigned to the current rank, ensures each device computes only with its local experts and uses an all-reduce to combine partial outputs across devices.\n\n## Training MoEs with Transformers\n\nMoEs are excellent for scaling inference, but training them is significantly more complex.\n\nMoEs have a Massive parameter count, the distributed expert communication is complicated, there are routing in-stabilities that need to be handled. To address this, we collaborated with **Unsloth** to enable significantly faster Mixture-of-Experts training:\n\n- \\~12× faster MoE training\n- \\>35% VRAM reduction\n- \\~6× longer context\n- 12–30× overall speedup compared to v4\n\nWe leverage the Expert Backend abstraction, standardize around PyTorch’s `torch._grouped_mm` API and use custom Triton grouped-GEMM + LoRA kernels. Unsloth builds on top of the Transformers (and TRL) optimizations to push performance further.\n\n> For full details, we recommend reading: [Unsloth’s official guide](https://unsloth.ai/docs/new/faster-moe)\n\n## Conclusion\n\nAs sparse architectures continue to evolve, we want the transformers library to evolve with them. If you’re building with MoEs or experimenting with new sparse ideas, we’d love to hear from you. Let us know what abstractions, kernels, or workflows you’d like to see next in `transformers`.\n",
  "dateCreated": "2026-10-04T12:50:30.804479567+00:00",
  "date_created": "2026-02-26T00:00:00+00:00",
  "date_modified": "2026-02-26T00:00:00+00:00",
  "date_published": "2026-02-26T00:00:00+00:00",
  "description": "Introduction Over the past few years, scaling dense language models has driven most progress in LLMs. From early models like the original ULMFiT (~30M parameters) or GPT-2 (1.5B parameters, which at the time was considered \"too dangerous…",
  "external_url": "https://huggingface.co/blog/moe-transformers",
  "git": {
    "commit": "ebb0941c5c06a2ab2377342d607535262a3e969e",
    "path": "items/huggingface-co/2026/02/2026-02-26-mixture-of-experts-moes-in-transformers.md",
    "permalink": "https://github.com/aymericbeaumet/aggr-instance/blob/ebb0941c5c06a2ab2377342d607535262a3e969e/items/huggingface-co/2026/02/2026-02-26-mixture-of-experts-moes-in-transformers.md"
  },
  "id": "urn:aggr:item:13a678c4c08dc598ef45d1dbae08ef6f3ee664c1",
  "isBasedOn": {
    "@id": "https://huggingface.co/blog/moe-transformers"
  },
  "mainEntity": {
    "@id": "https://huggingface.co/blog/moe-transformers",
    "@type": "CreativeWork",
    "archivedAt": {
      "@id": "https://aggr.aymericbeaumet.com/items/huggingface-co/2026-02-26-mixture-of-experts-moes-in-transformers/#webpage"
    },
    "dateModified": "2026-02-26T00:00:00+00:00",
    "datePublished": "2026-02-26T00:00:00+00:00",
    "headline": "Mixture of Experts (MoEs) in Transformers",
    "timeRequired": "PT9M",
    "url": "https://huggingface.co/blog/moe-transformers",
    "wordCount": 1963
  },
  "name": "Archived snapshot: Mixture of Experts (MoEs) in Transformers",
  "preview": null,
  "reading_minutes": 9,
  "replicated_at": null,
  "source": "huggingface-co",
  "source_name": "Hugging Face - Blog",
  "summary": null,
  "tags": [],
  "temporalCoverage": "2026-10-04T12:50:30.804479567+00:00",
  "title": "Mixture of Experts (MoEs) in Transformers",
  "url": "https://aggr.aymericbeaumet.com/items/huggingface-co/2026-02-26-mixture-of-experts-moes-in-transformers/",
  "word_count": 1963
}