ereniko commited on
Commit
ab56428
·
verified ·
1 Parent(s): a62ab9e

Upload folder using huggingface_hub

Browse files
model/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ from .config import IvmeConfig
2
+ from .transformer import IvmeConversateV2
3
+
4
+ __all__ = ["IvmeConfig", "IvmeConversateV2"]
model/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (266 Bytes). View file
 
model/__pycache__/attention.cpython-312.pyc ADDED
Binary file (3.19 kB). View file
 
model/__pycache__/config.cpython-312.pyc ADDED
Binary file (1.73 kB). View file
 
model/__pycache__/feedforward.cpython-312.pyc ADDED
Binary file (2 kB). View file
 
model/__pycache__/rmsnorm.cpython-312.pyc ADDED
Binary file (1.73 kB). View file
 
model/__pycache__/rope.cpython-312.pyc ADDED
Binary file (2.2 kB). View file
 
model/__pycache__/transformer.cpython-312.pyc ADDED
Binary file (6.68 kB). View file
 
model/attention.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+
5
+ from .rope import apply_rope
6
+
7
+
8
+ class CausalSelfAttention(nn.Module):
9
+ """Full multi-head causal self-attention (Section 4.4).
10
+
11
+ Deliberately NOT using Grouped Query Attention (GQA) — the doc is explicit
12
+ that at this scale, GQA's memory savings are negligible and it can quietly
13
+ cost quality. Every head gets its own independent K/V projections.
14
+ """
15
+
16
+ def __init__(self, hidden_dim: int, n_heads: int, dropout: float = 0.0):
17
+ super().__init__()
18
+ assert hidden_dim % n_heads == 0
19
+ self.n_heads = n_heads
20
+ self.head_dim = hidden_dim // n_heads
21
+ self.dropout = dropout
22
+
23
+ # separate q, k, v projections -- no sharing across heads (full attention)
24
+ self.q_proj = nn.Linear(hidden_dim, hidden_dim, bias=False)
25
+ self.k_proj = nn.Linear(hidden_dim, hidden_dim, bias=False)
26
+ self.v_proj = nn.Linear(hidden_dim, hidden_dim, bias=False)
27
+ self.out_proj = nn.Linear(hidden_dim, hidden_dim, bias=False)
28
+
29
+ def forward(self, x: torch.Tensor, rope_freqs: torch.Tensor) -> torch.Tensor:
30
+ B, T, C = x.shape
31
+
32
+ q = self.q_proj(x).view(B, T, self.n_heads, self.head_dim).transpose(1, 2)
33
+ k = self.k_proj(x).view(B, T, self.n_heads, self.head_dim).transpose(1, 2)
34
+ v = self.v_proj(x).view(B, T, self.n_heads, self.head_dim).transpose(1, 2)
35
+
36
+ q = apply_rope(q, rope_freqs[:T])
37
+ k = apply_rope(k, rope_freqs[:T])
38
+
39
+ # scaled dot-product attention with causal masking (built-in flash-attention
40
+ # kernel when running on a CUDA GPU; falls back to a math kernel on CPU)
41
+ out = F.scaled_dot_product_attention(
42
+ q, k, v,
43
+ is_causal=True,
44
+ dropout_p=self.dropout if self.training else 0.0,
45
+ )
46
+
47
+ out = out.transpose(1, 2).contiguous().view(B, T, C)
48
+ return self.out_proj(out)
model/config.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class IvmeConfig:
6
+ """Ivme-Conversate-v2 (Dense) architecture config.
7
+
8
+ Every field here corresponds to a decision in Section 4 of the design doc.
9
+ Values are chosen to match v1 wherever the doc calls for it, so any quality
10
+ difference between v1 and v2 is attributable to data/training, not size.
11
+ """
12
+
13
+ vocab_size: int = 16_000 # Section 4.9: 16k tokens, English-only
14
+ hidden_dim: int = 384 # Section 4.2: matches v1
15
+ n_layers: int = 10 # Section 4.3: matches v1
16
+ n_heads: int = 6 # Section 4.4: full attention, no GQA
17
+ context_len: int = 1024 # Section 4.10: matches v1
18
+ ffn_mult: float = 4.0 # SwiGLU hidden expansion (adjusted below for param parity)
19
+ rope_theta: float = 10_000.0 # standard RoPE base frequency
20
+ norm_eps: float = 1e-5 # RMSNorm epsilon
21
+ tie_embeddings: bool = True # Section 4.8
22
+ dropout: float = 0.0 # no dropout at this data:param ratio (heavily overtrained regime)
23
+
24
+ def __post_init__(self):
25
+ assert self.hidden_dim % self.n_heads == 0, "hidden_dim must be divisible by n_heads"
26
+
27
+ @property
28
+ def head_dim(self) -> int:
29
+ return self.hidden_dim // self.n_heads
model/feedforward.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+
5
+
6
+ class SwiGLU(nn.Module):
7
+ """SwiGLU feed-forward block (Section 4.6), as used in Llama/PaLM.
8
+
9
+ Standard formulation: down_proj(silu(gate_proj(x)) * up_proj(x))
10
+ The inner dim is scaled down from the naive 4x so that SwiGLU's extra
11
+ gate_proj matrix doesn't blow the parameter budget relative to a plain MLP
12
+ of the same nominal "4x" size -- this matches how Llama-style models size it.
13
+ """
14
+
15
+ def __init__(self, hidden_dim: int, mult: float = 4.0):
16
+ super().__init__()
17
+ # standard correction: 4 * hidden * (2/3) keeps param count comparable
18
+ # to a plain (non-gated) 4x MLP, rounded to a clean multiple of 8.
19
+ inner_dim = int(hidden_dim * mult * 2 / 3)
20
+ inner_dim = ((inner_dim + 7) // 8) * 8
21
+
22
+ self.gate_proj = nn.Linear(hidden_dim, inner_dim, bias=False)
23
+ self.up_proj = nn.Linear(hidden_dim, inner_dim, bias=False)
24
+ self.down_proj = nn.Linear(inner_dim, hidden_dim, bias=False)
25
+
26
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
27
+ return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
model/rmsnorm.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+
4
+
5
+ class RMSNorm(nn.Module):
6
+ """RMSNorm (Section 4.7): cheaper alternative to LayerNorm.
7
+
8
+ Rescales by root-mean-square of the activations instead of full
9
+ mean/variance normalization. No bias, single learnable scale per dim.
10
+ """
11
+
12
+ def __init__(self, dim: int, eps: float = 1e-5):
13
+ super().__init__()
14
+ self.eps = eps
15
+ self.weight = nn.Parameter(torch.ones(dim))
16
+
17
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
18
+ # compute in float32 for stability regardless of input dtype (bf16 etc.)
19
+ dtype = x.dtype
20
+ x = x.float()
21
+ rms = torch.rsqrt(x.pow(2).mean(dim=-1, keepdim=True) + self.eps)
22
+ out = x * rms
23
+ return (out.to(dtype)) * self.weight
model/rope.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+
3
+
4
+ def precompute_rope_freqs(head_dim: int, max_seq_len: int, theta: float = 10_000.0):
5
+ """Precompute the rotation angles used by RoPE (Section 4.5).
6
+
7
+ Returns a complex tensor of shape (max_seq_len, head_dim // 2) where each
8
+ entry encodes the rotation to apply at that position/frequency pair.
9
+ """
10
+ assert head_dim % 2 == 0, "RoPE requires an even head_dim"
11
+ freqs = 1.0 / (theta ** (torch.arange(0, head_dim, 2).float() / head_dim))
12
+ positions = torch.arange(max_seq_len).float()
13
+ angles = torch.outer(positions, freqs) # (seq_len, head_dim/2)
14
+ return torch.polar(torch.ones_like(angles), angles) # complex64
15
+
16
+
17
+ def apply_rope(x: torch.Tensor, rope_freqs: torch.Tensor) -> torch.Tensor:
18
+ """Apply rotary position embedding to a tensor of shape (B, n_heads, T, head_dim).
19
+
20
+ rope_freqs should be pre-sliced to the current sequence length T before
21
+ being passed in, i.e. rope_freqs[:T].
22
+ """
23
+ B, H, T, D = x.shape
24
+ x_complex = torch.view_as_complex(x.float().reshape(B, H, T, D // 2, 2))
25
+ freqs = rope_freqs.view(1, 1, T, D // 2)
26
+ x_rotated = x_complex * freqs
27
+ out = torch.view_as_real(x_rotated).reshape(B, H, T, D)
28
+ return out.type_as(x)
model/transformer.py ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+
4
+ from .config import IvmeConfig
5
+ from .rmsnorm import RMSNorm
6
+ from .rope import precompute_rope_freqs
7
+ from .attention import CausalSelfAttention
8
+ from .feedforward import SwiGLU
9
+
10
+
11
+ class TransformerBlock(nn.Module):
12
+ """One dense transformer layer (Section 3): pre-norm attention + pre-norm SwiGLU,
13
+ with residual connections around each. Identical shape repeated n_layers times --
14
+ no loops, no weight sharing (Section 3.1, distinguishing this from the shelved
15
+ Ivmetron design).
16
+ """
17
+
18
+ def __init__(self, cfg: IvmeConfig):
19
+ super().__init__()
20
+ self.attn_norm = RMSNorm(cfg.hidden_dim, eps=cfg.norm_eps)
21
+ self.attn = CausalSelfAttention(cfg.hidden_dim, cfg.n_heads, cfg.dropout)
22
+ self.ffn_norm = RMSNorm(cfg.hidden_dim, eps=cfg.norm_eps)
23
+ self.ffn = SwiGLU(cfg.hidden_dim, cfg.ffn_mult)
24
+
25
+ def forward(self, x: torch.Tensor, rope_freqs: torch.Tensor) -> torch.Tensor:
26
+ x = x + self.attn(self.attn_norm(x), rope_freqs)
27
+ x = x + self.ffn(self.ffn_norm(x))
28
+ return x
29
+
30
+
31
+ class IvmeConversateV2(nn.Module):
32
+ """Ivme-Conversate-v2 (Dense) -- the full model described in Section 4.
33
+
34
+ ~20M parameters, 10 layers, hidden_dim 384, 6 heads, RoPE, SwiGLU, RMSNorm,
35
+ tied embeddings, 16k vocab, 1024 context. See config.py for the exact spec.
36
+ """
37
+
38
+ def __init__(self, cfg: IvmeConfig):
39
+ super().__init__()
40
+ self.cfg = cfg
41
+
42
+ self.tok_embed = nn.Embedding(cfg.vocab_size, cfg.hidden_dim)
43
+ self.blocks = nn.ModuleList([TransformerBlock(cfg) for _ in range(cfg.n_layers)])
44
+ self.final_norm = RMSNorm(cfg.hidden_dim, eps=cfg.norm_eps)
45
+
46
+ # Section 4.8: tied embeddings -- output head reuses the input embedding
47
+ # table instead of learning a separate one.
48
+ self.lm_head = nn.Linear(cfg.hidden_dim, cfg.vocab_size, bias=False)
49
+ if cfg.tie_embeddings:
50
+ self.lm_head.weight = self.tok_embed.weight
51
+
52
+ rope_freqs = precompute_rope_freqs(cfg.head_dim, cfg.context_len, cfg.rope_theta)
53
+ self.register_buffer("rope_freqs", rope_freqs, persistent=False)
54
+
55
+ self.apply(self._init_weights)
56
+
57
+ def _init_weights(self, module: nn.Module):
58
+ if isinstance(module, nn.Linear):
59
+ nn.init.normal_(module.weight, mean=0.0, std=0.02)
60
+ if module.bias is not None:
61
+ nn.init.zeros_(module.bias)
62
+ elif isinstance(module, nn.Embedding):
63
+ nn.init.normal_(module.weight, mean=0.0, std=0.02)
64
+
65
+ def forward(self, idx: torch.Tensor, targets: torch.Tensor | None = None):
66
+ B, T = idx.shape
67
+ assert T <= self.cfg.context_len, (
68
+ f"sequence length {T} exceeds context_len {self.cfg.context_len}"
69
+ )
70
+
71
+ x = self.tok_embed(idx)
72
+ for block in self.blocks:
73
+ x = block(x, self.rope_freqs)
74
+ x = self.final_norm(x)
75
+ logits = self.lm_head(x)
76
+
77
+ loss = None
78
+ if targets is not None:
79
+ loss = nn.functional.cross_entropy(
80
+ logits.view(-1, logits.size(-1)),
81
+ targets.view(-1),
82
+ ignore_index=-1,
83
+ )
84
+ return logits, loss
85
+
86
+ def num_params(self, non_embedding: bool = False) -> int:
87
+ n = sum(p.numel() for p in self.parameters())
88
+ if non_embedding:
89
+ n -= self.tok_embed.weight.numel()
90
+ return n