Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions benchmarks/bench_torchembed_rope.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""Standalone benchmark: torchembed fused RoPE vs. litgpt plain-PyTorch RoPE.

Reproduces the numbers cited in the PR description.

Hardware used for reference results: NVIDIA GB10, bfloat16.
Run with:
python benchmarks/bench_torchembed_rope.py

Requirements:
pip install torchembed
CUDA GPU with triton support
"""

import time

import torch

from litgpt.model import apply_rope

try:
from torchembed.positional import RotaryEmbedding as TorchembedRotaryEmbedding
except ImportError as e:
raise SystemExit("torchembed is not installed. Run: pip install torchembed") from e


def _time_fn(fn, *args, warmup: int = 5, iters: int = 50) -> float:
"""Return median wall-clock time in milliseconds."""
for _ in range(warmup):
fn(*args)
torch.cuda.synchronize()

times = []
for _ in range(iters):
start = time.perf_counter()
fn(*args)
torch.cuda.synchronize()
times.append((time.perf_counter() - start) * 1000)

times.sort()
return times[len(times) // 2]


def _litgpt_rope(q: torch.Tensor, k: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor):
"""Thin wrapper matching the litgpt apply_rope calling convention."""
apply_rope(q, cos, sin)
apply_rope(k, cos, sin)


def _build_litgpt_cos_sin(seq_len: int, rope_n_elem: int, base: int, device: torch.device, dtype: torch.dtype):
"""Replicate litgpt's build_rope_cache (simplified, standard RoPE)."""
inv_freq = 1.0 / (base ** (torch.arange(0, rope_n_elem, 2, device=device).float() / rope_n_elem))
t = torch.arange(seq_len, device=device).float()
freqs = torch.outer(t, inv_freq)
emb = torch.cat([freqs, freqs], dim=-1)
# litgpt expects (1, T, rope_n_elem)
cos = emb.cos().unsqueeze(0).to(dtype)
sin = emb.sin().unsqueeze(0).to(dtype)
return cos, sin


def main():
if not torch.cuda.is_available():
raise SystemExit("CUDA GPU required for this benchmark.")

device = torch.device("cuda")
dtype = torch.bfloat16

batch = 4
n_heads = 32
d_qk = 128 # head dim / rope_n_elem
base = 10_000

print(f"Device: {torch.cuda.get_device_name(device)}")
print(f"dtype={dtype}, batch={batch}, n_heads={n_heads}, d_qk={d_qk}")
print()
print(f"{'seq_len':>8} {'litgpt (ms)':>12} {'torchembed (ms)':>16} {'speedup':>8}")
print("-" * 52)

rope_tc = TorchembedRotaryEmbedding(dim=d_qk, max_seq_len=8192, base=base, use_fused=True).to(device)

for seq_len in [512, 1024, 2048, 4096, 8192]:
q = torch.randn(batch, n_heads, seq_len, d_qk, device=device, dtype=dtype)
k = torch.randn(batch, n_heads, seq_len, d_qk, device=device, dtype=dtype)

cos, sin = _build_litgpt_cos_sin(seq_len, d_qk, base, device, dtype)

t_litgpt = _time_fn(_litgpt_rope, q, k, cos, sin)
t_tc = _time_fn(rope_tc, q, k)

speedup = t_litgpt / t_tc
print(f"{seq_len:>8} {t_litgpt:>12.3f} {t_tc:>16.3f} {speedup:>7.2f}x")


if __name__ == "__main__":
main()
5 changes: 5 additions & 0 deletions litgpt/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ class Config:
rope_condense_ratio: int = 1
rope_adjustments: dict | None = None
rope_interleave: bool = False
# When True, delegates RoPE application to torchembed's fused Triton kernel
# (requires ``pip install torchembed``). Falls back to the standard
# implementation if torchembed is unavailable or the tensors are not on a
# CUDA device. Incompatible with rope_interleave=True or rope_adjustments.
use_torchembed_rope: bool = False
# Transformer block (MLP)
intermediate_size: int | None = None
moe_intermediate_size: int | None = None
Expand Down
36 changes: 35 additions & 1 deletion litgpt/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@
from litgpt.config import Config
from litgpt.scripts.convert_hf_checkpoint import qkv_reassemble

# Optional: torchembed fused RoPE kernel (pip install torchembed)
try:
from torchembed.positional import RotaryEmbedding as TorchembedRotaryEmbedding

_TORCHEMBED_AVAILABLE = True
except ImportError:
_TORCHEMBED_AVAILABLE = False


class GPT(nn.Module):
def __init__(self, config: Config) -> None:
Expand Down Expand Up @@ -424,6 +432,26 @@ def __init__(self, config: Config, block_idx: int) -> None:
else:
self.mscale = 1.0

# Optionally wire up the torchembed fused Triton RoPE kernel.
# Restricted to the standard (non-interleaved, non-adjusted) RoPE path.
self._torchembed_rope: TorchembedRotaryEmbedding | None = None
if config.use_torchembed_rope:
if not _TORCHEMBED_AVAILABLE:
raise ImportError(
"use_torchembed_rope=True requires the 'torchembed' package. "
"Install it with: pip install torchembed"
)
if config.rope_interleave:
raise ValueError("use_torchembed_rope is incompatible with rope_interleave=True")
if config.rope_adjustments is not None:
raise ValueError("use_torchembed_rope is incompatible with rope_adjustments (YaRN/Llama3 scaling)")
self._torchembed_rope = TorchembedRotaryEmbedding(
dim=config.rope_n_elem,
max_seq_len=config.block_size,
base=config.rope_base,
use_fused=True,
)

self.config = config
self.block_idx = block_idx

Expand Down Expand Up @@ -503,7 +531,13 @@ def forward(
k = self.norm_k(k)

# Unlike standard positional embeddings rotary embeddings must be applied at every layer.
if self.config.rope_interleave:
# When use_torchembed_rope=True and we are in training (input_pos is None, contiguous
# positions), hand off to torchembed's fused Triton kernel for a ~3-4x speedup.
# Inference with an active KV-cache uses non-contiguous position indices that the
# torchembed module does not support, so we fall back to the standard path there.
if self._torchembed_rope is not None and input_pos is None:
q_roped, k_roped = self._torchembed_rope(q[..., :rope_n_elem], k[..., :rope_n_elem])
elif self.config.rope_interleave:
q_roped = apply_rope_interleave(q[..., :rope_n_elem], cos, sin)
k_roped = apply_rope_interleave(k[..., :rope_n_elem], cos, sin)
else:
Expand Down
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ optional-dependencies.test = [
"pytest-rerunfailures>=14",
"pytest-timeout>=2.3.1",
]
optional-dependencies.torchembed = [
# Fused Triton RoPE kernel (use_torchembed_rope=True in Config).
# Requires a CUDA GPU and the triton package. Provides ~3-4x speedup over
# the standard PyTorch RoPE implementation during training.
"torchembed>=0.3.1",
]
urls.documentation = "https://github.com/lightning-AI/litgpt/tutorials"
urls.homepage = "https://github.com/lightning-AI/litgpt"
scripts.litgpt = "litgpt.__main__:main"
Expand Down