CoolFace
Modelpublic

Rorical/logos-1b-base

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
2likes83downloads
linear.py611 linesDownload Raw Back to root
1"""Linear (Kimi Delta Attention) decoder-only transformer.2 3Pure-PyTorch chunkwise-parallel KDA scan.4"""5 6import math7from dataclasses import dataclass8from typing import List, Optional, Tuple, Dict, Any9 10import torch11import torch.nn as nn12import torch.nn.functional as F13 14from einops import rearrange15 16from .lm_loss import (17    lm_cross_entropy_from_logits,18    token_superposition_attention_mask,19    token_superposition_embeddings,20)21from .baseline import (22    BaselineConfig,23    RMSNorm,24    SwiGLU,25    MoELayer,26    combine_lm_and_aux_loss,27    init_moe_router_weights,28    _validate_moe_config,29    count_parameters,30    model_summary,31)32 33 34class _ShortConvolution(nn.Module):35    """Causal depthwise 1-D conv with optional cached state for O(1) decode."""36 37    def __init__(38        self,39        hidden_size: int,40        kernel_size: int,41        activation: str = "silu",42        bias: bool = False,43    ):44        super().__init__()45        self.kernel_size = kernel_size46        self.conv = nn.Conv1d(47            hidden_size,48            hidden_size,49            kernel_size=kernel_size,50            groups=hidden_size,51            padding=kernel_size - 1,52            bias=bias,53        )54        self.activation = activation55 56    def forward(57        self,58        x: torch.Tensor,59        cache: Optional[torch.Tensor] = None,60        return_cache: bool = False,61    ):62        T = x.size(1)63        K = self.kernel_size64 65        if cache is None:66            y = self.conv(x.transpose(1, 2))[..., :T].transpose(1, 2)67        else:68            x_full = torch.cat([cache, x], dim=1)69            y = F.conv1d(70                x_full.transpose(1, 2),71                self.conv.weight,72                self.conv.bias,73                stride=1,74                padding=0,75                groups=self.conv.groups,76            ).transpose(1, 2)77 78        if self.activation == "silu":79            y = F.silu(y)80 81        if not return_cache:82            return y83 84        if K <= 1:85            new_cache = x.new_zeros(x.size(0), 0, x.size(-1))86        else:87            combined = torch.cat([cache, x], dim=1) if cache is not None else x88            if combined.size(1) >= K - 1:89                new_cache = combined[:, -(K - 1):].contiguous()90            else:91                pad = combined.new_zeros(92                    combined.size(0), (K - 1) - combined.size(1), combined.size(-1)93                )94                new_cache = torch.cat([pad, combined], dim=1)95        return y, new_cache96 97 98class _RMSNormGatedSigmoid(nn.Module):99    def __init__(self, dim: int, eps: float = 1e-5):100        super().__init__()101        self.weight = nn.Parameter(torch.ones(dim))102        self.eps = eps103 104    def forward(self, x: torch.Tensor, gate: torch.Tensor) -> torch.Tensor:105        dtype = x.dtype106        x_f = x.float()107        rms_inv = x_f.pow(2).mean(dim=-1, keepdim=True).add_(self.eps).rsqrt()108        y = (x_f * rms_inv).to(dtype) * self.weight109        return y * torch.sigmoid(gate.to(dtype))110 111 112def _kda_gate(113    g: torch.Tensor,114    A_log: torch.Tensor,115    dt_bias: torch.Tensor,116) -> torch.Tensor:117    """Log-space decay gate: ``-exp(A_log) * softplus(g + dt_bias)``."""118    H, K = g.shape[-2], g.shape[-1]119    g = g.float() + dt_bias.float().view(H, K)120    dt = F.softplus(g)121    A = A_log.float().view(1, 1, H, 1)122    return -A.exp() * dt123 124 125def _kda_chunk_scan(126    q: torch.Tensor,127    k: torch.Tensor,128    v: torch.Tensor,129    log_g: torch.Tensor,130    beta: torch.Tensor,131    chunk_size: int = 64,132    use_qk_l2norm: bool = True,133    initial_state: Optional[torch.Tensor] = None,134    output_final_state: bool = False,135):136    """Chunkwise-parallel KDA scan in pure PyTorch.137 138    Recurrence: ``S_i = (I - beta_i k_i k_i^T) D_i S_{i-1} + beta_i k_i v_i^T``,139    ``o_i = q_i @ S_i``, with ``D_i = diag(exp(log_g_i))``. Chunk-level140    parallelism comes from the similarity transform ``~S_i = W_i^{-1} S_i``141    plus a single triangular solve per chunk.142    """143    B, T, H, K = q.shape144    V = v.shape[-1]145    orig_dtype = v.dtype146    device = q.device147 148    # The body runs in fp32: per-channel decays accumulate aggressively and149    # CUDA's triangular_solve has no bf16/fp16 kernel.150    with torch.autocast(device_type=device.type, enabled=False):151        if use_qk_l2norm:152            q = F.normalize(q, dim=-1)153            k = F.normalize(k, dim=-1)154        scale = K ** -0.5155 156        q = q.float() * scale157        k = k.float()158        v = v.float()159        log_g = log_g.float()160        beta = beta.float()161 162        pad = (chunk_size - T % chunk_size) % chunk_size163        if pad > 0:164            q = F.pad(q, (0, 0, 0, 0, 0, pad))165            k = F.pad(k, (0, 0, 0, 0, 0, pad))166            v = F.pad(v, (0, 0, 0, 0, 0, pad))167            log_g = F.pad(log_g, (0, 0, 0, 0, 0, pad))168            beta = F.pad(beta, (0, 0, 0, pad))169        Nc = (T + pad) // chunk_size170        C = chunk_size171 172        q = rearrange(q, "b (n c) h k -> b h n c k", c=C)173        k = rearrange(k, "b (n c) h k -> b h n c k", c=C)174        v = rearrange(v, "b (n c) h v -> b h n c v", c=C)175        log_g = rearrange(log_g, "b (n c) h k -> b h n c k", c=C)176        beta = rearrange(beta, "b (n c) h -> b h n c", c=C)177 178        # Clamp the cumulative log-decay to [-15, 0]: at default A/dt_bias179        # ranges a 64-token cumsum can drop below -80, and exp(-cum) then180        # overflows fp32 and NaNs the triangular solve.181        cum_log_g = log_g.cumsum(dim=-2).clamp(min=-15.0)182        W = cum_log_g.exp()183        W_inv = (-cum_log_g).exp()184 185        u_mat = k * W_inv186        w_mat = k * W187        q_tilde = q * W188 189        beta_e = beta.unsqueeze(-1)190        beta_w = beta_e * w_mat191        beta_v = beta_e * v192 193        L = torch.einsum("bhnik,bhnjk->bhnij", beta_w, u_mat)194        upper_incl_diag = torch.triu(195            torch.ones(C, C, dtype=torch.bool, device=device), diagonal=0196        )197        L = L.masked_fill(upper_incl_diag, 0)198 199        I_plus_L = L + torch.eye(C, dtype=L.dtype, device=device)200        effective_v = torch.linalg.solve_triangular(201            I_plus_L, beta_v, upper=False, unitriangular=True202        )203        effective_w = torch.linalg.solve_triangular(204            I_plus_L, beta_w, upper=False, unitriangular=True205        )206 207        intra_attn = torch.einsum("bhnik,bhnjk->bhnij", q_tilde, u_mat)208        strict_upper = torch.triu(209            torch.ones(C, C, dtype=torch.bool, device=device), diagonal=1210        )211        intra_attn = intra_attn.masked_fill(strict_upper, 0)212 213        if initial_state is not None:214            S = initial_state.to(dtype=q.dtype, device=q.device)215        else:216            S = q.new_zeros(B, H, K, V)217        outputs: List[torch.Tensor] = []218        for n in range(Nc):219            delta = effective_v[:, :, n] - effective_w[:, :, n] @ S220            o_inter = q_tilde[:, :, n] @ S221            o_chunk = o_inter + intra_attn[:, :, n] @ delta222            outputs.append(o_chunk)223 224            state_update = torch.einsum(225                "bhck,bhcv->bhkv", u_mat[:, :, n], delta226            )227            S = W[:, :, n, -1].unsqueeze(-1) * (S + state_update)228 229        out = torch.stack(outputs, dim=2)230        out = rearrange(out, "b h n c v -> b (n c) h v")231        if pad > 0:232            out = out[:, :T]233        out = out.to(orig_dtype)234 235    if output_final_state:236        # State stays fp32 so cached decode preserves precision.237        return out, S238    return out239 240 241def _kda_recurrent_step(242    q: torch.Tensor,243    k: torch.Tensor,244    v: torch.Tensor,245    log_g: torch.Tensor,246    beta: torch.Tensor,247    state: torch.Tensor,248    use_qk_l2norm: bool = True,249) -> Tuple[torch.Tensor, torch.Tensor]:250    """Single-token KDA step matching ``_kda_chunk_scan`` for ``T == 1``."""251    assert q.size(1) == 1 and k.size(1) == 1 and v.size(1) == 1252    orig_dtype = v.dtype253    K = q.size(-1)254    device = q.device255 256    with torch.autocast(device_type=device.type, enabled=False):257        if use_qk_l2norm:258            q = F.normalize(q, dim=-1)259            k = F.normalize(k, dim=-1)260        scale = K ** -0.5261 262        q_t = (q[:, 0].float()) * scale263        k_t = k[:, 0].float()264        v_t = v[:, 0].float()265        g_t = log_g[:, 0].float()266        b_t = beta[:, 0].float()267 268        S = state.to(torch.float32)269        S = S * g_t.exp().unsqueeze(-1)270        kS = torch.einsum("bhk,bhkv->bhv", k_t, S)271        update = torch.einsum(272            "bhk,bhv->bhkv", (b_t.unsqueeze(-1) * k_t), (v_t - kS)273        )274        S = S + update275        o = torch.einsum("bhk,bhkv->bhv", q_t, S).unsqueeze(1)276    return o.to(orig_dtype), S277 278 279@dataclass280class LinearConfig(BaselineConfig):281    head_dim: int = 64282    conv_size: int = 4283    chunk_size: int = 64284    A_init_range: Tuple[float, float] = (1, 16)285 286    expand: int = 2287    rope_base: float = 10000.0288 289    def __post_init__(self):290        if self.d_model % self.num_heads != 0:291            raise ValueError("d_model must be divisible by num_heads")292        if self.partial_rope_dim is not None:293            if self.partial_rope_dim % 2 != 0:294                raise ValueError(295                    f"partial_rope_dim ({self.partial_rope_dim}) must be even"296                )297        if self.head_dim < 1:298            raise ValueError("head_dim must be >= 1")299        if self.chunk_size < 1:300            raise ValueError("chunk_size must be >= 1")301        if self.conv_size < 1:302            raise ValueError("conv_size must be >= 1")303        _validate_moe_config(self)304 305 306class KimiDeltaAttention(nn.Module):307    def __init__(self, config: LinearConfig):308        super().__init__()309        self.hidden_size = config.d_model310        self.num_heads = config.num_heads311        self.head_dim = config.head_dim312        self.head_k_dim = self.head_dim313        self.conv_size = config.conv_size314        self.chunk_size = config.chunk_size315 316        projection_size = self.num_heads * self.head_dim317 318        self.q_proj = nn.Linear(self.hidden_size, projection_size, bias=False)319        self.k_proj = nn.Linear(self.hidden_size, projection_size, bias=False)320        self.v_proj = nn.Linear(self.hidden_size, projection_size, bias=False)321 322        self.q_conv1d = _ShortConvolution(projection_size, self.conv_size, "silu")323        self.k_conv1d = _ShortConvolution(projection_size, self.conv_size, "silu")324        self.v_conv1d = _ShortConvolution(projection_size, self.conv_size, "silu")325 326        A = torch.empty(self.num_heads, dtype=torch.float32).uniform_(327            *config.A_init_range328        )329        self.A_log = nn.Parameter(torch.log(A))330        self.A_log._no_weight_decay = True331 332        self.dt_bias = nn.Parameter(torch.empty(projection_size, dtype=torch.float32))333        self.dt_bias._no_weight_decay = True334 335        self.f_a_proj = nn.Linear(self.hidden_size, self.head_dim, bias=False)336        self.f_b_proj = nn.Linear(self.head_dim, projection_size, bias=False)337 338        self.b_proj = nn.Linear(self.hidden_size, self.num_heads, bias=False)339 340        self.g_a_proj = nn.Linear(self.hidden_size, self.head_dim, bias=False)341        self.g_b_proj = nn.Linear(self.head_dim, projection_size, bias=True)342 343        self.o_norm = _RMSNormGatedSigmoid(self.head_dim, eps=config.norm_eps)344        self.o_proj = nn.Linear(projection_size, self.hidden_size, bias=False)345 346        self._reset_parameters()347 348    def _reset_parameters(self):349        # Inverse-softplus init (Mamba-2 / KDA scheme).350        dt = torch.exp(351            torch.rand(self.num_heads * self.head_dim)352            * (math.log(0.1) - math.log(0.001))353            + math.log(0.001)354        )355        dt = torch.clamp(dt, min=1e-4)356        inv_dt = dt + torch.log(-torch.expm1(-dt))357        with torch.no_grad():358            self.dt_bias.copy_(inv_dt)359 360    def forward(361        self,362        x: torch.Tensor,363        attention_mask: Optional[torch.Tensor] = None,364        cache: Optional[Dict[str, Optional[torch.Tensor]]] = None,365    ) -> torch.Tensor:366        use_cache = cache is not None367 368        q_in = self.q_proj(x)369        k_in = self.k_proj(x)370        v_in = self.v_proj(x)371        if use_cache:372            q, cache["conv_state_q"] = self.q_conv1d(373                q_in, cache=cache.get("conv_state_q"), return_cache=True374            )375            k, cache["conv_state_k"] = self.k_conv1d(376                k_in, cache=cache.get("conv_state_k"), return_cache=True377            )378            v, cache["conv_state_v"] = self.v_conv1d(379                v_in, cache=cache.get("conv_state_v"), return_cache=True380            )381        else:382            q = self.q_conv1d(q_in)383            k = self.k_conv1d(k_in)384            v = self.v_conv1d(v_in)385 386        q = rearrange(q, "... (h d) -> ... h d", d=self.head_dim)387        k = rearrange(k, "... (h d) -> ... h d", d=self.head_dim)388        v = rearrange(v, "... (h d) -> ... h d", d=self.head_dim)389 390        g_raw = self.f_b_proj(self.f_a_proj(x))391        g_raw = rearrange(g_raw, "... (h d) -> ... h d", d=self.head_dim)392        log_g = _kda_gate(g_raw, self.A_log, self.dt_bias)393 394        beta = self.b_proj(x).float().sigmoid()395 396        # Zero q/k/v, log_g, beta at padded positions so they contribute no397        # content and no decay to the recurrent state.398        if attention_mask is not None:399            mask_4d = attention_mask.unsqueeze(-1).unsqueeze(-1)400            q = q * mask_4d.to(q.dtype)401            k = k * mask_4d.to(k.dtype)402            v = v * mask_4d.to(v.dtype)403            log_g = log_g * mask_4d.to(log_g.dtype)404            beta = beta * attention_mask.unsqueeze(-1).to(beta.dtype)405 406        if use_cache:407            prev_state = cache.get("recurrent_state")408            if prev_state is not None and x.size(1) == 1:409                o, new_state = _kda_recurrent_step(410                    q, k, v, log_g, beta, prev_state, use_qk_l2norm=True411                )412            else:413                o, new_state = _kda_chunk_scan(414                    q=q, k=k, v=v, log_g=log_g, beta=beta,415                    chunk_size=self.chunk_size,416                    use_qk_l2norm=True,417                    initial_state=prev_state,418                    output_final_state=True,419                )420            cache["recurrent_state"] = new_state421        else:422            o = _kda_chunk_scan(423                q=q, k=k, v=v, log_g=log_g, beta=beta,424                chunk_size=self.chunk_size,425                use_qk_l2norm=True,426            )427 428        gate = self.g_b_proj(self.g_a_proj(x))429        gate = rearrange(gate, "... (h d) -> ... h d", d=self.head_dim)430        o = self.o_norm(o, gate)431 432        o = rearrange(o, "b t h d -> b t (h d)")433        return self.o_proj(o)434 435 436class LinearTransformerBlock(nn.Module):437    def __init__(self, config: LinearConfig):438        super().__init__()439        self.use_moe = config.use_moe440 441        self.kda_norm = RMSNorm(config.d_model, eps=config.norm_eps)442        self.kda = KimiDeltaAttention(config)443 444        self.ffn_norm = RMSNorm(config.d_model, eps=config.norm_eps)445        if config.use_moe:446            self.ffn = MoELayer(config)447        else:448            self.ffn = SwiGLU(config.d_model, config.d_ff)449 450    def forward(451        self,452        x: torch.Tensor,453        attention_mask: Optional[torch.Tensor] = None,454        is_causal: bool = True,455        cache: Optional[Dict[str, Optional[torch.Tensor]]] = None,456    ) -> Tuple[torch.Tensor, torch.Tensor, Optional[torch.Tensor]]:457        x = x + self.kda(self.kda_norm(x), attention_mask=attention_mask, cache=cache)458 459        if self.use_moe:460            ffn_out, aux_loss, topk_indices = self.ffn(self.ffn_norm(x))461            x = x + ffn_out462            return x, aux_loss, topk_indices463        else:464            x = x + self.ffn(self.ffn_norm(x))465            return x, torch.zeros((), device=x.device, dtype=x.dtype), None466 467 468class LinearTransformer(nn.Module):469    def __init__(self, config: LinearConfig):470        super().__init__()471        self.config = config472 473        self.token_emb = nn.Embedding(config.vocab_size, config.d_model)474 475        self.layers = nn.ModuleList([476            LinearTransformerBlock(config) for _ in range(config.num_layers)477        ])478 479        self.final_norm = RMSNorm(config.d_model, eps=config.norm_eps)480        self.lm_head = nn.Linear(config.d_model, config.vocab_size, bias=False)481        self.lm_head.weight = self.token_emb.weight482 483        self._init_weights()484 485    def _init_weights(self):486        for module in self.modules():487            if isinstance(module, nn.Linear):488                torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)489                if module.bias is not None:490                    torch.nn.init.zeros_(module.bias)491            elif isinstance(module, nn.Embedding):492                torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)493        init_moe_router_weights(self, self.config.router_init_std)494 495    def forward(496        self,497        input_ids: torch.Tensor,498        attention_mask: Optional[torch.Tensor] = None,499        labels: Optional[torch.Tensor] = None,500        is_causal: bool = True,501        caches: Optional[List[Dict[str, Optional[torch.Tensor]]]] = None,502        token_superposition_bag_size: int = 1,503    ) -> Dict[str, Any]:504        x = token_superposition_embeddings(505            self.token_emb, input_ids, token_superposition_bag_size,506        )507        attention_mask = token_superposition_attention_mask(508            attention_mask, token_superposition_bag_size,509        )510 511        aux_loss = torch.zeros((), device=input_ids.device, dtype=x.dtype)512        topk_indices_list: List[Optional[torch.Tensor]] = []513        for i, layer in enumerate(self.layers):514            layer_cache = caches[i] if caches is not None else None515            x, layer_aux, layer_topk = layer(516                x, attention_mask=attention_mask,517                is_causal=is_causal, cache=layer_cache,518            )519            aux_loss = aux_loss + layer_aux520            topk_indices_list.append(layer_topk)521 522        x = self.final_norm(x)523        logits = self.lm_head(x)524 525        lm_loss: Optional[torch.Tensor] = None526        if labels is not None:527            lm_loss = lm_cross_entropy_from_logits(528                logits,529                labels,530                token_superposition_bag_size=token_superposition_bag_size,531                ignore_index=-100,532            )533        loss = combine_lm_and_aux_loss(534            lm_loss,535            aux_loss if self.config.use_moe else None,536            self.training,537        )538 539        return {540            "logits": logits,541            "loss": loss,542            "lm_loss": lm_loss,543            "aux_loss": aux_loss if self.config.use_moe else None,544            "topk_indices": topk_indices_list if self.config.use_moe else None,545        }546 547    def update_router_biases(self, topk_indices_list: List[Optional[torch.Tensor]]) -> None:548        if not self.config.use_moe:549            return550        for layer, topk_indices in zip(self.layers, topk_indices_list):551            if topk_indices is not None and isinstance(layer.ffn, MoELayer):552                layer.ffn.update_bias(topk_indices)553 554    @torch.no_grad()555    def get_balance_stats(self) -> Dict[str, float]:556        if not self.config.use_moe:557            return {}558        stats = {}559        for idx, layer in enumerate(self.layers):560            if hasattr(layer.ffn, "bias"):561                bias = layer.ffn.bias562                stats[f"layer{idx}_bias_mean"] = bias.abs().mean().item()563                stats[f"layer{idx}_bias_max"] = bias.abs().max().item()564        return stats565 566    @torch.no_grad()567    def generate(568        self,569        input_ids: torch.Tensor,570        max_new_tokens: int = 100,571        temperature: float = 1.0,572        top_k: Optional[int] = None,573        attention_mask: Optional[torch.Tensor] = None,574        eos_token_id: Optional[int] = None,575    ) -> torch.Tensor:576        self.train(False)577 578        caches: List[Dict[str, Optional[torch.Tensor]]] = [579            {580                "recurrent_state": None,581                "conv_state_q": None,582                "conv_state_k": None,583                "conv_state_v": None,584            }585            for _ in self.layers586        ]587 588        def _sample(logits: torch.Tensor) -> torch.Tensor:589            logits = logits / temperature590            if top_k is not None:591                v, _ = torch.topk(logits, min(top_k, logits.size(-1)))592                logits = logits.masked_fill(logits < v[:, [-1]], float("-inf"))593            probs = F.softmax(logits, dim=-1)594            return torch.multinomial(probs, num_samples=1)595 596        outputs = self.forward(input_ids, is_causal=True, caches=caches)597        next_token = _sample(outputs["logits"][:, -1, :])598        input_ids = torch.cat([input_ids, next_token], dim=-1)599 600        if eos_token_id is not None and (next_token == eos_token_id).all():601            return input_ids602 603        for _ in range(max_new_tokens - 1):604            outputs = self.forward(next_token, is_causal=True, caches=caches)605            next_token = _sample(outputs["logits"][:, -1, :])606            input_ids = torch.cat([input_ids, next_token], dim=-1)607            if eos_token_id is not None and (next_token == eos_token_id).all():608                break609 610        return input_ids611