Summary
I have implemented Flash SwiGLU MLP, a Triton-based SwiGLU MLP that co-optimizes the complete forward and backward dataflow rather than only the element-wise SiLU-and-gating stage.
The current LigerSwiGLUMLP keeps gate_proj, up_proj, and down_proj as nn.Linear modules and applies LigerSiLUMulFunction to the outputs of the first two projections. This implementation explores a broader fusion boundary:
- fuse the gate and up projections with SiLU and element-wise gating;
- store
A, G, and U in one contiguous buffer;
- avoid materializing the full
dA tensor during backward;
- overwrite expired
G/U storage with dG/dU;
- aggregate the
dWg and dWu computations into one GEMM;
- compute
dI in one fused kernel with a single accumulator;
- use a dedicated no-grad path that does not save
G/U.
Across the complete benchmark set, most measured end-to-end training speedups are in the 5%–15% range compared with LigerSwiGLUMLP. The no-grad inference path also uses substantially less intermediate memory, although training-memory behavior is shape-dependent.
Question for maintainers
Would an implementation with this fusion scope be appropriate for Liger Kernel?
I would mainly appreciate feedback on whether this broader fusion scope is interesting and fits the project's goals.
Validation environment
- GPU: RTX 5060 Ti 16 GB (SM120)
- Driver: 580.159.03
- CUDA: 13.1
- PyTorch: 2.13.0
- Triton: 3.7.1
- Liger Kernel: 0.8.1
- Dtypes: BF16 and FP16; the tables below show BF16
- Training shapes:
(D, H) ∈ {(1024, 2816), (2048, 5632), (4096, 11008)}
B = 8
S ∈ {256, 512, 1024, 2048, 4096, 8192}
- Inference shapes:
B = 1
- effective request count
S ∈ {1, 2, 4, 8, 16, 32, 64, 128, 256}
The main GEMM kernels use Triton's host-side TensorDescriptor API. I have only benchmarked the implementation on SM120, so I am not yet making portability or performance claims for other GPU architectures.
Training results: forward + backward, BF16, B=8
| D → H |
S |
Flash SwiGLU |
Liger |
torch.compile |
Speedup vs. Liger |
| 1024 → 2816 |
2048 |
17.25 ms (49.3 TFLOPS) |
19.59 ms |
19.68 ms |
1.14× |
| 1024 → 2816 |
8192 |
70.00 ms (48.6 TFLOPS) |
78.93 ms |
79.16 ms |
1.13× |
| 2048 → 5632 |
4096 |
137.96 ms (49.3 TFLOPS) |
149.80 ms |
149.99 ms |
1.09× |
| 4096 → 11008 |
8192 |
1049.66 ms (50.7 TFLOPS) |
1136.68 ms |
1136.79 ms |
1.08× |
These are representative rows; the linked report contains the complete plots and methodology. Each benchmark uses 10 warm-up iterations and reports the mean of 50 measured iterations.
Inference results
For the smallest configuration (D=1024, H=2816), Flash SwiGLU is slightly slower than Liger and torch.compile. At larger dimensions, the three implementations converge, with Flash SwiGLU marginally ahead in several cases.
A likely contributor at small shapes is the fixed CPU-side overhead of host-side TensorDescriptor construction. CUDA Graphs may reduce that overhead, but I have not validated a CUDA-Graph path, so I do not want to claim a decoding advantage at this stage.
Intermediate-memory results
Inference, BF16, B=1
| D → H |
S |
Flash SwiGLU |
Liger |
torch.compile |
| 1024 → 2816 |
256 |
1.9 MB |
4.9 MB |
3.5 MB |
| 4096 → 11008 |
256 |
7.4 MB |
16.1 MB |
10.8 MB |
Across the tested inference configurations, Flash SwiGLU uses approximately 2.2× less intermediate memory than Liger.
Training, forward + backward, BF16, B=8
| D → H |
S |
Flash SwiGLU |
Liger |
Difference |
| 4096 → 11008 |
256 |
178.0 MB |
107.0 MB |
+66% for Flash |
| 4096 → 11008 |
8192 |
5664.0 MB |
6356.0 MB |
−11% for Flash |
Training memory is shape-dependent. At large H and small S, fixed intermediate/workspace overhead makes this implementation less memory-efficient than Liger. It becomes favorable as S increases. I am including both cases to avoid presenting only the favorable region.
Correctness
I compared against a torch.compile-based reference implementation using torch.allclose:
- FP16:
atol=1e-3, rtol=1e-4
- BF16:
atol=4e-3, rtol=1e-4
The tests cover the forward output and all four gradients (dI, dWg, dWu, and dWd) at both small and large shapes. All tested comparisons pass.
Distributed-training status
Distributed execution has not been tested yet.
Structurally, the implementation uses a standard torch.autograd.Function and introduces no collectives inside the fused kernels. The arithmetic should remain local to each tensor-parallel shard, with the surrounding framework retaining its existing communication points. However, I do not want to claim DDP, ZeRO, or tensor-parallel support until multi-GPU correctness and performance tests have been completed.
Known limitations and validation gaps
retain_graph=True and higher-order differentiation through create_graph=True are not supported because backward overwrites saved G/U storage with dG/dU.
- The in-place
dGU kernel cannot safely run an independent online autotuning search without restoring its overwritten inputs; it currently reuses the forward kernel's selected configuration.
- Performance has been benchmarked only on one SM120 GPU.
- CUDA-Graph decoding and multi-GPU execution have not been tested.
Thanks for maintaining Liger Kernel. It has been a valuable reference while developing and validating this project, and I would appreciate feedback on whether this design direction fits the repository and is worth exploring further.
Summary
I have implemented Flash SwiGLU MLP, a Triton-based SwiGLU MLP that co-optimizes the complete forward and backward dataflow rather than only the element-wise SiLU-and-gating stage.
The current
LigerSwiGLUMLPkeepsgate_proj,up_proj, anddown_projasnn.Linearmodules and appliesLigerSiLUMulFunctionto the outputs of the first two projections. This implementation explores a broader fusion boundary:A,G, andUin one contiguous buffer;dAtensor during backward;G/Ustorage withdG/dU;dWganddWucomputations into one GEMM;dIin one fused kernel with a single accumulator;G/U.Across the complete benchmark set, most measured end-to-end training speedups are in the 5%–15% range compared with
LigerSwiGLUMLP. The no-grad inference path also uses substantially less intermediate memory, although training-memory behavior is shape-dependent.Question for maintainers
Would an implementation with this fusion scope be appropriate for Liger Kernel?
I would mainly appreciate feedback on whether this broader fusion scope is interesting and fits the project's goals.
Validation environment
(D, H) ∈ {(1024, 2816), (2048, 5632), (4096, 11008)}B = 8S ∈ {256, 512, 1024, 2048, 4096, 8192}B = 1S ∈ {1, 2, 4, 8, 16, 32, 64, 128, 256}The main GEMM kernels use Triton's host-side
TensorDescriptorAPI. I have only benchmarked the implementation on SM120, so I am not yet making portability or performance claims for other GPU architectures.Training results: forward + backward, BF16, B=8
torch.compileThese are representative rows; the linked report contains the complete plots and methodology. Each benchmark uses 10 warm-up iterations and reports the mean of 50 measured iterations.
Inference results
For the smallest configuration (
D=1024, H=2816), Flash SwiGLU is slightly slower than Liger andtorch.compile. At larger dimensions, the three implementations converge, with Flash SwiGLU marginally ahead in several cases.A likely contributor at small shapes is the fixed CPU-side overhead of host-side
TensorDescriptorconstruction. CUDA Graphs may reduce that overhead, but I have not validated a CUDA-Graph path, so I do not want to claim a decoding advantage at this stage.Intermediate-memory results
Inference, BF16, B=1
torch.compileAcross the tested inference configurations, Flash SwiGLU uses approximately 2.2× less intermediate memory than Liger.
Training, forward + backward, BF16, B=8
Training memory is shape-dependent. At large
Hand smallS, fixed intermediate/workspace overhead makes this implementation less memory-efficient than Liger. It becomes favorable asSincreases. I am including both cases to avoid presenting only the favorable region.Correctness
I compared against a
torch.compile-based reference implementation usingtorch.allclose:atol=1e-3,rtol=1e-4atol=4e-3,rtol=1e-4The tests cover the forward output and all four gradients (
dI,dWg,dWu, anddWd) at both small and large shapes. All tested comparisons pass.Distributed-training status
Distributed execution has not been tested yet.
Structurally, the implementation uses a standard
torch.autograd.Functionand introduces no collectives inside the fused kernels. The arithmetic should remain local to each tensor-parallel shard, with the surrounding framework retaining its existing communication points. However, I do not want to claim DDP, ZeRO, or tensor-parallel support until multi-GPU correctness and performance tests have been completed.Known limitations and validation gaps
retain_graph=Trueand higher-order differentiation throughcreate_graph=Trueare not supported because backward overwrites savedG/Ustorage withdG/dU.dGUkernel cannot safely run an independent online autotuning search without restoring its overwritten inputs; it currently reuses the forward kernel's selected configuration.Thanks for maintaining Liger Kernel. It has been a valuable reference while developing and validating this project, and I would appreciate feedback on whether this design direction fits the repository and is worth exploring further.