tardits 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tardits/gen.py +150 -0
- tardits/models/__init__.py +4 -0
- tardits/models/hyper.py +144 -0
- tardits/models/real.py +167 -0
- tardits/modules/__init__.py +27 -0
- tardits/modules/base.py +99 -0
- tardits/modules/conf.py +31 -0
- tardits/modules/core.py +286 -0
- tardits/modules/logging.py +239 -0
- tardits/modules/trainer.py +88 -0
- tardits/train.py +106 -0
- tardits-0.2.0.dist-info/METADATA +267 -0
- tardits-0.2.0.dist-info/RECORD +16 -0
- tardits-0.2.0.dist-info/WHEEL +4 -0
- tardits-0.2.0.dist-info/entry_points.txt +3 -0
- tardits-0.2.0.dist-info/licenses/LICENSE +201 -0
tardits/gen.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
from argparse import ArgumentParser
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
import os
|
|
5
|
+
import yaml
|
|
6
|
+
|
|
7
|
+
import torch
|
|
8
|
+
from tardits.models import HyperFormer, RealGPT
|
|
9
|
+
from tardits.modules import get_device
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main():
|
|
13
|
+
# --- CLI Arguments ---
|
|
14
|
+
parser = ArgumentParser(
|
|
15
|
+
description="Generates text from a trained model using its YAML config"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
parser.add_argument(
|
|
19
|
+
"--config",
|
|
20
|
+
"-c",
|
|
21
|
+
type=str,
|
|
22
|
+
default="examples/configs/complex.yaml",
|
|
23
|
+
help="Path to YAML configuration file",
|
|
24
|
+
)
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
"--checkpoint",
|
|
27
|
+
type=str,
|
|
28
|
+
default=None,
|
|
29
|
+
help="Optional override for model checkpoint path",
|
|
30
|
+
)
|
|
31
|
+
parser.add_argument(
|
|
32
|
+
"--vocab",
|
|
33
|
+
type=str,
|
|
34
|
+
default=None,
|
|
35
|
+
help="Optional override for vocab path",
|
|
36
|
+
)
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"--prompt",
|
|
39
|
+
"-p",
|
|
40
|
+
type=str,
|
|
41
|
+
default="\n",
|
|
42
|
+
help="Text prompt to start generation",
|
|
43
|
+
)
|
|
44
|
+
parser.add_argument(
|
|
45
|
+
"--length",
|
|
46
|
+
"-l",
|
|
47
|
+
type=int,
|
|
48
|
+
default=500,
|
|
49
|
+
help="Number of tokens to generate",
|
|
50
|
+
)
|
|
51
|
+
parser.add_argument(
|
|
52
|
+
"--temp",
|
|
53
|
+
"-t",
|
|
54
|
+
type=float,
|
|
55
|
+
default=1.0,
|
|
56
|
+
help="Model sampling temperature",
|
|
57
|
+
)
|
|
58
|
+
parser.add_argument(
|
|
59
|
+
"--top_k",
|
|
60
|
+
"-k",
|
|
61
|
+
type=int,
|
|
62
|
+
default=10,
|
|
63
|
+
help="Top-k sampling threshold",
|
|
64
|
+
)
|
|
65
|
+
parser.add_argument(
|
|
66
|
+
"--follow",
|
|
67
|
+
"-f",
|
|
68
|
+
action="store_true",
|
|
69
|
+
help="Endless stream of tokens",
|
|
70
|
+
)
|
|
71
|
+
parser.add_argument(
|
|
72
|
+
"--delay",
|
|
73
|
+
type=float,
|
|
74
|
+
default=0.04,
|
|
75
|
+
help="Delay between tokens in follow mode (seconds)",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
args = parser.parse_args()
|
|
79
|
+
|
|
80
|
+
# --- Read Config File ---
|
|
81
|
+
with open(args.config, "r", encoding="utf-8") as f:
|
|
82
|
+
cfg = yaml.safe_load(f)
|
|
83
|
+
|
|
84
|
+
if cfg["model"]["level"] < 1:
|
|
85
|
+
GPT = RealGPT
|
|
86
|
+
else:
|
|
87
|
+
GPT = HyperFormer
|
|
88
|
+
|
|
89
|
+
# --- Load Config ---
|
|
90
|
+
checkpoint_path = args.checkpoint or cfg["trainer"]["checkpoint"]
|
|
91
|
+
vocab_path = args.vocab or cfg.get("data", {}).get(
|
|
92
|
+
"vocab_save_path", "examples/saved/chars.txt"
|
|
93
|
+
)
|
|
94
|
+
device = get_device(cfg.get("device", "auto"))
|
|
95
|
+
|
|
96
|
+
# --- Load Weights ---
|
|
97
|
+
model = GPT.load_from_checkpoint(checkpoint_path, device=device)
|
|
98
|
+
model.eval()
|
|
99
|
+
|
|
100
|
+
# --- Load Vocabulary ---
|
|
101
|
+
with open(vocab_path, "r", encoding="utf-8") as ch:
|
|
102
|
+
chars = list(ch.read())
|
|
103
|
+
|
|
104
|
+
stoi = {ch: i for i, ch in enumerate(chars)}
|
|
105
|
+
itos = {i: ch for i, ch in enumerate(chars)}
|
|
106
|
+
|
|
107
|
+
def encode(si):
|
|
108
|
+
return [stoi[c] for c in si]
|
|
109
|
+
|
|
110
|
+
def decode(li):
|
|
111
|
+
return "".join([itos[i] for i in li])
|
|
112
|
+
|
|
113
|
+
prompt = args.prompt.replace("\\n", "\n")
|
|
114
|
+
idx = torch.tensor([encode(prompt)], dtype=torch.long).to(device)
|
|
115
|
+
|
|
116
|
+
# --- Generation ---
|
|
117
|
+
if args.follow:
|
|
118
|
+
# ๐งน Clear terminal screen
|
|
119
|
+
print("\033[2J\033[H", end="")
|
|
120
|
+
print(
|
|
121
|
+
f"โณ Token-Stream (complexity-lvl: {cfg['model']['level']} @ {checkpoint_path}) โ Ctrl+C to exit\n"
|
|
122
|
+
)
|
|
123
|
+
try:
|
|
124
|
+
sys.stdout.write(prompt)
|
|
125
|
+
sys.stdout.flush()
|
|
126
|
+
|
|
127
|
+
for token_id in model.generate_stream(
|
|
128
|
+
idx, temp=args.temp, top_k=args.top_k
|
|
129
|
+
):
|
|
130
|
+
char = itos[int(token_id)]
|
|
131
|
+
sys.stdout.write(char)
|
|
132
|
+
sys.stdout.flush()
|
|
133
|
+
|
|
134
|
+
if args.delay > 0:
|
|
135
|
+
time.sleep(args.delay)
|
|
136
|
+
except KeyboardInterrupt:
|
|
137
|
+
print("\n\n๐ Token stream stopped.")
|
|
138
|
+
|
|
139
|
+
else:
|
|
140
|
+
print(
|
|
141
|
+
decode(
|
|
142
|
+
model.generate(
|
|
143
|
+
idx, max_new_tokens=args.length, temp=args.temp, top_k=args.top_k
|
|
144
|
+
)[0].tolist()
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if __name__ == "__main__":
|
|
150
|
+
main()
|
tardits/models/hyper.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import torch.nn as nn
|
|
3
|
+
from torch.nn import functional as F
|
|
4
|
+
|
|
5
|
+
from tardits.modules import (
|
|
6
|
+
HyperEmbedding,
|
|
7
|
+
HyperLMHead,
|
|
8
|
+
HyperRMSNorm,
|
|
9
|
+
SAIL,
|
|
10
|
+
GyRoPE,
|
|
11
|
+
HyperLinear,
|
|
12
|
+
LMCoreMixin
|
|
13
|
+
)
|
|
14
|
+
from tardits.modules.conf import ModelConfig
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class HyperSelfAttention(nn.Module):
|
|
18
|
+
"""
|
|
19
|
+
Universal multi-headed self-attention for 2^level hypercomplex spaces.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, config: ModelConfig):
|
|
23
|
+
super().__init__()
|
|
24
|
+
self.config = config
|
|
25
|
+
self.level = config.level
|
|
26
|
+
self.head_size = config.n_embd // config.n_head
|
|
27
|
+
self.n_head = config.n_head
|
|
28
|
+
self.n_embd = config.n_embd
|
|
29
|
+
self.hyper_dim = 2**config.level
|
|
30
|
+
|
|
31
|
+
# Projections
|
|
32
|
+
self.q_proj = HyperLinear(self.n_embd, self.n_embd, level=self.level)
|
|
33
|
+
self.k_proj = HyperLinear(self.n_embd, self.n_embd, level=self.level)
|
|
34
|
+
self.v_proj = HyperLinear(self.n_embd, self.n_embd, level=self.level)
|
|
35
|
+
self.out_proj = HyperLinear(self.n_embd, self.n_embd, level=self.level)
|
|
36
|
+
|
|
37
|
+
self.rope = GyRoPE(
|
|
38
|
+
self.head_size, config.block_size, self.n_head, level=self.level
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
self.register_buffer(
|
|
42
|
+
"tril", torch.tril(torch.ones(config.block_size, config.block_size))
|
|
43
|
+
)
|
|
44
|
+
self.tril: torch.Tensor
|
|
45
|
+
|
|
46
|
+
self.dropout = nn.Dropout(config.dropout)
|
|
47
|
+
|
|
48
|
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
49
|
+
# x Shape: [B, T, C, hyper_dim]
|
|
50
|
+
B, T, C, hyper_dim = x.shape
|
|
51
|
+
|
|
52
|
+
q = self.q_proj(x)
|
|
53
|
+
k = self.k_proj(x)
|
|
54
|
+
v = self.v_proj(x)
|
|
55
|
+
|
|
56
|
+
q = q.view(B, T, self.n_head, self.head_size, hyper_dim).transpose(1, 2)
|
|
57
|
+
k = k.view(B, T, self.n_head, self.head_size, hyper_dim).transpose(1, 2)
|
|
58
|
+
v = v.view(B, T, self.n_head, self.head_size, hyper_dim).transpose(1, 2)
|
|
59
|
+
|
|
60
|
+
q = self.rope(q)
|
|
61
|
+
k = self.rope(k)
|
|
62
|
+
|
|
63
|
+
# [B, nh, T, hs, hyper_dim] -> [B, nh, T, hs * hyper_dim]
|
|
64
|
+
q_flat = q.flatten(start_dim=-2)
|
|
65
|
+
k_flat = k.flatten(start_dim=-2)
|
|
66
|
+
v_flat = v.flatten(start_dim=-2)
|
|
67
|
+
|
|
68
|
+
wei = q_flat @ k_flat.adjoint() * (self.hyper_dim * self.head_size) ** -0.5
|
|
69
|
+
wei = wei.real
|
|
70
|
+
wei = wei.masked_fill(self.tril[:T, :T] == 0, float("-inf"))
|
|
71
|
+
wei = F.softmax(wei, dim=-1)
|
|
72
|
+
wei = self.dropout(wei)
|
|
73
|
+
|
|
74
|
+
wei_complex = wei.to(torch.complex64)
|
|
75
|
+
|
|
76
|
+
out_flat = wei_complex @ v_flat # -> [B, nh, T, hs * hyper_dim]
|
|
77
|
+
|
|
78
|
+
# 6. Un-Flatten und Recombine: [B, nh, T, hs * hyper_dim] -> [B, nh, T, hs, hyper_dim]
|
|
79
|
+
out = out_flat.view(B, self.n_head, T, self.head_size, hyper_dim)
|
|
80
|
+
|
|
81
|
+
# -> [B, T, C, hyper_dim]
|
|
82
|
+
out = out.transpose(1, 2).contiguous().view(B, T, C, hyper_dim)
|
|
83
|
+
|
|
84
|
+
out = self.out_proj(out)
|
|
85
|
+
|
|
86
|
+
return out
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class HyperBlock(nn.Module):
|
|
91
|
+
"""Transformer block: Attention + SAIL with Residuals & RMSNorm"""
|
|
92
|
+
|
|
93
|
+
def __init__(self, config: ModelConfig):
|
|
94
|
+
super().__init__()
|
|
95
|
+
self.sa = HyperSelfAttention(config)
|
|
96
|
+
self.sail = SAIL(dim=config.n_embd, level=config.level)
|
|
97
|
+
self.ln1 = HyperRMSNorm(config.n_embd, level=config.level)
|
|
98
|
+
self.ln2 = HyperRMSNorm(config.n_embd, level=config.level)
|
|
99
|
+
|
|
100
|
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
101
|
+
# Attention + Residual
|
|
102
|
+
x = x + self.ln1(self.sa(x))
|
|
103
|
+
# FeedForward (SAIL) + Residual
|
|
104
|
+
x = x + self.ln2(self.sail(x))
|
|
105
|
+
return x
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class HyperFormer(nn.Module, LMCoreMixin):
|
|
109
|
+
"""
|
|
110
|
+
Universal Activation-Free Hypercomplex Transformer (v0.2.0).
|
|
111
|
+
Dynamically scaled via `config.level` (1=Complex, 2=Quaternion, 3=Octonion, 4=Sedenion...).
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
def __init__(self, config: ModelConfig):
|
|
115
|
+
super().__init__()
|
|
116
|
+
self.config = config
|
|
117
|
+
self.level = config.level
|
|
118
|
+
|
|
119
|
+
self.token_embedding = HyperEmbedding(
|
|
120
|
+
config.vocab_size, config.n_embd, level=self.level
|
|
121
|
+
)
|
|
122
|
+
self.blocks = nn.ModuleList([HyperBlock(config) for _ in range(config.n_layer)])
|
|
123
|
+
self.ln_f = HyperRMSNorm(config.n_embd, level=self.level)
|
|
124
|
+
self.lm_head = HyperLMHead(config.n_embd, config.vocab_size, level=self.level)
|
|
125
|
+
|
|
126
|
+
def forward(self, idx: torch.Tensor, targets: torch.Tensor | None = None):
|
|
127
|
+
B, T = idx.shape
|
|
128
|
+
x = self.token_embedding(idx)
|
|
129
|
+
|
|
130
|
+
for block in self.blocks:
|
|
131
|
+
x = block(x)
|
|
132
|
+
|
|
133
|
+
x = self.ln_f(x)
|
|
134
|
+
logits = self.lm_head(x)
|
|
135
|
+
|
|
136
|
+
if targets is None:
|
|
137
|
+
loss = None
|
|
138
|
+
else:
|
|
139
|
+
B, T, C = logits.shape
|
|
140
|
+
logits_flat = logits.view(B * T, C)
|
|
141
|
+
targets_flat = targets.view(B * T)
|
|
142
|
+
loss = F.cross_entropy(logits_flat, targets_flat)
|
|
143
|
+
|
|
144
|
+
return logits, loss
|
tardits/models/real.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import torch.nn as nn
|
|
3
|
+
from torch.nn import functional as F
|
|
4
|
+
|
|
5
|
+
from tardits.modules import LMCoreMixin
|
|
6
|
+
from tardits.modules.conf import ModelConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class RotaryPositionalEmbedding(nn.Module):
|
|
10
|
+
"""๐ Rotates query/key pairwise in the complex plane based on position."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, head_size, block_size):
|
|
13
|
+
super().__init__()
|
|
14
|
+
self.head_size = head_size
|
|
15
|
+
|
|
16
|
+
inv_freq = 1.0 / (
|
|
17
|
+
10000.0 ** (torch.arange(0, head_size, 2).float() / head_size)
|
|
18
|
+
)
|
|
19
|
+
t = torch.arange(block_size, dtype=torch.float32)
|
|
20
|
+
freqs = torch.outer(t, inv_freq)
|
|
21
|
+
self.emb = torch.cat((freqs, freqs), dim=-1)
|
|
22
|
+
|
|
23
|
+
self.register_buffer("cos_cached", self.emb.cos(), persistent=False)
|
|
24
|
+
self.register_buffer("sin_cached", self.emb.sin(), persistent=False)
|
|
25
|
+
self.cos_cached: torch.Tensor
|
|
26
|
+
self.sin_cached: torch.Tensor
|
|
27
|
+
|
|
28
|
+
def _rotate_half(self, x):
|
|
29
|
+
x1 = x[..., : self.head_size // 2]
|
|
30
|
+
x2 = x[..., self.head_size // 2 :]
|
|
31
|
+
return torch.cat((-x2, x1), dim=-1)
|
|
32
|
+
|
|
33
|
+
def forward(self, x):
|
|
34
|
+
# x.shape: (B, nh, T, hs)
|
|
35
|
+
T = x.shape[2]
|
|
36
|
+
cos = self.cos_cached[:T, :].unsqueeze(0).unsqueeze(1) # (1, 1, T, hs)
|
|
37
|
+
sin = self.sin_cached[:T, :].unsqueeze(0).unsqueeze(1) # (1, 1, T, hs)
|
|
38
|
+
|
|
39
|
+
return (x * cos) + (self._rotate_half(x) * sin)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
RoPE = RotaryPositionalEmbedding
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SwiGLU(nn.Module):
|
|
46
|
+
"""๐ SwiGLU Feed-Forward Network."""
|
|
47
|
+
|
|
48
|
+
def __init__(self, n_embd, hidden_dim, dropout):
|
|
49
|
+
super().__init__()
|
|
50
|
+
self.w = nn.Linear(n_embd, hidden_dim, bias=False)
|
|
51
|
+
self.v = nn.Linear(n_embd, hidden_dim, bias=False)
|
|
52
|
+
self.out_proj = nn.Linear(hidden_dim, n_embd, bias=False)
|
|
53
|
+
self.dropout = nn.Dropout(dropout)
|
|
54
|
+
|
|
55
|
+
def forward(self, x):
|
|
56
|
+
gate = F.silu(self.w(x))
|
|
57
|
+
activated = gate * self.v(x)
|
|
58
|
+
out = self.out_proj(activated)
|
|
59
|
+
|
|
60
|
+
return self.dropout(out)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class CasualSelfAttention(nn.Module):
|
|
64
|
+
"""casual (and causal) multi-headed self-attention"""
|
|
65
|
+
|
|
66
|
+
def __init__(self, config):
|
|
67
|
+
super().__init__()
|
|
68
|
+
self.config = config
|
|
69
|
+
self.head_size = config.n_embd // config.n_head
|
|
70
|
+
self.n_head = config.n_head
|
|
71
|
+
self.n_embd = config.n_embd
|
|
72
|
+
|
|
73
|
+
self.c_attn = nn.Linear(self.n_embd, 3 * self.n_embd, bias=False)
|
|
74
|
+
|
|
75
|
+
self.rope = RoPE(self.head_size, config.block_size)
|
|
76
|
+
|
|
77
|
+
self.register_buffer(
|
|
78
|
+
"tril", torch.tril(torch.ones(config.block_size, config.block_size))
|
|
79
|
+
)
|
|
80
|
+
self.tril: torch.Tensor
|
|
81
|
+
|
|
82
|
+
self.dropout = nn.Dropout(config.dropout)
|
|
83
|
+
|
|
84
|
+
self.proj = nn.Linear(config.n_embd, config.n_embd)
|
|
85
|
+
self.proj_dropout = nn.Dropout(config.dropout)
|
|
86
|
+
|
|
87
|
+
def forward(self, x):
|
|
88
|
+
B, T, C = x.shape
|
|
89
|
+
|
|
90
|
+
qkv = self.c_attn(x) # (B,T,3*C)
|
|
91
|
+
q, k, v = qkv.split(C, dim=2) # (B,T,C)
|
|
92
|
+
|
|
93
|
+
# view(B,T,nh,hs): (B,T,C) == (B,T,nh*hs) --> (B,T,nh,hs)
|
|
94
|
+
# transpose(1, 2): (B,T,nh,hs) --> (B,nh,T,hs)
|
|
95
|
+
q = q.view(B, T, self.n_head, self.head_size).transpose(1, 2)
|
|
96
|
+
k = k.view(B, T, self.n_head, self.head_size).transpose(1, 2)
|
|
97
|
+
v = v.view(B, T, self.n_head, self.head_size).transpose(1, 2)
|
|
98
|
+
|
|
99
|
+
# fancy RotaryPositionalEmbedding
|
|
100
|
+
q = self.rope(q)
|
|
101
|
+
k = self.rope(k)
|
|
102
|
+
|
|
103
|
+
wei = q @ k.transpose(-2, -1) * (self.head_size**-0.5) # (B,nh,T,T)
|
|
104
|
+
wei = wei.masked_fill(self.tril[:T, :T] == 0, float("-inf")) # (B,nh,T,T)
|
|
105
|
+
wei = F.softmax(wei, dim=-1) # (B,nh,T,T)
|
|
106
|
+
wei = self.dropout(wei) # (B,nh,T,T)
|
|
107
|
+
|
|
108
|
+
# weighted aggregation of value
|
|
109
|
+
out = wei @ v # (B,nh,T,T) @ (B,nh,T,hs) --> (B,nh,T,hs)
|
|
110
|
+
|
|
111
|
+
out = out.transpose(1, 2).reshape(B, T, C) # -> (B,T,nh,hs) -> (B,T,C)
|
|
112
|
+
|
|
113
|
+
out = self.proj(out)
|
|
114
|
+
out = self.proj_dropout(out)
|
|
115
|
+
|
|
116
|
+
return out # (B,T,C)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class Block(nn.Module):
|
|
120
|
+
"""Transformer block: communication followed by computation"""
|
|
121
|
+
|
|
122
|
+
def __init__(self, config):
|
|
123
|
+
# n_embd: embedding dimension, n_head: number of heads
|
|
124
|
+
super().__init__()
|
|
125
|
+
self.sa = CasualSelfAttention(config)
|
|
126
|
+
self.ffwd = SwiGLU(config.n_embd, int(config.n_embd * (8 / 3)), config.dropout)
|
|
127
|
+
self.ln1 = nn.RMSNorm(config.n_embd)
|
|
128
|
+
self.ln2 = nn.RMSNorm(config.n_embd)
|
|
129
|
+
|
|
130
|
+
def forward(self, x):
|
|
131
|
+
x = x + self.ln1(self.sa(x)) # x + because "residual connections"...
|
|
132
|
+
x = x + self.ln2(self.ffwd(x))
|
|
133
|
+
return x
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class RealGPT(nn.Module, LMCoreMixin):
|
|
137
|
+
def __init__(
|
|
138
|
+
self,
|
|
139
|
+
config: ModelConfig,
|
|
140
|
+
):
|
|
141
|
+
super().__init__()
|
|
142
|
+
|
|
143
|
+
self.config = config
|
|
144
|
+
self.token_embedding_table = nn.Embedding(config.vocab_size, config.n_embd)
|
|
145
|
+
|
|
146
|
+
self.blocks = nn.Sequential(*[Block(config) for _ in range(config.n_layer)])
|
|
147
|
+
|
|
148
|
+
self.ln_f = nn.RMSNorm(config.n_embd)
|
|
149
|
+
self.lm_head = nn.Linear(config.n_embd, config.vocab_size)
|
|
150
|
+
|
|
151
|
+
def forward(self, idx, targets=None):
|
|
152
|
+
B, T = idx.shape
|
|
153
|
+
x = self.token_embedding_table(idx) # (B,T,C)
|
|
154
|
+
x = self.blocks(x)
|
|
155
|
+
x = self.ln_f(x)
|
|
156
|
+
logits = self.lm_head(x)
|
|
157
|
+
|
|
158
|
+
if targets is None:
|
|
159
|
+
loss = None
|
|
160
|
+
else:
|
|
161
|
+
B, T, C = logits.shape
|
|
162
|
+
logits = logits.view(B * T, C)
|
|
163
|
+
targets = targets.view(B * T)
|
|
164
|
+
|
|
165
|
+
loss = F.cross_entropy(logits, targets)
|
|
166
|
+
|
|
167
|
+
return logits, loss
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from .trainer import Trainer
|
|
2
|
+
from .base import get_device
|
|
3
|
+
from .conf import ModelConfig, TrainerConfig, MLFlowConfig
|
|
4
|
+
from .core import (
|
|
5
|
+
SAIL,
|
|
6
|
+
GyRoPE,
|
|
7
|
+
HyperRMSNorm,
|
|
8
|
+
HyperEmbedding,
|
|
9
|
+
HyperLMHead,
|
|
10
|
+
HyperLinear,
|
|
11
|
+
)
|
|
12
|
+
from .base import LMCoreMixin
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"Trainer",
|
|
16
|
+
"ModelConfig",
|
|
17
|
+
"TrainerConfig",
|
|
18
|
+
"MLFlowConfig",
|
|
19
|
+
"SAIL",
|
|
20
|
+
"GyRoPE",
|
|
21
|
+
"LMCoreMixin"
|
|
22
|
+
"HyperRMSNorm",
|
|
23
|
+
"HyperEmbedding",
|
|
24
|
+
"HyperLMHead",
|
|
25
|
+
"HyperLinear",
|
|
26
|
+
"get_device"
|
|
27
|
+
]
|
tardits/modules/base.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# pyright: reportAttributeAccessIssue=false
|
|
2
|
+
import torch
|
|
3
|
+
from torch.nn import functional as F
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_device(requested_device: str = "auto") -> torch.device:
|
|
8
|
+
if requested_device == "auto" or not requested_device:
|
|
9
|
+
if torch.cuda.is_available():
|
|
10
|
+
return torch.device("cuda")
|
|
11
|
+
elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
|
|
12
|
+
return torch.device("mps") # Apple Silicon GPU
|
|
13
|
+
return torch.device("cpu")
|
|
14
|
+
return torch.device(requested_device)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class LMCoreMixin:
|
|
18
|
+
@torch.no_grad()
|
|
19
|
+
def generate(self, idx, max_new_tokens, temp=1.0, top_k=10):
|
|
20
|
+
device = next(self.parameters()).device
|
|
21
|
+
idx = idx.to(device)
|
|
22
|
+
# idx is (B,T) array of indices in the current context
|
|
23
|
+
for _ in range(max_new_tokens):
|
|
24
|
+
# crop idx to the last block_size max_new_tokens
|
|
25
|
+
idx_cond = idx[:, -self.config.block_size :]
|
|
26
|
+
# get predictions
|
|
27
|
+
logits, _ = self.forward(idx_cond)
|
|
28
|
+
# focus only on the last timestep
|
|
29
|
+
logits = logits[:, -1, :] # becomes (B,C)
|
|
30
|
+
# temp
|
|
31
|
+
logits = logits / temp
|
|
32
|
+
# top_k
|
|
33
|
+
v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
|
|
34
|
+
logits[logits < v[:, [-1]]] = -float("Inf")
|
|
35
|
+
# apply softmax to get probabilities
|
|
36
|
+
probs = F.softmax(logits, dim=-1) # (B,C)
|
|
37
|
+
# sample from distribution
|
|
38
|
+
idx_next = torch.multinomial(probs, num_samples=1) # (B,1)
|
|
39
|
+
# append sampled index to the running sequence
|
|
40
|
+
idx = torch.cat((idx, idx_next), dim=1) # (B,T+1)
|
|
41
|
+
return idx
|
|
42
|
+
|
|
43
|
+
@torch.no_grad()
|
|
44
|
+
def generate_stream(self, idx, temp=1.0, top_k=10):
|
|
45
|
+
while True:
|
|
46
|
+
# slicing:
|
|
47
|
+
idx_cond = idx[:, -self.config.block_size :]
|
|
48
|
+
logits, _ = self.forward(idx_cond)
|
|
49
|
+
logits = logits[:, -1, :] # becomes (B,C)
|
|
50
|
+
|
|
51
|
+
# temp/top_k
|
|
52
|
+
logits = logits / temp
|
|
53
|
+
v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
|
|
54
|
+
logits[logits < v[:, [-1]]] = -float("Inf")
|
|
55
|
+
|
|
56
|
+
# sampling
|
|
57
|
+
probs = F.softmax(logits, dim=-1) # (B,C)
|
|
58
|
+
idx_next = torch.multinomial(probs, num_samples=1) # (B,1)
|
|
59
|
+
|
|
60
|
+
# append
|
|
61
|
+
idx = torch.cat((idx, idx_next), dim=1) # (B,T+1)
|
|
62
|
+
|
|
63
|
+
# yield single token
|
|
64
|
+
yield idx_next.item()
|
|
65
|
+
|
|
66
|
+
def save_checkpoint(self, filepath="checkpoint.pt"):
|
|
67
|
+
"""saves model weights and config to disk"""
|
|
68
|
+
dirname = os.path.dirname(filepath)
|
|
69
|
+
if dirname:
|
|
70
|
+
os.makedirs(dirname, exist_ok=True)
|
|
71
|
+
checkpoint = {"model_state_dict": self.state_dict(), "config": self.config}
|
|
72
|
+
torch.save(checkpoint, filepath)
|
|
73
|
+
# print(f"๐พ checkpoint successfully saved: {filepath}")
|
|
74
|
+
|
|
75
|
+
def get_num_params(self):
|
|
76
|
+
"""returns number of trainable weights"""
|
|
77
|
+
total_params = 0
|
|
78
|
+
for p in self.parameters():
|
|
79
|
+
if p.requires_grad:
|
|
80
|
+
if p.is_complex():
|
|
81
|
+
total_params += p.numel() * 2
|
|
82
|
+
else:
|
|
83
|
+
total_params += p.numel()
|
|
84
|
+
return total_params
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def load_from_checkpoint(
|
|
88
|
+
cls, filepath="checkpoint.pt", device: str | torch.device = "cpu"
|
|
89
|
+
):
|
|
90
|
+
"""loads model state from checkpoint file"""
|
|
91
|
+
if not os.path.exists(filepath):
|
|
92
|
+
raise FileNotFoundError(f"โ file not found:\n{filepath}")
|
|
93
|
+
|
|
94
|
+
checkpoint = torch.load(filepath, map_location=device, weights_only=False)
|
|
95
|
+
model = cls(checkpoint["config"]) # type: ignore
|
|
96
|
+
model.load_state_dict(checkpoint["model_state_dict"])
|
|
97
|
+
model.to(device)
|
|
98
|
+
# print(f"๐ checkpoint successfully restored from:\n'{filepath}'")
|
|
99
|
+
return model
|
tardits/modules/conf.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class ModelConfig:
|
|
6
|
+
level: int = 1
|
|
7
|
+
block_size: int = 8 # maximum context length
|
|
8
|
+
vocab_size: int = 74
|
|
9
|
+
n_head: int = 4
|
|
10
|
+
n_embd: int = 32
|
|
11
|
+
n_layer: int = 3
|
|
12
|
+
dropout: float = 0.2
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class TrainerConfig:
|
|
17
|
+
batch_size: int = 32 # how many independent sequences processed in parallel?
|
|
18
|
+
max_iters: int = 5000
|
|
19
|
+
eval_interval: int = 500
|
|
20
|
+
learning_rate: float = 3e-4
|
|
21
|
+
eval_iters: int = 200
|
|
22
|
+
checkpoint: str = ""
|
|
23
|
+
save_interval: int = 0
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class MLFlowConfig:
|
|
28
|
+
experiment: str = "sail_experiment"
|
|
29
|
+
log_interval: int = 10
|
|
30
|
+
deep_log_interval: int = 100
|
|
31
|
+
system_metrics: bool = False
|