Files
Buffett LiuandVaibhav Jindal 2120862ba5 [Megatron] Add SwiGLU integration (#1326)
## Summary
Adds Liger's Triton SwiGLU to Megatron-Core. Megatron's dense MLP routes
its activation through `bias_swiglu_impl` in
`megatron.core.fusions.fused_bias_swiglu`, a TorchScript `@jit_fuser`
implementation. This PR replaces it with a **new fused gate-up Triton
kernel** written for Megatron's memory layout.

At the Megatron training shape (`S=2048, B=4, bf16`) on **H100**,
against `megatron-core==0.16.1`:

|  | Forward | Full (fwd+bwd) | Memory | Memory (`in_place=True`) |
|---|---|---|---|---|
| `ffn_local=4096` | **+57%** | **+146%** | +20% | **−20%** |
| `ffn_local=32768` | **+63%** | **+166%** | +20% | **−20%** |

<img width="1680" height="1200" alt="megatron_swiglu_panel_H100"
src="https://github.com/user-attachments/assets/0c0a2710-42df-4354-a0ce-bd7a25837058"
/>

### Background

Liger's existing `LigerSiLUMulFunction` takes gate and up as **two
separate tensors** (`a`, `b`), because that is how HuggingFace models
arrive: `gate_proj(x)` and `up_proj(x)` are distinct GEMMs.

Megatron is the opposite. `linear_fc1` is a single fused
`ColumnParallelLinear` producing one `[s, b, 2 * ffn_local]` tensor with
gate in columns `[0, n)` and up in `[n, 2n)`. Bridging to the two-tensor
kernel therefore requires a `torch.chunk`, and `chunk` on the last
dimension returns **non-contiguous views**, so `@ensure_contiguous`
materializes 2 full copies on every forward. We measured that bridge
before writing anything new.


| H100, forward, `ffn_local=32768` | Time | vs Megatron |
|---|---|---|
| Megatron `bias_swiglu_impl` | 0.853 ms | — |
| Liger via `chunk` bridge | 1.828 ms | **−53%** (2.1× slower) |
| **Liger fused gate-up (this PR)** | **0.536 ms** | **+59%** |

The chunk bridge is **3.4× slower than the fused kernel** and also
inflated memory by +40%. The new `LigerFusedGateUpSiLUMulFunction` reads
both halves via a column offset into the single buffer, with no copies,
no `cat`. Input row stride is `2n`, output row stride is `n`.


## How to use
### Mode 1: One-line monkey patch

```python
from liger_kernel.megatron import apply_liger_kernel_to_megatron

apply_liger_kernel_to_megatron(rms_norm=True, cross_entropy=True, swiglu=True) # swiglu defaults to False
```
Patches `bias_swiglu_impl` across Megatron's core modules
(`fused_bias_swiglu`, `mlp`, and `moe.shared_experts`).

### Mode 2: Use the class directly

```python
from liger_kernel.megatron import LigerMegatronSwiGLU

swiglu = LigerMegatronSwiGLU()
out = swiglu(intermediate, bias, fp8_input_store, cpu_offload_input)
```

The signature mirrors `bias_swiglu_impl` positionally. To use Mode 2,
subclass MLP (`see examples/megatron/run_mode2_hand_spec.py`).

### Optional: `in_place=True` for a 20% memory reduction

```python
LigerMegatronSwiGLU(in_place=True) # Opt-in, off by default
```
Overwrites the linear_fc1 output buffer during backward passes to reduce
memory by 20% vs Megatron with zero speed penalty.

## Scope
- Supported: Dense MLP (`bias_swiglu_impl`) and shared experts at any TP
size with `bias=None`.
- Transparent Fallbacks to Megatron: Auto-delegates to Megatron (and
logs once) when:
  - `bias is not None` (Liger lacks bias support)
  - `fp8_input_store=True` (requires FP8 input retention)
  - `cpu_offload_input=True` (requires offloadable inputs)
- Pass `fallback_impl=None` to raise an error instead of falling back.
- Out of Scope: MoE routed experts (`weighted_bias_swiglu_impl`).

## What's added

| File | Purpose |
|---|---|
| `src/liger_kernel/ops/swiglu.py` | New
`LigerFusedGateUpSiLUMulFunction` + fwd/bwd Triton kernels for the
`[tokens, 2n]` layout. Existing two-tensor path untouched. |
| `src/liger_kernel/megatron/swiglu.py` | `LigerMegatronSwiGLU`
nn.Module, fallback policy, shape validation. |
| `src/liger_kernel/megatron/monkey_patch.py` | `swiglu=` flag,
`_patch_bias_swiglu_impl()`, `_rebind_consumer_symbol()`. |
| `src/liger_kernel/megatron/__init__.py` | Re-exports
`LigerMegatronSwiGLU`. |
| `test/megatron/test_swiglu.py` | 36 tests — 28 dependency-free + 8
against real `megatron-core`. |
| `test/megatron/test_monkey_patch.py` | +14 SwiGLU patch-mechanism
tests; stub-harness snapshot/restore rewrite. |
| `benchmark/scripts/benchmark_megatron_swiglu.py` | 4 providers:
`liger`, `liger_in_place`, `megatron`, `torch`. |
| `examples/megatron/*` | Mode 1 + Mode 2 extended to cover SwiGLU. |

## Megatron feature compatibility

**Verified:** Tensor Parallelism (TP), fp32 + bf16, 2D and 3D inputs,
distributed checkpointing (save/load round-trip in both example
scripts), real `MLP` forward+backward parity against Megatron's
TorchScript, and both consumer module paths.

**Should work, not yet E2E-verified:** multi-rank patch application
(every rank must call `apply_...` before model build — single-process
patching is verified, the multi-rank ordering is not), CP,
`use_distributed_optimizer=True`.

**Out of scope:** activation recompute and CUDA-graph capture *with
`in_place=True`* — precisely why that flag is opt-in. The default path
is recompute-safe.

## Known issues / follow-ups
1. `in_place=True` and `retain_graph=True` incompatibility: In-place
Triton kernel writes don't bump PyTorch's `_version` counter, so running
backward a second time silently yields garbage gradients without
triggering autograd guards. We added a `ctx.already_backward` flag to
explicitly raise an error when reusing the graph with `in_place=True`.
2. MoE routed experts: `weighted_bias_swiglu_impl` requires a
`per_token_scale`-aware kernel (left as a follow-up).
3. RMSNorm patch fix: Fixed a TypeError in
`_patch_local_spec_provider_layer_norm` caused by forwarding an
unsupported has_residual kwarg on `megatron-core 0.16.1`. Signature
matching is now version-agnostic via `inspect.signature`.
4. Example script fixes: Fixed three pre-existing `megatron-core 0.16.1`
compatibility issues in the example scripts (AttributeError on
`as_mlp_submodule`, Makefile build errors in pip wheels, and invalid
tokenizer metadata).

## Testing Done
- Hardware Type: NVIDIA A10G (correctness, 1× and 2×), H100 80GB HBM3
(benchmarks), B200 (benchmarks)
- [x] `pytest test/megatron/` **with** `megatron-core` installed — **175
passed, 49 skipped**
- [x] `pytest test/megatron/` **without** `megatron-core` (default CI
shape) — **168 passed, 56 skipped**
- [x] `pytest test/transformers/test_swiglu.py` — **49 passed, 4
skipped, 4 xfailed** (existing two-tensor SwiGLU not regressed by the
shared `ops/swiglu.py` change)
- [x] run `make checkstyle` to ensure code style — clean; `prek run -a`
also clean
- [x] H100 + B200 benchmark sweeps; H100 rows committed to
`all_benchmark_data.csv`
- [ ] run `make test-convergence` to ensure convergence

## Benchmark reproduction
```bash
python benchmark/scripts/benchmark_megatron_swiglu.py --overwrite

python benchmark/benchmarks_visualizer.py \
    --kernel-name megatron_swiglu --metric-name speed --overwrite
```

---------

Co-authored-by: Vaibhav Jindal <[email protected]>
2026-08-10 22:36:34 +00:00
..