## 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]>
Benchmarking Liger Kernels
Benchmark Framework Overview
The benchmarking system is designed to provide a consistent, low-boilerplate way to evaluate kernel performance across:
- Different model configurations (e.g., LLaMA and Qwen variants)
- Different sequence lengths / Batch size * token length
- Multiple kernel providers (e.g.,
liger,huggingface)
Core Concepts
-
setup_fnDefines how to construct inputs and modules for a single forward pass.
- Input:
SingleBenchmarkRunInput - Output: tuple of tensors / modules
def _setup_fn(input: SingleBenchmarkRunInput) -> Tuple[Any, ...]: x = ... layer = ... return x, layer - Input:
-
Benchmark Function Builders
Reusable helpers to generate benchmark functions handle:
- forward / backward / full modes
- timing and memory measurement
build_speed_bench_fn(setup_fn) build_memory_bench_fn(setup_fn) -
Sweep Builders
(a)
build_model_config_sweep- Sweeps across model configurations(e.g. hidden size, dtype, vocab size)
- Keeps total tokens (
B * T) approximately constant - Automatically derives a suitable
(B, T)that will not cause OOM under the given token budget probe_dimmust align with howinput.xis interpreted insetup_fn
common_configs = build_model_config_sweep( kernel_name=..., all_model_configs=..., setup_fn=..., model_keys=[...], probe_dim: Literal["T", "B", "BT"] = "T" )(b)
build_token_length_sweep-
Sweeps along a chosen scaling dimension:
-
"T"→ sequence length -
"B"→ batch size -
"BT"→ total tokens -
Uses a single fixed model configuration
-
Maintains a consistent memory model via bytes-per-token estimation
-
scale_dimmust align with howinput.xis interpreted insetup_fn
common_configs = build_token_length_sweep( kernel_name=..., probe_x=..., model=..., setup_fn=..., model_keys=[...], scale_dim: Literal["T", "B", "BT"] = "T", ) -
model_keysandextra_configs-
model_keys: attributes pulled fromModelConfig -
e.g.
["hidden_size", "dtype"] -
extra_configs: static overrides -
e.g.
{"eps": 1e-6}
These form
extra_benchmark_config, passed intosetup_fn. -
Benchmark workflow:
-
Create a benchmark script
- Add your script under
benchmark/scripts/ - Name it according to the kernel (e.g.,
benchmark_<kernel_name>.py)
- Add your script under
-
Run the benchmark
- Results will be saved to
benchmark/data/all_benchmark_data.csv
Example: Benchmarking KTO Loss
cd benchmark python scripts/benchmark_kto_loss.py --sweep-mode model_config [--model llama_3_8b] python scripts/benchmark_kto_loss.py [--sweep-mode token_length] [--bt 2048] - Results will be saved to
-
Visualize results
-
Use the visualization script with optional modes:
--sweep-mode: Select which sweep data to plot.token_length(default): plots where x-axis is sequence length.model_config: plots where x-axis is model configuration.
- To target specific operation mode(s), pass
--kernel-operation-modeone or more values. - If you omit
--kernel-operation-mode, the script will:- For
speedmetrics: generate plots for all available modes (forward/backward/full). - For
memorymetrics: generate only thefullplot.
- For
Examples:
- Token-length sweep, specific modes (speed):
python benchmarks_visualizer.py \ --kernel-name kto_loss \ --metric-name speed \ --kernel-operation-mode forward backward- Token-length sweep, all modes (speed):
python benchmarks_visualizer.py \ --kernel-name kto_loss \ --metric-name speed- Model-config sweep (speed):
python benchmarks_visualizer.py \ --kernel-name geglu \ --metric-name speed \ --sweep-mode model_config- Memory (always full):
python benchmarks_visualizer.py \ --kernel-name kto_loss \ --metric-name memory -
-
View results
- Generated plots will be saved in
benchmark/visualizations/ - Filenames include the sweep mode when specified (e.g.
geglu_speed_full_model_config.png)
- Generated plots will be saved in
Comparing kernel backends (Triton vs CuTile / CuTe-DSL)
Some kernels have an alternative backend (a different DSL on the same CUDA
device) that is opt-in via the LIGER_KERNEL_IMPL environment variable:
- CuTile (
LIGER_KERNEL_IMPL=cutile) — forcross_entropy,fused_linear_jsd,geglu,jsd,layer_norm. - CuTe-DSL (
LIGER_KERNEL_IMPL=cutedsl) — forcross_entropy,fused_linear_cross_entropy, andrms_norm.
To benchmark a kernel's Triton and alternative backend side by side in one
CSV, use the corresponding compare driver. It runs the standard
benchmark_<kernel>.py twice — once on Triton, once on the alternative — and
tags the liger provider for each run (liger_triton / liger_cutile /
liger_cutedsl) so both land in a dedicated CSV without colliding.
cd benchmark/scripts
# Triton vs CuTile (writes data/all_benchmark_data_cutile.csv)
python run_cutile_compare.py --kernel cross_entropy [--model llama_3_8b] [--overwrite]
# Triton vs CuTe-DSL (writes data/all_benchmark_data_cutedsl.csv)
python run_cutedsl_compare.py --kernel cross_entropy [--overwrite]
python run_cutedsl_compare.py --kernel fused_linear_cross_entropy \
[--model llama_3_8b] [--overwrite]
Any extra args (--model, --sweep-mode, --bt, --overwrite) are forwarded
to the underlying benchmark script. Plot the merged CSV by pointing the
visualizer at it with --data-file:
python ../benchmarks_visualizer.py \
--kernel-name cross_entropy --metric-name speed \
--data-file data/all_benchmark_data_cutedsl.csv