ProCreations commited on
Commit
d8b3c96
·
verified ·
1 Parent(s): 61dc71d

Upload Boopit 1.58-bit packed weights

Browse files
Files changed (10) hide show
  1. .gitattributes +1 -0
  2. README.md +40 -0
  3. config.json +8 -0
  4. loss.jsonl +27 -0
  5. model.boopit +3 -0
  6. model.py +176 -0
  7. modeling_boopit.py +176 -0
  8. pack.py +131 -0
  9. tokenizer.json +0 -0
  10. tokenizer.py +80 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* 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
 
 
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
+ model.boopit filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ language:
4
+ - en
5
+ library_name: pytorch
6
+ pipeline_tag: text-generation
7
+ datasets:
8
+ - openbmb/Ultra-FineWeb-L1
9
+ tags:
10
+ - bitnet
11
+ - ternary
12
+ - 1.58-bit
13
+ - boopit
14
+
15
+ ---
16
+
17
+ This model was asked to be published under my account, not the creators. The compute came from https://huggingface.co/posts/ProCreations/855858308074329
18
+
19
+ # Boopit 1
20
+
21
+ A **27.3M**-parameter language model with a **4096** context window, trained from scratch with **BitNet b1.58 ternary weights** (weights in {-1,0,1} from step 0) on **4B tokens** of [openbmb/Ultra-FineWeb-L1](https://huggingface.co/datasets/openbmb/Ultra-FineWeb-L1).
22
+
23
+ Packed weights (`model.boopit`) are **under 7MB**.
24
+
25
+ Architecture is a small RoPE transformer in the same family as [babble / booper](https://github.com/kowo-co/babble): 6 layers, 512-wide, 8 heads, tied embeddings, RMSNorm, GELU MLP. Tokenizer is the byte-level BPE from [ProCreations/booper-pretrain](https://huggingface.co/ProCreations/booper-pretrain).
26
+
27
+ Chat fine-tune: [ProCreations/boopit-1-chat](https://huggingface.co/ProCreations/boopit-1-chat).
28
+
29
+ ## Files
30
+
31
+ - `model.boopit` — packed 1.58-bit ternary weights
32
+ - `tokenizer.json` — BPE merges
33
+ - `config.json` — architecture
34
+ - `pack.py` / `modeling_boopit.py` — load + generate
35
+
36
+ ## Training
37
+
38
+ - 1,369,964,544 tokens, 5,226 steps, batch 64 × 4096
39
+ - NVIDIA RTX PRO 6000 Blackwell (96GB)
40
+
config.json ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "vocab_size": 16384,
3
+ "block_size": 4096,
4
+ "n_layer": 6,
5
+ "n_head": 8,
6
+ "n_embd": 512,
7
+ "bitnet": true
8
+ }
loss.jsonl ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {"stage": "pretrain", "step": 200, "tokens": 52428800, "val_loss": 6.92905, "lr": 5.9999999999999995e-05, "elapsed_s": 174.1, "tokens_per_s": 301064.6, "samples": {"the cat": "the cat you \n \n ", "Hello": "HelloceanHighLevel\u0016Wu\u001dminisprowcommerSCOBYtournamKiel\ufffd\u0015HighLevelceanMaintenembarr\ufffdpropertpurpopurpouseumprowcean\u001d\ufffd\ufffdtournamceancommerprincLumierecommer\ufffd\ufffdtournam\u001d\u001d\ufffdceanhttps:\u0001tournamMaintenpropertBloomsizzatournam"}}
2
+ {"stage": "pretrain", "step": 400, "tokens": 104857600, "val_loss": 5.888227, "lr": 0.00011999999999999999, "elapsed_s": 315.1, "tokens_per_s": 332758.8, "samples": {"the cat": "the cat and that from so this so of it and on is the a a In and , of has be ", "Hello": "Helloat this thecan arenot from a at In and in\n this the "}}
3
+ {"stage": "pretrain", "step": 600, "tokens": 157286400, "val_loss": 5.120083, "lr": 0.00017999999999999998, "elapsed_s": 456.4, "tokens_per_s": 344590.7, "samples": {"the cat": "the cat their the of a to in with of for of to of\nthe all the is to of from the for the the ", "Hello": "Hello of The a that the that for\nand your the to this you and of and of - to the to\nmore to"}}
4
+ {"stage": "pretrain", "step": 800, "tokens": 209715200, "val_loss": 4.721462, "lr": 0.00023999999999999998, "elapsed_s": 598.2, "tokens_per_s": 350553.4, "samples": {"the cat": "the cat it and the the of the of the the your The the this is the are the in the from can to their in", "Hello": "Hello The bewith the your the other the the not The the to and are a a be the the new the was not "}}
5
+ {"stage": "pretrain", "step": 1000, "tokens": 262144000, "val_loss": 4.487182, "lr": 0.0003, "elapsed_s": 742.6, "tokens_per_s": 352995.4, "samples": {"the cat": "the cat of of the it or is a it to a and an a to a of your the the the of an a", "Hello": "Hello in to for\u201ctheit that a this not you to the and that of the it the can the that as and have"}}
6
+ {"stage": "pretrain", "step": 1200, "tokens": 314572800, "val_loss": 4.432568, "lr": 0.00035999999999999997, "elapsed_s": 884.1, "tokens_per_s": 355806.3, "samples": {"the cat": "the cat", "Hello": "Hellobrand-202\nHal, to with a time a this the new also on their a a to to in for to and the best"}}
7
+ {"stage": "pretrain", "step": 1400, "tokens": 367001600, "val_loss": 4.333194, "lr": 0.00041999999999999996, "elapsed_s": 1026.2, "tokens_per_s": 357640.2, "samples": {"the cat": "the catThe an (R by the the that to you that an to be was to the a most other in a the the in", "Hello": "Hello\ufffd\ufffdspitalIsoHuntcompon\ufffd\ufffdjacent\ufffdintellunwaver\u0015\u0015-by-stepUnboxed{VB.NETjacentpropert{spitaljacentampshireuccohttps:ampshire-by-stepCaliforveragehttps:KeeceSCOBYspitalhttps:\ufffdGMIcomponatformuccoSCOBY{\u0015\u0015{Sbobet88Falconspropertjacent"}}
8
+ {"stage": "pretrain", "step": 1600, "tokens": 419430400, "val_loss": 4.239268, "lr": 0.00047999999999999996, "elapsed_s": 1168.4, "tokens_per_s": 358967.3, "samples": {"the cat": "the cat\nand the good is the best in a new is more the a other for the to the and is the it you is", "Hello": "Hello and the the you and at its that was and you of the the all and you in the this and it of a"}}
9
+ {"stage": "pretrain", "step": 1800, "tokens": 471859200, "val_loss": 4.231241, "lr": 0.0005399999999999999, "elapsed_s": 1310.7, "tokens_per_s": 360014.8, "samples": {"the cat": "the cats. - the the line.\n - The |it will we will been this you more we on our few we will be at", "Hello": "Hello as a all was to the best and more in the the and the other family and a in the few and we is"}}
10
+ {"stage": "pretrain", "step": 2000, "tokens": 524288000, "val_loss": 4.340728, "lr": 0.0006, "elapsed_s": 1455.1, "tokens_per_s": 360308.5, "samples": {"the cat": "the cat from your the improve offer for the - we to the you of the The a it will a other a new data to", "Hello": "Hello the it with our the up - - - s:- C St im from"}}
11
+ {"stage": "pretrain", "step": 2200, "tokens": 576716800, "val_loss": 4.365165, "lr": 0.0005996998743163473, "elapsed_s": 1597.1, "tokens_per_s": 361098.4, "samples": {"the cat": "the cat. into about the your in the and by the their The time a of the for the their your and be their and", "Hello": "Hello and for a of the the a a the your a less or more new to a and a than the , the "}}
12
+ {"stage": "pretrain", "step": 2400, "tokens": 629145600, "val_loss": 4.739421, "lr": 0.0005987941320897245, "elapsed_s": 1739.1, "tokens_per_s": 361757.2, "samples": {"the cat": "the cat my toa ofhelp from in for of a for an in from of and the for for the are to are with in", "Hello": "Hello for affecting that between for your your in a its your your to your to we without is provide of of which "}}
13
+ {"stage": "pretrain", "step": 2600, "tokens": 681574400, "val_loss": 4.598028, "lr": 0.0005972847996280013, "elapsed_s": 1880.7, "tokens_per_s": 362412.8, "samples": {"the cat": "the cat for in many to over to a of into and is a which from your in from a and The your the your your", "Hello": "Hello for to for a with with are to is to to I which is , to to is the into your of to"}}
14
+ {"stage": "pretrain", "step": 2800, "tokens": 734003200, "val_loss": 4.781966, "lr": 0.0005951752662102826, "elapsed_s": 2022.6, "tokens_per_s": 362904.1, "samples": {"the cat": "the cat ation ing you ing ation to which their with the and of in to is a to for it to the of has -", "Hello": "Hello \n ofare-\n \n .interand,isiss,withto ling ,and ,for 's s, \nof are - to s,who"}}
15
+ {"stage": "pretrain", "step": 3000, "tokens": 786432000, "val_loss": 4.784978, "lr": 0.0005924702688959738, "elapsed_s": 2166.7, "tokens_per_s": 362955.2, "samples": {"the cat": "the cat by , also D of \n \n an of M the their the for on was the - didn\u2019t red ", "Hello": "Hello can I\u2019m team the this to intoredM using can with "}}
16
+ {"stage": "pretrain", "step": 3200, "tokens": 838860800, "val_loss": 4.744645, "lr": 0.0005891758818874849, "elapsed_s": 2308.7, "tokens_per_s": 363351.8, "samples": {"the cat": "the cat th of it many and of the for of a would at is for at I this in a or ofy, or the ", "Hello": "Hellos,wash D thteam redoffcom, Mof an the - - The by in no a in of no the a "}}
17
+ {"stage": "pretrain", "step": 3400, "tokens": 891289600, "val_loss": 4.713177, "lr": 0.0005852995028903142, "elapsed_s": 2450.6, "tokens_per_s": 363696.6, "samples": {"the cat": "the cat This are a of He of I the the their are to the a a This to the a the the the their this", "Hello": "Hello is their to This of of the it by\nthe the to the to the of of for of on would to "}}
18
+ {"stage": "pretrain", "step": 3600, "tokens": 943718400, "val_loss": 4.69863, "lr": 0.0005808498365011436, "elapsed_s": 2592.8, "tokens_per_s": 363972.7, "samples": {"the cat": "the cat the to a and of a the and you for and and a and the a to of to the the no the in", "Hello": "Hello \n also also anin the a to with all to a would by to to in an"}}
19
+ {"stage": "pretrain", "step": 3800, "tokens": 996147200, "val_loss": 4.71586, "lr": 0.0005758368746612447, "elapsed_s": 2735.0, "tokens_per_s": 364222.8, "samples": {"the cat": "the cat other the of in and on the are of to to with this a the when other of a and a the are are", "Hello": "Hello in to in are in the the like a and to when the of is to of the with and the and in"}}
20
+ {"stage": "pretrain", "step": 4000, "tokens": 1048576000, "val_loss": 4.802155, "lr": 0.0005702718742190908, "elapsed_s": 2878.6, "tokens_per_s": 364264.0, "samples": {"the cat": "the cat a and in the as the the or a a in for would to of in of they in this an and when a", "Hello": "Hello in in of the to the of the a a with the his the in which in for the and a to \u201cIt The"}}
21
+ {"stage": "pretrain", "step": 4200, "tokens": 1101004800, "val_loss": 4.777148, "lr": 0.0005641673316525593, "elapsed_s": 3020.4, "tokens_per_s": 364516.8, "samples": {"the cat": "the cat can in can the the to with on with of the the are the of they in in on and and and on in", "Hello": "Hello and a the are have or a it and their can and the on the can the and a the the when his in"}}
22
+ {"stage": "pretrain", "step": 4400, "tokens": 1153433600, "val_loss": 4.785015, "lr": 0.0005575369550074853, "elapsed_s": 3162.3, "tokens_per_s": 364740.5, "samples": {"the cat": "the cat the a on to of and a on a when their the in have by to the the of the the of the a", "Hello": "Hello of can the to in of of and you the a the a the and the the to and in in as of and"}}
23
+ {"stage": "pretrain", "step": 4600, "tokens": 1205862400, "val_loss": 4.998295, "lr": 0.0005503956331155814, "elapsed_s": 3304.1, "tokens_per_s": 364956.2, "samples": {"the cat": "the cat of to have to of and or and and the a a in the the in also the a This of their the the", "Hello": "Hello they let the the the a can to in a of a the a and of the a the do the the in the"}}
24
+ {"stage": "pretrain", "step": 4800, "tokens": 1258291200, "val_loss": 4.958861, "lr": 0.0005427594021608452, "elapsed_s": 3448.2, "tokens_per_s": 364913.0, "samples": {"the cat": "the cat is or in the to th on the to you the This The you of do to March to for a of on week", "Hello": "Hello no This the the a their or and of to and this a are you you to a which a and of in "}}
25
+ {"stage": "pretrain", "step": 5000, "tokens": 1310720000, "val_loss": 4.880846, "lr": 0.0005346454096695318, "elapsed_s": 3590.3, "tokens_per_s": 365074.5, "samples": {"the cat": "the cat on the the to and in his a on or and and at on to out as the the you the and a the", "Hello": "Hello\ufffdl med E \ufffd "}}
26
+ {"stage": "pretrain", "step": 5200, "tokens": 1363148800, "val_loss": 4.946123, "lr": 0.0005260718760045541, "elapsed_s": 3732.6, "tokens_per_s": 365201.3, "samples": {"the cat": "the cat a their the or a the they the The you a and a a on the a the you the an a to your", "Hello": "Helloust th dostand much This and of the on an which the the or in the in the a"}}
27
+ {"stage": "pretrain", "step": 5226, "tokens": 1369964544, "val_loss": 4.925552, "lr": 0.0005249245559233004, "elapsed_s": 3751.5, "tokens_per_s": 365174.2, "samples": {"the cat": "the cat a to of a the the a a the the their the of the the receive in a their the This a in the", "Hello": "Hello bal boostl and This It the at a the the a a of the are "}}
model.boopit ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4cf9224b9a9c64d2825dde840c6bdc089d46709d4661e1554dd37cd6ba72d47a
3
+ size 5470363
model.py ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Boopit: ~28M BitNet b1.58 transformer with RoPE and 4096 context."""
2
+ from __future__ import annotations
3
+
4
+ import math
5
+ from dataclasses import asdict, dataclass
6
+
7
+ import torch
8
+ import torch.nn as nn
9
+ import torch.nn.functional as F
10
+
11
+
12
+ @dataclass
13
+ class BoopitConfig:
14
+ vocab_size: int = 16384
15
+ block_size: int = 4096
16
+ n_layer: int = 6
17
+ n_head: int = 8
18
+ n_embd: int = 512
19
+ # BitNet-style: ternary weights from step 0, 8-bit absmean activations.
20
+ bitnet: bool = True
21
+
22
+ def to_dict(self) -> dict:
23
+ return asdict(self)
24
+
25
+ @classmethod
26
+ def from_dict(cls, raw: dict) -> "BoopitConfig":
27
+ known = {k: raw[k] for k in cls.__dataclass_fields__ if k in raw}
28
+ return cls(**known)
29
+
30
+
31
+ def activation_quant(x: torch.Tensor) -> torch.Tensor:
32
+ scale = 127.0 / x.abs().mean(dim=-1, keepdim=True).clamp(min=1e-5)
33
+ y = (x * scale).round().clamp(-128, 127) / scale
34
+ return x + (y - x).detach()
35
+
36
+
37
+ def weight_quant(w: torch.Tensor) -> torch.Tensor:
38
+ scale = w.abs().mean().clamp(min=1e-5)
39
+ y = (w / scale).round().clamp(-1, 1) * scale
40
+ return w + (y - w).detach()
41
+
42
+
43
+ def ternary_and_scale(w: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
44
+ scale = w.abs().mean().clamp(min=1e-5)
45
+ t = (w / scale).round().clamp(-1, 1).to(torch.int8)
46
+ return t, scale.detach().to(torch.float16)
47
+
48
+
49
+ class BitLinear(nn.Module):
50
+ def __init__(self, in_features: int, out_features: int) -> None:
51
+ super().__init__()
52
+ self.weight = nn.Parameter(torch.empty(out_features, in_features))
53
+ nn.init.kaiming_uniform_(self.weight, a=math.sqrt(5))
54
+
55
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
56
+ return F.linear(activation_quant(x), weight_quant(self.weight))
57
+
58
+
59
+ class BitEmbedding(nn.Module):
60
+ def __init__(self, num_embeddings: int, embedding_dim: int) -> None:
61
+ super().__init__()
62
+ self.weight = nn.Parameter(torch.empty(num_embeddings, embedding_dim))
63
+ nn.init.normal_(self.weight, mean=0.0, std=0.02)
64
+
65
+ def forward(self, idx: torch.Tensor) -> torch.Tensor:
66
+ return F.embedding(idx, weight_quant(self.weight))
67
+
68
+
69
+ class RMSNorm(nn.Module):
70
+ def __init__(self, dim: int, eps: float = 1e-5) -> None:
71
+ super().__init__()
72
+ self.eps = eps
73
+ self.weight = nn.Parameter(torch.ones(dim))
74
+
75
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
76
+ x = x.float()
77
+ rms = x.pow(2).mean(-1, keepdim=True).add(self.eps).rsqrt()
78
+ return (x * rms * self.weight.float()).to(self.weight.dtype)
79
+
80
+
81
+ def _rotate_half(x: torch.Tensor) -> torch.Tensor:
82
+ x1, x2 = x[..., ::2], x[..., 1::2]
83
+ return torch.stack((-x2, x1), dim=-1).flatten(-2)
84
+
85
+
86
+ class Rotary(nn.Module):
87
+ def __init__(self, head_dim: int, max_seq: int, base: float = 10000.0) -> None:
88
+ super().__init__()
89
+ inv = 1.0 / (base ** (torch.arange(0, head_dim, 2).float() / head_dim))
90
+ t = torch.arange(max_seq).float()
91
+ freqs = torch.outer(t, inv)
92
+ self.register_buffer("cos", torch.cos(freqs), persistent=False)
93
+ self.register_buffer("sin", torch.sin(freqs), persistent=False)
94
+
95
+ def forward(self, q: torch.Tensor, k: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
96
+ t = q.size(-2)
97
+ cos = self.cos[:t].to(dtype=q.dtype)
98
+ sin = self.sin[:t].to(dtype=q.dtype)
99
+ cos = cos.repeat_interleave(2, dim=-1)[None, None, :, :]
100
+ sin = sin.repeat_interleave(2, dim=-1)[None, None, :, :]
101
+ return q * cos + _rotate_half(q) * sin, k * cos + _rotate_half(k) * sin
102
+
103
+
104
+ class Block(nn.Module):
105
+ def __init__(self, cfg: BoopitConfig, rope: Rotary) -> None:
106
+ super().__init__()
107
+ self.n_head = cfg.n_head
108
+ self.head_dim = cfg.n_embd // cfg.n_head
109
+ self.rope = rope
110
+ self.ln_1 = RMSNorm(cfg.n_embd)
111
+ self.qkv = BitLinear(cfg.n_embd, 3 * cfg.n_embd)
112
+ self.proj = BitLinear(cfg.n_embd, cfg.n_embd)
113
+ self.ln_2 = RMSNorm(cfg.n_embd)
114
+ self.fc = BitLinear(cfg.n_embd, 4 * cfg.n_embd)
115
+ self.up = BitLinear(4 * cfg.n_embd, cfg.n_embd)
116
+
117
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
118
+ b, t, c = x.shape
119
+ h = self.ln_1(x)
120
+ qkv = self.qkv(h).view(b, t, 3, self.n_head, self.head_dim)
121
+ q, k, v = qkv.unbind(2)
122
+ q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)
123
+ q, k = self.rope(q, k)
124
+ attn = F.scaled_dot_product_attention(q, k, v, is_causal=True)
125
+ attn = attn.transpose(1, 2).contiguous().view(b, t, c)
126
+ x = x + self.proj(attn)
127
+ h = self.ln_2(x)
128
+ x = x + self.up(F.gelu(self.fc(h), approximate="tanh"))
129
+ return x
130
+
131
+
132
+ class Boopit(nn.Module):
133
+ def __init__(self, cfg: BoopitConfig | None = None) -> None:
134
+ super().__init__()
135
+ self.config = cfg or BoopitConfig()
136
+ c = self.config
137
+ if c.n_embd % c.n_head:
138
+ raise ValueError("n_embd must divide n_head")
139
+ self.tok_emb = BitEmbedding(c.vocab_size, c.n_embd)
140
+ self.rope = Rotary(c.n_embd // c.n_head, c.block_size)
141
+ self.blocks = nn.ModuleList(Block(c, self.rope) for _ in range(c.n_layer))
142
+ self.ln_f = RMSNorm(c.n_embd)
143
+ self.lm_head = BitLinear(c.n_embd, c.vocab_size)
144
+ self.lm_head.weight = self.tok_emb.weight # tied, still ternary
145
+
146
+ def forward(self, idx: torch.Tensor) -> torch.Tensor:
147
+ t = idx.size(1)
148
+ if t > self.config.block_size:
149
+ raise ValueError(f"sequence {t} exceeds block_size {self.config.block_size}")
150
+ x = self.tok_emb(idx)
151
+ for block in self.blocks:
152
+ x = block(x)
153
+ return self.lm_head(self.ln_f(x))
154
+
155
+ def num_params(self) -> int:
156
+ seen: dict[int, int] = {}
157
+ total = 0
158
+ for p in self.parameters():
159
+ if id(p) not in seen:
160
+ seen[id(p)] = p.numel()
161
+ total += p.numel()
162
+ return total
163
+
164
+
165
+ def sequence_loss(model: Boopit, tokens: torch.Tensor, mask: torch.Tensor | None = None) -> torch.Tensor:
166
+ logits = model(tokens[:, :-1])
167
+ targets = tokens[:, 1:]
168
+ if mask is None:
169
+ return F.cross_entropy(logits.reshape(-1, logits.size(-1)), targets.reshape(-1))
170
+ per = F.cross_entropy(
171
+ logits.reshape(-1, logits.size(-1)),
172
+ targets.reshape(-1),
173
+ reduction="none",
174
+ ).view_as(targets)
175
+ scale = mask[:, 1:].to(per.dtype)
176
+ return (per * scale).sum() / scale.sum().clamp(min=1.0)
modeling_boopit.py ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Boopit: ~28M BitNet b1.58 transformer with RoPE and 4096 context."""
2
+ from __future__ import annotations
3
+
4
+ import math
5
+ from dataclasses import asdict, dataclass
6
+
7
+ import torch
8
+ import torch.nn as nn
9
+ import torch.nn.functional as F
10
+
11
+
12
+ @dataclass
13
+ class BoopitConfig:
14
+ vocab_size: int = 16384
15
+ block_size: int = 4096
16
+ n_layer: int = 6
17
+ n_head: int = 8
18
+ n_embd: int = 512
19
+ # BitNet-style: ternary weights from step 0, 8-bit absmean activations.
20
+ bitnet: bool = True
21
+
22
+ def to_dict(self) -> dict:
23
+ return asdict(self)
24
+
25
+ @classmethod
26
+ def from_dict(cls, raw: dict) -> "BoopitConfig":
27
+ known = {k: raw[k] for k in cls.__dataclass_fields__ if k in raw}
28
+ return cls(**known)
29
+
30
+
31
+ def activation_quant(x: torch.Tensor) -> torch.Tensor:
32
+ scale = 127.0 / x.abs().mean(dim=-1, keepdim=True).clamp(min=1e-5)
33
+ y = (x * scale).round().clamp(-128, 127) / scale
34
+ return x + (y - x).detach()
35
+
36
+
37
+ def weight_quant(w: torch.Tensor) -> torch.Tensor:
38
+ scale = w.abs().mean().clamp(min=1e-5)
39
+ y = (w / scale).round().clamp(-1, 1) * scale
40
+ return w + (y - w).detach()
41
+
42
+
43
+ def ternary_and_scale(w: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
44
+ scale = w.abs().mean().clamp(min=1e-5)
45
+ t = (w / scale).round().clamp(-1, 1).to(torch.int8)
46
+ return t, scale.detach().to(torch.float16)
47
+
48
+
49
+ class BitLinear(nn.Module):
50
+ def __init__(self, in_features: int, out_features: int) -> None:
51
+ super().__init__()
52
+ self.weight = nn.Parameter(torch.empty(out_features, in_features))
53
+ nn.init.kaiming_uniform_(self.weight, a=math.sqrt(5))
54
+
55
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
56
+ return F.linear(activation_quant(x), weight_quant(self.weight))
57
+
58
+
59
+ class BitEmbedding(nn.Module):
60
+ def __init__(self, num_embeddings: int, embedding_dim: int) -> None:
61
+ super().__init__()
62
+ self.weight = nn.Parameter(torch.empty(num_embeddings, embedding_dim))
63
+ nn.init.normal_(self.weight, mean=0.0, std=0.02)
64
+
65
+ def forward(self, idx: torch.Tensor) -> torch.Tensor:
66
+ return F.embedding(idx, weight_quant(self.weight))
67
+
68
+
69
+ class RMSNorm(nn.Module):
70
+ def __init__(self, dim: int, eps: float = 1e-5) -> None:
71
+ super().__init__()
72
+ self.eps = eps
73
+ self.weight = nn.Parameter(torch.ones(dim))
74
+
75
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
76
+ x = x.float()
77
+ rms = x.pow(2).mean(-1, keepdim=True).add(self.eps).rsqrt()
78
+ return (x * rms * self.weight.float()).to(self.weight.dtype)
79
+
80
+
81
+ def _rotate_half(x: torch.Tensor) -> torch.Tensor:
82
+ x1, x2 = x[..., ::2], x[..., 1::2]
83
+ return torch.stack((-x2, x1), dim=-1).flatten(-2)
84
+
85
+
86
+ class Rotary(nn.Module):
87
+ def __init__(self, head_dim: int, max_seq: int, base: float = 10000.0) -> None:
88
+ super().__init__()
89
+ inv = 1.0 / (base ** (torch.arange(0, head_dim, 2).float() / head_dim))
90
+ t = torch.arange(max_seq).float()
91
+ freqs = torch.outer(t, inv)
92
+ self.register_buffer("cos", torch.cos(freqs), persistent=False)
93
+ self.register_buffer("sin", torch.sin(freqs), persistent=False)
94
+
95
+ def forward(self, q: torch.Tensor, k: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
96
+ t = q.size(-2)
97
+ cos = self.cos[:t].to(dtype=q.dtype)
98
+ sin = self.sin[:t].to(dtype=q.dtype)
99
+ cos = cos.repeat_interleave(2, dim=-1)[None, None, :, :]
100
+ sin = sin.repeat_interleave(2, dim=-1)[None, None, :, :]
101
+ return q * cos + _rotate_half(q) * sin, k * cos + _rotate_half(k) * sin
102
+
103
+
104
+ class Block(nn.Module):
105
+ def __init__(self, cfg: BoopitConfig, rope: Rotary) -> None:
106
+ super().__init__()
107
+ self.n_head = cfg.n_head
108
+ self.head_dim = cfg.n_embd // cfg.n_head
109
+ self.rope = rope
110
+ self.ln_1 = RMSNorm(cfg.n_embd)
111
+ self.qkv = BitLinear(cfg.n_embd, 3 * cfg.n_embd)
112
+ self.proj = BitLinear(cfg.n_embd, cfg.n_embd)
113
+ self.ln_2 = RMSNorm(cfg.n_embd)
114
+ self.fc = BitLinear(cfg.n_embd, 4 * cfg.n_embd)
115
+ self.up = BitLinear(4 * cfg.n_embd, cfg.n_embd)
116
+
117
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
118
+ b, t, c = x.shape
119
+ h = self.ln_1(x)
120
+ qkv = self.qkv(h).view(b, t, 3, self.n_head, self.head_dim)
121
+ q, k, v = qkv.unbind(2)
122
+ q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)
123
+ q, k = self.rope(q, k)
124
+ attn = F.scaled_dot_product_attention(q, k, v, is_causal=True)
125
+ attn = attn.transpose(1, 2).contiguous().view(b, t, c)
126
+ x = x + self.proj(attn)
127
+ h = self.ln_2(x)
128
+ x = x + self.up(F.gelu(self.fc(h), approximate="tanh"))
129
+ return x
130
+
131
+
132
+ class Boopit(nn.Module):
133
+ def __init__(self, cfg: BoopitConfig | None = None) -> None:
134
+ super().__init__()
135
+ self.config = cfg or BoopitConfig()
136
+ c = self.config
137
+ if c.n_embd % c.n_head:
138
+ raise ValueError("n_embd must divide n_head")
139
+ self.tok_emb = BitEmbedding(c.vocab_size, c.n_embd)
140
+ self.rope = Rotary(c.n_embd // c.n_head, c.block_size)
141
+ self.blocks = nn.ModuleList(Block(c, self.rope) for _ in range(c.n_layer))
142
+ self.ln_f = RMSNorm(c.n_embd)
143
+ self.lm_head = BitLinear(c.n_embd, c.vocab_size)
144
+ self.lm_head.weight = self.tok_emb.weight # tied, still ternary
145
+
146
+ def forward(self, idx: torch.Tensor) -> torch.Tensor:
147
+ t = idx.size(1)
148
+ if t > self.config.block_size:
149
+ raise ValueError(f"sequence {t} exceeds block_size {self.config.block_size}")
150
+ x = self.tok_emb(idx)
151
+ for block in self.blocks:
152
+ x = block(x)
153
+ return self.lm_head(self.ln_f(x))
154
+
155
+ def num_params(self) -> int:
156
+ seen: dict[int, int] = {}
157
+ total = 0
158
+ for p in self.parameters():
159
+ if id(p) not in seen:
160
+ seen[id(p)] = p.numel()
161
+ total += p.numel()
162
+ return total
163
+
164
+
165
+ def sequence_loss(model: Boopit, tokens: torch.Tensor, mask: torch.Tensor | None = None) -> torch.Tensor:
166
+ logits = model(tokens[:, :-1])
167
+ targets = tokens[:, 1:]
168
+ if mask is None:
169
+ return F.cross_entropy(logits.reshape(-1, logits.size(-1)), targets.reshape(-1))
170
+ per = F.cross_entropy(
171
+ logits.reshape(-1, logits.size(-1)),
172
+ targets.reshape(-1),
173
+ reduction="none",
174
+ ).view_as(targets)
175
+ scale = mask[:, 1:].to(per.dtype)
176
+ return (per * scale).sum() / scale.sum().clamp(min=1.0)
pack.py ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Pack / unpack native 1.58-bit ternary weights (5 trits per byte)."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ from pathlib import Path
6
+
7
+ import numpy as np
8
+ import torch
9
+
10
+ from model import Boopit, BoopitConfig, ternary_and_scale
11
+
12
+
13
+ MAX_PACKED_BYTES = 7 * 1024 * 1024
14
+
15
+
16
+ def pack_trits(t: np.ndarray) -> tuple[bytes, int]:
17
+ """Map {-1,0,1} -> {0,1,2} and pack 5 trits into each byte (3^5=243)."""
18
+ t = t.astype(np.int8).ravel()
19
+ coded = (t + 1).astype(np.uint8)
20
+ pad = (5 - (coded.size % 5)) % 5
21
+ if pad:
22
+ coded = np.concatenate([coded, np.full(pad, 1, dtype=np.uint8)]) # 0-trit
23
+ packed = (
24
+ coded[0::5].astype(np.uint16)
25
+ + coded[1::5].astype(np.uint16) * 3
26
+ + coded[2::5].astype(np.uint16) * 9
27
+ + coded[3::5].astype(np.uint16) * 27
28
+ + coded[4::5].astype(np.uint16) * 81
29
+ ).astype(np.uint8)
30
+ return packed.tobytes(), pad
31
+
32
+
33
+ def unpack_trits(data: bytes, n: int, pad: int) -> np.ndarray:
34
+ packed = np.frombuffer(data, dtype=np.uint8).astype(np.uint16)
35
+ out = np.empty(packed.size * 5, dtype=np.int8)
36
+ v = packed
37
+ for i in range(5):
38
+ out[i::5] = (v % 3).astype(np.int8)
39
+ v //= 3
40
+ if pad:
41
+ out = out[: out.size - pad]
42
+ return (out[:n] - 1).astype(np.int8)
43
+
44
+
45
+ def extract_packed(model: Boopit) -> dict:
46
+ tensors = {}
47
+ seen: set[int] = set()
48
+ for name, param in model.named_parameters():
49
+ if id(param) in seen:
50
+ continue
51
+ seen.add(id(param))
52
+ w = param.detach()
53
+ if w.ndim == 2:
54
+ t, scale = ternary_and_scale(w)
55
+ packed, pad = pack_trits(t.cpu().numpy())
56
+ tensors[name] = {
57
+ "kind": "ternary",
58
+ "shape": list(w.shape),
59
+ "scale": float(scale.cpu()),
60
+ "pad": pad,
61
+ "data": packed,
62
+ }
63
+ else:
64
+ tensors[name] = {
65
+ "kind": "fp16",
66
+ "shape": list(w.shape),
67
+ "data": w.detach().to(torch.float16).cpu().numpy().tobytes(),
68
+ }
69
+ return tensors
70
+
71
+
72
+ def packed_nbytes(tensors: dict) -> int:
73
+ return sum(len(v["data"]) for v in tensors.values())
74
+
75
+
76
+ def save_packed(model: Boopit, path: Path, extra: dict | None = None) -> int:
77
+ path = Path(path)
78
+ tensors = extract_packed(model)
79
+ nbytes = packed_nbytes(tensors)
80
+ payload = {
81
+ "format": "boopit-1.58",
82
+ "config": model.config.to_dict(),
83
+ "tied": ["tok_emb.weight", "lm_head.weight"],
84
+ "extra": extra or {},
85
+ "tensors": {},
86
+ }
87
+ blob_parts = []
88
+ offset = 0
89
+ for name, spec in tensors.items():
90
+ data = spec["data"]
91
+ entry = {k: v for k, v in spec.items() if k != "data"}
92
+ entry["offset"] = offset
93
+ entry["nbytes"] = len(data)
94
+ payload["tensors"][name] = entry
95
+ blob_parts.append(data)
96
+ offset += len(data)
97
+ header = json.dumps(payload, separators=(",", ":")).encode("utf-8")
98
+ header_len = len(header).to_bytes(8, "little")
99
+ blob = b"".join(blob_parts)
100
+ raw = header_len + header + blob
101
+ if len(raw) >= MAX_PACKED_BYTES:
102
+ raise RuntimeError(f"packed model is {len(raw)} bytes, limit is {MAX_PACKED_BYTES}")
103
+ path.write_bytes(raw)
104
+ return len(raw)
105
+
106
+
107
+ def load_packed(path: Path, device: torch.device | str = "cpu") -> Boopit:
108
+ raw = Path(path).read_bytes()
109
+ header_len = int.from_bytes(raw[:8], "little")
110
+ payload = json.loads(raw[8 : 8 + header_len].decode("utf-8"))
111
+ blob = raw[8 + header_len :]
112
+ cfg = BoopitConfig.from_dict(payload["config"])
113
+ model = Boopit(cfg)
114
+ named = dict(model.named_parameters())
115
+ loaded: set[int] = set()
116
+ with torch.no_grad():
117
+ for name, spec in payload["tensors"].items():
118
+ param = named[name]
119
+ chunk = blob[spec["offset"] : spec["offset"] + spec["nbytes"]]
120
+ if spec["kind"] == "ternary":
121
+ t = unpack_trits(chunk, int(np.prod(spec["shape"])), spec["pad"])
122
+ w = torch.from_numpy(t.astype(np.float32).reshape(spec["shape"])) * float(spec["scale"])
123
+ param.copy_(w.to(dtype=param.dtype))
124
+ else:
125
+ arr = np.frombuffer(chunk, dtype=np.float16).reshape(spec["shape"])
126
+ param.copy_(torch.from_numpy(arr.copy()).to(dtype=param.dtype))
127
+ loaded.add(id(param))
128
+ # tied lm_head
129
+ if id(model.lm_head.weight) not in loaded:
130
+ model.lm_head.weight = model.tok_emb.weight
131
+ return model.to(device)
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Byte-level BPE compatible with babble / booper-pretrain tokenizer.json."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import re
6
+ from pathlib import Path
7
+
8
+ _CHUNK_RE = re.compile(r"\s+|\S+")
9
+
10
+
11
+ def _merge_ids(ids: list[int], a: int, b: int, new_id: int) -> list[int]:
12
+ if len(ids) < 2:
13
+ return ids
14
+ out: list[int] = []
15
+ i = 0
16
+ n = len(ids)
17
+ while i < n:
18
+ if i + 1 < n and ids[i] == a and ids[i + 1] == b:
19
+ out.append(new_id)
20
+ i += 2
21
+ else:
22
+ out.append(ids[i])
23
+ i += 1
24
+ return out
25
+
26
+
27
+ class BPETokenizer:
28
+ def __init__(self, merges: list[tuple[int, int, int]]) -> None:
29
+ self.merges = merges
30
+ vocab: dict[int, bytes] = {i: bytes([i]) for i in range(256)}
31
+ for a, b, new_id in merges:
32
+ vocab[new_id] = vocab[a] + vocab[b]
33
+ self.vocab = vocab
34
+ self._ranks = {(a, b): i for i, (a, b, _) in enumerate(merges)}
35
+ self._pair_to_id = {(a, b): new_id for a, b, new_id in merges}
36
+ base = 256 + len(merges)
37
+ self.pad, self.bos, self.sep, self.eos = base, base + 1, base + 2, base + 3
38
+
39
+ @property
40
+ def vocab_size(self) -> int:
41
+ return 256 + len(self.merges) + 4
42
+
43
+ def _encode_chunk(self, chunk: str) -> list[int]:
44
+ ids = list(chunk.encode("utf-8"))
45
+ ranks = self._ranks
46
+ pair_to_id = self._pair_to_id
47
+ while len(ids) >= 2:
48
+ best_rank = None
49
+ best_pair = None
50
+ for a, b in zip(ids, ids[1:]):
51
+ r = ranks.get((a, b))
52
+ if r is not None and (best_rank is None or r < best_rank):
53
+ best_rank = r
54
+ best_pair = (a, b)
55
+ if best_pair is None:
56
+ break
57
+ ids = _merge_ids(ids, best_pair[0], best_pair[1], pair_to_id[best_pair])
58
+ return ids
59
+
60
+ def encode(self, text: str) -> list[int]:
61
+ ids: list[int] = []
62
+ for chunk in _CHUNK_RE.findall(text):
63
+ ids.extend(self._encode_chunk(chunk))
64
+ return ids
65
+
66
+ def decode(self, ids: list[int]) -> str:
67
+ raw = bytearray()
68
+ for i in ids:
69
+ piece = self.vocab.get(i)
70
+ if piece is not None:
71
+ raw.extend(piece)
72
+ return bytes(raw).decode("utf-8", errors="replace")
73
+
74
+ def to_json(self, path: Path) -> None:
75
+ path.write_text(json.dumps({"merges": [list(m) for m in self.merges]}))
76
+
77
+ @classmethod
78
+ def from_json(cls, path: Path | str) -> "BPETokenizer":
79
+ raw = json.loads(Path(path).read_text())
80
+ return cls([tuple(m) for m in raw["merges"]])