mirror of
https://github.com/linkedin/Liger-Kernel.git
synced 2026-10-11 22:29:55 +00:00
main
63
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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 |
||
|
|
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]>
|
||
|
|
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]> |
||
|
|
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]> |
||
|
|
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]> |
||
|
|
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]> |
||
|
|
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]> |
||
|
|
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 |
||
|
|
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]>
|
||
|
|
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]> |
||
|
|
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]> |
||
|
|
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]> |
||
|
|
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]>
|
||
|
|
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]> |
||
|
|
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]> |
||
|
|
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]> |
||
|
|
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]> |
||
|
|
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]> |
||
|
|
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]>
|
||
|
|
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]> |
||
|
|
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]>
|
||
|
|
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
|
||
|
|
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]> |
||
|
|
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]> |
||
|
|
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 |
||
|
|
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]> |
||
|
|
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]> |
||
|
|
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
|
||
|
|
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 |
||
|
|
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 |
||
|
|
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]> |
||
|
|
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]> |
||
|
|
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
|
||
|
|
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 |
||
|
|
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]> |
||
|
|
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]> |
||
|
|
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]> |
||
|
|
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]> |
||
|
|
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]> |
||
|
|
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]> |
||
|
|
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]> |
||
|
|
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]> |
||
|
|
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]> |
||
|
|
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]>
|
||
|
|
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 |
||
|
|
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 |
||
|
|
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]> |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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]> |
||
|
|
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 |
||
|
|
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
|
||
|
|
d2431a9027 |
Revert "Bug Fix: name patching for modules" (#833)
This reverts commit
|
||
|
|
67a5439ebf | Bug Fix: name patching for modules | ||
|
|
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 |
||
|
|
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 |
||
|
|
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]> |
||
|
|
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 |