clyxbox 0.2.0__tar.gz

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.
clyxbox-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: clyxbox
3
+ Version: 0.2.0
4
+ Summary: Inference library for Clyx language models
5
+ License: Apache-2.0
6
+ Project-URL: Homepage, https://huggingface.co/syntropic-clx
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: torch>=2.0
10
+ Requires-Dist: tokenizers>=0.19
11
+ Requires-Dist: huggingface_hub>=0.23
12
+ Requires-Dist: safetensors>=0.4
@@ -0,0 +1,5 @@
1
+ from .model import ClyxModel, ModelConfig
2
+ from .tokenizer import ClyxTokenizer
3
+
4
+ __version__ = "0.2.0"
5
+ __all__ = ["ClyxModel", "ClyxTokenizer", "ModelConfig"]
@@ -0,0 +1,311 @@
1
+ import json
2
+ import math
3
+ from dataclasses import dataclass
4
+ from typing import List, Optional, Tuple, Union
5
+
6
+ import torch
7
+ import torch.nn as nn
8
+ import torch.nn.functional as F
9
+ import torch.utils.checkpoint as checkpoint_utils
10
+
11
+
12
+ # ── Config ────────────────────────────────────────────────────────────────────
13
+
14
+ @dataclass
15
+ class ModelConfig:
16
+ vocab_size: int = 32000
17
+ hidden_size: int = 768
18
+ num_layers: int = 12
19
+ num_heads: int = 12
20
+ max_position_embeddings: int = 2048
21
+ intermediate_size: int = 2048
22
+ norm_eps: float = 1e-6
23
+ attn_dropout: float = 0.0
24
+ resid_dropout: float = 0.0
25
+ tie_word_embeddings: bool = True
26
+ gradient_checkpointing: bool = False
27
+
28
+
29
+ # ── Architecture ──────────────────────────────────────────────────────────────
30
+
31
+ class RMSNorm(nn.Module):
32
+ def __init__(self, dim: int, eps: float = 1e-6):
33
+ super().__init__()
34
+ self.eps = eps
35
+ self.weight = nn.Parameter(torch.ones(dim))
36
+
37
+ def forward(self, x):
38
+ return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps) * self.weight
39
+
40
+
41
+ class RotaryEmbedding(nn.Module):
42
+ def __init__(self, dim, max_position_embeddings=2048, base=10000, device=None):
43
+ super().__init__()
44
+ self.dim = dim
45
+ self.base = base
46
+ self.max_position_embeddings = max_position_embeddings
47
+ inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2).float().to(device) / dim))
48
+ self.register_buffer("inv_freq", inv_freq, persistent=False)
49
+ self._set_cos_sin_cache(max_position_embeddings, device=device, dtype=torch.get_default_dtype())
50
+
51
+ def _set_cos_sin_cache(self, seq_len, device, dtype):
52
+ self.max_seq_len_cached = seq_len
53
+ t = torch.arange(seq_len, device=device, dtype=self.inv_freq.dtype)
54
+ freqs = torch.outer(t, self.inv_freq)
55
+ emb = torch.cat((freqs, freqs), dim=-1)
56
+ self.register_buffer("cos_cached", emb.cos().to(dtype), persistent=False)
57
+ self.register_buffer("sin_cached", emb.sin().to(dtype), persistent=False)
58
+
59
+ def forward(self, x, seq_len):
60
+ if seq_len > self.max_seq_len_cached or self.cos_cached.device != x.device:
61
+ self._set_cos_sin_cache(seq_len, device=x.device, dtype=x.dtype)
62
+ return self.cos_cached[:seq_len].to(dtype=x.dtype), self.sin_cached[:seq_len].to(dtype=x.dtype)
63
+
64
+
65
+ def rotate_half(x):
66
+ x1 = x[..., : x.shape[-1] // 2]
67
+ x2 = x[..., x.shape[-1] // 2 :]
68
+ return torch.cat((-x2, x1), dim=-1)
69
+
70
+
71
+ def apply_rotary_pos_emb(q, k, cos, sin, position_ids):
72
+ cos = cos[position_ids].unsqueeze(1)
73
+ sin = sin[position_ids].unsqueeze(1)
74
+ return (q * cos) + (rotate_half(q) * sin), (k * cos) + (rotate_half(k) * sin)
75
+
76
+
77
+ class CausalSelfAttention(nn.Module):
78
+ def __init__(self, config: ModelConfig):
79
+ super().__init__()
80
+ self.num_heads = config.num_heads
81
+ self.head_dim = config.hidden_size // config.num_heads
82
+ self.q_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=False)
83
+ self.k_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=False)
84
+ self.v_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=False)
85
+ self.out_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=False)
86
+ self.attn_dropout = config.attn_dropout
87
+ self.resid_dropout = nn.Dropout(config.resid_dropout)
88
+
89
+ def forward(self, x, rotary_emb, position_ids, attn_mask=None, past_key_value=None, use_cache=False):
90
+ bsz, seq_len, hidden = x.size()
91
+ q = self.q_proj(x).view(bsz, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
92
+ k = self.k_proj(x).view(bsz, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
93
+ v = self.v_proj(x).view(bsz, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
94
+
95
+ max_pos = int(position_ids.max().item()) + 1
96
+ cos, sin = rotary_emb(q, seq_len=max_pos)
97
+ q, k = apply_rotary_pos_emb(q, k, cos, sin, position_ids)
98
+
99
+ if past_key_value is not None:
100
+ prev_k, prev_v = past_key_value
101
+ k = torch.cat([prev_k, k], dim=2)
102
+ v = torch.cat([prev_v, v], dim=2)
103
+
104
+ new_kv = (k, v) if use_cache else None
105
+ is_causal = attn_mask is None and seq_len > 1
106
+ y = F.scaled_dot_product_attention(
107
+ q, k, v,
108
+ attn_mask=attn_mask,
109
+ dropout_p=self.attn_dropout if self.training else 0.0,
110
+ is_causal=is_causal,
111
+ )
112
+ y = y.transpose(1, 2).contiguous().view(bsz, seq_len, hidden)
113
+ return self.resid_dropout(self.out_proj(y)), new_kv
114
+
115
+
116
+ class SwiGLUMLP(nn.Module):
117
+ def __init__(self, config: ModelConfig):
118
+ super().__init__()
119
+ self.w1 = nn.Linear(config.hidden_size, config.intermediate_size, bias=False)
120
+ self.w2 = nn.Linear(config.hidden_size, config.intermediate_size, bias=False)
121
+ self.w3 = nn.Linear(config.intermediate_size, config.hidden_size, bias=False)
122
+ self.dropout = nn.Dropout(config.resid_dropout)
123
+
124
+ def forward(self, x):
125
+ return self.dropout(self.w3(F.silu(self.w1(x)) * self.w2(x)))
126
+
127
+
128
+ class TransformerBlock(nn.Module):
129
+ def __init__(self, config: ModelConfig):
130
+ super().__init__()
131
+ self.attn_norm = RMSNorm(config.hidden_size, eps=config.norm_eps)
132
+ self.attn = CausalSelfAttention(config)
133
+ self.mlp_norm = RMSNorm(config.hidden_size, eps=config.norm_eps)
134
+ self.mlp = SwiGLUMLP(config)
135
+
136
+ def forward(self, x, rotary_emb, position_ids, attn_mask=None, past_key_value=None, use_cache=False):
137
+ residual = x
138
+ attn_out, new_kv = self.attn(
139
+ self.attn_norm(x), rotary_emb, position_ids,
140
+ attn_mask=attn_mask, past_key_value=past_key_value, use_cache=use_cache,
141
+ )
142
+ x = residual + attn_out
143
+ x = x + self.mlp(self.mlp_norm(x))
144
+ return x, new_kv
145
+
146
+
147
+ class _CoreTransformer(nn.Module):
148
+ """Internal transformer — matches the checkpoint keys exactly."""
149
+ def __init__(self, config: ModelConfig):
150
+ super().__init__()
151
+ self.config = config
152
+ self.embed = nn.Embedding(config.vocab_size, config.hidden_size)
153
+ self.rotary_emb = RotaryEmbedding(
154
+ dim=config.hidden_size // config.num_heads,
155
+ max_position_embeddings=config.max_position_embeddings,
156
+ )
157
+ self.layers = nn.ModuleList([TransformerBlock(config) for _ in range(config.num_layers)])
158
+ self.norm = RMSNorm(config.hidden_size, eps=config.norm_eps)
159
+ self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
160
+ if config.tie_word_embeddings:
161
+ self.lm_head.weight = self.embed.weight
162
+
163
+ def forward(self, input_ids, position_ids=None, past_key_values=None, use_cache=False):
164
+ bsz, seq_len = input_ids.size()
165
+ if position_ids is None:
166
+ past_len = past_key_values[0][0].size(2) if past_key_values else 0
167
+ position_ids = torch.arange(past_len, past_len + seq_len, dtype=torch.long, device=input_ids.device)
168
+ position_ids = position_ids.unsqueeze(0).expand(bsz, -1)
169
+
170
+ x = self.embed(input_ids)
171
+ new_kvs = []
172
+ for idx, block in enumerate(self.layers):
173
+ past_kv = past_key_values[idx] if past_key_values else None
174
+ x, new_kv = block(x, self.rotary_emb, position_ids, past_key_value=past_kv, use_cache=use_cache)
175
+ new_kvs.append(new_kv)
176
+
177
+ x = self.norm(x)
178
+ logits = self.lm_head(x)
179
+ return logits, new_kvs
180
+
181
+
182
+ # ── Public API ────────────────────────────────────────────────────────────────
183
+
184
+ class ClyxModel(nn.Module):
185
+ """
186
+ Clyx causal language model — ready for inference.
187
+
188
+ Usage:
189
+ model = ClyxModel.from_pretrained("syntropic-clx/Clyx_0.2-115.67M-BASE")
190
+ model.eval()
191
+ """
192
+
193
+ def __init__(self, config: ModelConfig):
194
+ super().__init__()
195
+ self.config = config
196
+ self._model = _CoreTransformer(config)
197
+
198
+ # ── from_pretrained ──────────────────────────────────────────────────────
199
+
200
+ @classmethod
201
+ def from_pretrained(cls, repo_id: str, device: Optional[str] = None, token: Optional[str] = None):
202
+ """Load a Clyx model from a HuggingFace Hub repository."""
203
+ try:
204
+ from huggingface_hub import hf_hub_download
205
+ except ImportError:
206
+ raise ImportError("pip install huggingface_hub")
207
+ try:
208
+ from safetensors.torch import load_file
209
+ except ImportError:
210
+ raise ImportError("pip install safetensors")
211
+
212
+ if device is None:
213
+ device = "cuda" if torch.cuda.is_available() else "cpu"
214
+
215
+ # Load config
216
+ cfg_path = hf_hub_download(repo_id=repo_id, filename="config.json", token=token)
217
+ with open(cfg_path) as f:
218
+ raw = json.load(f)
219
+
220
+ # Map HF config keys → ModelConfig fields
221
+ field_map = {
222
+ "hidden_size": "hidden_size",
223
+ "num_hidden_layers": "num_layers",
224
+ "num_attention_heads": "num_heads",
225
+ "max_position_embeddings": "max_position_embeddings",
226
+ "intermediate_size": "intermediate_size",
227
+ "rms_norm_eps": "norm_eps",
228
+ "vocab_size": "vocab_size",
229
+ "tie_word_embeddings": "tie_word_embeddings",
230
+ }
231
+ cfg_kwargs = {}
232
+ for hf_key, our_key in field_map.items():
233
+ if hf_key in raw:
234
+ cfg_kwargs[our_key] = raw[hf_key]
235
+ elif our_key in raw:
236
+ cfg_kwargs[our_key] = raw[our_key]
237
+
238
+ config = ModelConfig(**cfg_kwargs)
239
+ model = cls(config)
240
+
241
+ # Load weights
242
+ weights_path = hf_hub_download(repo_id=repo_id, filename="model.safetensors", token=token)
243
+ state = load_file(weights_path, device=device)
244
+ model._model.load_state_dict(state, strict=True)
245
+ model = model.to(device)
246
+ return model
247
+
248
+ # ── forward ──────────────────────────────────────────────────────────────
249
+
250
+ def forward(self, input_ids, **kwargs):
251
+ return self._model(input_ids, **kwargs)
252
+
253
+ # ── generate ─────────────────────────────────────────────────────────────
254
+
255
+ @torch.no_grad()
256
+ def generate(
257
+ self,
258
+ input_ids: torch.Tensor,
259
+ max_new_tokens: int = 200,
260
+ temperature: float = 1.0,
261
+ top_p: float = 1.0,
262
+ top_k: int = 0,
263
+ repetition_penalty: float = 1.0,
264
+ eos_token_id: Optional[int] = None,
265
+ ) -> torch.Tensor:
266
+ """Auto-regressive generation with temperature / top-p / top-k / repetition penalty."""
267
+ device = next(self.parameters()).device
268
+ ids = input_ids.to(device)
269
+ past_key_values = None
270
+
271
+ for _ in range(max_new_tokens):
272
+ # On the first step pass the full prompt; after that pass only the new token
273
+ cur_input = ids if past_key_values is None else ids[:, -1:]
274
+ logits, past_key_values = self._model(cur_input, past_key_values=past_key_values, use_cache=True)
275
+ next_logits = logits[:, -1, :] # (B, vocab)
276
+
277
+ # Repetition penalty
278
+ if repetition_penalty != 1.0:
279
+ for b in range(ids.size(0)):
280
+ unique = ids[b].unique()
281
+ next_logits[b, unique] = torch.where(
282
+ next_logits[b, unique] < 0,
283
+ next_logits[b, unique] * repetition_penalty,
284
+ next_logits[b, unique] / repetition_penalty,
285
+ )
286
+
287
+ # Temperature
288
+ if temperature != 1.0:
289
+ next_logits = next_logits / temperature
290
+
291
+ # Top-k
292
+ if top_k > 0:
293
+ topk_vals = torch.topk(next_logits, top_k).values[:, -1, None]
294
+ next_logits = next_logits.masked_fill(next_logits < topk_vals, float("-inf"))
295
+
296
+ # Top-p (nucleus)
297
+ if top_p < 1.0:
298
+ sorted_logits, sorted_idx = torch.sort(next_logits, descending=True)
299
+ cum_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1)
300
+ remove = cum_probs - F.softmax(sorted_logits, dim=-1) > top_p
301
+ sorted_logits[remove] = float("-inf")
302
+ next_logits = torch.zeros_like(next_logits).scatter(1, sorted_idx, sorted_logits)
303
+
304
+ probs = F.softmax(next_logits, dim=-1)
305
+ next_token = torch.multinomial(probs, num_samples=1)
306
+ ids = torch.cat([ids, next_token], dim=1)
307
+
308
+ if eos_token_id is not None and (next_token == eos_token_id).all():
309
+ break
310
+
311
+ return ids
@@ -0,0 +1,77 @@
1
+ from typing import List, Optional, Union
2
+
3
+ import torch
4
+
5
+
6
+ SPECIAL_TOKENS = {
7
+ "PAD": "<PAD>", "UNK": "<UNK>", "BOS": "<BOS>", "EOS": "<EOS>",
8
+ "USER": "<USER>", "USER_END": "</USER>", "MODEL": "<MODEL>", "MODEL_END": "</MODEL>",
9
+ "STOP": "<STOP>", "SYSTEM": "<SYSTEM>", "SYSTEM_END": "</SYSTEM>",
10
+ "MEMORY": "<MEMORY>", "MEMORY_END": "</MEMORY>",
11
+ "TOOL_CALL": "<TOOL_CALL>", "TOOL_CALL_END": "</TOOL_CALL>",
12
+ "TOOL_RESULT": "<TOOL_RESULT>", "TOOL_RESULT_END": "</TOOL_RESULT>",
13
+ }
14
+
15
+
16
+ class ClyxTokenizer:
17
+ """
18
+ Clyx tokenizer backed by a ByteLevel BPE tokenizer.json.
19
+
20
+ Usage:
21
+ tok = ClyxTokenizer.from_pretrained("syntropic-clx/Clyx_0.2-115.67M-BASE")
22
+ ids = tok.encode("Hello world", return_tensors="pt")
23
+ text = tok.decode(ids[0])
24
+ """
25
+
26
+ def __init__(self, tokenizer):
27
+ self._tok = tokenizer
28
+ for name, token in SPECIAL_TOKENS.items():
29
+ setattr(self, f"{name.lower()}_id", tokenizer.token_to_id(token))
30
+
31
+ # ── from_pretrained ──────────────────────────────────────────────────────
32
+
33
+ @classmethod
34
+ def from_pretrained(cls, repo_id: str, token: Optional[str] = None):
35
+ """Load tokenizer from a HuggingFace Hub repository."""
36
+ try:
37
+ from huggingface_hub import hf_hub_download
38
+ except ImportError:
39
+ raise ImportError("pip install huggingface_hub")
40
+ try:
41
+ from tokenizers import Tokenizer
42
+ except ImportError:
43
+ raise ImportError("pip install tokenizers")
44
+
45
+ path = hf_hub_download(repo_id=repo_id, filename="tokenizer.json", token=token)
46
+ return cls(Tokenizer.from_file(path))
47
+
48
+ # ── encode / decode ──────────────────────────────────────────────────────
49
+
50
+ def encode(
51
+ self,
52
+ text: str,
53
+ add_special_tokens: bool = False,
54
+ return_tensors: Optional[str] = None,
55
+ ) -> Union[List[int], torch.Tensor]:
56
+ ids = self._tok.encode(text).ids
57
+ if add_special_tokens:
58
+ ids = [self.bos_id] + ids + [self.eos_id]
59
+ if return_tensors == "pt":
60
+ return torch.tensor([ids], dtype=torch.long)
61
+ return ids
62
+
63
+ def decode(
64
+ self,
65
+ ids: Union[List[int], torch.Tensor],
66
+ skip_special_tokens: bool = True,
67
+ ) -> str:
68
+ if isinstance(ids, torch.Tensor):
69
+ ids = ids.detach().cpu().tolist()
70
+ return self._tok.decode([int(x) for x in ids], skip_special_tokens=skip_special_tokens)
71
+
72
+ def __call__(self, text: str, **kwargs):
73
+ return self.encode(text, **kwargs)
74
+
75
+ @property
76
+ def vocab_size(self) -> int:
77
+ return self._tok.get_vocab_size()
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: clyxbox
3
+ Version: 0.2.0
4
+ Summary: Inference library for Clyx language models
5
+ License: Apache-2.0
6
+ Project-URL: Homepage, https://huggingface.co/syntropic-clx
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: torch>=2.0
10
+ Requires-Dist: tokenizers>=0.19
11
+ Requires-Dist: huggingface_hub>=0.23
12
+ Requires-Dist: safetensors>=0.4
@@ -0,0 +1,9 @@
1
+ pyproject.toml
2
+ clyxbox/__init__.py
3
+ clyxbox/model.py
4
+ clyxbox/tokenizer.py
5
+ clyxbox.egg-info/PKG-INFO
6
+ clyxbox.egg-info/SOURCES.txt
7
+ clyxbox.egg-info/dependency_links.txt
8
+ clyxbox.egg-info/requires.txt
9
+ clyxbox.egg-info/top_level.txt
@@ -0,0 +1,4 @@
1
+ torch>=2.0
2
+ tokenizers>=0.19
3
+ huggingface_hub>=0.23
4
+ safetensors>=0.4
@@ -0,0 +1 @@
1
+ clyxbox
@@ -0,0 +1,24 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "clyxbox"
7
+ version = "0.2.0"
8
+ description = "Inference library for Clyx language models"
9
+ readme = "README.md"
10
+ license = { text = "Apache-2.0" }
11
+ requires-python = ">=3.9"
12
+ dependencies = [
13
+ "torch>=2.0",
14
+ "tokenizers>=0.19",
15
+ "huggingface_hub>=0.23",
16
+ "safetensors>=0.4",
17
+ ]
18
+
19
+ [project.urls]
20
+ Homepage = "https://huggingface.co/syntropic-clx"
21
+
22
+ [tool.setuptools.packages.find]
23
+ where = ["."]
24
+ include = ["clyxbox*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+