ereniko commited on
Commit
ebccfa3
·
verified ·
1 Parent(s): 67578d3

Upload folder using huggingface_hub

Browse files
README.md ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ tags:
3
+ - text-generation
4
+ - from-scratch
5
+ - experimental
6
+ ---
7
+
8
+ # Ivme-Conversate-S-v1-Base
9
+
10
+ Sub-10M parameter language model, trained single-epoch on ~836M tokens across
11
+ 12 diverse sources (web/edu, dialogue, code, math, reasoning, science, etc).
12
+
13
+ Architecture: factorized + untied token embeddings, grouped-query attention,
14
+ DIFF Transformer V2 attention, nGPT hypersphere-normalized residual stream,
15
+ SwiGLU FFN, immediate block-wise weight sharing, learnable meta/register
16
+ tokens, RoPE.
17
+
18
+ - 9,545,840 parameters
19
+ - vocab_size=8000, d_model=256
20
+ - 14 unique layers x 2 share_factor
21
+ = 28 effective depth
22
+
23
+ ## Usage
24
+
25
+ ```python
26
+ from transformers import AutoModelForCausalLM, AutoTokenizer
27
+
28
+ model = AutoModelForCausalLM.from_pretrained(
29
+ "ivmelabs/Ivme-Conversate-S-v1-Base", trust_remote_code=True
30
+ )
31
+ tok = AutoTokenizer.from_pretrained("ivmelabs/Ivme-Conversate-S-v1-Base")
32
+ ```
33
+
34
+ Note: this architecture has no KV-cache -- `forward()` recomputes attention
35
+ over the full sequence each call, so `.generate()` works but is O(n^2) rather
36
+ than the O(n) a cached model gets. Fine for short generations, not tuned for
37
+ long-form serving.
config.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "IvmeConversateSModel"
4
+ ],
5
+ "auto_map": {
6
+ "AutoConfig": "configuration_ivme_s_v1.IvmeConversateSConfig",
7
+ "AutoModelForCausalLM": "modeling_ivme_s_v1.IvmeConversateSModel"
8
+ },
9
+ "d_ff": 512,
10
+ "d_model": 256,
11
+ "dtype": "float32",
12
+ "embed_rank": 48,
13
+ "max_seq_len": 768,
14
+ "model_type": "ivme_conversate_s",
15
+ "n_heads": 8,
16
+ "n_kv_heads": 2,
17
+ "n_meta_tokens": 4,
18
+ "n_unique_layers": 14,
19
+ "share_factor": 2,
20
+ "transformers_version": "5.14.1",
21
+ "vocab_size": 8000
22
+ }
configuration_ivme_s_v1.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Configuration class for Ivme-Conversate-S-v1.
3
+
4
+ Mirrors train_ivme_s_v1.ModelConfig exactly (same field names, same defaults),
5
+ so config.json produced from a real training run's ModelConfig round-trips
6
+ into this class with no field remapping needed.
7
+ """
8
+
9
+ from transformers import PretrainedConfig
10
+
11
+
12
+ class IvmeConversateSConfig(PretrainedConfig):
13
+ model_type = "ivme_conversate_s"
14
+
15
+ def __init__(
16
+ self,
17
+ vocab_size: int = 8000,
18
+ embed_rank: int = 48,
19
+ d_model: int = 256,
20
+ n_unique_layers: int = 14,
21
+ share_factor: int = 2,
22
+ n_heads: int = 8,
23
+ n_kv_heads: int = 2,
24
+ d_ff: int = 512,
25
+ n_meta_tokens: int = 4,
26
+ max_seq_len: int = 768,
27
+ **kwargs,
28
+ ):
29
+ self.vocab_size = vocab_size
30
+ self.embed_rank = embed_rank
31
+ self.d_model = d_model
32
+ self.n_unique_layers = n_unique_layers
33
+ self.share_factor = share_factor
34
+ self.n_heads = n_heads
35
+ self.n_kv_heads = n_kv_heads
36
+ self.d_ff = d_ff
37
+ self.n_meta_tokens = n_meta_tokens
38
+ self.max_seq_len = max_seq_len
39
+ super().__init__(**kwargs)
40
+
41
+ @property
42
+ def n_layers_effective(self):
43
+ return self.n_unique_layers * self.share_factor
generation_config.json ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ {
2
+ "_from_model_config": true,
3
+ "output_attentions": false,
4
+ "output_hidden_states": false,
5
+ "transformers_version": "5.14.1"
6
+ }
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:55a89c28acdd1486f2914bef72b894948b13b4f3c1131fa52046b3c191ace632
3
+ size 38394760
modeling_ivme_s_v1.py ADDED
@@ -0,0 +1,300 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Modeling file for Ivme-Conversate-S-v1.
3
+
4
+ Architecture ported directly from the training script (train_ivme_s_v1.py),
5
+ verified correct there via extensive isolated testing during development:
6
+ - Factorized, untied token embeddings (separate small-rank projections for
7
+ input embedding and output head -- NOT tied, unlike most tiny LMs).
8
+ - GQA (grouped-query attention), 4:1 query:kv head ratio.
9
+ - DIFF attention V2 (per microsoft/unilm Diff-Transformer-V2): Q has 2x
10
+ heads, K/V unchanged, single fused attention call, interleaved head split
11
+ (NOT a half-split -- verified against the reference blog's explicit
12
+ "Wrong Implementation" ablation warning), lambda is a per-token per-head
13
+ sigmoid-projected value.
14
+ - nGPT-style hypersphere normalization: weights renormalized onto the unit
15
+ hypersphere after every optimizer step during training (a training-time
16
+ concern, not present in this inference-only file), EXCLUDING the output
17
+ head and token embedding -- confirmed via a direct overfitting test that
18
+ including them creates a hard, unmovable floor on achievable loss (~2.1
19
+ on a trivially overfittable 8-token batch, vs 0.0005 when excluded).
20
+ - Immediate block-wise weight sharing: `n_unique_layers` distinct blocks,
21
+ each executed `share_factor` times in a row, giving an effective depth of
22
+ n_unique_layers * share_factor at the parameter cost of n_unique_layers.
23
+ - Learnable meta/register tokens prepended to the sequence, dropped before
24
+ the output head.
25
+ - RoPE positional encoding, applied at full head_dim (not split -- DIFF V2
26
+ doesn't split head_dim, unlike V1).
27
+
28
+ FlashAttention-2 (via HF Kernels, pinned specifically because SDPA's
29
+ FLASH_ATTENTION label was found to silently resolve to FA4 on Blackwell-class
30
+ GPUs and regress for this model's shape profile) is used opportunistically
31
+ when available and the GPU meets its Ampere+ compute-capability floor;
32
+ otherwise this falls back to SDPA's default (unrestricted) backend selection,
33
+ which works correctly on any GPU including pre-Ampere hardware, just without
34
+ the fused-kernel speedup.
35
+ """
36
+
37
+ import math
38
+
39
+ import torch
40
+ import torch.nn as nn
41
+ import torch.nn.functional as F
42
+ from transformers import PreTrainedModel
43
+ from transformers.modeling_outputs import CausalLMOutput
44
+
45
+ try:
46
+ from .configuration_ivme_s_v1 import IvmeConversateSConfig
47
+ except ImportError:
48
+ # Fallback for non-package imports (e.g. cloning the repo and running
49
+ # `import modeling_ivme_s_v1` directly rather than through HF's
50
+ # trust_remote_code dynamic-module loader, which resolves the relative
51
+ # import above correctly via its own transformers_modules.* packaging).
52
+ from configuration_ivme_s_v1 import IvmeConversateSConfig
53
+
54
+ try:
55
+ from torch.nn.attention import SDPBackend, sdpa_kernel
56
+ _HAS_SDPA_KERNEL_CONTEXT = True
57
+ except ImportError:
58
+ _HAS_SDPA_KERNEL_CONTEXT = False
59
+
60
+ _HF_FLASH_ATTN2 = None
61
+ _HF_FLASH_ATTN2_IMPORT_ERROR = None
62
+ try:
63
+ from kernels import get_kernel as _get_kernel
64
+ _HF_FLASH_ATTN2 = _get_kernel("kernels-community/flash-attn2", version=2)
65
+ except Exception as _e:
66
+ _HF_FLASH_ATTN2_IMPORT_ERROR = _e
67
+
68
+ _FA2_MIN_COMPUTE_CAPABILITY = (8, 0) # Ampere+
69
+ _fa2_capability_cache = {}
70
+
71
+
72
+ def _cuda_supports_fa2(device):
73
+ key = str(device)
74
+ if key not in _fa2_capability_cache:
75
+ try:
76
+ cap = torch.cuda.get_device_capability(device)
77
+ _fa2_capability_cache[key] = cap >= _FA2_MIN_COMPUTE_CAPABILITY
78
+ except Exception:
79
+ _fa2_capability_cache[key] = False
80
+ return _fa2_capability_cache[key]
81
+
82
+
83
+ # ---------------------------------------------------------------------
84
+ # RoPE
85
+ # ---------------------------------------------------------------------
86
+ def build_rope_cache(dim, max_seq_len, base=10000.0, device="cpu"):
87
+ assert dim % 2 == 0
88
+ inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2, device=device).float() / dim))
89
+ t = torch.arange(max_seq_len, device=device).float()
90
+ freqs = torch.outer(t, inv_freq)
91
+ emb = torch.cat([freqs, freqs], dim=-1)
92
+ return emb.cos(), emb.sin()
93
+
94
+
95
+ def rotate_half(x):
96
+ x1, x2 = x.chunk(2, dim=-1)
97
+ return torch.cat([-x2, x1], dim=-1)
98
+
99
+
100
+ def apply_rope(x, cos, sin):
101
+ T = x.shape[-2]
102
+ # cos/sin cast to match x's dtype at the point of use (not stored that way)
103
+ # -- multiplying a bf16 autocast tensor against permanently-fp32 buffers
104
+ # silently upcasts the RESULT back to fp32 via normal type promotion,
105
+ # which propagates downstream with no error. Confirmed by a real crash
106
+ # when this reached a bf16-only FA2 kernel.
107
+ cos = cos[:T].unsqueeze(0).unsqueeze(0).to(x.dtype)
108
+ sin = sin[:T].unsqueeze(0).unsqueeze(0).to(x.dtype)
109
+ return x * cos + rotate_half(x) * sin
110
+
111
+
112
+ def l2norm(x, dim=-1, eps=1e-6):
113
+ return x / (x.norm(dim=dim, keepdim=True) + eps)
114
+
115
+
116
+ # ---------------------------------------------------------------------
117
+ # Factorized, untied embeddings
118
+ # ---------------------------------------------------------------------
119
+ class FactorizedEmbedding(nn.Module):
120
+ def __init__(self, vocab_size, r, d_model):
121
+ super().__init__()
122
+ self.embed = nn.Embedding(vocab_size, r)
123
+ self.proj = nn.Linear(r, d_model, bias=False)
124
+
125
+ def forward(self, ids):
126
+ return self.proj(self.embed(ids))
127
+
128
+
129
+ class FactorizedHead(nn.Module):
130
+ def __init__(self, vocab_size, r, d_model):
131
+ super().__init__()
132
+ self.proj = nn.Linear(d_model, r, bias=False)
133
+ self.unembed = nn.Linear(r, vocab_size, bias=False)
134
+
135
+ def forward(self, h):
136
+ return self.unembed(self.proj(h))
137
+
138
+
139
+ # ---------------------------------------------------------------------
140
+ # DIFF attention V2 + GQA
141
+ # ---------------------------------------------------------------------
142
+ class DiffGQAAttention(nn.Module):
143
+ def __init__(self, d_model, n_heads, n_kv_heads, layer_idx, n_layers):
144
+ super().__init__()
145
+ assert d_model % n_heads == 0
146
+ assert n_heads % n_kv_heads == 0
147
+ self.n_heads = n_heads
148
+ self.n_kv_heads = n_kv_heads
149
+ self.head_dim = d_model // n_heads
150
+
151
+ self.wq = nn.Linear(d_model, 2 * n_heads * self.head_dim, bias=False)
152
+ self.wk = nn.Linear(d_model, n_kv_heads * self.head_dim, bias=False)
153
+ self.wv = nn.Linear(d_model, n_kv_heads * self.head_dim, bias=False)
154
+ self.wo = nn.Linear(n_heads * self.head_dim, d_model, bias=False)
155
+ self.lam_proj = nn.Linear(d_model, n_heads, bias=True)
156
+
157
+ def forward(self, x, rope_cos, rope_sin):
158
+ B, T, D = x.shape
159
+ q = self.wq(x).view(B, T, 2 * self.n_heads, self.head_dim).transpose(1, 2)
160
+ k = self.wk(x).view(B, T, self.n_kv_heads, self.head_dim).transpose(1, 2)
161
+ v = self.wv(x).view(B, T, self.n_kv_heads, self.head_dim).transpose(1, 2)
162
+
163
+ q = apply_rope(q, rope_cos, rope_sin)
164
+ k = apply_rope(k, rope_cos, rope_sin)
165
+ q, k = l2norm(q), l2norm(k)
166
+
167
+ if x.is_cuda and _HF_FLASH_ATTN2 is not None and _cuda_supports_fa2(x.device):
168
+ target_dtype = torch.bfloat16 if x.dtype != torch.float16 else torch.float16
169
+ qt = q.transpose(1, 2).to(target_dtype)
170
+ kt = k.transpose(1, 2).to(target_dtype)
171
+ vt = v.transpose(1, 2).to(target_dtype)
172
+ attn = _HF_FLASH_ATTN2.flash_attn_func(qt, kt, vt, causal=True)
173
+ attn = attn.transpose(1, 2)
174
+ elif x.is_cuda and _HAS_SDPA_KERNEL_CONTEXT and _cuda_supports_fa2(x.device):
175
+ with sdpa_kernel([SDPBackend.FLASH_ATTENTION, SDPBackend.EFFICIENT_ATTENTION]):
176
+ attn = F.scaled_dot_product_attention(q, k, v, is_causal=True, enable_gqa=True)
177
+ else:
178
+ # Unrestricted SDPA -- correct on any hardware (falls back to the
179
+ # MATH backend where no fused kernel is available, e.g. pre-Ampere
180
+ # GPUs). Confirmed necessary: restricting to fused-only backends
181
+ # on such hardware leaves SDPA with nothing to fall back to and
182
+ # raises "No available kernel."
183
+ attn = F.scaled_dot_product_attention(q, k, v, is_causal=True, enable_gqa=True)
184
+
185
+ attn = attn.transpose(1, 2) # (B, T, 2h, head_dim)
186
+ attn1, attn2 = attn[:, :, 0::2, :], attn[:, :, 1::2, :] # interleaved, not halved
187
+
188
+ lam_val = torch.sigmoid(self.lam_proj(x)).unsqueeze(-1)
189
+ out = attn1 - lam_val * attn2
190
+ out = out.reshape(B, T, self.n_heads * self.head_dim)
191
+ return self.wo(out)
192
+
193
+
194
+ class SwiGLU(nn.Module):
195
+ def __init__(self, d_model, d_ff):
196
+ super().__init__()
197
+ self.w_gate_up = nn.Linear(d_model, 2 * d_ff, bias=False)
198
+ self.w_down = nn.Linear(d_ff, d_model, bias=False)
199
+ self.d_ff = d_ff
200
+
201
+ def forward(self, x):
202
+ gate, up = self.w_gate_up(x).split(self.d_ff, dim=-1)
203
+ return self.w_down(F.silu(gate) * up)
204
+
205
+
206
+ class Block(nn.Module):
207
+ def __init__(self, d_model, n_heads, n_kv_heads, d_ff, layer_idx, n_layers):
208
+ super().__init__()
209
+ self.attn = DiffGQAAttention(d_model, n_heads, n_kv_heads, layer_idx, n_layers)
210
+ self.ffn = SwiGLU(d_model, d_ff)
211
+ self.alpha_attn = nn.Parameter(torch.full((d_model,), 1.0 / math.sqrt(d_model)))
212
+ self.alpha_ffn = nn.Parameter(torch.full((d_model,), 1.0 / math.sqrt(d_model)))
213
+
214
+ def forward(self, x, rope_cos, rope_sin):
215
+ h = self.attn(l2norm(x), rope_cos, rope_sin)
216
+ x = l2norm(x + self.alpha_attn * (l2norm(h) - x))
217
+ h = self.ffn(l2norm(x))
218
+ x = l2norm(x + self.alpha_ffn * (l2norm(h) - x))
219
+ return x
220
+
221
+
222
+ class IvmeConversateSModel(PreTrainedModel):
223
+ """HF-compatible wrapper. Load with:
224
+ AutoModelForCausalLM.from_pretrained(repo_id, trust_remote_code=True)
225
+ """
226
+
227
+ config_class = IvmeConversateSConfig
228
+
229
+ def __init__(self, config: IvmeConversateSConfig):
230
+ super().__init__(config)
231
+ n_eff = config.n_unique_layers * config.share_factor
232
+
233
+ self.tok_embed = FactorizedEmbedding(config.vocab_size, config.embed_rank, config.d_model)
234
+ self.head = FactorizedHead(config.vocab_size, config.embed_rank, config.d_model)
235
+ self.meta_tokens = nn.Parameter(torch.randn(config.n_meta_tokens, config.d_model) * 0.02)
236
+
237
+ self.blocks = nn.ModuleList([
238
+ Block(config.d_model, config.n_heads, config.n_kv_heads, config.d_ff, i, n_eff)
239
+ for i in range(config.n_unique_layers)
240
+ ])
241
+ self.execution_order = [
242
+ b for b in range(config.n_unique_layers) for _ in range(config.share_factor)
243
+ ]
244
+
245
+ head_dim = config.d_model // config.n_heads
246
+ cos, sin = build_rope_cache(head_dim, config.max_seq_len + config.n_meta_tokens)
247
+ # NOTE: persistent=True (not False). HF's from_pretrained() uses a
248
+ # fast/meta-device init path by default that SKIPS real __init__
249
+ # buffer computation for non-persistent buffers -- this is a
250
+ # documented transformers behavior (see huggingface/transformers
251
+ # issue #33326: sinusoidal/positional buffers computed in __init__
252
+ # are "rendered completely ineffective" under this path, while
253
+ # persistent buffers/weights ARE correctly restored from the
254
+ # checkpoint's state_dict). Confirmed by a real bug: with
255
+ # persistent=False, model.from_pretrained(model.save_pretrained(...))
256
+ # produced NaN logits because rope_cos/rope_sin were left as
257
+ # uninitialized memory. persistent=True saves this small deterministic
258
+ # buffer in the checkpoint and lets the normal state_dict-loading path
259
+ # (which works correctly) restore it, sidestepping the meta-device
260
+ # gap entirely.
261
+ self.register_buffer("rope_cos", cos, persistent=True)
262
+ self.register_buffer("rope_sin", sin, persistent=True)
263
+
264
+ self.post_init()
265
+
266
+ def get_input_embeddings(self):
267
+ return self.tok_embed.embed
268
+
269
+ def set_input_embeddings(self, value):
270
+ self.tok_embed.embed = value
271
+
272
+ def can_generate(self):
273
+ # No KV-cache in this architecture -- forward() always recomputes
274
+ # attention over the full sequence. .generate() would technically run
275
+ # (each step re-does the full forward pass) but is O(n^2) rather than
276
+ # the O(n) a cached model gets, so it's slow, not broken. True either
277
+ # way; documented here rather than silently pretending otherwise.
278
+ return True
279
+
280
+ def forward(self, input_ids, labels=None, **kwargs):
281
+ B, T = input_ids.shape
282
+ tok = self.tok_embed(input_ids)
283
+ meta = self.meta_tokens.unsqueeze(0).expand(B, -1, -1)
284
+ x = torch.cat([meta, tok], dim=1)
285
+ x = l2norm(x)
286
+
287
+ for b_idx in self.execution_order:
288
+ x = self.blocks[b_idx](x, self.rope_cos, self.rope_sin)
289
+
290
+ x = x[:, self.config.n_meta_tokens:, :]
291
+ logits = self.head(x)
292
+
293
+ loss = None
294
+ if labels is not None:
295
+ loss = F.cross_entropy(
296
+ logits[:, :-1, :].reshape(-1, self.config.vocab_size),
297
+ labels[:, 1:].reshape(-1),
298
+ )
299
+
300
+ return CausalLMOutput(loss=loss, logits=logits)
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff