From a028c1d3aa8cb9d3e4a6ab0538c05da188bb4209 Mon Sep 17 00:00:00 2001 From: GarlGuo Date: Wed, 22 Apr 2026 01:10:27 -0400 Subject: [PATCH 1/3] sonicmoe first commit --- _posts/2026-04-26-sonicmoe-blackwell.md | 703 ++++++++++++++++++++++++ 1 file changed, 703 insertions(+) create mode 100644 _posts/2026-04-26-sonicmoe-blackwell.md diff --git a/_posts/2026-04-26-sonicmoe-blackwell.md b/_posts/2026-04-26-sonicmoe-blackwell.md new file mode 100644 index 0000000..427f128 --- /dev/null +++ b/_posts/2026-04-26-sonicmoe-blackwell.md @@ -0,0 +1,703 @@ +--- +layout: distill +title: "SonicMoE: A Hardware-Efficient and Software-Extensible Blueprint for Fine-Grained MoEs" +giscus_comments: false +date: 2025-12-15 +featured: true +related_posts: false + +# Optionally, you can add a table of contents to your post. +# NOTES: +# - make sure that TOC names match the actual section names +# for hyperlinks within the post to work correctly. +toc: + - name: 1. Opportunities and Pains of Fine-Grained MoEs + subsections: + - name: MoE as Grouped GEMM + - name: SonicMoE's Solution - Algorithm and Kernel Decomposition + - name: 2. QuACK's Software Abstraction that Empowers SonicMoE + subsections: + - name: Tiled GEMM kernel on NVIDIA GPUs + - name: Customizable Epilogue + - name: 3. Underneath the Abstraction - Hardware Features that Empower the IO Overlap + subsections: + - name: GEMM programming model + - name: 2CTA MMA + - name: Native Dynamic Persistent Tile Scheduler + - name: 4. Reducing the Impact of IO Costs + subsections: + - name: Gather Fusion + - name: SwiGLU and dSwiGLU Fusion + - name: Overlapping IO with MMA Compute - dH kernel + - name: 5. Benchmark Results + subsections: + - name: Forward and Backward TFLOPS of 6 Open-source MoE Configs + - name: Profiling Time Breakdown + - name: Conclusion + - name: Appendix + +authors: + - name: Wentao Guo + affiliations: + name: Princeton University + - name: Mayank Mishra + affiliations: + name: IBM Research + - name: Xinle Cheng + affiliations: + name: Princeton University + - name: Ion Stoica + affiliations: + name: UC Berkeley + - name: Tri Dao + affiliations: + name: Princeton University +--- + + + + +[![arXiv](https://img.shields.io/badge/arXiv-2512.14080-b31b1b.svg)](https://arxiv.org/abs/2512.14080) [![Code](https://img.shields.io/badge/GitHub-SonicMoE-blue?logo=github)](https://github.com/Dao-AILab/sonic-moe) [![PyPI](https://img.shields.io/pypi/v/sonic-moe?cache=no)](https://pypi.org/project/sonic-moe/) + +

+

Figure: SonicMoE's per-layer activation memory footprint (left) stays constant even when expert granularity (embedding dimension / expert intermediate dimension) increases, and SonicMoE can achieve 1.87-4.04x relative speedup compared to existing MoE training kernels ScatterMoE and MoMoE.

