63 Commits
Author SHA1 Message Date
Vaibhav Jindal 23ba1065a7 Update README.md (#1522)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [ ] run `make test` to ensure correctness
- [ ] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence
2026-10-11 17:26:02 +00:00
Vaibhav JindalandCopilot d5f281789a fix(test): stop passing removed device kwarg to Llama4 rotary embed… (#1516)
## Summary

`test/transformers/test_llama4_rope.py` constructs the rotary embedding
directly as
`Llama4TextRotaryEmbedding(config=config, device=device)`. Newer
HuggingFace `transformers`
deprecated the `device` kwarg on config-based
`*RotaryEmbedding.__init__`
(`@deprecate_kwarg("device", version="5.18")`) and **removes it at
5.18**. Because CI installs
the latest `transformers` (`setup.py` pins `transformers>=4.52.0`, no
upper bound), every test
in the file fails at setup with:

```
TypeError: Llama4TextRotaryEmbedding.__init__() got an unexpected keyword argument 'device'
```

This fixes all of the currently-failing `test_llama4_rope.py` cases by
dropping the removed kwarg.

## Why dropping `device=` is correct (not just silencing)

`Llama4TextRotaryEmbedding.forward` relocates the frequency buffer to
the input tensor's device:

```python
inv_freq_expanded = self.inv_freq[...].to(x.device)
```

and the test tensors are already created on the target device, so the
rotary module does not
need to be constructed on a specific device. Verified on
`transformers==5.15.0` that `cos`/`sin`
are **bitwise identical** (`torch.equal`) to the previous
`device=`-based construction.

## Relationship to #1514

#1514 fixed the same `device`-kwarg drift, but only for call sites
routed through
`transformers_version_dispatch`. `test_llama4_rope.py` constructs the
module **directly**, so it
was not covered. This PR is the direct-construction counterpart.

## Testing

- `pytest test/transformers/test_llama4_rope.py` → **19 passed**
(transformers 5.15.0, H100).
- Simulated transformers ≥5.18 (strict signature): the old `device=`
call raises the exact CI
  `TypeError`; the new call succeeds.
- `ruff check` / `ruff format --check` clean.

## Compatibility

| transformers | Before this PR | After this PR |
|---|---|---|
| < 4.48.0 | n/a (test path unchanged) | n/a |
| 4.48.0 – <5.18 | `device=` accepted (deprecation warning) | no
`device=`, identical result, no warning |
| ≥ 5.18 | **`TypeError` at construction** | works |

Co-authored-by: Copilot <[email protected]>
2026-10-07 23:20:35 +00:00
Vaibhav JindalandCopilot 98f6371a9c ci: run only Checkstyle in the merge queue; make AMD/Intel manual-only (#1515)
## Summary

The AMD and Intel GPU runners are currently unavailable, so those CI
jobs fail whenever they run — including inside the **merge queue**,
which blocks all merges for some time. This PR makes the merge queue run
**only Checkstyle** for now.

## Change

Reduce the **AMD GPU** and **Intel GPU** workflow triggers to
`workflow_dispatch` only, mirroring the **NVIDIA GPU** workflow's `on:`
(manual trigger) **minus the schedule** — we don't maintain a schedule
for AMD/Intel.

This removes their `push`, `pull_request`, and `merge_group` triggers,
so:

- **Only Checkstyle runs in the merge queue** (it keeps its
`merge_group` trigger).
- AMD/Intel no longer run automatically on PRs or pushes.
- AMD/Intel can still be launched on demand via the Actions **"Run
workflow"** button once the runners are healthy again.

`checkstyle.yml` is unchanged.

## Notes

- Observed (not changed here): `merge-queue-failure.yml` listens for the
**NVIDIA GPU** workflow with `event == 'merge_group'`, but NVIDIA GPU
has no `merge_group` trigger, so that handler is currently a no-op. Can
be addressed separately.

Co-authored-by: Copilot <[email protected]>
2026-10-07 21:54:52 +00:00
Vaibhav JindalandCopilot 74777f44fb fix(test): drop unsupported device kwarg from config-based RoPE con… (#1514)
## Summary

Fixes the cuTile / cuTeDSL / correctness RoPE CI jobs that started
failing across both H100 and B200 (e.g. run 37538343198) with:

```
TypeError: LlamaRotaryEmbedding.__init__() got an unexpected keyword argument 'device'
```

## Root cause

Newer `transformers` (observed on 5.19.0 in CI) removed the `device`
keyword from the **config-based** `*RotaryEmbedding.__init__`
(`__init__(self, config)`). Our `transformers_version_dispatch(...)`
helper still forwarded `device` in `after_kwargs`, so every RoPE test
that builds a rotary embedding raised the `TypeError` **at setup**,
before any kernel ran — which is why all cuTile + cuTeDSL jobs failed on
both architectures at once. The on-pod/mirror `transformers` (<= 5.15.0)
still accepted `device`, so it only surfaced in CI where public PyPI
resolves a newer version.

## Fix

The config-based constructor derives device from the input tensor in
`forward` (`inv_freq` is moved to `x.device`), so passing `device` at
construction is redundant. This PR drops `"device": device` from every
`after_kwargs`, while keeping it in `before_kwargs` for the pre-4.48.0
positional-`dim` path.

Changed call sites:
- `test/transformers/test_rope.py` (3)
- `test/cutile/test_rope.py` (3)
- `test/cutedsl/test_rope.py` (3)
- `benchmark/scripts/benchmark_rope.py` (1)
- `benchmark/scripts/benchmark_llama4_rope.py` (1)
- `src/liger_kernel/utils.py` — docstring example updated

## Verification

- `test/transformers/test_rope.py`: **81 passed** on transformers 5.15.0
(backward-compatible).
- Simulated the 5.19.0 strict signature (reject `device`): the **old**
pattern raises the exact CI `TypeError`; the **new** pattern constructs
successfully and `forward` returns `cos`/`sin` on the input's CUDA
device.
- `ruff check` + `ruff format --check`: clean.
- cuTile/cuTeDSL backends aren't installed on the dev pod, but all three
test files use the identical dispatch pattern, so the fix applies
uniformly.

Co-authored-by: Copilot <[email protected]>
2026-10-07 20:52:57 +00:00
Vaibhav JindalandCopilot 280444a998 fix(test): use even head_dim in rope functional-correctness weird shape (#1513)
Transfomers change:
https://github.com/huggingface/transformers/pull/48524, leads to failing
tests because of odd head_dim.

## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->
HuggingFace's newer config validation (validate_rope) requires an even
rotary dimension, so constructing LlamaConfig(head_dim=41) now raises
StrictDataclassClassValidationError. test_functional_correctness builds
that config in its fixture, so the weird-shape case [*-9-7-41-41-41]
fails during setup (before the kernel runs) on transformers versions
that enforce the validator -- this is what breaks the daily H100
correctness CI.

Odd head_dim is unsupported across the stack: the CuTeDSL backend
rejects it (test_rope_cutedsl_rejects_odd_head_dimension) and the
sibling test_correctness block in this same file already documents this
and uses an even head_dim. Switch the weird shape from head_dim=41 to
head_dim=92, matching that convention while preserving non-power-of-2
masking coverage (bsz=9, seq_len=7, 41 q/kv heads).

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [ ] run `make test` to ensure correctness
- [x] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence

Co-authored-by: Copilot <[email protected]>
2026-10-06 22:03:57 +00:00
Vaibhav JindalandCopilot de77856a01 perf(flce): reuse logits buffers and write input gradients directly (#1468)
## Summary

Reduce peak allocated memory in fused linear cross entropy by reusing
chunk-logits storage and writing input gradients directly into their
destination.

On B300 with BF16/FP16, the reference 8,192-token workload uses **33.3%
less incremental peak memory**; the 65,536-token workload uses **46.5%
less**. Latency is effectively unchanged. This is a buffer-management
optimization, not a new kernel or chunk-size policy.

## Details

- Allocate one reusable logits buffer on eligible NVIDIA CUDA paths, and
use `torch.mm(..., out=...)` to fill each chunk.
- Write dX directly into its preallocated slice instead of allocating a
GEMM result and copying it.
- Release old logits, gradient/transpose views, CE argument tuples, and
token-scaling temporaries once consumed. Release reusable workspace
before the final gradient-dtype conversion.
- Preserve the original allocating matmul and bias-promotion behavior
when direct output would change dtype or autocast semantics.

The direct-output path requires no-grad execution, matching
FP16/BF16/FP32 operands, and disabled or dtype-matching CUDA autocast.
Logits reuse additionally requires absent/same-dtype bias; direct dX
requires a matching destination dtype. Other cases retain the original
matmul behavior. The older autocast-query API remains supported.

## B300 measurements

Full forward + backward, mean reduction, no bias, default accumulation,
H=4096 and V=128256. Memory is incremental peak allocated MiB above the
resident input/parameter baseline, not reserved or total process memory.

| Dtype | Tokens | Before ms | After ms | Before peak MiB | After peak
MiB | Memory reduction |
|---|---:|---:|---:|---:|---:|---:|
| BF16 | 8,192 | 19.193 | 19.067 | 3,102.04 | 2,068.04 | 33.3% |
| BF16 | 16,384 | 38.301 | 38.420 | 5,202.08 | 3,134.08 | 39.8% |
| BF16 | 65,536 | 146.720 | 148.472 | 17,802.31 | 9,530.31 | 46.5% |
| FP16 | 8,192 | 16.120 | 16.164 | 3,102.04 | 2,068.04 | 33.3% |
| FP16 | 16,384 | 31.688 | 31.556 | 5,202.08 | 3,134.08 | 39.8% |
| FP16 | 65,536 | 124.956 | 123.641 | 17,802.31 | 9,530.31 | 46.5% |

Across 23 cases covering BF16, FP16, FP32, FP32 accumulation, AMP, and
optional features, observed latency changes ranged from **2.2% faster to
1.4% slower**. No material speedup is claimed. The measured losses and
gradients matched the baseline with zero numerical difference in these
cases.

Methodology:

- NVIDIA B300 SM103; PyTorch 2.11.0.1+cu130; Triton 3.6.0.
- Baseline: FLCE source at `f2ac965f619aa4aa090c34252f87349a968b7597`,
unchanged by the two subsequent main commits used as this PR's base.
- Baseline and candidate loaded in the same process; identical seeded
tensors, chunk sizes, precision, and upstream gradient 1.0.
- Fresh forward/backward graph per iteration; both leaf gradients
cleared. No optimizer step.
- Compilation excluded; eager CUDA-event `triton.testing.do_bench`, 100
ms warmup, 400 ms repetition budget, four alternating-order rounds;
median of round medians.
- Memory: median of three separate warmed peak-allocation probes.

The improvement removes approximately one overlapping logits slab plus a
temporary dX chunk. It does **not** make memory independent of token
count: the unchanged C=16 policy still grows the chunk size with total
tokens.

## Hopper and other GPUs

There are no new SM100/SM103-specific instructions or kernels. The
optimization uses ordinary PyTorch/cuBLAS output-buffer operations, so
the same allocation-lifetime benefit is expected on eligible Hopper and
other NVIDIA CUDA workloads.

**Only B300 has been measured.** H100/H200/Ampere latency, exact peak
allocations, and real older-PyTorch behavior still need hardware
coverage; identical performance percentages are not claimed. ROCm and
non-CUDA devices do not enter the new direct-output path. The HIP
eligibility simulation below is not a ROCm hardware run.

## Testing Done

- **322 passed:** transformer FLCE suite, ops FLCE suite, and CuTe
shape-fallback contracts.
- **170 passed:** ops FLCE suite with `LIGER_KERNEL_STRICT=1` (overlaps
the preceding coverage).
- **58 passed / 19 NVIDIA-only skips:** targeted tests with only the HIP
eligibility flag simulated on B300.
- Regression coverage includes actual aten dispatch, storage lifetime
through views, ragged/noncontiguous chunks, AMP and bias promotion, FP32
accumulation, token scaling, forward-only execution, and deferred
`reduction="none"` backward.
- Ruff lint, format checks, and `git diff --check` pass.
- Full-repository `make test` and convergence suites were not run.

Only the FLCE implementation and its existing regression-test file are
included. Local dependency pins, benchmark artifacts, and unrelated
files are excluded.

Co-authored-by: Copilot <[email protected]>
2026-09-15 23:50:07 +00:00
Vaibhav JindalandCopilot 95b01e9402 perf(flce): accumulate low-precision Triton dW with addmm (#1454)
## Summary

Optimize Liger's Triton fused linear cross entropy (FLCE)
weight-gradient accumulation for BF16/FP16 parameters on NVIDIA SM80+
GPUs.

The previous default-accumulator path computes a parameter-sized matrix,
converts it to FP32, then adds it into the BF16/FP16 running gradient
buffer for **every token chunk**:

```python
grad_weight += torch.mm(grad_logits_chunk.t(), input_chunk).float()
```

This PR instead accumulates directly into the existing dtype-matched
gradient buffer:

```python
torch.addmm(grad_weight, grad_logits_t, input_chunk, out=grad_weight)
```

This removes the separate parameter-sized product/conversion temporary
and extra addition pass. The stored gradient-buffer dtype is unchanged;
the old `.float()` temporary was not an FP32 running accumulator. The
fused accumulation can change rounding relative to the old standalone
product, so bitwise equivalence is not claimed.

The existing guarded FP32 accumulation fast path, AMP operand alignment,
and legacy dtype/device fallbacks are preserved. The CE kernel, its
launch configuration, the default token-chunk heuristic, and the
forward/backward scheduling are unchanged.

**Scope:** this is the first stack layer only: Triton accumulation plus
regressions in the existing FLCE test file. It does **not** add a public
`chunk_size` argument, change cuTile, or include the later three-way
benchmark implementation.

## Performance improvements attributable to this PR

### Unchanged production defaults

BF16 inputs/weights, `accum_dtype=None`, and the existing
`_CHUNK_MEM_CONST=16` heuristic on both sides. The number of token
chunks is identical before and after. At the Llama-3-sized reference
shape, this is **1.26x faster** with **38.9% lower incremental peak
allocated memory** without changing any user settings.

| Tokens / hidden / vocab | Chunk rows | Before ms | After ms | Speedup
| Before / after peak MiB | Memory reduction |
|---|---:|---:|---:|---:|---:|---:|
| 8,192 / 4,096 / 128,256 | 4,096 | 25.002 | 19.897 | **1.26x** |
5,074.04 / 3,102.04 | **38.9%** |
| 4,096 / 2,048 / 32,000 | 4,096 | 1.774 | 1.366 | **1.30x** | 766.02 /
407.02 | **46.9%** |

### Controlled smaller chunks: benefit per accumulation step

These are additional controlled experiments, **not the production
defaults**. For the reference shape, the existing private chunk-budget
constant was set to 4 (1,024-row chunks) or 1 (256-row chunks) on
**both** versions. No new chunk-size API or later stack-layer code was
needed.

| Tokens / hidden / vocab | Chunk rows | Before ms | After ms | Speedup
| Before / after peak MiB | Memory reduction |
|---|---:|---:|---:|---:|---:|---:|
| 8,192 / 4,096 / 128,256 | 1,024 | 43.167 | 21.836 | **1.98x** |
4,322.54 / 1,575.04 | **63.6%** |
| 8,192 / 4,096 / 128,256 | 256 | 123.443 | 31.686 | **3.90x** |
4,134.66 / 1,193.29 | **71.1%** |

The larger gain at smaller chunks is expected: the old implementation
repeatedly allocated/converted/added a full `[vocab, hidden]` matrix,
and this PR removes that work on each iteration. At the reference shape,
one such FP32 matrix alone is approximately 2,004 MiB.

### FP32-accumulator control

The existing `accum_dtype=torch.float32` fast path is intentionally
preserved. At 8,192 tokens / hidden 4,096 / vocab 128,256, with the
unchanged default 4,096-row chunk:

- Before: **20.473 ms**, **4,104.04 MiB** incremental peak.
- After: **20.471 ms**, **4,104.04 MiB** incremental peak.

This control is effectively unchanged; the improvement targets the
default low-precision gradient-buffer path.

### Measurement methodology

- **Hardware:** NVIDIA B200, SM100, driver 580.105.08.
- **Software:** PyTorch 2.11.0.1+cu130, Triton 3.6.0.
- **Before / after source:** base FLCE implementation `96233e3` versus
`6a5077f`, loaded against the same installed dependencies. Current head
`d5f3593` changes only a test comment relative to `6a5077f`; its kernel
implementation is identical.
- Identical seeded BF16 inputs, weights, targets, and token-chunk
geometry; both inputs and weights require gradients; no bias or optional
loss features in the timing cases.
- Measure **full forward + backward**, mean reduction, fixed upstream
gradient 1.0. Each iteration uses a fresh graph and clears both leaf
gradients. No optimizer step or model-training-throughput claim.
- Compilation plus five warmup iterations excluded. Each run uses 30
CUDA-event timed iterations; tables report the median of three runs,
alternating before/after order in the same process.
- Memory is **incremental peak allocated MiB above the live input/weight
baseline**, with both gradients cleared before reset and a dedicated
memory-probe iteration. It is not total process memory or reserved
memory.
- These compare the same Triton-backed implementation before/after the
portable cuBLAS accumulation improvement. They are not
CuTe-versus-Triton or DSL/compiler speedup claims.

## Testing Done

- **Hardware:** NVIDIA B200 / SM100.
- Latest relevant suites: **470 passed, 1 environment-gated skipped**
across `test/ops/test_fused_linear_cross_entropy.py`,
`test/transformers/test_fused_linear_cross_entropy.py`, and
`test/cutedsl/test_fused_linear_cross_entropy.py`.
- All **70 addmm regression cases** now live in the existing ops FLCE
test file alongside its existing 23 cases. Their chunk-budget fixture is
class-scoped and does not alter the pre-existing tests.
- Coverage includes real aten dispatch checks for direct `addmm.out` and
no parameter-shaped upcasts, preserved FP32 `dtype_out`, AMP and
FP32-bias promotion, BF16/FP16/FP32, mean/sum/nonuniform per-token
gradients, ignored chunks, frozen weights, noncontiguous tensors, and
old-Torch/SM capability fallbacks.
- Review requests addressed: restored the original inline FP32 guard,
removed the extra boolean aliases, consolidated tests, and renamed the
marker to `requires_sm80_or_higher`.
- [x] `make checkstyle` passes locally; GitHub Checkstyle is also green.
- [ ] Full-repository `make test` was not run; the targeted suites above
were run.
- [ ] `make test-convergence` was not run; no convergence claim is made.

---------

Co-authored-by: Copilot <[email protected]>
2026-09-10 18:30:11 +00:00
Vaibhav Jindal 0a5e9a55f8 Update tests.py to use B200 instead of B200! (#1437)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->
"B200!" is not a defined GPU type unlike "H100!". Need to use "B200"
instead of "B200!" at all locations.

Refer: https://modal.com/docs/guide/gpu#b200-gpus

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [ ] run `make test` to ensure correctness
- [ ] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence
2026-09-02 17:45:50 +00:00
71293bebcc fix(mlp): correct SwiGLU backward_dI on Blackwell (SM100/B200) (#1435)
## Summary

`test/transformers/test_mlp.py` (the Triton SwiGLU MLP correctness
suite) fails on B200 (SM100) for **every** dtype. Root-caused to a
single kernel — `_swiglu_kernel_backward_dI` — and fixed with a minimal
restructuring of its accumulation loop.

## Symptoms on B200

| dtype | failure |
|-------|---------|
| fp32 | `triton.runtime.errors.OutOfResources: shared memory, Required:
327744, Hardware limit: 232448` |
| bf16 / fp16 | wrong `x.grad` — `calc_diff ≈ 0.12`, relative error ≈
**45%** (forward + weight-grads pass; only `dI` is wrong) |

## Root cause

`dI = dG @ Wg + dU @ Wu` was computed by interleaving **two `tl.dot(...,
acc=acc)` calls into the same accumulator within one K-loop iteration**:

```python
for k in range(0, hidden_dim, BLOCK_K):
    acc = tl.dot(dG_block, gate_weight_block, acc=acc)
    acc = tl.dot(dU_block, up_weight_block, acc=acc)   # same acc
```

On Blackwell (tcgen05) this miscompiles. Minimal repro on B200 (TMA
descriptors, `M,K,N = 256,512,256`, bf16):

| pattern | relative error |
|---------|-----|
| single accumulating dot in a K-loop | ✅ 1.7e-3 |
| **two dots interleaved into one accumulator** | ❌ **4.6e-1** |
| two dots via `+=` into one accumulator | ❌ 4.6e-1 |
| two separate accumulators, added at end | ✅ 1.7e-3 |
| **two sequential K-loops, one accumulator** | ✅ 1.7e-3 |

The same interleaving also pipelines all four operands (`dG, Wg, dU,
Wu`) at once, so for fp32 with `num_stages≥2` the smallest config needs
≥327744 bytes of shared memory — more than SM100 exposes (232448).
Because **every** config in the autotune grid OOMs, the autotuner falls
back to the first (also-OOM) config and the launch crashes.

## Fix

Accumulate the two matmuls in **two sequential K-loops sharing one
accumulator**. This:
- is correct on SM100 (and unchanged behaviour on SM90/H100), and
- pipelines only two operands at a time, so the `BLOCK_K=64` configs fit
in smem and the autotuner selects a valid config for fp32.

The `forward` kernel (two separate accumulators) and `backward_dGU`
kernel (single dot) already use safe patterns and are untouched. The
`fixA` alternative (two separate accumulators) was rejected: it keeps
the 4-operand smem footprint (fp32 still OOMs) and hit `PTXAS Internal
Triton PTX codegen` errors on B200 for `BLOCK_M=128` configs.

## Verification

On B200: `test/transformers/test_mlp.py` → **59 passed, 1 xfailed** (was
28 failed before). fp32/bf16/fp16 across all parametrised shapes now
pass.

Co-authored-by: GitHub Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
2026-09-02 00:37:28 +00:00
1621f42d7e ci(nvi): add cuTile tests on B200 to the daily run (#1436)
## Summary

Adds a B200 (SM100) counterpart to the daily H100 cuTile CI job, so the
cuTile suite runs on **both** archs — mirroring cuTeDSL, which already
runs on H100 and B200. Arch-specific cuTile regressions (e.g.
SM100/tcgen05 codegen issues) would otherwise go uncaught by CI.

## Changes

- **`dev/modal/tests.py`** — new `liger_cutile_tests_b200` Modal
function, identical to `liger_cutile_tests` but on B200. Installs
`.[dev,cutile-tileiras]` (so `import cuda.tile` resolves and the tests
run rather than `importorskip`) and runs `make test-cutile` (which sets
`LIGER_KERNEL_IMPL=cutile`).
- **`.github/workflows/nvi-ci.yml`** — adds a `cuTile (B200)` entry to
the `daily` matrix and refreshes the surrounding comments.

## Strict `B200!` GPU spec

Both B200 jobs now use the strict `gpu="B200!"` form, matching the H100
jobs' `H100!`. The `!` tells Modal not to substitute a different GPU
family. This matters for arch-specific SM100 tests: without it, a
fallback/substituted GPU could produce a **misleading pass**. This PR
also flips the pre-existing `liger_cutedsl_tests_b200` from `"B200"` →
`"B200!"` for consistency (small, related change — easy to drop if
undesired).

`max-parallel: 3` and the workflow-level concurrency group are
unchanged, so the 5th daily matrix entry still runs at most 3 GPU jobs
at once.

## Verification

Locally on a B200: `LIGER_KERNEL_IMPL=cutile pytest test/cutile/` →
**199 passed, 12 skipped**. The new job is meaningful and currently
green on B200.

Co-authored-by: GitHub Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
2026-09-01 23:00:26 +00:00
6b7e3dd6fa fix(cutedsl): self-skip SM100-only FLCE tests on SM90 (H100 CI) (#1431)
## Summary

Fixes the **H100 (SM90)** half of the `NVIDIA GPU` daily CI job `cuTeDSL
SM90 (H100)`.

`make test-cutedsl` (`LIGER_KERNEL_IMPL=cutedsl pytest test/cutedsl/`)
runs the whole `test/cutedsl/` folder on **both** H100 (SM90) and B200
(SM100) — per `nvi-ci.yml`, each arch is meant to *self-skip the other
half*. But the native FLCE op
`liger_kernel.ops.cutedsl.ops.fused_linear_cross_entropy` is
**SM100/Blackwell-only** and raises

```
RuntimeError: Native CuTe DSL FLCE requires exact SM100 hardware, FP16/BF16 input and weight, and mean/sum reduction.
```

on non-SM100 GPUs. On the H100 job this made **214 FLCE tests fail
instead of skip**, because the `_run_or_skip` helper only caught
`NotImplementedError`.

## Root cause

The file mixes two skip mechanisms. The 6 `@sm100_required` tests skip
correctly, but the ~34 parity tests are `@cuda_required` and route
through `_run_or_skip`, which only swallowed `NotImplementedError` — not
the SM100 `RuntimeError`. A few tests also call the native kernel
directly (outside `_run_or_skip`), and
`test_flce_native_support_requires_exact_sm100` implicitly assumes
Blackwell hardware.

## Changes (test-only)

- **Broaden `_run_or_skip`** to also `pytest.skip` on the SM100-hardware
`RuntimeError` (message contains `SM100`), re-raising every other
`RuntimeError` so genuine SM100 failures still surface on B200.
- **Route the 3 direct-call tests** through `_run_or_skip`:
`test_flce_native_z_loss_preserves_input_dtype`,
`test_flce_independent_requires_grad_matches_torch`,
`test_flce_all_ignored_targets_are_valid`.
- **Mark `test_flce_native_support_requires_exact_sm100`
`@sm100_required`**: on SM90 `_native_sm100_supported` short-circuits at
`infer_device_arch() != "blackwell"`, so `get_device_capability` is
never called and its `assert seen == [tensor.device]` can't hold.

Guard/validation tests that use `pytest.raises` (`rejects_fp32`,
`rejects_non_sm100`, `reduction_none`, OOB targets, `invalid_inputs`)
still **run and pass** on SM90 — coverage preserved.

## Verification (on an H100 node)

- FLCE file alone: **214 failed → 0 failed** (`12 passed, 225 skipped`,
6.9s).
- Full `test/cutedsl/` (with #1430 also applied): **462 passed, 229
skipped, 0 failed**.
- `ruff check` + `ruff format --check`: clean.

## Relationship to #1430

The other 28 SM90 failures (26 CE-fp32 `assumed_align` + 2 routing) are
fixed by #1430. This PR is complementary and independent (touches only
`test/cutedsl/test_fused_linear_cross_entropy.py`); together they make
the H100 job fully green.

---------

Co-authored-by: svc-kingkong_LinkedIn <[email protected]>
Co-authored-by: Copilot <[email protected]>
2026-09-01 20:26:39 +00:00
083c92342b fix(cutedsl): fp32 CE loss precision on B200 + restore LigerCrossEntropyFunction export (#1430)
## Summary

Two bugs fixed, both caught by `make test-cutedsl` on B200 (SM100).

---

### Bug 1 — `assumed_align=2` causes bf16-precision stores for fp32 loss
tensors (pre-existing)

**Failing tests (26):** all fp32 CE tests with feature flags — z_loss,
softcap, label_smoothing — when `requires_grad=True`.

**Symptom:** Forward loss quantised to bf16 precision (`1.46875` instead
of `1.47212`, `~2.8e-2` error) even though `loss_1d` is fp32. Error only
appears with `HAS_GRAD=True`.

**Root cause:** `to_cute_tensor(loss, assumed_align=2)` passed a 2-byte
alignment hint to `from_dlpack` for a 4-byte (fp32) tensor. When
compiled with `HAS_GRAD=True`, the CuTe DSL compiler generated 16-bit
(bf16) stores for `mLoss[row]` instead of correct 32-bit stores. Same
issue affected the backward `_scale_in_place` gradient scaler.

**Fix:** Replace hardcoded `assumed_align=2` with
`assumed_align=<tensor>.element_size()` at three call sites (loss
buffer, z_loss buffer, scale buffer). Keeps 2-byte alignment for
bf16/fp16, uses 4-byte for fp32.

---

### Bug 2 — `LigerCrossEntropyFunction` not exported from
`transformers.cross_entropy` (since #1416)

**Failing tests (2):**
`test_backend.py::test_liger_kernel_impl_cutedsl_routes` and
`test_cross_entropy.py::test_liger_kernel_impl_cutedsl_selects_cutedsl_ce`.

**Root cause:** PR #1416 refactored
`liger_kernel/transformers/cross_entropy.py` to use `dispatch()` from
the new multi-DSL backends system, removing the explicit
`LigerCrossEntropyFunction` import. The cutedsl routing tests expect
this attribute to be importable and to resolve to the cutedsl class when
`LIGER_KERNEL_IMPL=cutedsl`.

**Fix:** Re-add the import as a re-export (`# noqa: F401`). Because
`liger_kernel.ops` applies backend replacement at import time, the
attribute correctly resolves to the cutedsl implementation when
`LIGER_KERNEL_IMPL=cutedsl`.

---

## Verification (B200 / SM100)

```
641 passed, 50 skipped, 0 failed  (50 skipped = SM90-only tests self-skip on B200)
```

Co-authored-by: GitHub Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
2026-09-01 17:19:17 +00:00
Vaibhav JindalandCopilot c02362cb5b fix(cutile): pin cuda-tile>=1.5.0 so CI gets CompilerOptions.num_worker_warps (#1427)
## Summary

Fixes the scheduled **cuTile (H100)** job, which failed at
import/collection with:

```
src/liger_kernel/ops/cutile/ops/dyt.py:144: in <module>
    _dyt_bwd_kernel_nww8 = _dyt_bwd_kernel.replace_hints(num_worker_warps=8)
...
E   TypeError: CompilerOptions.__init__() got an unexpected keyword argument 'num_worker_warps'
```

## Root cause

The cuTile ops use `num_worker_warps` both as a `@ct.kernel(...)`
argument and via
`replace_hints(num_worker_warps=...)` (in `dyt.py`,
`fused_add_rms_norm.py`,
`poly_norm.py`, `softmax.py`, `fused_linear_cross_entropy.py`,
`swiglu.py`).

That `CompilerOptions` field was **only added in cuda-tile 1.4.0** —
it's absent in
1.3.0 and 1.0.0 (verified by inspecting the wheels). Because the
`cutile` /
`cutile-tileiras` extras pinned only `"cuda-tile"` (unpinned),
dependency resolution
backtracked to an older cuda-tile whose `[tileiras]` extra constraints
were
satisfiable, landing below 1.4.0. The field was then missing, so
importing
`liger_kernel.ops.cutile.ops` (triggered under
`LIGER_KERNEL_IMPL=cutile`) crashed and
took down the whole cuTile suite at collection.

Since this runs at import via the backend-replacement in
`liger_kernel/ops/__init__.py`,
it is not recoverable at runtime — the version floor must guarantee the
field exists.

## Fix

Pin the floor to `cuda-tile>=1.5.0` (the validated version) in both
cuTile extras in
`setup.py`, so the resolver can no longer fall back to a version without
`num_worker_warps`.

## Notes

- cuTeDSL (SM90/SM100) jobs also failed in the same run, but that's a
**separate**
  library (`nvidia-cutlass-dsl`) and will be addressed separately.
- Verified locally: `num_worker_warps` present in cuda-tile 1.4.0 &
1.5.0, absent in
1.3.0 & 1.0.0; `setup.py` parses and is ruff-clean. The full cuTile
suite needs an
  H100 to confirm end-to-end (no GPU in the dev sandbox).

Co-authored-by: Copilot <[email protected]>
2026-08-31 21:43:55 +00:00
Vaibhav JindalandCopilot 00e72b86ca ci: schedule NVIDIA tests by cadence/GPU with a hard 3-job concurrency cap (#1415)
## Summary

Reorganizes the **NVIDIA GPU** workflow into a cadence- and GPU-aware
schedule, and adds the Modal functions to back it. Follows up #1410
(which added the per-DSL test folders and `make test-cutile` /
`test-cutedsl` / `test-cute` targets).

### Schedule

**Daily** (cron `24 9 * * *`):
| Suite | GPU | Modal fn |
|---|---|---|
| general correctness | H100 | `liger_correctness_tests` |
| cuTile | H100 | `liger_cutile_tests` (new) |
| cuTeDSL — SM90 half | H100 | `liger_cutedsl_tests_h100` (new) |
| cuTeDSL — SM100 half | B200 | `liger_cutedsl_tests_b200` (new) |

cuTeDSL runs on **both** archs on purpose: its SM90 tests only run on
H100 and its SM100 tests only run on B200 — each arch self-skips the
other half, so both are needed for full coverage.

**Weekly** (cron `24 13 * * 0`, Sundays, H100): `convergence` (latest
tf) + oldest-supported-tf **correctness** and **convergence**.

**Not scheduled:** `cute` (fused-MoE) tests, for now.

### The ≤3-concurrent fix

A 4th simultaneous Modal GPU call was being rejected (one job died
instantly at ~0.7 min). This is now capped two ways:
- **`strategy.max-parallel: 3`** on each matrix job (daily's 4 entries
run 3-then-1; weekly's 3 run together).
- **Workflow-level `concurrency: { group: <workflow>,
cancel-in-progress: false }`** serializes whole runs, so the daily and
weekly runs never overlap.

Only **one** cadence job activates per trigger (the job-level `if`
selects daily vs weekly from `github.event.schedule` / the
`workflow_dispatch` `suite` input), so at most **3** GPU jobs ever run
together. The `if` deliberately uses only the `github` context — **not**
the `matrix` context, which isn't available in a job-level `if`.

### Manual runs
`workflow_dispatch` gains a `suite` input (`daily` / `weekly`, default
`daily`).


## Notes / caveats
- **Convergence is currently red** on `main`'s scheduled runs
(pre-existing; correctness passes). Weekly scheduling is fine but expect
failures until that's fixed separately.
- **DSL suites run in CI for the first time** — the cuTeDSL-B200 (SM100)
path and the cuTile routing test may surface latent failures on first
run.
- **B200 access**: uses `gpu="B200"` on Modal — confirm the token/plan
has B200 capacity.
- `cuTile` installs the `cutile-tileiras` extra so `import cuda.tile`
resolves (otherwise those tests importorskip).
- Validated locally with a YAML parser + a gating simulation (exactly
one job per trigger) + modal-fn-name cross-check; `actionlint` wasn't
installable in the dev sandbox, and GPU suites can't run there.

Co-authored-by: Copilot <[email protected]>
2026-08-28 04:53:37 +00:00
Vaibhav JindalandCopilot 38c6a455ea test: organize DSL tests into per-DSL folders (cutedsl/cutile/cute) (#1410)
## Summary

The three opt-in GPU DSL backends — **cutedsl**, **cutile**, and
**cute** (fused-MoE) — had their tests inconsistently placed: a partial
`test/cutedsl/` folder existed, but most DSL tests were scattered in
`test/transformers/`. This PR gives each DSL its **own test folder**,
mirroring the existing `test/cutedsl/` convention (drop the DSL prefix
from filenames), and wires up matching `make` targets.

One minor change in cron schedule for nvidia-ci.

## Changes

### cutedsl → `test/cutedsl/`
- Moved `test_cutedsl_fused_linear_cross_entropy_sm90` →
`test_fused_linear_cross_entropy_sm90`
- Moved `test_cutedsl_rms_norm_fastpath` → `test_rms_norm_fastpath`
- Moved `test_cutedsl_rope` → `test_rope`
- Moved `test_swiglu_cutedsl` → `test_swiglu`
- **Deleted** `test_cutedsl_rms_norm` — it was a duplicate *subset* of
the canonical `test/cutedsl/test_rms_norm.py` (same two test-function
names; the canonical file is a strict superset with the extra Blackwell
fast-path tests and stricter mode-specific bf16 dW tolerances). Deleting
avoids a filename + test-name collision; the canonical file is left
untouched.
- Fixed a stale docstring path in `test/cutedsl/test_backend.py`.

### cutile → new `test/cutile/`
- Moved `test_cutile_backend` → `test_backend`,
`test_cutile_fused_linear_cross_entropy_sm90` →
`test_fused_linear_cross_entropy_sm90`, `test_cutile_rms_norm` →
`test_rms_norm`, `test_cutile_rope` → `test_rope`, `test_cutile_swiglu`
→ `test_swiglu`.
- **Split** the 7 env-gated `test_cutile_*` tests out of the shared
`test/transformers/test_fused_linear_scaled_cross_entropy.py` into
`test/cutile/test_fused_linear_scaled_cross_entropy.py`. The
backend-agnostic frontend/dispatcher tests stay in `test/transformers/`.

### cute (fused-MoE) → new `test/cute/`
- Moved `test_cute_moe_autograd` → `test_moe_autograd` and `test_moe` →
`test_moe`.
- `test_fused_moe.py` is the general Triton MoE test and **stays** in
`test/transformers/`.

### Makefile
- `test:` and `test-verbose:` now `--ignore=test/cutile` and
`--ignore=test/cute` (and `test-verbose` also ignores `test/cutedsl` for
consistency), so DSL tests run only via their dedicated targets.
- New **`test-cutile`** (`LIGER_KERNEL_IMPL=cutile`, mirrors
`test-cutedsl`).
- New **`test-cute`** — intentionally does **not** set
`LIGER_KERNEL_IMPL=cute`, because selecting it would force `import
liger_kernel.ops` to load the separate `liger_cute_kernels` (lck) wheel
and break the always-on registration/import-guard tests.

## Notes / decisions
- All moves use `git mv` to preserve history. The `test.utils` import
blocks in the moved rope/swiglu files were re-sorted by ruff because
`test` is now first-party inside the packaged DSL folders.
- **Coverage shift (intended):** a few always-on pure-Python tests
(cutedsl rms_norm fastpath, cute registration/import-guard) previously
ran under `make test`; they now run via the dedicated `test-*` targets,
consistent with the one-folder-per-DSL model.
- No new basename collisions: the DSL folders all have `__init__.py`
(unique package-qualified module names) and are ignored by `make test`.

## Validation
- `ruff check` and `ruff format --check` pass across the whole `test/`
tree.
- All affected files pass `py_compile`; `make -n` dry-runs verified for
the new targets.
- ⚠️ GPU/Modal test execution (`make test-cutedsl` / `test-cutile` /
`test-cute`) was **not** run in this environment (no CUDA/torch
available). These should be validated on a GPU host with the
corresponding extras installed.

---------

Co-authored-by: Copilot <[email protected]>
2026-08-27 05:37:31 +00:00
Vaibhav JindalandCopilot 0406e4ddc0 ci: run NVIDIA GPU tests on manual dispatch and daily cron only (#1409)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->

Remove push, pull_request, and merge_group triggers from the NVIDIA GPU
workflow so it no longer runs on every PR/merge. It now runs only via
workflow_dispatch (manual) and a daily schedule (00:00 UTC). Also drop
the now-dead merge_group if-guards on all jobs and simplify the
concurrency key.

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->
CI test to be done

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [ ] run `make test` to ensure correctness
- [ ] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence

Co-authored-by: Copilot <[email protected]>
2026-08-26 21:48:05 +00:00
b28a0ca4f4 [cuTeDSL] Make SwiGLU CUDA-graph capturable + harmonize plain SiLU-Mul tests (#1408)
## Summary
Two related changes to the plain SiLU-Mul (SwiGLU:
`silu(a*gate)*b*down`) kernels — a CUDA-graph capturability fix for the
CuTeDSL backend, and harmonized test coverage across all three backends
(Triton / CuTeDSL / cuTile). No change to the Triton or cuTile kernels;
the fused-GEMM MLP kernels are untouched.

## 1. CuTeDSL SwiGLU: CUDA-graph capturability fix
The CuTeDSL SwiGLU launched all four kernels (vectorized fast path +
predicated fallback, forward and backward) without threading PyTorch's
current CUDA stream, so they ran on the default/null stream. As a result
the op was **not CUDA-graph capturable** — graph replay produced
stale/empty results (verified: graph-vs-eager max diff ~10-15, should be
0).

Fix mirrors `rms_norm.py` (the existing TVM-FFI + stream reference):
- add `_cute_stream()` (a cached `cuda.CUstream` over
`torch.cuda.current_stream()`),
- add `stream: cuda.CUstream = None` to each `@cute.jit` launcher and
pass `stream=stream` into every `.launch(...)`,
- for the TVM-FFI-compiled paths, compile against
`make_fake_stream(use_tvm_ffi_env_stream=True)` so the kernel tracks the
caller's env stream (the non-TVM-FFI `_dyn` path passes a real
`_cute_stream()`).

Numerics and gate/down semantics are unchanged. After the fix,
CUDA-graph capture + replay is **bit-identical to eager** on all paths
(fast/predicated, fwd/bwd), and the existing CuTeDSL suite still passes.

## 2. Harmonized plain SiLU-Mul tests across the three backends
Each backend test file (`test_swiglu.py`, `test_swiglu_cutedsl.py`,
`test_cutile_swiglu.py`) now defines a **byte-identical shared block**
(`_SILU_SHAPES`, `_SILU_MULTIPLIERS`, dtype lists, an fp32 reference,
tolerances — verified same md5 across files) and a **single parallel
set** of tests exercising each backend's own SiLU-Mul Function:
- correctness vs PyTorch (fwd+bwd) over aligned / predicated / tiny /
odd / non-power-of-2 "cliff" shapes (incl. Qwen2.5 11008 / 13824 /
18944) x fp32/fp16/bf16,
- gate/down multipliers,
- default-multiplier backward-compat,
- shape flexibility (1D-4D),
- CUDA-graph capturability (guards the fix in part 1),
- parity vs the Triton kernel (CuTeDSL and cuTile files).

The set is intentionally non-redundant: the cliff sizes live in the
shared `_SILU_SHAPES` (so every backend covers them once), the earlier
cuTile-specific cliff-sweep tests were folded into that shared list, and
Triton's multiplier coverage stays in its existing
`test_correctness_silumul_with_multipliers` rather than a duplicate.
Existing backend-specific tests are preserved: Triton's HF MLP-module +
DTensor tests, and CuTeDSL's low-level `swiglu_forward` functional test.

## Results (B200)
- cuTile: **68 passed**
- CuTeDSL: **70 passed**
- Triton: plain SiLU-Mul suite **48 passed** (adds
correctness-vs-PyTorch, shape-flexibility, and CUDA-graph tests;
multipliers already covered by the existing test). The only Triton
failures are the **pre-existing fused-GEMM `MixtralExperts`** tests —
unchanged from `main` and out of scope for plain SiLU-Mul.
- The new CUDA-graph tests pass on all three backends; lint (`ruff
check` + `format --check`) clean; the shared `_SILU_SHAPES` block is
byte-identical across the three files.

Co-authored-by: Copilot <[email protected]>

Co-authored-by: vaibhavjindal <[email protected]>
Co-authored-by: Copilot <[email protected]>
2026-08-26 17:14:47 +00:00
4310065d8c [cuTile] Fix SwiGLU non-power-of-2 forward cliff with exact-fit pow2 tiling (#1407)
## Summary
The cuTile SwiGLU **forward** kernel had a performance cliff on
non-power-of-2 intermediate sizes. When no large power-of-2 tile divided
`n_cols` evenly, the kernel fell back to `check_bounds=True` (masking)
for the overhang tile, making common non-power-of-2 widths ~1.4-1.6x
slower on B200 — this hits the entire Qwen2.5 family (intermediate sizes
11008 / 13824 / 18944) and others (e.g. Gemma-ish 4608).

This PR replaces the forward tiling with an exact-fit power-of-2
decomposition so every chunk is fully in-bounds and uses the fast
`check_bounds=False` path for all shapes — no masking, no overhang.

## Approach
Each row is covered by:
- `n_cols // BASE` full `BASE`-width chunks via a uniform `for` loop,
then
- the `n_cols % BASE` remainder as a fixed ladder of `if <present>:`
guards for chunk widths `BASE/2 .. 1` (each guard is constant-folded
away when that power is absent).

Because it's a descending-power-of-2 decomposition, each chunk's column
offset is a sum of strictly-larger powers of two — hence divisible by
the chunk width — so every gather/scatter index is `< n_cols`.
`check_bounds=False` is therefore always safe.

`ct.arange` requires a compile-time power-of-2 size and cuTile compiles
`for`/`while` as device loops (the loop variable is a runtime value), so
a single loop can't vary the chunk width. The kernel is built by an
`n_cols`-keyed factory (cached); `gate_multiplier` stays a `ct.Constant`
kernel param so the cache keys only on `n_cols`. BASE = 2048 gives the
best DRAM utilisation on B200.

Why still gather/scatter (not block ct.load/store)? On B200, block
load/store emit ~3x more address-arithmetic instructions for
byte-identical, equally-coalesced memory traffic (confirmed via Nsight
Compute) and are ~1.5-2x slower for this row-wise pattern. The cliff is
caused by the check_bounds=True masking, not the access primitive — so
the fix keeps gather/scatter and only changes the tiling.

Backward is intentionally unchanged. Its per-element compute hides the
check_bounds predicate — measured backward throughput is flat across
aligned and non-aligned n_cols (no cliff) — and exact-fit actually
regressed it on some sizes. It keeps the existing adaptive uniform-tile
kernel.

## Results (device-only, CUDA-graph, B200, 4096 rows, bf16)
| n_cols | model | forward speedup | backward |
|---|---|---|---|
| 13824 | Qwen2.5-3B | 1.57x | 1.00x |
| 4608 | Gemma-ish | 1.36x | 1.00x |
| 16384 | (aligned) | 1.25x | 1.00x |
| 18944 | Qwen2.5-14B | 1.18x | 1.00x |
| 11008 | Qwen2.5-7B | 1.04x | 1.00x |
| 8192 / 12288 / 14336 | aligned | ~1.00x | 1.00x |

No regressions on any tested shape (aligned 16384 even improves, since
the previous kernel used less-efficient 4096-wide tiles).

## Correctness
Forward output, da, and db are bit-identical (0.0 max diff) to the
previous kernel across bf16/fp16/fp32, aligned + non-aligned + tiny/odd
n_cols (down to 1), 3D inputs, and gate/down multipliers. CUDA-graph
capture+replay is bit-identical to eager.

Adds test/transformers/test_cutile_swiglu.py (83 cases) exercising the
exact-fit decomposition across cliff sizes and the full power-of-2
remainder ladder (odd widths like 1023 / 431 / 7 / 1), multipliers, and
3D inputs.

## Notes
- Scope is the cuTile SwiGLU forward only. Pre-existing behaviours are
preserved (in-place backward, .view().contiguous() input handling,
torch.compile treating the launch stream as opaque — shared by all
cuTile ops).

Co-authored-by: Copilot <[email protected]>

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [ ] run `make test` to ensure correctness
- [ ] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence

Co-authored-by: vaibhavjindal <[email protected]>
Co-authored-by: Copilot <[email protected]>
2026-08-25 22:49:36 +00:00
ead96b618e fix(cutile): handle 2-D (seq, head_dim) cos/sin in RoPE + add vision tests (#1399)
The vision rotary helper (liger_rotary_pos_emb_vision, used by Qwen*-VL)
transposes q/k to 4-D but forwards cos/sin unchanged as rank-2 (seq_len,
head_dim) tables. The cuTile RoPE kernels index cos/sin with a 3-tuple,
so a 2-D table raised:

    TileTypeError: Index size 3 does not match the array rank 2

Fix: add a `cos.dim() == 2 -> unsqueeze(0)` guard to cuTile rope_forward
and rope_backward, matching the existing CuTe DSL behaviour. (Triton
already tolerates the 2-D table because its per-batch offset multiplies
by batch_idx=0.)

Tests:
- Add test/transformers/test_cutile_rope.py: cuTile RoPE numerics vs
Triton + HF across both code paths (ALIGNED 4-D and general
gather/scatter), fwd+bwd, and a 2-D vision cos/sin regression test.
- Add matching 2-D vision cos/sin regression tests to test_rope.py
(Triton) and test_cutedsl_rope.py (CuTe DSL).
- Bring the Triton test suite to dtype/shape parity with the DSL suites:
add fp16 and a long-sequence (8192) shape.

All three RoPE suites pass (195 tests).

## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [ ] run `make test` to ensure correctness
- [ ] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence

Co-authored-by: vaibhavjindal <[email protected]>
Co-authored-by: Copilot <[email protected]>
2026-08-21 23:53:43 +00:00
16e49d4068 Add LigerCute: fused expert-parallel MoE kernels for Hopper and Blackwell (CUTLASS + NVSHMEM) (#1355)
## Summary

Adds **LigerCute** — fused expert-parallel MoE kernels for NVIDIA Hopper
(`sm_90a`) and Blackwell (`sm_100a`), built on CUTLASS + NVSHMEM.

A persistent, warp-specialized kernel overlaps NVSHMEM communication
with CUTLASS matmuls: communication warps move remote token tiles while
the remaining warps run the expert MLP. Forward and backward are both
fused, use statically sized symmetric buffers, and support CUDA Graph
capture without a token-capacity limit.

The change is almost entirely a **new top-level `liger_cute_kernels/`
directory** (88 files) plus a small opt-in Python integration.

### What's added

| Path | Contents |
|---|---|
| `liger_cute_kernels/` | New native CUDA package: `csrc/`
(CUTLASS/NVSHMEM MoE kernels), `cmake/`, C++ tests, TVM FFI bindings,
its own `setup.py`/`pyproject.toml` |
| `src/liger_kernel/ops/cute/` | Opt-in `cute` impl registration +
`moe_fused` autograd op |
| `src/liger_kernel/transformers/moe.py` | `LigerExpertParallelFusedMoe`
`nn.Module` |
| `test/transformers/test_moe.py`, `test_cute_moe_autograd.py` |
Python-side tests |

### Reported BF16 results

Benchmark snapshots from a CUDA 12.9 environment — not performance
guarantees for every model or system.

| Evaluation | Headline result |
|---|---|
| Standalone MoE kernels | Up to **32% lower forward latency**, **7%
lower backward latency** vs the strongest compared implementation |
| Communication-intensive H200 cases | **10–35% higher forward
throughput** than Comet; selected backward cases up to **~108% higher**
than DeepEP |
| Qwen3-30B-A3B on 8×H100 | **2.35× speedup / 57% lower step time** than
Megatron; **~17% lower step time** than Transformer Engine |
| End-to-end convergence | **5.06% final-loss improvement** over the
Megatron baseline |

## Details

**This is opt-in and does not affect existing users.**

- The top-level `liger_kernel` wheel stays **pure Python/Triton**. It
does not build, contain, or link any of this native code.
- The native core ships as a **separate `liger_cute_kernels` wheel**
(CUDA/torch-version prefixed), built independently.
- `liger_kernel/ops/cute/__init__.py` registration is **pure metadata**
— it does not import the native extension. The ops module (which does
load it) is imported only when the impl is actively selected via
`LIGER_KERNEL_IMPL=cute`.
- If the lck wheel is absent, everything else in `liger_kernel` keeps
working; `is_available()` returns `False` and selection raises a helpful
`ImportError`.

The core's public ABI is `extern "C"` only (no `std::`/torch types cross
it), symbols are hidden except `liger_cute_*`/`__tvm_ffi_*`, and
libstdc++/libgcc are linked statically — so the core is ABI-agnostic and
not tied to a specific torch wheel or a runtime JIT compile. The Python
binding is TVM FFI/DLPack based.

Blackwell support keeps each kernel's Hopper (WGMMA) consumer
byte-for-byte as `Impl<90>` and adds a UMMA/tcgen05 `Impl<100>` selected
by an `int Compute` template parameter; the producer is arch-agnostic
and shared.

### Changes outside the new directory

Only two, both trivial:
- `.gitignore` — 3 lines to ignore `liger_cute_kernels/build*`
- `setup.py` — `packages=["liger_kernel"]` →
`find_packages(where="src")`

See `liger_cute_kernels/README.md` for build instructions and the full
architecture writeup.

## Testing Done

- C++ correctness tests per kernel under `liger_cute_kernels/tests/cpp/`
(`test_mlp1_fused`, `test_mlp2_fused`, `test_mlp2_t_fused`, `test_mlp3`,
`test_mlp4`, `test_mlp5_fused`, `test_moe_api_dispatch`), validated
against an fp32 CPU reference on identically bf16-rounded inputs. Pass
bar: `mean_rel < 1%`, `max_rel < 5%`.
- Python binding / build / CUDA-graph / NVSHMEM team-setup tests under
`liger_cute_kernels/test/`.
- Autograd parity tests in `test/transformers/test_cute_moe_autograd.py`
and module tests in `test/transformers/test_moe.py`.

- Hardware Type: H100 / H200 / B200 (`sm_90a` + `sm_100a`), CUDA 12.9,
CUTLASS 4.4.1
- [ ] run `make test` to ensure correctness
- [ ] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence

---------

Co-authored-by: Jari Kolehmainen <[email protected]>
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Co-authored-by: Chen <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: autinn <[email protected]>
Co-authored-by: autinn <[email protected]>
Co-authored-by: jkolehma <[email protected]>
2026-08-07 21:29:09 +00:00
Vaibhav JindalandCopilot 11702da962 fix(triton): pin fp64 scalar kernel params to fp32 in swiglu and rms_norm (#1350)
## Summary

Hand-written Triton kernels get their non-constexpr Python-float params
specialized differently by eager Triton and by Inductor. Eager
specializes them to `fp32`; when the same kernel is launched from inside
`torch.compile`, Inductor specializes them to `fp64`:

```
# TORCH_LOGS=output_code, _swiglu_forward_kernel as generated by Inductor
triton_meta={'signature': {'a_ptr': '*bf16', ..., 'gate_multiplier': 'fp64', ...}}
```

Since `x.to(tl.float32) * gate_multiplier` mixes an fp32 tensor with an
fp64 scalar, Triton promotes the **whole expression to float64** —
including the transcendentals (`tl.sigmoid` in swiglu, `rsqrt` in
rms_norm). These kernels are memory bound, so the promotion roughly
halves their throughput.

Isolated repro at 8192x3072 bf16 on H100:

| scalar typing | time | bandwidth |
|---|---|---|
| fp32 (eager path) | 56.1 us | 2693 GB/s |
| fp64 (Inductor path) | 108.5 us | 1392 GB/s |
| fp64 + `.to(tl.float32)` | **56.0 us** | **2694 GB/s** |

The fix is an explicit `.to(tl.float32)` on the scalar inside the kernel
— a no-op on the eager path (already fp32) and a full recovery on the
compiled path. `rms_norm` already did exactly this for
`_CASTING_MODE_NONE`; this extends it to the LLAMA/GEMMA casting modes
and to the affine `offset` used by both backward kernels.

## Details

This was found while benchmarking Liger against `torch.compile` on
Qwen3-0.6B. Worth noting what it was **not**: there are no graph breaks
in either config (Liger actually lowers op count 1266 -> 844), and
standalone the Liger SwiGLU kernel already matched Inductor's exactly
(both 56us, ~80% of roofline). The regression was `torch.compile`
silently degrading Liger's own kernels via scalar typing.

Per-kernel GPU time, Qwen3-0.6B trunk forward @ 8x1024, H100 bf16:

| kernel | before | after | compile-only equivalent |
|---|---|---|---|
| `_swiglu_forward_kernel` | 3.017 ms | **1.361 ms** | 1.316 ms |
| `_rms_norm_forward_kernel` | 0.848 ms | **0.655 ms** | — |
| total GPU | 24.396 ms | **22.958 ms** | 23.395 ms |

End-to-end speedup vs eager HF baseline (Liger patched with `rope +
rms_norm + swiglu` only; CE / fused-linear-CE off so the LM head is
identical across configs):

| shape | mode | `torch.compile` | `liger+compile` before |
`liger+compile` after |
|---|---|---|---|---|
| 8x1024 | trunk fwd | 1.870x | 1.856x | **2.075x** |
| 8x2048 | trunk fwd | 1.557x | 1.509x | **1.674x** |
| 4x2048 | trunk fwd | 1.578x | 1.534x | **1.727x** |
| 8x1024 | fwd+bwd | 1.978x | 1.845x | **2.008x** |
| 8x2048 | fwd+bwd | 1.651x | 1.554x | **1.691x** |

So stacking Liger with `torch.compile` was previously a regression
relative to `torch.compile` alone, and is now a win. Eager-only Liger is
unchanged (27.765 ms vs 27.755 ms before), confirming the cast is free
on that path.

Numerical drift versus the eager baseline also **improves**, since the
compiled path now does the same fp32 arithmetic eager does:

```
liger        : rel=2.01e-02  argmax_agree=100.0%
compile      : rel=1.92e-02  argmax_agree=100.0%
liger+compile: rel=4.77e-02  ->  2.51e-02   argmax_agree=100.0%
```

Two things I deliberately left out of this PR:

1. **The same pattern likely affects other ops.** An AST scan turned up
~80 float-scalar kernel params across ~20 kernels (`layer_norm`,
`group_norm`, `poly_norm`, `dyt`, `cross_entropy` softcap, `grpo_loss`
temperature, `tvd` scale, ...). I only fixed the two ops I could measure
end-to-end. Happy to do a sweep in a follow-up.
2. **Unrelated:** `torch.compile` with Liger RoPE requires
`dynamic=False` — with dynamic shapes Dynamo hits an internal assertion
in `higher_order_ops.py` (`assert subgraph_vt.is_tensor() or
isinstance(subgraph_vt, SymNodeVariable)`) while tracing
`LigerRopeFunction`, on the *second* distinct input shape. That's a
separate issue, not addressed here.

## Testing Done

Correctness on the touched ops, plus convergence:

- `pytest test/transformers/test_swiglu.py
test/transformers/test_rms_norm.py` -> **113 passed**, 4 skipped, 12
xfailed
- `pytest test/transformers/test_fused_add_rms_norm.py
test/transformers/test_monkey_patch.py` -> **100 passed**, 2 xfailed
- `pytest test/convergence/bf16/test_mini_models.py -k "qwen3 or llama"`
-> **10 passed**
- `make checkstyle` -> passed

- Hardware Type: **H100 80GB HBM3**
- [ ] run `make test` to ensure correctness
- [x] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence

> Note: I ran the targeted suites listed above rather than the full
`make test` /
> `make test-convergence` sweeps, so I've left those two boxes unchecked
rather than
> claim more coverage than I actually have. Relying on CI for the full
run.

Co-authored-by: Copilot <[email protected]>
2026-08-07 18:55:27 +00:00
Vaibhav Jindal 08e8ccdc6a [Tests] Move cutedsl tests to test/cutedsl/ and add make test-cutedsl target (#1337)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->
[Tests] Move cutedsl tests to test/cutedsl/ and add make test-cutedsl
target


<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->


- Move test_cutedsl_{backend,cross_entropy,rms_norm}.py from
test/transformers/ to a dedicated test/cutedsl/ directory, mirroring the
layout of test/megatron/ and test/convergence/.
- Add `make test-cutedsl` Makefile target that sets
LIGER_KERNEL_IMPL=cutedsl and runs pytest against test/cutedsl/.
Requires the `cutedsl` extra (nvidia-cutlass-dsl): pip install -e
'.[cutedsl]'
- Exclude test/cutedsl/ from `make test` so the main correctness suite
stays clean (previously the cutedsl tests silently skipped there).

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [ ] run `make test` to ensure correctness
- [ ] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence
2026-08-03 21:59:13 +00:00
Vaibhav JindalandCopilot be1cdc176b fix(ce): restore true-class gradient precision in low-precision buffers (#1329)
#1268 replaced the per-element `tl.where(X_offsets != y, ...)` selection
with a post-loop read-modify-write of the true-class gradient:

    tl.store(X_ptr + y, tl.load(X_ptr + y) + dxy)

X_ptr is the logits buffer, which is bf16/fp16 in mixed-precision
training (FLCE computes the gradient in place). The round-trip therefore
rounds softmax(x_y) / N to the buffer dtype *before* the -(1 -
label_smoothing) / N term is subtracted.

Since dx_y = (softmax(x_y) - 1) / N cancels catastrophically as
softmax(x_y) -> 1, the pre-rounding error is amplified by roughly 1 / (1
- softmax(x_y)). Measured max relative error of dx_y against an fp64
reference (bf16, V=32000, H100):

    softmax(x_y)   before #1268   at #1268   torch
    0.90                  0.24%      1.95%   0.43%
    0.99                  0.30%     19.99%   0.46%
    0.999                 0.38%    100.00%   0.42%

Confident predictions are the common case once a model has trained for a
while, so this silently degrades the dominant gradient signal. It
surfaced as a convergence failure in

test_mini_model[mini_gemma3_text-32-1e-05-dtype25-0.01-0.01-0.1-0.01-0.01-0.01],
where 32 steps of accumulated drift push a top-k logprob past tolerance.

Recompute dx_y once in fp32 after the loop, from the already-live
ori_X_y, m and d, and write it with a single store. This keeps #1268's
O(1) correction (no O(V) compare/select is reintroduced) while folding
in the -(1 - label_smoothing) term before the result is rounded to the
buffer dtype.

Accuracy returns to the pre-#1268 values exactly (0.02% / 0.36% / 0.24%
/ 0.30% / 0.38%), and the speedup is retained: 3.585 ms vs 3.571 ms
(this PR vs #1268) at BT=8192, V=128256 bf16 forward+backward on H100,
against 3.619 ms before #1268.

The existing CE suite passed with the regression because its random
logits leave softmax(x_y) near zero, so add a regression test that
drives softmax(x_y) -> 1 and requires the true-class gradient to be no
less accurate than torch's own low-precision backward. It fails on the
#1268 kernel and passes here with ~6x headroom.

## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>

- [x] run `make test` to ensure correctness
- [x] run `make checkstyle` to ensure code style
- [x] run `make test-convergence` to ensure convergence

Co-authored-by: Copilot <[email protected]>
2026-08-03 20:37:22 +00:00
Vaibhav JindalandCopilot f5df160741 Pin ruff version to <0.16.0 to avoid checkstyle breakage (#1318)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->

ruff 0.16.0 changed formatting behavior for Python code fences embedded
in Markdown files, causing 'ruff format --check --diff' to flag
previously-formatted docs/skills markdown files as needing changes.
Since dev/fmt-requirements.txt and setup.py pinned only a lower bound
(ruff>=0.1.6 / ruff>=0.12.0), CI always installs the latest ruff, which
broke the merge queue checkstyle job for unrelated PRs.

Pin ruff to >=0.12.0,<0.16.0 in both setup.py and
dev/fmt-requirements.txt to keep checkstyle stable until the repo is
updated to support the new formatting behavior.
<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [ ] run `make test` to ensure correctness
- [ ] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence

Co-authored-by: Copilot <[email protected]>
2026-07-23 23:50:43 +00:00
Vaibhav Jindal a5234dfc1d Update pyproject.toml for v0.8.1 release (#1314)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->
Update pyproject.toml for v0.8.1 release

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [x] run `make test` to ensure correctness
- [x] run `make checkstyle` to ensure code style
- [x] run `make test-convergence` to ensure convergence
2026-07-22 23:56:50 +00:00
Vaibhav JindalandCopilot 7cdd19f73a Fix convergence test failures (#1313)
## Summary

`make test-convergence` had 6 failing tests on the latest transformers
(5.13.0). This PR
root-causes each and fixes them — one is a genuine Liger kernel bug
(Llama4 vision RoPE
producing `NaN`), one is a workaround for an upstream transformers
processor bug, and the
rest are precision/near-tie fragilities in the eval top-k-logprobs
comparison. It also adds
two version-gated `xfail`s for tests that crash only on the **oldest**
supported transformers
(4.52.0), so the suite is green across the supported range.

Only one source file changes (`llama4_rope.py`); everything else is
test-side.

## Root causes & fixes

### 1. Llama4 multimodal (bf16) → `NaN` — real kernel bug
`Llama4VisionRotaryEmbedding.freqs_ci` is registered as a **complex64**
buffer. `model.to(bf16)`
recursively casts every buffer, and `complex64 → bf16` **discards the
imaginary part** (PyTorch
warns "Casting complex values to real discards the imaginary part").
Liger's fused vision-RoPE
kernel assumes complex freqs; given a real tensor it indexes with the
wrong stride/element-count
and emits `NaN` within ~2 steps. HF's reference stays finite (`complex ×
real`), so the non-Liger
baseline trains fine and the divergence looks Liger-specific. Kernel
bisection confirmed it's the
**vision** RoPE only — the text RoPE recomputes complex freqs each
forward and is bit-exact vs HF.

**Fix** (`src/liger_kernel/transformers/llama4_rope.py`):
`liger_llama4_vision_rotary_pos_emb`
falls back to a reference implementation (matching HF
`vision_apply_rotary_emb`) when `freqs_ci`
is not complex. The complex fast-path kernel is unchanged, so this is a
no-op for callers that
pass genuine complex freqs.

### 2. Gemma4 multimodal (fp32 + bf16) — upstream processor bug +
fragile metric → `xfail`
`transformers>=5.13` `Gemma4Processor.validate_inputs` has an
operator-precedence bug:
`audio is not None and self.audio_token is None or self.boa_token is
None or self.eoa_token is None`
binds as `(audio is not None and ...) or self.boa_token is None or
self.eoa_token is None`, so it
raises the "audio ... audio_token" `ValueError` even with **no audio**
whenever `boa/eoa` tokens
are unset (the mini `GemmaTokenizer` doesn't define them).

**Fix**: set dummy `boa/eoa` tokens on the mini processor (audio isn't
exercised). With that
unblocked, loss and params converge, but the eval **top-k logprobs**
comparison is fragile to
bf16/fp32 near-tie token swaps (a handful of the ~40k slots swap, diffs
up to ~3.3). Rather than
gut the tolerance, the two Gemma4-multimodal tests are marked
`xfail(strict=False)`; they'll
auto-`XPASS`-flag if a future transformers release realigns the
numerics.

### 3. Top-k-logprobs near-tie fragility — tolerance bumps
Under-trained mini models have near-uniform output heads, so a few
low-probability top-k logprob
slots swap run-to-run (bf16/fp32 near-ties amplified by the
non-deterministic attention backward).
Loss and params stay tight and guard real convergence. Bumped
`logprobs_atol` for:
- `mini_gemma4_text` bf16 (`test_mini_models` +
`test_mini_models_with_logits`): `0.5 → 0.7`
- `mini_llama4` bf16 multimodal: `0.1 → 0.5`
- `mini_hunyuan_v1_moe` fp32: `0.1 → 0.7`

### 4. Oldest-transformers crashes (`transformers < 5.0.0`) →
version-gated `xfail`
Surfaced when running the suite against the oldest supported
transformers (4.52.0). Both crash on
`<5.0.0` and pass on `>=5.0.0`, in code this PR doesn't otherwise touch:
- `mini_llama4` fp32: fp32 SDPA raises "Expected query, key, and value
to have the same dtype"
(query fp32 vs key/value bf16) — fixed in the transformers v5.0.0 Llama4
rework. (Activates the
  param's previously commented-out `xfail`, made conditional.)
- `mini_nemotron` bf16: `NemotronModel.forward()` lacks the universal
`**kwargs` before v5.0.0, so
  the harness's `accum_dtype` kwarg raises `TypeError`.

Both gated with `pytest.mark.xfail(condition=not
IS_TRANSFORMERS_V5_OR_LATER, strict=False)` using
the existing helper.

## Validation

- **transformers 5.13.0**: the 6 originally-failing tests now resolve as
**4 passed, 2 xfailed**
(run together, no ordering interaction). Stability: hunyuan 8/8,
gemma4_text 5/5,
  gemma4_text_with_logits 5/5, llama4 5/5.
- **Version gating**: the two new `xfail`s show `XFAIL` on 4.52.0 and
`PASS` on 5.13.0.
- **Backward compatibility**: verified on **transformers 5.5.0**
(earliest with both gemma4 and
llama4) — llama4 rope unit tests 18/18 pass, llama4 bf16 multimodal
passes (fallback path),
gemma4 multimodal `xfail`s cleanly (dummy-token change is a no-op on the
non-buggy 5.5.0
processor), tolerance-bumped tests pass. The complex-freqs kernel
fast-path is unchanged across
  4.57 → 5.13.
- `make checkstyle` clean.

## Notes
- The `xfail`s use `strict=False`, so a future release that fixes the
numerics/crash surfaces as a
non-fatal `XPASS` (a nudge to remove the marker) rather than breaking
CI.
- The repo already uses `skipif(not IS_TRANSFORMERS_V5_OR_LATER)` for
the bf16 Llama4 param; the
new markers use `xfail` instead so the tests still execute and report on
old transformers. Easy
  to switch to `skipif` if that's preferred.
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [ ] run `make test` to ensure correctness
- [x] run `make checkstyle` to ensure code style
- [x] run `make test-convergence` to ensure convergence

---------

Co-authored-by: Copilot <[email protected]>
2026-07-22 18:43:50 +00:00
Vaibhav JindalandCopilot 5b8476aa92 Stop printing coverage report by default in make test / test-convergence and fix tolerance in polynorm (#1310)
## Summary

`pytest-cov` was forced on for every pytest invocation via `addopts` in
`pyproject.toml`, dumping a full per-file coverage table (~190 lines) to
the terminal on every `make test` and `make test-convergence` run (and
any ad-hoc `pytest` invocation), regardless of whether coverage was
wanted. This bloated CI/local logs significantly.

- Remove `--cov*` flags from `[tool.pytest.ini_options] addopts` in
`pyproject.toml` so coverage is no longer collected/printed by default.
- Remove the redundant `--cov` flags from the Makefile `test` target.
- Add a new opt-in `test-verbose` Makefile target (renamed from an
earlier `test-cov`) that runs the same correctness tests with both
coverage reporting (`term-missing` + `html`) and a full `--durations=0`
test-duration breakdown, for anyone who explicitly wants that diagnostic
output.
- Remove `--durations=0` from `[tool.pytest.ini_options] addopts` — this
was forcing every test's duration (slowest-first) to print on every run,
adding a large, mostly-noisy timing block to every `make test`/`make
test-convergence` run. Anyone who wants timing info can still pass
`--durations=N` (or `--durations=0` for all) manually.

### Follow-up: tightened `poly_norm` bf16 gradient tolerance

Also includes a small follow-up from review on #1309: the bf16 `atol`
used for `test_poly_norm.py::test_correctness`'s
`weight.grad`/`bias.grad` checks was loosened to `1.0`, which was
flagged as unnecessarily risky. The largest mismatch actually observed
(catastrophic cancellation in a large `sum()` reduction over up to ~524k
near-canceling terms, differing reduction order between Triton and
PyTorch — see #1309 for full root cause) was `2.25` at a value of
`~34.5`. `atol=0.5` (tolerance `2.225`) fails just under that;
`atol=0.6` (tolerance `2.325`) gives a small, deliberate margin and
passed 5/5 repeated local runs. Tightened from `1.0 → 0.6`.

## Testing Done

1. Verified plain `pytest` / `make test` / `make test-convergence` no
longer print coverage tables.
2. Verified `make test-cov` (opt-in) still produces the full coverage
report as before.
3. Ran `test_poly_norm.py::test_correctness[dtype1-...]` (bf16) 5x with
`atol=0.6` — all passed. Ran the full `test_poly_norm.py` +
`test_fused_add_rms_norm.py` suites 3x — consistently `55 passed, 2
xfailed`.


- Hardware Type: H100
- [x] run `make test` to ensure correctness
- [x] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence

---------

Co-authored-by: Copilot <[email protected]>
2026-07-21 23:51:07 +00:00
Vaibhav Jindal 5eebfda273 Fix failing tests in fused_add_rms_norm and polynorm (#1309)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->

Fixes intermittent/consistent CI failures in
`test_fused_add_rms_norm.py` and `test_poly_norm.py` on CUDA (H100)
caused by bf16/fp32 floating-point precision edge cases — not functional
regressions in the kernels.

**Failing tests:**
-
`test_fused_add_rms_norm.py::test_correctness[{True,False}-BaseAddRMSNorm-0.0-none-dtype1-0.2-0.02-5-123-123]`
- `test_poly_norm.py::test_correctness[dtype1-0.2-0.02-2-128-512]`
- `test_poly_norm.py::test_correctness[dtype1-0.2-0.02-5-123-123]`
- `test_poly_norm.py::test_correctness[dtype0-0.0001-1e-06-8-64-1024]`

### Root cause

**`fused_add_rms_norm` (bf16, `casting_mode="none"`):** Triton's
`tl.sum` accumulates bf16 reductions in bf16, while PyTorch's `.mean()`
internally upcasts to fp32 even for bf16 tensors. Since
`casting_mode="none"` intentionally skips fp32 upcasting (for
speed/memory — see existing docstring), the two implementations'
variance sums diverge slightly, occasionally pushing a single element
past the tight bf16 tolerance for the `(5, 123, 123)` "weird shape"
case.

**`poly_norm` (`weight.grad` / `bias.grad`, both bf16 and fp32):** These
gradients are `sum()` reductions over `bs*sl*hd` (up to ~524k)
near-canceling terms. Triton's kernel and PyTorch's autograd graph
accumulate the sum in different (non-associative) orders, so the two
results can differ from each other by a bit more than the tight
tolerances, even though both are individually close to a
higher-precision (fp64) ground-truth gradient. Verified numerically: for
the failing bf16 case, Liger's result (`-34.5`) is actually *closer* to
the fp64 ground truth (`-35.49`) than the naive PyTorch reference
(`-36.75`) — confirming this is precision noise, not a kernel bug.

### Fix

- **`fused_add_rms_norm`**: Added a targeted, declarative `xfail` (via
`request.node.add_marker(pytest.mark.xfail(..., strict=False))`) for the
specific `BaseAddRMSNorm` + `casting_mode="none"` + bf16 + `(5, 123,
123)` combination, with an inline explanation. `strict=False` means the
test still runs to completion — if this ever starts reliably passing
(e.g. after an upstream Triton/PyTorch fix), it'll report as `XPASS`
instead of silently staying green, so we notice and can remove the
marker.
- **`poly_norm`**: Slightly loosened `atol`/`rtol` in
`test_correctness`'s `dtype, atol, rtol` parametrize instead of
xfail-ing, since the issue here is a a genuine (if small)
tolerance-boundary gap rather than a fundamentally different reduction
dtype:
  - fp32: `atol` `1e-4 → 3e-4` (`rtol` unchanged at `1e-6`)
  - bf16: `atol` `2e-1 → 1.0`, `rtol` `2e-2 → 5e-2`

Added a comment explaining why these are looser than
`test_correctness_functional`'s tolerances.

No production kernel code (`src/liger_kernel/...`) was changed — this PR
only adjusts test tolerances/markers to reflect expected floating-point
behavior.
<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [x] run `make test` to ensure correctness
- [x] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence
2026-07-21 23:10:42 +00:00
Vaibhav Jindal bb715c93a8 Make all the convergence tests run with make test-convergence (#1306)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->
Make all the convergence tests run with make test-convergence. 

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->
Before this change, if one of the convergence test files failed, the
rest were not getting executed. This change will allow running all the
files regardless of failure of any single file.

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [ ] run `make test` to ensure correctness
- [ ] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence
2026-07-20 21:49:12 +00:00
Vaibhav Jindal e2c374b6f7 Add -m flag for modal runs (#1278)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->
Add -m flag for modal runs. Otherwise modal runs fail with 
```
Run modal run dev.modal.tests::liger_oldest_v4_correctness_tests
╭─ Error ──────────────────────────────────────────────────────────────────────╮
│ Python module paths must be specified with the -m flag. Use `modal run -m    │
│ dev.modal.tests` instead.                                                    │
╰──────────────────────────────────────────────────────────────────────────────╯
```

Also, updated secrets with a temporary modal secrets to test things out.

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [ ] run `make test` to ensure correctness
- [ ] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence
2026-06-29 23:03:51 +00:00
Vaibhav JindalandClaude Opus 4.7 80f19ad741 [Megatron] Add vocab-parallel cross-entropy (TP>1 support) (#1260)
## Summary

Adds TP>1 (vocab-parallel) support to Liger's Megatron-LM cross-entropy.
The previous PR (#1207) shipped CE for TP=1 only. At TP>1, each rank
holds a slice of the vocabulary and the loss needs cross-rank
all-reduces to be correct. This PR writes the vocab-parallel kernel and
wires it through `LigerMegatronCrossEntropy` so the same one-line monkey
patch now works at **every** TP size — TP=1, 2, 4, 8, ...

Single code path. AllReduces are no-ops at TP=1 and real NCCL
collectives at TP>1.

Two new public Module classes — both backed by the same kernel:

- **`LigerVocabParallelCrossEntropy`** (`liger_kernel.transformers`) —
general-purpose vocab-parallel CE. Defaults match
`torch.nn.functional.cross_entropy` semantics.
- **`LigerMegatronCrossEntropy`** (`liger_kernel.megatron`) — same
kernel, Megatron-LM defaults. Bit-identical to Megatron's
`vocab_parallel_cross_entropy`.

Label smoothing supports both PyTorch's α/K formula and Megatron's NeMo
α·V/(V−1) formula; pick via `label_smoothing_formula="pytorch"` or
`"megatron"`. At TP>1 the averaging scope is selectable too:
`label_smoothing_mode="global"` (exact, one extra AllReduce) or
`"partition"` (bit-identical to Megatron, no extra AllReduce, per-rank
loss may differ).

### Future Work
- Comparison with TE kernels
- exp in bf16

## 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)
```

Defaults of `LigerMegatronCrossEntropy` match Megatron's vocab-parallel
CE bit-for-bit, so users monkey-patching from Megatron see zero
numerical change.

### Mode 2: Use the classes directly

```python
# Drop-in for Megatron's vocab_parallel_cross_entropy:
from liger_kernel.megatron import LigerMegatronCrossEntropy
ce = LigerMegatronCrossEntropy(ignore_index=-100, label_smoothing=0.0)
loss = ce(vocab_parallel_logits, target, tp_group=my_tp_group)

# Or, general-purpose vocab-parallel CE with PyTorch / F.cross_entropy semantics:
from liger_kernel.transformers import LigerVocabParallelCrossEntropy
ce = LigerVocabParallelCrossEntropy(ignore_index=-100, label_smoothing=0.0)
loss = ce(vocab_parallel_logits, target, tp_group=my_tp_group)
```

## Scope

In scope:
- Vocab-parallel CE for TP=1, 2, 4, 8, … (any TP size).
- Both label-smoothing formulas + both averaging modes at TP>1.

---

## Performance — Liger vs Megatron's vocab-parallel CE

Measured on **8× NVIDIA H100 80GB HBM3** at the Megatron training shape
`S=2048, B=4, bf16`. Compared against `megatron-core==0.19.0+96a894ef6`:

- `megatron-fused`: Megatron's *fused*
`fused_vocab_parallel_cross_entropy` (TorchScript JIT via `@jit_fuser`)
- `megatron-unfused`: Megatron's *unfused*
`vocab_parallel_cross_entropy` (eager Python; the path with runtime
`label_smoothing` support)

### Headline: Liger vs Megatron-fused at production V (≥32K)

|  | Forward | Backward (incl. fwd) | Full (fwd+bwd) |
|---|---|---|---|
| **TP=1** at V=128K  | **+298%** | +129% | +129% |
| **TP=2** at V=128K  | +274% | +123% | +123% |
| **TP=4** at V=128K  | +239% | +114% | +114% |
| **TP=8** at V=128K  | +210% | +108% | +108% |
| **TP=8** at V=32K   | +21%  | +24%  | +24%  |

At small `V_local` (high TP × small V) the kernels become tiny and
Python orchestration dominates; even there Liger is at-worst tied with
Megatron-fused, never significantly slower. At production-scale V (32K
and up), Liger is **2–4× faster on forward** and **~2× faster on
backward** at every TP we tested.

vs `megatron-unfused`: Liger is **30–430% faster** at every (TP, V)
combination — that path doesn't JIT-fuse anything.

### Per-TP plots

<img width="1680" height="1200" alt="megatron_cross_entropy_tp1_panel"
src="https://github.com/user-attachments/assets/06142823-835a-43e9-9fa9-53c6a0c7ae14"
/>
<img width="1680" height="1200" alt="megatron_cross_entropy_tp2_panel"
src="https://github.com/user-attachments/assets/82389842-9f83-4ce7-8972-a486757846f4"
/>
<img width="1680" height="1200" alt="megatron_cross_entropy_tp4_panel"
src="https://github.com/user-attachments/assets/fdc74163-f5af-46e9-904d-aa8ac9ceaeb3"
/>
<img width="1680" height="1200" alt="megatron_cross_entropy_tp8_panel"
src="https://github.com/user-attachments/assets/fff82bf2-b6ab-4635-910a-cde9ffef58e8"
/>


(Drag-and-drop the 4 PNGs at
`/tmp/megatron_ce_panels/megatron_cross_entropy_tp{1,2,4,8}_panel.png`
into the PR description here. Each PNG has forward / backward / full
speed + memory in one 2×2 view.)

### Why is Liger faster?

H100 CE is **memory-bandwidth-bound**, not compute-bound. At V=128K the
input logits tensor is ~2GB of bf16; each full read or write of it is
~0.7ms on HBM3, and the runtime is dominated by how many times you touch
that buffer.

**Megatron's fused (TorchScript) path does it in stages**, with each
stage being its own JIT-fused kernel that allocates a new buffer:
- `calculate_logits_max` casts the input bf16 → fp32 (writes 4GB), then
computes max
- `calculate_predicted_logits` shifts in-place (read 4GB + write 4GB),
then exps in-place (read 4GB + write 4GB), then gathers predicted and
sum_exp
- → roughly **4 full reads + 3 full writes** of the V_local-sized buffer

**Liger does it in ONE pass.** Kernel 1 reads input bf16, casts fp32 in
registers, subtracts the global max in registers, exps in registers,
writes the exp buffer (fp32) once, and accumulates `sum_exp` +
`predicted_logit` as scalars on the way through. Kernel 2 (backward)
does symmetrically one read + one write. AllReduces happen on tiny
`[BT]`-shaped tensors between kernels at TP>1.

That's ~3.5× less memory traffic — which matches the observed 3-4×
forward speedup almost exactly. The backward speedup (~2×) is smaller
because the backward is fundamentally one-pass-shaped for both
implementations; the win there is fusing the `grad_output` scaling into
the same kernel as the gradient computation instead of doing it as a
separate buffer mul.

---

## Memory — Liger vs Megatron

**What "memory" means at TP>1.** The number we report is
`torch.cuda.max_memory_allocated()` on rank 0 — peak HBM usage on a
**single GPU** during one fwd+bwd CE call. At TP=N each rank holds only
`V_global/N` of the vocab, so per-rank memory **scales as 1/N**. All
ranks have identical memory profiles (vocab is partitioned evenly), so
rank 0 represents every rank. It is **not** "aggregate across all GPUs"
— each GPU is independently HBM-bound, so per-rank is the metric that
matters for fitting a model on a node.

### Numbers (MB per rank — Liger / Megatron-fused / Megatron-unfused)

| V | TP=1 | TP=2 | TP=4 | TP=8 |
|---|---|---|---|---|
| 4K | 256 / 384 / 256 | 128 / 192 / 128 | 64 / 96 / 64 | 32 / 48 / 32 |
| 16K | 1024 / 1536 / 1024 | 512 / 768 / 512 | 256 / 384 / 256 | 128 /
192 / 128 |
| 32K | 2048 / 3072 / 2048 | 1024 / 1536 / 1024 | 512 / 768 / 512 | 256
/ 384 / 256 |
| 128K | 8192 / 12288 / 8192 | 4096 / 6144 / 4096 | 2048 / 3072 / 2048 |
1024 / 1536 / 1024 |

**Two takeaways:**

1. **Liger uses 33% less per-rank HBM than Megatron-fused at every (TP,
V) point.** That's 512 MB freed at V=128K/TP=8 — and the same 512 MB
savings replicates across all 8 ranks. Usable for longer sequences,
larger batches, or activation checkpointing.

2. **Liger uses exactly the same memory as Megatron-unfused.** Both save
a single `[BT, V_local]` fp32 softmax buffer for backward — that's the
irreducible lower bound for vocab-parallel CE. The plot annotates this
overlap explicitly.

### Why is Megatron-fused 50% heavier than Liger / Megatron-unfused?

Both Liger and Megatron-unfused save one `[BT, V_local]` fp32 softmax
buffer for backward. Megatron-fused saves the same buffer, but its
`@jit_fuser` TorchScript scope holds **captured input tensors alive
until the function returns** — so during forward the bf16 input AND the
fp32 working buffer are both resident in HBM, peaking at ~1 extra `[BT,
V_local]` buffer's worth. The 50% premium is consistent across V and TP.

Liger avoids this entirely: the bf16 input is read in-register inside
Kernel 1 and never replicated to a separate fp32 buffer in HBM.
Megatron-unfused avoids it because eager Python rebinds the local
variable after `.float()`, letting the bf16 input be GC'd.

So Liger and unfused arrive at the same lower bound through different
routes (Triton single-pass kernel vs eager Python ops), and
Megatron-fused pays a 50% premium in exchange for TorchScript fusion
that — as the speed numbers show — doesn't actually pay off vs
hand-written Triton.

---

## Testing

All unit tests live in `test/megatron/`.

### Single-GPU tests

```bash
python -m pytest test/megatron/test_cross_entropy.py --no-cov -k "not tp_vocab_parallel"
python -m pytest test/megatron/test_monkey_patch.py --no-cov
```

Covers:
- `LigerVocabParallelCrossEntropy` correctness — fwd + bwd parity vs
`F.cross_entropy` across shape sweeps, fp32 / bf16, `ignore_index`,
`label_smoothing`, large logit magnitudes, out-of-bounds targets,
CE-not-last-layer, `torch.no_grad()`.
- `LigerMegatronCrossEntropy` correctness — same matrix, with
`label_smoothing > 0` parity referenced against
`F.cross_entropy(label_smoothing=α·V/(V-1))` which is algebraically
equivalent to Megatron's NeMo formula at TP=1.
- Config plumbing: validation of `reduction`, `label_smoothing_formula`,
`label_smoothing_mode`, `label_smoothing` range; 3-D shape guard.
- Input-preservation invariant: forward must NOT mutate the caller's
input tensor even when fp32 (regression test for in-place ops aliasing).
- Monkey-patch plumbing: symbol replacement for both fused / unfused CE
paths, idempotency, missing-megatron errors, runtime `label_smoothing`
override on the unfused path, end-to-end correctness through patched
symbols.

77 single-GPU CE tests + 35 monkey-patch tests = 112 single-GPU tests,
all passing.

### Multi-GPU TP>1 parity tests

```bash
python -m pytest test/megatron/test_cross_entropy.py --no-cov -k "tp_vocab_parallel"
```

Parametrized over `tp_size ∈ {2, 4, 8}` with per-tp_size
`skipif(torch.cuda.device_count() < tp_size, ...)`. 48 tests total
covering the full cross-product `formula × mode × label_smoothing ×
shape`. The right subset runs based on available GPUs (2 GPUs: 16 run,
32 skip; 8 GPUs: all 48 run).

Each test broadcasts the same full-vocab logits to every rank, has each
rank slice out its `V_local`, runs Liger's vocab-parallel CE, and
compares forward + backward against a single-rank `F.cross_entropy`
reference on the full logits (with α rescaled for the Megatron formula).

### Style

```bash
make checkstyle
```

Clean.

---

## Benchmarking

Single script handles all TP sizes via `--tp-size`. Uses `mp.spawn`
internally and allocates `tp_size` GPUs.

```bash
python benchmark/scripts/benchmark_megatron_cross_entropy.py --tp-size 1 --overwrite
python benchmark/scripts/benchmark_megatron_cross_entropy.py --tp-size 2 --overwrite
python benchmark/scripts/benchmark_megatron_cross_entropy.py --tp-size 4 --overwrite   # 4 GPUs
python benchmark/scripts/benchmark_megatron_cross_entropy.py --tp-size 8 --overwrite   # 8 GPUs

# Render per-TP plots from the CSV
python benchmark/benchmarks_visualizer.py \
    --kernel-name megatron_cross_entropy --metric-name speed --overwrite \
    --extra-config-filter '"TP": 1'
```

Note: the committed CSV intentionally contains **only TP=1 rows** for
`megatron_cross_entropy` to keep the data file small. Re-run at TP=2/4/8
to repopulate the rows for those TPs if you want to validate the numbers
in the headline table above.

## Testing Done

- Hardware Type: H100 80GB HBM3 × 8
- [x] `python -m pytest test/megatron/ --no-cov` — passes (77 single-GPU
CE + 35 monkey-patch + N multi-GPU CE depending on `cuda.device_count()`
+ 13 RMSNorm)
- [x] `python -m pytest test/transformers/test_cross_entropy.py --no-cov
-k "test_correctness and not z_loss"` — 136 passed (existing CE not
regressed)
- [x] `make checkstyle` — clean
- [x] Full TP=1/2/4/8 benchmark sweep on 8×H100 — numbers committed
inline above; 4-panel plots attached
- [ ] `make test-convergence`

---------

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
2026-06-16 19:10:30 +00:00
Vaibhav JindalandClaude Opus 4.7 27fe47e2ee [Megatron] Add RMSNorm benchmark + measured H100 results (#1257)
## Summary

Adds `benchmark/scripts/benchmark_megatron_rms_norm.py` — the RMSNorm
parallel to the Megatron cross-entropy benchmark landed in #1207.
Provides empirical speed/memory numbers so we can point at concrete data
when claiming RMSNorm wins, instead of just citing the kernel sweep.

Output goes to the shared `benchmark/data/all_benchmark_data.csv`
(tagged `kernel_name="megatron_rms_norm"`), so the standard visualizer
renders the plots:

```bash
python benchmark/benchmarks_visualizer.py \
    --kernel-name megatron_rms_norm --metric-name speed
python benchmark/benchmarks_visualizer.py \
    --kernel-name megatron_rms_norm --metric-name memory
```

## Providers compared

- **liger** — `LigerMegatronRMSNorm` (Liger's Triton RMSNorm via the
Megatron-shaped wrapper from #1254)
- **torch** — vanilla `torch.nn.RMSNorm`
- **megatron** — Megatron's `WrappedTorchNorm` — its `__new__` returns
`torch.nn.RMSNorm`, so timings should match `torch` exactly. Included
for explicit parity confirmation since `WrappedTorchNorm` is the symbol
Liger displaces in the local-backend path.

If `megatron-core` is not installed, the `megatron` provider is silently
dropped and the run proceeds with `liger` + `torch`.

## Results on H100 80GB (S=4096, B=1, bf16)

60 rows committed in the CSV (5 hidden sizes × 3 providers × 4
measurements).

### Speed — forward

| H     | liger    | torch    | speedup |
|-------|----------|----------|---------|
| 1024  | 0.012 ms | 0.013 ms | ~flat   |
| 2048  | 0.018 ms | 0.019 ms | ~6%     |
| 4096  | 0.029 ms | 0.033 ms | ~12%    |
| 8192  | 0.051 ms | 0.074 ms | ~31%    |
| 16384 | 0.095 ms | 0.149 ms | **~36%** |

Liger forward wins, and the gap widens with hidden size.

### Speed — full (fwd + bwd)

| H     | liger   | torch    | notes                                  |
|-------|---------|----------|----------------------------------------|
| 1024  | 0.30 ms | 0.075 ms | Liger SLOWER (Triton launch overhead)  |
| 4096  | 0.31 ms | 0.20 ms  | Liger slower                           |
| 8192  | 0.32 ms | 0.40 ms  | crossover                              |
| 16384 | 0.59 ms | 0.77 ms  | **~24% faster**                        |

The flat Liger curve at small H is the giveaway: kernel launch overhead
dominates. `nn.RMSNorm`'s backward is a single fused C++/CUDA kernel;
Liger's backward launches multiple Triton kernels (dx + dw reduction +
element_mul). At small H the actual compute is tiny relative to
per-launch overhead, so the math wins from Triton get drowned. At H ≥
~6K the compute dominates and Liger wins.

### Memory — full

Approximately neutral across all H — Liger uses 0.5–1% more than torch,
within measurement noise.

`nn.RMSNorm` is already a single fused CUDA kernel with minimal
intermediates, so Liger doesn't get the activation-memory win it gets
when replacing eager-PyTorch RMSNorm (which materializes variance +
rsqrt + scale separately). Speed is the win in this comparison, not
memory.

### Parity sanity check

`torch` and `megatron` rows are bit-identical across the entire sweep —
exactly what we'd expect since `WrappedTorchNorm` is a factory that
returns `nn.RMSNorm`. Confirms Liger is replacing the right baseline.

## Honest read

Production LLMs run at H ≥ 4096. At those sizes:
- Forward: Liger wins ~12–36%
- Full: Liger wins from ~H=6K onward
- Memory: neutral

So Liger's RMSNorm is a real speed improvement for typical training
shapes. The tiny-H regression on full is launch overhead, not a
numerical/math issue.

## Plots

### Memory
<img width="1000" height="600"
alt="megatron_rms_norm_memory_full_token_length"
src="https://github.com/user-attachments/assets/168a8783-bbeb-4467-ab65-2e54a28d2882"
/>

### Backward speed
<img width="1000" height="600"
alt="megatron_rms_norm_speed_backward_token_length"
src="https://github.com/user-attachments/assets/31501a5f-082e-47f7-a596-760a5add9de7"
/>

### Forward speed
<img width="1000" height="600"
alt="megatron_rms_norm_speed_forward_token_length"
src="https://github.com/user-attachments/assets/b5084441-8011-473a-b9b4-9ff67bcee7c4"
/>

### Forward + Backward speed
<img width="1000" height="600"
alt="megatron_rms_norm_speed_full_token_length"
src="https://github.com/user-attachments/assets/5504491d-e15a-42b4-9c02-e5a3d75a781f"
/>


## Testing Done

- Hardware Type: H100 80GB HBM3
- [x] Benchmark runs end-to-end on H100 with all 3 providers active
- [x] Standard visualizer renders all 4 PNGs (3 speed modes + memory)
from the shared CSV
- [x] `torch` / `megatron` providers produce bit-identical numbers
(parity confirmation)
- [x] `make checkstyle` passes

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
2026-06-10 21:23:01 +00:00
Vaibhav Jindal e06ee2ce44 Add Megatron-Core integration for RMSNorm (#1254)
## Summary

Adds first-class Megatron-Core support for Liger's Triton RMSNorm with
**two adoption modes**, both reaching **5/5 norm-slot coverage** on a
standard GPT-style model. No changes are required on Megatron-LM — this
PR works against upstream `megatron-core`.

**Note** - The kernel works for TP, and should ideally not break for SP,
but the behavior needs to verified end-to-end. Marked as a TODO in
`rms_norm.py`.

### Background — Megatron has *two* norm slots, not one

A GPT-style Megatron decoder has `2 × num_layers + 1` RMSNorm modules:

- `decoder.layers[i].input_layernorm` and
`decoder.layers[i].pre_mlp_layernorm` — populated from the per-layer
`TransformerLayerSubmodules` dataclass.
- `decoder.final_layernorm` — populated from a *separate*
`TransformerBlockSubmodules.layer_norm` field, which defaults to a
module-level global `LayerNormImpl` in `transformer_block.py` (TE / Apex
/ `WrappedTorchNorm`, chosen at import time).

To cover all norms, the integration must hit both code paths. Both modes
below do.

### Mode 1 — monkey-patch (one line, all GPT-family models, 5/5 norms)

```python
from liger_kernel.megatron import apply_liger_kernel_to_megatron

apply_liger_kernel_to_megatron(rms_norm=True)

# Then build your model normally — every RMSNorm slot is now Liger.
gpt_model = GPTModel(
    config=cfg,
    transformer_layer_spec=get_gpt_layer_local_spec(normalization="RMSNorm"),
    ...,
)
```

The call rewrites two sites:

1. `megatron.core.models.backends.LocalSpecProvider.layer_norm` →
returns `LigerMegatronRMSNorm` when `rms_norm=True`. Covers
`input_layernorm`, `pre_mlp_layernorm`, and (if `qk_layernorm=True`)
`q_layernorm` / `k_layernorm`.
2. `megatron.core.transformer.transformer_block.LayerNormImpl` →
replaced with a small adapter that routes to `LigerMegatronRMSNorm` when
`config.normalization == "RMSNorm"` and falls back to the original
`WrappedTorchNorm` otherwise. Covers `final_layernorm` without
disturbing LayerNorm users.

Both patches:
- Only displace the **`WrappedTorchNorm` fallback**. Users on TE or Apex
chose those deliberately — replacing them would undo TE's
`TELayerNormColumnParallelLinear` fusion or surprise Apex users.

Because both patches target the `BackendSpecProvider` / `LayerNormImpl`
abstractions — not individual model files — **a single call covers every
model that routes through Megatron's standard spec system** (GPT-3,
Llama-N, Mixtral, Nemotron, MLA, any `TransformerLayer`-based model). No
per-model code, in contrast to HF where every `modeling_<x>.py` needs
its own `apply_liger_kernel_to_<x>()`.

### Mode 2 — hand-assembled `TransformerBlockSubmodules` (slot-level
surgical control, 5/5 norms)

```python
from megatron.core.transformer.spec_utils import ModuleSpec
from megatron.core.transformer.transformer_block import TransformerBlockSubmodules
from megatron.core.transformer.transformer_layer import TransformerLayer, TransformerLayerSubmodules
from liger_kernel.megatron import LigerMegatronRMSNorm

per_layer = ModuleSpec(
    module=TransformerLayer,
    submodules=TransformerLayerSubmodules(
        input_layernorm=LigerMegatronRMSNorm,    # Liger
        self_attention=...,                      # TE here if you want
        pre_mlp_layernorm=LigerMegatronRMSNorm,  # Liger
        mlp=...,
    ),
)
block_spec = TransformerBlockSubmodules(
    layer_specs=[per_layer] * num_layers,
    layer_norm=LigerMegatronRMSNorm,             # ← covers final_layernorm
)
gpt_model = GPTModel(config=cfg, transformer_layer_spec=block_spec, ...)
```

Useful when mixing Liger with TransformerEngine per slot (e.g. "TE for
everything except norms"). Verbose but maximally explicit. Passing a
`TransformerBlockSubmodules` instead of a bare per-layer `ModuleSpec` is
the only way to reach `final_layernorm` without monkey-patching —
Megatron auto-promotes a per-layer spec via its module-level
`LayerNormImpl` global, which would otherwise leave the trailing norm on
PyTorch's `nn.RMSNorm`.

## What's added

| File | Purpose |
|---|---|
| `src/liger_kernel/megatron/rms_norm.py` | `LigerMegatronRMSNorm` —
conforms to Megatron's `LayerNormBuilder` protocol (`config,
hidden_size, eps`). Reads `config.normalization` (asserts `"RMSNorm"`),
`config.sequence_parallel`, `config.layernorm_zero_centered_gamma`. Sets
`weight.sequence_parallel` for the dist optimizer. `in_place=False`
hardcoded for Megatron's recompute / CUDA-graph safety. No
`megatron-core` import — duck-typed config. |
| `src/liger_kernel/megatron/monkey_patch.py` |
`apply_liger_kernel_to_megatron(rms_norm=True)` — patches both
`LocalSpecProvider.layer_norm` and `transformer_block.LayerNormImpl`.
Idempotent. |
| `src/liger_kernel/megatron/__init__.py` | Re-exports
`LigerMegatronRMSNorm`, `apply_liger_kernel_to_megatron`. |
| `examples/megatron/run_mode1_monkey_patch.py` | End-to-end runnable
script demonstrating Mode 1. |
| `examples/megatron/run_mode2_hand_spec.py` | End-to-end runnable
script demonstrating Mode 2. |
| `examples/megatron/README.md` | Prerequisites + run instructions +
expected output. |
| `test/megatron/test_rms_norm.py` | 10 unit tests against
`torch.nn.RMSNorm` reference. |

## How to reproduce

Two self-contained scripts live in this PR under
[`examples/megatron/`](examples/megatron). Each is an end-to-end
training run (TP=2, mock GPT data, 5 iterations, distributed-checkpoint
round-trip) that prints the full module tree and the resolved norm class
at every slot so reviewers can verify Liger took over. Both adapt
Megatron's own `examples/run_simple_mcore_train_loop.py`.

```bash
# Mode 1 — apply_liger_kernel_to_megatron(rms_norm=True)
torchrun --nproc_per_node=2 --master_addr=127.0.0.1 --master_port=29500 \
    examples/megatron/run_mode1_monkey_patch.py

# Mode 2 — hand-assembled TransformerBlockSubmodules
torchrun --nproc_per_node=2 --master_addr=127.0.0.1 --master_port=29500 \
    examples/megatron/run_mode2_hand_spec.py
```

The Liger-specific lines in each script are flagged with `↓↓ Mode N ↓↓`
/ `↑↑ ↑↑` comment fences so reviewers can scan straight to the
integration code without reading the boilerplate.

See [`examples/megatron/README.md`](examples/megatron/README.md) for
prerequisites and expected output (5 lines of loss + 5 lines of
`LigerMegatronRMSNorm` slots + `Successfully loaded the model`).

## Megatron feature compatibility

**Verified:** TP (tested at TP=2), distributed checkpointing (save/load
round-trip in the harness), fp32 + bf16 (unit-tested), recompute paths
(`in_place=False` keeps activation references valid), `qk_layernorm`
slots, and `final_layernorm` (covered in both modes — mode 1 via
`transformer_block.LayerNormImpl`, mode 2 via
`TransformerBlockSubmodules.layer_norm`).

**Should work, not yet E2E-verified:** SP (wrapper sets
`weight.sequence_parallel`; notably the local baseline
`WrappedTorchNorm` asserts SP off, so this is a strict improvement), CP,
`use_distributed_optimizer=True`, CUDA-graph capture. Positive tests for
these are tracked as follow-up.

**Out of scope:** FP8 / FP4 (TE owns that path), the TE backend, and the
Apex backend — the patches only displace the `WrappedTorchNorm`
fallback. Users on TE or Apex keep their existing norms.
## Known issues / follow-ups

1. **SP / CP / dist-opt / CUDA-graph not end-to-end verified.** Should
work by construction; need positive tests before production claims.
2. **Pre-existing torch 2.11 compatibility issue in
`liger_kernel.ops.rms_norm`.** It references
`torch.distributed.tensor.DTensor` without forcing the submodule import.
Worked around in our wrapper with an explicit `import
torch.distributed.tensor` — proper fix is a one-line change to
`ops/rms_norm.py`, intentionally not bundled here.
3. **Performance not measured.** This PR establishes correctness,
integration shape, and full norm coverage. Throughput / memory
benchmarks on a realistic hidden size (Llama-7B class, hidden=4096+) are
the natural next step.
4. **Convergence tests need to be added**
## Testing Done

- Hardware Type: NVIDIA H100 80GB (2×)
- [x] run `make test` to ensure correctness
- [x] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence
2026-06-09 20:25:03 +00:00
Vaibhav Jindal bbbafc9112 Remove dead lce_forward_deprecated path from qwen2 (#1241)
## Summary
Cleaning up lce_forward_deprecated in qwen2.py as it is not being used
anywhere.


## Testing Done

* Hardware Type: NVIDIA GeForce RTX 4090
* [ ] run `make test` to ensure correctness
* [x] run `make checkstyle` to ensure code style
* [ ] run `make test-convergence` to ensure convergence
2026-05-26 21:08:56 +00:00
Vaibhav JindalandVaibhav Jindal 809baf44ed Make skills vendor-agnostic via .agents/skills + symlink (#1226)
Move .claude/skills to .agents/skills as the canonical (vendor-neutral)
location, with .claude/skills now a symlink so Claude Code still
auto-discovers them. This mirrors the pattern NVIDIA TileGym uses.

Skill content updates:
- SKILL.md stage intros rephrased from "Spawn an X agent" to "Follow the
X workflow in <file>", which describes the workflow without mandating
any single runtime's subagent mechanism. A note explains that runtimes
with parallel subagent support may still delegate.
- finalizer.md commit/PR templates no longer hardcode a Claude-specific
Co-Authored-By trailer or "Generated with Claude Code" footer; runtime
attribution is left to the host runtime's own convention.

Adds a root-level AGENTS.md (a convention picked up by Codex, Cursor,
Aider, Gemini CLI, etc.) pointing readers to .agents/skills/ and
summarising the three skills.

## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [ ] run `make test` to ensure correctness
- [ ] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence

Co-authored-by: Vaibhav Jindal <[email protected]>
2026-05-15 19:59:44 +00:00
Vaibhav JindalandClaude Opus 4.6 b7025a7027 Improve bwd kernel for fused_add_rms_norm (#1187)
## Summary
This has been generated using the `liger-kernel-perf` skill introduced
in #1185 .

Restructure the backward kernel of `fused_add_rms_norm` to reduce peak
register liveness, dropping register count from **115 → 95 per thread**
on H100. This eliminates a performance cliff at large hidden sizes and
yields up to **70% backward speedup** at H=16384.

## Root Cause Analysis

NCU profiling on H100 revealed the backward kernel was severely
underutilizing the GPU:

| Metric | Forward Kernel | Backward Kernel |
|--------|---------------|-----------------|
| Registers/Thread | 40 | **115** |
| Theoretical Occupancy | 75% | **25%** |
| Achieved Occupancy | 67.87% | **12.49%** |
| Blocks/SM (register-limited) | 6 | **2** |
| Waves/SM | 5.17 | **0.50** |
| Memory Throughput (% peak) | 78.24% | 60.88% |
| Compute Throughput (% peak) | 27.64% | 19.87% |

The H100 has 65,536 registers per SM. With 256 threads/block (8 warps x
32 threads), each thread using 115 registers consumes 115 x 256 = 29,440
registers per block. This means only `floor(65536 / 29440) = 2` blocks
can run concurrently per SM, yielding just 12.5% occupancy. With so few
active warps, the GPU cannot overlap memory accesses with computation —
stalling 77.5% of scheduler slots.

### Why so many registers?

The backward kernel processes one row per loop iteration, maintaining
several BLOCK_SIZE-wide vectors simultaneously in registers. With
BLOCK_SIZE=4096 and 256 threads, each thread handles 16 elements per
vector, requiring 16 registers per vector. The original code had up to
**~8 vectors live simultaneously** at peak:

| Vector | Lifetime | Purpose |
|--------|----------|---------|
| `W_row` | Persistent (loop-invariant) | Weight + offset |
| `dW_row` | Persistent (accumulator) | Weight gradient |
| `col_offsets` | Persistent | Column indices |
| `mask` | Persistent | Bounds mask |
| `dY_row` | Per-iteration | Upstream gradient |
| `X_row` | Per-iteration | Saved residual (fp32) |
| `m` | Per-iteration | `dY * W` intermediate |
| `dX_row` | Per-iteration | Output gradient |
| `dS_out_row` | Per-iteration (conditional) | Residual stream gradient
|

At 16 registers per vector x 8 vectors = 128 registers, plus
scalars/pointers/loop vars, this matches the observed 115 registers
(compiler optimizes some away).

## Changes

### 1. Reorder: compute dW before dX

**Before:** The original code computed `m -> dX -> dW -> store dX`. This
meant `dY_row`, `m`, `X_row`, `dX_row`, and `dW_row` were all live
simultaneously during the dW accumulation.

**After:** By computing `dW` first (`dW -> m -> dX -> store dX`), the
compiler can reuse `dY_row`'s registers when computing `m = dY_row *
W_row`, since `dY_row` is no longer needed independently after the dW
step. This is safe because both `dW` and `m` read `dY_row` — they don't
modify it (except in GEMMA mode where the cast happens after dW).

### 2. Factor the dX formula with a precomputed scalar

**Before:**
```python
dX_row = rstd_row * m
dX_row += (rstd_row) * (-(1 / n_cols) * rstd_row * rstd_row * tl.sum(m * X_row, axis=0) * X_row)
```

This expression keeps `m`, `X_row`, and the intermediate `rstd * (... *
X_row)` vector all live while computing the compound expression.

**After:**
```python
dot = tl.sum(m * X_row, axis=0)                              # scalar reduction
c = -(1.0 / n_cols) * rstd_row * rstd_row * rstd_row * dot   # scalar
dX_row = rstd_row * m + c * X_row                             # single fused expression
```

By precomputing `c` as a scalar, the final `dX_row` expression is a
simple element-wise combination of two vectors (`m` and `X_row`) scaled
by scalars. The compiler doesn't need to keep intermediate vector
results alive — it can compute each element of `dX_row` independently,
allowing better register reuse.

### 3. Defer dS_out load

**Before:** `dS_out_row` was loaded and added to `dX_row` inside the
compound correction expression, contributing to peak liveness.

**After:** `dS_out_row` is loaded only after the `dX_row = rstd * m + c
* X` computation is complete. By this point, `m` registers are freed, so
`dS_out_row` reuses them rather than competing for register space.

### 4. Add `num_stages=2` for Hopper software pipelining

**Rationale:** The current code never sets `num_stages`, so Triton
defaults to `num_stages=1` (no software pipelining). On H100 (Hopper
architecture), setting `num_stages >= 2` enables the compiler to overlap
memory loads for the next iteration with computation for the current
iteration using Hopper's async copy engine (TMA).

A parameter sweep confirmed `num_stages=2` is safe across all hidden
sizes with no regressions. Higher values (3-4) showed no additional
benefit for this kernel because the backward kernel's low occupancy
limits the pipeline depth that can be effectively utilized.
`num_stages=2` was chosen as the conservative optimal: it enables basic
pipelining without increasing register pressure (higher stages require
more pipeline buffer registers).

The `num_stages` value is passed through `ctx` from forward to backward,
following the same pattern as `BLOCK_SIZE` and `num_warps`.

### Combined effect

After these changes, the peak register liveness drops to ~5-6 vectors:

| Vector | Still live? |
|--------|------------|
| `W_row` | Yes (persistent) |
| `dW_row` | Yes (persistent) |
| `col_offsets` | Yes (persistent) |
| `mask` | Yes (persistent) |
| `dY_row` / `m` | **Shared** (compiler reuses registers) |
| `X_row` / `dX_row` | **Shared** (compiler reuses after dot product) |
| `dS_out_row` | Loaded late, **reuses freed `m` registers** |

NCU confirmed registers dropped from **115 → 95 per thread** (17%
reduction). While this doesn't cross the 85-register threshold needed
for 3 blocks/SM, the reduced pressure gives the compiler more freedom
for instruction scheduling and ILP (instruction-level parallelism),
which explains the dramatic speedup especially at H=16384 where the
original had extreme register spilling.

## Benchmark Results

**NVIDIA H100 80GB HBM3, M=2048, dtype=float32**

### Speed

| Mode | H | Before (ms) | After (ms) | Improvement |
|------|---|------------|-----------|-------------|
| Backward | 2048 | 0.124 | 0.096 | **23% faster** |
| Backward | 4096 | 0.124 | 0.101 | **18% faster** |
| Backward | 8192 | 0.153 | 0.154 | ~same |
| Backward | 16384 | 1.006 | 0.300 | **70% faster (3.35x)** |
| Backward | 32768 | 1.390 | 1.119 | **20% faster** |
| Full | 16384 | 1.186 | 0.482 | **59% faster** |
| Full | 32768 | 1.819 | 1.550 | **15% faster** |
| Forward | all sizes | - | - | unchanged |

### Memory

No change at any size (~30% savings vs HuggingFace maintained).

### Plots

**Backward Speed** (before / after / HuggingFace):

<!-- Paste backward speed plot here -->

**Full Pass Speed**:

<!-- Paste full pass speed plot here -->

**Forward Speed** (unchanged):

<!-- Paste forward speed plot here -->

**Memory** (unchanged):

<!-- Paste memory plot here -->

## Test plan

- [x] Full test suite passed: `python -m pytest
test/transformers/test_fused_add_rms_norm.py -xvs` (40/40 tests, all
casting modes + dtypes + shapes)
- [x] Checkstyle passed (ruff)
- [x] No memory regression at any hidden size
- [x] No forward pass regression
- [x] Benchmarked on NVIDIA H100 80GB HBM3
- [x] NCU profiling confirmed register reduction (115 → 95)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
2026-04-08 19:41:01 +00:00
Vaibhav JindalandClaude Opus 4.6 2bf7117bd9 [Skill] Add liger-kernel-perf skill for Triton kernel performance optimization (#1185)
## Summary

Adds a new Claude Code skill (`liger-kernel-perf`) that optimizes
existing Liger Kernel Triton kernels through a 3-stage pipeline: Profile
-> Optimize -> Finalize. The skill automatically diagnoses bottlenecks,
generates multiple optimization variants, benchmarks them against the
full suite, and creates a PR with the winning variant -- all while
maintaining correctness.

Tested on `fused_add_rms_norm` (see PR #1187), which achieved up to 70%
backward speedup on H100 via register pressure reduction.

## What the skill does

- **Stage 1 (Profiler):** Runs baseline benchmarks, detects GPU
architecture (Ampere/Hopper/Blackwell), optionally runs NCU profiling,
classifies bottleneck (memory-bound vs compute-bound), produces
optimization profile with recommended strategy order
- **Stage 2 (Optimizer):** Autonomous optimization loop -- tries
parameter tuning first (manual sweep, NOT @triton.autotune), then
diagnosis-driven techniques. Each variant gets a versioned file + lab
notebook tracking hypothesis/changes/results/learnings. Stops on budget
exhaustion, diminishing returns, or target met
- **Stage 3 (Finalizer):** Applies winner in-place, runs full test suite
(hard gate), generates 3-way comparison plots (original vs optimized vs
baseline), updates benchmark CSV, creates PR with descriptive body

## Key design decisions

- **No @triton.autotune**: Incompatible with Liger's forward-backward
ctx coupling pattern and NPU backends. Uses manual parameter sweeps
instead.
- **Full benchmark suite every iteration**: No lightweight shortcuts --
ensures optimization is good across ALL input sizes, not just
cherry-picked ones.
- **Balanced guardrails**: Rejects variants that regress non-target
metric >5% or regress one pass >10% to improve the other.
- **Comment preservation**: All existing comments kept, explanatory
comments added for every optimization change.
- **Autonomous + interactive modes**: Runs end-to-end when told "just
optimize it", or pauses at human checkpoints between stages.

## Files (7 files, ~2,100 lines)

| File | Lines | Purpose |
|------|-------|---------|
| `SKILL.md` | 116 | Orchestration, input parsing, pipeline flow,
guardrails |
| `profiler.md` | 156 | Stage 1: baseline benchmarks, GPU detection,
bottleneck diagnosis |
| `optimizer.md` | 395 | Stage 2: optimization loop with accumulated
learning |
| `finalizer.md` | 417 | Stage 3: apply, test, plot, update CSV, create
PR, report |
| `optimization-strategies.md` | 794 | Technique catalog: parameter
tuning, memory-bound, compute-bound, architecture-specific |
| `templates/optimization-profile.md` | 140 | Cross-stage contract
between Profiler and Optimizer |
| `templates/variant-notes.md` | 70 | Per-variant lab notebook format
for learning accumulation |

## Test plan

- [x] Skill triggers correctly on "optimize the X kernel" prompts
- [x] Tested end-to-end on `fused_add_rms_norm` (PR #1187)
- [x] Verified against Claude Code skill best practices (conciseness,
progressive disclosure, SKILL.md under 500 lines)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
2026-04-08 19:05:43 +00:00
Vaibhav JindalandClaude Opus 4.6 1c013e286c [Feature] Use Liger's Relu_Squared kernel for Nemotron models (#1176)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->
Use relu_squared in nemotron.
This PR is generated using the liger-autopatch skill and tests the
changes in #1177 .

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->
Class patching and instance patching of relu_squared function.

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: H100
- [x] run `make test` to ensure correctness
- [x] run `make checkstyle` to ensure code style
- [x] run `make test-convergence` to ensure convergence

---------

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
2026-04-01 18:37:55 +00:00
Vaibhav JindalandClaude Opus 4.6 f16c9f76f4 Improve liger-autopatch skill to enable modifying existing monkey-patches (#1177)
## Summary

Extends the `liger-autopatch` Claude Code skill to support **modifying
existing monkey-patches**, not just adding new models. Previously, the
skill only triggered for new model support, so modification tasks (e.g.,
adding `LigerReLUSquared` to nemotron in #1172) bypassed the skill
entirely and missed its guidelines.

Changes to 2 skill files:

**SKILL.md:**
- Broadened skill description to trigger on modification-related
keywords (update, fix, change, extend, etc.)
- Added **Mode Detection** section that routes to create vs modify
pipelines
- Added **Modify Pipeline** with 3 stages: Change Impact Analysis →
Apply Changes → Validate
- Validate stage lists all convergence test commands explicitly
(including multimodal)

**code-generator.md:**
- Added **Mode** section distinguishing create vs modify mode
- Added **Modification Checklist** with 7 rules (R1-R7):
  - R1: Both patching levels (class-level + instance-level)
  - R2: New parameter with default
  - R3: Update docstring
  - R4: Update tests
  - R5: Check revert function in test/utils.py
  - R6: Run convergence tests (all 6 files including multimodal)
  - R7: Update README.md
- Added **Common Modification Patterns** for: adding activation kernels,
adding norm variants, fixing missing instance patching, updating for
upstream HF changes

## Testing Done

Tested by asking Claude to add `relu_squared` to nemotron's existing
monkey-patch. Verified the skill triggers in modify mode and follows the
modification checklist.

- Hardware Type: N/A (skill files only, no code changes)
- [x] run `make test` to ensure correctness
- [x] run `make checkstyle` to ensure code style
- [x] run `make test-convergence` to ensure convergence

---------

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
2026-04-01 18:35:13 +00:00
Vaibhav JindalandClaude Opus 4.6 8e6232a08f Add Liger Kernel support for Ministral models (#1166)
This PR was generated automatically by Claude Code with minimal human
intervention. Claude uses the skill written in #1167 to generate this PR
and test it out.

Ministral (model_type: "ministral") is a separate model type in
transformers v5+ with its own MinistralForCausalLM class. This adds full
Liger Kernel optimization support including RoPE, RMSNorm, SwiGLU,
CrossEntropyLoss, and FusedLinearCrossEntropy.

## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->

- Adds full Liger Kernel optimization for Ministral models (model_type:
"ministral") in transformers v5+
- Patches: RoPE, RMSNorm, SwiGLU, CrossEntropyLoss,
FusedLinearCrossEntropy
- All 5 tests (instance patching + 4 convergence) validated on H100 GPU

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [x] run `make test` to ensure correctness
- [x] run `make checkstyle` to ensure code style
- [x] run `make test-convergence` to ensure convergence

---------

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
2026-03-27 22:21:31 +00:00
Vaibhav JindalandClaude Opus 4.6 eac9ce6ede Add Liger Kernel support for nemotron models (#1165)
This PR is generated by Claude code using the skill in #1167.

## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->

- Adds monkey patching support for NVIDIA Nemotron models with
apply_liger_kernel_to_nemotron
- Supports FusedLinearCrossEntropy (default) and CrossEntropyLoss
optimizations
- RoPE, MLP, and LayerNorm patching are intentionally excluded due to
Nemotron's unique architecture

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

- Instance patching test passes                                  
- All 4 convergence tests pass on H100 GPU (bf16/fp32 x
FLCE/with_logits)
- Lint passes   

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [x] run `make test` to ensure correctness
- [x] run `make checkstyle` to ensure code style
- [x] run `make test-convergence` to ensure convergence

---------

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
2026-03-27 21:53:51 +00:00
Vaibhav JindalandClaude Opus 4.6 f1b7e47f09 Claude skill for automatically monkey patching HF transformers models (liger-autopatch) (#1167)
## Summary
This PR adds a **Claude Code skill** (`liger-autopatch`) that automates
adding Liger Kernel support for new HuggingFace Transformers models.
Instead of manually writing 10+ files per model, a
contributor can now say *"Add Liger Kernel support for nemotron"* and
the skill handles analysis, code generation, testing, and validation
through a 3-stage agentic pipeline.
### How it works
1. **Analyze** — A Model Analyzer agent reads the HF `modeling_*.py`
source and produces a structured model profile, resolving 12
architectural decisions (norm type, MLP activation, MoE structure,
  RoPE variant, casting mode, etc.)
2. **Generate** — A Code Generator agent takes the profile and
creates/modifies up to 13 files: `lce_forward`, monkey-patch function,
`__init__.py` exports, output classes, instance patching tests,
convergence tests (bf16 + fp32, FLCE + with_logits + multimodal), revert
utilities, and README entry
3. **Validate** — A Validator agent runs the instance patching test, all
applicable convergence tests, and `make checkstyle`, with up to 3 retry
attempts per step
### Key design decisions
- **Progressive disclosure**: SKILL.md is a concise entrypoint (~55
lines); detailed instructions live in separate agent files and templates
- **Version compatibility**: Uses `try/except ImportError` for
availability guards — CI on transformers 4.52.0 naturally skips newer
models, no explicit version strings needed
- **Placement enforcement**: Templates explicitly instruct alphabetical
ordering for all insertions into existing files (imports, availability
checkers, test functions, MINI_MODEL_SETUPS entries)
- **Full convergence coverage**: Generates test entries in all 6
convergence test files (bf16/fp32 x FLCE/with_logits/multimodal) as
appropriate for the model type
  ## Testing Done

- Tested end-to-end on **nemotron** (dense, RMSNorm + SwiGLU + RoPE) →
#1165
  - Tested on **ministral** (dense, Mistral-family) → #1166
- Both PRs generated fully working code including monkey-patch,
lce_forward, instance patching tests, and convergence tests across
bf16/fp32 FLCE and with_logits variants
- Iteratively refined the skill based on issues found during testing
(alphabetical placement, convergence test coverage, checkstyle
integration, version compatibility)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
2026-03-27 21:36:06 +00:00
Vaibhav JindalandClaude Opus 4.6 39a1f454f3 [Feature] Add Triton kernel for ReLU Squared activation (#1171)
This code change was generated end-to-end by Claude Code using the
`liger-kernel-dev` skill introduced in #1170.

## Summary
Adds triton implementation for relu squared activation function. This is
used for NVIDIA's nemotron models.

(https://github.com/huggingface/transformers/blob/78bdaf0b39c29737b9ca48a274ef4a34bdafd4d1/src/transformers/activations.py#L206)

### Files added/modified

| File | Description |
|------|-------------|
| `src/liger_kernel/ops/relu_squared.py` | **NEW** — Triton
forward/backward kernels + `LigerReLUSquaredFunction` autograd Function
|
| `src/liger_kernel/transformers/relu_squared.py` | **NEW** —
`LigerReLUSquared` nn.Module wrapper |
| `src/liger_kernel/transformers/functional.py` | **MODIFIED** — added
`liger_relu_squared()` functional API |
| `src/liger_kernel/ops/__init__.py` | **MODIFIED** — export
`LigerReLUSquaredFunction` + forward/backward |
| `src/liger_kernel/transformers/__init__.py` | **MODIFIED** — export
`LigerReLUSquared` + added to `__all__` |
| `test/transformers/test_relu_squared.py` | **NEW** — 18 unit tests
(correctness + functional API, float32 + bf16, multiple shapes) |
| `benchmark/scripts/benchmark_relu_squared.py` | **NEW** — speed +
memory benchmarks |
| `benchmark/data/all_benchmark_data.csv` | **MODIFIED** — benchmark
results appended |

### Kernel design

- **Tier 1 (element-wise)** — simplest kernel tier, one program per row
- **Forward**: fused `relu + square` in a single kernel pass
- **Backward**: `dX = dY * 2 * relu(X)`, reads saved X without modifying
it
- **No float32 upcasting needed** — relu and square are numerically
stable
- Uses `calculate_settings(n_cols)` for BLOCK_SIZE/num_warps auto-tuning

## Benchmark Results (NVIDIA H100 80GB HBM3, bfloat16, M=4096)

### Speed

| Hidden Size | Mode | Liger (ms) | PyTorch (ms) | Speedup |
|-------------|------|-----------|-------------|---------|
| 4096 | Forward | 0.028 | 0.053 | **1.9x** |
| 4096 | Backward | 0.039 | 0.126 | **3.2x** |
| 4096 | Full | 0.087 | 0.178 | **2.0x** |
| 16384 | Forward | 0.094 | 0.186 | **2.0x** |
| 16384 | Backward | 0.137 | 0.453 | **3.3x** |
| 16384 | Full | 0.229 | 0.635 | **2.8x** |

### Memory

| Hidden Size | Liger (MB) | PyTorch (MB) | Savings |
|-------------|-----------|-------------|---------|
| 4096 | 0.039 | 0.063 | **37.5%** |
| 16384 | 0.156 | 0.250 | **37.5%** |

### Speed — Forward
<!-- INSERT relu_squared_speed_forward.png -->
<img width="1000" height="600" alt="relu_squared_speed_forward"
src="https://github.com/user-attachments/assets/747e0ede-8d97-449c-9505-a1ba21f5470d"
/>.


### Speed — Backward
<!-- INSERT relu_squared_speed_backward.png -->
<img width="1000" height="600" alt="relu_squared_speed_backward"
src="https://github.com/user-attachments/assets/033f3373-f1f5-44f0-b630-393b82bfd93c"
/>


### Speed — Full (Forward + Backward)
<!-- INSERT relu_squared_speed_full.png -->
<img width="1000" height="600" alt="relu_squared_speed_full"
src="https://github.com/user-attachments/assets/cb32cc32-6a39-4b2a-a61e-2df92c88414e"
/>


### Memory — Full
<!-- INSERT relu_squared_memory_full.png -->
<img width="1000" height="600" alt="relu_squared_memory_full"
src="https://github.com/user-attachments/assets/0128bade-ec3b-419a-9885-f7e37ca8c400"
/>

## Testing Done
Hardware Type: NVIDIA H100 80GB HBM3

- [x] 18/18 unit tests pass on H100 (float32 + bfloat16, 6 shape configs
including non-power-of-2)
- [x] `ruff check` and `ruff format` pass
- [x] Speed benchmarks run successfully (forward, backward, full)
- [x] Memory benchmarks run successfully

---------

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
2026-03-27 21:25:06 +00:00
Vaibhav JindalandClaude Opus 4.6 0169c4def2 Claude skill for Triton kernel development (liger-kernel-dev) (#1170)
## Summary

This PR adds a **Claude Code skill** (`liger-kernel-dev`) that automates
the full lifecycle of Triton kernel development for Liger Kernel.
Instead of manually writing 8+ files per kernel (ops, transformers
wrapper, functional API, exports, tests, benchmarks), a contributor can
now describe a PyTorch operation and the skill handles analysis, code
generation, and validation through a 3-stage agentic pipeline.

### How it works

1. **Analyze** — An Analyzer agent reads the PyTorch operation (from a
local file, URL, code snippet, natural language description, or model
component reference), writes a standalone PyTorch reference
implementation, and produces a structured kernel profile classifying the
operation into one of 3 complexity tiers
2. **Generate** — A Generator agent takes the profile and
creates/modifies up to 8 files: Triton forward+backward kernels with
`torch.autograd.Function`, `nn.Module` wrapper, functional API,
`__init__.py` exports, unit tests (parametrized over shapes and dtypes),
and benchmark scripts
3. **Validate** — A Validator agent runs `make checkstyle`, unit tests
(hard gate with 3 retries — stops on persistent failure), benchmarks
(speed + memory for fwd/bwd/full), and generates plots. Optionally runs
`ncu` profiling

### Key design decisions

- **Progressive disclosure**: SKILL.md is a concise entrypoint (~63
lines); detailed instructions live in separate agent files and templates
that are loaded only when needed
- **Three complexity tiers**: Templates cover element-wise ops
(SwiGLU-like), reduction ops (RMSNorm-like), and fused/complex ops
(CrossEntropy-like) with tier-specific patterns
- **Correctness is a hard gate**: If tests fail after 3 retries, the
pipeline stops and reports to the user rather than pushing forward with
broken code
- **Single dtype benchmarks**: Benchmarks use `model.dtype` (typically
bfloat16). Multi-dtype coverage is handled by unit tests
- **Supports both create and modify modes**: Detects intent
automatically — creating new kernels goes through the full pipeline,
modifying existing kernels skips analysis

### Skill file structure

```
.claude/skills/liger-kernel-dev/
├── SKILL.md                          # Main entry point (63 lines)
├── analyzer.md                       # Stage 1: understand operation, produce profile
├── generator.md                      # Stage 2: generate all 8 files
├── validator.md                      # Stage 3: checkstyle → tests → benchmarks → plots
├── kernel-profile-format.md          # Kernel profile schema + naming conventions
├── templates/
│   ├── ops-kernel.md                 # Triton kernel patterns by tier
│   ├── module-wrapper.md             # nn.Module wrapper pattern
│   ├── functional-api.md             # functional.py modification pattern
│   ├── unit-test.md                  # Test file pattern with testing rules
│   └── benchmark.md                  # Benchmark script pattern
└── examples/
    ├── swiglu-profile.md             # Tier 1 (element-wise) reference
    ├── rms-norm-profile.md           # Tier 2 (reduction) reference
    └── cross-entropy-profile.md      # Tier 3 (fused/complex) reference
```

## Testing Done

- Tested end-to-end on **ReLU Squared** (Tier 1, element-wise activation
used in Nemotron models) → #1171
- Skill generated all 8 files: Triton kernel, module wrapper, functional
API, exports, 18 unit tests, and benchmarks
  - All tests pass on H100 (float32 + bfloat16)
- Benchmarks show **1.9-3.3x speedup** and **37.5% memory savings** vs
PyTorch
- Full PR including benchmark plots was generated end-to-end by the
skill
- Iteratively refined the skill based on issues found during testing
(single-dtype benchmarks, template accuracy)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
2026-03-27 19:56:37 +00:00
Vaibhav Jindal 5c9cecc85c Optimize tvd kernel to perform scaling inside the forward kernel (#1127)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->

### Gains
Optimized forward kernel that is **~2.6x** faster than torch
implemetation and **~1.7x** faster than older liger implementation.

## Details

### Fused gradient scaling
- Kernel gets a single scalar scale (e.g. 1/n_non_ignore for batchmean,
1/(n_non_ignore*V) for mean).
- Gradients are scaled inside the kernel: grad_res = tl.where(p > q, 0.5
* scale, -0.5 * scale) instead of writing 0.5/-0.5 and scaling in
Python.
- Python no longer does grads / n_non_ignore (or mean divisor); grads
are returned already scaled.

### Fused loss scaling
- The same scale is applied to the per-row loss: kernel stores loss_sum
* scale instead of loss_sum.
- Python returns output_tensor.sum() for batchmean/mean (no extra /
n_non_ignore or / (n_non_ignore * V)).

### Result
Same math, fewer ops and less Python-side work: one scalar scale in the
kernel replaces separate tensor divisions for loss and grads.

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

On 1xH100, new forward pass is **~2.6x** faster than torch
implementation and **~1.7x** faster than older liger implementation.

Fwd pass speed:
<img width="1000" height="600" alt="tvd_speed_forward"
src="https://github.com/user-attachments/assets/86d9a96d-d57d-45ee-8fe7-fa8aceb96e42"
/>
Fwd+Bwd speed:
<img width="1000" height="600" alt="tvd_speed_full"
src="https://github.com/user-attachments/assets/9ac5491f-a579-4f6d-a81b-bbb7c1abc930"
/>
Memory:
<img width="1000" height="600" alt="tvd_memory_full"
src="https://github.com/user-attachments/assets/c091f805-68bc-4e0b-b01e-debd6deffcb7"
/>




<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: 1xH100
- [x] run `make test` to ensure correctness
- [x] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence
2026-03-04 22:54:12 +00:00
Vaibhav Jindal dea3a772e1 Optimize Group norm forward kernel (#1112)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->
This PR refactors the **GroupNorm forward Triton kernel** to apply
affine parameters (`W`, `B`) using a **single flat loop over the group’s
hidden dimension** instead of looping **channel-by-channel**.

* Previously: iterate over channels in the group, then iterate over each
channel’s contiguous slice, reusing one `W/B` per channel.
* Now: iterate once over all elements in the group and compute the
corresponding channel via `channel = offset // hidden_size_per_channel`,
then select `W/B` from that.
* Mean/RSTD computation and normalization semantics stay the same, and
masking still guards out-of-bounds elements.
* Net effect: simpler indexing and better handling when per-channel
slices are small, at the cost of potentially more frequent `W/B` loads
(gathers) compared to the per-channel reuse before.


<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->
Gives about **~2x speedup** in the forward kernel compared to old liger
kernel, without any memory changes.
Fwd speed:

<img width="1000" height="600" alt="group_norm_speed_forward"
src="https://github.com/user-attachments/assets/be32a251-a6cb-41b2-89cb-3540ec3edae2"
/>

Full speed:

<img width="1000" height="600" alt="group_norm_speed_full"
src="https://github.com/user-attachments/assets/5d48f992-37a4-4721-a9fd-db1a9270a7b7"
/>

Memory:

<img width="1000" height="600" alt="group_norm_memory_full"
src="https://github.com/user-attachments/assets/ed536d69-5482-4833-a272-43a7c8022478"
/>



## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->
Yes, on H100.

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [x] run `make test` to ensure correctness
- [ ] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence
2026-02-28 02:49:59 +00:00
Vaibhav JindalandVaibhav Jindal 7644a0fc69 Update pyproject.toml for v0.7.0 release (#1102)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->
Update pyproject.toml for v0.7.0 release.

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->
N/A

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [ ] run `make test` to ensure correctness
- [ ] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence

Co-authored-by: Vaibhav Jindal <[email protected]>
2026-02-12 21:51:53 +00:00
Vaibhav Jindal 81f932ac19 New version release v0.6.5 (#1063)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->
New version release v0.6.5!

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [x] run `make test` to ensure correctness
- [x] run `make checkstyle` to ensure code style
- [x] run `make test-convergence` to ensure convergence
2026-02-03 17:59:19 -08:00
Vaibhav Jindal 24416a4286 Increase time limit for modal tests (#947)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->
Modal tests on nvidia gpus failing becuase of time limit errors:
https://github.com/linkedin/Liger-Kernel/actions/runs/19472103084/job/55721981739.

This PR increases the timeout to 90 mins to fix this.

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [ ] run `make test` to ensure correctness
- [ ] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence
2025-11-20 17:02:43 -08:00
Vaibhav Jindal 817436fa71 Handle aux_loss for different transformer versions (#934)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->
as only newer upcoming version of transformers exposes it.

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [x] run `make test` to ensure correctness
- [x] run `make checkstyle` to ensure code style
- [x] run `make test-convergence` to ensure convergence
2025-11-06 14:03:12 -08:00
Vaibhav Jindal 03ad3d166b Fix qwen3 related tests (#933)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->
Fix qwen3 related tests to fix failing nvidia tests.

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [x] run `make test` to ensure correctness
- [x] run `make checkstyle` to ensure code style
- [x] run `make test-convergence` to ensure convergence
2025-11-05 14:43:53 -08:00
Vaibhav Jindal 99a90f7cc4 Copy and paste benchmarks before and after gh-pages deployment (#909)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->
Copy the benchmark data from gh-pages before every mkdocs deployment and
paste it after the deployment. This helps in keeping the benchmark data
after every mkdocs deployment.

Fixes: https://github.com/linkedin/Liger-Kernel/issues/875
<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [ ] run `make test` to ensure correctness
- [ ] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence
2025-10-14 13:50:07 -07:00
Vaibhav Jindal 1c07943e9f Don't deploy mkdocs to fix benchmarking (#904)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->
Will have to fix the docs in a new PR.

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [ ] run `make test` to ensure correctness
- [ ] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence
2025-10-10 11:45:50 -07:00
Vaibhav Jindal 49fdc2dfd4 Remove daily test cron job (#890)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->
Daily tests have become costly in terms of Modal credits since we now
run them on H100s. At the same time, they don’t add much value because
we already run tests on every push to the main branch. The only real
benefit is catching regressions when upstream dependencies like
transformers change, but those issues will surface soon enough once new
changes are pushed to the main branch.


<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [x] run `make test` to ensure correctness
- [ ] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence
2025-09-23 09:14:34 -07:00
Vaibhav JindalandSteven Shimizu 389d9cf369 Fix tests and CI (#882)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->


1. Recent PR (https://github.com/linkedin/Liger-Kernel/pull/876) added
functionality to run tests in parallel. However, this leads to GPU OOM
errors breaking the CI. Even commands like
`pytest test/transformers/test_tvd.py` are not working on a single GPU
setup because of parallelism. This PR fixes this issue by changing the
behavior to run tests sequentially.

2. Fixes flaky tests for bf16 for glm4v models by increasing tolerance.

3. Use H100 for nvidia tests.

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [x] run `make test` to ensure correctness
- [x] run `make checkstyle` to ensure code style
- [x] run `make test-convergence` to ensure convergence

---------

Co-authored-by: Steven Shimizu <[email protected]>
2025-09-23 09:13:28 -07:00
Vaibhav Jindal 77a4c1a473 Update pyproject.toml version to 0.6.2 (#861)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->
Update pyproject.toml version to 0.6.2 for new release.

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [ ] run `make test` to ensure correctness
- [ ] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence
2025-08-21 17:02:49 -07:00
Vaibhav Jindal 0ea822f9cf Bug fixes in patching module (#834)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->
1. Fix `_patch_layer_norm_module` by replacing `LigerRMSNorm` with
`LigerLayerNorm`.
2. Correctly change the name of the instance and not of the Class by
replacing patches like `module.__class__.__name__ =
LigerLayerNorm.__name__` with `_bind_method_to_module(module,
"_get_name", lambda self: LigerLayerNorm.__name__)`.

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

```
from transformers import AutoModelForCausalLM
from liger_kernel.transformers.monkey_patch import apply_liger_kernel_to_qwen2
import torch

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2-0.5B-Instruct").to(device)
apply_liger_kernel_to_qwen2(model=model)

print(model)
```
prints:

```
Applied Liger kernels to Qwen2
Qwen2ForCausalLM(
  (model): Qwen2Model(
    (embed_tokens): Embedding(151936, 896)
    (layers): ModuleList(
      (0-23): 24 x Qwen2DecoderLayer(
        (self_attn): Qwen2Attention(
          (q_proj): Linear(in_features=896, out_features=896, bias=True)
          (k_proj): Linear(in_features=896, out_features=128, bias=True)
          (v_proj): Linear(in_features=896, out_features=128, bias=True)
          (o_proj): Linear(in_features=896, out_features=896, bias=False)
        )
        (mlp): LigerSwiGLUMLP(
          (gate_proj): Linear(in_features=896, out_features=4864, bias=False)
          (up_proj): Linear(in_features=896, out_features=4864, bias=False)
          (down_proj): Linear(in_features=4864, out_features=896, bias=False)
          (act_fn): SiLU()
        )
        (input_layernorm): LigerRMSNorm((896,), eps=1e-06, offset=0.0, in_place=True, row_mode=None)
        (post_attention_layernorm): LigerRMSNorm((896,), eps=1e-06, offset=0.0, in_place=True, row_mode=None)
      )
    )
    (norm): LigerRMSNorm((896,), eps=1e-06, offset=0.0, in_place=True, row_mode=None)
    (rotary_emb): Qwen2RotaryEmbedding()
  )
  (lm_head): Linear(in_features=896, out_features=151936, bias=False)
)
```

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [ ] run `make test` to ensure correctness
- [x] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence
2025-07-24 12:45:34 -07:00
Vaibhav Jindal d2431a9027 Revert "Bug Fix: name patching for modules" (#833)
This reverts commit 67a5439ebf.

## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [ ] run `make test` to ensure correctness
- [ ] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence
2025-07-23 17:38:56 -07:00
Vaibhav Jindal 67a5439ebf Bug Fix: name patching for modules 2025-07-24 00:25:49 +00:00
Vaibhav Jindal 9cf2019a6c Remove smollm3 import at top of file (#825)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->
Required for making the tests-bwd pass.

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [ ] run `make test` to ensure correctness
- [x] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence
2025-07-19 16:53:09 -07:00
Vaibhav Jindal 196540596b Skip smollm3 tests in tests-bwd (#821)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->
Skip smollm3 tests in tests-bwd as it is only available for
transformers>=4.53.0. This will help fix tests-bwd on nvidia.
 
<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: <BLANK>
- [x] run `make test` to ensure correctness
- [x] run `make checkstyle` to ensure code style
- [ ] run `make test-convergence` to ensure convergence
2025-07-18 21:10:55 -07:00
Vaibhav JindalandShao Tang 0ab432ec25 FusedAddRMSNorm: Fused residual addition and RMS Norm (#812)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->
This PR adds the FusedAddRMSNorm kernel. This kernel is meant to replace
a commonly observed pattern in decoder layers in transformers:
```
hidden_states = residual + hidden_states
residual = hidden_states
hidden_states = rmsnorm(hidden_states)
```
Some examples of this pattern:
-
https://github.com/huggingface/transformers/blob/0dc2df5ddafe3cb5824ad24e85beba13e0aa6726/src/transformers/models/qwen3/modeling_qwen3.py#L271
-
https://github.com/huggingface/transformers/blob/0dc2df5ddafe3cb5824ad24e85beba13e0aa6726/src/transformers/models/llama4/modeling_llama4.py#L393


Fusing the residual addition leads to both memory and speed savings.
This is because we are able to reduce one memory read and multiple
kernel launches for this pattern. This leads to ~15-20% memory savings
and 2-4x speed up for (fwd+bwd) mode!

With this kernel, the above 3 operations can be written as a single
efficient line of code:
```
hidden_states, residual = liger_fused_add_rms_norm(hidden_states, residual)
```


## Comparison Plots
- huggingface: Naive pytorch implementation
- liger_rms_norm: Liger RMS norm used for RMS norm, while keeping
residual addition separate
- liger_fused_add_rms_norm: Fused residual addition and RMS norm

### Memory
<img width="1000" height="600" alt="fused_add_rms_norm_memory_full"
src="https://github.com/user-attachments/assets/e8489905-5307-4eca-b726-8c97ece4fa97"
/>

### Speed
1. Forward
<img width="1000" height="600" alt="fused_add_rms_norm_speed_forward"
src="https://github.com/user-attachments/assets/4d9de5fa-3bc7-4cd7-8456-116b5cc43dfe"
/>

2. Backward
<img width="1000" height="600" alt="fused_add_rms_norm_speed_backward"
src="https://github.com/user-attachments/assets/155cd072-0a75-4b7f-acde-631af3f63ce2"
/>

3. Full (Forward+Backward)
<img width="1000" height="600" alt="fused_add_rms_norm_speed_full"
src="https://github.com/user-attachments/assets/288e252a-9414-4cd3-be0b-e81f29e4c94f"
/>


<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: H100
- [x] run `make test` to ensure correctness
- [x] run `make checkstyle` to ensure code style
- [x] run `make test-convergence` to ensure convergence

---------

Co-authored-by: Shao Tang <[email protected]>
2025-07-18 01:06:41 -07:00
Vaibhav Jindal 66570b1ae0 Update transformers version in tests-bwd and optional-deps, increase timeout, and fix tests-bwd (#794)
## Summary
<!--- This is a required section; please describe the main purpose of
this proposed code change. --->
Update tests-bwd and optional-deps to use transformers version 4.49.0
instead of 4.44.2. Also, increase the timeout for tests.

For this change, the following modifications are also needed:
- Mllama monkey_patch fix
- Perform llava convergence tests for `transformers>=4.52.0` as we don't
materialize logits for earlier versions
- Increase test tolerance for `granite`
- Run `qwen2_vl` and `qwen2_5_vl` tests only for `transformers>=4.52.4`

<!---
## Details
This is an optional section; is there anything specific that reviewers
should be aware of?
--->

## Testing Done
<!--- This is a required section; please describe how this change was
tested. --->

<!-- 
Replace BLANK with your device type. For example, A100-80G-PCIe

Complete the following tasks before sending your PR, and replace `[ ]`
with
`[x]` to indicate you have done them. 
-->

- Hardware Type: H100
- [x] run `make test` to ensure correctness
- [x] run `make checkstyle` to ensure code style
- [x] run `make test-convergence` to ensure convergence
2025-07-08 21:21:02 -07:00