File size: 5,226 Bytes
0f18680
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import torch
import torch.nn as nn
import torch.nn.functional as F
from transformers import PreTrainedModel
from transformers.modeling_outputs import CausalLMOutputWithPast

try:
    from mamba_ssm import Mamba2
except ImportError:
    raise ImportError("mamba-ssm is required. pip install mamba-ssm causal-conv1d")

from .configuration_pebble import PebbleConfig

class RMSNorm(nn.Module):
    def __init__(self, dim, eps=1e-6):
        super().__init__()
        self.eps = eps
        self.weight = nn.Parameter(torch.ones(dim))

    def forward(self, x):
        dt = x.dtype
        xf = x.float()
        xf = xf * torch.rsqrt(xf.pow(2).mean(-1, keepdim=True) + self.eps)
        return self.weight * xf.to(dt)

class AttentionBlock(nn.Module):
    def __init__(self, config):
        super().__init__()
        dim = config.hidden_size
        n_heads = config.num_attention_heads
        hidden = config.intermediate_size
        assert dim % n_heads == 0
        self.nh, self.hd = n_heads, dim // n_heads
        self.wqkv = nn.Linear(dim, 3 * dim, bias=False)
        self.wo   = nn.Linear(dim, dim, bias=False)
        self.fc1  = nn.Linear(dim, hidden, bias=False)
        self.fc2  = nn.Linear(hidden, dim, bias=False)
        self.ln1  = RMSNorm(dim, eps=config.rms_norm_eps)
        self.ln2  = RMSNorm(dim, eps=config.rms_norm_eps)
        self.rope_theta = config.attention.get("rope_theta", 10000.0)

    def forward(self, x):
        B, T, C = x.shape
        h = self.ln1(x)

        qkv = self.wqkv(h).view(B, T, 3, self.nh, self.hd) \
                          .permute(2, 0, 3, 1, 4)
        q, k, v = qkv[0].float(), qkv[1].float(), qkv[2]

        half = self.hd // 2
        invf = 1.0 / (self.rope_theta ** (
            torch.arange(0, half, device=x.device, dtype=torch.float32)
            * 2.0 / self.hd))
        ang = torch.outer(
            torch.arange(T, device=x.device, dtype=torch.float32), invf)
        cos, sin = ang.cos()[None, None], ang.sin()[None, None]

        q1, q2 = q[..., :half], q[..., half:]
        k1, k2 = k[..., :half], k[..., half:]
        q = torch.cat([q1 * cos - q2 * sin,
                       q1 * sin + q2 * cos], dim=-1).to(v.dtype)
        k = torch.cat([k1 * cos - k2 * sin,
                       k1 * sin + k2 * cos], dim=-1).to(v.dtype)

        y = F.scaled_dot_product_attention(q, k, v, is_causal=True)
        y = y.transpose(1, 2).reshape(B, T, C)

        x = x + self.wo(y)
        x = x + self.fc2(F.gelu(self.fc1(self.ln2(x))))
        return x

class MambaBlock(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.ln = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
        mamba_cfg = config.mamba2
        self.mixer = Mamba2(
            d_model=config.hidden_size,
            d_state=mamba_cfg.get("d_state", 128),
            d_conv=mamba_cfg.get("d_conv", 4),
            expand=mamba_cfg.get("expand", 2),
            headdim=mamba_cfg.get("headdim", 96),
            use_mem_eff_path=mamba_cfg.get("use_mem_eff_path", True),
        )

    def forward(self, x):
        return x + self.mixer(self.ln(x))

class PebbleForCausalLM(PreTrainedModel):
    config_class = PebbleConfig
    supports_gradient_checkpointing = False
    _no_split_modules = ["MambaBlock", "AttentionBlock"]

    def __init__(self, config):
        super().__init__(config)
        self.config = config
        
        self.wte = nn.Embedding(config.vocab_size, config.hidden_size)
        
        # 3:1 Mamba:Attention ratio layout
        self.blocks = nn.ModuleList([
            MambaBlock(config) if i % 4 < 3
            else AttentionBlock(config)
            for i in range(config.num_hidden_layers)
        ])
        
        self.lnf = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
        self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
        
        # Tie weights
        self.tie_weights()

    def tie_weights(self):
        if self.config.tie_word_embeddings:
            self.lm_head.weight = self.wte.weight

    def forward(self, input_ids=None, attention_mask=None, labels=None, past_key_values=None, **kwargs):
        x = self.wte(input_ids)
        
        for blk in self.blocks:
            x = blk(x)
            
        logits = self.lm_head(self.lnf(x))

        loss = None
        if labels is not None:
            # Shift so that tokens < n predict n+1
            shift_logits = logits[..., :-1, :].contiguous()
            shift_labels = labels[..., 1:].contiguous()
            loss = F.cross_entropy(
                shift_logits.view(-1, shift_logits.size(-1)),
                shift_labels.view(-1)
            )

        return CausalLMOutputWithPast(
            loss=loss,
            logits=logits,
            past_key_values=past_key_values,
        )

    def prepare_inputs_for_generation(self, input_ids, past_key_values=None, **kwargs):
        # Mamba handles state internally in the mixer, so we don't use past_key_values 
        # at the model level for now (standard HF generation will still work for greedy/beam).
        return {
            "input_ids": input_ids,
            "past_key_values": past_key_values,
        }