LisaMegaWatts commited on
Commit
331c4cc
Β·
verified Β·
1 Parent(s): 8da05f0

Upload folder using huggingface_hub

Browse files
Dockerfile ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ # HuggingFace Spaces requires user ID 1000
4
+ RUN useradd -m -u 1000 user
5
+
6
+ WORKDIR /home/user/app
7
+
8
+ # Install dependencies (CPU-only torch to keep image small)
9
+ COPY --chown=user requirements.txt .
10
+ RUN pip install --no-cache-dir -r requirements.txt \
11
+ --extra-index-url https://download.pytorch.org/whl/cpu
12
+
13
+ # Copy application code
14
+ COPY --chown=user symbio_model.py .
15
+ COPY --chown=user vocab_fusion_model.py .
16
+ COPY --chown=user server.py .
17
+
18
+ # Switch to non-root user
19
+ USER user
20
+ ENV HOME=/home/user
21
+
22
+ EXPOSE 7860
23
+
24
+ CMD ["python", "server.py"]
README.md CHANGED
@@ -1,10 +1,38 @@
1
  ---
2
  title: VocabFusion Pythia160m Fused
3
- emoji: πŸ’»
4
- colorFrom: indigo
5
- colorTo: green
6
  sdk: docker
 
7
  pinned: false
 
 
 
 
 
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: VocabFusion Pythia160m Fused
3
+ emoji: 🧬
4
+ colorFrom: purple
5
+ colorTo: blue
6
  sdk: docker
7
+ app_port: 7860
8
  pinned: false
9
+ license: apache-2.0
10
+ tags:
11
+ - symbiogenesis
12
+ - vocab-fusion
13
+ - pythia
14
+ - pca-fusion
15
+ - openai-compatible
16
  ---
17
 