+ + +**SonicMoE now runs at peak throughput on NVIDIA Blackwell GPUs (B200/B300), in addition to its existing Hopper (H100) support.** This blogpost walks through how we got there: an IO-aware algorithm that keeps activation memory independent of expert granularity, a unified software abstraction on [QuACK](https://github.com/Dao-AILab/quack) that makes porting across GPU architectures straightforward, and the Blackwell hardware features we exploit to hide IO costs behind computation. + +## 1. Opportunities and Pains of Fine-Grained MoEs + +Mixture-of-Experts (MoE) models have become the dominant architecture for scaling language models without proportionally increasing compute. The appeal is straightforward: by routing each token to a small subset of $K$ out of $E$ expert networks, we get a model with hundreds of billions of parameters at the compute cost of a much smaller dense model. The training FLOP savings and quality improvements are well-established, but they come with hardware costs that grow worse as models become more fine-grained. + + +

+

Figure: fine-grained MoE architecture [1]

+ +Two architectural dimensions define how an MoE model trades off quality and efficiency. + +- **Granularity** ($G = d/n$, where $d$ is the model embedding dimension and $n$ is each expert's intermediate size) measures how small the experts are relative to the model width. A high-granularity (fine-grained) MoE has many small experts. + +- **Sparsity** ($\rho = K/E$) measures the ratio of experts activated per token. + +MoE scaling laws, from controlled experiments (e.g. [Krajewski et al.](https://arxiv.org/pdf/2402.07871) and [Tian et al.](https://arxiv.org/pdf/2507.17702)) and recent open-source model scaling trends, consistently show that higher granularity and higher sparsity yield better model quality per FLOP: selecting more, smaller experts increases representational capacity, while sparser activation allows more total parameters within the same compute budget. Frontier open-source models reflect this clearly: [Mixtral 8x22B](https://huggingface.co/mistralai/Mixtral-8x22B-v0.1), released in 2024, operated at $G=0.38$ and $\rho=0.25$, while recent models since 2025 like [DeepSeek V3.2](https://huggingface.co/deepseek-ai/DeepSeek-V3.2) ($G=3.50$, $\rho=0.03$), [Kimi K2.5](https://huggingface.co/moonshotai/Kimi-K2.5) ($G=3.50$, $\rho=0.02$), and [Qwen3-Next-80B-A3B-Instruct](https://huggingface.co/Qwen/Qwen3-Next-80B-A3B-Instruct) ($G=4.00$, $\rho=0.02$) have pushed both dimensions aggressively. Every new generation of frontier MoE is more fine-grained and sparser than the last. + +However, the pursuit of granularity and sparsity comes with two painful hardware costs: + +

+

Figure: Per-layer activation memory (left) and forward IO costs (right) as expert granularity increases. We fix microbatch size as 32768 and each model's embedding dimension, then vary the expert intermediate size while keeping training FLOPs and parameter count constant.

+ + +#### Problem 1: Activation Memory Scales with Expert Granularity with Current Training Kernels. + +During training, intermediate tensors must be cached for the backward pass. The total FLOPs of MoE forward and backward computation is $(6+12)TnKd$. For fixed $T$ and $d$, keeping FLOPs constant requires $nK$ to stay constant. Increasing granularity means decreasing $n$ and proportionally increasing $K$. Any activation of size $O(TKd)$ thus grows linearly with granularity. + +For current MoE kernels like [ScatterMoE](https://arxiv.org/pdf/2403.08245) and [MoMoE](https://github.com/tilde-research/MoMoE-impl), variables such as the down-proj output $Y$ (size $TKd$) are cached for the backward pass, causing per-layer activation memory to grow linearly as experts become more fine-grained. Prior solutions such as MoMoE usually require a GEMM recomputation during backward to trade off activation memory for extra FLOPs. This raises the question: + +

Is it possible to achieve activation memory efficiency without extra FLOPs from GEMM recomputation?

+ + +#### Problem 2: IO Cost Scales with Expert Granularity and MoE Sparsity. + +A GPU kernel's runtime is determined by whichever resource is exhausted first: compute throughput (FLOP/s) or memory bandwidth (bytes/s). **Arithmetic intensity as the ratio of FLOPs to HBM bytes transferred is the metric that determines in which regime a kernel operates.** As the arithmetic intensity becomes higher, the kernel is likely to be compute-bound rather than memory-bound. + +Assuming perfect load balancing and SwiGLU activation, the arithmetic intensity of a single expert's forward pass is lower-bounded by: + +$$\text{Arithmetic Intensity} = \frac{3}{\frac{2}{d} + \frac{2G}{d} + \frac{3}{T\rho}} = O\left(\min\left(\frac{d}{G}, T\rho\right)\right)$$ + +where $T$ is the number of tokens in a microbatch ($T\rho$ is the average number of routed tokens per expert). + +In this case, **both increasing $G$ and increasing MoE sparsity (decreasing $\rho$) would drive arithmetic intensity down.** For example, [Qwen3-Next-80B-A3B-Instruct](https://huggingface.co/Qwen/Qwen3-Next-80B-A3B-Instruct) would have an arithmetic intensity of 210 for a microbatch of 16K tokens, while an iso-param dense SwiGLU MLP would have an arithmetic intensity of 2570, 12× higher. In this regime, kernel runtime is dominated by the IO costs, not compute throughput. + +> [!NOTE] +> For fine-grained and sparse MoEs, every expert's GEMM problem shape is small enough such that the kernel falls into the memory-bound regime. + +**These IO costs will become a greater bottleneck in expert parallelism, as the intra- or inter-node network bandwidth are often *much* slower than HBM loading speed.** SonicMoE currently focuses on the case of single GPU (EP degree=1), but the IO-aware algorithmic designs are transferable to expert parallelism. + + + +### MoE as Grouped GEMM + +MoE computation is often implemented using Grouped GEMM. A Grouped GEMM is a batch of matrix multiplications with possibly different problem shapes. Following standard BLAS conventions used by CUTLASS, each GEMM computes $C = AB$ where $A \in \mathbb{R}^{M \times K}$ (activations), $B \in \mathbb{R}^{K \times N}$ (weights), and $C \in \mathbb{R}^{M \times N}$ (outputs). + +In MoE, each expert usually receives a different number of tokens, and input tokens may need to be gathered from different positions, or they may already be contiguously packed by expert. + +For the forward pass and backward activation gradient, we would need Grouped GEMM with input shapes that have constant $N$ and $K$ (embedding dimension and expert intermediate dimension) but different $M$ (the number of routed tokens per expert). **We call this varlen-M Grouped GEMM**. (CUTLASS would describe it as *Grouped GEMM with ragged M dimensions*). For the backward weight gradient, we would reduce over token embeddings for each expert GEMM, in which $M$ and $N$ (embedding dimension and expert intermediate dimension) are fixed but the $K$ dimension varies. **We call this varlen-K Grouped GEMM**. + +

               

+

Left: Each expert gathers inputs from different positions on an input tensor (top) or reads a contiguous chunk on a grouped input array (bottom). Right: Illustration of using Grouped GEMM in MoE.

+ + +We can use varlen-M Grouped GEMM to build a standard MoE forward pass as demonstrated in the following code snippet. +

+

Figure: Visual workflow (left) with corresponding reference code (right) of standard MoE forward pass in PyTorch. Each yellow dashed line marks a kernel boundary. The standard implementation launches 6 separate kernels: gather, up-proj Grouped GEMM, SwiGLU, down-proj Grouped GEMM, scatter, and expert aggregation.

+ + +This can be simplified to the following workflow diagram: +

+

Figure: Workflow of standard MoE implementation forward pass. π is the binary mask that stores routing metadata. Yellow boxes are kernel boundaries. Blue boxes are variables in HBM. Red labels indicate the activations cached across the forward/backward. Purple boxes are the final outputs. The orange box beside each variable on global memory represents the tensor size in proportion for Qwen3-235B-A22B-Thinking-2507 MoE model with 32k tokens.

+ +The workflow of backward activation gradient is simply a reverse operation with dSwiGLU as follows: + +

+

Figure: Workflow of standard MoE implementation backward activation gradient pass.

+ + +For weight gradient, we need to use varlen-K Grouped GEMM to reduce over token embeddings. + +

+

Figure: Workflow of standard MoE implementation backward weight gradient pass.

+ +The standard implementation materializes every intermediate tensor in HBM between kernel launches. This creates two separate costs that both scale with expert granularity: + +- **Activation memory**: gathered $X$, down-proj output $Y$, and scattered $Y$ must all be cached for the backward pass, each consuming $2TKd$ bytes. As granularity increases, these $O(TKd)$-sized tensors grow linearly. + +- **IO costs**: every materialized intermediate is a round-trip to HBM. The backward pass is worse: it must additionally materialize $dY$ and gathered $dO$, both $O(TKd)$-sized. **Since fine-grained MoE kernels operate in the memory-bound regime, these IO costs directly dominate runtime.** + + +### SonicMoE's Solution: Algorithm and Kernel Decomposition + +**SonicMoE addresses both problems through a single algorithmic redesign: we circumvent the need to cache or materialize any variable with size $O(TKd)$.** This makes activation memory independent of expert granularity, and simultaneously eliminates multiple large HBM round-trips that dominate runtime. + +In particular, SonicMoE avoids caching down-proj output $Y$, scattered $Y$, and gathered $X$ which all have size $T K d$. We also avoid writing $dY$ and gathered $dO$ to HBM: + +- **Gathered $X$ and $dO$**: we gather inputs at each kernel runtime and *never* cache the gathered results. + +- **Scattered $Y$**: we fuse it with the aggregation operation where each token will gather and sum over activated expert results. + +- **$Y$ and $dY$**: we redesign the computational path that starts from $dO$ and $H$ to directly compute $dS$ and $dH$ during the backward pass **without $Y$ and $dY$**. **Prior MoE kernels such as ScatterMoE and MoMoE must cache $Y$ for this computation**: + - $dH$: we apply gather fusion with $dO$ (no need for $dY$) and dSwiGLU fusion with an extra load of $H$. + + - $dS$: we swap the contraction order. **This is equivalent to placing $S$ weighting *before* down-proj forward pass and using only $A$ and $dA'$ for computing $dS$ instead of $Y$ and $dO$.** We no longer need to cache $Y$. + + For an expert $e$, denote the down-proj weights for expert $e$ as $W_{2,e}\in \mathbb{R}^{n\times d}$. The Grouped GEMM in down-proj activation gradient will compute $dA' = dO_e W_2^\top$. + + The standard path computes $dS_{t,e} = \langle dO_t,\ Y_{e,t}\rangle$, which requires caching $Y$. By substituting $Y_e = A_e W_{2,e}$ and rearranging the contraction order: + + $$dS_{t,e} = \langle dO_t,\ Y_{e,t}\rangle = \langle dO_t,\ A_e W_{2,e}\rangle = \langle dO_t W_{2,e}^\top,\ A_{e,t}\rangle = \langle dA_{e,t}',\ A_{e,t}\rangle$$ + + Neither $dA_{e,t}'$ nor $A_{e,t}$ depends on $dY$ or $Y$. + +#### Activation Memory Independent of Expert Granularity + +**SonicMoE's forward pass.** In the forward pass, SonicMoE only caches $X$ and $H$. The gathered results for $X$ are *never* cached or materialized. The expert aggregation kernel fuses the scatter and summation together. + +

+

Figure: SonicMoE's forward computational workflow and comparison with a standard MoE implementation in PyTorch. We also compare the activation memory and IO costs for both methods.

+ +The following figure gives a brief comparison on the activation memory breakdown. SonicMoE caches only inputs $X$ and pre-SwiGLU activation $H$ and *does not need any GEMM recomputation*. + +

+

Figure: illustration of cached activation memory for a single layer of Qwen3-235B MoE model (microbatch=32k) when equipped with different training kernels.

+ +> [!NOTE] +> SonicMoE can achieve the same activation memory efficiency as a dense model with the same activated number of parameters without extra training FLOPs. + +#### IO Cost Reduction through Algorithmic Reordering + +Each variable that is no longer cached is also one fewer read or write to HBM. The same redesign that eliminates $O(TKd)$-sized activations eliminates the corresponding HBM round-trips. + +**SonicMoE's forward pass.** We fuse the gather and SwiGLU activation in the up-projection. The scatter $Y$ operation is fused with the expert aggregation. + +**SonicMoE's backward pass.** + +- **Activation gradient**: The down-proj activation grad $dH$ kernel computes $dH$, $dS$, and $A'$ (input for $dW_2$) simultaneously, none of which require caching $Y$ or $dY$. We similarly fuse dSwiGLU and the gather operation into the GEMM. + +

+

Figure: SonicMoE's backward computational workflow for activation gradient and comparison with a standard MoE implementation in PyTorch.

+ +- **Weight gradient**: The weight gradient kernels for $dW_1$ and $dW_2$ gather $X$ and $dO$ on the fly during execution. While their *algorithmic IO costs* match a standard MoE implementation, SonicMoE's gather fusion reduces the *hardware IO costs* by exploiting L2 cache locality, which we will discuss later. + +

+

Figure: SonicMoE's backward computational workflow for weight gradient and comparison with a standard MoE implementation in PyTorch.

+ +The net effect is a large reduction in IO costs even before any hardware-specific optimizations: + +

+

Figure: Illustration of IO costs for a single layer of Qwen3-235B MoE model (microbatch=32k) when equipped with different training kernels. SonicMoE's workflow circumvents the need to read or write multiple massive-sized tensors compared to existing MoE kernels.

+ +Among these kernels, we want to give a special highlight to our backward down-proj activation gradient $dH$ kernel as a combination of IO-aware and hardware-aware algorithmic design: + +

+

Figure: the semantics of SonicMoE's dH workflow diagram is equivalent to standard PyTorch MoE implementation for multiple kernels while SonicMoE substantially reduces the IO costs.

+ + +- **reduction of IO costs**: we gather $dO$, fuse the dSwiGLU call, and do not read or write $Y$ and $dY$. + + +- **hardware asynchrony features that further hide the remaining IO cost latency** (will discuss later): the design of this $dH$ kernel already reduces IO costs, and we further minimize the remaining impact of IO costs by leveraging the asynchrony features on modern NVIDIA GPUs. + +

+

Figure: we can leverage recent NVIDIA hardware features to hide the IO latency in SonicMoE's dH kernel and greatly reduce the overall runtime.

+ + +> [!TIP] +> A careful algorithmic design is sufficient to address the activation memory issue and partially the IO cost issue. We can further minimize the impact of IO costs by leveraging hardware asynchrony. + +We want SonicMoE to achieve peak throughput on both Hopper and Blackwell GPUs, so we apply hardware-aware optimizations to all Grouped GEMM kernels in SonicMoE. However, modern NVIDIA GPU architectures often differ substantially in their execution models. **In response, we build a unified and modular software abstraction that expresses all grouped gemm kernels while localizing all architecture-specific optimizations to a small number of overrides.** The rest of this post describes that abstraction and how it is realized on each architecture. + +## 2. QuACK's Software Abstraction that Empowers SonicMoE + +SonicMoE already supports NVIDIA Hopper (SM90), Blackwell GPUs (SM100), and the support for Blackwell GeForce (SM120) GPUs is on the way. When we first considered porting the Hopper kernels to Blackwell, the straightforward path was to rewrite 6 Grouped GEMM kernels from scratch. We chose instead to factor out the shared structure, and this decision proved highly productive later. + +Every Grouped GEMM kernel is an instance of the same underlying structure: **a producer-consumer GEMM mainloop that overlaps data movement with tensor core computation, followed by a parameterized epilogue** that applies fusion logic directly to the accumulator before any data reaches HBM. + +> [!NOTE] +> This shared structure of GEMM mainloop with customizable epilogue would make SonicMoE's implementation modular, extendable to new hardware while still maintaining peak performance. + +We also unify the API and encapsulate other architecture-specific changes. **SonicMoE's GEMM kernels are built on top of [QuACK](https://github.com/Dao-AILab/quack), our in-house CuTeDSL library that draws heavily from [CUTLASS](https://github.com/NVIDIA/cutlass) and the [CuTeDSL official examples](https://github.com/NVIDIA/cutlass/tree/main/examples/python/CuTeDSL).** CUTLASS defines a clean layered programming model for GPU kernels: a mainloop that tiles the matrix multiplication across the parallel workers (Streaming Processors), and an epilogue that post-processes the results before writing them back to memory. QuACK adopts this layered programming model and extends it with modular components (tile schedulers, customizable epilogue, etc.). + +Below, we examine the design of QuACK GEMM and how it helps SonicMoE achieve peak throughput amid high IO costs. + + +### Tiled GEMM kernel on NVIDIA GPUs + +A General Matrix Multiplication (GEMM) kernel on NVIDIA GPUs repeatedly fetches tiles of input data $A,B$ ($A$ is usually the activations while $B$ is the weights), and we accumulate the tiled MMA (matrix multiply-accumulate) results into a zero-initialized buffer $C$ (often the output activations). + +

+

Figure: illustration of GEMM tiled accumulation [2]

+ + +#### Repeated 3-phase Accumulation for Each Output Tile + +

+

Figure: each Streaming Processor (SM) on GPUs will perform tiled MMA in 3 phases until no tiles left. Usually there will be a persistent tile scheduler that schedules which tile each SM will receive. Adapted from [3].

+ +For every output tile, the accumulation process is formulated into three phases: + +- **Prologue** (by *producer*): the load warp(s) load the inputs to fill SMEM buffers with tiles of A and B. + +- **Mainloop** (input loading by *producer*, MMA by *consumer*): the MMA warp/warpgroup consumes filled shared memory (SMEM) buffers, executes the MMA instruction, and accumulates into an output buffer. On Hopper this result buffer lives in registers (WGMMA). On Blackwell the result lives in TMEM (UMMA). + +- **Epilogue** (by *consumer*): the consumer warpgroup (Hopper) or the dedicated epilogue warps (Blackwell) apply any fused post-processing to the accumulated results, and write back to GMEM (global memory, often the HBM). + +This three-stage structure is the same for all 6 Grouped GEMM kernels in MoE. What changes between kernels is exclusively the following: + +1. How the producer loads the data when we have contiguous or gathered inputs +2. What the epilogue consumer does to the accumulator before writing it to GMEM + +Point (1) is the gather fusion described in Section 4. Point (2) is where all MoE-specific fusion logic lives, and it is the core of QuACK's customizable epilogue abstraction. + + +#### Tile Scheduling: Decide which Output Tile to Process by Each CTA + +A persistent tile scheduler will give a unique tile coordinate to each CTA (thread block, usually 1 per SM) until all tiles are consumed. Multiple modes of tile schedulers are supported and selected automatically based on architecture and kernel configuration: + +- **Static** (SM90 default): fixed linear tile-to-CTA assignment. + +- **Cluster Launch Control (CLC)** (SM100 default): hardware-assisted cluster-level dynamic scheduling via the Blackwell-specific `clusterlaunchcontrol.try_cancel` PTX instruction. The hardware manages the work queue. We will describe CLC in detail in Section 3. + + +### Customizable Epilogue + +The base GEMM class implements the epilogue as a fixed loop skeleton. For each sub-tile of the output: + +1. Load the accumulator fragment into a register tensor +2. Call `epi_visit_subtile` to **execute customized epilogue ops**. +3. Write epilogue results to shared memory and finally to global memory + +The `epi_visit_subtile` method is a no-op in the base class. Subclasses override it to inject arbitrary per-element fusion logic. **This single method is the injection point for every activation function, every backward pass computation, every scaling operation, and every reduction in the entire SonicMoE codebase.** + +Each epilogue mixin (e.g., `GemmGatedMixin` for SwiGLU, `GemmDGatedMixin` for the $dH$ backward) is paired with an architecture-specific base class: `GemmGatedSm90` / `GemmGatedSm100`, `GemmDGatedSm90` / `GemmDGatedSm100`, etc. The architecture-specific suffix controls only the warp layout, accumulator movement (registers vs. tensor memory), and hardware resource management. **The epilogue fusion logic in `epi_visit_subtile` is shared across architectures.** For example, the heaviest kernel in SonicMoE is just a `GemmDGatedMixin` with additional arguments, implemented in 88 lines: + +

+

Figure: Two SonicMoE kernels implemented with QuACK. Left: the kernel workflow diagram. Center: the QuACK epilogue mixin class where each kernel overrides `epi_visit_subtile` (88 LoC for dH, 21 LoC for up-proj forward). Right: SonicMoE's simplified kernel launch call.

+ +In total, this software abstraction on QuACK delivers three properties we prioritize: +- **Adaptability to new model architecture or algorithms**: future developers need only modify how epilogue works to provide a fast kernel implementation for other model architectures or algorithms, not only MoE. + + - For example, [Gram Newton-Schulz](https://github.com/Dao-AILab/gram-newton-schulz) is also built on top of symmetric gemm on QuACK, with the quote from its blogpost: + > Using these abstractions, we are able to implement the symmetric GEMM kernel for both Hopper and Blackwell in just 160 lines, while achieving SOTA performance. + + - We also only write **~200 LoC** to implement SonicMoE on top of QuACK Grouped GEMM which works automatically on both Hopper and Blackwell GPUs. + +- **Fast extensibility to new hardware (features)**: a unified API from top to bottom across different hardware architectures. + + We can change our base GEMM implementation and the existing kernels should work on the new hardware, which enables quick research development: + + - We develop TMA gather4 for Grouped GEMM on Blackwell GPUs [by simply modifying copy atoms and SMEM layouts](https://github.com/Dao-AILab/quack/commit/e282ee6529089d32d01fc178a1043b28bbf8bb9c#diff-fcdc3df7cf71ffdd7a3bde39db27fc4f729c71549614be61621441966393df2e) with ~100 LoC changes. *We do not change anything on the MMA warps.* + + - We extend to SM120 (Blackwell GeForce GPUs such as 5090) by simply adding [a base GEMM class](https://github.com/Dao-AILab/quack/blob/main/quack/gemm_sm120.py) with ~500 LoC changes. *We do not change anything on the customizable epilogue and GEMM interface.* + +- **Codebase maintainability**: the new modular design reduces the cost of future maintenance and makes the codebase accessible to new contributors. + + - Our prior Hopper Grouped GEMM integrated 3-phase GEMM programming model and all possible fusions together, with more than 3k lines of code. This complexity placed a significant burden on maintainers and made adding new features error-prone. + + +In the next section, we will describe how SonicMoE benefits from new Blackwell features. + +## 3. Underneath the Abstraction: Hardware Features that Empower the IO Overlap + +The software abstraction described in the previous section was designed so that all architecture-specific behavior is confined to a small number of localized overrides. This section describes what Blackwell provides at the hardware level, and why each new feature maps cleanly onto one of those overrides. + + +### GEMM programming model + +**On Hopper**, MMA is usually performed via a *warpgroup-level* instruction WGMMA (`wgmma.mma_async`). It requires 128 threads (4 contiguous warps) to issue and manage: all threads in the warpgroup participate in tracking the accumulator state, and the accumulator result is distributed across the register files of those 128 threads. We often have 2 consumer warpgroups, and we can either let them *cooperatively* issue 2 WGMMA instructions, or **we can overlap the IO of 1 warpgroup with the GEMM of another warpgroup**. In this case, we can let 1 consumer warpgroup do MMA while the other consumer warpgroup does the epilogue, and they switch roles once each finishes. This is called "Ping-Pong warpgroup scheduling", often particularly useful to maintain high Tensor Core throughput with heavy epilogue. + +For example, the down-proj forward kernel's epilogue has heavy HBM store IO relative to the mainloop. In the $dH$ kernel's epilogue, we need to load $H$ and execute multiple activation and reduction operations to compute and store $dH$, $dS$, and $A'$ as inputs for $dW_2$. + +

+

Figure: Hopper Ping-Pong: two consumer warpgroups alternate between MMA and epilogue: while one runs Tensor Core MMA, the other +runs the epilogue (TMA store + any async load). Green arrows show the signal from one warpgroup that the other can proceed.

+ + +**On Blackwell**, new UMMA (`tcgen05.mma`) instruction breaks this coupling entirely. UMMA is *single-threaded asynchronous*: one thread in the warp issues it, and execution proceeds asynchronously without occupying any other threads or registers. The accumulator result is written directly into Tensor Memory (TMEM) — a new dedicated 256 KB on-chip memory per SM that is wired into the tensor cores and completely separate from the register file. + +TMEM is organized as 128 rows × 512 columns of 32-bit cells, for a total of 256 KB per SM. The 512-column structure can hold two independent accumulator stages of 256 columns each. This is the hardware basis for Blackwell's MMA/epilogue overlap as shown below. + +

+

Figure: TMEM column ownership transfer between MMA warp and epilogue warps. This technique is often referred to as "double-buffering".

+ + +While the MMA warp accumulates into one 256-column stage, the epilogue warps are simultaneously draining the other stage via `tcgen05.ld` (the TMEM-to-register copy instruction) and performing epilogue ops afterwards. When the epilogue warps finish and signal via the accumulator pipeline, the MMA warp acquires the next stage and begins filling it. The stages alternate every tile. **This is Ping-Pong in spirit as it overlaps MMA with epilogue IO.** + +

+

Figure: Blackwell warp-specialized pipeline: one producer warp (top), one MMA warp (middle), multiple epilogue warps (bottom) running concurrently. Green arrows show the ready signal of TMEM stage from the MMA to epilogue warp. Yellow arrows show the release signal of TMEM stage from the epilogue to MMA warp.

+ + +### 2CTA MMA + +A second major Blackwell feature is the `cta_group::2` variant of UMMA. When this mode is enabled, a *pair* of CTAs in the same cluster cooperatively execute a single MMA instruction. The tile M dimension doubles: where a single-CTA UMMA supports up to $M_\mathrm{tile}=128$, a 2CTA UMMA supports up to $M_\mathrm{tile}=256$. + +For a tile of shape $M_\mathrm{tile} \times N_\mathrm{tile} \times K_\mathrm{tile}$, the number of FLOPs is $2 M_\mathrm{tile} N_\mathrm{tile} K_\mathrm{tile}$ and the number of bytes loaded from SMEM is $2(M_\mathrm{tile} K_\mathrm{tile} + N_\mathrm{tile} K_\mathrm{tile})$ for A and B. For fixed $N_\mathrm{tile}$ and $K_\mathrm{tile}$, doubling $M_\mathrm{tile}$ doubles the FLOPs but only adds $2M_\mathrm{tile} K_\mathrm{tile}$ bytes of A data — the B tile of shape $N_\mathrm{tile} \times K_\mathrm{tile}$ is *shared* across the pair, so each CTA loads only half the B data it would need for two independent 1CTA tiles. This is the key benefit: the B tile is multicasted via TMA across the CTA pair, halving B-side SMEM traffic per output element. + +

+

Figure: independent 1CTA MMA (left) vs. 2CTA MMA, referred to as 2xSM MMA in the figure (right). Left: two separate CTAs each load a full B tile and hold a full accumulator in TMEM. Right: in 2CTA MMA, B tile is halved and shared. Each CTA holds the full accumulator on TMEM but loads only half the B data. [4]

+ + + + + +### Native Dynamic Persistent Tile Scheduler + +A persistent tile scheduler is essential for MoE kernels because it allows one CTA to begin loading the next tile while the current tile's epilogue is still in progress, keeping both the producer and consumer pipelines continuously occupied. + +On Hopper, we often have a fixed, *static* linear pre-assignment of tiles to CTAs (we call it "static tile scheduler"). This induces *zero synchronization overhead*, but it is susceptible to workload imbalance when expert token counts vary. Implementing a dynamic persistent tile scheduler aware of each SM's progress requires a global semaphore counter in GMEM and atomic traffic. The advantage of dynamic persistent over static persistent is often not obvious or decisive. + +Blackwell introduces **Cluster Launch Control (CLC)**: a hardware instruction `clusterlaunchcontrol.try_cancel` that lets a running cluster query the hardware for its next tile coordinate without touching GMEM atomics. The hardware manages the work queue, operates at cluster granularity, and returns either a tile coordinate or a decline signal when all tiles are processed. The query to the hardware has minimal overhead and the response is broadcast to the whole cluster at once, eliminating per-CTA atomic traffic entirely. + +

+

Figure: SM heatmap without persistent tile scheduler (left) and with CLC tile scheduler (right) [5]. The CLC tile scheduler can help all SMs stay active throughout the kernel runtime.

+ + +**The CLC tile scheduler and extensive use of 2CTA MMA in varlen-M Grouped GEMM already help SonicMoE to achieve higher throughput (~10\%) than both [DeepGEMM sm100_m_grouped_bf16_gemm_contiguous](https://github.com/deepseek-ai/DeepGEMM/blob/d30fc36c8f229f4f873b90a492f6e19e6e610923/csrc/jit_kernels/impls/sm100_bf16_gemm.hpp#L124) and [triton official example](https://github.com/triton-lang/triton/blob/7d0756121cc95d6971112fc5c1fa99107b892444/python/triton_kernels/triton_kernels/matmul_details/_p_matmul.py#L57).** We compare SonicMoE's implementation with the DeepGEMM and triton official example in the appendix. + +

+

Figure: Varlen-M Grouped GEMM with contiguously-packed inputs on B300 GPUs. Detailed descriptions of other baselines can be found in the caption of Figure 18 of our arXiv paper.

+ + +## 4. Reducing the Impact of IO Costs + +The hardware features described in Section 3 provide the infrastructure for high throughput. But for fine-grained MoE, the dominant cost is not raw MMA throughput: it is the IO overhead of gathering tokens from arbitrary positions and of executing heavy epilogue computations without stalling the tensor cores. This section describes the three fusion principles that address these costs, and how each one is adapted for Blackwell. + + +### Gather Fusion + +Multiple varlen-M GEMMs in SonicMoE read tokens from arbitrary positions in the input tensor where the routing decision determines which rows of $X$ (or $dO$) belong to each expert. SonicMoE fuses the gather directly into the GMEM-to-SMEM load. On Blackwell GPUs, SonicMoE will dispatch to gather with either `cp.async` or TMA gather4 (`cp.async.bulk.tensor.2d.shared::cta.global.tile::gather4` gathers 4 rows each time), whichever is faster at autotuning stage. + +- **`cp.async` gather fusion with 2CTA MMA.** When 2CTA MMA is combined with cp.async gather fusion, a synchronization challenge arises: cp.async can only signal completion within its own CTA, **but the leader CTA's MMA needs both CTAs' data ready.** We resolve this with a dedicated relay warp in CTA 1 (non-leader) that forwards the completion signal to CTA 0 (leader) via a cluster-scope barrier. + + +

+

Figure: 2CTA MMA relay mechanism. CTA 0 (top) as the leader CTA: 1 warp fetches indices, 4 warps issue `cp.async` gathers, 1 warp issues the 2CTA MMA instruction after waiting at its barrier. CTA 1 (bottom): 1 warp fetches indices, 4 warps issue `cp.async` gathers, 1 relay warp waits for the `cp.async` completion and then arrives at CTA 0's barrier.

+ +We then compare the speed of SonicMoE's gather fusion against other MoE kernels' GEMM with a separate gather kernel or with gather fusion. SonicMoE's gather fusion is only 1.4% slower on the M dimension and 0.5% faster on the K dimension relative to contiguous inputs. Therefore, SonicMoE consistently achieves higher TFLOPS than ScatterMoE, MoMoE, and the triton official example even with gather fusion. + +

+

Figure: Forward pass up-proj (gather on M dim) and backward dW1 kernel (gather on K dim) kernel on B300 GPUs. SonicMoE supports both inputs gathered from different positions (opaque bars) and contiguously-packed inputs (transparent bars). Detailed descriptions of other baselines can be found in the caption of Figure 19 of our arXiv paper.

+ +#### Gather Fusion Reduces *Hardware* IO costs via L2 Cache Locality + +The L2 cache sits between HBM and SMEM in the GPU memory hierarchy and is shared across all SMs. All traffic between SMs and HBM flows through L2: when an SM requests data that is already cached, the request is served at L2 bandwidth (~20 TB/s [7]) without touching HBM. When the request misses, the data is fetched from HBM (7.7 TB/s) and inserted into L2 for future reuse. + +A common alternative to gather fusion is to run a separate gather kernel that pre-arranges the inputs into a contiguous buffer before the Grouped GEMM. Although both approaches have identical *algorithmic IO costs* (assuming no TMA multicast along the N dimension), gather fusion reduces the actual HBM load traffic through better L2 cache utilization. + +

+

Figure: Gather fusion (left) reads from a compact source tensor. Contiguous load (right) reads from a K times larger tensor where each token is duplicated across K distinct addresses, expanding the working set beyond L2 capacity as granularity increases.

+ + +> [!NOTE] +> Although gather fusion has the same *algorithmic IO costs* as contiguous load from pre-gathered inputs, **gather fusion achieves lower hardware HBM IO costs via better L2 cache hit rate.** + + +We validate this with NCU profiling and present detailed results in the appendix. + + +### SwiGLU and dSwiGLU Fusion + +SonicMoE applies the activation function in-register before any data leaves the epilogue. The GEMM accumulator holds MMA result sub-tiles in registers. SwiGLU is applied element-wise in an interleaved format to produce activation sub-tiles. Both MMA results ($H$) and SwiGLU activations ($A$) will be written to the HBM via the async TMA store mechanism which does not add latency to the critical path. + + +### Overlapping IO with MMA Compute: $dH$ kernel +SonicMoE overlaps IO with MMA whenever possible. Here we focus on the $dH$ kernel which has the heaviest epilogue in SonicMoE. To address this, we overlap the role of epilogue warps with the role of MMA warp by splitting the TMEM resources and employing dedicated TMA pipeline. + +

+

Figure: illustration of epilogue ops overlapped with GEMM MMA in SonicMoE's dH kernel.

+ + +In the following figure, we examine the hardware unit utilization of SonicMoE's $dH$ kernel with heavy epilogue (left column) or GEMM with normal epilogue store (right column) on Qwen3-235B-A22B-Thinking-2507 ($(T,d,n,E,K)=(32768,4096,1536,128,8)$). **The drop in MMA throughput is *subproportional* to the increase in epilogue IO costs:** +- The $dH$ kernel epilogue increases HBM traffic by 24% (6.33 to 7.86 GB). +- However, both the Tensor Core and Tensor Memory utilization only drop from 98% to 88% with the corresponding TFLOPS drop from 1213 to 1078 (11% decrease). + + +

   

+

   

+

Figure: Nsight Compute Profiling of SonicMoE's dH kernel Grouped GEMM with 4 epilogue ops (left column) vs. Grouped GEMM alone (right column) of Qwen3-235B-A22B-Thinking-2507 (microbatch size=32k) on B300 GPUs. The top row is the achieved throughput of Tensor Pipe (MMA) and DRAM at kernel runtime, and the bottom row shows the transferred bytes on hardware units.

+ +> [!NOTE] +> **Overlapping IO with computation effectively absorbs the additional memory traffic, so the increase in IO cost does not translate proportionally into increased runtime.** + + + +## 5. Benchmark Results + +We evaluate SonicMoE against multiple baselines on B300 GPUs. We benchmark the forward and backward pass of a single MoE layer with configurations adapted from open-source 7B to 685B MoE, and we then profile kernel-level time breakdown on 7B MoE specifically. + + +### Forward and Backward TFLOPS of 6 Open-source MoE Configs + +The figure below shows forward and backward TFLOPS across six real open-source MoE configurations, ranging from a 7B to a 685B MoE model. + +

+

Figure: Forward (left) and backward (right) TFLOPS on B300 for 6 real MoE configurations. From left to right: OLMoE-1B-7B-0125, gpt-oss-20b, Kimi-Linear-48B-A3B-Base, Qwen3-Next-80B-A3B-Thinking, Qwen3-235B-A22B-Thinking-2507, and DeepSeek-V3.2-Exp. Triton official example does not support backward pass, nor K=10 for Qwen3-Next-80B forward pass.

+ + +#### Baselines: + +| Baseline | Description | +|---|---| +| **ScatterMoE** | [OpenLM Engine version](https://github.com/open-lm-engine/accelerated-model-architectures/blob/main/xma/layers/moe/triton_implementation/__init__.py) (same kernel code, slightly different API). | +| **MoMoE** | [Official implementation](https://github.com/tilde-research/MoMoE-impl) with shared experts disabled and expert bias adjustment removed. | +| **DeepGEMM** | DeepGEMM's [SM100 varlen-M](https://github.com/deepseek-ai/DeepGEMM/blob/d30fc36c8f229f4f873b90a492f6e19e6e610923/csrc/jit_kernels/impls/sm100_bf16_gemm.hpp#L124) and [varlen-K](https://github.com/deepseek-ai/DeepGEMM/blob/d30fc36c8f229f4f873b90a492f6e19e6e610923/csrc/jit_kernels/impls/sm100_bf16_gemm.hpp#L233) BF16 Grouped GEMM, paired with a separate optimized gather kernel and `torch.compile` for all activation and expert aggregation kernels. This represents the throughput a practitioner would achieve by integrating DeepGEMM as a drop-in Grouped GEMM library. | +| **Triton official example** | Adapted from [bench_mlp.py](https://github.com/triton-lang/triton/blob/7d0756121cc95d6971112fc5c1fa99107b892444/python/triton_kernels/bench/bench_mlp.py#L53) with expert parallelism disabled. | + + +#### Results: + +**SonicMoE consistently leads on all configurations**. On average across 6 configs, SonicMoE achieves 54%/35% higher forward/backward TFLOPS than DeepGEMM baseline, and 21% higher forward TFLOPS than triton official example. **SonicMoE has a decisive advantage (often achieving *double* TFLOPS) over the ScatterMoE and MoMoE baselines across all configs.** + + + + +### Profiling Time Breakdown + +The runtime breakdown below makes the speedup concrete. The "gather $X$" segment in the forward pass and "gather $dO$ and $X$" segment in the backward pass are absorbed into the GEMM bars for SonicMoE, and this constitutes one major source of speedup over the DeepGEMM-built baseline, which also has optimized Grouped GEMM but requires a separate gather kernel. + +We note that **although Triton official example has gather fusion and *does not* store $H$ (as it is inference-oriented with no need of caching activation), SonicMoE is still faster for all three kernels during forward pass**. This is because SonicMoE employs a faster Grouped GEMM implementation with the CLC tile scheduler and 2CTA MMA, and the expert aggregation kernel is heavily optimized. Please refer to the appendix for more details. + +

+

Figure: Runtime breakdown of SonicMoE vs baselines on B300 for a 7B OLMoE-sized MoE (T=32768, d=2048, n=1024, E=64, K=8). Detailed descriptions of other baselines can be found in the caption of Figure 5 of our arXiv paper. On this config, SonicMoE's major speedup comes from the gather fusion, and the faster GEMM delivers another 10% speedup. We abbreviate TFLOPS as "TF/s" in the figure.

+ + + +## Conclusion + +SonicMoE started from a simple observation: the field is building MoEs that are more fine-grained and sparser with every generation, and existing kernels were not designed for that regime. Roughly 2 years from Mixtral to Kimi K2.5 represent a 9× increase in granularity and a 12× drop in activation ratio, and every step of that journey makes the arithmetic intensity worse and the activation memory larger. **We need to re-visit our infrastructure design blueprint to embrace this MoE model trend, and SonicMoE is one of our answers.** + +- **Activation memory-efficient and IO-aware algorithm design.** By redesigning the backward pass to avoid caching any $O(TKd)$-sized tensor, SonicMoE's per-layer activation memory is independent of expert granularity — matching a dense model with the same activated parameter count, without any GEMM recomputation. The same algorithmic reordering eliminates multiple large HBM round-trips, and the remaining IO costs are largely hidden behind MMA computation through hardware asynchrony on both Hopper and Blackwell GPUs. + +- **Extensible software abstraction with hardware-aware optimization.** All of SonicMoE's kernels are instances of one shared structure built on QuACK. This abstraction confines architecture-specific behavior to localized overrides while leaving the epilogue fusion logic and the GEMM interface untouched. This enables fast iteration for prototyping new model architectures and benchmarking new hardware features. + + +**Future directions.** The most immediate extension is expert parallelism: the IO-aware design principles transfer directly to the intra-node and inter-node setting, where network bandwidth is even more constraining than HBM. After that, we plan to add MXFP8 and MXFP4 support. Finally, the next GPU generation (Rubin) will bring new hardware primitives, and with the abstraction in place, we expect the port to require no more work than the Hopper-to-Blackwell migration did. + +## Citing this blogpost + +If you find SonicMoE helpful in your research or development, please consider citing us: + +``` +@article{guo2025sonicmoe, + title={SonicMoE: Accelerating MoE with IO and Tile-aware Optimizations}, + author={Guo, Wentao and Mishra, Mayank and Cheng, Xinle and Stoica, Ion and Dao, Tri}, + journal={arXiv preprint arXiv:2512.14080}, + year={2025} +} +``` + +## References + +[1] Yang, Haoqi, et al. "Faster moe llm inference for extremely large models." arXiv preprint arXiv:2505.03531 (2025). + +[2] Michael Diggin. "Implementing a Split-K Matrix Multiplication Kernel in Triton." https://medium.com/@michael.diggin/implementing-a-split-k-matrix-multiplication-kernel-in-triton-7ad93fe4a54c + +[3] NVIDIA CUTLASS Documentation. "Blackwell Cluster Launch Control." https://docs.nvidia.com/cutlass/4.4.1/media/docs/cpp/blackwell_cluster_launch_control.html + +[4] Modular. "Matrix Multiplication on NVIDIA's Blackwell Part 3: The Optimizations Behind 85% of SOTA Performance." https://www.modular.com/blog/matrix-multiplication-on-nvidias-blackwell-part-3-the-optimizations-behind-85-of-sota-performance + +[5] PyTorch Blog. "Enabling Cluster Launch Control with TLX." https://pytorch.org/blog/enabling-cluster-launch-control-with-tlx/ + +[6] Alex Armbuster. "How To Write A Fast Matrix Multiplication From Scratch With Tensor Cores." https://alexarmbr.github.io/2024/08/10/How-To-Write-A-Fast-Matrix-Multiplication-From-Scratch-With-Tensor-Cores.html + +[7] K.V. Nagesh. "NVIDIA Blackwell Architecture: A Deep Dive into the Next Generation of AI Computing." https://medium.com/@kvnagesh/nvidia-blackwell-architecture-a-deep-dive-into-the-next-generation-of-ai-computing-79c2b1ce3c1b + +

+

+ +## Appendix + +Below, we collect implementation comparisons and ablation studies that support the main text. We present a few ablation studies: cp.async vs. TMA gather4 for gather fusion, L2 cache locality analysis of gather fusion, and the design choice between GEMM + gather-and-sum vs. GEMM with scatter fusion + sum for expert aggregation. + + +### Ablation Studies + +#### `cp.async` vs. TMA gather4 for gather fusion + +We first autotune on the best GEMM configs (tile shape, tile scheduler type, etc.) with `cp.async`, and then we switch in-place to TMA gather. In the following figure, we find that these two mechanisms deliver similar TFLOPS (diff < 2% for most cases). Nevertheless, we add whether to use TMA gather or cp.async gather as an autotunable configuration at kernel runtime. + +

+

Figure: `cp.async` vs. TMA gather TFLOPS for forward pass up-proj (gather on M dim) and backward dW1 kernel (gather on K dim) kernels on B300 GPUs. Percentages indicate the relative TFLOPS difference of TMA gather over `cp.async`.

+ +#### L2 Cache Locality with Gather Fusion + +We compare the gather fusion against running a separate gather kernel to pre-arrange the inputs into a contiguous buffer before feeding into the Grouped GEMM kernel. The Nsight Compute memory charts below show a varlen-M Grouped GEMM kernel with gather fusion (left) and with pre-gathered contiguous inputs (right). Despite nearly identical L2->SMEM traffic (17.74 GB), the gather fusion (left figure) shows less HBM load traffic (2.20 vs 2.68 GB) and higher L2 cache hit rate (74.9% vs 66.3%). + +

   

+

Figure: Nsight Compute memory chart for varlen-M Grouped GEMM during up-proj forward pass for MoE with size (T, d, n, E, K) = (32768, 2048, 512, 256, 32). Left: gather fusion with `cp.async`. Right: contiguous TMA load with pre-gathered inputs. Both use the same tile shape, scheduler configuration, and L2 swizzling pattern.

+ + + +This is because gather fusion's source tensor ($X$ or $dO$) often has size $T \times d$, which is $K\times$ smaller than the pre-gathered tensor of size $T\times K \times d$. As expert granularity increases, $K$ grows proportionally, and the pre-gathered tensor can exceed the GPU's L2 cache capacity (192 MB on B300). When this happens, the data request will miss L2 and be served from HBM. Gather fusion avoids this: it reads from the compact original tensor, which is more likely to stay resident in L2 cache. + + + + +This advantage compounds with expert granularity. Gathered $X$ and gathered $dO$, which are inputs to four of SonicMoE's six Grouped GEMM kernels, are both $O(TKd)$-sized and grow linearly with $K$. The figures below confirm the trend across three model families: as granularity increases (from left to right on each column), the contiguous path's HBM load traffic grows faster and its L2 hit rate drops further relative to gather fusion. + +

+

Figure: HBM load bytes (top row) and device L2 cache hit rate (bottom row) for gather fusion vs. contiguous load across varying expert granularity on B300 GPUs. Annotations on the top row show the absolute and relative HBM load increase of the contiguous path over gather fusion. Annotations on the bottom row show the L2 hit rate advantage of gather fusion.

+ + +#### Expert Aggregation Bandwidth + +In SonicMoE's expert aggregation kernel, each token will gather the Grouped GEMM results and sum over them in parallel. No GEMM is involved and this is a memory-bound kernel. The first version was implemented on CuteDSL, but we later switched to a pure Triton implementation due to the convenience of autotuning. This kernel achieves close-to-peak memory bandwidth on Hopper; here we validate its performance on Blackwell: + +

+

Figure: Expert aggregation kernel memory bandwidth on B300 across 1.4B, 7B, 30B, and 120B MoE configurations. SonicMoE's gather-and-sum kernel (blue) approaches the triton upper bound (grey, max of tl.load and TMA) at every scale.

+ +**Surprisingly, we find this Triton implementation still performs well enough on Blackwell GPUs (B300). This kernel surpasses 6.5 TB/s (85%+ peak) across most configs, achieving 98% of an optimized summation kernel on contiguous inputs.** We also find this simple aggregation kernel outperforms the [Gluon TMA gather-and-sum, adapted from Gluon official example](https://github.com/triton-lang/triton/blob/main/python/tutorials/gluon/09-tma-gather-scatter.py) implementation by 5% on average. This further suggests that gather with `cp.async` is not worse than TMA gather. + +#### GEMM + gather-and-sum vs. GEMM with scatter + sum aggregation + +

+

Figure: Possible strategies for storing the results and aggregating the results for each token. SonicMoE chooses the first strategy (left) in which each expert directly stores contiguously-packed outputs via TMA in the GEMM epilogue. In the expert aggregation kernel, each token gathers and sums over activated expert outputs. ScatterMoE and MoMoE (middle) choose to fuse HBM store with scatter in epilogue and launch a summation kernel afterwards. It is also possible to fuse atomic add in the epilogue to circumvent the requirement of an expert aggregation kernel as the right subfigure illustrated.

+ +On Hopper GPUs, SonicMoE makes an unconventional design choice that we *do not* fuse scatter with GEMM. Instead, we perform this task alongside the aggregation. **We previously ablated on Hopper GPUs and identified that the synchronous `st.global` PTX instruction required for scatter fusion on Hopper would degrade TFLOPS by 20% for fine-grained MoE configs.** + + +> [!TIP] +> An IO-aware design emerges only when algorithmic intent and hardware execution semantics are reasoned about together. + +##### New Asynchronous Scatter Store Instructions on Blackwell +However, Blackwell introduces multiple asynchronous store instructions: (1) `st.async.release.global` and (2) TMA scatter4. **The advantage of GEMM + gather-and-sum over GEMM w. scatter fusion + sum becomes less apparent as we no longer run into the synchronous IO issue for GEMM w. scatter fusion on Hopper.** Even so, as we (1) do not observe major bandwidth degradation (0.98x) of gather-and-sum compared with contiguous summation kernel and (2) expect GEMM with TMA to be no slower than GEMM with TMA scatter4 or `st.async`, we do not change SonicMoE's design choice on Blackwell. + +We perform an ablation comparing varlen-M Grouped GEMM w. TMA + gather-and-sum against varlen-M Grouped GEMM w. TMA scatter + sum, adapting the official Triton Grouped GEMM example for both. The `grouped gemm w. TMA + gth-and-sum` approach stores Grouped GEMM results into a contiguously-packed tensor across all experts during the down-projection forward epilogue, where each token gathers and sums its corresponding expert outputs in a single fused operation. The `grouped gemm w. TMA sct + sum` approach instead scatters results via TMA during the epilogue and applies a separate contiguous summation kernel afterwards. + +*Disclaimer: the Grouped GEMM kernel in this ablation study is implemented with triton with fewer low-level optimizations (e.g. without 2CTA MMA) than SonicMoE's Grouped GEMM, but it still provides insight on the relative performance comparison between GEMM w. TMA and GEMM w. TMA scatter4.* + +

+

Figure: Throughput of varlen-M Grouped GEMM and expert aggregation kernel on B300 GPUs during forward pass. In the first row, we report the Grouped GEMM TFLOPS on transparent bars and the gemm-and-aggregation TFLOPS on opaque bars. In the second row, we compare the expert aggregation bandwidth between gather-and-sum and a contiguous sum kernel.

+ +In the first row, +- **GEMM-only TFLOPS** (transparent bars): `grouped gemm w. TMA` still has 5% higher TFLOPS than `grouped gemm w. TMA sct` + +- **GEMM-and-aggregation TFLOPS** (opaque bars): `grouped gemm w. TMA + gth-and-sum` still has 3% higher TFLOPS than `grouped gemm w. TMA sct + sum` + +In the second row, we already know that `gth-and-sum` only has 2% less bandwidth than `sum`. + +Although this 3% gap is much smaller than the prior gap on Hopper GPUs (20%), it still validates SonicMoE's design on Blackwell GPUs. From feebf4aa6bba49a4c467221b1ca71ff7927e73ee Mon Sep 17 00:00:00 2001 From: GarlGuo Date: Wed, 22 Apr 2026 11:27:46 -0400 Subject: [PATCH 2/3] sonicmoe --- _posts/2026-04-26-sonicmoe-blackwell.md | 139 +++++++++++++----------- 1 file changed, 74 insertions(+), 65 deletions(-) diff --git a/_posts/2026-04-26-sonicmoe-blackwell.md b/_posts/2026-04-26-sonicmoe-blackwell.md index 427f128..4edba77 100644 --- a/_posts/2026-04-26-sonicmoe-blackwell.md +++ b/_posts/2026-04-26-sonicmoe-blackwell.md @@ -2,7 +2,7 @@ layout: distill title: "SonicMoE: A Hardware-Efficient and Software-Extensible Blueprint for Fine-Grained MoEs" giscus_comments: false -date: 2025-12-15 +date: 2026-04-22 featured: true related_posts: false @@ -14,8 +14,8 @@ toc: - name: 1. Opportunities and Pains of Fine-Grained MoEs subsections: - name: MoE as Grouped GEMM - - name: SonicMoE's Solution - Algorithm and Kernel Decomposition - - name: 2. QuACK's Software Abstraction that Empowers SonicMoE + - name: SonicMoE - the Algorithm and Kernel Decomposition + - name: 2. the Software Abstraction of QuACK that Empowers SonicMoE subsections: - name: Tiled GEMM kernel on NVIDIA GPUs - name: Customizable Epilogue @@ -82,13 +82,17 @@ authors: font-style: normal !important; } -/* Style theorem boxes to look like HackMD */ +/* Style theorem boxes to look like HackMD - centered and boxed */ .post blockquote { - background-color: transparent; + background-color: rgba(76, 158, 255, 0.08); + border: 1px solid var(--global-theme-color, #4c9eff); border-left: 4px solid var(--global-theme-color, #4c9eff); + border-radius: 4px; padding: 1rem 1.5rem; font-size: inherit; color: inherit; + max-width: 85%; + margin: 1.5rem auto; } /* Center display math equations */ @@ -153,7 +157,6 @@ html[data-theme='dark'] .post blockquote mjx-container * { -[![arXiv](https://img.shields.io/badge/arXiv-2512.14080-b31b1b.svg)](https://arxiv.org/abs/2512.14080) [![Code](https://img.shields.io/badge/GitHub-SonicMoE-blue?logo=github)](https://github.com/Dao-AILab/sonic-moe) [![PyPI](https://img.shields.io/pypi/v/sonic-moe?cache=no)](https://pypi.org/project/sonic-moe/)

Figure: SonicMoE's per-layer activation memory footprint (left) stays constant even when expert granularity (embedding dimension / expert intermediate dimension) increases, and SonicMoE can achieve 1.87-4.04x relative speedup compared to existing MoE training kernels ScatterMoE and MoMoE.

@@ -161,9 +164,15 @@ html[data-theme='dark'] .post blockquote mjx-container * { **SonicMoE now runs at peak throughput on NVIDIA Blackwell GPUs (B200/B300), in addition to its existing Hopper (H100) support.** This blogpost walks through how we got there: an IO-aware algorithm that keeps activation memory independent of expert granularity, a unified software abstraction on [QuACK](https://github.com/Dao-AILab/quack) that makes porting across GPU architectures straightforward, and the Blackwell hardware features we exploit to hide IO costs behind computation. +

+ arXiv + Code + PyPI +

+ ## 1. Opportunities and Pains of Fine-Grained MoEs -Mixture-of-Experts (MoE) models have become the dominant architecture for scaling language models without proportionally increasing compute. The appeal is straightforward: by routing each token to a small subset of $K$ out of $E$ expert networks, we get a model with hundreds of billions of parameters at the compute cost of a much smaller dense model. The training FLOP savings and quality improvements are well-established, but they come with hardware costs that grow worse as models become more fine-grained. +Mixture-of-Experts (MoE) models have become the dominant architecture for scaling language models without proportionally increasing compute. The appeal is straightforward: by routing each token to a small subset of $$K$$ out of $$E$$ expert networks, we get a model with hundreds of billions of parameters at the compute cost of a much smaller dense model. The training FLOP savings and quality improvements are well-established, but they come with hardware costs that grow worse as models become more fine-grained.

@@ -171,11 +180,11 @@ Mixture-of-Experts (MoE) models have become the dominant architecture for scalin Two architectural dimensions define how an MoE model trades off quality and efficiency. -- **Granularity** ($G = d/n$, where $d$ is the model embedding dimension and $n$ is each expert's intermediate size) measures how small the experts are relative to the model width. A high-granularity (fine-grained) MoE has many small experts. +- **Granularity** ($$G = d/n$$, where $$d$$ is the model embedding dimension and $$n$$ is each expert's intermediate size) measures how small the experts are relative to the model width. A high-granularity (fine-grained) MoE has many small experts. -- **Sparsity** ($\rho = K/E$) measures the ratio of experts activated per token. +- **Sparsity** ($$\rho = K/E$$) measures the ratio of experts activated per token. -MoE scaling laws, from controlled experiments (e.g. [Krajewski et al.](https://arxiv.org/pdf/2402.07871) and [Tian et al.](https://arxiv.org/pdf/2507.17702)) and recent open-source model scaling trends, consistently show that higher granularity and higher sparsity yield better model quality per FLOP: selecting more, smaller experts increases representational capacity, while sparser activation allows more total parameters within the same compute budget. Frontier open-source models reflect this clearly: [Mixtral 8x22B](https://huggingface.co/mistralai/Mixtral-8x22B-v0.1), released in 2024, operated at $G=0.38$ and $\rho=0.25$, while recent models since 2025 like [DeepSeek V3.2](https://huggingface.co/deepseek-ai/DeepSeek-V3.2) ($G=3.50$, $\rho=0.03$), [Kimi K2.5](https://huggingface.co/moonshotai/Kimi-K2.5) ($G=3.50$, $\rho=0.02$), and [Qwen3-Next-80B-A3B-Instruct](https://huggingface.co/Qwen/Qwen3-Next-80B-A3B-Instruct) ($G=4.00$, $\rho=0.02$) have pushed both dimensions aggressively. Every new generation of frontier MoE is more fine-grained and sparser than the last. +MoE scaling laws, from controlled experiments (e.g. [Krajewski et al.](https://arxiv.org/pdf/2402.07871) and [Tian et al.](https://arxiv.org/pdf/2507.17702)) and recent open-source model scaling trends, consistently show that higher granularity and higher sparsity yield better model quality per FLOP: selecting more, smaller experts increases representational capacity, while sparser activation allows more total parameters within the same compute budget. Frontier open-source models reflect this clearly: [Mixtral 8x22B](https://huggingface.co/mistralai/Mixtral-8x22B-v0.1), released in 2024, operated at $$G=0.38$$ and $$\rho=0.25$$, while recent models since 2025 like [DeepSeek V3.2](https://huggingface.co/deepseek-ai/DeepSeek-V3.2) ($$G=3.50$$, $$\rho=0.03$$), [Kimi K2.5](https://huggingface.co/moonshotai/Kimi-K2.5) ($$G=3.50$$, $$\rho=0.02$$), and [Qwen3-Next-80B-A3B-Instruct](https://huggingface.co/Qwen/Qwen3-Next-80B-A3B-Instruct) ($$G=4.00$$, $$\rho=0.02$$) have pushed both dimensions aggressively. Every new generation of frontier MoE is more fine-grained and sparser than the last. However, the pursuit of granularity and sparsity comes with two painful hardware costs: @@ -185,9 +194,9 @@ However, the pursuit of granularity and sparsity comes with two painful hardware #### Problem 1: Activation Memory Scales with Expert Granularity with Current Training Kernels. -During training, intermediate tensors must be cached for the backward pass. The total FLOPs of MoE forward and backward computation is $(6+12)TnKd$. For fixed $T$ and $d$, keeping FLOPs constant requires $nK$ to stay constant. Increasing granularity means decreasing $n$ and proportionally increasing $K$. Any activation of size $O(TKd)$ thus grows linearly with granularity. +During training, intermediate tensors must be cached for the backward pass. The total FLOPs of MoE forward and backward computation is $$(6+12)TnKd$$. For fixed $$T$$ and $$d$$, keeping FLOPs constant requires $$nK$$ to stay constant. Increasing granularity means decreasing $$n$$ and proportionally increasing $$K$$. Any activation of size $$O(TKd)$$ thus grows linearly with granularity. -For current MoE kernels like [ScatterMoE](https://arxiv.org/pdf/2403.08245) and [MoMoE](https://github.com/tilde-research/MoMoE-impl), variables such as the down-proj output $Y$ (size $TKd$) are cached for the backward pass, causing per-layer activation memory to grow linearly as experts become more fine-grained. Prior solutions such as MoMoE usually require a GEMM recomputation during backward to trade off activation memory for extra FLOPs. This raises the question: +For current MoE kernels like [ScatterMoE](https://arxiv.org/pdf/2403.08245) and [MoMoE](https://github.com/tilde-research/MoMoE-impl), variables such as the down-proj output $$Y$$ (size $$TKd$$) are cached for the backward pass, causing per-layer activation memory to grow linearly as experts become more fine-grained. Prior solutions such as MoMoE usually require a GEMM recomputation during backward to trade off activation memory for extra FLOPs. This raises the question:

Is it possible to achieve activation memory efficiency without extra FLOPs from GEMM recomputation?

@@ -200,11 +209,10 @@ Assuming perfect load balancing and SwiGLU activation, the arithmetic intensity $$\text{Arithmetic Intensity} = \frac{3}{\frac{2}{d} + \frac{2G}{d} + \frac{3}{T\rho}} = O\left(\min\left(\frac{d}{G}, T\rho\right)\right)$$ -where $T$ is the number of tokens in a microbatch ($T\rho$ is the average number of routed tokens per expert). +where $$T$$ is the number of tokens in a microbatch ($$T\rho$$ is the average number of routed tokens per expert). -In this case, **both increasing $G$ and increasing MoE sparsity (decreasing $\rho$) would drive arithmetic intensity down.** For example, [Qwen3-Next-80B-A3B-Instruct](https://huggingface.co/Qwen/Qwen3-Next-80B-A3B-Instruct) would have an arithmetic intensity of 210 for a microbatch of 16K tokens, while an iso-param dense SwiGLU MLP would have an arithmetic intensity of 2570, 12× higher. In this regime, kernel runtime is dominated by the IO costs, not compute throughput. +In this case, **both increasing $$G$$ and increasing MoE sparsity (decreasing $$\rho$$) would drive arithmetic intensity down.** For example, [Qwen3-Next-80B-A3B-Instruct](https://huggingface.co/Qwen/Qwen3-Next-80B-A3B-Instruct) would have an arithmetic intensity of 210 for a microbatch of 16K tokens, while an iso-param dense SwiGLU MLP would have an arithmetic intensity of 2570, 12× higher. In this regime, kernel runtime is dominated by the IO costs, not compute throughput. -> [!NOTE] > For fine-grained and sparse MoEs, every expert's GEMM problem shape is small enough such that the kernel falls into the memory-bound regime. **These IO costs will become a greater bottleneck in expert parallelism, as the intra- or inter-node network bandwidth are often *much* slower than HBM loading speed.** SonicMoE currently focuses on the case of single GPU (EP degree=1), but the IO-aware algorithmic designs are transferable to expert parallelism. @@ -213,11 +221,11 @@ In this case, **both increasing $G$ and increasing MoE sparsity (decreasing $\rh ### MoE as Grouped GEMM -MoE computation is often implemented using Grouped GEMM. A Grouped GEMM is a batch of matrix multiplications with possibly different problem shapes. Following standard BLAS conventions used by CUTLASS, each GEMM computes $C = AB$ where $A \in \mathbb{R}^{M \times K}$ (activations), $B \in \mathbb{R}^{K \times N}$ (weights), and $C \in \mathbb{R}^{M \times N}$ (outputs). +MoE computation is often implemented using Grouped GEMM. A Grouped GEMM is a batch of matrix multiplications with possibly different problem shapes. Following standard BLAS conventions used by CUTLASS, each GEMM computes $$C = AB$$ where $$A \in \mathbb{R}^{M \times K}$$ (activations), $$B \in \mathbb{R}^{K \times N}$$ (weights), and $$C \in \mathbb{R}^{M \times N}$$ (outputs). In MoE, each expert usually receives a different number of tokens, and input tokens may need to be gathered from different positions, or they may already be contiguously packed by expert. -For the forward pass and backward activation gradient, we would need Grouped GEMM with input shapes that have constant $N$ and $K$ (embedding dimension and expert intermediate dimension) but different $M$ (the number of routed tokens per expert). **We call this varlen-M Grouped GEMM**. (CUTLASS would describe it as *Grouped GEMM with ragged M dimensions*). For the backward weight gradient, we would reduce over token embeddings for each expert GEMM, in which $M$ and $N$ (embedding dimension and expert intermediate dimension) are fixed but the $K$ dimension varies. **We call this varlen-K Grouped GEMM**. +For the forward pass and backward activation gradient, we would need Grouped GEMM with input shapes that have constant $$N$$ and $$K$$ (embedding dimension and expert intermediate dimension) but different $$M$$ (the number of routed tokens per expert). **We call this varlen-M Grouped GEMM**. (CUTLASS would describe it as *Grouped GEMM with ragged M dimensions*). For the backward weight gradient, we would reduce over token embeddings for each expert GEMM, in which $$M$$ and $$N$$ (embedding dimension and expert intermediate dimension) are fixed but the $$K$$ dimension varies. **We call this varlen-K Grouped GEMM**.

               

Left: Each expert gathers inputs from different positions on an input tensor (top) or reads a contiguous chunk on a grouped input array (bottom). Right: Illustration of using Grouped GEMM in MoE.

@@ -245,63 +253,62 @@ For weight gradient, we need to use varlen-K Grouped GEMM to reduce over token e The standard implementation materializes every intermediate tensor in HBM between kernel launches. This creates two separate costs that both scale with expert granularity: -- **Activation memory**: gathered $X$, down-proj output $Y$, and scattered $Y$ must all be cached for the backward pass, each consuming $2TKd$ bytes. As granularity increases, these $O(TKd)$-sized tensors grow linearly. +- **Activation memory**: gathered $$X$$, down-proj output $$Y$$, and scattered $$Y$$ must all be cached for the backward pass, each consuming $$2TKd$$ bytes. As granularity increases, these $$O(TKd)$$-sized tensors grow linearly. -- **IO costs**: every materialized intermediate is a round-trip to HBM. The backward pass is worse: it must additionally materialize $dY$ and gathered $dO$, both $O(TKd)$-sized. **Since fine-grained MoE kernels operate in the memory-bound regime, these IO costs directly dominate runtime.** +- **IO costs**: every materialized intermediate is a round-trip to HBM. The backward pass is worse: it must additionally materialize $$dY$$ and gathered $$dO$$, both $$O(TKd)$$-sized. **Since fine-grained MoE kernels operate in the memory-bound regime, these IO costs directly dominate runtime.** -### SonicMoE's Solution: Algorithm and Kernel Decomposition +### SonicMoE: the Algorithm and Kernel Decomposition -**SonicMoE addresses both problems through a single algorithmic redesign: we circumvent the need to cache or materialize any variable with size $O(TKd)$.** This makes activation memory independent of expert granularity, and simultaneously eliminates multiple large HBM round-trips that dominate runtime. +**SonicMoE addresses both problems through a single algorithmic redesign: we circumvent the need to cache or materialize any variable with size $$O(TKd)$$.** This makes activation memory independent of expert granularity, and simultaneously eliminates multiple large HBM round-trips that dominate runtime. -In particular, SonicMoE avoids caching down-proj output $Y$, scattered $Y$, and gathered $X$ which all have size $T K d$. We also avoid writing $dY$ and gathered $dO$ to HBM: +In particular, SonicMoE avoids caching down-proj output $$Y$$, scattered $$Y$$, and gathered $$X$$ which all have size $$T K d$$. We also avoid writing $$dY$$ and gathered $$dO$$ to HBM: -- **Gathered $X$ and $dO$**: we gather inputs at each kernel runtime and *never* cache the gathered results. +- **Gathered $$X$$ and $$dO$$**: we gather inputs at each kernel runtime and *never* cache the gathered results. -- **Scattered $Y$**: we fuse it with the aggregation operation where each token will gather and sum over activated expert results. +- **Scattered $$Y$$**: we fuse it with the aggregation operation where each token will gather and sum over activated expert results. -- **$Y$ and $dY$**: we redesign the computational path that starts from $dO$ and $H$ to directly compute $dS$ and $dH$ during the backward pass **without $Y$ and $dY$**. **Prior MoE kernels such as ScatterMoE and MoMoE must cache $Y$ for this computation**: - - $dH$: we apply gather fusion with $dO$ (no need for $dY$) and dSwiGLU fusion with an extra load of $H$. +- **$$Y$$ and $$dY$$**: we redesign the computational path that starts from $$dO$$ and $$H$$ to directly compute $$dS$$ and $$dH$$ during the backward pass **without $$Y$$ and $$dY$$**. **Prior MoE kernels such as ScatterMoE and MoMoE must cache $$Y$$ for this computation**: + - $$dH$$: we apply gather fusion with $$dO$$ (no need for $$dY$$) and dSwiGLU fusion with an extra load of $$H$$. - - $dS$: we swap the contraction order. **This is equivalent to placing $S$ weighting *before* down-proj forward pass and using only $A$ and $dA'$ for computing $dS$ instead of $Y$ and $dO$.** We no longer need to cache $Y$. + - $$dS$$: we swap the contraction order. **This is equivalent to placing $$S$$ weighting *before* down-proj forward pass and using only $$A$$ and $$dA'$$ for computing $$dS$$ instead of $$Y$$ and $$dO$$.** We no longer need to cache $$Y$$. - For an expert $e$, denote the down-proj weights for expert $e$ as $W_{2,e}\in \mathbb{R}^{n\times d}$. The Grouped GEMM in down-proj activation gradient will compute $dA' = dO_e W_2^\top$. + For an expert $$e$$, denote the down-proj weights for expert $$e$$ as $$W_{2,e}\in \mathbb{R}^{n\times d}$$. The Grouped GEMM in down-proj activation gradient will compute $$dA' = dO_e W_2^\top$$. - The standard path computes $dS_{t,e} = \langle dO_t,\ Y_{e,t}\rangle$, which requires caching $Y$. By substituting $Y_e = A_e W_{2,e}$ and rearranging the contraction order: + The standard path computes $$dS_{t,e} = \langle dO_t,\ Y_{e,t}\rangle$$, which requires caching $$Y$$. By substituting $$Y_e = A_e W_{2,e}$$ and rearranging the contraction order: $$dS_{t,e} = \langle dO_t,\ Y_{e,t}\rangle = \langle dO_t,\ A_e W_{2,e}\rangle = \langle dO_t W_{2,e}^\top,\ A_{e,t}\rangle = \langle dA_{e,t}',\ A_{e,t}\rangle$$ - Neither $dA_{e,t}'$ nor $A_{e,t}$ depends on $dY$ or $Y$. + Neither $$dA_{e,t}'$$ nor $$A_{e,t}$$ depends on $$dY$$ or $$Y$$. #### Activation Memory Independent of Expert Granularity -**SonicMoE's forward pass.** In the forward pass, SonicMoE only caches $X$ and $H$. The gathered results for $X$ are *never* cached or materialized. The expert aggregation kernel fuses the scatter and summation together. +**SonicMoE's forward pass.** In the forward pass, SonicMoE only caches $$X$$ and $$H$$. The gathered results for $$X$$ are *never* cached or materialized. The expert aggregation kernel fuses the scatter and summation together.

Figure: SonicMoE's forward computational workflow and comparison with a standard MoE implementation in PyTorch. We also compare the activation memory and IO costs for both methods.

-The following figure gives a brief comparison on the activation memory breakdown. SonicMoE caches only inputs $X$ and pre-SwiGLU activation $H$ and *does not need any GEMM recomputation*. +The following figure gives a brief comparison on the activation memory breakdown. SonicMoE caches only inputs $$X$$ and pre-SwiGLU activation $$H$$ and *does not need any GEMM recomputation*.

Figure: illustration of cached activation memory for a single layer of Qwen3-235B MoE model (microbatch=32k) when equipped with different training kernels.

-> [!NOTE] > SonicMoE can achieve the same activation memory efficiency as a dense model with the same activated number of parameters without extra training FLOPs. #### IO Cost Reduction through Algorithmic Reordering -Each variable that is no longer cached is also one fewer read or write to HBM. The same redesign that eliminates $O(TKd)$-sized activations eliminates the corresponding HBM round-trips. +Each variable that is no longer cached is also one fewer read or write to HBM. The same redesign that eliminates $$O(TKd)$$-sized activations eliminates the corresponding HBM round-trips. -**SonicMoE's forward pass.** We fuse the gather and SwiGLU activation in the up-projection. The scatter $Y$ operation is fused with the expert aggregation. +**SonicMoE's forward pass.** We fuse the gather and SwiGLU activation in the up-projection. The scatter $$Y$$ operation is fused with the expert aggregation. **SonicMoE's backward pass.** -- **Activation gradient**: The down-proj activation grad $dH$ kernel computes $dH$, $dS$, and $A'$ (input for $dW_2$) simultaneously, none of which require caching $Y$ or $dY$. We similarly fuse dSwiGLU and the gather operation into the GEMM. +- **Activation gradient**: The down-proj activation grad $$dH$$ kernel computes $$dH$$, $$dS$$, and $$A'$$ (input for $$dW_2$$) simultaneously, none of which require caching $$Y$$ or $$dY$$. We similarly fuse dSwiGLU and the gather operation into the GEMM.

Figure: SonicMoE's backward computational workflow for activation gradient and comparison with a standard MoE implementation in PyTorch.

-- **Weight gradient**: The weight gradient kernels for $dW_1$ and $dW_2$ gather $X$ and $dO$ on the fly during execution. While their *algorithmic IO costs* match a standard MoE implementation, SonicMoE's gather fusion reduces the *hardware IO costs* by exploiting L2 cache locality, which we will discuss later. +- **Weight gradient**: The weight gradient kernels for $$dW_1$$ and $$dW_2$$ gather $$X$$ and $$dO$$ on the fly during execution. While their *algorithmic IO costs* match a standard MoE implementation, SonicMoE's gather fusion reduces the *hardware IO costs* by exploiting L2 cache locality, which we will discuss later.

Figure: SonicMoE's backward computational workflow for weight gradient and comparison with a standard MoE implementation in PyTorch.

@@ -311,33 +318,31 @@ The net effect is a large reduction in IO costs even before any hardware-specifi

Figure: Illustration of IO costs for a single layer of Qwen3-235B MoE model (microbatch=32k) when equipped with different training kernels. SonicMoE's workflow circumvents the need to read or write multiple massive-sized tensors compared to existing MoE kernels.

-Among these kernels, we want to give a special highlight to our backward down-proj activation gradient $dH$ kernel as a combination of IO-aware and hardware-aware algorithmic design: +Among these kernels, we want to give a special highlight to our backward down-proj activation gradient $$dH$$ kernel as a combination of IO-aware and hardware-aware algorithmic design:

Figure: the semantics of SonicMoE's dH workflow diagram is equivalent to standard PyTorch MoE implementation for multiple kernels while SonicMoE substantially reduces the IO costs.

-- **reduction of IO costs**: we gather $dO$, fuse the dSwiGLU call, and do not read or write $Y$ and $dY$. +- **reduction of IO costs**: we gather $$dO$$, fuse the dSwiGLU call, and do not read or write $$Y$$ and $$dY$$. -- **hardware asynchrony features that further hide the remaining IO cost latency** (will discuss later): the design of this $dH$ kernel already reduces IO costs, and we further minimize the remaining impact of IO costs by leveraging the asynchrony features on modern NVIDIA GPUs. +- **hardware asynchrony features that further hide the remaining IO cost latency** (will discuss later): the design of this $$dH$$ kernel already reduces IO costs, and we further minimize the remaining impact of IO costs by leveraging the asynchrony features on modern NVIDIA GPUs.

Figure: we can leverage recent NVIDIA hardware features to hide the IO latency in SonicMoE's dH kernel and greatly reduce the overall runtime.

-> [!TIP] > A careful algorithmic design is sufficient to address the activation memory issue and partially the IO cost issue. We can further minimize the impact of IO costs by leveraging hardware asynchrony. We want SonicMoE to achieve peak throughput on both Hopper and Blackwell GPUs, so we apply hardware-aware optimizations to all Grouped GEMM kernels in SonicMoE. However, modern NVIDIA GPU architectures often differ substantially in their execution models. **In response, we build a unified and modular software abstraction that expresses all grouped gemm kernels while localizing all architecture-specific optimizations to a small number of overrides.** The rest of this post describes that abstraction and how it is realized on each architecture. -## 2. QuACK's Software Abstraction that Empowers SonicMoE +## 2. the Software Abstraction of QuACK that Empowers SonicMoE SonicMoE already supports NVIDIA Hopper (SM90), Blackwell GPUs (SM100), and the support for Blackwell GeForce (SM120) GPUs is on the way. When we first considered porting the Hopper kernels to Blackwell, the straightforward path was to rewrite 6 Grouped GEMM kernels from scratch. We chose instead to factor out the shared structure, and this decision proved highly productive later. Every Grouped GEMM kernel is an instance of the same underlying structure: **a producer-consumer GEMM mainloop that overlaps data movement with tensor core computation, followed by a parameterized epilogue** that applies fusion logic directly to the accumulator before any data reaches HBM. -> [!NOTE] > This shared structure of GEMM mainloop with customizable epilogue would make SonicMoE's implementation modular, extendable to new hardware while still maintaining peak performance. We also unify the API and encapsulate other architecture-specific changes. **SonicMoE's GEMM kernels are built on top of [QuACK](https://github.com/Dao-AILab/quack), our in-house CuTeDSL library that draws heavily from [CUTLASS](https://github.com/NVIDIA/cutlass) and the [CuTeDSL official examples](https://github.com/NVIDIA/cutlass/tree/main/examples/python/CuTeDSL).** CUTLASS defines a clean layered programming model for GPU kernels: a mainloop that tiles the matrix multiplication across the parallel workers (Streaming Processors), and an epilogue that post-processes the results before writing them back to memory. QuACK adopts this layered programming model and extends it with modular components (tile schedulers, customizable epilogue, etc.). @@ -347,7 +352,7 @@ Below, we examine the design of QuACK GEMM and how it helps SonicMoE achieve pea ### Tiled GEMM kernel on NVIDIA GPUs -A General Matrix Multiplication (GEMM) kernel on NVIDIA GPUs repeatedly fetches tiles of input data $A,B$ ($A$ is usually the activations while $B$ is the weights), and we accumulate the tiled MMA (matrix multiply-accumulate) results into a zero-initialized buffer $C$ (often the output activations). +A General Matrix Multiplication (GEMM) kernel on NVIDIA GPUs repeatedly fetches tiles of input data $$A,B$$ ($$A$$ is usually the activations while $$B$$ is the weights), and we accumulate the tiled MMA (matrix multiply-accumulate) results into a zero-initialized buffer $$C$$ (often the output activations).

Figure: illustration of GEMM tiled accumulation [2]

@@ -393,7 +398,7 @@ The base GEMM class implements the epilogue as a fixed loop skeleton. For each s The `epi_visit_subtile` method is a no-op in the base class. Subclasses override it to inject arbitrary per-element fusion logic. **This single method is the injection point for every activation function, every backward pass computation, every scaling operation, and every reduction in the entire SonicMoE codebase.** -Each epilogue mixin (e.g., `GemmGatedMixin` for SwiGLU, `GemmDGatedMixin` for the $dH$ backward) is paired with an architecture-specific base class: `GemmGatedSm90` / `GemmGatedSm100`, `GemmDGatedSm90` / `GemmDGatedSm100`, etc. The architecture-specific suffix controls only the warp layout, accumulator movement (registers vs. tensor memory), and hardware resource management. **The epilogue fusion logic in `epi_visit_subtile` is shared across architectures.** For example, the heaviest kernel in SonicMoE is just a `GemmDGatedMixin` with additional arguments, implemented in 88 lines: +Each epilogue mixin (e.g., `GemmGatedMixin` for SwiGLU, `GemmDGatedMixin` for the $$dH$$ backward) is paired with an architecture-specific base class: `GemmGatedSm90` / `GemmGatedSm100`, `GemmDGatedSm90` / `GemmDGatedSm100`, etc. The architecture-specific suffix controls only the warp layout, accumulator movement (registers vs. tensor memory), and hardware resource management. **The epilogue fusion logic in `epi_visit_subtile` is shared across architectures.** For example, the heaviest kernel in SonicMoE is just a `GemmDGatedMixin` with additional arguments, implemented in 88 lines:

Figure: Two SonicMoE kernels implemented with QuACK. Left: the kernel workflow diagram. Center: the QuACK epilogue mixin class where each kernel overrides `epi_visit_subtile` (88 LoC for dH, 21 LoC for up-proj forward). Right: SonicMoE's simplified kernel launch call.

@@ -430,7 +435,7 @@ The software abstraction described in the previous section was designed so that **On Hopper**, MMA is usually performed via a *warpgroup-level* instruction WGMMA (`wgmma.mma_async`). It requires 128 threads (4 contiguous warps) to issue and manage: all threads in the warpgroup participate in tracking the accumulator state, and the accumulator result is distributed across the register files of those 128 threads. We often have 2 consumer warpgroups, and we can either let them *cooperatively* issue 2 WGMMA instructions, or **we can overlap the IO of 1 warpgroup with the GEMM of another warpgroup**. In this case, we can let 1 consumer warpgroup do MMA while the other consumer warpgroup does the epilogue, and they switch roles once each finishes. This is called "Ping-Pong warpgroup scheduling", often particularly useful to maintain high Tensor Core throughput with heavy epilogue. -For example, the down-proj forward kernel's epilogue has heavy HBM store IO relative to the mainloop. In the $dH$ kernel's epilogue, we need to load $H$ and execute multiple activation and reduction operations to compute and store $dH$, $dS$, and $A'$ as inputs for $dW_2$. +For example, the down-proj forward kernel's epilogue has heavy HBM store IO relative to the mainloop. In the $$dH$$ kernel's epilogue, we need to load $$H$$ and execute multiple activation and reduction operations to compute and store $$dH$$, $$dS$$, and $$A'$$ as inputs for $$dW_2$$.

Figure: Hopper Ping-Pong: two consumer warpgroups alternate between MMA and epilogue: while one runs Tensor Core MMA, the other @@ -453,9 +458,9 @@ While the MMA warp accumulates into one 256-column stage, the epilogue warps are ### 2CTA MMA -A second major Blackwell feature is the `cta_group::2` variant of UMMA. When this mode is enabled, a *pair* of CTAs in the same cluster cooperatively execute a single MMA instruction. The tile M dimension doubles: where a single-CTA UMMA supports up to $M_\mathrm{tile}=128$, a 2CTA UMMA supports up to $M_\mathrm{tile}=256$. +A second major Blackwell feature is the `cta_group::2` variant of UMMA. When this mode is enabled, a *pair* of CTAs in the same cluster cooperatively execute a single MMA instruction. The tile M dimension doubles: where a single-CTA UMMA supports up to $$M_\mathrm{tile}=128$$, a 2CTA UMMA supports up to $$M_\mathrm{tile}=256$$. -For a tile of shape $M_\mathrm{tile} \times N_\mathrm{tile} \times K_\mathrm{tile}$, the number of FLOPs is $2 M_\mathrm{tile} N_\mathrm{tile} K_\mathrm{tile}$ and the number of bytes loaded from SMEM is $2(M_\mathrm{tile} K_\mathrm{tile} + N_\mathrm{tile} K_\mathrm{tile})$ for A and B. For fixed $N_\mathrm{tile}$ and $K_\mathrm{tile}$, doubling $M_\mathrm{tile}$ doubles the FLOPs but only adds $2M_\mathrm{tile} K_\mathrm{tile}$ bytes of A data — the B tile of shape $N_\mathrm{tile} \times K_\mathrm{tile}$ is *shared* across the pair, so each CTA loads only half the B data it would need for two independent 1CTA tiles. This is the key benefit: the B tile is multicasted via TMA across the CTA pair, halving B-side SMEM traffic per output element. +For a tile of shape $$M_\mathrm{tile} \times N_\mathrm{tile} \times K_\mathrm{tile}$$, the number of FLOPs is $$2 M_\mathrm{tile} N_\mathrm{tile} K_\mathrm{tile}$$ and the number of bytes loaded from SMEM is $$2(M_\mathrm{tile} K_\mathrm{tile} + N_\mathrm{tile} K_\mathrm{tile})$$ for A and B. For fixed $$N_\mathrm{tile}$$ and $$K_\mathrm{tile}$$, doubling $$M_\mathrm{tile}$$ doubles the FLOPs but only adds $$2M_\mathrm{tile} K_\mathrm{tile}$$ bytes of A data — the B tile of shape $$N_\mathrm{tile} \times K_\mathrm{tile}$$ is *shared* across the pair, so each CTA loads only half the B data it would need for two independent 1CTA tiles. This is the key benefit: the B tile is multicasted via TMA across the CTA pair, halving B-side SMEM traffic per output element.

Figure: independent 1CTA MMA (left) vs. 2CTA MMA, referred to as 2xSM MMA in the figure (right). Left: two separate CTAs each load a full B tile and hold a full accumulator in TMEM. Right: in 2CTA MMA, B tile is halved and shared. Each CTA holds the full accumulator on TMEM but loads only half the B data. [4]

@@ -489,7 +494,7 @@ The hardware features described in Section 3 provide the infrastructure for high ### Gather Fusion -Multiple varlen-M GEMMs in SonicMoE read tokens from arbitrary positions in the input tensor where the routing decision determines which rows of $X$ (or $dO$) belong to each expert. SonicMoE fuses the gather directly into the GMEM-to-SMEM load. On Blackwell GPUs, SonicMoE will dispatch to gather with either `cp.async` or TMA gather4 (`cp.async.bulk.tensor.2d.shared::cta.global.tile::gather4` gathers 4 rows each time), whichever is faster at autotuning stage. +Multiple varlen-M GEMMs in SonicMoE read tokens from arbitrary positions in the input tensor where the routing decision determines which rows of $$X$$ (or $$dO$$) belong to each expert. SonicMoE fuses the gather directly into the GMEM-to-SMEM load. On Blackwell GPUs, SonicMoE will dispatch to gather with either `cp.async` or TMA gather4 (`cp.async.bulk.tensor.2d.shared::cta.global.tile::gather4` gathers 4 rows each time), whichever is faster at autotuning stage. - **`cp.async` gather fusion with 2CTA MMA.** When 2CTA MMA is combined with cp.async gather fusion, a synchronization challenge arises: cp.async can only signal completion within its own CTA, **but the leader CTA's MMA needs both CTAs' data ready.** We resolve this with a dedicated relay warp in CTA 1 (non-leader) that forwards the completion signal to CTA 0 (leader) via a cluster-scope barrier. @@ -512,7 +517,6 @@ A common alternative to gather fusion is to run a separate gather kernel that pr

Figure: Gather fusion (left) reads from a compact source tensor. Contiguous load (right) reads from a K times larger tensor where each token is duplicated across K distinct addresses, expanding the working set beyond L2 capacity as granularity increases.

-> [!NOTE] > Although gather fusion has the same *algorithmic IO costs* as contiguous load from pre-gathered inputs, **gather fusion achieves lower hardware HBM IO costs via better L2 cache hit rate.** @@ -521,18 +525,18 @@ We validate this with NCU profiling and present detailed results in the appendix ### SwiGLU and dSwiGLU Fusion -SonicMoE applies the activation function in-register before any data leaves the epilogue. The GEMM accumulator holds MMA result sub-tiles in registers. SwiGLU is applied element-wise in an interleaved format to produce activation sub-tiles. Both MMA results ($H$) and SwiGLU activations ($A$) will be written to the HBM via the async TMA store mechanism which does not add latency to the critical path. +SonicMoE applies the activation function in-register before any data leaves the epilogue. The GEMM accumulator holds MMA result sub-tiles in registers. SwiGLU is applied element-wise in an interleaved format to produce activation sub-tiles. Both MMA results ($$H$$) and SwiGLU activations ($$A$$) will be written to the HBM via the async TMA store mechanism which does not add latency to the critical path. -### Overlapping IO with MMA Compute: $dH$ kernel -SonicMoE overlaps IO with MMA whenever possible. Here we focus on the $dH$ kernel which has the heaviest epilogue in SonicMoE. To address this, we overlap the role of epilogue warps with the role of MMA warp by splitting the TMEM resources and employing dedicated TMA pipeline. +### Overlapping IO with MMA Compute: $$dH$$ kernel +SonicMoE overlaps IO with MMA whenever possible. Here we focus on the $$dH$$ kernel which has the heaviest epilogue in SonicMoE. To address this, we overlap the role of epilogue warps with the role of MMA warp by splitting the TMEM resources and employing dedicated TMA pipeline.

Figure: illustration of epilogue ops overlapped with GEMM MMA in SonicMoE's dH kernel.

-In the following figure, we examine the hardware unit utilization of SonicMoE's $dH$ kernel with heavy epilogue (left column) or GEMM with normal epilogue store (right column) on Qwen3-235B-A22B-Thinking-2507 ($(T,d,n,E,K)=(32768,4096,1536,128,8)$). **The drop in MMA throughput is *subproportional* to the increase in epilogue IO costs:** -- The $dH$ kernel epilogue increases HBM traffic by 24% (6.33 to 7.86 GB). +In the following figure, we examine the hardware unit utilization of SonicMoE's $$dH$$ kernel with heavy epilogue (left column) or GEMM with normal epilogue store (right column) on Qwen3-235B-A22B-Thinking-2507 ($$(T,d,n,E,K)=(32768,4096,1536,128,8)$$). **The drop in MMA throughput is *subproportional* to the increase in epilogue IO costs:** +- The $$dH$$ kernel epilogue increases HBM traffic by 24% (6.33 to 7.86 GB). - However, both the Tensor Core and Tensor Memory utilization only drop from 98% to 88% with the corresponding TFLOPS drop from 1213 to 1078 (11% decrease). @@ -540,7 +544,6 @@ In the following figure, we examine the hardware unit utilization of SonicMoE's

   

Figure: Nsight Compute Profiling of SonicMoE's dH kernel Grouped GEMM with 4 epilogue ops (left column) vs. Grouped GEMM alone (right column) of Qwen3-235B-A22B-Thinking-2507 (microbatch size=32k) on B300 GPUs. The top row is the achieved throughput of Tensor Pipe (MMA) and DRAM at kernel runtime, and the bottom row shows the transferred bytes on hardware units.

-> [!NOTE] > **Overlapping IO with computation effectively absorbs the additional memory traffic, so the increase in IO cost does not translate proportionally into increased runtime.** @@ -577,9 +580,9 @@ The figure below shows forward and backward TFLOPS across six real open-source M ### Profiling Time Breakdown -The runtime breakdown below makes the speedup concrete. The "gather $X$" segment in the forward pass and "gather $dO$ and $X$" segment in the backward pass are absorbed into the GEMM bars for SonicMoE, and this constitutes one major source of speedup over the DeepGEMM-built baseline, which also has optimized Grouped GEMM but requires a separate gather kernel. +The runtime breakdown below makes the speedup concrete. The "gather $$X$$" segment in the forward pass and "gather $$dO$$ and $$X$$" segment in the backward pass are absorbed into the GEMM bars for SonicMoE, and this constitutes one major source of speedup over the DeepGEMM-built baseline, which also has optimized Grouped GEMM but requires a separate gather kernel. -We note that **although Triton official example has gather fusion and *does not* store $H$ (as it is inference-oriented with no need of caching activation), SonicMoE is still faster for all three kernels during forward pass**. This is because SonicMoE employs a faster Grouped GEMM implementation with the CLC tile scheduler and 2CTA MMA, and the expert aggregation kernel is heavily optimized. Please refer to the appendix for more details. +We note that **although Triton official example has gather fusion and *does not* store $$H$$ (as it is inference-oriented with no need of caching activation), SonicMoE is still faster for all three kernels during forward pass**. This is because SonicMoE employs a faster Grouped GEMM implementation with the CLC tile scheduler and 2CTA MMA, and the expert aggregation kernel is heavily optimized. Please refer to the appendix for more details.

Figure: Runtime breakdown of SonicMoE vs baselines on B300 for a 7B OLMoE-sized MoE (T=32768, d=2048, n=1024, E=64, K=8). Detailed descriptions of other baselines can be found in the caption of Figure 5 of our arXiv paper. On this config, SonicMoE's major speedup comes from the gather fusion, and the faster GEMM delivers another 10% speedup. We abbreviate TFLOPS as "TF/s" in the figure.

@@ -590,7 +593,7 @@ We note that **although Triton official example has gather fusion and *does not* SonicMoE started from a simple observation: the field is building MoEs that are more fine-grained and sparser with every generation, and existing kernels were not designed for that regime. Roughly 2 years from Mixtral to Kimi K2.5 represent a 9× increase in granularity and a 12× drop in activation ratio, and every step of that journey makes the arithmetic intensity worse and the activation memory larger. **We need to re-visit our infrastructure design blueprint to embrace this MoE model trend, and SonicMoE is one of our answers.** -- **Activation memory-efficient and IO-aware algorithm design.** By redesigning the backward pass to avoid caching any $O(TKd)$-sized tensor, SonicMoE's per-layer activation memory is independent of expert granularity — matching a dense model with the same activated parameter count, without any GEMM recomputation. The same algorithmic reordering eliminates multiple large HBM round-trips, and the remaining IO costs are largely hidden behind MMA computation through hardware asynchrony on both Hopper and Blackwell GPUs. +- **Activation memory-efficient and IO-aware algorithm design.** By redesigning the backward pass to avoid caching any $$O(TKd)$$-sized tensor, SonicMoE's per-layer activation memory is independent of expert granularity — matching a dense model with the same activated parameter count, without any GEMM recomputation. The same algorithmic reordering eliminates multiple large HBM round-trips, and the remaining IO costs are largely hidden behind MMA computation through hardware asynchrony on both Hopper and Blackwell GPUs. - **Extensible software abstraction with hardware-aware optimization.** All of SonicMoE's kernels are instances of one shared structure built on QuACK. This abstraction confines architecture-specific behavior to localized overrides while leaving the epilogue fusion logic and the GEMM interface untouched. This enables fast iteration for prototyping new model architectures and benchmarking new hardware features. @@ -626,8 +629,15 @@ If you find SonicMoE helpful in your research or development, please consider ci [7] K.V. Nagesh. "NVIDIA Blackwell Architecture: A Deep Dive into the Next Generation of AI Computing." https://medium.com/@kvnagesh/nvidia-blackwell-architecture-a-deep-dive-into-the-next-generation-of-ai-computing-79c2b1ce3c1b -

-

+
+ +
+ +
+ +
+ +
## Appendix @@ -652,12 +662,12 @@ We compare the gather fusion against running a separate gather kernel to pre-arr -This is because gather fusion's source tensor ($X$ or $dO$) often has size $T \times d$, which is $K\times$ smaller than the pre-gathered tensor of size $T\times K \times d$. As expert granularity increases, $K$ grows proportionally, and the pre-gathered tensor can exceed the GPU's L2 cache capacity (192 MB on B300). When this happens, the data request will miss L2 and be served from HBM. Gather fusion avoids this: it reads from the compact original tensor, which is more likely to stay resident in L2 cache. +This is because gather fusion's source tensor ($$X$$ or $$dO$$) often has size $$T \times d$$, which is $$K\times$$ smaller than the pre-gathered tensor of size $$T\times K \times d$$. As expert granularity increases, $$K$$ grows proportionally, and the pre-gathered tensor can exceed the GPU's L2 cache capacity (192 MB on B300). When this happens, the data request will miss L2 and be served from HBM. Gather fusion avoids this: it reads from the compact original tensor, which is more likely to stay resident in L2 cache. -This advantage compounds with expert granularity. Gathered $X$ and gathered $dO$, which are inputs to four of SonicMoE's six Grouped GEMM kernels, are both $O(TKd)$-sized and grow linearly with $K$. The figures below confirm the trend across three model families: as granularity increases (from left to right on each column), the contiguous path's HBM load traffic grows faster and its L2 hit rate drops further relative to gather fusion. +This advantage compounds with expert granularity. Gathered $$X$$ and gathered $$dO$$, which are inputs to four of SonicMoE's six Grouped GEMM kernels, are both $$O(TKd)$$-sized and grow linearly with $$K$$. The figures below confirm the trend across three model families: as granularity increases (from left to right on each column), the contiguous path's HBM load traffic grows faster and its L2 hit rate drops further relative to gather fusion.

Figure: HBM load bytes (top row) and device L2 cache hit rate (bottom row) for gather fusion vs. contiguous load across varying expert granularity on B300 GPUs. Annotations on the top row show the absolute and relative HBM load increase of the contiguous path over gather fusion. Annotations on the bottom row show the L2 hit rate advantage of gather fusion.

@@ -680,7 +690,6 @@ In SonicMoE's expert aggregation kernel, each token will gather the Grouped GEMM On Hopper GPUs, SonicMoE makes an unconventional design choice that we *do not* fuse scatter with GEMM. Instead, we perform this task alongside the aggregation. **We previously ablated on Hopper GPUs and identified that the synchronous `st.global` PTX instruction required for scatter fusion on Hopper would degrade TFLOPS by 20% for fine-grained MoE configs.** -> [!TIP] > An IO-aware design emerges only when algorithmic intent and hardware execution semantics are reasoned about together. ##### New Asynchronous Scatter Store Instructions on Blackwell @@ -700,4 +709,4 @@ In the first row, In the second row, we already know that `gth-and-sum` only has 2% less bandwidth than `sum`. -Although this 3% gap is much smaller than the prior gap on Hopper GPUs (20%), it still validates SonicMoE's design on Blackwell GPUs. +Although this 3% gap is much smaller than the prior gap on Hopper GPUs (20%), it still validates SonicMoE's design on Blackwell GPUs. \ No newline at end of file From 23b144862b4c012ba1c78f945b1174e903213fd5 Mon Sep 17 00:00:00 2001 From: GarlGuo Date: Wed, 22 Apr 2026 11:40:41 -0400 Subject: [PATCH 3/3] sonicmoe thumbnail --- _posts/2026-04-26-sonicmoe-blackwell.md | 1 + .../2026-04-22-sonicmoe/blogpost_thumbnail.png | Bin 0 -> 123764 bytes 2 files changed, 1 insertion(+) create mode 100644 assets/img/2026-04-22-sonicmoe/blogpost_thumbnail.png diff --git a/_posts/2026-04-26-sonicmoe-blackwell.md b/_posts/2026-04-26-sonicmoe-blackwell.md index 4edba77..acee8a8 100644 --- a/_posts/2026-04-26-sonicmoe-blackwell.md +++ b/_posts/2026-04-26-sonicmoe-blackwell.md @@ -1,6 +1,7 @@ --- layout: distill title: "SonicMoE: A Hardware-Efficient and Software-Extensible Blueprint for Fine-Grained MoEs" +thumbnail: assets/img/2026-04-22-sonicmoe/blogpost_thumbnail.png giscus_comments: false date: 2026-04-22 featured: true diff --git a/assets/img/2026-04-22-sonicmoe/blogpost_thumbnail.png b/assets/img/2026-04-22-sonicmoe/blogpost_thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..75b47ff609aa43dbf3ffceff67d4e31fd4efaf5a GIT binary patch literal 123764 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¨xo{HrUx7Z5b;-t+3>RI->ePUAPE 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 r>}UV$