# Copyright Lightning AI. Licensed under the Apache License 2.0, see LICENSE file. import sys from pathlib import Path import torch from torch import Tensor import litgpt.model from litgpt.constants import _THUNDER_AVAILABLE from litgpt.model import LLaMAMLP as OriginalLLaMAMLP from thunder.core.proxies import TensorProxy from thunder.core.transforms import get_grad, mean_backward, put_grads from thunder.extend import OperatorExecutor, register_executor from thunder.torch import ne, sum, true_divide if _THUNDER_AVAILABLE: import thunder import thunder.torch as ltorch sys.path.append(str(Path(__file__).parent)) import kernels unsloth_ex = OperatorExecutor("unsloth", version="0.1") register_executor(unsloth_ex) """ ==================== Cross Entropy Loss ==================== """ def unsloth_cross_entropy_meta(logits: TensorProxy, labels: TensorProxy) -> tuple[TensorProxy, TensorProxy]: return ( TensorProxy( shape=(logits.shape[0],), # the cross entropy kernel only supports float32 dtype=thunder.dtypes.float32, device=logits.device, requires_grad=logits.requires_grad, ), TensorProxy(shape=(logits.shape[0],), dtype=thunder.dtypes.float32, device=logits.device, requires_grad=False), ) unsloth_cross_entropy = unsloth_ex.register_operator( "unsloth_cross_entropy", meta=unsloth_cross_entropy_meta, fn=kernels.cross_entropy_loss._cross_entropy_forward_impl ) def unsloth_cross_entropy_backward_impl(dlosses: Tensor, logits: Tensor, labels: Tensor, logsumexp: Tensor) -> Tensor: # clone() because the kernel writes the grads in the logits return kernels.cross_entropy_loss._cross_entropy_backward_impl(dlosses, logits.clone(), logsumexp, labels) def unsloth_cross_entropy_backward_meta( dlosses: TensorProxy, logits: TensorProxy, logsumexp: TensorProxy, labels: TensorProxy ) -> TensorProxy: return thunder.TensorProxy(like=logits) unsloth_cross_entropy_backward = unsloth_ex.register_operator( "unsloth_cross_entropy_backward", meta=unsloth_cross_entropy_backward_meta, fn=unsloth_cross_entropy_backward_impl ) def unsloth_cross_entropy_checker( logits: TensorProxy, labels: TensorProxy, weight: TensorProxy | None = None, size_average: bool | None = None, ignore_index: int = -100, reduce: bool | None = None, reduction: str = "mean", label_smoothing: float = 0.0, ) -> bool: return ( weight is None and size_average is None and reduce is None and reduction in ("none", "mean") and ignore_index == -100 and label_smoothing == 0.0 and logits.device.type == "cuda" and labels.device.type == "cuda" ) def cross_entropy_to_unsloth( logits: TensorProxy, labels: TensorProxy, weight: TensorProxy | None = None, size_average: bool | None = None, ignore_index: int = -100, reduce: bool | None = None, reduction: str = "mean", label_smoothing: float = 0.0, ) -> tuple[TensorProxy, TensorProxy]: loss, logsumexp = unsloth_cross_entropy(logits, labels) if reduction == "mean": # "mean" reduction is not part of the kernel # TODO: this doesn't consider that all elements could be masked, causing a division by 0 n_items = sum(ne(labels, -100)) loss = true_divide(sum(loss), n_items) elif reduction != "none": raise NotImplementedError(reduction) return loss, logsumexp def unsloth_cross_entropy_grad( logits: TensorProxy, labels: TensorProxy, weight: TensorProxy | None = None, size_average: bool | None = None, ignore_index: int = -100, reduce: bool | None = None, reduction: str = "mean", label_smoothing: float = 0.0, ) -> TensorProxy: loss, logsumexp = cross_entropy_to_unsloth(**locals()) grad = get_grad(loss) if reduction == "mean": grad = mean_backward(logsumexp.ndim, logsumexp.shape, (0,), grad) logits_grad = unsloth_cross_entropy_backward(grad, logits, labels, logsumexp) put_grads((logits,), (logits_grad,)) return loss # registers as cross entropy implementation, including the execution transform and now a grad transform unsloth_ex.register_implementation( ltorch.cross_entropy, checker=unsloth_cross_entropy_checker, execution_transform=lambda *args: cross_entropy_to_unsloth(*args)[0], grad_transform=unsloth_cross_entropy_grad, ) """ ========= RMSNorm ========= The RMSNorm kernel is not integrated because it's not numerically equal and it doesn't compute the gradient for the weight, just for the input. """ """ ======== SwiGLU ======== """ def swiglu(e: torch.Tensor, g: torch.Tensor) -> torch.Tensor: return torch.nn.functional.silu(e) * g class ThunderLLaMAMLP(OriginalLLaMAMLP): def forward(self, x: torch.Tensor) -> torch.Tensor: x_fc_1 = self.fc_1(x) x_fc_2 = self.fc_2(x) x = swiglu(x_fc_1, x_fc_2) return self.proj(x) litgpt.model.LLaMAMLP = ThunderLLaMAMLP def swiglu_forward_meta(e: TensorProxy, g: TensorProxy) -> TensorProxy: return TensorProxy(like=e) litgpt_swiglu = unsloth_ex.register_operator("litgpt_swiglu", meta=swiglu_forward_meta, fn=swiglu, replaces=swiglu) unsloth_swiglu_forward = unsloth_ex.register_operator( "unsloth_swiglu_forward", meta=swiglu_forward_meta, fn=lambda *args: kernels.swiglu_fg_kernel(*args) ) def unsloth_swiglu_backward_meta(DW: TensorProxy, e: TensorProxy, g: TensorProxy) -> tuple[TensorProxy, TensorProxy]: return TensorProxy(like=g), TensorProxy(like=e) def unsloth_swiglu_backward_fn(DW: Tensor, e: Tensor, g: Tensor) -> tuple[Tensor, tuple]: B, T, n_embd = e.shape e = e.view(-1, n_embd) g = g.view(-1, n_embd) DW, e, g = kernels.swiglu_DWf_DW_dfg_kernel(DW, e, g) e = e.view(B, T, n_embd) g = g.view(B, T, n_embd) return g, e unsloth_swiglu_backward = unsloth_ex.register_operator( "unsloth_swiglu_backward", meta=unsloth_swiglu_backward_meta, fn=unsloth_swiglu_backward_fn ) def swiglu_to_unsloth_checker(e: TensorProxy, g: TensorProxy) -> bool: return e.device.type == "cuda" and g.device.type == "cuda" def unsloth_swiglu_grad(e: TensorProxy, g: TensorProxy) -> TensorProxy: h = unsloth_swiglu_forward(**locals()) grad = get_grad(h) e_grad, g_grad = unsloth_swiglu_backward(grad, e, g) put_grads((e, g), (e_grad, g_grad)) return h unsloth_ex.register_implementation( litgpt_swiglu, checker=swiglu_to_unsloth_checker, execution_transform=unsloth_swiglu_forward, grad_transform=unsloth_swiglu_grad, ) """ ====== RoPE ====== """ def apply_rope_meta(x: TensorProxy, cos: TensorProxy, sin: TensorProxy) -> TensorProxy: return TensorProxy(like=x) apply_rope = unsloth_ex.register_operator( "litgpt_apply_rope", like=apply_rope_meta, fn=litgpt.model.apply_rope, replaces=litgpt.model.apply_rope ) def unsloth_apply_rope_meta( Q: TensorProxy, cos: TensorProxy, sin: TensorProxy ) -> tuple[TensorProxy, TensorProxy, TensorProxy, int, int, int]: batch, n_heads, seq_len, head_dim = Q.shape assert seq_len <= cos.shape[-2] BLOCK_SIZE, num_warps = kernels.calculate_settings(head_dim // 2) div, mod = divmod(n_heads, kernels.rope_embedding.ROPE_GROUP_SIZE) n_groups = div + (mod != 0) return TensorProxy(like=Q), cos, sin, n_groups, BLOCK_SIZE, num_warps unsloth_apply_rope = unsloth_ex.register_operator( "unsloth_apply_rope", meta=unsloth_apply_rope_meta, fn=kernels._rope_embedding_forward_impl ) def unsloth_apply_rope_backward_meta( dY: TensorProxy, cos: TensorProxy, sin: TensorProxy, n_groups: int, BLOCK_SIZE: int, num_warps: int ) -> TensorProxy: return TensorProxy(like=dY) unsloth_apply_rope_backward = unsloth_ex.register_operator( "unsloth_apply_rope_backward", meta=unsloth_apply_rope_backward_meta, fn=kernels._rope_embedding_backward_impl ) def apply_rope_to_unsloth_checker(x: TensorProxy, cos: TensorProxy, sin: TensorProxy) -> bool: return len(x.shape) == 4 and x.device.type == "cuda" and cos.device.type == "cuda" and sin.device.type == "cuda" def unsloth_apply_rope_grad(x: TensorProxy, cos: TensorProxy, sin: TensorProxy) -> TensorProxy: Q, cos, sin, n_groups, BLOCK_SIZE, num_warps = unsloth_apply_rope(x, cos, sin) dY = get_grad(Q) dX = unsloth_apply_rope_backward(dY, cos, sin, n_groups, BLOCK_SIZE, num_warps) put_grads((x,), (dX,)) return Q unsloth_ex.register_implementation( apply_rope, checker=apply_rope_to_unsloth_checker, execution_transform=lambda *args: unsloth_apply_rope(*args)[0], grad_transform=unsloth_apply_rope_grad, )