18
+ # VocabFusion-Pythia160m-Fused
19
+
20
+ VocabFusion-SymbioGPT architecture with knowledge from Pythia-160M (12 layers, d=768) projected via PCA decomposition and blended at alpha=0.3 (92% variance retained). 17.5M params.
21
+
22
+ The donor embedding (GPTNeoX, ~50k tokens, 128-dim) bridges to the 320-dim SymbioGPT body (4-organelle decoder) via a junction layer (Linear + LayerNorm). Output is weight-tied to the donor embedding.
23
+
24
+ ## API
25
+
26
+ OpenAI-compatible endpoints:
27
+
28
+ - `GET /` β€” model info
29
+ - `GET /v1/models` β€” list models
30
+ - `POST /v1/chat/completions` β€” generate (supports `stream: true`)
31
+
32
+ ## Links
33
+
34
+ - **Fused model**: [LisaMegaWatts/VocabFusion-Pythia160m-Fused](https://huggingface.co/LisaMegaWatts/VocabFusion-Pythia160m-Fused)
35
+ - **Base VocabFusion**: [LisaMegaWatts/VocabFusion-SymbioGPT-10M](https://huggingface.co/LisaMegaWatts/VocabFusion-SymbioGPT-10M)
36
+ - **Source model**: [EleutherAI/pythia-160m](https://huggingface.co/EleutherAI/pythia-160m)
37
+ - **Base architecture**: [LisaMegaWatts/SymbioGPT-10M](https://huggingface.co/LisaMegaWatts/SymbioGPT-10M)
38
+ - **GitHub**: [DavinciDreams/SymbioGPT](https://github.com/DavinciDreams/SymbioGPT)
__pycache__/symbio_model.cpython-312.pyc ADDED
Binary file (25.2 kB). View file
 
__pycache__/vocab_fusion_model.cpython-312.pyc ADDED
Binary file (3.38 kB). View file
 
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ torch>=2.0.0
2
+ numpy
3
+ huggingface_hub
4
+ tokenizers
5
+ fastapi
6
+ uvicorn
server.py ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """VocabFusion-Pythia160m-Fused β€” OpenAI-compatible inference server.
2
+
3
+ VocabFusion-SymbioGPT architecture (donor embedding + junction + SymbioGPT body)
4
+ with knowledge from Pythia-160M (12 layers, d=768) projected in via PCA
5
+ decomposition and blended at alpha=0.3. 17.5M params.
6
+
7
+ Downloads checkpoint from model repo and tokenizer from VocabFusion-SymbioGPT-10M.
8
+ """
9
+ import json as json_mod
10
+ import math
11
+ import os
12
+ import time
13
+ import uuid
14
+
15
+ import torch
16
+ import torch.nn.functional as F
17
+ import uvicorn
18
+ from fastapi import FastAPI, Request
19
+ from fastapi.middleware.cors import CORSMiddleware
20
+ from fastapi.responses import JSONResponse, StreamingResponse
21
+ from huggingface_hub import hf_hub_download
22
+ from tokenizers import Tokenizer
23
+
24
+ from symbio_model import SymbioConfig
25
+ from vocab_fusion_model import VocabFusionSymbioGPT
26
+
27
+ # ═══════════════════════════════════════════════════════════════════
28
+ # Configuration
29
+ # ═══════════════════════════════════════════════════════════════════
30
+
31
+ MODEL_REPO = os.environ.get("MODEL_REPO", "LisaMegaWatts/VocabFusion-Pythia160m-Fused")
32
+ TOKENIZER_REPO = os.environ.get("TOKENIZER_REPO", "LisaMegaWatts/VocabFusion-SymbioGPT-10M")
33
+ PORT = int(os.environ.get("PORT", "7860"))
34
+ CHECKPOINT_FILE = "vocabfusion_pythia160m_fused.pt"
35
+
36
+ DONOR_VOCAB_SIZE = 50304
37
+ DONOR_DIM = 128
38
+
39
+ MODEL_CONFIG = SymbioConfig(
40
+ d_model=320,
41
+ n_layers=8,
42
+ n_heads=5,
43
+ head_dim=64,
44
+ ffn_mult=4,
45
+ context_length=256,
46
+ vocab_size=2000,
47
+ weight_tying=True,
48
+ organelles=("causal_conv", "monarch", "long_conv", "attention"),
49
+ conv_kernel_size=4,
50
+ n_monarch_heads=1,
51
+ gate_temperature_init=1.0,
52
+ free_energy_beta=0.001,
53
+ )
54
+
55
+ # ═══════════════════════════════════════════════════════════════════
56
+ # Load model and tokenizer
57
+ # ═══════════════════════════════════════════════════════════════════
58
+
59
+ print(f"Downloading checkpoint from {MODEL_REPO} ...")
60
+ ckpt_path = hf_hub_download(repo_id=MODEL_REPO, filename=CHECKPOINT_FILE)
61
+
62
+ print(f"Downloading tokenizer from {TOKENIZER_REPO} ...")
63
+ tok_path = hf_hub_download(repo_id=TOKENIZER_REPO, filename="tokenizer/tokenizer.json")
64
+
65
+ print("Loading tokenizer ...")
66
+ tokenizer = Tokenizer.from_file(tok_path)
67
+ actual_vocab_size = tokenizer.get_vocab_size()
68
+ print(f" Tokenizer vocab_size = {actual_vocab_size}")
69
+ print(f" Donor embedding size = {DONOR_VOCAB_SIZE} (padded)")
70
+
71
+ print("Loading VocabFusion-Pythia160m-Fused model ...")
72
+ model = VocabFusionSymbioGPT(MODEL_CONFIG, donor_vocab_size=DONOR_VOCAB_SIZE, donor_dim=DONOR_DIM)
73
+
74
+ state_dict = torch.load(ckpt_path, map_location="cpu", weights_only=True)
75
+ if "model_state_dict" in state_dict:
76
+ state_dict = state_dict["model_state_dict"]
77
+ elif "state_dict" in state_dict:
78
+ state_dict = state_dict["state_dict"]
79
+ state_dict = {k.replace("_orig_mod.", ""): v for k, v in state_dict.items()}
80
+ model.load_state_dict(state_dict)
81
+
82
+ model.eval()
83
+ n_params = sum(p.numel() for p in model.parameters())
84
+ print(f" Model ready: {n_params/1e6:.1f}M params (Pythia-160M knowledge fused)")
85
+
86
+ # ═══════════════════════════════════════════════════════════════════
87
+ # Generation
88
+ # ═══════════════════════════════════════════════════════════════════
89
+
90
+
91
+ @torch.no_grad()
92
+ def generate_streaming(
93
+ prompt: str,
94
+ max_tokens: int = 200,
95
+ temperature: float = 0.8,
96
+ top_k: int = 40,
97
+ top_p: float = 1.0,
98
+ ):
99
+ """Generator yielding token strings one at a time for real SSE streaming."""
100
+ encoding = tokenizer.encode(prompt)
101
+ tokens = encoding.ids
102
+ if not tokens:
103
+ tokens = [0]
104
+ idx = torch.tensor([tokens], dtype=torch.long)
105
+
106
+ generated_ids = []
107
+ prev_text = ""
108
+
109
+ for _ in range(max_tokens):
110
+ idx_cond = idx[:, -MODEL_CONFIG.context_length:]
111
+ logits = model(idx_cond)
112
+ logits_last = logits[0, -1, :].float()
113
+
114
+ if temperature > 0.01:
115
+ logits_last = logits_last / temperature
116
+ else:
117
+ logits_last = logits_last / 0.01
118
+
119
+ if 0 < top_k < logits_last.size(0):
120
+ threshold = torch.topk(logits_last, top_k).values[-1]
121
+ logits_last[logits_last < threshold] = float("-inf")
122
+
123
+ if top_p < 1.0:
124
+ sorted_logits, sorted_indices = torch.sort(logits_last, descending=True)
125
+ probs_sorted = F.softmax(sorted_logits, dim=-1)
126
+ cumprobs = torch.cumsum(probs_sorted, dim=-1)
127
+ cutoff_mask = cumprobs - probs_sorted > top_p
128
+ sorted_logits[cutoff_mask] = float("-inf")
129
+ logits_last = sorted_logits.scatter(0, sorted_indices, sorted_logits)
130
+
131
+ probs = F.softmax(logits_last, dim=-1)
132
+ next_id = torch.multinomial(probs, 1).item()
133
+
134
+ if next_id == 0:
135
+ break
136
+
137
+ idx = torch.cat([idx, torch.tensor([[next_id]])], dim=1)
138
+
139
+ generated_ids.append(next_id)
140
+ full_text = tokenizer.decode(generated_ids, skip_special_tokens=True)
141
+ delta = full_text[len(prev_text):]
142
+ prev_text = full_text
143
+ if delta:
144
+ yield delta
145
+
146
+
147
+ @torch.no_grad()
148
+ def generate(
149
+ prompt: str,
150
+ max_tokens: int = 200,
151
+ temperature: float = 0.8,
152
+ top_k: int = 40,
153
+ top_p: float = 1.0,
154
+ ) -> str:
155
+ """Generate complete text (non-streaming wrapper)."""
156
+ return "".join(generate_streaming(prompt, max_tokens, temperature, top_k, top_p))
157
+
158
+
159
+ # ═══════════════════════════════════════════════════════════════════
160
+ # FastAPI server
161
+ # ═══════════════════════════════════════════════════════════════════
162
+
163
+ app = FastAPI()
164
+ app.add_middleware(
165
+ CORSMiddleware,
166
+ allow_origins=["*"],
167
+ allow_methods=["*"],
168
+ allow_headers=["*"],
169
+ )
170
+ MODEL_CREATED_AT = int(time.time())
171
+ MODEL_ID = "vocabfusion-pythia160m-fused"
172
+
173
+
174
+ def extract_prompt(messages):
175
+ if not messages:
176
+ return ""
177
+ for msg in reversed(messages):
178
+ if msg.get("role") == "user":
179
+ return msg.get("content", "")
180
+ return messages[-1].get("content", "")
181
+
182
+
183
+ @app.get("/")
184
+ def health():
185
+ return {
186
+ "name": "VocabFusion-Pythia160m-Fused",
187
+ "version": "1.0.0",
188
+ "description": (
189
+ "VocabFusion-SymbioGPT with knowledge from Pythia-160M (12 layers, d=768) "
190
+ "projected via PCA decomposition and blended at alpha=0.3. "
191
+ "The donor embedding (GPTNeoX, ~50k tokens, 128-dim) bridges to "
192
+ "the 320-dim SymbioGPT body via a junction layer."
193
+ ),
194
+ "architecture": "VocabFusion wrapper + 4-organelle decoder "
195
+ "(CausalConv + Monarch + LongConv + Attention) "
196
+ "+ OrganelleGate + SwiGLU + RoPE + RMSNorm",
197
+ "model": {
198
+ "d_model": MODEL_CONFIG.d_model,
199
+ "n_layers": MODEL_CONFIG.n_layers,
200
+ "n_heads": MODEL_CONFIG.n_heads,
201
+ "context_length": MODEL_CONFIG.context_length,
202
+ "donor_vocab_size": DONOR_VOCAB_SIZE,
203
+ "donor_dim": DONOR_DIM,
204
+ "params": f"{n_params/1e6:.1f}M",
205
+ },
206
+ "fusion": {
207
+ "source": "EleutherAI/pythia-160m (12 layers, d=768)",
208
+ "method": "PCA projection + blend",
209
+ "blend_alpha": 0.3,
210
+ "pca_avg_variance": 0.921,
211
+ },
212
+ "organelles": list(MODEL_CONFIG.organelles),
213
+ "endpoints": ["/v1/models", "/v1/chat/completions"],
214
+ "features": ["streaming", "OpenAI-compatible", "top-k", "top-p"],
215
+ "github": "https://github.com/DavinciDreams/SymbioGPT",
216
+ }
217
+
218
+
219
+ @app.get("/v1/models")
220
+ def list_models():
221
+ return {
222
+ "object": "list",
223
+ "data": [{
224
+ "id": MODEL_ID,
225
+ "object": "model",
226
+ "created": MODEL_CREATED_AT,
227
+ "owned_by": "symbiogpt",
228
+ }],
229
+ }
230
+
231
+
232
+ @app.post("/v1/chat/completions")
233
+ async def chat_completions(request: Request):
234
+ try:
235
+ body = await request.json()
236
+ except Exception:
237
+ return JSONResponse(status_code=400, content={
238
+ "error": {"message": "Invalid JSON", "type": "invalid_request_error"}
239
+ })
240
+
241
+ temperature = max(0.01, min(2.0, body.get("temperature", 0.8)))
242
+ max_tokens = max(1, min(MODEL_CONFIG.context_length, body.get("max_tokens", 200)))
243
+ top_k_val = max(0, min(DONOR_VOCAB_SIZE, body.get("top_k", 40)))
244
+ top_p_val = max(0.0, min(1.0, body.get("top_p", 1.0)))
245
+ stream = body.get("stream", False)
246
+ messages = body.get("messages", [])
247
+ prompt_text = extract_prompt(messages)
248
+ prompt_tokens = len(tokenizer.encode(prompt_text).ids) if prompt_text else 0
249
+
250
+ completion_id = f"chatcmpl-{uuid.uuid4()}"
251
+ created = int(time.time())
252
+
253
+ if stream:
254
+ def sse_stream():
255
+ initial = {
256
+ "id": completion_id,
257
+ "object": "chat.completion.chunk",
258
+ "created": created,
259
+ "model": MODEL_ID,
260
+ "choices": [{"index": 0, "delta": {"role": "assistant", "content": ""}, "finish_reason": None}],
261
+ }
262
+ yield f"data: {json_mod.dumps(initial)}\n\n"
263
+
264
+ token_count = 0
265
+ for token_str in generate_streaming(
266
+ prompt_text, max_tokens=max_tokens, temperature=temperature,
267
+ top_k=top_k_val, top_p=top_p_val,
268
+ ):
269
+ token_count += 1
270
+ chunk = {
271
+ "id": completion_id,
272
+ "object": "chat.completion.chunk",
273
+ "created": created,
274
+ "model": MODEL_ID,
275
+ "choices": [{"index": 0, "delta": {"content": token_str}, "finish_reason": None}],
276
+ }
277
+ yield f"data: {json_mod.dumps(chunk)}\n\n"
278
+
279
+ finish = {
280
+ "id": completion_id,
281
+ "object": "chat.completion.chunk",
282
+ "created": created,
283
+ "model": MODEL_ID,
284
+ "choices": [{"index": 0, "delta": {}, "finish_reason": "length" if token_count >= max_tokens else "stop"}],
285
+ "usage": {
286
+ "prompt_tokens": prompt_tokens,
287
+ "completion_tokens": token_count,
288
+ "total_tokens": prompt_tokens + token_count,
289
+ },
290
+ }
291
+ yield f"data: {json_mod.dumps(finish)}\n\n"
292
+ yield "data: [DONE]\n\n"
293
+
294
+ return StreamingResponse(sse_stream(), media_type="text/event-stream",
295
+ headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"})
296
+ else:
297
+ text = generate(prompt_text, max_tokens=max_tokens, temperature=temperature,
298
+ top_k=top_k_val, top_p=top_p_val)
299
+ completion_tokens = len(tokenizer.encode(text).ids)
300
+
301
+ return {
302
+ "id": completion_id,
303
+ "object": "chat.completion",
304
+ "created": created,
305
+ "model": MODEL_ID,
306
+ "choices": [{
307
+ "index": 0,
308
+ "message": {"role": "assistant", "content": text},
309
+ "finish_reason": "length",
310
+ }],
311
+ "usage": {
312
+ "prompt_tokens": prompt_tokens,
313
+ "completion_tokens": completion_tokens,
314
+ "total_tokens": prompt_tokens + completion_tokens,
315
+ },
316
+ "system_fingerprint": "vocabfusion-pythia160m-fused-v1",
317
+ }
318
+
319
+
320
+ if __name__ == "__main__":
321
+ print(f"\nVocabFusion-Pythia160m-Fused server starting on 0.0.0.0:{PORT} ...")
322
+ uvicorn.run(app, host="0.0.0.0", port=PORT)
symbio_model.py ADDED
@@ -0,0 +1,355 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """SymbioGPT β€” Multi-organelle GPT with learned per-channel gating.
2
+
3
+ 4 organelles: CausalConv + Monarch + LongConv + CausalSelfAttention
4
+ fused via OrganelleGate with learnable temperature.
5
+
6
+ Architecture: RoPE, RMSNorm, SwiGLU, SkipGate, weight-tied output.
7
+ """
8
+ import math
9
+ from dataclasses import dataclass
10
+ from typing import List, Optional, Tuple
11
+
12
+ import torch
13
+ import torch.nn as nn
14
+ import torch.nn.functional as F
15
+
16
+
17
+ # ═══════════════════════════════════════════════════════════════════
18
+ # Configuration
19
+ # ═══════════════════════════════════════════════════════════════════
20
+
21
+
22
+ @dataclass
23
+ class SymbioConfig:
24
+ d_model: int = 320
25
+ n_layers: int = 8
26
+ n_heads: int = 5
27
+ head_dim: int = 64
28
+ ffn_mult: int = 4
29
+ dropout: float = 0.0
30
+ context_length: int = 256
31
+ vocab_size: int = 2000
32
+ weight_tying: bool = True
33
+ organelles: Tuple[str, ...] = ("causal_conv", "monarch", "long_conv", "attention")
34
+ conv_kernel_size: int = 4
35
+ n_monarch_heads: int = 1
36
+ gate_temperature_init: float = 1.0
37
+ free_energy_beta: float = 0.001
38
+ per_layer_organelles: Optional[List[Tuple[str, ...]]] = None
39
+
40
+ @property
41
+ def p(self) -> int:
42
+ return int(math.isqrt(self.context_length))
43
+
44
+ @property
45
+ def n_organelles(self) -> int:
46
+ return len(self.organelles)
47
+
48
+
49
+ # ═══════════════════════════════════════════════════════════════════
50
+ # Building blocks
51
+ # ═══════════════════════════════════════════════════════════════════
52
+
53
+
54
+ class RMSNorm(nn.Module):
55
+ def __init__(self, dim: int, eps: float = 1e-6):
56
+ super().__init__()
57
+ self.weight = nn.Parameter(torch.ones(dim))
58
+ self.eps = eps
59
+
60
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
61
+ rms = torch.sqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
62
+ return x / rms * self.weight
63
+
64
+
65
+ class RotaryEmbedding(nn.Module):
66
+ def __init__(self, dim: int, max_seq_len: int = 2048):
67
+ super().__init__()
68
+ freqs = 1.0 / (10000.0 ** (torch.arange(0, dim, 2).float() / dim))
69
+ positions = torch.arange(max_seq_len).float()
70
+ angles = torch.outer(positions, freqs)
71
+ self.register_buffer("cos_cache", angles.cos())
72
+ self.register_buffer("sin_cache", angles.sin())
73
+
74
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
75
+ seq_len = x.size(2)
76
+ half = x.size(-1) // 2
77
+ x1, x2 = x[..., :half], x[..., half:]
78
+ cos = self.cos_cache[:seq_len, :half].unsqueeze(0).unsqueeze(0)
79
+ sin = self.sin_cache[:seq_len, :half].unsqueeze(0).unsqueeze(0)
80
+ return torch.cat([x1 * cos - x2 * sin, x1 * sin + x2 * cos], dim=-1)
81
+
82
+
83
+ class SwiGLU(nn.Module):
84
+ def __init__(self, d_model: int, ffn_mult: int = 4):
85
+ super().__init__()
86
+ raw_hidden = 2 * d_model * ffn_mult // 3
87
+ hidden_dim = max(64, (raw_hidden // 64) * 64)
88
+ self.w1 = nn.Linear(d_model, hidden_dim, bias=False)
89
+ self.v = nn.Linear(d_model, hidden_dim, bias=False)
90
+ self.w2 = nn.Linear(hidden_dim, d_model, bias=False)
91
+
92
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
93
+ return self.w2(F.silu(self.w1(x)) * self.v(x))
94
+
95
+
96
+ class CausalSelfAttention(nn.Module):
97
+ def __init__(self, d_model: int, n_heads: int, head_dim: int, dropout: float = 0.0):
98
+ super().__init__()
99
+ self.n_heads = n_heads
100
+ self.head_dim = head_dim
101
+ total_dim = n_heads * head_dim
102
+ self.wq = nn.Linear(d_model, total_dim, bias=False)
103
+ self.wk = nn.Linear(d_model, total_dim, bias=False)
104
+ self.wv = nn.Linear(d_model, total_dim, bias=False)
105
+ self.wo = nn.Linear(total_dim, d_model, bias=False)
106
+ self.attn_dropout = nn.Dropout(dropout) if dropout > 0.0 else nn.Identity()
107
+
108
+ def forward(self, x, rope, mask=None):
109
+ B, T, D = x.shape
110
+ H, HD = self.n_heads, self.head_dim
111
+ q = self.wq(x).view(B, T, H, HD).transpose(1, 2)
112
+ k = self.wk(x).view(B, T, H, HD).transpose(1, 2)
113
+ v = self.wv(x).view(B, T, H, HD).transpose(1, 2)
114
+ q = rope(q)
115
+ k = rope(k)
116
+ scale = 1.0 / math.sqrt(HD)
117
+ attn = torch.matmul(q, k.transpose(-2, -1)) * scale
118
+ if mask is not None:
119
+ attn = attn + mask
120
+ attn = F.softmax(attn, dim=-1)
121
+ attn = self.attn_dropout(attn)
122
+ out = torch.matmul(attn, v)
123
+ out = out.transpose(1, 2).contiguous().view(B, T, H * HD)
124
+ return self.wo(out)
125
+
126
+
127
+ # ════════════════════���══════════════════════════════════════════════
128
+ # Organelles
129
+ # ═══════════════════════════════════════════════════════════════════
130
+
131
+
132
+ class CausalDepthwiseConv1d(nn.Module):
133
+ def __init__(self, channels: int, kernel_size: int = 4):
134
+ super().__init__()
135
+ self.channels = channels
136
+ self.kernel_size = kernel_size
137
+ self.weight = nn.Parameter(torch.empty(channels, 1, kernel_size))
138
+ nn.init.normal_(self.weight, std=math.sqrt(1.0 / kernel_size))
139
+
140
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
141
+ B, T, D = x.shape
142
+ x_t = x.transpose(1, 2)
143
+ x_padded = F.pad(x_t, (self.kernel_size - 1, 0))
144
+ out = F.conv1d(x_padded, self.weight, groups=D)
145
+ return out.transpose(1, 2)
146
+
147
+
148
+ class MonarchMatrix(nn.Module):
149
+ def __init__(self, seq_len: int):
150
+ super().__init__()
151
+ p = int(math.isqrt(seq_len))
152
+ self.seq_len = seq_len
153
+ self.p = p
154
+ scale = math.sqrt(2.0 / (p + p))
155
+ self.L1 = nn.Parameter(torch.randn(p, p, p) * scale)
156
+ self.L2 = nn.Parameter(torch.randn(p, p, p) * scale)
157
+
158
+ @staticmethod
159
+ def _julia_batched_mul(A, B):
160
+ return torch.bmm(A.permute(2, 0, 1), B.permute(2, 0, 1)).permute(1, 2, 0)
161
+
162
+ def realize(self):
163
+ p = self.p
164
+ T = self.seq_len
165
+ I_T = torch.eye(T, device=self.L1.device, dtype=self.L1.dtype)
166
+ x = I_T.reshape(p, p, T)
167
+ x = x.permute(0, 2, 1)
168
+ x = self._julia_batched_mul(self.L2, x)
169
+ x = x.permute(0, 2, 1)
170
+ x = x.permute(1, 0, 2)
171
+ x = x.permute(0, 2, 1)
172
+ x = self._julia_batched_mul(self.L1, x)
173
+ x = x.permute(0, 2, 1)
174
+ x = x.permute(1, 0, 2)
175
+ return x.reshape(T, T)
176
+
177
+ def forward(self, x, causal_mask=None):
178
+ B, T, D_head = x.shape
179
+ M = self.realize()[:T, :T]
180
+ if causal_mask is not None:
181
+ M = M * causal_mask[:T, :T]
182
+ x_flat = x.permute(1, 0, 2).reshape(T, B * D_head)
183
+ y_flat = M @ x_flat
184
+ return y_flat.reshape(T, B, D_head).permute(1, 0, 2)
185
+
186
+
187
+ class LongConv(nn.Module):
188
+ def __init__(self, channels: int, seq_len: int):
189
+ super().__init__()
190
+ self.channels = channels
191
+ self.seq_len = seq_len
192
+ self.kernel = nn.Parameter(torch.empty(channels, 1, seq_len))
193
+ self._init_weights()
194
+
195
+ def _init_weights(self):
196
+ scale = math.sqrt(1.0 / self.seq_len)
197
+ nn.init.normal_(self.kernel, mean=0.0, std=scale)
198
+ with torch.no_grad():
199
+ decay = torch.exp(-0.1 * torch.arange(self.seq_len, dtype=torch.float32))
200
+ self.kernel.mul_(decay.unsqueeze(0).unsqueeze(0))
201
+
202
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
203
+ B, T, D = x.shape
204
+ K = self.seq_len
205
+ x_t = x.transpose(1, 2)
206
+ x_padded = F.pad(x_t, (K - 1, 0))
207
+ out = F.conv1d(x_padded, self.kernel, groups=D)
208
+ return out.transpose(1, 2)
209
+
210
+
211
+ # ═══════════════════════════════════════════════════════════════════
212
+ # Gate and mixer
213
+ # ═══════════════════════════════════════════════════════════════════
214
+
215
+
216
+ class OrganelleGate(nn.Module):
217
+ def __init__(self, dim: int, n_organelles: int, temperature_init: float = 1.0):
218
+ super().__init__()
219
+ self.n_organelles = n_organelles
220
+ self.logits = nn.Parameter(torch.zeros(n_organelles, dim))
221
+ self.temperature = nn.Parameter(torch.tensor([temperature_init]))
222
+
223
+ def forward(self, organelle_outputs, organelle_mask=None):
224
+ logits = self.logits
225
+ if organelle_mask is not None:
226
+ mask_additive = torch.zeros_like(logits)
227
+ for i in range(self.n_organelles):
228
+ if not organelle_mask[i]:
229
+ mask_additive[i, :] = -1e10
230
+ logits = logits + mask_additive
231
+ tau = self.temperature.clamp(min=0.01)
232
+ weights = F.softmax(logits / tau, dim=0)
233
+ out = torch.zeros_like(organelle_outputs[0])
234
+ for i in range(self.n_organelles):
235
+ w = weights[i].unsqueeze(0).unsqueeze(0)
236
+ out = out + w * organelle_outputs[i]
237
+ return out
238
+
239
+
240
+ class SkipGate(nn.Module):
241
+ def __init__(self):
242
+ super().__init__()
243
+ self.scale = nn.Parameter(torch.ones(1))
244
+
245
+ def forward(self, x):
246
+ return self.scale * x
247
+
248
+
249
+ class SymbioSequenceMixer(nn.Module):
250
+ def __init__(self, config: SymbioConfig):
251
+ super().__init__()
252
+ self.config = config
253
+ d = config.d_model
254
+ T = config.context_length
255
+ self.organelle_names = list(config.organelles)
256
+ self.organelle_modules = nn.ModuleDict()
257
+ for name in self.organelle_names:
258
+ if name == "causal_conv":
259
+ self.organelle_modules[name] = CausalDepthwiseConv1d(d, config.conv_kernel_size)
260
+ elif name == "monarch":
261
+ self.organelle_modules[name] = nn.ModuleList(
262
+ [MonarchMatrix(T) for _ in range(config.n_monarch_heads)]
263
+ )
264
+ elif name == "long_conv":
265
+ self.organelle_modules[name] = LongConv(d, T)
266
+ elif name == "attention":
267
+ self.organelle_modules[name] = CausalSelfAttention(
268
+ d, config.n_heads, config.head_dim, config.dropout
269
+ )
270
+ self.gate = OrganelleGate(d, len(self.organelle_names), config.gate_temperature_init)
271
+ if "monarch" in self.organelle_names:
272
+ self.register_buffer("monarch_causal_mask", torch.tril(torch.ones(T, T)))
273
+
274
+ def forward(self, x, rope, attn_mask=None, organelle_mask=None):
275
+ B, T, D = x.shape
276
+ outputs = []
277
+ for name in self.organelle_names:
278
+ if name == "causal_conv":
279
+ out = self.organelle_modules[name](x)
280
+ elif name == "monarch":
281
+ heads = self.organelle_modules[name]
282
+ n_mh = len(heads)
283
+ hd = D // n_mh
284
+ slices = []
285
+ for i, monarch in enumerate(heads):
286
+ x_slice = x[:, :, i * hd : (i + 1) * hd]
287
+ y_slice = monarch(x_slice, self.monarch_causal_mask)
288
+ slices.append(y_slice)
289
+ out = torch.cat(slices, dim=-1)
290
+ elif name == "long_conv":
291
+ out = self.organelle_modules[name](x)
292
+ elif name == "attention":
293
+ out = self.organelle_modules[name](x, rope, attn_mask)
294
+ outputs.append(out)
295
+ return self.gate(tuple(outputs), organelle_mask)
296
+
297
+
298
+ # ═══════════════════════════════════════════════════════════════════
299
+ # SymbioBlock and SymbioGPT
300
+ # ═══════════════════════════════════════════════════════════════════
301
+
302
+
303
+ class SymbioBlock(nn.Module):
304
+ def __init__(self, config: SymbioConfig, layer_organelles=None):
305
+ super().__init__()
306
+ d = config.d_model
307
+ if layer_organelles is not None:
308
+ from dataclasses import replace
309
+ layer_config = replace(config, organelles=layer_organelles)
310
+ else:
311
+ layer_config = config
312
+ self.ln1 = RMSNorm(d)
313
+ self.seq_mixer = SymbioSequenceMixer(layer_config)
314
+ self.skip1 = SkipGate()
315
+ self.ln2 = RMSNorm(d)
316
+ self.ffn = SwiGLU(d, config.ffn_mult)
317
+ self.skip2 = SkipGate()
318
+
319
+ def forward(self, x, rope, attn_mask=None, organelle_mask=None):
320
+ x = x + self.skip1(self.seq_mixer(self.ln1(x), rope, attn_mask, organelle_mask))
321
+ x = x + self.skip2(self.ffn(self.ln2(x)))
322
+ return x
323
+
324
+
325
+ class SymbioGPT(nn.Module):
326
+ def __init__(self, config: SymbioConfig):
327
+ super().__init__()
328
+ self.config = config
329
+ self.tok_emb = nn.Embedding(config.vocab_size, config.d_model)
330
+ self.rope = RotaryEmbedding(config.head_dim, config.context_length)
331
+ blocks = []
332
+ for i in range(config.n_layers):
333
+ layer_org = None
334
+ if config.per_layer_organelles is not None:
335
+ layer_org = config.per_layer_organelles[i]
336
+ blocks.append(SymbioBlock(config, layer_org))
337
+ self.blocks = nn.ModuleList(blocks)
338
+ self.ln_f = RMSNorm(config.d_model)
339
+ if config.weight_tying:
340
+ self.head = None
341
+ else:
342
+ self.head = nn.Linear(config.d_model, config.vocab_size, bias=False)
343
+
344
+ def forward(self, input_ids, organelle_mask=None):
345
+ B, T = input_ids.shape
346
+ x = self.tok_emb(input_ids)
347
+ attn_mask = torch.triu(
348
+ torch.full((T, T), float("-inf"), device=x.device, dtype=x.dtype), diagonal=1
349
+ )
350
+ for block in self.blocks:
351
+ x = block(x, self.rope, attn_mask, organelle_mask)
352
+ x = self.ln_f(x)
353
+ if self.head is not None:
354
+ return self.head(x)
355
+ return F.linear(x, self.tok_emb.weight)
vocab_fusion_model.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """VocabFusion-SymbioGPT β€” SymbioGPT body with donor vocabulary embedding.
2
+
3
+ Takes a large donor vocab (GPTNeoX-style, ~50k tokens) and projects through
4
+ a junction layer (Linear + LayerNorm) into the 320-dim SymbioGPT body.
5
+ Output projects back to donor dim and is weight-tied to the donor embedding.
6
+ """
7
+ import math
8
+
9
+ import torch
10
+ import torch.nn as nn
11
+ import torch.nn.functional as F
12
+
13
+ from symbio_model import (
14
+ SymbioConfig,
15
+ SymbioBlock,
16
+ RotaryEmbedding,
17
+ RMSNorm,
18
+ )
19
+
20
+
21
+ class VocabFusionSymbioGPT(nn.Module):
22
+ def __init__(self, config: SymbioConfig, donor_vocab_size: int = 50304, donor_dim: int = 128):
23
+ super().__init__()
24
+ self.config = config
25
+ self.donor_vocab_size = donor_vocab_size
26
+ self.donor_dim = donor_dim
27
+
28
+ # Donor embedding: large vocab -> donor_dim
29
+ self.donor_embedding = nn.Embedding(donor_vocab_size, donor_dim)
30
+
31
+ # Junction: donor_dim -> d_model (Linear + LayerNorm)
32
+ self.junction = nn.Sequential(
33
+ nn.Linear(donor_dim, config.d_model, bias=False),
34
+ nn.LayerNorm(config.d_model),
35
+ )
36
+
37
+ # Standard SymbioGPT body
38
+ self.rope = RotaryEmbedding(config.head_dim, config.context_length)
39
+ self.blocks = nn.ModuleList(
40
+ [SymbioBlock(config) for _ in range(config.n_layers)]
41
+ )
42
+ self.ln_f = RMSNorm(config.d_model)
43
+
44
+ # Output projection: d_model -> donor_dim (weight-tied with donor_embedding)
45
+ self.output_proj = nn.Linear(config.d_model, donor_dim, bias=False)
46
+
47
+ def forward(self, input_ids, organelle_mask=None):
48
+ B, T = input_ids.shape
49
+ x = self.donor_embedding(input_ids)
50
+ x = self.junction(x)
51
+
52
+ attn_mask = torch.triu(
53
+ torch.full((T, T), float("-inf"), device=x.device, dtype=x.dtype),
54
+ diagonal=1,
55
+ )
56
+ for block in self.blocks:
57
+ x = block(x, self.rope, attn_mask, organelle_mask)
58
+ x = self.ln_f(x)
59
+
60
+ x = self.output_proj(x)
61
+ logits = F.linear(x, self.donor_embedding.weight)
62
+ return logits