twgood commited on
Commit
ec8932d
·
0 Parent(s):

Duplicate from EBLANSoft/eblangpt1984

Browse files
Files changed (4) hide show
  1. .gitattributes +36 -0
  2. README.md +90 -0
  3. eblangpt1984.gguf +3 -0
  4. tiny_llama.py +255 -0
.gitattributes ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ eblangpt1984.gguf filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ language:
4
+ - ru
5
+ tags:
6
+ - llama
7
+ - gguf
8
+ - russian
9
+ - tiny
10
+ - char-level
11
+ - byte-level
12
+ pipeline_tag: text-generation
13
+ library_name: gguf
14
+ ---
15
+
16
+ # 🍅 EBLAN GPT 1984
17
+
18
+ Крошечная LLaMA-архитектура **на 115K параметров**, обученная на русском словаре словоформ.
19
+ Совместима с **llama.cpp** из коробки — архитектура `llama`, всё по спеке.
20
+
21
+ Это скорее демка того что можно обучить настоящий трансформер с нуля и экспортнуть в GGUF,
22
+ чем полезная модель. Генерит морфологически правдоподобные псевдорусские слова.
23
+
24
+ ## Архитектура
25
+
26
+ | Параметр | Значение |
27
+ |---|---|
28
+ | Тип | LLaMA (RMSNorm + RoPE + SwiGLU + causal self-attention) |
29
+ | vocab | 256 (byte-level) |
30
+ | n_embd | 64 |
31
+ | n_layers | 2 |
32
+ | n_heads | 4 |
33
+ | head_dim | 16 |
34
+ | n_ff | 128 |
35
+ | context | 64 |
36
+ | параметров | 115,008 |
37
+ | размер | 468 KB (F32) |
38
+
39
+ ## Использование
40
+
41
+ ### llama.cpp
42
+
43
+ ```bash
44
+ llama-completion -m eblangpt1984.gguf -p "программ" -n 200 --temp 0.7
45
+ ```
46
+
47
+ ### Python (llama-cpp-python)
48
+
49
+ ```python
50
+ from llama_cpp import Llama
51
+
52
+ llm = Llama(model_path="eblangpt1984.gguf", n_ctx=64)
53
+ out = llm("программ", max_tokens=100, temperature=0.7)
54
+ print(out["choices"][0]["text"])
55
+ ```
56
+
57
+ ## Пример вывода
58
+
59
+ Промпт `программ` при temp=0.7:
60
+
61
+ ```
62
+ программою плапланную окоплежившему отмехальногичество
63
+ выконымпарваюшеннпруеках арлватлеганиа тууу стшнноурра маш
64
+ ```
65
+
66
+ Модель поймала окончания русских слов (склонения, спряжения, причастия),
67
+ но синтаксису и семантике не обучена — в датасете был только словарь,
68
+ без связных предложений.
69
+
70
+ ## Обучение
71
+
72
+ Обучено на сабсете русского словаря словоформ (30 KB),
73
+ 1000 шагов AdamW, batch=16, lr=3e-3. Лосс 5.7 → 0.89.
74
+
75
+ Код обучения: см. `tiny_llama.py`.
76
+
77
+ Переобучить на своих данных:
78
+ ```bash
79
+ python tiny_llama.py train свой_текст.txt model.gguf 2000
80
+ ```
81
+
82
+ ## Как улучшить
83
+
84
+ - Увеличить `N_EMBD=128`, `N_LAYERS=4`, `N_FF=256` — будет ~1M параметров
85
+ - Обучать на связной прозе (не только словарь) — появится хоть какая-то семантика
86
+ - Больше шагов (10K+) на большем датасете (несколько МБ)
87
+
88
+ ## Лицензия
89
+
90
+ Apache 2.0 — делай что хочешь.
eblangpt1984.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9424433aef770ceffa88771e7553d25f2481e5adaf3595b21172a84bd73486e3
3
+ size 467840
tiny_llama.py ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ tiny_llama.py — крошечная LLaMA для llama.cpp.
3
+
4
+ Архитектура как у настоящей LLaMA: RMSNorm + RoPE + SwiGLU + causal attention.
5
+ Байт-вокаб (256 токенов) чтоб не возиться со спм/бпе.
6
+
7
+ Запуск:
8
+ python tiny_llama.py train russian.txt eblangpt1984.gguf
9
+ python tiny_llama.py test eblangpt1984.gguf # проверить что корректно читается
10
+
11
+ После экспорта пробуй:
12
+ llama-cli -m eblangpt1984.gguf -p "привет" -n 200 --temp 0.8
13
+ """
14
+
15
+ import sys
16
+ import math
17
+ import time
18
+ import struct
19
+ import numpy as np
20
+ import torch
21
+ import torch.nn as nn
22
+ import torch.nn.functional as F
23
+ from gguf import GGUFWriter, TokenType
24
+
25
+ # ===================== КОНФИГ =====================
26
+ VOCAB = 256
27
+ N_EMBD = 64
28
+ N_LAYERS = 2
29
+ N_HEADS = 4
30
+ HEAD_DIM = N_EMBD // N_HEADS # 16
31
+ N_FF = 128
32
+ CTX_LEN = 64
33
+ ROPE_THETA = 10000.0
34
+ RMS_EPS = 1e-5
35
+
36
+ ARCH = "llama"
37
+ MODEL_NAME = "eblangpt1984"
38
+
39
+ # ===================== МОДЕЛЬ =====================
40
+ class RMSNorm(nn.Module):
41
+ def __init__(self, d, eps=RMS_EPS):
42
+ super().__init__()
43
+ self.weight = nn.Parameter(torch.ones(d))
44
+ self.eps = eps
45
+ def forward(self, x):
46
+ return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps) * self.weight
47
+
48
+
49
+ def precompute_rope(seqlen, head_dim, theta=ROPE_THETA, device="cpu"):
50
+ freqs = 1.0 / (theta ** (torch.arange(0, head_dim, 2, device=device).float() / head_dim))
51
+ t = torch.arange(seqlen, device=device).float()
52
+ f = torch.outer(t, freqs) # [T, D/2]
53
+ return torch.cos(f), torch.sin(f) # каждый [T, D/2]
54
+
55
+
56
+ def apply_rope(x, cos, sin):
57
+ # x: [B, H, T, D]. Используется "interleaved" схема — как в llama.cpp.
58
+ T = x.size(-2)
59
+ cos = cos[:T].unsqueeze(0).unsqueeze(0)
60
+ sin = sin[:T].unsqueeze(0).unsqueeze(0)
61
+ x1, x2 = x[..., 0::2], x[..., 1::2]
62
+ y1 = x1 * cos - x2 * sin
63
+ y2 = x1 * sin + x2 * cos
64
+ return torch.stack((y1, y2), dim=-1).flatten(-2)
65
+
66
+
67
+ class Block(nn.Module):
68
+ def __init__(self):
69
+ super().__init__()
70
+ self.attn_norm = RMSNorm(N_EMBD)
71
+ self.wq = nn.Linear(N_EMBD, N_EMBD, bias=False)
72
+ self.wk = nn.Linear(N_EMBD, N_EMBD, bias=False)
73
+ self.wv = nn.Linear(N_EMBD, N_EMBD, bias=False)
74
+ self.wo = nn.Linear(N_EMBD, N_EMBD, bias=False)
75
+
76
+ self.ffn_norm = RMSNorm(N_EMBD)
77
+ self.w_gate = nn.Linear(N_EMBD, N_FF, bias=False)
78
+ self.w_up = nn.Linear(N_EMBD, N_FF, bias=False)
79
+ self.w_down = nn.Linear(N_FF, N_EMBD, bias=False)
80
+
81
+ def forward(self, x, cos, sin, mask):
82
+ B, T, D = x.shape
83
+ h = self.attn_norm(x)
84
+ q = self.wq(h).view(B, T, N_HEADS, HEAD_DIM).transpose(1, 2)
85
+ k = self.wk(h).view(B, T, N_HEADS, HEAD_DIM).transpose(1, 2)
86
+ v = self.wv(h).view(B, T, N_HEADS, HEAD_DIM).transpose(1, 2)
87
+ q = apply_rope(q, cos, sin)
88
+ k = apply_rope(k, cos, sin)
89
+
90
+ att = (q @ k.transpose(-2, -1)) / math.sqrt(HEAD_DIM)
91
+ att = att.masked_fill(mask[:T, :T], float("-inf"))
92
+ att = F.softmax(att, dim=-1)
93
+ out = (att @ v).transpose(1, 2).contiguous().view(B, T, D)
94
+ x = x + self.wo(out)
95
+
96
+ h = self.ffn_norm(x)
97
+ x = x + self.w_down(F.silu(self.w_gate(h)) * self.w_up(h))
98
+ return x
99
+
100
+
101
+ class TinyLlama(nn.Module):
102
+ def __init__(self):
103
+ super().__init__()
104
+ self.embed = nn.Embedding(VOCAB, N_EMBD)
105
+ self.blocks = nn.ModuleList([Block() for _ in range(N_LAYERS)])
106
+ self.norm = RMSNorm(N_EMBD)
107
+ self.lm_head = nn.Linear(N_EMBD, VOCAB, bias=False)
108
+
109
+ cos, sin = precompute_rope(CTX_LEN, HEAD_DIM)
110
+ self.register_buffer("cos", cos, persistent=False)
111
+ self.register_buffer("sin", sin, persistent=False)
112
+ mask = torch.triu(torch.ones(CTX_LEN, CTX_LEN, dtype=torch.bool), diagonal=1)
113
+ self.register_buffer("mask", mask, persistent=False)
114
+
115
+ def forward(self, x):
116
+ h = self.embed(x)
117
+ for b in self.blocks:
118
+ h = b(h, self.cos, self.sin, self.mask)
119
+ return self.lm_head(self.norm(h))
120
+
121
+
122
+ # ===================== ОБУЧЕНИЕ =====================
123
+ def train_model(text_path, out_path, steps=3000, lr=3e-3, bs=16):
124
+ with open(text_path, "rb") as f:
125
+ data = f.read()
126
+ print(f"текст: {len(data)} байт")
127
+
128
+ ids = np.frombuffer(data, dtype=np.uint8).astype(np.int64)
129
+ torch.manual_seed(42)
130
+ model = TinyLlama()
131
+ n_params = sum(p.numel() for p in model.parameters())
132
+ print(f"модель: {n_params:,} параметров")
133
+
134
+ opt = torch.optim.AdamW(model.parameters(), lr=lr, weight_decay=0.01)
135
+
136
+ def sample_batch():
137
+ idx = np.random.randint(0, len(ids) - CTX_LEN - 1, size=bs)
138
+ x = np.stack([ids[i:i + CTX_LEN] for i in idx])
139
+ y = np.stack([ids[i + 1:i + CTX_LEN + 1] for i in idx])
140
+ return torch.from_numpy(x), torch.from_numpy(y)
141
+
142
+ model.train()
143
+ t0 = time.time()
144
+ run = 0.0
145
+ for step in range(steps):
146
+ x, y = sample_batch()
147
+ logits = model(x)
148
+ loss = F.cross_entropy(logits.view(-1, VOCAB), y.view(-1))
149
+ opt.zero_grad()
150
+ loss.backward()
151
+ torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
152
+ opt.step()
153
+ run = 0.98 * run + 0.02 * loss.item() if step else loss.item()
154
+ if step % 100 == 0 or step == steps - 1:
155
+ dt = time.time() - t0
156
+ print(f" шаг {step:5d}/{steps} loss={run:.3f} [{dt:.1f}s]")
157
+
158
+ export_gguf(model, out_path)
159
+
160
+
161
+ # ===================== ЭКСПОРТ В GGUF (архитектура "llama") =====================
162
+ def export_gguf(model, out_path):
163
+ print(f"экспорт в {out_path}...")
164
+ w = GGUFWriter(out_path, ARCH)
165
+
166
+ # --- метадата LLaMA ---
167
+ w.add_name(MODEL_NAME)
168
+ w.add_context_length(CTX_LEN)
169
+ w.add_embedding_length(N_EMBD)
170
+ w.add_block_count(N_LAYERS)
171
+ w.add_feed_forward_length(N_FF)
172
+ w.add_head_count(N_HEADS)
173
+ w.add_head_count_kv(N_HEADS) # без GQA
174
+ w.add_layer_norm_rms_eps(RMS_EPS)
175
+ w.add_rope_dimension_count(HEAD_DIM)
176
+ w.add_rope_freq_base(ROPE_THETA)
177
+ w.add_file_type(0) # all F32
178
+
179
+ # --- байт-токенайзер ---
180
+ tokens = [f"<0x{b:02X}>".encode("utf-8") for b in range(VOCAB)]
181
+ scores = [-1000.0 + float(i) for i in range(VOCAB)]
182
+ types = [TokenType.BYTE.value] * VOCAB
183
+
184
+ w.add_tokenizer_model("llama")
185
+ w.add_tokenizer_pre("default")
186
+ w.add_token_list(tokens)
187
+ w.add_token_scores(scores)
188
+ w.add_token_types(types)
189
+ w.add_bos_token_id(0)
190
+ w.add_eos_token_id(0)
191
+ w.add_unk_token_id(0)
192
+ w.add_add_bos_token(False)
193
+ w.add_add_eos_token(False)
194
+
195
+ # --- тензоры ---
196
+ sd = model.state_dict()
197
+
198
+ def add(name, tensor):
199
+ arr = tensor.detach().to(torch.float32).cpu().numpy()
200
+ w.add_tensor(name, arr)
201
+
202
+ add("token_embd.weight", sd["embed.weight"]) # [V, E]
203
+ add("output_norm.weight", sd["norm.weight"]) # [E]
204
+ add("output.weight", sd["lm_head.weight"]) # [V, E]
205
+
206
+ for i in range(N_LAYERS):
207
+ p = f"blocks.{i}"
208
+ q = f"blk.{i}"
209
+ add(f"{q}.attn_norm.weight", sd[f"{p}.attn_norm.weight"])
210
+ add(f"{q}.attn_q.weight", sd[f"{p}.wq.weight"])
211
+ add(f"{q}.attn_k.weight", sd[f"{p}.wk.weight"])
212
+ add(f"{q}.attn_v.weight", sd[f"{p}.wv.weight"])
213
+ add(f"{q}.attn_output.weight", sd[f"{p}.wo.weight"])
214
+ add(f"{q}.ffn_norm.weight", sd[f"{p}.ffn_norm.weight"])
215
+ add(f"{q}.ffn_gate.weight", sd[f"{p}.w_gate.weight"])
216
+ add(f"{q}.ffn_up.weight", sd[f"{p}.w_up.weight"])
217
+ add(f"{q}.ffn_down.weight", sd[f"{p}.w_down.weight"])
218
+
219
+ w.write_header_to_file()
220
+ w.write_kv_data_to_file()
221
+ w.write_tensors_to_file()
222
+ w.close()
223
+ print(f"готово: {out_path}")
224
+
225
+
226
+ # ===================== ПРОВЕРКА ФАЙЛА =====================
227
+ def test_gguf(path):
228
+ with open(path, "rb") as f:
229
+ magic, ver = struct.unpack("<II", f.read(8))
230
+ tc, kv = struct.unpack("<QQ", f.read(16))
231
+ print(f"GGUF v{ver}, магия=0x{magic:08X}")
232
+ print(f" тензоров: {tc}")
233
+ print(f" метадата записей: {kv}")
234
+ print(f" размер файла: {__import__('os').path.getsize(path)} байт")
235
+ assert magic == 0x46554747, "битая магия"
236
+ print("формат валидный ✓")
237
+
238
+
239
+ # ===================== MAIN =====================
240
+ if __name__ == "__main__":
241
+ if len(sys.argv) < 2:
242
+ print(__doc__)
243
+ sys.exit(1)
244
+
245
+ cmd = sys.argv[1]
246
+ if cmd == "train":
247
+ text = sys.argv[2] if len(sys.argv) > 2 else "russian_mini.txt"
248
+ out = sys.argv[3] if len(sys.argv) > 3 else "eblangpt1984.gguf"
249
+ steps = int(sys.argv[4]) if len(sys.argv) > 4 else 3000
250
+ train_model(text, out, steps=steps)
251
+ test_gguf(out)
252
+ elif cmd == "test":
253
+ test_gguf(sys.argv[2])
254
+ else:
255
+ print(__doc__)