Pragya / model.py
ArushBuilds's picture
Update model.py
4e554e2 verified
Raw History Blame Contribute Delete
103 kB
import functools
import math
import os
import warnings
from dataclasses import dataclass
from typing import Optional, Tuple
import torch
import torch.nn as nn
import torch.utils.checkpoint
from torch.nn import functional as F
_native_rms_norm = getattr(F, "rms_norm", None)
_HAS_NATIVE_RMSNORM = _native_rms_norm is not None
if not _HAS_NATIVE_RMSNORM:
raise ImportError("model.py requires torch>=2.4 (F.rms_norm)")
try:
import config as alpha_config
except ImportError: # pragma: no cover - fallback for package-style imports
from . import config as alpha_config
_LigerRMSNormFn = None
try:
# pyrefly: ignore [missing-import]
from liger_kernel.ops.rms_norm import LigerRMSNormFunction as _LigerRMSNormFn
except ImportError:
pass
try:
import kernel
except ImportError: # pragma: no cover - fallback for package-style imports
from . import kernel
try:
import kernel_triton as _moe_kernel
except ImportError:
try:
from . import kernel_triton as _moe_kernel
except ImportError:
_moe_kernel = None
except Exception as _e: # noqa: BLE001 -- optional backend must not block model import
warnings.warn(f"[model.py/moe] kernel_triton import failed ({_e!r}) -- using eager MoE loop.")
_moe_kernel = None
except Exception as _e: # noqa: BLE001 -- optional backend must not block model import
warnings.warn(f"[model.py/moe] kernel_triton import failed ({_e!r}) -- using eager MoE loop.")
_moe_kernel = None
def _moe_kernel_ready() -> bool:
# Only when the config switch (use_triton_fusions) is on. Otherwise the eager expert loop runs,
# so no graph-breaking kernel entry point is reached.
return _moe_kernel is not None and bool(_moe_kernel.fusion_enabled())
# -- Optional Triton fusions (triton_kernel.py) --------------------------------
# Auto-detected, no config flag. Kill switch for debugging / A-B timing:
# ALPHA_DISABLE_TRITON=1
# Every op is verified once against the eager reference (outputs AND grads) on a
# small slice of the first real call, then used; any failure falls back to eager
# for that (op, dtype/shape-key) and warns once. See _triton_attempt below.
# Config switch: config.py `use_triton_fusions` (default False). Off = no triton_kernel import and every
# `if _HAS_TRITON` branch is dead code under dynamo.
def _triton_fusions_on_in_config() -> bool:
return bool(getattr(alpha_config, "use_triton_fusions", False))
_triton_kernel = None
if os.environ.get("ALPHA_DISABLE_TRITON") != "1" and _triton_fusions_on_in_config():
try:
import triton_kernel as _triton_kernel
except ImportError: # pragma: no cover - fallback for package-style imports
try:
from . import triton_kernel as _triton_kernel
except ImportError:
_triton_kernel = None
except Exception as _e: # noqa: BLE001 -- file present but broken: warn, run eager
warnings.warn(f"[model.py/triton] triton_kernel failed to import ({_e!r}) -- running eager.")
_triton_kernel = None
def _triton_ready() -> bool:
if _triton_kernel is None:
return False
for _name in ("is_available", "_ok"):
_fn = getattr(_triton_kernel, _name, None)
if callable(_fn):
try:
return bool(_fn())
except Exception: # noqa: BLE001
return False
return True
# Static module constant: when False dynamo drops every Triton branch at trace
# time (no graph breaks, no overhead).
_HAS_TRITON = _triton_ready()
_TRITON_OK: set = set()
_TRITON_BAD: set = set()
_triton_warned: set = set()
try:
from ngram import Engram, NgramHasher
except ImportError: # pragma: no cover - fallback for package-style imports
try:
from .ngram import Engram, NgramHasher
except ImportError:
Engram = None
NgramHasher = None
_alpha_config_warned: set[str] = set()
_TORCHAO_AVAILABLE: bool = False
_NVFP4_RHT_AVAILABLE: bool = False
_INT8_AVAILABLE: bool = False
# BUGFIX: these two were only bound inside the `try` body. If `import torchao`
# succeeded but a later import in the same try block raised, they stayed
# undefined and apply_int8_to_model() hit a NameError instead of the intended
# RuntimeError. Bind safe defaults up front.
_INT8_GROUPED_AVAILABLE: bool = False
_Int8WOConfig = None # type: ignore[assignment]
try:
import torchao as _torchao_probe # noqa: F401
_TORCHAO_AVAILABLE = True
from torchao.quantization import quantize_ as _torchao_quantize
from torchao.quantization import (
Int8DynamicActivationInt8WeightConfig as _Int8Config,
)
try:
from torchao.quantization import Int8WeightOnlyConfig as _Int8WOConfig
_INT8_GROUPED_AVAILABLE = True
except ImportError:
_Int8WOConfig = None # type: ignore[assignment]
_INT8_GROUPED_AVAILABLE = False
_INT8_AVAILABLE = True
try:
from torchao.prototype.mx_formats import (
NVFP4DynamicActivationNVFP4WeightConfig as _NVFP4Config,
)
_NVFP4_RHT_AVAILABLE = True
except ImportError:
_NVFP4Config = None # type: ignore[assignment]
except ImportError:
_torchao_quantize = None # type: ignore[assignment]
_Int8Config = None # type: ignore[assignment]
_NVFP4Config = None # type: ignore[assignment]
_NVFP4_DISABLED_REASON: "str | None" = None
_NVFP4_SKIP_KEYWORDS: tuple[str, ...] = (
"wte", "lm_head", "ln_", "ln_f", "norm",
"router", "gate", "expert_bias", "engram",
)
def _nvfp4_filter(module: nn.Module, fqn: str) -> bool:
"""torchao filter_fn: True = quantize this module."""
if not isinstance(module, nn.Linear):
return False
for kw in _NVFP4_SKIP_KEYWORDS:
if kw in fqn:
return False
out_f, in_f = module.weight.shape
if in_f < 32 or out_f < 64 or in_f % 16 != 0:
return False
return True
def _nvfp4_enabled() -> bool:
global _NVFP4_DISABLED_REASON
if not getattr(alpha_config, 'use_nvfp4', False):
return False
if not torch.cuda.is_available():
_NVFP4_DISABLED_REASON = "nvFP4: no CUDA device"
return False
major, _minor = torch.cuda.get_device_capability()
if major < 10:
msg = (
f"nvFP4 requires Blackwell (sm_100+); found sm_{major}{_minor} "
f"({torch.cuda.get_device_name()}) -- disabled."
)
if msg not in _alpha_config_warned:
_alpha_config_warned.add(msg)
warnings.warn(msg)
_NVFP4_DISABLED_REASON = msg
return False
if not _NVFP4_RHT_AVAILABLE:
_NVFP4_DISABLED_REASON = (
"nvFP4: torchao or torchao.prototype.mx_formats not available. "
"Install: pip install torchao --pre"
)
warnings.warn(_NVFP4_DISABLED_REASON)
return False
return True
def apply_nvfp4_to_model(model: nn.Module) -> int:
before_ids = {fqn: id(m.weight) for fqn, m in model.named_modules()
if _nvfp4_filter(m, fqn)}
_torchao_quantize(model, _NVFP4Config(), filter_fn=_nvfp4_filter)
# BUGFIX: torchao swaps the *weight* for a tensor subclass and leaves the
# module a plain nn.Linear, so the old `type(m).__name__ != "Linear"` test
# always counted 0. Compare weight identity instead (same test the INT8
# path already used).
current = dict(model.named_modules())
count = 0
for fqn, old_id in before_ids.items():
m = current.get(fqn)
if m is not None and getattr(m, "weight", None) is not None and id(m.weight) != old_id:
count += 1
model._quant_mode = "nvfp4"
model._use_nvfp4 = True
model._use_int8 = False
return count
# -- FP8 *training* (torchao.float8.Float8Linear) ----------------------------------
# Master weights stay in their own dtype and the state_dict is unchanged, so FP8-trained
# checkpoints are ordinary checkpoints (_quant_mode stays None). Applied explicitly from
# train.py (not GPT.__init__), so inference never sees Float8Linear.
_FP8_AVAILABLE: bool = False
try:
from torchao.float8 import convert_to_float8_training as _convert_to_float8_training
from torchao.float8 import Float8LinearConfig as _Float8LinearConfig
_FP8_AVAILABLE = True
except Exception: # noqa: BLE001 -- torchao missing/too old: FP8 simply unavailable
_convert_to_float8_training = None # type: ignore[assignment]
_Float8LinearConfig = None # type: ignore[assignment]
# nn.Linear layers that always stay in BF16 (note: "gate" alone would also hit SwiGLU's W_gate).
_FP8_SKIP_KEYWORDS: tuple[str, ...] = (
"wte", "lm_head", "ln_", "ln_f", "norm", "router", "expert_bias", "engram", "attn_gate_proj", "mtp",
"routed_experts",
)
_FP8_BLOCK_RE = __import__("re").compile(r"(?:^|\.)h\.(\d+)\.")
_FP8_RECIPES = ("tensorwise", "rowwise", "rowwise_with_gw_hp")
def _fp8_enabled() -> bool:
"""use_fp8=True and the hardware/library can do it. Resolved once per run (train.py)."""
if not getattr(alpha_config, 'use_fp8', False):
return False
msg = None
if not torch.cuda.is_available():
msg = "FP8 training: no CUDA device -- running BF16."
elif torch.cuda.get_device_capability() < (8, 9):
msg = (f"FP8 training needs sm_89+ (Ada/Hopper/Blackwell); found "
f"sm_{''.join(map(str, torch.cuda.get_device_capability()))} -- running BF16.")
elif not _FP8_AVAILABLE:
msg = "FP8 training: torchao.float8 not importable (pip install torchao) -- running BF16."
if msg is not None:
if msg not in _alpha_config_warned:
_alpha_config_warned.add(msg)
warnings.warn(msg)
return False
return True
def apply_fp8_to_model(model: nn.Module, n_layer: int, recipe: str = "rowwise_with_gw_hp",
bf16_tail_frac: float = 0.15, emulate: bool = False) -> dict:
"""Swap eligible nn.Linear -> torchao Float8Linear in place (must run BEFORE torch.compile).
Keeps the last max(1, round(frac * n_layer)) blocks and everything in _FP8_SKIP_KEYWORDS in
BF16. Eligible = no bias, both dims multiples of 16 (scaled_mm) and >= 64 (tiny GEMMs lose).
`emulate` runs the matmul in high precision (tests on non-FP8 hardware only)."""
import dataclasses as _dc
if recipe not in _FP8_RECIPES:
raise ValueError(f"fp8 recipe {recipe!r} not in {_FP8_RECIPES}")
if not _FP8_AVAILABLE:
raise RuntimeError("torchao.float8 not importable")
n_tail = max(1, round(bf16_tail_frac * n_layer)) if bf16_tail_frac > 0 else 0
first_bf16 = n_layer - n_tail
kept_tail = [0]
def _filter(mod: nn.Module, fqn: str) -> bool:
if type(mod) is not nn.Linear or mod.bias is not None:
return False
if any(k in fqn for k in _FP8_SKIP_KEYWORDS):
return False
out_f, in_f = mod.weight.shape
if in_f % 16 or out_f % 16 or in_f < 64 or out_f < 64:
return False
mb = _FP8_BLOCK_RE.search(fqn)
if mb is not None and int(mb.group(1)) >= first_bf16:
kept_tail[0] += 1
return False
return True
cfg = _Float8LinearConfig.from_recipe_name(recipe)
if emulate:
cfg = _dc.replace(cfg, emulate=True)
before = {fqn for fqn, m in model.named_modules() if type(m) is nn.Linear}
_convert_to_float8_training(model, module_filter_fn=_filter, config=cfg)
converted = sum(1 for fqn, m in model.named_modules()
if fqn in before and type(m).__name__ == "Float8Linear")
model._fp8_train = converted > 0
return {"converted": converted, "kept_bf16_tail": kept_tail[0], "tail_blocks": n_tail, "recipe": recipe}
_INT8_SKIP_KEYWORDS: tuple[str, ...] = _NVFP4_SKIP_KEYWORDS
_INT8_DISABLED_REASON: "str | None" = None
def _int8_filter(module: nn.Module, fqn: str) -> bool:
"""torchao filter_fn for INT8: True = quantize this module."""
if not isinstance(module, nn.Linear):
return False
for kw in _INT8_SKIP_KEYWORDS:
if kw in fqn:
return False
out_f, in_f = module.weight.shape
if in_f < 64 or out_f < 32:
return False
return True
def _int8_enabled() -> bool:
global _INT8_DISABLED_REASON
if not getattr(alpha_config, 'use_int8', False):
return False
if not torch.cuda.is_available():
_INT8_DISABLED_REASON = "INT8: no CUDA device"
return False
if not _INT8_AVAILABLE:
_INT8_DISABLED_REASON = (
"INT8: torchao not installed or Int8DynamicActivationInt8WeightConfig "
"not found. Install: pip install torchao"
)
warnings.warn(_INT8_DISABLED_REASON)
return False
if _nvfp4_enabled():
msg = (
"INT8 disabled: use_nvfp4=True also set and this is SM100+ hardware -- "
"nvFP4 takes priority. Set use_nvfp4=False to use INT8 instead."
)
if msg not in _alpha_config_warned:
_alpha_config_warned.add(msg)
warnings.warn(msg)
_INT8_DISABLED_REASON = msg
return False
return True
def apply_int8_to_model(model: nn.Module, group_size: int = 128) -> int:
"""Quantize eligible Linear layers to INT8 (Jetfire-style, dynamic activations).
Config selection priority:
1. Int8DynamicActivationInt8WeightConfig() -- stable torchao, per-channel
weights + dynamic per-tensor INT8 activations. Best for T4 throughput.
2. Int8WeightOnlyConfig(group_size=N) -- weight-only, activations stay fp16.
Only used as fallback if (1) is unavailable (shouldn't happen on stable).
Works on T4 (sm_75+). Fused kernel emitted by torch.compile via Inductor.
Must be called BEFORE torch.compile().
"""
def _filter_with_gs(module: nn.Module, fqn: str) -> bool:
if not _int8_filter(module, fqn):
return False
in_f = module.weight.shape[1]
if in_f % 32 != 0: # 32 = minimum alignment for any INT8 kernel
return False
return True
# torchao replaces module.weight in place and keeps the nn.Linear object, so
# identity must be tracked on the weight (same test apply_nvfp4_to_model uses).
before_ids = {fqn: id(m.weight) for fqn, m in model.named_modules() if _filter_with_gs(m, fqn)}
if _INT8_AVAILABLE and _Int8Config is not None:
config_obj = _Int8Config()
elif _INT8_GROUPED_AVAILABLE and _Int8WOConfig is not None:
warnings.warn(
f"[INT8] Int8DynamicActivationInt8WeightConfig unavailable -- "
f"falling back to Int8WeightOnlyConfig(group_size={group_size}). "
f"Activations will stay fp16 (weight-only quant).",
)
config_obj = _Int8WOConfig(group_size=group_size)
else:
raise RuntimeError("[INT8] No usable INT8 config found in torchao -- pip install torchao")
_torchao_quantize(model, config_obj, filter_fn=_filter_with_gs)
current = dict(model.named_modules())
count = 0
for fqn, old_id in before_ids.items():
mod = current.get(fqn)
if mod is not None and getattr(mod, "weight", None) is not None and id(mod.weight) != old_id:
count += 1
model._quant_mode = "int8"
model._use_int8 = True
model._use_nvfp4 = False
return count
def _alpha_config_attr(name: str, default):
if not hasattr(alpha_config, name):
warnings.warn(
f"config module has no attribute {name!r} -- falling back to default "
f"{default!r}. If this is unexpected, check for a casing mismatch "
"or rename in your config.py."
)
return default
return getattr(alpha_config, name)
try:
from liger_kernel.ops.rope import LigerRopeFunction as _LigerRopeFn
except ImportError:
_LigerRopeFn = None
try:
from liger_kernel.transformers.fused_linear_cross_entropy import (
LigerFusedLinearCrossEntropyLoss as _LigerFusedLinearCrossEntropyLoss,
)
except ImportError:
_LigerFusedLinearCrossEntropyLoss = None
_liger_warned: set[str] = set()
def _liger_warn_once(key: str, msg: str) -> None:
if key not in _liger_warned:
_liger_warned.add(key)
warnings.warn(f"[model.py/liger] {msg}", stacklevel=3)
def _liger_cuda_gate(x: torch.Tensor) -> bool:
return x.device.type == "cuda"
_CAP_CACHE: dict = {}
def _liger_fused_ce_cuda_gate(x: torch.Tensor) -> bool:
"""Liger's fused linear CE backward has a dangling-buffer bug on sm_75 (T4/Turing)
under FP16 autocast + torch.compile: grad_output is read after its CUDA buffer is
freed, producing cudaErrorIllegalAddress in the scalar equality check at backward
line 294. Block sm_75 specifically; sm_80+ (Ampere+) is unaffected."""
if x.device.type != "cuda":
return False
if x.device not in _CAP_CACHE:
_CAP_CACHE[x.device] = torch.cuda.get_device_capability(x.device)
major, minor = _CAP_CACHE[x.device]
if major < 8: # sm_75 = Turing, sm_70 = Volta -- both affected
return False
return True
def _use_liger_config() -> bool:
return bool(_alpha_config_attr('use_liger', False))
# Config is fixed for the run: resolve the Liger switches once, not on every forward.
_LIGER_RMSNORM_ON = _use_liger_config() and _LigerRMSNormFn is not None
_LIGER_CE_ON = _use_liger_config() and _LigerFusedLinearCrossEntropyLoss is not None
@torch.compiler.disable
def _liger_rmsnorm_attempt(x: torch.Tensor, weight: torch.Tensor, eps: float):
try:
out = _LigerRMSNormFn.apply(x, weight, eps)
kernel._record_backend("liger_rmsnorm_success")
return out
except Exception as e: # noqa: BLE001 -- must never crash training
kernel._record_backend(f"liger_rmsnorm_fallback:{type(e).__name__}")
_liger_warn_once(
f"rmsnorm_fail_{type(e).__name__}",
f"Liger RMSNorm failed on {x.device} ({e!r}) -- falling back to native/eager RMSNorm.",
)
return None
def _triton_warn_once(key, msg: str) -> None:
if key not in _triton_warned:
_triton_warned.add(key)
warnings.warn(f"[model.py/triton] {msg}", stacklevel=3)
def triton_status() -> dict:
"""Which (op, key) pairs were verified and engaged vs fell back to eager."""
return {"available": _HAS_TRITON, "verified": sorted(map(str, _TRITON_OK)),
"fell_back": sorted(map(str, _TRITON_BAD))}
def _as_list(out):
return list(out) if isinstance(out, (tuple, list)) else [out]
def _triton_close(a: torch.Tensor, b: torch.Tensor, what: str, grad: bool = False) -> None:
dt = b.dtype
tol = {torch.float32: 1e-3, torch.float16: 1e-2, torch.bfloat16: 3e-2}.get(dt, 3e-2)
if grad:
tol *= 2
af, bf = a.float(), b.float()
d = (af - bf).abs().max().item()
scale = bf.abs().max().item()
if not (d <= tol * scale + 1e-6): # also catches NaN
raise AssertionError(f"{what} mismatch vs eager: max|diff|={d:.3e}, ref max|.|={scale:.3e}, tol={tol}")
def _triton_probe(tri_fn, ref_fn, probe_args) -> None:
"""One-time check on a small slice: shape, dtype, values, grad flow, grad values."""
def mk(a):
if torch.is_tensor(a):
b = a.detach().clone()
if a.requires_grad and a.is_floating_point() and torch.is_grad_enabled():
b.requires_grad_(True)
return b
return a
a_t = [mk(a) for a in probe_args]
a_r = [mk(a) for a in probe_args]
out_t, out_r = _as_list(tri_fn(*a_t)), _as_list(ref_fn(*a_r))
if len(out_t) != len(out_r):
raise AssertionError(f"output arity {len(out_t)} != eager {len(out_r)}")
for i, (x, y) in enumerate(zip(out_t, out_r)):
if x.shape != y.shape:
raise AssertionError(f"output[{i}] shape {tuple(x.shape)} != eager {tuple(y.shape)}")
if x.dtype != y.dtype:
raise AssertionError(f"output[{i}] dtype {x.dtype} != eager {y.dtype}")
if y.is_floating_point(): # integer outputs (top-k indices / counts): shape+dtype only (ties may differ)
_triton_close(x, y, f"output[{i}]")
wrt = [i for i, a in enumerate(a_t) if torch.is_tensor(a) and a.requires_grad]
if not wrt:
return
out_t = [o for o in out_t if o.is_floating_point()]
out_r = [o for o in out_r if o.is_floating_point()]
if not all(o.requires_grad for o in out_t):
raise AssertionError("output has no grad_fn although inputs require grad (no backward?)")
gs = []
for y in out_r:
n = y.numel()
gs.append(torch.cos(torch.arange(n, device=y.device, dtype=torch.float32)).reshape(y.shape).to(y.dtype))
g_t = torch.autograd.grad(out_t, [a_t[i] for i in wrt], gs, allow_unused=True)
g_r = torch.autograd.grad(out_r, [a_r[i] for i in wrt], gs, allow_unused=True)
for i, x, y in zip(wrt, g_t, g_r):
if x is None and y is None:
continue
if x is None or y is None:
raise AssertionError(f"grad[arg {i}] present on only one side (triton={x is not None})")
_triton_close(x, y, f"grad[arg {i}]", grad=True)
@torch.compiler.disable
def _triton_attempt(op, key, tri_fn, ref_fn, args, probe_args):
"""Run tri_fn(*args) or return None (caller then runs its eager path).
probe_args may be a callable (evaluated only on the first call for this key)."""
tag = (op, key)
if tag in _TRITON_BAD:
return None
try:
if tag not in _TRITON_OK:
_triton_probe(tri_fn, ref_fn, probe_args() if callable(probe_args) else probe_args)
_TRITON_OK.add(tag)
kernel._record_backend(f"triton_{op}_verified")
out = tri_fn(*args)
if torch.is_grad_enabled() and any(torch.is_tensor(a) and a.requires_grad for a in args):
if not all(o.requires_grad for o in _as_list(out) if o.is_floating_point()):
raise RuntimeError("output has no grad_fn although inputs require grad")
return out
except Exception as e: # noqa: BLE001 -- must never crash training
_TRITON_BAD.add(tag)
kernel._record_backend(f"triton_{op}_fallback:{type(e).__name__}")
_triton_warn_once(
tag,
f"triton {op} {key} disabled -> eager fallback ({type(e).__name__}: {e}). "
f"If this is a signature mismatch, fix the adapter next to _tri_{op}.",
)
return None
# -- Adapters: the ONLY place that knows triton_kernel's call signatures --------
def _tri_rmsnorm(x, weight, eps):
return _triton_kernel.rmsnorm(x, weight, eps)
def _tri_swiglu(gate, value):
return _triton_kernel.swiglu(gate, value)
def _tri_rope_qk(q, k, cos, sin, rope_dim):
return _triton_kernel.rope_qk(q, k, cos, sin, rope_dim)
def _tri_fused_ce(h, weight, t):
return _triton_kernel.fused_linear_cross_entropy(h, weight, t, ignore_index=-1)
# -- kernel_triton fusions (attention post, QK-norm+RoPE, add+RMSNorm, router, MoE dispatch/combine, FFN) --
# Same contract as the triton_kernel ops above: verified once per (op, key) against eager on a small slice
# (outputs and grads), then used; any failure -> warn once + eager fallback. Disable all with
# ALPHA_DISABLE_TRITON=1, or per op with KT_OFF=attn_post,qk_rope,add_rmsnorm,router,route,ffn.
def _kt_on(x, op: str) -> bool:
fn = getattr(_moe_kernel, "use_fused", None)
return fn is not None and os.environ.get("ALPHA_DISABLE_TRITON") != "1" and bool(fn(x, op))
_KT_YDT: dict = {}
def _kt_norm_dtype(x, weight, eps):
"""dtype the eager RMSNorm path returns for (x.dtype, weight.dtype) -- learned once from a 1-row eager call."""
key = (x.dtype, weight.dtype)
if key not in _KT_YDT:
_KT_YDT[key] = _rmsnorm_eager(
torch.zeros(1, weight.numel(), dtype=x.dtype, device=x.device), weight, eps).dtype
return _KT_YDT[key]
def _rmsnorm_eager(x, weight, eps):
# weight cast to the input dtype: F.rms_norm falls back to a slow composite path on a dtype mismatch.
return _native_rms_norm(x, (x.shape[-1],), weight.to(x.dtype), eps)
def _swiglu_eager(gate, value):
return F.silu(gate) * value
class RMSNorm(nn.Module):
"""Root Mean Square Layer Normalization (used in modern transformers)"""
def __init__(self, dim, eps=1e-5):
super().__init__()
self.eps = eps
self.weight = nn.Parameter(torch.ones(dim))
self.dim = dim
def forward(self, x: torch.Tensor) -> torch.Tensor:
if _HAS_TRITON and x.is_cuda:
out = _triton_attempt(
"rmsnorm", (x.dtype, x.ndim, x.is_contiguous()),
_tri_rmsnorm, _rmsnorm_eager,
(x, self.weight, self.eps), (x[:1], self.weight, self.eps),
)
if out is not None:
return out
if _LIGER_RMSNORM_ON and x.is_cuda:
out = _liger_rmsnorm_attempt(x, self.weight, self.eps)
if out is not None:
return out
return _native_rms_norm(x, (self.dim,), self.weight.to(x.dtype), self.eps)
def rotate_half(x):
x1, x2 = x.chunk(2, dim=-1)
return torch.cat((-x2, x1), dim=-1)
@dataclass
class AttentionOutput:
"""Uniform return type so callers can optionally inspect weights / cache cost."""
output: torch.Tensor
attention_weights: Optional[torch.Tensor] = None
kv_cache_bytes: Optional[int] = None
selected_indices: Optional[torch.Tensor] = None
def module_output_tensor(x):
return x.output if isinstance(x, AttentionOutput) else x
def reshape_heads(x: torch.Tensor, num_heads: int, head_dim: int) -> torch.Tensor:
b, t, _ = x.shape
return x.view(b, t, num_heads, head_dim).transpose(1, 2)
def merge_heads(x: torch.Tensor) -> torch.Tensor:
b, h, t, d = x.shape
return x.transpose(1, 2).contiguous().view(b, t, h * d)
def expand_kv_heads(x: torch.Tensor, num_heads: int) -> torch.Tensor:
b, h_kv, t, d = x.shape
if h_kv == num_heads:
return x
if num_heads % h_kv != 0:
raise ValueError(f"num_heads ({num_heads}) must be divisible by kv heads ({h_kv})")
reps = num_heads // h_kv
return x.repeat_interleave(reps, dim=1)
@functools.lru_cache(maxsize=64)
def _rope_cache(seq_len: int, dim: int, device, dtype, base: float = 10000.0):
inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2, device=device, dtype=torch.float32) / dim))
t = torch.arange(seq_len, device=device, dtype=torch.float32)
freqs = torch.outer(t, inv_freq)
emb = torch.cat((freqs, freqs), dim=-1)
return emb.cos().to(dtype), emb.sin().to(dtype)
def build_rope_cache(seq_len: int, dim: int, base: float = 10000.0):
inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2, dtype=torch.float32) / dim))
t = torch.arange(seq_len, dtype=torch.float32)
freqs = torch.outer(t, inv_freq)
emb = torch.cat((freqs, freqs), dim=-1)
return emb.cos(), emb.sin()
def apply_rope(
x: torch.Tensor,
positions: torch.Tensor | None = None,
rope_dim: int | None = None,
max_position: int | None = None,
cos_full: torch.Tensor | None = None,
sin_full: torch.Tensor | None = None,
) -> torch.Tensor:
b, h, t, d = x.shape
rope_dim = d if rope_dim is None else rope_dim
is_sequential = positions is None
if positions is None:
positions = torch.arange(t, device=x.device)
if cos_full is None or sin_full is None:
if max_position is None:
max_position = t
max_pos = int(max_position) if max_position is not None else int(positions.max().item()) + 1 if positions.numel() > 0 else 1
cos_full, sin_full = _rope_cache(max_pos, rope_dim, x.device, x.dtype)
if is_sequential:
if t > cos_full.shape[0]:
raise ValueError(
f"RoPE table too small: highest requested position="
f"{t - 1}, table size={cos_full.shape[0]}. If this "
f"happened during generate(), set gen_headroom in config.py "
f"to however many extra positions past CONTEXT (block_size) "
f"generation needs -- see GPT.__init__'s gen_headroom "
f"wiring. This is separate from max_gen_tokens (a hard cap "
f"on tokens produced per generate() call, unrelated to "
f"RoPE table size). Default gen_headroom is 0 if unset."
)
elif positions is not None and positions.numel() > 0:
highest_pos = int(positions.max().item())
if highest_pos >= cos_full.shape[0]:
raise ValueError(
f"RoPE table too small: highest requested position="
f"{highest_pos}, table size={cos_full.shape[0]}. If this "
f"happened during generate(), set gen_headroom in config.py "
f"to however many extra positions past CONTEXT (block_size) "
f"generation needs -- see GPT.__init__'s gen_headroom "
f"wiring. This is separate from max_gen_tokens (a hard cap "
f"on tokens produced per generate() call, unrelated to "
f"RoPE table size). Default gen_headroom is 0 if unset."
)
if is_sequential:
cos = cos_full[:t].to(x.dtype).unsqueeze(0).unsqueeze(0)
sin = sin_full[:t].to(x.dtype).unsqueeze(0).unsqueeze(0)
else:
cos = cos_full[positions].to(x.dtype).unsqueeze(0).unsqueeze(0)
sin = sin_full[positions].to(x.dtype).unsqueeze(0).unsqueeze(0)
x_rot, x_pass = x[..., :rope_dim], x[..., rope_dim:]
x_rot = (x_rot * cos) + (rotate_half(x_rot) * sin)
return torch.cat([x_rot, x_pass], dim=-1) if x_pass.shape[-1] > 0 else x_rot
def _eager_rope_qk(q, k, cos, sin, rope_dim):
return (
apply_rope(q, positions=None, rope_dim=rope_dim, cos_full=cos, sin_full=sin),
apply_rope(k, positions=None, rope_dim=rope_dim, cos_full=cos, sin_full=sin),
)
def apply_rope_qk(
q: torch.Tensor,
k: torch.Tensor,
cos_full: torch.Tensor,
sin_full: torch.Tensor,
rope_dim: int,
positions: torch.Tensor | None = None,
) -> tuple[torch.Tensor, torch.Tensor]:
b, h, t, d = q.shape
hk = k.shape[1]
if positions is None and _HAS_TRITON and q.is_cuda:
cos_t = cos_full[:t].to(q.dtype)
sin_t = sin_full[:t].to(q.dtype)
result = _triton_attempt(
"rope_qk", (q.dtype, h == hk), _tri_rope_qk, _eager_rope_qk,
(q, k, cos_t, sin_t, rope_dim), (q[:1], k[:1], cos_t, sin_t, rope_dim),
)
if result is not None:
return tuple(result)
if (positions is None
and _use_liger_config()
and _LigerRopeFn is not None
and _liger_cuda_gate(q)
and rope_dim == d
and h == hk):
result = _liger_rope_attempt(q, k, cos_full[:t], sin_full[:t])
if result is not None:
return result
q_rot = apply_rope(q, positions=positions, rope_dim=rope_dim, cos_full=cos_full, sin_full=sin_full)
k_rot = apply_rope(k, positions=positions, rope_dim=rope_dim, cos_full=cos_full, sin_full=sin_full)
return q_rot, k_rot
@torch.compiler.disable
def _liger_rope_attempt(q: torch.Tensor, k: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor):
try:
cos = cos.to(q.dtype)
sin = sin.to(q.dtype)
q_rot, k_rot = _LigerRopeFn.apply(q, k, cos, sin)
kernel._record_backend("liger_rope_success")
return q_rot, k_rot
except Exception as e: # noqa: BLE001 -- must never crash training
kernel._record_backend(f"liger_rope_fallback:{type(e).__name__}")
_liger_warn_once(
f"rope_fail_{type(e).__name__}",
f"Liger RoPE failed on {q.device} ({e!r}) -- falling back to eager RoPE.",
)
return None
@torch.compiler.disable
def _liger_fused_ce_attempt(hidden: torch.Tensor, weight: torch.Tensor, targets: torch.Tensor):
try:
loss_fn = _LigerFusedLinearCrossEntropyLoss(ignore_index=-1)
# LigerFusedLinearCrossEntropyLoss.forward(lin_weight, _input, target): weight FIRST.
loss = loss_fn(weight, hidden.reshape(-1, hidden.size(-1)), targets.reshape(-1))
kernel._record_backend("liger_fused_ce_success")
return loss
except Exception as e: # noqa: BLE001 -- must never crash training
kernel._record_backend(f"liger_fused_ce_fallback:{type(e).__name__}")
_liger_warn_once(
f"fused_ce_fail_{type(e).__name__}",
f"Liger fused linear CE failed on {hidden.device} ({e!r}) -- "
f"falling back to eager lm_head + F.cross_entropy.",
)
return None
def chunked_cross_entropy(
hidden: torch.Tensor,
weight: torch.Tensor,
targets: torch.Tensor,
ignore_index: int = -1,
chunk_size: int = 4096,
) -> torch.Tensor:
"""Triton fused linear-CE when verified, else the chunked eager path below."""
if _HAS_TRITON and hidden.is_cuda and ignore_index == -1:
h = hidden.reshape(-1, hidden.size(-1))
t = targets.reshape(-1)
valid = t != ignore_index
if bool(valid.any()): # one sync; the eager path syncs on n_valid too
def _probe():
# 192 valid + up to 64 ignored rows, so ignore_index handling is verified too.
vi = valid.nonzero(as_tuple=True)[0][:192]
ii = (~valid).nonzero(as_tuple=True)[0][:64]
idx = torch.cat([vi, ii])
return (h[idx], weight, t[idx])
out = _triton_attempt(
"fused_ce", (h.dtype,), _tri_fused_ce,
lambda hh, ww, tt: _chunked_cross_entropy_eager(hh, ww, tt, ignore_index=-1),
(h, weight, t), _probe,
)
if out is not None:
return out
return _chunked_cross_entropy_eager(hidden, weight, targets, ignore_index, chunk_size)
def _chunked_cross_entropy_eager(
hidden: torch.Tensor, # (B, T, D) or (N, D) already flattened
weight: torch.Tensor, # (vocab_size, D)
targets: torch.Tensor, # (B, T) or (N,) already flattened
ignore_index: int = -1,
chunk_size: int = 4096,
) -> torch.Tensor:
h = hidden.reshape(-1, hidden.size(-1)) # (N, D)
t = targets.reshape(-1) # (N,)
N = h.size(0)
total_loss = torch.zeros((), device=h.device, dtype=torch.float32)
n_valid = (t != ignore_index).sum().float()
# No data-dependent early return: an all-ignored batch still gets zero loss with a grad edge,
# because every checkpointed chunk stays in the graph. The clamp below avoids 0/0.
def _chunk_loss(h_chunk: torch.Tensor, t_chunk: torch.Tensor) -> torch.Tensor:
# Recomputed during backward via checkpoint -- only the scalar loss is
# retained, not the (chunk_size, vocab) fp32 logit tensor.
logits_chunk = F.linear(h_chunk, weight).float()
return F.cross_entropy(logits_chunk, t_chunk, ignore_index=ignore_index, reduction="sum")
for start in range(0, N, chunk_size):
end = min(start + chunk_size, N)
h_chunk = h[start:end] # (C, D)
t_chunk = t[start:end] # (C,)
# checkpoint recomputes the F.linear in backward; only the scalar
# loss_chunk is kept alive in the autograd graph between forward and
# backward, so the (C, vocab) fp32 logit tensor is never retained.
loss_chunk = torch.utils.checkpoint.checkpoint(
_chunk_loss, h_chunk, t_chunk, use_reentrant=False
)
total_loss = total_loss + loss_chunk
return total_loss / n_valid.clamp(min=1)
def make_causal_mask(q_len: int, k_len: int, device, q_offset: int = 0) -> torch.Tensor:
q_idx = (torch.arange(q_len, device=device).view(q_len, 1) + q_offset)
k_idx = torch.arange(k_len, device=device).view(1, k_len)
return torch.where(k_idx <= q_idx, torch.zeros(1, device=device), torch.full((1,), float("-inf"), device=device))
def make_sliding_window_causal_mask(q_len: int, k_len: int, window_size: int, device, q_offset: int = 0) -> torch.Tensor:
q_idx = (torch.arange(q_len, device=device).view(q_len, 1) + q_offset)
k_idx = torch.arange(k_len, device=device).view(1, k_len)
visible = (k_idx <= q_idx) & (k_idx > q_idx - window_size)
return torch.where(visible, torch.zeros(1, device=device), torch.full((1,), float("-inf"), device=device))
def masked_softmax(scores: torch.Tensor, mask: torch.Tensor | None, dim: int = -1) -> torch.Tensor:
if mask is not None:
scores = scores + mask.to(scores.dtype)
return torch.softmax(scores.float(), dim=dim).to(scores.dtype)
def estimate_kv_cache_bytes(
batch_size, seq_len, num_kv_heads, head_dim, dtype,
compression_ratio: int = 1, window_size: int | None = None,
) -> int:
elem_size = torch.zeros(1, dtype=dtype).element_size()
effective_len = max(1, -(-seq_len // compression_ratio)) # ceil div
if window_size is not None:
effective_len = min(effective_len, window_size)
return int(batch_size * num_kv_heads * effective_len * head_dim * elem_size * 2)
class KVCache:
"""Per-layer K/V buffers for incremental decoding.
Buffers are allocated lazily on first write (dtype/device/batch follow k) and
written in place, so a decode step costs O(T) attention reads and no torch.cat.
Invariant: after a forward, `pos` == number of tokens already cached.
"""
def __init__(self, n_layers: int, capacity: int):
self.capacity = int(capacity)
self.pos = 0
self._layers = [{"capacity": self.capacity} for _ in range(n_layers)]
def layer(self, i: int) -> dict:
return self._layers[i]
def reset(self) -> None:
self.pos = 0 # stale data past pos is never read
# -- Static architectural constants ------------------------------------------
# XSA, QK-Norm and Gated Attention are always on. No config flags: the
# checkpoint shape is fully determined by (n_embd, n_head).
#
# Attn-gate input window: how many dims of the post-norm block input the
# per-head gate reads. Smaller = fewer params. 16 is the speedrun value;
# 0 means "use full n_embd" (dense gate).
ATTN_GATE_WINDOW_DIMS = 16
class DenseMHA(nn.Module):
def __init__(
self,
hidden_size: int,
num_heads: int,
head_dim: int | None = None,
num_kv_heads: int | None = None,
dropout: float = 0.0,
use_rope: bool = True,
causal: bool = True,
bias: bool = True,
max_seq_len: int = 4096,
window_size: int | None = None,
tp_size: int = 1,
rope_dim: int | None = None,
) -> None:
super().__init__()
if head_dim is None:
if hidden_size % num_heads != 0:
raise ValueError("hidden_size must be divisible by num_heads when head_dim is omitted")
head_dim = hidden_size // num_heads
num_kv_heads = num_heads if num_kv_heads is None else num_kv_heads
if num_heads % num_kv_heads != 0:
raise ValueError(f"num_heads ({num_heads}) must be divisible by num_kv_heads ({num_kv_heads})")
if tp_size > 1:
raise NotImplementedError(
"tensor_parallel_size > 1 is not implemented: per-rank weights are "
"independently initialized (never sharded from a canonical weight), "
"so DDP gradient averaging corrupts them. Set tensor_parallel_size=1. "
"Real TP needs: init full weights once, slice per rank, and a "
"skip_out_proj guard that honors tp_size."
)
if num_heads % tp_size != 0:
raise ValueError(
f"tensor_parallel_size ({tp_size}) must divide num_heads ({num_heads}) "
f"for this layer -- reduce tensor_parallel_size or increase n_head."
)
local_num_heads = num_heads // tp_size
if num_kv_heads < tp_size:
local_num_kv_heads = num_kv_heads
else:
local_num_kv_heads = num_kv_heads // tp_size if num_kv_heads % tp_size == 0 else num_kv_heads
self.hidden_size = hidden_size
self.num_heads = local_num_heads
self.num_kv_heads = local_num_kv_heads
self.head_dim = head_dim
self.inner_dim = local_num_heads * head_dim
self.use_rope = use_rope
self.causal = causal
self.tp_size = tp_size
self.tp_group = None
self.window_size = window_size
self.rope_dim = head_dim if rope_dim is None else rope_dim
self._q_width = self.inner_dim
self._k_width = self.num_kv_heads * head_dim
self._v_width = self.num_kv_heads * head_dim
self.qkv_proj = nn.Linear(hidden_size, self._q_width + self._k_width + self._v_width, bias=bias)
self.out_proj = nn.Linear(self.inner_dim, hidden_size, bias=bias)
self.dropout = nn.Dropout(dropout)
# -- Always-on QK-Norm --------------------------------------------------
# Per-head RMSNorm over head_dim on q and k, before the rope split.
# Bounds attention-logit scale so softmax doesn't saturate.
self.q_norm = RMSNorm(self.head_dim, eps=1e-6)
self.k_norm = RMSNorm(self.head_dim, eps=1e-6)
# -- Always-on per-head XSA strength gate -------------------------------
# tanh(alpha) in (-1, 1). Zero-init -> gate=0 -> step-0 forward is
# numerically identical to vanilla SA.
self.xsa_alpha = nn.Parameter(torch.zeros(self.num_heads))
self._xsa_v_reps = local_num_heads // local_num_kv_heads
# -- Always-on Gated Attention (Qiu et al., NeurIPS 2025) ---------------
# Per-head sigmoid gate on the SDPA output, before XSA/out_proj. Reads
# the first ATTN_GATE_WINDOW_DIMS dims of the post-norm block input.
# Zero-init -> 2*sigmoid(0) = 1.0 -> identity at step 0.
# NOTE: GPT.__init__ re-zeroes this after self.apply(_init_weights),
# which would otherwise overwrite it with N(0, 0.02).
self.attn_gate_window = min(ATTN_GATE_WINDOW_DIMS, hidden_size) or hidden_size
self.attn_gate_proj = nn.Linear(self.attn_gate_window, self.num_heads, bias=False)
nn.init.zeros_(self.attn_gate_proj.weight)
if use_rope:
rope_cos, rope_sin = build_rope_cache(max_seq_len, self.rope_dim)
self.register_buffer("rope_cos", rope_cos, persistent=False)
self.register_buffer("rope_sin", rope_sin, persistent=False)
# XSA pos-0 guard mask: registered once at init so forward() never
# allocates a fresh (T,) tensor. Non-persistent: re-created from
# max_seq_len on load, costs nothing in the state_dict.
self.register_buffer(
"_xsa_pos_mask",
torch.ones(max_seq_len),
persistent=False,
)
# Slot 0 is always masked off (position-0 has no context beyond itself).
self._xsa_pos_mask[0] = 0.0
def forward(
self,
hidden_states: torch.Tensor,
output_attentions: bool = False,
return_kv_cache_estimate: bool = False,
skip_out_proj: bool = False,
positions: torch.Tensor | None = None,
kv_cache: dict | None = None,
cache_pos: int = 0,
) -> torch.Tensor | AttentionOutput:
batch_size, seq_len, _ = hidden_states.shape
if kv_cache is not None:
if output_attentions or positions is not None or not self.causal:
raise ValueError("kv_cache requires causal attention, positions=None, output_attentions=False")
if cache_pos > 0 and seq_len != 1:
raise NotImplementedError("cached forward: one full prefill at position 0, then one token per step")
if cache_pos + seq_len > kv_cache["capacity"]:
raise ValueError(f"KV cache capacity {kv_cache['capacity']} exceeded at position {cache_pos + seq_len}")
qkv = self.qkv_proj(hidden_states)
q = None
if self.use_rope:
if kv_cache is not None:
rope_c = self.rope_cos[cache_pos:cache_pos + seq_len]
rope_s = self.rope_sin[cache_pos:cache_pos + seq_len]
else:
rope_c, rope_s = self.rope_cos, self.rope_sin
# Fused QKV split + per-head QK-RMSNorm + RoPE + (B,H,T,hd) layout (+ v copy), one kernel each way.
if self.use_rope and positions is None and qkv.is_cuda and _kt_on(qkv, "qk_rope"):
_wq, _wk = self.q_norm.weight, self.k_norm.weight
_eps = self.q_norm.eps
_odt = _kt_norm_dtype(qkv, _wq, _eps)
_rc, _rs = rope_c[:seq_len], rope_s[:seq_len]
def _qk_ref(qkv_, wq_, wk_):
_q, _k, _v = qkv_.split([self._q_width, self._k_width, self._v_width], dim=-1)
_q = _rmsnorm_eager(reshape_heads(_q, self.num_heads, self.head_dim), wq_, _eps)
_k = _rmsnorm_eager(reshape_heads(_k, self.num_kv_heads, self.head_dim), wk_, _eps)
_q, _k = _eager_rope_qk(_q, _k, _rc, _rs, self.rope_dim)
return _q, _k, reshape_heads(_v, self.num_kv_heads, self.head_dim)
_r = _triton_attempt(
"qk_rope", (qkv.dtype, self.num_heads, self.num_kv_heads, self.head_dim, self.rope_dim),
lambda qkv_, wq_, wk_: _moe_kernel.qk_rope(
qkv_, wq_, wk_, _rc, _rs, self.num_heads, self.num_kv_heads, self.head_dim,
self.rope_dim, _eps, _odt),
_qk_ref,
(qkv, _wq, _wk),
lambda: (qkv[:1],
torch.linspace(0.5, 1.5, self.head_dim, device=qkv.device, dtype=_wq.dtype).requires_grad_(),
torch.linspace(1.5, 0.5, self.head_dim, device=qkv.device, dtype=_wk.dtype).requires_grad_()),
)
if _r is not None:
q, k, v = _r
if q is None:
q_flat, k_flat, v_flat = qkv.split([self._q_width, self._k_width, self._v_width], dim=-1)
q = reshape_heads(q_flat, self.num_heads, self.head_dim)
k = reshape_heads(k_flat, self.num_kv_heads, self.head_dim)
v = reshape_heads(v_flat, self.num_kv_heads, self.head_dim)
# QK-Norm (always on): per-head RMSNorm over head_dim, before rope.
q = self.q_norm(q)
k = self.k_norm(k)
if self.use_rope:
q, k = apply_rope_qk(
q,
k,
cos_full=rope_c,
sin_full=rope_s,
rope_dim=self.rope_dim,
positions=positions,
)
if kv_cache is not None:
if "k" not in kv_cache:
cap = kv_cache["capacity"]
kv_cache["k"] = k.new_empty(batch_size, self.num_kv_heads, cap, self.head_dim)
kv_cache["v"] = v.new_empty(batch_size, self.num_kv_heads, cap, self.head_dim)
kv_cache["k"][:, :, cache_pos:cache_pos + seq_len] = k
kv_cache["v"][:, :, cache_pos:cache_pos + seq_len] = v
if output_attentions:
if self.num_kv_heads != self.num_heads:
reps = self.num_heads // self.num_kv_heads
k_eager = k.repeat_interleave(reps, dim=1)
v_eager = v.repeat_interleave(reps, dim=1)
else:
k_eager, v_eager = k, v
scores = torch.matmul(q, k_eager.transpose(-2, -1)) / math.sqrt(self.head_dim)
mask = None
if self.window_size is not None:
mask = make_sliding_window_causal_mask(
seq_len, seq_len, self.window_size, hidden_states.device
).view(1, 1, seq_len, seq_len)
elif self.causal:
mask = make_causal_mask(seq_len, seq_len, hidden_states.device).view(1, 1, seq_len, seq_len)
weights = masked_softmax(scores, mask, dim=-1)
weights = self.dropout(weights)
context = torch.matmul(weights, v_eager)
elif kv_cache is not None and cache_pos > 0:
# Single-token decode: the new token sees every cached key (or the last
# `window_size` of them for sliding-window layers), so no mask is needed.
# GQA without copying K/V: heads of one KV group become the "query rows".
end = cache_pos + 1
start = 0 if self.window_size is None else max(0, end - self.window_size)
k_all = kv_cache["k"][:, :, start:end].to(q.dtype)
v_all = kv_cache["v"][:, :, start:end].to(q.dtype)
reps = self.num_heads // self.num_kv_heads
q_g = q.reshape(batch_size, self.num_kv_heads, reps, self.head_dim)
ctx = F.scaled_dot_product_attention(q_g, k_all, v_all)
context = ctx.reshape(batch_size, self.num_heads, 1, self.head_dim)
weights = None
else:
context = kernel.fused_attention(
q,
k,
v,
causal=self.causal,
window_size=self.window_size,
dropout_p=self.dropout.p,
training=self.training,
)
weights = None
# ── Gated Attention (Qiu et al., NeurIPS 2025 Best Paper) ───────────────
# Per-head sigmoid gate modulates the SDPA output before XSA/out_proj.
# Gate input: first `attn_gate_window` dims of the post-norm block input
# (hidden_states). Sparse window keeps the gate head lightweight.
# 2*sigmoid(z) is used so the zero-init projection gives gate=1 (identity)
# at step 0; the head learns to suppress or amplify over training.
gate_in = hidden_states[..., :self.attn_gate_window] # (B, T, gw)
gate = self.attn_gate_proj(gate_in) # (B, T, num_heads)
# Fused gated-attention + XSA + position-0 guard + head merge (one kernel each way). The probe uses
# non-zero gate logits / alpha so the zero-init (identity) state doesn't hide a wrong XSA path.
_merged = None
if (not output_attentions and context.is_cuda and context.shape[-1] & (context.shape[-1] - 1) == 0
and _kt_on(context, "attn_post")):
def _attn_probe(c_=context, v_=v, g_=gate):
_pg = (0.5 * torch.cos(torch.arange(g_[:1].numel(), device=g_.device, dtype=torch.float32))
).reshape(g_[:1].shape).to(g_.dtype).requires_grad_()
_pa = torch.linspace(-0.8, 0.8, self.num_heads, device=g_.device,
dtype=self.xsa_alpha.dtype).requires_grad_()
return (c_[:1], v_[:1], _pg, _pa)
_merged = _triton_attempt(
"attn_post", (context.dtype, self.num_heads, self.num_kv_heads, self.head_dim, cache_pos > 0),
lambda c_, v_, g_, a_: _moe_kernel.attn_post(c_, v_, g_, a_, cache_pos),
lambda c_, v_, g_, a_: _moe_kernel.attn_post_eager(c_, v_, g_, a_, cache_pos),
(context, v, gate, self.xsa_alpha),
_attn_probe,
)
if _merged is not None:
context = None
merged_context = _merged
else:
gate = 2.0 * torch.sigmoid(gate) # (0, 2), =1.0 at init
gate = gate.transpose(1, 2).unsqueeze(-1) # (B, H, T, 1)
context = context * gate
# ────────────────────────────────────────────────────────────────────────
if context is not None:
v_xsa = v
if self._xsa_v_reps > 1:
# GQA/MQA-aware broadcast: mathematically identical to
# v.repeat_interleave(self._xsa_v_reps, dim=1) (verified via
# torch.equal), but avoids materializing a full copy of v.
Bv, Hkv, Tv, Dv = v.shape
v_xsa = (
v.reshape(Bv, Hkv, 1, Tv, Dv)
.expand(Bv, Hkv, self._xsa_v_reps, Tv, Dv)
.reshape(Bv, Hkv * self._xsa_v_reps, Tv, Dv)
)
v_norm = v_xsa.norm(dim=-1, keepdim=True).clamp(min=1e-6)
v_unit = v_xsa / v_norm
proj_coeff = (context * v_unit).sum(dim=-1, keepdim=True)
# Learnable per-head gate (modded-nanoGPT style): tanh(alpha) in (-1, 1).
# alpha starts at 0 -> gate=0 -> forward pass is numerically identical
# to vanilla SA at step 0. Each head learns how much (if any) self-value
# component to remove, rather than a hard, deterministic subtraction.
gate = torch.tanh(self.xsa_alpha).view(1, -1, 1, 1)
correction = gate * proj_coeff * v_unit
# Position-0 guard: causal attention at position 0 has exactly one
# reachable key (itself), so context[0] == v[0] identically, at every
# step of training. XSA's projection therefore removes 100% of the
# context for that head, for any gate > 0. This is structural, not
# a training artifact -- there is no "context minus self" at
# position 0 because there is no context other than self there.
# Mask the correction off so the first token always keeps a
# full-strength value contribution.
T = context.shape[2]
# Slice the pre-built buffer; no allocation per forward. Applied for T == 1 as well:
# a 1-token input IS position 0 and must be masked exactly as it is in training.
mask = self._xsa_pos_mask[cache_pos:cache_pos + T].to(dtype=context.dtype).view(1, 1, T, 1)
correction = correction * mask
context = context - correction
merged_context = merge_heads(context)
# ─────────────────────────────────────────────────────────────────────
if skip_out_proj and self.tp_size == 1 and not output_attentions and not return_kv_cache_estimate:
return merged_context
output = self.out_proj(merged_context)
if self.tp_size > 1:
if self.tp_group is None:
raise RuntimeError(
"DenseMHA was built with tp_size>1 but tp_group was never "
"set -- call GPT.set_tp_group(group) after construction, "
"before the first forward pass."
)
torch.distributed.all_reduce(output, op=torch.distributed.ReduceOp.SUM, group=self.tp_group)
if output_attentions or return_kv_cache_estimate:
cache_bytes = None
if return_kv_cache_estimate:
cache_bytes = estimate_kv_cache_bytes(
batch_size, seq_len, self.num_kv_heads, self.head_dim,
hidden_states.dtype, window_size=self.window_size,
)
return AttentionOutput(output=output, attention_weights=weights if output_attentions else None, kv_cache_bytes=cache_bytes)
return output
class SwiGLU_FFN(nn.Module):
"""
SwiGLU Feed Forward Network.
"""
def __init__(self, d_model, ffn_mult=8/3, dropout=0.0, tp_size: int = 1):
super().__init__()
raw_hidden = d_model * ffn_mult
hidden_dim = int(((raw_hidden + 63) // 64) * 64)
if hidden_dim % tp_size != 0:
raise ValueError(
f"tensor_parallel_size ({tp_size}) must divide the FFN hidden_dim "
f"({hidden_dim} = round({d_model} * {ffn_mult}) -> next mult of 64) "
f"-- adjust ffn_mult or tensor_parallel_size."
)
local_hidden_dim = hidden_dim // tp_size
self.tp_size = tp_size
self.tp_group = None # set post-construction via GPT.set_tp_group()
self.W_gate = nn.Linear(d_model, local_hidden_dim, bias=False)
self.W_value = nn.Linear(d_model, local_hidden_dim, bias=False)
self.W_out = nn.Linear(local_hidden_dim, d_model, bias=False)
self.dropout = nn.Dropout(dropout)
def forward(self, x):
if (self.tp_size == 1 and (not self.training or self.dropout.p == 0.0)
and type(self.W_gate) is nn.Linear and type(self.W_value) is nn.Linear
and type(self.W_out) is nn.Linear and x.is_cuda and _kt_on(x, "ffn")):
out = _triton_attempt(
"ffn", (x.dtype, x.ndim),
lambda x_: _moe_kernel.swiglu_ffn(x_, self.W_gate.weight, self.W_value.weight, self.W_out.weight),
lambda x_: self.W_out(F.silu(self.W_gate(x_)) * self.W_value(x_)),
(x,), lambda: (x[:1],),
)
if out is not None:
return out
if (self.tp_size == 1 and type(self.W_gate.weight) is nn.Parameter
and type(self.W_value.weight) is nn.Parameter):
# One GEMM for both projections: [W_gate; W_value] stacked on the output dim, then split.
# Parameters and state_dict keys are unchanged; only the cat (2*H*D weights) is extra work.
gate_raw, value = F.linear(
x, torch.cat((self.W_gate.weight, self.W_value.weight), dim=0)
).chunk(2, dim=-1)
else:
gate_raw = self.W_gate(x)
value = self.W_value(x)
hidden = None
if _HAS_TRITON and gate_raw.is_cuda:
hidden = _triton_attempt(
"swiglu", (gate_raw.dtype,), _tri_swiglu, _swiglu_eager,
(gate_raw.contiguous(), value.contiguous()), (gate_raw[:1].contiguous(), value[:1].contiguous()),
)
if hidden is None:
hidden = F.silu(gate_raw) * value
hidden = self.dropout(hidden)
out = self.W_out(hidden)
if self.tp_size > 1:
if self.tp_group is None:
raise RuntimeError(
"SwiGLU_FFN was built with tp_size>1 but tp_group was never "
"set -- call GPT.set_tp_group(group) after construction, "
"before the first forward pass."
)
torch.distributed.all_reduce(out, op=torch.distributed.ReduceOp.SUM, group=self.tp_group)
return out # Return delta only
class AryaSparseMoE(nn.Module):
"""
DeepSeek-style Sparse Mixture of Experts.
Expects input to be ALREADY normalized (Pre-LN).
Returns the combined FFN delta.
"""
def __init__(self, d_model, n_experts=8, n_shared=1, top_k=2, ffn_mult=8/3, dropout=0.0,
use_capacity_routing=False, capacity_factor=1.25, fixed_capacity: int | None = None):
super().__init__()
self.n_experts = n_experts
self.n_routed = n_experts - n_shared
self.top_k = top_k
self.d_model = d_model
self.use_capacity_routing = use_capacity_routing
self.capacity_factor = capacity_factor
self.fixed_capacity = fixed_capacity
if self.n_routed < top_k:
raise ValueError(
f"AryaSparseMoE config error: n_experts={n_experts} - n_shared={n_shared} "
f"= {self.n_routed} routed experts, but top_k={top_k} routing needs at "
f"least {top_k} routed experts to choose from."
)
self.shared_expert = SwiGLU_FFN(d_model, ffn_mult, dropout=dropout)
self.routed_experts = nn.ModuleList([
SwiGLU_FFN(d_model, ffn_mult, dropout=dropout)
for _ in range(self.n_routed)
])
self.router = nn.Linear(d_model, self.n_routed, bias=False)
self.expert_bias = nn.Parameter(torch.zeros(self.n_routed), requires_grad=False)
self.aux_loss_weight = float(getattr(alpha_config, 'moe_aux_loss_weight', 0.01))
self.last_aux_loss = torch.zeros((), dtype=torch.float32)
def forward(self, x):
B, T, D = x.shape
shared_out = self.shared_expert(x)
router_logits = self.router(x)
_kt_router = None
if x.is_cuda and _kt_on(router_logits, "router"):
_lg = router_logits.reshape(B * T, self.n_routed)
if self.training and torch.is_grad_enabled():
_kt_router = _triton_attempt(
"router_train", (_lg.dtype, self.n_routed, self.top_k),
lambda l_: _moe_kernel.router_topk(l_, self.top_k, self.expert_bias, True),
lambda l_: _moe_kernel.router_eager(l_, self.expert_bias, self.top_k, True),
(_lg,), lambda: (_lg[:512],),
)
else:
_kt_router = _triton_attempt(
"router_eval", (_lg.dtype, self.n_routed, self.top_k),
lambda l_: _moe_kernel.router_topk(l_, self.top_k),
lambda l_: _moe_kernel.router_eager(l_, None, self.top_k, False),
(_lg,), lambda: (_lg[:512],),
)
if _kt_router is not None:
if len(_kt_router) == 4: # training: probs, idx, mean softmax prob P, per-expert assignment counts
topk_probs, topk_indices, _P, _cnt = _kt_router
self.last_aux_loss = self.aux_loss_weight * self.n_routed * ((_cnt.float() / (B * T)) * _P).sum()
else:
topk_probs, topk_indices = _kt_router
self.last_aux_loss = torch.zeros((), device=x.device, dtype=torch.float32)
elif self.training and torch.is_grad_enabled():
biased_logits = router_logits.float() + self.expert_bias.float()
_, topk_indices = biased_logits.topk(self.top_k, dim=-1)
full_probs = F.softmax(router_logits.float(), dim=-1)
topk_probs = full_probs.gather(-1, topk_indices).to(router_logits.dtype)
topk_probs = topk_probs / topk_probs.sum(dim=-1, keepdim=True).clamp_min(1e-9)
one_hot = F.one_hot(topk_indices, num_classes=self.n_routed).float()
f = one_hot.mean(dim=(0, 1))
P = full_probs.mean(dim=(0, 1))
self.last_aux_loss = self.aux_loss_weight * self.n_routed * (f * P).sum()
# Do not mutate expert_bias here. Checkpoint recomputation runs
# this forward a second time, so an in-forward update would apply
# twice per optimizer step and make routing state nondeterministic.
else:
full_probs = F.softmax(router_logits.float(), dim=-1)
topk_probs, topk_indices = full_probs.topk(self.top_k, dim=-1)
topk_probs = (topk_probs / topk_probs.sum(dim=-1, keepdim=True).clamp_min(1e-9)).to(router_logits.dtype)
self.last_aux_loss = torch.zeros((), device=x.device, dtype=torch.float32)
gate_weights = topk_probs
x_flat = x.view(B * T, D)
gate_flat = gate_weights.view(B * T, self.top_k)
idx_flat = topk_indices.view(B * T, self.top_k)
if self.use_capacity_routing:
routed_out = self._forward_capacity_routed(x_flat, gate_flat, idx_flat)
routed_out = routed_out.view(B, T, D)
elif _moe_kernel_ready():
# shared + routed is fused into the combine kernel; verified vs the eager loop on first use.
_shared_flat = shared_out.reshape(B * T, D)
out = _triton_attempt(
"moe_experts", (x_flat.dtype, self.n_routed, self.top_k),
lambda x_, g_, i_, s_: _moe_kernel.fused_moe_experts(self.routed_experts, x_, g_, i_, shared=s_),
lambda x_, g_, i_, s_: self._eager_loop(x_, g_, i_) + s_,
(x_flat, gate_flat, idx_flat, _shared_flat),
lambda: (x_flat[:256], gate_flat[:256], idx_flat[:256], _shared_flat[:256]),
) if x_flat.is_cuda else None
if out is not None:
return out.view(B, T, D), self.last_aux_loss
routed_out = self._eager_loop(x_flat, gate_flat, idx_flat).view(B, T, D)
else:
routed_out = self._eager_loop(x_flat, gate_flat, idx_flat).view(B, T, D)
out = shared_out + routed_out
return out, self.last_aux_loss
def _eager_loop(self, x_flat, gate_flat, idx_flat):
"""Per-expert Python loop -- the reference implementation and fallback."""
routed_out = torch.zeros_like(x_flat)
expert_masks = F.one_hot(idx_flat, num_classes=self.n_routed).bool().any(dim=1)
for expert_id in range(self.n_routed):
expert = self.routed_experts[expert_id]
token_mask = expert_masks[:, expert_id]
selected_tokens = x_flat[token_mask]
if selected_tokens.shape[0] == 0:
continue
expert_out = expert(selected_tokens)
weight = (
(idx_flat[token_mask] == expert_id).float() * gate_flat[token_mask]
).sum(dim=-1)
routed_out[token_mask] += weight.unsqueeze(-1) * expert_out
return routed_out
def _forward_capacity_routed(self, x_flat, gate_flat, idx_flat):
n_tokens, D = x_flat.shape
n_routed, capacity = self.n_routed, self._capacity(n_tokens)
flat_expert_ids = idx_flat.reshape(-1)
flat_gates = gate_flat.reshape(-1)
flat_token_ids = torch.arange(n_tokens, device=x_flat.device).unsqueeze(1).expand(-1, self.top_k).reshape(-1)
one_hot = F.one_hot(flat_expert_ids, num_classes=n_routed).to(torch.float32)
position_in_expert = (one_hot.cumsum(dim=0) * one_hot).sum(dim=1).long() - 1
keep = position_in_expert < capacity
safe_position = position_in_expert.clamp(min=0, max=capacity - 1)
flat_slot = flat_expert_ids * capacity + safe_position
gathered_x = x_flat[flat_token_ids] * keep.unsqueeze(-1).to(x_flat.dtype)
dispatch = torch.zeros(n_routed * capacity, D, device=x_flat.device, dtype=x_flat.dtype)
dispatch.index_add_(0, flat_slot, gathered_x)
dispatch = dispatch.view(n_routed, capacity, D)
expert_out = torch.stack(
[expert(dispatch[e]) for e, expert in enumerate(self.routed_experts)],
dim=0,
).view(n_routed * capacity, D)
contrib = expert_out[flat_slot] * keep.unsqueeze(-1).to(x_flat.dtype) * flat_gates.unsqueeze(-1)
routed_out = torch.zeros(n_tokens, D, device=x_flat.device, dtype=x_flat.dtype)
routed_out.index_add_(0, flat_token_ids, contrib)
return routed_out
def _capacity(self, n_tokens) -> int:
if self.fixed_capacity is not None:
return self.fixed_capacity
total_assignments = n_tokens * self.top_k
avg_load = -(-total_assignments // self.n_routed)
numer = int(round(self.capacity_factor * 1000))
capacity = -(-(avg_load * numer) // 1000)
return capacity + 1
def build_attention_layers(
num_layers: int,
hidden_size: int,
num_heads: int,
dropout: float = 0.0,
bias: bool = True,
num_kv_heads: int | None = None,
pattern: str = "dense",
max_seq_len: int = 4096,
sliding_window_size: int = 256,
tp_size: int = 1,
rope_dim: int | None = None,
):
if num_kv_heads is None:
num_kv_heads = num_heads
layers = []
layer_types = []
if pattern == "dense":
for _ in range(num_layers):
layers.append(DenseMHA(
hidden_size=hidden_size, num_heads=num_heads, num_kv_heads=num_kv_heads,
dropout=dropout, bias=bias, max_seq_len=max_seq_len,
window_size=None, tp_size=tp_size, rope_dim=rope_dim,
))
layer_types.append("dense")
elif pattern == "pyramid_swa":
if num_kv_heads <= 1:
pyramid_kv_heads = [1, 1, 1]
else:
import math as _math
mqa_floor = max(2, _math.ceil(num_kv_heads / 4))
mqa_floor = min(mqa_floor, num_kv_heads) # never exceed full heads
pyramid_kv_heads = sorted(set([
mqa_floor,
max(mqa_floor, _math.ceil(num_kv_heads / 2)),
num_kv_heads,
]))
while len(pyramid_kv_heads) < 3:
pyramid_kv_heads.append(num_kv_heads)
pyramid_kv_heads = pyramid_kv_heads[:3]
for i in range(num_layers):
pos_in_group = i % 4
is_last_layer = (i == num_layers - 1)
if pos_in_group == 3 or is_last_layer:
layers.append(DenseMHA(
hidden_size=hidden_size, num_heads=num_heads, num_kv_heads=num_kv_heads,
dropout=dropout, bias=bias, max_seq_len=max_seq_len,
window_size=None, tp_size=tp_size, rope_dim=rope_dim,
))
layer_types.append("global")
else:
kv_heads_here = pyramid_kv_heads[pos_in_group]
layers.append(DenseMHA(
hidden_size=hidden_size, num_heads=num_heads, num_kv_heads=kv_heads_here,
dropout=dropout, bias=bias, max_seq_len=max_seq_len,
window_size=sliding_window_size, tp_size=tp_size, rope_dim=rope_dim,
))
layer_types.append("swa")
elif pattern == "mod":
for i in range(num_layers):
layers.append(DenseMHA(
hidden_size=hidden_size, num_heads=num_heads, num_kv_heads=num_kv_heads,
dropout=dropout, bias=bias, max_seq_len=max_seq_len,
window_size=None, tp_size=tp_size, rope_dim=rope_dim,
))
is_full_dense = (i % 4 == 3) or (i == num_layers - 1)
layer_types.append("mod_dense" if is_full_dense else "mod")
else:
raise ValueError(
f"Unknown attention pattern {pattern!r} -- supported patterns: "
f"'dense', 'pyramid_swa', 'mod'. "
f"(BigBird/conv-hybrid/LSA/HCA patterns have been removed.)"
)
return nn.ModuleList(layers), layer_types
class AryaBlock(nn.Module):
"""Single transformer block: Layer Norm → Attention → MLP (with residual connections).
`attn_module` is DenseMHA,
pre-built by GPT via build_attention_layers. Every layer is fully
standalone -- no cross-layer attention/KV sharing of any kind.
`role` is purely a label ("dense" | "lsa") for inspection/debugging.
"""
def __init__(self, config, attn_module: nn.Module, role: str = "dense"):
super().__init__()
self.ln_1 = RMSNorm(config.n_embd, eps=1e-5)
# Pre-LN for the MLP branch: ensure router and experts see the
# identical normalized tensor the MLPs expect.
self.ln_2 = RMSNorm(config.n_embd, eps=1e-5)
self.attn = attn_module
self.role = role # "dense" | "lsa"
self.use_moe = getattr(config, 'use_moe', False)
if self.use_moe:
if getattr(config, 'tp_size', 1) > 1:
raise ValueError(
"tensor_parallel_size > 1 is not supported together with use_moe=True -- "
"MoE needs its own (unimplemented here) expert-parallel sharding scheme, "
"column/row-parallel head splitting does not apply to expert routing. "
"Set use_moe=False or tensor_parallel_size=1."
)
self.ffn = AryaSparseMoE(
d_model=config.n_embd,
n_experts=config.n_experts,
n_shared=config.n_shared,
top_k=getattr(config, 'moe_top_k', 2),
ffn_mult=config.ffn_mult,
dropout=config.dropout,
use_capacity_routing=False,
fixed_capacity=getattr(config, 'moe_fixed_capacity', None),
)
else:
self.ffn = SwiGLU_FFN(config.n_embd, config.ffn_mult, dropout=config.dropout, tp_size=getattr(config, 'tp_size', 1))
def forward(self, x, kv_cache=None, cache_pos=0):
"""Every attention layer (DenseMHA, LSA) is standalone and
computes its own K/V from `x` alone --
no external_kv, no cross-layer sharing, no produced_kv to pass on.
"""
normed = self.ln_1(x)
if kv_cache is None:
attn_delta = module_output_tensor(self.attn(normed))
else:
attn_delta = module_output_tensor(self.attn(normed, kv_cache=kv_cache, cache_pos=cache_pos))
_fused = None
if x.is_cuda and _kt_on(x, "add_rmsnorm"):
_w, _eps = self.ln_2.weight, self.ln_2.eps
_odt = _kt_norm_dtype(x, _w, _eps)
_fused = _triton_attempt(
"add_rmsnorm", (x.dtype, attn_delta.dtype, _w.dtype),
lambda x_, d_, w_: _moe_kernel.add_rmsnorm(x_, d_, w_, _eps, _odt),
lambda x_, d_, w_: ((x_ + d_), _rmsnorm_eager(x_ + d_, w_, _eps)),
(x, attn_delta, _w),
lambda: (x[:1], attn_delta[:1],
torch.linspace(0.5, 1.5, _w.numel(), device=_w.device, dtype=_w.dtype).requires_grad_()),
)
if _fused is not None:
x, ffn_in = _fused
else:
x = x + attn_delta
ffn_in = self.ln_2(x)
ffn_result = self.ffn(ffn_in)
if self.use_moe:
ffn_delta, moe_aux = ffn_result
return x + ffn_delta, moe_aux
return x + ffn_result
class MoDBlock(nn.Module):
def __init__(self, config, inner: nn.Module, capacity: float = 0.125):
super().__init__()
self.inner = inner
self.capacity = capacity
self.keep_target = float(capacity)
self._budget_coeff = float(getattr(config, 'mod_budget_coeff', 0.05))
self._entropy_coeff = float(getattr(config, 'mod_gate_entropy_coeff', 0.0))
self.hard_eval = bool(getattr(config, 'mod_hard_eval', False))
self._router_norm = RMSNorm(config.n_embd, eps=1e-5)
self.router = nn.Linear(config.n_embd, 1, bias=True)
self.last_gate_aux = None
def forward(self, x, kv_cache=None, cache_pos=0):
gate = torch.sigmoid(self.router(self._router_norm(x)))
aux = torch.zeros((), device=x.device, dtype=torch.float32)
if self.training:
g = gate.float()
if self._budget_coeff > 0.0:
aux = aux + self._budget_coeff * (g.mean() - self.keep_target) ** 2
if self._entropy_coeff > 0.0:
gc = g.clamp(1e-6, 1.0 - 1e-6)
aux = aux + self._entropy_coeff * (
-(gc * gc.log() + (1 - gc) * (1 - gc).log()).mean()
)
# BUGFIX: `aux` must stay attached to the graph -- returning
# aux.detach() made the MoD budget/entropy regularizer contribute
# exactly zero gradient to the router, i.e. it was inert.
self.last_gate_aux = aux.detach() # detached copy, for logging only
else:
aux = torch.zeros((), device=x.device, dtype=torch.float32)
self.last_gate_aux = aux
kw = {} if kv_cache is None else {"kv_cache": kv_cache, "cache_pos": cache_pos}
if self.inner.use_moe:
inner_out, moe_aux = self.inner(x, **kw)
else:
inner_out = self.inner(x, **kw)
moe_aux = torch.zeros((), device=x.device, dtype=torch.float32)
if gate.dtype == x.dtype == inner_out.dtype:
out = torch.lerp(x, inner_out, gate) # == x + gate * (inner_out - x), single fused kernel
else:
out = x + gate * (inner_out - x)
return (out, moe_aux), aux
def extra_repr(self) -> str:
return (
f"gate_based=True,keep_target={self.keep_target},"
f"budget_coeff={self._budget_coeff},entropy_coeff={self._entropy_coeff}"
)
class MTPHead(nn.Module):
"""Residual shared-head auxiliary for a future-token prediction depth."""
def __init__(self, hidden_size: int, ln_f_weight: torch.Tensor):
super().__init__()
self.weight = nn.Parameter(ln_f_weight.detach().clone())
self.proj = nn.Linear(hidden_size, hidden_size, bias=False)
nn.init.zeros_(self.proj.weight)
self.eps = 1e-5
def forward(self, hidden: torch.Tensor) -> torch.Tensor:
# F.rms_norm handles the dtype internally -- no manual fp32 upcast
# allocation, no (B, T, D) intermediate tensor, free for any dtype.
h_norm = _native_rms_norm(hidden, (self.weight.shape[0],), self.weight.to(hidden.dtype), self.eps)
return h_norm + self.proj(h_norm)
class MultiTokenPrediction(nn.Module):
def __init__(self, config, depth: int = 1, ln_f_weight: torch.Tensor | None = None):
super().__init__()
self.depth = depth
initial_weight = (
ln_f_weight if ln_f_weight is not None else torch.ones(config.n_embd)
)
self.heads = nn.ModuleList([
MTPHead(config.n_embd, initial_weight) for _ in range(depth)
])
self.last_raw = None # unweighted mean MTP loss from the last compute_loss (for logging)
def compute_loss(
self,
x: torch.Tensor, # (B, T, D) -- pre-lm_head hidden states
targets: torch.Tensor, # (B, T) -- already the +1 shifted targets
lm_head_weight: torch.Tensor, # (vocab_size, D) -- tied weight
mtp_lambda: float = 0.3,
) -> torch.Tensor:
total = torch.zeros((), device=x.device, dtype=torch.float32)
valid_depths = 0
for k, head in enumerate(self.heads):
offset = k + 1 # additional token offset beyond the main +1 shift
Tk = x.size(1) - offset
if Tk <= 0:
continue
h = head(x[:, :Tk]) # (B, Tk, D)
tgt_k = targets[:, offset:] # (B, Tk)
# Keep sum reduction: all-ignored chunks must contribute zero, not NaN.
loss_k = chunked_cross_entropy(h, lm_head_weight, tgt_k, ignore_index=-1)
total = total + loss_k
valid_depths += 1
if valid_depths == 0:
self.last_raw = torch.zeros((), device=x.device, dtype=torch.float32)
return self.last_raw
self.last_raw = (total / valid_depths).detach()
return mtp_lambda * (total / valid_depths)
@dataclass
class GPTConfig:
"""Configuration for GPT model."""
block_size: int = _alpha_config_attr('CONTEXT', 1024)
vocab_size: int = _alpha_config_attr('vocab_size', 50304)
n_layer: int = _alpha_config_attr('numberoflayers', 12)
n_head: int = _alpha_config_attr('numberofheads', 12)
n_embd: int = _alpha_config_attr('D_MODEL', 768)
dropout: float = _alpha_config_attr('dropout', 0.0)
bias: bool = _alpha_config_attr('bias', False)
ffn_mult: float = _alpha_config_attr('ffn_mult', 4.0)
d_rope: int | None = _alpha_config_attr('d_rope', None)
top_k: int = _alpha_config_attr('top_k', 64)
n_experts: int = _alpha_config_attr('n_experts', 8)
n_shared: int = _alpha_config_attr('n_shared', 1)
use_moe: bool = _alpha_config_attr('use_moe', False)
use_liger: bool = _alpha_config_attr('use_liger', False)
moe_top_k: int = _alpha_config_attr('moe_top_k', 2)
gradient_checkpointing: bool = _alpha_config_attr('gradient_checkpointing', False) # trade ~20-30% more compute for substantially less activation memory (grows with n_layer -- see GPT.forward)
num_kv_heads: int | None = _alpha_config_attr('num_kv_heads', None)
pattern: str = _alpha_config_attr('pattern', 'pyramid_swa')
sliding_window_size: int = _alpha_config_attr('sliding_window_size', 256)
tp_size: int = _alpha_config_attr('tensor_parallel_size', 1)
gen_headroom: int = _alpha_config_attr('gen_headroom', 0)
max_gen_tokens: int | None = _alpha_config_attr('max_gen_tokens', None)
mod_capacity: float = _alpha_config_attr('mod_capacity', 0.125)
use_mtp: bool = _alpha_config_attr('use_mtp', False)
mtp_depth: int = _alpha_config_attr('mtp_depth', 1)
mtp_lambda: float = _alpha_config_attr('mtp_lambda', 0.3)
use_nvfp4: bool = _alpha_config_attr('use_nvfp4', False)
use_int8: bool = _alpha_config_attr('use_int8', False)
int8_group_size: int = _alpha_config_attr('int8_group_size', 128)
mod_gate_entropy_coeff: float = _alpha_config_attr('mod_gate_entropy_coeff', 0.01)
# Engram static n-gram memory (arXiv:2601.07372). Tuples, not lists:
# dataclass rejects mutable defaults.
use_engram: bool = _alpha_config_attr('use_engram', False)
engram_layer_ids: tuple = tuple(_alpha_config_attr('engram_layer_ids', (1,)))
engram_max_ngram: int = _alpha_config_attr('engram_max_ngram', 3)
engram_vocab_size: tuple = tuple(_alpha_config_attr('engram_vocab_size', (8192, 8192)))
engram_embed_per_ngram: int = _alpha_config_attr('engram_embed_per_ngram', 128)
engram_n_heads: int = _alpha_config_attr('engram_n_heads', 4)
engram_kernel_size: int = _alpha_config_attr('engram_kernel_size', 4)
engram_seed: int = _alpha_config_attr('engram_seed', 0)
# Set automatically from the tokenizer lookup at build time; saved in
# checkpoints so GPT(config) can be rebuilt without a tokenizer.
engram_compressed_vocab: int = _alpha_config_attr('engram_compressed_vocab', 0)
class GPT(nn.Module):
"""Full GPT language model"""
def __init__(self, config, engram_lookup=None):
super().__init__()
if config.vocab_size is None:
raise ValueError("config.vocab_size must be set")
if config.block_size is None:
raise ValueError("config.block_size must be set")
self.config = config
head_dim = config.n_embd // config.n_head
rope_dim = head_dim if getattr(config, 'd_rope', None) is None else config.d_rope
gen_headroom = max(0, int(getattr(config, 'gen_headroom', 0)))
rope_table_len = config.block_size + gen_headroom
attn_layers, self.layer_types = build_attention_layers(
num_layers=config.n_layer,
hidden_size=config.n_embd,
num_heads=config.n_head,
dropout=config.dropout,
bias=config.bias,
num_kv_heads=config.num_kv_heads,
pattern=config.pattern,
max_seq_len=rope_table_len,
sliding_window_size=config.sliding_window_size,
tp_size=config.tp_size,
rope_dim=rope_dim,
)
mod_capacity = float(getattr(config, 'mod_capacity', 0.125))
blocks = []
for i in range(config.n_layer):
block = AryaBlock(config, attn_layers[i], role=self.layer_types[i])
if self.layer_types[i] == "mod":
block = MoDBlock(config, block, capacity=mod_capacity)
blocks.append(block)
# Hoist once: avoid an isinstance(block, MoDBlock) check (and the
# use_moe lookup that decides whether a block yields a moe_aux
# tensor) on every block on every forward.
self._no_kw = [{} for _ in blocks] # shared empty kwargs: no per-layer dict alloc
self._block_flags = [
(isinstance(b, MoDBlock), b.inner.use_moe if isinstance(b, MoDBlock) else b.use_moe)
for b in blocks
]
self.transformer = nn.ModuleDict(dict(
wte=nn.Embedding(config.vocab_size, config.n_embd),
drop=nn.Dropout(config.dropout),
h=nn.ModuleList(blocks),
ln_f=RMSNorm(config.n_embd, eps=1e-5),
))
self.lm_head = nn.Linear(config.n_embd, config.vocab_size, bias=False)
# ── Engram (optional) ────────────────────────────────────────────
self.engram_hasher = None
self.engram_layers = nn.ModuleDict()
self._engram_layer_ids = frozenset()
if bool(getattr(config, 'use_engram', False)):
if Engram is None:
raise ImportError("use_engram=True but engram.py could not be imported")
ids = tuple(int(l) for l in config.engram_layer_ids)
if not ids or any(not (0 <= l < config.n_layer) for l in ids) or len(set(ids)) != len(ids):
raise ValueError(
f"engram_layer_ids={ids} must be unique and within [0, {config.n_layer})")
if engram_lookup is not None:
config.engram_compressed_vocab = int(engram_lookup.max()) + 1
if int(getattr(config, 'engram_compressed_vocab', 0)) <= 0:
raise ValueError(
"use_engram=True needs engram_lookup (built from the tokenizer via "
"engram.CompressedTokenizer) or a config with engram_compressed_vocab set")
self.engram_hasher = NgramHasher(
layer_ids=ids,
max_ngram=config.engram_max_ngram,
vocab_size_per_ngram=config.engram_vocab_size,
n_heads=config.engram_n_heads,
compressed_vocab=config.engram_compressed_vocab,
raw_vocab_size=config.vocab_size,
seed=config.engram_seed,
lookup=engram_lookup,
)
self.engram_layers = nn.ModuleDict({
str(l): Engram(
hidden_size=config.n_embd,
head_sizes=self.engram_hasher.head_sizes[i],
max_ngram=config.engram_max_ngram,
embed_per_ngram=config.engram_embed_per_ngram,
n_heads=config.engram_n_heads,
kernel_size=config.engram_kernel_size,
)
for i, l in enumerate(ids)
})
self._engram_layer_ids = frozenset(ids)
use_mtp = bool(getattr(config, 'use_mtp', False))
mtp_depth = int(getattr(config, 'mtp_depth', 1))
self.mtp = MultiTokenPrediction(config, depth=mtp_depth) if use_mtp else None
self._mtp_lambda = float(getattr(config, 'mtp_lambda', 0.3))
self.apply(self._init_weights)
for _eg in self.engram_layers.values():
_eg.reset_parameters() # tables std=0.01 (apply() above overwrote it with 0.02)
# apply(_init_weights) above re-inits every nn.Linear to N(0, 0.02),
# including the always-on attn_gate_proj. Restore its zero-init so the
# gate is identity (2*sigmoid(0) = 1) at step 0.
for block in self.transformer.h:
real_block = block.inner if isinstance(block, MoDBlock) else block
if isinstance(real_block.attn, DenseMHA):
nn.init.zeros_(real_block.attn.attn_gate_proj.weight)
for block in self.transformer.h:
if isinstance(block, MoDBlock):
with torch.no_grad():
block.router.bias.fill_(2.0)
if self.mtp is not None:
with torch.no_grad():
for head in self.mtp.heads:
head.weight.copy_(self.transformer.ln_f.weight)
head.proj.weight.zero_()
self.transformer.wte.weight = self.lm_head.weight
residual_std = 0.02 / math.sqrt(config.n_layer)
for name, p in self.named_parameters():
if name.endswith("out_proj.weight") or name.endswith("W_out.weight"):
torch.nn.init.normal_(p, mean=0.0, std=residual_std)
self._use_nvfp4: bool = False
self._use_int8: bool = False
self._quant_mode = None
if _nvfp4_enabled():
n_quantized = apply_nvfp4_to_model(self)
self._use_nvfp4 = True
self._quant_mode = "nvfp4"
warnings.warn(
f"[nvFP4/RHT] torchao quantized {n_quantized} Linear layers "
f"(Blackwell SM100+). Weight: FP4 e2m1 per-block-16 RHT. "
f"Activation: FP4 dynamic per-tensor scale (FP8 intermediate). "
f"Fused GEMM kernel emitted by torch.compile. "
f"Skipped: wte, lm_head, norms, router, gate, small/misaligned linears. "
f"Verify val_loss vs bf16 baseline before committing to a long run.",
stacklevel=2,
)
elif _int8_enabled():
gs = int(getattr(config, 'int8_group_size', 128))
n_quantized = apply_int8_to_model(self, group_size=gs)
self._use_int8 = True
self._quant_mode = "int8"
warnings.warn(
f"[INT8/Jetfire] torchao quantized {n_quantized} Linear layers "
f"(T4/Turing+). Weight: INT8 per-group-{gs} symmetric. "
f"Activation: INT8 dynamic per-tensor scale (runtime, no calibration). "
f"Fused kernel emitted by torch.compile via Inductor. "
f"Skipped: wte, lm_head, norms, router, gate, small linears. "
f"Verify val_loss vs fp16 baseline before a long run.",
stacklevel=2,
)
self.transformer.wte.weight = self.lm_head.weight
assert self.transformer.wte.weight is self.lm_head.weight
_init_device = (
torch.device("cuda", torch.cuda.current_device())
if torch.cuda.is_available()
else torch.device("cpu")
)
kernel.print_attention_backend(_init_device)
kernel.warmup_attention_backend(_init_device)
def set_tp_group(self, group) -> None:
if self.config.tp_size <= 1:
return
for block in self.transformer.h:
real_block = block.inner if isinstance(block, MoDBlock) else block
real_block.attn.tp_group = group
if hasattr(real_block, "ffn") and real_block.ffn is not None:
real_block.ffn.tp_group = group
if torch.distributed.is_initialized() and group is not None:
for block in self.transformer.h:
real_block = block.inner if isinstance(block, MoDBlock) else block
attn = real_block.attn
if not isinstance(attn, DenseMHA):
continue
if attn.num_kv_heads >= attn.tp_size:
continue
with torch.no_grad():
# Full qkv projection tensor is the canonical object to align.
torch.distributed.broadcast(attn.qkv_proj.weight.data, src=0, group=group)
if attn.qkv_proj.bias is not None:
torch.distributed.broadcast(attn.qkv_proj.bias.data, src=0, group=group)
def _init_weights(self, module):
if isinstance(module, nn.Linear):
torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
if module.bias is not None:
torch.nn.init.zeros_(module.bias)
elif isinstance(module, nn.Embedding):
torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
def get_num_params(self, non_embedding=True):
"""Count number of parameters"""
n_params = sum(p.numel() for p in self.parameters())
if non_embedding:
n_params -= self.transformer.wte.weight.numel()
return n_params
def forward(self, idx, targets=None, need_logits=True, kv_cache=None):
idx = idx.long()
if targets is not None:
targets = targets.long()
b, t = idx.size()
# Plain check, not torch._check with a message closure (that closure forces a graph break).
if t > self.config.block_size:
raise ValueError(f"Sequence too long: {t} > {self.config.block_size}")
cache_pos = 0
if kv_cache is not None:
if not self.supports_kv_cache:
raise NotImplementedError("this model config (e.g. Engram) does not support kv_cache")
cache_pos = kv_cache.pos
tok_emb = self.transformer.wte(idx) # (B, T, n_embd)
x = self.transformer.drop(tok_emb)
mod_gate_loss = torch.zeros((), device=x.device, dtype=torch.float32)
moe_aux_loss = torch.zeros((), device=x.device, dtype=torch.float32)
engram_hashes = self.engram_hasher(idx) if self.engram_hasher is not None else None
# Static-per-call decisions, resolved once instead of once per layer.
engram_on = engram_hashes is not None
use_ckpt = self.config.gradient_checkpointing and self.training
kws = (self._no_kw if kv_cache is None else
[{"kv_cache": kv_cache.layer(i), "cache_pos": cache_pos} for i in range(len(self._no_kw))])
for layer_i, (block, (is_mod, has_moe), kw) in enumerate(zip(self.transformer.h, self._block_flags, kws)):
if engram_on and layer_i in self._engram_layer_ids:
# Official placement: h = h + Engram(h, ids) BEFORE the block's attention.
x = x + self.engram_layers[str(layer_i)](x, engram_hashes[layer_i])
if use_ckpt:
if is_mod:
(x, moe_aux), gate_aux = torch.utils.checkpoint.checkpoint(
block, x, use_reentrant=False
)
mod_gate_loss = mod_gate_loss + gate_aux.float()
elif has_moe:
x, moe_aux = torch.utils.checkpoint.checkpoint(
block, x, use_reentrant=False
)
else:
x = torch.utils.checkpoint.checkpoint(
block, x, use_reentrant=False
)
moe_aux = None
elif is_mod:
(x, moe_aux), gate_aux = block(x, **kw)
mod_gate_loss = mod_gate_loss + gate_aux.float()
elif has_moe:
x, moe_aux = block(x, **kw)
else:
x = block(x, **kw)
moe_aux = None
if moe_aux is not None:
moe_aux_loss = moe_aux_loss + moe_aux.float()
pre_ln_f = x
x = self.transformer.ln_f(x)
if targets is not None:
mtp_loss = torch.zeros((), device=x.device, dtype=torch.float32)
if self.mtp is not None:
mtp_loss = self.mtp.compute_loss(
pre_ln_f, targets, self.lm_head.weight, mtp_lambda=self._mtp_lambda
)
self.last_mtp_loss = self.mtp.last_raw # raw (unweighted), for logging
else:
self.last_mtp_loss = None
liger_ce = None # must exist even when the Liger path is not taken
if (not need_logits
and _LIGER_CE_ON
and _liger_fused_ce_cuda_gate(x)):
liger_ce = _liger_fused_ce_attempt(x, self.lm_head.weight, targets)
if liger_ce is not None:
total_loss = liger_ce + mtp_loss + mod_gate_loss + moe_aux_loss
self.last_ce_loss = liger_ce.detach()
self.last_moe_aux_loss = moe_aux_loss.detach()
self.last_total_loss = total_loss.detach()
return None, total_loss
if not need_logits:
ce_loss = chunked_cross_entropy(x, self.lm_head.weight, targets, ignore_index=-1)
logits = None
else:
logits = F.linear(x, self.lm_head.weight)
ce_loss = F.cross_entropy(
logits.reshape(-1, logits.size(-1)).float(),
targets.reshape(-1),
ignore_index=-1,
)
total_loss = ce_loss + mtp_loss + mod_gate_loss + moe_aux_loss
self.last_ce_loss = ce_loss.detach()
self.last_moe_aux_loss = moe_aux_loss.detach()
self.last_total_loss = total_loss.detach()
else:
logits = F.linear(x[:, [-1], :], self.lm_head.weight) # (B, 1, vocab_size)
ce_loss = None
total_loss = None
if kv_cache is not None:
kv_cache.pos += t
return logits, total_loss
@property
def supports_kv_cache(self) -> bool:
return (
self.engram_hasher is None
and all(
isinstance((b.inner if isinstance(b, MoDBlock) else b).attn, DenseMHA)
and (b.inner if isinstance(b, MoDBlock) else b).attn.causal
for b in self.transformer.h
)
)
def new_kv_cache(self, max_len: int | None = None) -> "KVCache":
"""Cache sized for `max_len` tokens (default and upper bound: block_size)."""
cap = self.config.block_size if max_len is None else min(int(max_len), self.config.block_size)
return KVCache(len(self.transformer.h), cap)
@torch.no_grad()
def generate(self, idx, max_new_tokens=None, temperature=1.0, top_k=None, use_cache=True):
if max_new_tokens is None:
max_new_tokens = getattr(self.config, 'max_gen_tokens', None)
if max_new_tokens is None:
raise ValueError(
"generate() needs max_new_tokens, either passed "
"directly or set as max_gen_tokens in config.py."
)
if temperature is None or temperature <= 0:
raise ValueError(
f"generate(): temperature must be > 0, got {temperature!r}. "
f"For greedy decoding use top_k=1 with a small temperature."
)
# BUGFIX: @torch.no_grad() does NOT put the module in eval mode, so
# sampling from a model left in .train() ran with dropout active (and
# kept mutating MoE expert_bias). Force eval, restore afterwards.
was_training = self.training
self.eval()
try:
return self._generate_loop(idx, max_new_tokens, temperature, top_k, use_cache)
finally:
if was_training:
self.train()
def _generate_loop(self, idx, max_new_tokens, temperature, top_k, use_cache=True):
block_size = self.config.block_size
cached = bool(use_cache) and self.supports_kv_cache
cache = None
for _ in range(max_new_tokens):
if cached and idx.size(1) <= block_size:
# Full context still fits: KV cache gives the same result as re-encoding it.
if cache is None:
cache = self.new_kv_cache()
logits, _ = self(idx, kv_cache=cache) # prefill
else:
logits, _ = self(idx[:, -1:], kv_cache=cache) # one token
else:
# Past block_size the original sliding-window re-encode applies
# (positions re-based each step), which a cache can't reproduce.
cached, cache = False, None
idx_cond = idx if idx.size(1) <= block_size else idx[:, -block_size:]
logits, _ = self(idx_cond)
logits = logits[:, -1, :] / temperature
if top_k is not None:
v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
logits[logits < v[:, [-1]]] = -float('Inf')
probs = F.softmax(logits.float(), dim=-1) # (B, vocab_size)
# Sample next token
idx_next = torch.multinomial(probs, num_samples=1) # (B, 1)
# Append to sequence
idx = torch.cat((idx, idx_next), dim=1) # (B, T+1)
return idx