Upload Boopit 1.58-bit packed weights
Browse files- .gitattributes +1 -0
- README.md +40 -0
- config.json +8 -0
- loss.jsonl +27 -0
- model.boopit +3 -0
- model.py +176 -0
- modeling_boopit.py +176 -0
- pack.py +131 -0
- tokenizer.json +0 -0
- 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"]])
|